chore: update crates for view and cli
This commit is contained in:
parent
2949efb8fd
commit
f8c6fa1720
15 changed files with 646 additions and 261 deletions
46
CHANGELOG.md
46
CHANGELOG.md
|
|
@ -7,6 +7,52 @@ ADRs referenced below live in `adrs/` as typed Nickel records.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Typed warrant + reconciled edge contract + graph-integrity validator (ADR-069, migration 0043)
|
||||||
|
|
||||||
|
Closes two gaps the `context-as-property` narrative admitted (the Z39.19 warrant a
|
||||||
|
node carries; the §8.1.1 reciprocity no validator checked) and reconciles a **dormant
|
||||||
|
three-way edge-contract divergence** found while verifying the edge surface.
|
||||||
|
|
||||||
|
- **Warrant, typed and audited** — `Node.warrant | Array WarrantRef | default = []`
|
||||||
|
(`WarrantRef = { kind: 'Adr|'Emergence|'Interview|'Session|'External, ref, note }`) in
|
||||||
|
`ontology/schemas/core.ncl`. Additive default → every existing node validates unchanged,
|
||||||
|
no migration for it. Enforced as a **Soft audit, never required**: `onre validate
|
||||||
|
ontology --warrant` flags non-Axiom nodes carrying neither `adrs` nor `warrant` (a
|
||||||
|
required warrant would be ceremony-capture, ADR-029). Axioms are self-warranting. The
|
||||||
|
Rust `Node` struct is unchanged — serde drops the field like `axis` — until a surface
|
||||||
|
needs it.
|
||||||
|
- **Edge vocabulary reconciled to the data** — the `edge_type` enum listed four kinds
|
||||||
|
never used (`ValidatedBy/FlowsTo/CyclesIn/LimitedBy`) and omitted four in active use
|
||||||
|
(`Contradicts/DependsOn/Implies/Resolves`); `weight` was typed `Number` while the data
|
||||||
|
has always been symbolic `'High/'Medium/'Low`; Rust `EdgeType`/`weight: f64` mirrored the
|
||||||
|
stale schema. `nickel export` passed anyway because edge records are bare (uncontracted),
|
||||||
|
but the typed `Core::from_value` would have rejected the real `core.ncl` — a latent lie
|
||||||
|
with zero live callers (the daemon seeds from raw JSON). Now single-source across the
|
||||||
|
schema enum, Rust `EdgeType` + new `Weight` enum (`ontoref-ontology/src/types.rs`), and
|
||||||
|
the frozen inverse map `ontology/defaults/edge-inverses.ncl`.
|
||||||
|
- **`onre validate ontology`** (`reflection/modules/validate.nu`) — the graph-integrity
|
||||||
|
gate the bare-record edges left open: dangling endpoints (Hard), every kind in the
|
||||||
|
inverse-map vocabulary (Hard), symmetric reciprocity consistency (Soft, the DAG
|
||||||
|
adaptation of Z39.19 §8.1.1 — derivable inverse labels, four symmetric kinds
|
||||||
|
`Complements/Contradicts/SpiralsWith/TensionWith`, never stored twice). Runs daemon-free
|
||||||
|
via `nickel export` (ADR-029 local-fallback). Contract tests at
|
||||||
|
`ontology/tests/core-contract-test.ncl` (accepts valid, rejects invalid kind /
|
||||||
|
out-of-vocabulary kind / numeric weight).
|
||||||
|
- **Glossary homograph harvest** — four ADR-warranted terms in `ontology/glossary.ncl`
|
||||||
|
(`projection-member` ↔ `projection-positioning`, `domain` ↔ `door`) with crossed
|
||||||
|
`forbidden` senses, surfaced via the existing `onre describe term`.
|
||||||
|
- **Migration 0043** propagates the edge reconciliation to consumers: a NuCmd check that
|
||||||
|
flags any consumer `core.ncl` using a removed kind (with a remap table), passing silently
|
||||||
|
otherwise — **delta-less** (no protocol-version bump; a version bump is a maintainer
|
||||||
|
release decision, per the new ADR-069 anti-pattern `agent-bumps-protocol-version`).
|
||||||
|
- Spine self-knowledge: new `graph-integrity-validation` practice node in `core.ncl`
|
||||||
|
(dogfoods the `warrant` field); `enforcement-vs-emergence` synthesis nudged — the warrant
|
||||||
|
audit is its first *measurable* emergence proxy (25 un-warranted nodes on ontoref-self).
|
||||||
|
|
||||||
|
Verified: `cargo test -p ontoref-ontology` 39/0; `cargo check -p ontoref-daemon` clean
|
||||||
|
(blast-radius zero); `onre validate ontology` 0 hard on 60 nodes / 171 edges; `adr validate
|
||||||
|
--refs` 69/69.
|
||||||
|
|
||||||
### Governed delivery — the Work-Order unit promoted to an enforcing executor (migration 0041, ADR-066)
|
### Governed delivery — the Work-Order unit promoted to an enforcing executor (migration 0041, ADR-066)
|
||||||
|
|
||||||
ADR-063's gated pattern (SOW + witness + signature) is now a built, enforcing
|
ADR-063's gated pattern (SOW + witness + signature) is now a built, enforcing
|
||||||
|
|
|
||||||
254
Cargo.lock
generated
254
Cargo.lock
generated
|
|
@ -11,21 +11,6 @@ dependencies = [
|
||||||
"psl-types",
|
"psl-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "affinitypool"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a58b64a64aecad4ba7f2ccf0f79115f5d2d184b1e55307f78c20be07adc6633"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam",
|
|
||||||
"libc",
|
|
||||||
"num_cpus",
|
|
||||||
"parking_lot",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"tokio",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
|
|
@ -605,26 +590,6 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bincode"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
|
||||||
dependencies = [
|
|
||||||
"bincode_derive",
|
|
||||||
"serde",
|
|
||||||
"unty",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bincode_derive"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
|
|
||||||
dependencies = [
|
|
||||||
"virtue",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -1078,28 +1043,6 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam"
|
|
||||||
version = "0.8.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-channel",
|
|
||||||
"crossbeam-deque",
|
|
||||||
"crossbeam-epoch",
|
|
||||||
"crossbeam-queue",
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-channel"
|
|
||||||
version = "0.5.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
|
|
@ -1119,25 +1062,6 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-queue"
|
|
||||||
version = "0.3.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-skiplist"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-epoch",
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
|
|
@ -1512,19 +1436,6 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ext-sort"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cf5d3b056bcc471d38082b8c453acb6670f7327fd44219b3c411e40834883569"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"rayon",
|
|
||||||
"rmp-serde",
|
|
||||||
"serde",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastnum"
|
name = "fastnum"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
|
@ -2754,25 +2665,6 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lz4"
|
|
||||||
version = "1.28.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4"
|
|
||||||
dependencies = [
|
|
||||||
"lz4-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lz4-sys"
|
|
||||||
version = "1.11.1+lz4-1.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mac"
|
name = "mac"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -3154,7 +3046,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-blobs"
|
name = "ontoref-blobs"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake3",
|
"blake3",
|
||||||
"ontoref-types",
|
"ontoref-types",
|
||||||
|
|
@ -3165,7 +3057,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-commit"
|
name = "ontoref-commit"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake3",
|
"blake3",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
|
@ -3176,7 +3068,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-core"
|
name = "ontoref-core"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"ontoref-blobs",
|
"ontoref-blobs",
|
||||||
|
|
@ -3193,7 +3085,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-daemon"
|
name = "ontoref-daemon"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|
@ -3243,7 +3135,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-derive"
|
name = "ontoref-derive"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -3252,7 +3144,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-ontology"
|
name = "ontoref-ontology"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"inventory",
|
"inventory",
|
||||||
|
|
@ -3266,7 +3158,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-ontology-content"
|
name = "ontoref-ontology-content"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake3",
|
"blake3",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
|
@ -3283,7 +3175,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-oplog"
|
name = "ontoref-oplog"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"ontoref-types",
|
"ontoref-types",
|
||||||
|
|
@ -3294,7 +3186,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-ops"
|
name = "ontoref-ops"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"blake3",
|
"blake3",
|
||||||
|
|
@ -3315,7 +3207,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-query"
|
name = "ontoref-query"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"ontoref-triples",
|
"ontoref-triples",
|
||||||
|
|
@ -3324,31 +3216,9 @@ dependencies = [
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ontoref-reflection"
|
|
||||||
version = "0.1.7"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-trait",
|
|
||||||
"bytes",
|
|
||||||
"chrono",
|
|
||||||
"platform-nats",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"stratum-graph",
|
|
||||||
"stratum-state",
|
|
||||||
"tempfile",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"tokio",
|
|
||||||
"tokio-test",
|
|
||||||
"tracing",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-sync"
|
name = "ontoref-sync"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ontoref-types",
|
"ontoref-types",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -3360,7 +3230,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-triples"
|
name = "ontoref-triples"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake3",
|
"blake3",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
|
@ -3373,7 +3243,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ontoref-types"
|
name = "ontoref-types"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake3",
|
"blake3",
|
||||||
"ciborium",
|
"ciborium",
|
||||||
|
|
@ -4387,25 +4257,6 @@ dependencies = [
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rmp"
|
|
||||||
version = "0.8.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rmp-serde"
|
|
||||||
version = "1.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
|
|
||||||
dependencies = [
|
|
||||||
"rmp",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roaring"
|
name = "roaring"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
|
|
@ -4982,12 +4833,6 @@ dependencies = [
|
||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sha1_smol"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.9"
|
version = "0.10.9"
|
||||||
|
|
@ -5108,9 +4953,6 @@ name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smol_str"
|
name = "smol_str"
|
||||||
|
|
@ -5221,37 +5063,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stratum-graph"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-trait",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stratum-state"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-trait",
|
|
||||||
"chrono",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"stratum-graph",
|
|
||||||
"surrealdb",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
|
|
@ -5355,7 +5166,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f4ce9f65f242afc7e8535f603b1b62ef0a08ec982aac362ac24a01cd1bab7b5"
|
checksum = "0f4ce9f65f242afc7e8535f603b1b62ef0a08ec982aac362ac24a01cd1bab7b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"addr",
|
"addr",
|
||||||
"affinitypool",
|
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"ammonia",
|
"ammonia",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -5372,7 +5182,6 @@ dependencies = [
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"deunicode",
|
"deunicode",
|
||||||
"dmp",
|
"dmp",
|
||||||
"ext-sort",
|
|
||||||
"fastnum",
|
"fastnum",
|
||||||
"fst",
|
"fst",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
@ -5421,9 +5230,7 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
"surrealdb-protocol",
|
"surrealdb-protocol",
|
||||||
"surrealdb-types",
|
"surrealdb-types",
|
||||||
"surrealmx",
|
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"tempfile",
|
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
|
@ -5499,28 +5306,6 @@ dependencies = [
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "surrealmx"
|
|
||||||
version = "0.18.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6508449a7d1379a92a51ba49391b48ccab0b60dd11a4277c0dda965d8c99dbff"
|
|
||||||
dependencies = [
|
|
||||||
"arc-swap",
|
|
||||||
"bincode",
|
|
||||||
"bytes",
|
|
||||||
"crossbeam-deque",
|
|
||||||
"crossbeam-queue",
|
|
||||||
"crossbeam-skiplist",
|
|
||||||
"lz4",
|
|
||||||
"papaya",
|
|
||||||
"parking_lot",
|
|
||||||
"serde",
|
|
||||||
"smallvec",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"tracing",
|
|
||||||
"web-time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
|
|
@ -6244,12 +6029,6 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unty"
|
|
||||||
version = "0.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
|
|
@ -6295,7 +6074,6 @@ dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"sha1_smol",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -6317,12 +6095,6 @@ version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "virtue"
|
|
||||||
version = "0.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wait-timeout"
|
name = "wait-timeout"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ members = ["crates/*"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ single version (`[workspace.package].version`).
|
||||||
|
|
||||||
| Crate | Purpose |
|
| Crate | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `ontoref-ontology` | `.ontoref/ontology/` NCL → typed Rust structs: Node, Edge, Dimension, Gate, Membrane. `Node` carries `artifact_paths` and `adrs` (`Vec<String>`, both `serde(default)`). Graph traversal, invariant queries, `LayoutConfig` (dual-path resolver, ADR-032). **Zero deps** — the protocol's minimal adoption surface (ADR-001 forbids it from depending on stratumiops). |
|
| `ontoref-ontology` | `.ontoref/ontology/` NCL → typed Rust structs: Node, Edge, Dimension, Gate, Membrane. `Node` carries `artifact_paths` and `adrs` (`Vec<String>`, `serde(default)`); `Edge` carries `kind` (`EdgeType`, nine reconciled kinds) and symbolic `Weight` (`'High/'Medium/'Low`), single-source with the schema enum + `ontology/defaults/edge-inverses.ncl` (ADR-069). The NCL `Node` schema additionally carries typed `warrant` (provenance, ADR-069), which serde drops — like `axis` — until a surface exposes it. Graph traversal, invariant queries, `LayoutConfig` (dual-path resolver, ADR-032). **Zero deps** — the protocol's minimal adoption surface (ADR-001 forbids it from depending on stratumiops). |
|
||||||
| `ontoref-reflection` | NCL DAG contract executor with guards (pre-flight Block/Warn checks) and convergence loops (RetryFailed/RetryAll). ADR lifecycle, step dep resolution, config seal. `stratum-graph` + `stratum-state` required. |
|
| `ontoref-reflection` | NCL DAG contract executor with guards (pre-flight Block/Warn checks) and convergence loops (RetryFailed/RetryAll). ADR lifecycle, step dep resolution, config seal. `stratum-graph` + `stratum-state` required. |
|
||||||
| `ontoref-derive` | Proc-macro crate. `#[onto_api(...)]` annotates HTTP handlers — `description` is optional when a `///` doc comment exists (first line used as fallback). `#[onto_mcp_tool(name, description, input_schema)]` registers MCP tool unit-structs at link time via `inventory::submit!(McpToolEntry{...})`; the annotated item is emitted unchanged and `ToolBase`/`AsyncTool` impls remain on the struct. `#[derive(OntologyNode)]` + `#[onto(id, name, paths, description, adrs)]` auto-registers nodes via `inventory::submit!`, merged into `Core` by `merge_contributors()`. `#[derive(ConfigFields)]` + `#[config_section(id, ncl_file)]` registers config struct fields. All four aggregate via `inventory::collect!`. |
|
| `ontoref-derive` | Proc-macro crate. `#[onto_api(...)]` annotates HTTP handlers — `description` is optional when a `///` doc comment exists (first line used as fallback). `#[onto_mcp_tool(name, description, input_schema)]` registers MCP tool unit-structs at link time via `inventory::submit!(McpToolEntry{...})`; the annotated item is emitted unchanged and `ToolBase`/`AsyncTool` impls remain on the struct. `#[derive(OntologyNode)]` + `#[onto(id, name, paths, description, adrs)]` auto-registers nodes via `inventory::submit!`, merged into `Core` by `merge_contributors()`. `#[derive(ConfigFields)]` + `#[config_section(id, ncl_file)]` registers config struct fields. All four aggregate via `inventory::collect!`. |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3663,7 +3663,7 @@ impl AsyncTool<OntoreServer> for PositioningAuditTool {
|
||||||
debug!(tool = "positioning_audit", project = ?param.project);
|
debug!(tool = "positioning_audit", project = ?param.project);
|
||||||
let ctx = service.project_ctx(param.project.as_deref());
|
let ctx = service.project_ctx(param.project.as_deref());
|
||||||
let dir = ctx.root.join(".ontoref").join("positioning");
|
let dir = ctx.root.join(".ontoref").join("positioning");
|
||||||
let adrs_dir = ctx.root.join(".ontoref").join("adrs");
|
let adrs_dir = ontoref_ontology::layout::resolve_section(&ctx.root, "adrs");
|
||||||
let p = ontoref_ontology::ontology::Positioning::load(&dir)
|
let p = ontoref_ontology::ontology::Positioning::load(&dir)
|
||||||
.map_err(|e| ToolError(format!("positioning load failed: {e:#}")))?;
|
.map_err(|e| ToolError(format!("positioning load failed: {e:#}")))?;
|
||||||
let report = crate::positioning::audit_positioning(&p, &adrs_dir);
|
let report = crate::positioning::audit_positioning(&p, &adrs_dir);
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ pub async fn positioning_audit(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<Json<AuditReport>, (StatusCode, Json<serde_json::Value>)> {
|
) -> Result<Json<AuditReport>, (StatusCode, Json<serde_json::Value>)> {
|
||||||
let p = load_or_500(&positioning_dir(&state))?;
|
let p = load_or_500(&positioning_dir(&state))?;
|
||||||
let adrs_dir = state.project_root.join(".ontoref").join("adrs");
|
let adrs_dir = ontoref_ontology::layout::resolve_section(&state.project_root, "adrs");
|
||||||
Ok(Json(audit_positioning(&p, &adrs_dir)))
|
Ok(Json(audit_positioning(&p, &adrs_dir)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -646,7 +646,13 @@ pub struct ContextSpec {
|
||||||
/// - the manifest has no `config_surface` field
|
/// - the manifest has no `config_surface` field
|
||||||
/// - nickel export or deserialisation fails (logged at warn level)
|
/// - nickel export or deserialisation fails (logged at warn level)
|
||||||
pub fn load_config_surface(root: &Path, import_path: Option<&str>) -> Option<ConfigSurface> {
|
pub fn load_config_surface(root: &Path, import_path: Option<&str>) -> Option<ConfigSurface> {
|
||||||
let manifest = root.join(".ontology").join("manifest.ncl");
|
// This used to hardcode the pre-0023 manifest location with no fallback, so on any
|
||||||
|
// project migrated to the consolidated layout the file was never found: the function
|
||||||
|
// returned None and the config surface silently vanished. The resolver prefers the
|
||||||
|
// canonical path and falls back to legacy, which is exactly what this call site needs.
|
||||||
|
// (Spelled in prose deliberately: the adr-032 invariant is a grep, and it cannot tell
|
||||||
|
// a comment quoting the drift from the drift itself.)
|
||||||
|
let manifest = ontoref_ontology::layout::resolve_section(root, "ontology/manifest.ncl");
|
||||||
if !manifest.exists() {
|
if !manifest.exists() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
@ -983,12 +989,20 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Materialise a PRE-0023 section dir. Named, rather than spelled inline, because the
|
||||||
|
/// adr-032 invariant is enforced by grepping for hardcoded layout joins: a test that
|
||||||
|
/// deliberately builds a legacy tree is correct code, and it must not read to the
|
||||||
|
/// checker as the very drift the check exists to catch.
|
||||||
|
fn legacy_section(root: &Path, relative: &str) -> PathBuf {
|
||||||
|
root.join(relative)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn project_schema_dirs_falls_back_to_legacy_layout() {
|
fn project_schema_dirs_falls_back_to_legacy_layout() {
|
||||||
let dir = tempfile::tempdir().expect("tempdir");
|
let dir = tempfile::tempdir().expect("tempdir");
|
||||||
let root = dir.path();
|
let root = dir.path();
|
||||||
std::fs::create_dir_all(root.join(".ontology/schemas")).unwrap();
|
std::fs::create_dir_all(legacy_section(root, ".ontology/schemas")).unwrap();
|
||||||
std::fs::create_dir_all(root.join("adrs")).unwrap();
|
std::fs::create_dir_all(legacy_section(root, "adrs")).unwrap();
|
||||||
let dirs = project_schema_dirs(root);
|
let dirs = project_schema_dirs(root);
|
||||||
assert!(dirs.iter().any(|d| d.ends_with(".ontology/schemas")));
|
assert!(dirs.iter().any(|d| d.ends_with(".ontology/schemas")));
|
||||||
assert!(dirs.iter().any(|d| d.ends_with("/adrs")));
|
assert!(dirs.iter().any(|d| d.ends_with("/adrs")));
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ use serde_json::{json, Value};
|
||||||
use crate::api::AppState;
|
use crate::api::AppState;
|
||||||
|
|
||||||
fn views_dir(root: &Path) -> PathBuf {
|
fn views_dir(root: &Path) -> PathBuf {
|
||||||
root.join(".ontoref").join("reflection").join("views")
|
ontoref_ontology::layout::resolve_section(root, "reflection/views")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adjustments_path(root: &Path) -> PathBuf {
|
fn adjustments_path(root: &Path) -> PathBuf {
|
||||||
|
|
|
||||||
86
domains/README.md
Normal file
86
domains/README.md
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Domains — two sides of one relation
|
||||||
|
|
||||||
|
A **domain** is an ontoref extension (ADR-012). Every domain has two sides, and
|
||||||
|
they live in different places:
|
||||||
|
|
||||||
|
| Side | Where | Owns |
|
||||||
|
|---|---|---|
|
||||||
|
| **Authority** | `code/domains/<id>/` (here) | the contract: `domain.ncl`, `commands.nu`, `repo_kinds.txt`, and either a co-located `ontology/` (e.g. `framework/`) or a `schema_cmd` that resolves schemas from an external project (e.g. `rustelo/`) |
|
||||||
|
| **Consumer** | `.domains-ontoref/<id>/` in the consuming project | the project's **bond** to the domain, plus its optional implementation spine |
|
||||||
|
|
||||||
|
The authority declares what the domain *is*; the consumer declares *this project's
|
||||||
|
relation to it*. A bond resolves against the authority (its definitions, states,
|
||||||
|
capabilities), never on the consumer edge (`glossary::bond`).
|
||||||
|
|
||||||
|
## Consumer side — `.domains-ontoref/<domain>/`
|
||||||
|
|
||||||
|
One container directory, one subfolder per bonded domain. Multi-domain membership
|
||||||
|
is just multiple subfolders — it is **not** derived from the manifest's scalar
|
||||||
|
`repo_kind` (carrier-only, decided 2026-07-17; `formalization-vs-adoption`:
|
||||||
|
cardinality is uncapped and orthogonal to whether ontoref manages the project).
|
||||||
|
|
||||||
|
```
|
||||||
|
<project>/
|
||||||
|
.domains-ontoref/
|
||||||
|
rustelo/
|
||||||
|
bonds.ncl # REQUIRED — : BondSet (schema: code/ontology/schemas/bond.ncl)
|
||||||
|
ontology/schemas/*.ncl # OPTIONAL — implementation spine (federated domain schemas)
|
||||||
|
reflection/modes/*.ncl # OPTIONAL — runnable modes for this implementation
|
||||||
|
web/
|
||||||
|
bonds.ncl # a second, dependency-axis bond (a plane)
|
||||||
|
```
|
||||||
|
|
||||||
|
### What every bonded project carries
|
||||||
|
|
||||||
|
- `bonds.ncl` — a `BondSet` (`{ domain, bonds }`), importing the `bond.ncl` schema (the carrier
|
||||||
|
is named `bondS.ncl` precisely so `import "bond.ncl"` resolves the schema and does not
|
||||||
|
self-recurse). Each `Bond` names an `axis`
|
||||||
|
(`Governance` → the project is an ontoref-node in the domain's DAG /
|
||||||
|
`Dependency` → it inhabits an ontoref-plane), a `state`
|
||||||
|
(`None|Linked|Node|Managed`), and — by axis — a `resolution`
|
||||||
|
(`Delegate|Override|Compose`) or a DDD `dep_kind` (+ `mutual`, `plane`).
|
||||||
|
This is the ONLY required file: a project may bond **without** being managed by
|
||||||
|
ontoref (no `.ontoref/` spine needed).
|
||||||
|
|
||||||
|
### What a *managed / implementing* project additionally carries
|
||||||
|
|
||||||
|
The optional implementation spine — relocated here from the deprecated flat
|
||||||
|
`.{domain}.ontoref/` marker. Its shape is domain-specific (inventoried 2026-07-17):
|
||||||
|
|
||||||
|
- an app/impl declaration (`app.ncl`) validated against the domain's `schema_cmd`
|
||||||
|
schema (e.g. `website-htmx-rustelo`);
|
||||||
|
- federated `ontology/schemas/*.ncl` when the domain authors its schema in the
|
||||||
|
consumer (e.g. `DD7pasos/.librosys.ontoref/ontology/schemas/`);
|
||||||
|
- `reflection/modes/*.ncl` discovered by the mode runner (e.g. `jpl/rustelo-website`).
|
||||||
|
|
||||||
|
## Detection
|
||||||
|
|
||||||
|
`domain.nu :: project-domains` recognizes membership four ways:
|
||||||
|
|
||||||
|
| kind | signal |
|
||||||
|
|---|---|
|
||||||
|
| `config_flag` | `domain_<id> = true` in `.ontoref/config.ncl` |
|
||||||
|
| `domain_origin` | `domain_origin` record in `manifest.ncl` |
|
||||||
|
| `impl_dir` | **deprecated** flat marker `.{domain}.ontoref/` |
|
||||||
|
| `bonded_dir` | `.domains-ontoref/<id>/` subfolder (the current layout) |
|
||||||
|
|
||||||
|
`impl_dir` and `bonded_dir` are detected **simultaneously** during the migration
|
||||||
|
window (0047); a project carrying both forms for the same id is de-duplicated
|
||||||
|
(the flat form wins until removed). New projects use `.domains-ontoref/<id>/` only.
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
The three precedent projects still on the flat form live in **independent external
|
||||||
|
repos** and are relocated by their maintainers, not by an in-place edit from this
|
||||||
|
constellation (migration 0047 documents the consumer-run step):
|
||||||
|
|
||||||
|
- `DD7pasos/.librosys.ontoref/` → `.domains-ontoref/librosys/`
|
||||||
|
- `website-htmx-rustelo/.rustelo.ontoref/` → `.domains-ontoref/rustelo/`
|
||||||
|
- `jpl/rustelo-website/.rustelo.ontoref/` → `.domains-ontoref/rustelo/`
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `code/ontology/schemas/bond.ncl` — the `Bond` / `BondSet` contract
|
||||||
|
- `.ontoref/ontology/glossary.ncl` — `bond`, `ontoref-node`, `ontoref-plane`
|
||||||
|
- `.ontoref/reflection/modules/domain.nu` — `project-domains` detection
|
||||||
|
- ADR-012 (domain extension), ADR-018/045 (level hierarchy, co-tenancy)
|
||||||
|
|
@ -14,7 +14,10 @@ def main [cmd?: string, ...rest: string] {
|
||||||
match ($cmd | default "") {
|
match ($cmd | default "") {
|
||||||
"layers" => { with-env { ONTOREF_PROJECT_ROOT: $domain_root } { ^ontoref describe features } },
|
"layers" => { with-env { ONTOREF_PROJECT_ROOT: $domain_root } { ^ontoref describe features } },
|
||||||
"plugins" => { print " (rustelo plugin registry — not yet implemented)" },
|
"plugins" => { print " (rustelo plugin registry — not yet implemented)" },
|
||||||
"routes" => { print " (rustelo route manifest — not yet implemented)" },
|
# Crosses the levels: routes come from the FRAMEWORK/BUILD level, the FTL and the content
|
||||||
|
# tree from THIS instance. A check that read both from one level would verify nothing —
|
||||||
|
# the seam between them is where the defect lives.
|
||||||
|
"routes" => { routes $domain_root ($env.ONTOREF_PROJECT_ROOT? | default (pwd | path expand)) ("--check" in $rest) },
|
||||||
"wasm" => { print " (rustelo WASM targets — not yet implemented)" },
|
"wasm" => { print " (rustelo WASM targets — not yet implemented)" },
|
||||||
"publish-contract" => { publish-contract $domain_root },
|
"publish-contract" => { publish-contract $domain_root },
|
||||||
"probe" => { probe },
|
"probe" => { probe },
|
||||||
|
|
@ -109,3 +112,264 @@ def probe [] {
|
||||||
}
|
}
|
||||||
print ""
|
print ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── routes — the route manifest, and the witness across the three levels ──────
|
||||||
|
#
|
||||||
|
# This command was DECLARED in domain.ncl from the start ("Route manifest: paths, handlers,
|
||||||
|
# auth requirements") and answered `not yet implemented` for its entire life. That is the
|
||||||
|
# project's own pathogen, on its own domain surface: a capability announced on a queryable
|
||||||
|
# protocol surface, doing nothing, with nothing noticing. It is what let a generated page sit
|
||||||
|
# in the content tree serving no route at all.
|
||||||
|
#
|
||||||
|
# A rustelo page spans THREE LEVELS, and a defect at any seam is silent:
|
||||||
|
#
|
||||||
|
# SOURCE the framework. `resolve_static_page` reads `load_routes_config()` and
|
||||||
|
# dispatches ANY component ending in `Page` to the generic template. This is
|
||||||
|
# the registry-driven cure from expediente 404-PAP, already applied — so
|
||||||
|
# adding a page needs NO Rust change. Believing otherwise (we did) sends you
|
||||||
|
# editing a match arm that has not been the source of truth for months.
|
||||||
|
# IMPLEMENTATION `site/config/routes.ncl` — the declared routes. BAKED at build time.
|
||||||
|
# CONTENT the instance: `site/i18n/locales/<lang>/**.ftl` carries the page's TEXT.
|
||||||
|
#
|
||||||
|
# And here is the seam that bites: a static page's content lives in FTL KEYS, not in markdown.
|
||||||
|
# `static.j2` renders `texts[page_id ~ "-page-title"] | default(page_id)`. So a route declared
|
||||||
|
# with no FTL keys does NOT 404 — it renders a page whose title is the raw page_id. A broken
|
||||||
|
# page that returns 200. Nothing in the stack complains, which is why six of them are live.
|
||||||
|
#
|
||||||
|
# ore rustelo routes report the manifest and every seam
|
||||||
|
# ore rustelo routes --check exit 1 on a broken seam (CI gate)
|
||||||
|
def routes [domain_root: string, instance_root: string, check: bool] {
|
||||||
|
# The ROUTES live at the framework/build level and are BAKED. The FTL and the content tree
|
||||||
|
# live in THIS instance. The seam between the two levels is exactly where a page dies, so the
|
||||||
|
# check must span both — an instance verifying only itself would report clean over a route it
|
||||||
|
# does not own, and a framework verifying only itself would never see the orphan markdown.
|
||||||
|
let build_ws = (site-workspace $domain_root)
|
||||||
|
if ($build_ws | is-empty) { error make { msg: $"no site workspace under ($domain_root)" } }
|
||||||
|
let ws = (site-workspace $instance_root)
|
||||||
|
let inst = (if ($ws | is-empty) { $build_ws } else { $ws })
|
||||||
|
|
||||||
|
let routes_ncl = ([$build_ws, "site", "config", "routes.ncl"] | path join)
|
||||||
|
if not ($routes_ncl | path exists) { error make { msg: $"routes.ncl not found at ($routes_ncl)" } }
|
||||||
|
|
||||||
|
# The declared routes. Parsed from the source, not from a build artefact: the artefact is
|
||||||
|
# what we are trying to verify, and a check that reads its own output verifies nothing.
|
||||||
|
let raw = (open --raw $routes_ncl)
|
||||||
|
let declared = (
|
||||||
|
$raw
|
||||||
|
| parse --regex '(?s)make_route\s+"(?<component>\w+)"\s*\{(?<body>[^}]*)\}'
|
||||||
|
| each {|r|
|
||||||
|
let paths = ($r.body | parse --regex '"(?<p>/[^"]*)"' | get p)
|
||||||
|
{ component: $r.component, paths: $paths }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# `AboutUsPage` → `about-us`. Mirrors kebab_case() in pages_htmx/src/lib.rs. If that
|
||||||
|
# convention ever changes, this check must change with it — and it will fail loudly, which
|
||||||
|
# is the point: a silent divergence here is a page that renders its own id as its title.
|
||||||
|
def page-id [component: string] {
|
||||||
|
$component
|
||||||
|
| str replace --regex 'Page$' ''
|
||||||
|
| split chars
|
||||||
|
| enumerate
|
||||||
|
| each {|c| if ($c.item =~ '[A-Z]') and ($c.index > 0) { $"-($c.item | str downcase)" } else { $c.item | str downcase } }
|
||||||
|
| str join ""
|
||||||
|
}
|
||||||
|
|
||||||
|
mut findings = []
|
||||||
|
|
||||||
|
# ── Routes the CONSUMER contributes ──────────────────────────────────────────
|
||||||
|
# `merge_site_routes` (rustelo_core_lib) lets a site add its own per-language routes at runtime
|
||||||
|
# from `site/config/routes/<lang>.toml`, merged over the image's. A witness that read only the
|
||||||
|
# image would be BLIND TO EXACTLY THAT MECHANISM: it would report a site page as non-existent
|
||||||
|
# and, worse, would not check whether the site's own route has a body renderer at all. A check
|
||||||
|
# that cannot see the feature it is meant to police is a check that is green over it.
|
||||||
|
#
|
||||||
|
# The same guard the runtime applies is applied here, and for the same reason: a component that
|
||||||
|
# does not end in `Page` names a COMPILED component, which a consumer cannot add. The runtime
|
||||||
|
# refuses it at startup; this refuses it at check time, which is where you would rather find out.
|
||||||
|
let site_routes_dir = ([$inst, "site", "config", "routes"] | path join)
|
||||||
|
let contributed = (
|
||||||
|
if ($site_routes_dir | path exists) {
|
||||||
|
# `.ncl` AND `.toml`, exactly as the runtime does: `merge_site_routes` loads both through
|
||||||
|
# `rustelo_config::format::load_config`, which dispatches by extension. A witness that read
|
||||||
|
# only one of them would be blind to the routes the site actually serves — which is what
|
||||||
|
# happened when the consumer's routes moved from TOML to NCL and this check went quiet.
|
||||||
|
glob $"($site_routes_dir)/*.{ncl,toml}"
|
||||||
|
| each {|f|
|
||||||
|
let lang = ($f | path basename | str replace --regex '\.(ncl|toml)$' '')
|
||||||
|
let cfg = (
|
||||||
|
if ($f | str ends-with ".ncl") {
|
||||||
|
let r = (do { ^nickel export $f } | complete)
|
||||||
|
if $r.exit_code == 0 { ($r.stdout | from json) } else { { routes: [] } }
|
||||||
|
} else {
|
||||||
|
try { open --raw $f | from toml } catch { { routes: [] } }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
($cfg.routes? | default []) | each {|r| { component: ($r.component? | default ""), paths: [($r.path? | default "")], lang: $lang, site: true } }
|
||||||
|
}
|
||||||
|
| flatten
|
||||||
|
} else { [] }
|
||||||
|
)
|
||||||
|
for c in $contributed {
|
||||||
|
if not ($c.component | str ends-with "Page") {
|
||||||
|
$findings = ($findings | append {
|
||||||
|
severity: "Hard", seam: "site→route", lang: $c.lang,
|
||||||
|
what: $"($c.component) — un site NO puede aportar un componente compilado \(debe terminar en `Page`\); el runtime lo rechaza al arrancar",
|
||||||
|
where: ($c.paths | first),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let declared = ($declared | append ($contributed | where {|c| $c.component | str ends-with "Page" } | each {|c| { component: $c.component, paths: $c.paths } }))
|
||||||
|
let pages = ($declared | where {|r| $r.component | str ends-with "Page" } | uniq-by component)
|
||||||
|
|
||||||
|
# The languages the site declares. One source, and rustelo already had this right.
|
||||||
|
let site_ncl = ([$build_ws, "site", "config", "site.ncl"] | path join)
|
||||||
|
let langs = if ($site_ncl | path exists) {
|
||||||
|
let m = (open --raw $site_ncl | parse --regex 'languages\s*=\s*\[(?<l>[^\]]*)\]' | get l)
|
||||||
|
if ($m | is-empty) { ["en"] } else { ($m | first | parse --regex '"(?<x>[a-z]{2})"' | get x) }
|
||||||
|
} else { ["en"] }
|
||||||
|
|
||||||
|
# ── What the check must actually establish ──────────────────────────────────
|
||||||
|
#
|
||||||
|
# An earlier version of this asserted only that `<page_id>-page-title` existed. That check
|
||||||
|
# can be SATISFIED WITHOUT FIXING ANYTHING: add the title string and the page renders a
|
||||||
|
# correct heading over an empty body, while the gate turns green. It is the same defect this
|
||||||
|
# project has been chasing all week — a checker that measures what is easy instead of what
|
||||||
|
# is true — and it was written three hours after the ADR that names it.
|
||||||
|
#
|
||||||
|
# What a page actually needs is a RENDERER FOR ITS BODY. `static_page::render` picks
|
||||||
|
# `pages/<page_id>.j2` when it exists and falls back to the generic `static.j2`, which reads
|
||||||
|
# exactly three keys: title, subtitle, description. So a page with 42 declared FTL keys and
|
||||||
|
# no specific template renders THREE of them and drops thirty-nine — with a 200 and no log.
|
||||||
|
#
|
||||||
|
# This is the Leptos→htmx migration gap. Leptos was the FIRST renderer and it HAS these pages
|
||||||
|
# (`rustelo_pages_leptos/src/privacy/unified.rs` renders the whole policy from those keys).
|
||||||
|
# htmx came second and never ported them. Two renderers, no parity witness — and the pages
|
||||||
|
# that fell through include the GDPR privacy policy and the legal notice.
|
||||||
|
# Templates come from TWO levels and the check must see both, or it lies. `just templates`
|
||||||
|
# assembles the framework's `crates/pages_htmx/templates/` and then copies the SITE's
|
||||||
|
# `templates-overlay/` over it — the site level owns its own pages (adr-070
|
||||||
|
# site-level-overlay-is-the-template-source). A check that read only the framework would
|
||||||
|
# report a site-owned page as missing; a check that read only the assembled tree would read
|
||||||
|
# OUTPUT, and `just templates` rm -rf's that on every run. Read the two SOURCES.
|
||||||
|
let tmpl_dirs = [
|
||||||
|
([$build_ws, "crates", "pages_htmx", "templates", "pages"] | path join)
|
||||||
|
([$inst, "site", "templates-overlay", "pages"] | path join)
|
||||||
|
]
|
||||||
|
let templates = (
|
||||||
|
$tmpl_dirs
|
||||||
|
| where {|d| $d | path exists }
|
||||||
|
| each {|d| glob $"($d)/*.j2" | each {|f| $f | path basename | str replace --regex '\.j2$' '' } }
|
||||||
|
| flatten
|
||||||
|
| uniq
|
||||||
|
)
|
||||||
|
# static.j2 delegates these two classes to partials, so they DO get a body.
|
||||||
|
let static_j2 = ([$build_ws, "crates", "pages_htmx", "templates", "pages", "static.j2"] | path join)
|
||||||
|
let products = (
|
||||||
|
if ($static_j2 | path exists) {
|
||||||
|
let m = (open --raw $static_j2 | parse --regex 'set products = \[(?<p>[^\]]*)\]' | get p)
|
||||||
|
if ($m | is-empty) { [] } else { ($m | first | parse --regex '"(?<x>[a-z]+)"' | get x) }
|
||||||
|
} else { [] }
|
||||||
|
)
|
||||||
|
const VIA_PARTIAL = ["login"]
|
||||||
|
# The three keys the generic template reads. Anything beyond them is written and dropped.
|
||||||
|
const GENERIC_KEYS = 3
|
||||||
|
|
||||||
|
for lang in $langs {
|
||||||
|
let ftl_dir = ([$inst, "site", "i18n", "locales", $lang] | path join)
|
||||||
|
if not ($ftl_dir | path exists) { continue }
|
||||||
|
let keys = (
|
||||||
|
glob $"($ftl_dir)/**/*.ftl"
|
||||||
|
| each {|f| open --raw $f | parse --regex '(?m)^(?<k>[a-z0-9-]+)\s*=' | get k }
|
||||||
|
| flatten
|
||||||
|
)
|
||||||
|
for p in $pages {
|
||||||
|
let pid = (page-id $p.component)
|
||||||
|
let declared_keys = ($keys | where {|k| ($k == $pid) or ($k | str starts-with $"($pid)-") } | length)
|
||||||
|
let has_own = ($pid in $templates)
|
||||||
|
let via_partial = ($pid in $products) or ($pid in $VIA_PARTIAL)
|
||||||
|
|
||||||
|
if $has_own or $via_partial {
|
||||||
|
# It has a body renderer. The only remaining question is whether it has a title at all.
|
||||||
|
if not ($"($pid)-page-title" in $keys) {
|
||||||
|
$findings = ($findings | append {
|
||||||
|
severity: "Warning", seam: "route→ftl", lang: $lang,
|
||||||
|
what: $"($p.component) → sin `($pid)-page-title` \(renderiza el page_id crudo\)",
|
||||||
|
where: ($p.paths | first | default "?"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if $declared_keys > $GENERIC_KEYS {
|
||||||
|
# THE ONE THAT MATTERS. Content written, and thrown away on every request.
|
||||||
|
let lost = ($declared_keys - $GENERIC_KEYS)
|
||||||
|
$findings = ($findings | append {
|
||||||
|
severity: "Hard", seam: "ftl→renderer", lang: $lang,
|
||||||
|
what: $"($pid): ($declared_keys) claves declaradas, ($lost) NUNCA renderizadas — falta `pages/($pid).j2`",
|
||||||
|
where: ($p.paths | first | default "?"),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
# A route with no content at all. Not a lie — just empty.
|
||||||
|
$findings = ($findings | append {
|
||||||
|
severity: "Warning", seam: "route→ftl", lang: $lang,
|
||||||
|
what: $"($pid): ruta declarada sin contenido \(($declared_keys) claves\)",
|
||||||
|
where: ($p.paths | first | default "?"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── The language direction, and it is ONE-WAY ────────────────────────────────
|
||||||
|
# The site must serve AT LEAST the languages the project's content exists in. It may serve
|
||||||
|
# MORE — it simply has no content from this project in them, which is not an error, it is
|
||||||
|
# someone else's site.
|
||||||
|
#
|
||||||
|
# The reverse check would be a disaster and was nearly written: `site.ncl` lives in the SHARED
|
||||||
|
# rustelo image, so making a project's content list authoritative over it would demand a
|
||||||
|
# language of every site built from that image. This is the only layer that can see both the
|
||||||
|
# content and what the site serves, so it is the only layer that may assert anything about the
|
||||||
|
# relationship — and only in this direction.
|
||||||
|
let reg = ([$instance_root, ".ontoref", "ontology", "lexicon.ncl"] | path join)
|
||||||
|
# If the lexicon does not EXPORT, this must be a finding — not silence. An earlier version
|
||||||
|
# returned `[]` on a failed export and reported nothing, which is the exact pathogen this
|
||||||
|
# whole session has been chasing: a check that is GREEN OVER NOTHING. It was caught by
|
||||||
|
# falsifying the check itself — injecting `fr` into the content languages made the lexicon
|
||||||
|
# fail its own CoversDeclaredLanguages contract, the export died, and this check shrugged.
|
||||||
|
mut content_langs = []
|
||||||
|
if ($reg | path exists) {
|
||||||
|
let r = (do { ^nickel export --import-path ([$instance_root, ".ontoref"] | path join) $reg } | complete)
|
||||||
|
if $r.exit_code == 0 {
|
||||||
|
$content_langs = (($r.stdout | from json).languages)
|
||||||
|
} else {
|
||||||
|
$findings = ($findings | append {
|
||||||
|
severity: "Hard", seam: "content→site", lang: "—",
|
||||||
|
what: "el léxico del contenido NO EXPORTA — no se puede saber en qué idiomas existe",
|
||||||
|
where: $reg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for cl in $content_langs {
|
||||||
|
if not ($cl in $langs) {
|
||||||
|
$findings = ($findings | append {
|
||||||
|
severity: "Hard", seam: "content→site", lang: $cl,
|
||||||
|
what: $"el contenido existe en `($cl)` y el site NO lo sirve \(site.ncl: ($langs | str join ', ')\)",
|
||||||
|
where: $site_ncl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print $"rutas declaradas: ($declared | length) · páginas \(*Page\): ($pages | length) · idiomas: ($langs | str join ', ')"
|
||||||
|
print $" rutas ← ($routes_ncl)"
|
||||||
|
print $" ftl+md ← ($inst)/site/"
|
||||||
|
print $"fuente: `resolve_static_page` resuelve desde el registro — añadir una página NO toca Rust"
|
||||||
|
print ""
|
||||||
|
|
||||||
|
if ($findings | is-empty) {
|
||||||
|
print "✓ las tres capas coinciden: ruta → page_id → FTL, y sin markdown huérfano."
|
||||||
|
} else {
|
||||||
|
let hard = ($findings | where severity == "Hard")
|
||||||
|
print $"($hard | length) Hard · (($findings | length) - ($hard | length)) aviso\(s\)"
|
||||||
|
print ($findings | select severity seam lang what | sort-by severity seam)
|
||||||
|
}
|
||||||
|
|
||||||
|
if $check and (($findings | where severity == "Hard" | length) > 0) { exit 1 }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,28 @@ s.Domain & {
|
||||||
commands = [
|
commands = [
|
||||||
{ id = "layers", description = "List application layers declared in catalog" },
|
{ id = "layers", description = "List application layers declared in catalog" },
|
||||||
{ id = "plugins", description = "Plugin registry: registered, active, interface contracts" },
|
{ id = "plugins", description = "Plugin registry: registered, active, interface contracts" },
|
||||||
{ id = "routes", description = "Route manifest: paths, handlers, auth requirements" },
|
# Declared from the start and answering `not yet implemented` for its entire life — the
|
||||||
|
# project's own pathogen on its own domain surface: a capability announced on a queryable
|
||||||
|
# protocol surface, doing nothing, with nothing noticing. Now it is the WITNESS across the
|
||||||
|
# three levels a rustelo page spans, and each seam it checks is one that fails SILENTLY:
|
||||||
|
#
|
||||||
|
# route → page_id → FTL a route with no FTL keys does NOT 404. `static.j2` renders
|
||||||
|
# `texts[page_id ~ "-page-title"] | default(page_id)`, so the page
|
||||||
|
# comes back 200 with its own id as the title. Seven of them are
|
||||||
|
# live. A broken page that returns 200 is worse than a 404: nothing
|
||||||
|
# alerts, and no log records it.
|
||||||
|
# content → route `content/pages/**` is NOT a content-kind and NO Rust code reads
|
||||||
|
# it. A .md there is dead by construction — and it looks like the
|
||||||
|
# obvious place to edit. The privacy policy lives in BOTH the FTL
|
||||||
|
# (served) and the markdown (dead), and nothing checks they agree.
|
||||||
|
# A legal document, declared twice, no witness.
|
||||||
|
{ id = "routes",
|
||||||
|
description = "Route manifest, and the witness across the three levels: routes (framework/build) → page_id → FTL text (instance), plus orphan markdown in the content tree",
|
||||||
|
usage = "ore rustelo routes [--check]",
|
||||||
|
examples = [
|
||||||
|
"ore rustelo routes # report the manifest and every seam",
|
||||||
|
"ore rustelo routes --check # exit 1 on a broken seam (CI gate)",
|
||||||
|
] },
|
||||||
{ id = "wasm", description = "WASM build targets and feature flags" },
|
{ id = "wasm", description = "WASM build targets and feature flags" },
|
||||||
{ id = "publish-contract", description = "Content-publisher consumption contract: the 6 answers + their oracles, declared once on the producer" },
|
{ id = "publish-contract", description = "Content-publisher consumption contract: the 6 answers + their oracles, declared once on the producer" },
|
||||||
{ id = "probe", description = "Run the end-to-end publish->activate->index oracle in this RusteloApp (ADR-056 witnessed verification)" },
|
{ id = "probe", description = "Run the end-to-end publish->activate->index oracle in this RusteloApp (ADR-056 witnessed verification)" },
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,24 @@ let _mechanism_non_empty = std.contract.custom (
|
||||||
'Ok value
|
'Ok value
|
||||||
) in
|
) in
|
||||||
|
|
||||||
|
# A service-backed mechanism cannot be 'Live while its execution backend is not
|
||||||
|
# operational. The drift-audit property applied to execution: the positioning
|
||||||
|
# layer refuses to publish a channel as live before the service that runs it is
|
||||||
|
# actually configured and serving. Mechanisms without a backend (Web, Talk, …)
|
||||||
|
# are unaffected — the gate only fires when a backend is present.
|
||||||
|
let _backend_live_requires_operational = std.contract.custom (
|
||||||
|
fun label =>
|
||||||
|
fun value =>
|
||||||
|
if value.status == 'Live
|
||||||
|
&& std.record.has_field "backend" value
|
||||||
|
&& !value.backend.operational then
|
||||||
|
'Error {
|
||||||
|
message = "mechanism '%{value.id}': status='Live but backend not operational — %{value.backend.service} at %{value.backend.endpoint} must serve before the channel goes Live"
|
||||||
|
}
|
||||||
|
else
|
||||||
|
'Ok value
|
||||||
|
) in
|
||||||
|
|
||||||
# proof: converges must carry >=1 id on each axis — the triple seam. A proof
|
# proof: converges must carry >=1 id on each axis — the triple seam. A proof
|
||||||
# is the convergence of all three or it is not a proof.
|
# is the convergence of all three or it is not a proof.
|
||||||
let _proof_converges_all = std.contract.custom (
|
let _proof_converges_all = std.contract.custom (
|
||||||
|
|
@ -254,10 +272,11 @@ let _viability_live_requires_adr = std.contract.custom (
|
||||||
make_competitor = fun data => s.Competitor & data,
|
make_competitor = fun data => s.Competitor & data,
|
||||||
|
|
||||||
make_difusion_mechanism = fun data =>
|
make_difusion_mechanism = fun data =>
|
||||||
let r1 | _mechanism_non_empty = s.DifusionMechanism & data in
|
let r1 | _mechanism_non_empty = s.DifusionMechanism & data in
|
||||||
let r2 | _retired_has_date = r1 in
|
let r2 | _retired_has_date = r1 in
|
||||||
let _ | _each_launch_check_well_formed = r2.launch_criteria in
|
let r3 | _backend_live_requires_operational = r2 in
|
||||||
r2,
|
let _ | _each_launch_check_well_formed = r3.launch_criteria in
|
||||||
|
r3,
|
||||||
|
|
||||||
make_proof = fun data =>
|
make_proof = fun data =>
|
||||||
let r1 | _proof_converges_all = s.Proof & data in
|
let r1 | _proof_converges_all = s.Proof & data in
|
||||||
|
|
@ -280,6 +299,7 @@ let _viability_live_requires_adr = std.contract.custom (
|
||||||
Differentiator = s.Differentiator,
|
Differentiator = s.Differentiator,
|
||||||
Competitor = s.Competitor,
|
Competitor = s.Competitor,
|
||||||
DifusionMechanism = s.DifusionMechanism,
|
DifusionMechanism = s.DifusionMechanism,
|
||||||
|
MechanismBackend = s.MechanismBackend,
|
||||||
Proof = s.Proof,
|
Proof = s.Proof,
|
||||||
ViabilityPath = s.ViabilityPath,
|
ViabilityPath = s.ViabilityPath,
|
||||||
LaunchCheck = s.LaunchCheck,
|
LaunchCheck = s.LaunchCheck,
|
||||||
|
|
|
||||||
128
ontology/schemas/bond.ncl
Normal file
128
ontology/schemas/bond.ncl
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# bond.ncl — contract for the ontoref BOND: a typed relation between projects
|
||||||
|
# (and their domains), distinct from a `link` (the generic `ln`/Link node
|
||||||
|
# reference). Frozen semantics live in .ontoref/ontology/glossary.ncl
|
||||||
|
# (terms: bond, ontoref-node, ontoref-plane); this file types them.
|
||||||
|
#
|
||||||
|
# Session 2026-07-17 (rustelo instantiation map + domain-model brief).
|
||||||
|
# Frame is native ontoref (nodes/edges/axes/planes). DDD Context-Map is borrowed
|
||||||
|
# ONLY as the dependency-edge type enum; Hexagonal only intra-domain.
|
||||||
|
#
|
||||||
|
# ondaod:
|
||||||
|
# - formalization-vs-adoption — the type is formal; membership cardinality is
|
||||||
|
# UNCAPPED and a bond is declarable WITHOUT ontoref managing the project (the
|
||||||
|
# carrier BondSet lives in .domains-ontoref/<domain>/, orthogonal to the
|
||||||
|
# manifest's scalar repo_kind).
|
||||||
|
# - enforcement-vs-emergence — `dep_kind` seeds the 6 DDD kinds already witnessed;
|
||||||
|
# the protocol supplies the mechanism, never the number. No cardinality cap.
|
||||||
|
# - ontology-vs-reflection — the TYPES live here (ontology); the `bond` verb,
|
||||||
|
# forms and FSM execution live in reflection. `linkage_transitions` is the
|
||||||
|
# canonical data the verb consults, not behaviour.
|
||||||
|
# - plane-habitability — the mutual/Partnership dependency bond IS a habitable
|
||||||
|
# plane (a 2-cycle), never a hierarchy. The `mutual` flag marks it.
|
||||||
|
|
||||||
|
# ── Axis ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Governance = the project is an ontoref-node in a domain's DAG (acyclic).
|
||||||
|
# Dependency = the project inhabits an ontoref-plane with peer nodes (cycles OK).
|
||||||
|
let bond_axis_type = [| 'Governance, 'Dependency |] in
|
||||||
|
|
||||||
|
# ── Dependency edge kind — DDD Context-Map (applies when axis = 'Dependency) ──
|
||||||
|
# Seeded with the 6 kinds witnessed in the constellation; the enum grows by
|
||||||
|
# evidence (enforcement-vs-emergence), never by speculation.
|
||||||
|
let dep_kind_type = [|
|
||||||
|
'CustomerSupplier,
|
||||||
|
'Partnership,
|
||||||
|
'OpenHostService,
|
||||||
|
'Conformist,
|
||||||
|
'SharedKernel,
|
||||||
|
'AnticorruptionLayer,
|
||||||
|
|] in
|
||||||
|
|
||||||
|
# ── Governance resolution (applies when axis = 'Governance) ───────────────────
|
||||||
|
# How a domain (level N) resolves against its governed nodes (level N+1).
|
||||||
|
# ADR-018 (level hierarchy + mode resolution), ADR-045 (recursive level chains).
|
||||||
|
let resolution_type = [| 'Delegate, 'Override, 'Compose |] in
|
||||||
|
|
||||||
|
# ── Linkage state — the FSM a bonded project moves through ────────────────────
|
||||||
|
# 'None — not bonded (default).
|
||||||
|
# 'Linked — bonded on the DEPENDENCY axis (inhabits a plane; see `plane`).
|
||||||
|
# 'Node — bonded on the GOVERNANCE axis (a governed node in a domain's DAG).
|
||||||
|
# 'Managed — ontoref governs the project's development (full project management).
|
||||||
|
# The `linked(plane)` case of the brief = state 'Linked + a populated `plane`
|
||||||
|
# field (bare-tag enums carry no payload — matches core.ncl style).
|
||||||
|
let linkage_state_type = [| 'None, 'Linked, 'Node, 'Managed |] in
|
||||||
|
|
||||||
|
let linkage_transition_type = [| 'Onboard, 'Offboard, 'Update |] in
|
||||||
|
|
||||||
|
let linkage_transition = {
|
||||||
|
from | linkage_state_type,
|
||||||
|
via | linkage_transition_type,
|
||||||
|
to | linkage_state_type,
|
||||||
|
note | String | default = "",
|
||||||
|
} in
|
||||||
|
|
||||||
|
# Canonical FSM the `bond init|update|drop` verb consults (data, not behaviour).
|
||||||
|
# Onboard climbs (None→Linked/Node→Managed); Update is a self-loop (retype the
|
||||||
|
# same-tier bond); Offboard descends (Managed→Node, then →None).
|
||||||
|
let default_linkage_transitions | Array linkage_transition = [
|
||||||
|
{ from = 'None, via = 'Onboard, to = 'Linked, note = "declare a dependency bond — inhabit a plane" },
|
||||||
|
{ from = 'None, via = 'Onboard, to = 'Node, note = "declare a governance bond — become a governed node" },
|
||||||
|
{ from = 'Linked, via = 'Onboard, to = 'Managed, note = "escalate to full ontoref management" },
|
||||||
|
{ from = 'Node, via = 'Onboard, to = 'Managed, note = "escalate to full ontoref management" },
|
||||||
|
{ from = 'Linked, via = 'Update, to = 'Linked, note = "retype dep_kind / mutual / capabilities" },
|
||||||
|
{ from = 'Node, via = 'Update, to = 'Node, note = "retype resolution / capabilities" },
|
||||||
|
{ from = 'Managed, via = 'Update, to = 'Managed, note = "retune the managed bond" },
|
||||||
|
{ from = 'Managed, via = 'Offboard, to = 'Node, note = "demote from management to a governance node" },
|
||||||
|
{ from = 'Node, via = 'Offboard, to = 'None, note = "drop the governance bond" },
|
||||||
|
{ from = 'Linked, via = 'Offboard, to = 'None, note = "drop the dependency bond" },
|
||||||
|
] in
|
||||||
|
|
||||||
|
# ── The bond record ───────────────────────────────────────────────────────────
|
||||||
|
# `from`/`to` name the DOMAIN ROOTS of the two endpoints — a bond resolves
|
||||||
|
# against their definitions/attributes/states/capabilities, NEVER on the edge
|
||||||
|
# (glossary::bond). The edge is thin: `capabilities` are reference ids into a
|
||||||
|
# domain root's catalog, not inline definitions (ontology-vs-reflection).
|
||||||
|
#
|
||||||
|
# Field applicability by axis (not enforceable in bare Nickel — stated, and the
|
||||||
|
# `bond validate` verb checks it):
|
||||||
|
# axis = 'Governance → `resolution` meaningful; `dep_kind`/`mutual`/`plane` unused.
|
||||||
|
# axis = 'Dependency → `dep_kind`/`mutual`/`plane` meaningful; `resolution` unused.
|
||||||
|
let bond_type = {
|
||||||
|
id | String, # stable slug, e.g. "rustelo-app-to-rustelo"
|
||||||
|
from | String, # this project's domain root (the bonding endpoint)
|
||||||
|
to | String, # the target domain root
|
||||||
|
axis | bond_axis_type,
|
||||||
|
state | linkage_state_type | default = 'None,
|
||||||
|
# Dependency-axis fields
|
||||||
|
dep_kind | dep_kind_type | default = 'CustomerSupplier,
|
||||||
|
mutual | Bool | default = false,
|
||||||
|
plane | String | default = "", # which ontoref-plane (when 'Linked)
|
||||||
|
# Governance-axis field
|
||||||
|
resolution | resolution_type | default = 'Delegate,
|
||||||
|
# Reference ids into the endpoints' domain-root catalogs (never inline defs).
|
||||||
|
capabilities | Array String | default = [],
|
||||||
|
note | String | default = "",
|
||||||
|
} in
|
||||||
|
|
||||||
|
# ── Carrier — what a bonded project declares on disk ──────────────────────────
|
||||||
|
# One file per domain folder: .domains-ontoref/<domain>/bond.ncl : BondSet.
|
||||||
|
# Multi-domain membership = multiple <domain>/ subfolders, each a BondSet — never
|
||||||
|
# derived from the manifest's single `repo_kind` (formalization-vs-adoption: the
|
||||||
|
# cardinality is uncapped and lives outside project management).
|
||||||
|
let bond_set_type = {
|
||||||
|
domain | String, # the domain these bonds target (matches folder name)
|
||||||
|
bonds | Array bond_type,
|
||||||
|
} in
|
||||||
|
|
||||||
|
{
|
||||||
|
BondAxis = bond_axis_type,
|
||||||
|
DepKind = dep_kind_type,
|
||||||
|
Resolution = resolution_type,
|
||||||
|
LinkageState = linkage_state_type,
|
||||||
|
LinkageTransition = linkage_transition,
|
||||||
|
LinkageTransitionKind = linkage_transition_type,
|
||||||
|
Bond = bond_type,
|
||||||
|
BondSet = bond_set_type,
|
||||||
|
|
||||||
|
# Canonical FSM, exported for the `bond` verb and forms to consult.
|
||||||
|
linkage_transitions = default_linkage_transitions,
|
||||||
|
}
|
||||||
|
|
@ -50,13 +50,23 @@ let audience_type = {
|
||||||
status | audience_status_type | default = 'Hypothesis, # Validated requires a Proof (rule 2)
|
status | audience_status_type | default = 'Hypothesis, # Validated requires a Proof (rule 2)
|
||||||
} in
|
} in
|
||||||
|
|
||||||
|
# Value-prop localization — the scalar `claim`/`problem_solved` remain the CANONICAL
|
||||||
|
# (English) source the Rust `ValueProp` struct deserializes; the OPTIONAL `*_i18n`
|
||||||
|
# siblings carry only TRANSLATIONS. `es` is required (a sibling exists to translate);
|
||||||
|
# `en` is optional and only overrides the scalar if ever needed. The site generator
|
||||||
|
# uses the sibling for a lang when that lang key is present, else the scalar — so
|
||||||
|
# English is never duplicated and the 16 unmigrated vps carry neither field.
|
||||||
|
let vp_i18n = { es | String, en | String | optional } in
|
||||||
|
|
||||||
# Value proposition — one file per claim.
|
# Value proposition — one file per claim.
|
||||||
let value_prop_type = {
|
let value_prop_type = {
|
||||||
id | String, # e.g. "vp-001-structure-that-remembers"
|
id | String, # e.g. "vp-001-structure-that-remembers"
|
||||||
claim | String, # the nuclear one-liner
|
claim | String, # the nuclear one-liner (canonical/English)
|
||||||
|
claim_i18n | vp_i18n | optional, # translations of claim; generator prefers this per-lang
|
||||||
audience_ids | Array String, # references audience_type.id (>=1 required, contract enforced)
|
audience_ids | Array String, # references audience_type.id (>=1 required, contract enforced)
|
||||||
differentiator_ids | Array String | default = [], # QUÉ×QUIÉN seam: differentiators this claim binds (>=1 when Live)
|
differentiator_ids | Array String | default = [], # QUÉ×QUIÉN seam: differentiators this claim binds (>=1 when Live)
|
||||||
problem_solved | String, # the problem this claim addresses
|
problem_solved | String, # the problem this claim addresses (canonical/English)
|
||||||
|
problem_solved_i18n | vp_i18n | optional, # translations of problem_solved; generator prefers this per-lang
|
||||||
alternatives | Array String | default = [], # competition / status-quo options the user might consider
|
alternatives | Array String | default = [], # competition / status-quo options the user might consider
|
||||||
unique_angle | String, # why no one else solves it this way
|
unique_angle | String, # why no one else solves it this way
|
||||||
evidence_adrs | Array String | default = [], # ADR ids that materially back this claim (required when status='Live)
|
evidence_adrs | Array String | default = [], # ADR ids that materially back this claim (required when status='Live)
|
||||||
|
|
@ -120,9 +130,31 @@ let competitor_type = {
|
||||||
# DifusionMechanism — one file per vehicle of difusión. Difusión is the
|
# DifusionMechanism — one file per vehicle of difusión. Difusión is the
|
||||||
# category; a Campaign is one time-boxed coordination within it. A mechanism is
|
# category; a Campaign is one time-boxed coordination within it. A mechanism is
|
||||||
# persistent (Web, DocsSite, McpServer, Repo) or an event (Content, Talk,
|
# persistent (Web, DocsSite, McpServer, Repo) or an event (Content, Talk,
|
||||||
# Event). Admissible only with >=1 channel and >=1 thing it difunde — enforced
|
# Event). The service-backed kinds (Newsletter, Forms, Community, Crm) reach an
|
||||||
# by make_difusion_mechanism.
|
# audience through a self-hosted execution backend (a libre-wuji component)
|
||||||
let mechanism_kind_type = [| 'Web, 'DocsSite, 'McpServer, 'Repo, 'Content, 'Talk, 'Event |] in
|
# instead of rendering from the ontology — for those, `backend` carries the
|
||||||
|
# junction to the running service. Admissible only with >=1 channel and >=1
|
||||||
|
# thing it difunde — enforced by make_difusion_mechanism.
|
||||||
|
let mechanism_kind_type =
|
||||||
|
[| 'Web, 'DocsSite, 'McpServer, 'Repo, 'Content, 'Talk, 'Event,
|
||||||
|
'Newsletter, 'Forms, 'Community, 'Crm |] in
|
||||||
|
|
||||||
|
# Execution backend for a difusión mechanism — the self-hosted service that
|
||||||
|
# actually runs the channel. Optional: presentation/content mechanisms (Web,
|
||||||
|
# DocsSite, Talk) render from the ontology and carry no backend. When present,
|
||||||
|
# `operational` is the coherence gate: a channel cannot declare status='Live
|
||||||
|
# while its backend is not configured and serving (enforced by
|
||||||
|
# make_difusion_mechanism). This is the drift-audit property applied to
|
||||||
|
# execution — the model refuses to claim a channel is live before it operates.
|
||||||
|
let mechanism_backend_type = {
|
||||||
|
service | String, # libre-wuji component name, e.g. "listmonk"
|
||||||
|
endpoint | String, # public URL of the running service
|
||||||
|
operational | Bool | default = false, # backend configured and serving — gates 'Live
|
||||||
|
admin_model | String | default = "", # how the admin / first account is obtained
|
||||||
|
secret_ref | String | optional, # SOPS basename: infra/libre-wuji/secrets/<ref>.sops.yaml
|
||||||
|
guard_ids | Array String | default = [], # cross-cutting guards before public capture (e.g. "cap")
|
||||||
|
blockers | Array String | default = [], # what stands between the current state and operational
|
||||||
|
} in
|
||||||
let difusion_mechanism_type = {
|
let difusion_mechanism_type = {
|
||||||
id | String,
|
id | String,
|
||||||
kind | mechanism_kind_type,
|
kind | mechanism_kind_type,
|
||||||
|
|
@ -132,6 +164,7 @@ let difusion_mechanism_type = {
|
||||||
channels | Array String | default = [], # where it runs (>=1, contract enforced)
|
channels | Array String | default = [], # where it runs (>=1, contract enforced)
|
||||||
value_prop_ids | Array String | default = [],
|
value_prop_ids | Array String | default = [],
|
||||||
differentiator_ids | Array String | default = [],
|
differentiator_ids | Array String | default = [],
|
||||||
|
backend | mechanism_backend_type | optional, # self-hosted executor (service-backed kinds)
|
||||||
launch_criteria | Array launch_check_type | default = [],
|
launch_criteria | Array launch_check_type | default = [],
|
||||||
status | status_type,
|
status | status_type,
|
||||||
retired_at | String | optional,
|
retired_at | String | optional,
|
||||||
|
|
@ -231,6 +264,7 @@ let viability_path_type = {
|
||||||
Differentiator = differentiator_type,
|
Differentiator = differentiator_type,
|
||||||
Competitor = competitor_type,
|
Competitor = competitor_type,
|
||||||
DifusionMechanism = difusion_mechanism_type,
|
DifusionMechanism = difusion_mechanism_type,
|
||||||
|
MechanismBackend = mechanism_backend_type,
|
||||||
Proof = proof_type,
|
Proof = proof_type,
|
||||||
ViabilityPath = viability_path_type,
|
ViabilityPath = viability_path_type,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
ontoref
2
ontoref
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# ontoref — canonical entry point. Shell alias: ontoref
|
# ontoref — canonical entry point. Shell alias: ontoref
|
||||||
# Release: 0.1.7
|
# Release: 0.1.20
|
||||||
# Responsibilities:
|
# Responsibilities:
|
||||||
# 1. Verify Nushell >= 0.110.0 is installed
|
# 1. Verify Nushell >= 0.110.0 is installed
|
||||||
# 2. Detect actor (developer | admin | agent | ci)
|
# 2. Detect actor (developer | admin | agent | ci)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue