chore: clean up for constellation migration

This commit is contained in:
Jesús Pérez 2026-07-10 01:44:59 +01:00
parent c283788d03
commit d7a84651cb
Signed by: jesus
GPG key ID: 9F243E355E0BC939
653 changed files with 35279 additions and 133495 deletions

79
.cargo/config.base.toml Normal file
View file

@ -0,0 +1,79 @@
# Portable cargo configuration — safe to commit.
# Machine-local settings (target-dir, sccache) live in config.toml (gitignored).
# Run `just setup-dev` to generate config.toml for this machine.
[build]
jobs = 4
[profile.dev]
opt-level = 0
debug = true
debug-assertions = true
overflow-checks = true
lto = false
panic = "unwind"
incremental = true
[profile.clippy]
# Lint-only: no debug info, no codegen — clippy only needs MIR/HIR.
# Pre-commit uses this to avoid bloating target/ with DWARF/dSYM artifacts.
inherits = "dev"
debug = 0
incremental = true
[profile.release]
opt-level = 3
debug = false
debug-assertions = false
overflow-checks = false
lto = "thin"
codegen-units = 1
panic = "abort"
incremental = false
strip = false
[profile.test]
opt-level = 1
debug = true
debug-assertions = true
overflow-checks = true
lto = false
incremental = true
[profile.ci-test]
# Pre-commit profile: debug=0 halves binary size; shared by rust-test + docs-links hooks.
inherits = "test"
debug = 0
incremental = false
[profile.ci]
# CI: line-tables-only debug for actionable backtraces on remote failures.
inherits = "test"
debug = "line-tables-only"
incremental = false
[profile.bench]
opt-level = 3
debug = false
debug-assertions = false
overflow-checks = false
lto = "thin"
codegen-units = 1
incremental = false
[term]
color = "auto"
verbose = false
progress.when = "auto"
progress.width = 80
[net]
git-fetch-with-cli = true
offline = false
[alias]
build-all = "build --all-targets"
check-all = "check --all-targets --all-features"
test-all = "test --all-features --workspace"
doc-all = "doc --all-features --no-deps --open"
doc-json = "doc --no-deps -- -Z unstable-options --output-format json"

View file

@ -1,16 +1,13 @@
# Generated by dev-system/ci # Portable cargo configuration — safe to commit.
# Cargo configuration for build and compilation settings # Machine-local settings (target-dir, sccache) live in config.toml (gitignored).
# Run `just setup-dev` to generate config.toml for this machine.
[build] [build]
# Number of parallel jobs for compilation jobs = 4
jobs = 4
target-dir = "/Volumes/Devel/ontoref/target" target-dir = "/Volumes/Devel/ontoref/target"
# rustc-wrapper = "sccache" # install sccache to enable: cargo install sccache
# Code generation backend
# codegen-backend = "llvm"
[profile.dev] [profile.dev]
# Development profile - fast compilation, debug info
opt-level = 0 opt-level = 0
debug = true debug = true
debug-assertions = true debug-assertions = true
@ -20,14 +17,13 @@ panic = "unwind"
incremental = true incremental = true
[profile.clippy] [profile.clippy]
# Lint-only profile: no debug info, no codegen — clippy only needs MIR/HIR. # Lint-only: no debug info, no codegen — clippy only needs MIR/HIR.
# Used by pre-commit to avoid bloating target/debug with DWARF/dSYM artifacts. # Pre-commit uses this to avoid bloating target/ with DWARF/dSYM artifacts.
inherits = "dev" inherits = "dev"
debug = 0 debug = 0
incremental = true incremental = true
[profile.release] [profile.release]
# Release profile - slow compilation, optimized binary
opt-level = 3 opt-level = 3
debug = false debug = false
debug-assertions = false debug-assertions = false
@ -39,7 +35,6 @@ incremental = false
strip = false strip = false
[profile.test] [profile.test]
# Test profile - inherits from dev but can be optimized
opt-level = 1 opt-level = 1
debug = true debug = true
debug-assertions = true debug-assertions = true
@ -48,22 +43,18 @@ lto = false
incremental = true incremental = true
[profile.ci-test] [profile.ci-test]
# Pre-commit profile: no debug info, no incremental. # Pre-commit profile: debug=0 halves binary size; shared by rust-test + docs-links hooks.
# Shared by rust-test and docs-links hooks so both reuse the same .rlib artifacts.
# debug = 0 removes DWARF/dSYM — halves binary size without affecting correctness.
inherits = "test" inherits = "test"
debug = 0 debug = 0
incremental = false incremental = false
[profile.ci] [profile.ci]
# CI pipeline profile: line-tables-only debug for useful backtraces on remote failures. # CI: line-tables-only debug for actionable backtraces on remote failures.
# Distinct from ci-test (debug=0): CI failures must show file:line to be actionable.
inherits = "test" inherits = "test"
debug = "line-tables-only" debug = "line-tables-only"
incremental = false incremental = false
[profile.bench] [profile.bench]
# Benchmark profile - same as release
opt-level = 3 opt-level = 3
debug = false debug = false
debug-assertions = false debug-assertions = false
@ -73,24 +64,23 @@ codegen-units = 1
incremental = false incremental = false
[term] [term]
# Terminal colors
color = "auto" color = "auto"
verbose = false verbose = false
progress.when = "auto" progress.when = "auto"
progress.width = 80 progress.width = 80
[net] [net]
# Network settings
git-fetch-with-cli = true git-fetch-with-cli = true
offline = false offline = false
# Strict version requirements for dependencies
# force-non-semver-pre = true
[alias] [alias]
# Custom cargo commands
build-all = "build --all-targets" build-all = "build --all-targets"
check-all = "check --all-targets --all-features" check-all = "check --all-targets --all-features"
test-all = "test --all-features --workspace" test-all = "test --all-features --workspace"
doc-all = "doc --all-features --no-deps --open" doc-all = "doc --all-features --no-deps --open"
doc-json = "doc --no-deps -- -Z unstable-options --output-format json" doc-json = "doc --no-deps -- -Z unstable-options --output-format json"
[env]
SCCACHE_DIR = "/Volumes/Devel/sccache"
SCCACHE_CACHE_SIZE = "30G"

15
.gitignore vendored
View file

@ -50,6 +50,10 @@ cscope.*
# direnv .envrc files # direnv .envrc files
.envrc .envrc
# Machine-local cargo config — generated by `just setup-dev` from .cargo/config.base.toml
# Contains: target-dir, rustc-wrapper, sccache paths — never commit machine-local paths
.cargo/config.toml
# make-related metadata # make-related metadata
/.make/ /.make/
@ -77,3 +81,14 @@ SBOM.*.json
assets/css/node_modules/ assets/css/node_modules/
assets/css/pnpm-lock.yaml assets/css/pnpm-lock.yaml
crates/ontoref-daemon/public/css/ontoref.css crates/ontoref-daemon/public/css/ontoref.css
# sync-assets manifest — local artefact, not tracked
assets/.sync-manifest.json
# ADR-040: convention content is DERIVED — symlinks to a machine-specific shared
# origin (dev-system) or vendored snapshots. Source of truth is the card.ncl
# `conventions` declaration; `onre conventions sync` re-creates this tree.
.ontoref/conventions/
.coder/
.claude/
.internal.git/

10
.governance/receipts/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
# Routine verification runs are high-frequency, low-value logs — not tracked,
# same as CI's own logs aren't committed to the repo they build.
#
# Named Work Order receipts ARE tracked deliberately: a wo-*.jsonl is a
# milestone claim ("this exact state was audited and accredited"), not routine
# noise. Naming convention is the gate: name a receipt wo-<slug>.jsonl to keep it.
*
!.gitignore
!wo-*.jsonl
!wo-*.jsonl.minisig

View file

@ -0,0 +1,3 @@
{"wo":"wo-adr001-zero-deps-v2","contract":"C1-ontology-crate-zero-stratumiops-deps","phase":"static","exit":0,"verdict":"pass","detail":"","ts":"2026-07-04T13:36:26.003055+01:00"}
{"wo":"wo-adr001-zero-deps-v2","contract":"C1-ontology-crate-zero-stratumiops-deps","phase":"static","exit":0,"verdict":"pass","detail":"","ts":"2026-07-04T13:36:54.644471+01:00"}
{"wo":"wo-adr001-zero-deps-v2","contract":"C2-receipts-gitignore-allowlist","phase":"runtime","exit":0,"verdict":"pass","detail":"","ts":"2026-07-04T13:36:56.989432+01:00"}

View file

@ -0,0 +1,4 @@
untrusted comment: signature from minisign secret key
RUSMeO4Wu/zCY9eYOIsQ3zcXs58e7NztiwpB4yG5qgkMGrkRdmA11FSTqRfVz29ojAkP1NCTT5bkrk8XTuLardDaopVs7EsMag8=
trusted comment: timestamp:1783168801 file:wo-adr001-zero-deps-v2.jsonl hashed
qVuWIFTWblV4H6nlsKuT6l1mk1a78rsXB/2uR4niXusMagfdCHRoyE2ZGcOvC2jgNnR0JL9le5VC46RYF0rbAA==

View file

@ -0,0 +1 @@
{"wo":"wo-adr001-zero-deps","contract":"C1-ontology-crate-zero-stratumiops-deps","phase":"static","exit":0,"verdict":"pass","detail":"","ts":"2026-07-04T02:48:46.226297+01:00"}

View file

@ -0,0 +1,4 @@
untrusted comment: ontoref witness receipt
RUSMeO4Wu/zCY7YvqKOUtHxV1pZkgxYGVa0Ut1TJKo8JV0mObBpy8KgeXgfzj5i4aFkrgBWawTRQQe2PPaf462R6AaMZSBYlvA8=
trusted comment: wo=wo-adr001-zero-deps 1 contract(s) checked at 2026-07-04T02:48:46.226297+01:00
M1gXOqHc0j14IggOlC4GVFyrnsasg0TRAediXLc3LG+FVTEN8yAk8w2MJjztFPO+/sjtYaMnyijhB8lIjIU9AA==

2
.governance/witness.pub Normal file
View file

@ -0,0 +1,2 @@
untrusted comment: minisign public key 63C2FCBB16EE788C
RWSMeO4Wu/zCY7O4EOAXvT4o4LOzqvkzfW+t6ay9VXNxgZnwHdydxnnd

View file

@ -0,0 +1,35 @@
# code/.governance/wo-adr001-zero-deps-v2.ncl — revision of wo-adr001-zero-deps
# per §7.6 (iteration across Work Order instances, never within one run).
# Born from falsación finding F0: v1's C1 check (`! grep …` on a relative
# path) passed VACUOUSLY from any cwd not containing crates/ — grep exit 2
# inverted to 0. This revision hardens the check fail-closed (file must
# exist) and the witness now pins check cwd to the governed repo root.
{
id = "wo-adr001-zero-deps-v2",
objective = "Verify ontoref-ontology declares zero stratumiops dependencies (ADR-001), with a fail-closed check that cannot pass vacuously, and receipts hygiene per the mode's own §7.2 mandate.",
predecessor_wo = "wo-adr001-zero-deps",
existing_mechanisms_searched = [
"v1's own C1 check — exists but vacuous-passes from a wrong cwd; hardened here, not duplicated",
"scripts/witness.nu cwd handling — no prior cwd pinning existed; added to the witness engine (structural), not per-SOW workarounds",
".governance/receipts/.gitignore — already exists from WO#0's intake; C2 verifies its allowlist rather than recreating it",
],
contracts = [
{
id = "C1-ontology-crate-zero-stratumiops-deps",
kind = 'static,
why = "ADR-001: ontoref-ontology must stay buildable with zero stratumiops path deps — it is the protocol's minimal adoption surface. v2: the file's existence is asserted first, so a wrong cwd or a renamed crate fails instead of vacuously passing.",
check = "test -f crates/ontoref-ontology/Cargo.toml && ! grep -n stratum crates/ontoref-ontology/Cargo.toml",
scope = ["crates/ontoref-ontology/Cargo.toml"],
},
{
id = "C2-receipts-gitignore-allowlist",
kind = 'runtime,
why = "§7.2: routine verification runs are untracked logs; only named wo-*.jsonl receipts (and their signatures) are milestone claims worth committing. The allowlist is the gate — its absence would either flood the repo or silently untrack accredited receipts.",
check = "test -f .governance/receipts/.gitignore && grep -Fxq '*' .governance/receipts/.gitignore && grep -Fxq '!wo-*.jsonl' .governance/receipts/.gitignore && grep -Fxq '!wo-*.jsonl.minisig' .governance/receipts/.gitignore",
scope = [".governance/receipts/.gitignore"],
},
],
}

View file

@ -0,0 +1,4 @@
untrusted comment: signature from minisign secret key
RUSMeO4Wu/zCY335OqlgyJFdAZuVZcUvSmhJ8RvLb7i4WnQ3bwdWeSBNw5/n5dewPoQ566Er1dw6f6pIwsHQqVzJBAWGdVEmVgg=
trusted comment: timestamp:1783168424 file:wo-adr001-zero-deps-v2.ncl hashed
Q/6W0rP3SvAg0X8SmhNhdGZ/93wstp9N4ZlHQfwDTkpyzcM/MMWqrie3+DeM7hoqkZlPUNDwbEcbbjiWcIkSBw==

View file

@ -0,0 +1,24 @@
# code/sow/wo-adr001-zero-deps.ncl — first real instance of governed-delivery,
# built to actually test the mode (not a fabricated scenario): ontoref-ontology
# must never depend on stratum-graph/stratum-state/stratum-orchestrator per
# ADR-001 ("the ontology crate is the protocol's minimal adoption surface").
{
id = "wo-adr001-zero-deps",
objective = "Verify ontoref-ontology's Cargo.toml declares zero stratumiops dependencies, per ADR-001.",
existing_mechanisms_searched = [
"grep -rn stratum crates/*/Cargo.toml — no existing automated check found",
"code/.claude/CLAUDE.md's 'Critical constraint from ADR-001' — documented as prose, not enforced by any script or CI step",
"code/domains/framework/ontology/schema.ncl and .ontoref/reflection/schema.ncl — the mode/guard mechanism this WO now uses, not a new one",
],
contracts = [
{
id = "C1-ontology-crate-zero-stratumiops-deps",
kind = 'static,
why = "ADR-001: ontoref-ontology must stay buildable with zero stratumiops path deps — it is the protocol's minimal adoption surface. A dependency here would force every consumer to also vendor stratumiops just to read the ontology.",
check = "! grep -n stratum crates/ontoref-ontology/Cargo.toml",
scope = ["crates/ontoref-ontology/Cargo.toml"],
},
],
}

View file

@ -0,0 +1,4 @@
untrusted comment: signature from minisign secret key
RUSMeO4Wu/zCYwX/djdsdpK+geM3snQ9Ac8lFdXkso5YhevMxKk7D+SfSxQMNyvlv7nqeGptQlFJG1ruxDmPEvSFGqH47/Mumw8=
trusted comment: timestamp:1783166906 file:wo-adr001-zero-deps.ncl hashed
ZblNWxvpWLGiPFxjU3WtX2gp+7G/EguYsU9V7ONvAefNbuAhnkZGratwA/jnEt7lbe9EutJgSgHlMdxl8yurAg==

View file

@ -1,7 +0,0 @@
let s = import "../reflection/schemas/connections" in
{
upstream = [],
downstream = [],
peers = [],
} | s.Connections

View file

@ -1,769 +0,0 @@
let d = import "../ontology/defaults/core.ncl" in
{
nodes = [
# ── Axioms (invariant = true) ─────────────────────────────────────────────
d.make_node {
id = "protocol-not-runtime",
name = "Protocol, Not Runtime",
pole = 'Yang,
level = 'Axiom,
description = "Onref is a protocol specification and tooling layer. It is never a runtime dependency. Projects implement the protocol; onref provides the schemas and modules to do so.",
invariant = true,
},
d.make_node {
id = "self-describing",
name = "Self-Describing",
pole = 'Yang,
level = 'Axiom,
description = "Onref describes itself using its own protocol. The .ontology/, adrs/, and reflection/ directories in this repository are onref consuming ontoref.",
invariant = true,
artifact_paths = [".ontology/core.ncl", ".ontology/state.ncl", "adrs/"],
},
d.make_node {
id = "no-enforcement",
name = "No Enforcement",
pole = 'Yang,
level = 'Axiom,
description = "Onref defines contracts and patterns. There is no enforcement mechanism. Coherence is voluntary and emerges from justified adoption.",
invariant = true,
},
d.make_node {
id = "dag-formalized",
name = "DAG-Formalized Knowledge",
pole = 'Yin,
level = 'Axiom,
description = "All project knowledge — concepts, tensions, decisions, state — is formalized as directed acyclic graphs. This enables transversal queries, impact analysis, and ecosystem-level visibility.",
invariant = true,
artifact_paths = ["ontology/schemas/", "crates/ontoref-ontology/"],
},
# ── Tensions ──────────────────────────────────────────────────────────────
d.make_node {
id = "formalization-vs-adoption",
name = "Formalization vs Adoption Friction",
pole = 'Spiral,
level = 'Tension,
description = "Richer formalization produces better ecosystem visibility but increases the cost of adoption. The balance: schemas are optional layers, not mandatory gates.",
},
d.make_node {
id = "ontology-vs-reflection",
name = "Ontology vs Reflection",
pole = 'Spiral,
level = 'Tension,
description = "Ontology captures what IS (invariants, structure, being). Reflection captures what BECOMES (operations, drift, memory). Both must coexist without one dominating. This tension is onref's core identity.",
},
# ── Practices ─────────────────────────────────────────────────────────────
d.make_node {
id = "adr-lifecycle",
name = "ADR Lifecycle",
pole = 'Yang,
level = 'Practice,
description = "Architectural decisions follow: Proposed → Accepted → Superseded. Superseded ADRs retain constraints for historical reconstruction. Active Hard constraints drive the constraint set. Nodes declare which ADRs validate them via the adrs field — surfaced by describe and the daemon graph UI.",
artifact_paths = [
"adrs/schema.ncl",
"adrs/reflection.ncl",
"adrs/_template.ncl",
"adrs/adr-001-protocol-as-standalone-project.ncl",
"adrs/adr-002-daemon-for-caching-and-notification-barrier.ncl",
"adrs/adr-003-qa-and-knowledge-persistence-as-ncl.ncl",
"adrs/adr-004-ncl-pipe-bootstrap-pattern.ncl",
"adrs/adr-005-unified-auth-session-model.ncl",
"adrs/adr-006-nushell-0111-string-interpolation-compat.ncl",
"adrs/adr-007-api-surface-discoverability-onto-api-proc-macro.ncl",
"adrs/adr-008-ncl-first-config-validation-and-override-layer.ncl",
"adrs/adr-009-manifest-self-interrogation-layer-three-semantic-axes.ncl",
"adrs/adr-010-protocol-migration-system.ncl",
"adrs/adr-011-mode-guards-and-convergence.ncl",
"adrs/adr-012-domain-extension-system.ncl",
"adrs/adr-013-vcs-abstraction-layer.ncl",
"adrs/adr-014-runtime-service-toggles.ncl",
"adrs/adr-015-mcp-tool-inventory-auto-derive.ncl",
"adrs/adr-016-component-lift-out-pattern.ncl",
"adrs/adr-017-registry-credential-vault-model.ncl",
"adrs/adr-018-level-hierarchy-mode-resolution-strategy.ncl",
"adrs/adr-019-per-file-recipient-routing-tenant-isolation.ncl",
"CHANGELOG.md",
],
adrs = ["adr-001", "adr-002", "adr-003", "adr-004", "adr-005", "adr-006", "adr-007", "adr-008", "adr-009", "adr-010", "adr-011", "adr-012", "adr-013", "adr-014", "adr-015", "adr-016", "adr-017", "adr-018", "adr-019"],
},
d.make_node {
id = "reflection-modes",
name = "Reflection Modes",
pole = 'Yang,
level = 'Practice,
description = "Operational procedures are first-class artifacts encoded as NCL DAG contracts. Modes declare actors, steps, dependencies, and error strategies — not prose. Forms (reflection/forms/) provide structured input schemas that feed into modes and the compose pipeline.",
artifact_paths = ["reflection/modes/", "reflection/schemas/", "crates/ontoref-reflection/", "reflection/forms/"],
},
d.make_node {
id = "coder-process-memory",
name = "Coder Process Memory",
pole = 'Yin,
level = 'Practice,
description = "Session knowledge is captured as structured JSONL via coder record. Queryable, exportable, and promotable to reflection/knowledge/. The memory layer between sessions.",
artifact_paths = ["reflection/modules/coder.nu", "reflection/schemas/coder.ncl"],
},
d.make_node {
id = "describe-query-layer",
name = "Describe Query Layer",
pole = 'Yang,
level = 'Practice,
description = "describe.nu aggregates all project sources and answers self-knowledge queries: what IS this, what can I DO, what can I NOT do, what tools exist, what is the impact of changing X. Renders Validated by section when a node declares adrs. describe diff computes a semantic diff of .ontology/ files vs HEAD — nodes/edges added/removed/changed without text diffing. describe api queries GET /api/catalog and renders the annotated HTTP surface grouped by tag, filterable by actor/auth.",
artifact_paths = ["reflection/modules/describe.nu"],
},
d.make_node {
id = "ontoref-ontology-crate",
name = "Ontoref Ontology Crate",
pole = 'Yang,
level = 'Practice,
description = "Rust implementation for loading and querying .ontology/ NCL files as typed structs. Provides Core, Gate, and State types for ecosystem-level introspection. Node carries artifact_paths (Vec<String>) and adrs (Vec<String>) — both serde(default) for zero-migration backward compatibility.",
artifact_paths = ["crates/ontoref-ontology/"],
adrs = ["adr-001"],
},
d.make_node {
id = "ontoref-reflection-crate",
name = "Ontoref Reflection Crate",
pole = 'Yang,
level = 'Practice,
description = "Rust implementation for loading, validating, and executing Reflection modes as NCL DAG contracts against project state.",
artifact_paths = ["crates/ontoref-reflection/"],
},
d.make_node {
id = "adopt-ontoref-tooling",
name = "Adopt Ontoref Tooling",
pole = 'Yang,
level = 'Practice,
description = "Migration system for onboarding existing projects into the ontoref protocol. adopt_ontoref mode installs .ontoref/, .ontology/ stubs (core, state, gate, manifest, connections), config.ncl template, and scripts/ontoref wrapper — all idempotent. update_ontoref mode brings already-adopted projects to the current protocol version: adds manifest.ncl (content assets) and connections.ncl (cross-project federation) if missing, scans ADR migration status, validates both files, and prints a protocol update report. The 8-phase update-ontology-prompt.md guides an agent through full ontology enrichment on any project.",
artifact_paths = [
"ontoref",
"justfile",
"justfiles/ci.just",
"templates/ontology/",
"templates/ontoref-config.ncl",
"templates/scripts-ontoref",
"reflection/modes/adopt_ontoref.ncl",
"reflection/modes/update_ontoref.ncl",
"reflection/forms/adopt_ontoref.ncl",
"reflection/templates/adopt_ontoref.nu.j2",
"reflection/templates/update-ontology-prompt.md",
"reflection/migrations/",
],
},
d.make_node {
id = "protocol-migration-system",
name = "Protocol Migration System",
pole = 'Yang,
level = 'Practice,
description = "Progressive, ordered protocol migrations for consumer projects. Each migration is an NCL file in reflection/migrations/NNN-slug.ncl declaring id, slug, description, a typed check (FileExists | Grep | NuCmd), and instructions interpolated at runtime with project_root and project_name. Applied state is determined solely by whether the check passes — no state file, fully idempotent. NuCmd checks must be valid Nushell (no bash &&, $env.VAR not $VAR). Accessible via `ontoref migrate list/pending/show` and the interactive group dispatch. Narrows ADR instance checks to `adr-[0-9][0-9][0-9]-*.ncl` to exclude schema/template infrastructure files from pattern matching.",
invariant = false,
artifact_paths = [
"reflection/migrations/",
"reflection/modules/migrate.nu",
"reflection/nulib/interactive.nu",
"reflection/nulib/help.nu",
"reflection/bin/ontoref.nu",
],
adrs = ["adr-010"],
},
d.make_node {
id = "ontology-three-file-split",
name = "Ontology Three-File Split",
pole = 'Yang,
level = 'Practice,
description = "The .ontology/ directory separates three orthogonal concerns into three files. core.ncl captures what the project IS — invariant axioms and structural tensions; touching invariant=true nodes requires a new ADR. state.ncl captures where it IS vs where it wants to BE — current and desired state per dimension. gate.ncl defines when it is READY to cross a boundary — active membranes protecting key conditions. reflection/ reads all three and answers self-knowledge queries. This separation lets an agent understand a project without reading code — only by consulting the declarative graph.",
invariant = false,
artifact_paths = [".ontology/core.ncl", ".ontology/state.ncl", ".ontology/gate.ncl"],
},
d.make_node {
id = "adr-node-linkage",
name = "ADRNode Declared Linkage",
pole = 'Yang,
level = 'Practice,
description = "Nodes declare which ADRs validate them via the adrs field (Array String). This makes the ADR→Node relationship explicit in the graph rather than implicit in prose. describe surfaces a Validated by section per node. The daemon graph UI renders each ADR as a clickable link opening the full ADR via GET /api/adr/{id}. Field is serde(default) and Nickel default=[] — zero migration cost for existing nodes.",
artifact_paths = [
"ontology/schemas/core.ncl",
"crates/ontoref-ontology/src/types.rs",
"reflection/modules/describe.nu",
"crates/ontoref-daemon/templates/pages/graph.html",
"crates/ontoref-daemon/src/api.rs",
],
},
d.make_node {
id = "web-presence",
name = "Web Presence",
pole = 'Yang,
level = 'Practice,
description = "Landing page at assets/web/ describing the ontoref protocol to external audiences. Bilingual (EN/ES), covers protocol layers, yin/yang duality, crates, and adoption path. Self-description artifact.",
artifact_paths = ["assets/web/src/index.html", "assets/web/index.html", "README.md", "assets/architecture.svg"],
},
d.make_node {
id = "ontoref-daemon",
name = "Ontoref Daemon",
pole = 'Yang,
level = 'Practice,
description = "HTTP daemon for NCL export caching, file watching, actor registry, MCP surface, and GraphQL API. Provides notification barrier, HTTP API, MCP server (stdio + streamable-HTTP), GraphQL endpoint (async-graphql 8, Apollo Federation v2, WebSocket subscriptions), Q&A NCL persistence, quick-actions catalog, passive drift observation, unified auth/session model (ADR-005), runtime service toggles (ADR-014), and annotated API catalog (GET /api/catalog). API catalog populated at link time via #[onto_api] proc-macro + inventory — zero runtime overhead. Launched via ADR-004 NCL pipe bootstrap: nickel export config.ncl | ontoref-daemon.bin --config-stdin. Optional services (MCP, GraphQL) are feature-gated and runtime-toggleable via PUT /api/services/:service or UI manage page — changes take effect immediately without restart.",
invariant = false,
artifact_paths = [
"crates/ontoref-daemon/",
"crates/ontoref-daemon/src/api_catalog.rs",
"crates/ontoref-daemon/src/graphql/mod.rs",
"crates/ontoref-daemon/templates/pages/api_catalog.html",
"crates/ontoref-derive/",
"install/ontoref-daemon-boot",
"install/install.nu",
"nats/streams.json",
"reflection/modules/services.nu",
"crates/ontoref-daemon/src/ui/qa_ncl.rs",
"crates/ontoref-daemon/src/ui/drift_watcher.rs",
"crates/ontoref-daemon/src/mcp/mod.rs",
"crates/ontoref-daemon/src/session.rs",
"crates/ontoref-daemon/src/ui/auth.rs",
"crates/ontoref-daemon/src/ui/login.rs",
"crates/ontoref-daemon/src/ui/search_bookmarks_ncl.rs",
"justfiles/ci.just",
"justfiles/build.just",
"adrs/adr-014-runtime-service-toggles.ncl",
],
},
d.make_node {
id = "api-catalog-surface",
name = "API Catalog Surface",
pole = 'Yang,
level = 'Practice,
description = "Every HTTP handler is annotated with #[onto_api(method, path, description, auth, actors, params, tags)] — a proc-macro attribute that emits an inventory::submit!(ApiRouteEntry{...}) at link time. inventory::collect!(ApiRouteEntry) aggregates all entries into a zero-cost static catalog. GET /api/catalog serves the full annotated surface as JSON, sorted by path+method. describe api queries the catalog and renders it grouped by tag, filterable by actor/auth in the CLI. ApiCatalogTool exposes the catalog to MCP agents. The /ui/{slug}/api web page renders it with client-side filtering and a parameter detail panel.",
invariant = false,
artifact_paths = [
"crates/ontoref-daemon/src/api_catalog.rs",
"crates/ontoref-derive/src/lib.rs",
"crates/ontoref-daemon/src/api.rs",
"crates/ontoref-daemon/templates/pages/api_catalog.html",
"reflection/modules/describe.nu",
"crates/ontoref-daemon/src/mcp/mod.rs",
],
},
d.make_node {
id = "unified-auth-model",
name = "Unified Auth Model",
pole = 'Yang,
level = 'Practice,
description = "All surfaces (CLI, UI, MCP) exchange a raw key for a UUID v4 session token via POST /sessions (30-day lifetime, O(1) lookup vs O(~100ms) argon2 per request). Project keys carry role (admin|viewer) and label for audit trail. Daemon admin sessions use virtual slug '_daemon'. ONTOREF_TOKEN injected as Bearer by CLI automatically. Sessions have a stable public id (distinct from bearer token) for safe list/revoke operations. Key rotation revokes all sessions for the rotated project.",
invariant = false,
artifact_paths = [
"crates/ontoref-daemon/src/session.rs",
"crates/ontoref-daemon/src/api.rs",
"crates/ontoref-daemon/src/registry.rs",
"crates/ontoref-daemon/src/ui/login.rs",
"crates/ontoref-daemon/src/ui/auth.rs",
"reflection/modules/store.nu",
"install/resources/schemas/ontoref-project.ncl",
],
},
d.make_node {
id = "project-onboarding",
name = "Project Onboarding",
pole = 'Yang,
level = 'Practice,
description = "Idempotent onboarding via `ontoref setup`. Creates .ontoref/project.ncl, .ontoref/config.ncl (with logo auto-detection in assets/), .ontology/ scaffold, adrs/, reflection/modes/, backlog.ncl, qa.ncl, git hooks, and registers in projects.ncl. Supports --kind (repo_kind) and --parent (framework layers + browse modes for implementation children). Bootstrap key generation via --gen-keys ['admin:label' 'viewer:label']: idempotent (no-op if keys exist), hashes via daemon binary, prints passwords once.",
invariant = false,
artifact_paths = [
"reflection/bin/ontoref.nu",
"templates/project.ncl",
"templates/ontoref-config.ncl",
"templates/ontology/",
"install/gen-projects.nu",
"install/resources/schemas/ontoref-project.ncl",
],
},
d.make_node {
id = "daemon-config-management",
name = "Daemon Config Management",
pole = 'Yang,
level = 'Practice,
description = "Install and configuration infrastructure for ontoref-daemon. Global config at ~/.config/ontoref/config.ncl (Nickel, type-checked). Browser-based editing via typedialog roundtrip: form (config.ncl) → browser → Tera template (config.ncl.j2) → updated config.ncl. CI guard (check-config-sync.nu) enforces form/template parity on every commit. Global NATS topology at ~/.config/ontoref/streams.json; project-local override via nats/streams.json and NATS_STREAMS_CONFIG env var. Config validation + liveness probes via config-setup.nu.",
invariant = false,
artifact_paths = [
"install/resources/config.ncl",
"install/resources/streams.json",
"install/config-setup.nu",
"install/check-config-sync.nu",
"reflection/forms/config.ncl",
"reflection/forms/config.ncl.j2",
"reflection/nulib/bootstrap.nu",
],
},
d.make_node {
id = "qa-knowledge-store",
name = "Q&A Knowledge Store",
pole = 'Yin,
level = 'Practice,
description = "Accumulated Q&A entries persisted as NCL — questions and answers captured during development sessions, AI interactions, and architectural reviews. Git-versioned, typed by QaEntry schema, queryable via MCP (ontoref_qa_list/add) and HTTP (/qa-json). Bridges session boundaries: knowledge is never lost between actor sessions.",
artifact_paths = [
"reflection/qa.ncl",
"reflection/schemas/qa.ncl",
"crates/ontoref-daemon/src/ui/qa_ncl.rs",
],
},
d.make_node {
id = "quick-actions",
name = "Quick Actions Catalog",
pole = 'Yang,
level = 'Practice,
description = "Runnable shortcuts over existing reflection modes. Configured as quick_actions in .ontoref/config.ncl (id, label, icon, category, mode, actors). Accessible from UI (/actions), CLI (./ontoref), and MCP (ontoref_action_list/add). New modes created via ontoref_action_add are immediately available as actions. Reduces friction between knowing a mode exists and executing it.",
artifact_paths = [
".ontoref/config.ncl",
"crates/ontoref-daemon/templates/pages/actions.html",
"reflection/modes/",
],
},
d.make_node {
id = "personal-ontology-schemas",
name = "Personal Ontology Schemas",
pole = 'Yin,
level = 'Practice,
description = "Typed NCL schema layer for personal and career artifacts: career.ncl (Skills, WorkExperience, Talks, Positioning, CompanyTargets, PublicationCards), personal.ncl (Content and Opportunity lifecycle — BlogPost to CV to Application, Job to Conference to Grant), project-card.ncl (canonical display metadata for portfolio and cv_repo publication), links.ncl (typed Link record with LinkKind enum — replaces raw urls/docs/emails/slides_url/video_url/repository string arrays across all personal+career+core schemas). All types carry linked_nodes referencing .ontology/core.ncl node IDs — bridging career artifacts into the DAG.",
invariant = false,
artifact_paths = [
"ontology/schemas/links.ncl",
"ontology/schemas/career.ncl",
"ontology/schemas/personal.ncl",
"ontology/schemas/project-card.ncl",
"ontology/defaults/career.ncl",
"ontology/defaults/personal.ncl",
"ontology/defaults/project-card.ncl",
],
},
d.make_node {
id = "content-modes",
name = "Content & Career Reflection Modes",
pole = 'Yang,
level = 'Practice,
description = "NCL DAG modes for personal content and career operations: draft-application (job/grant/collaboration application anchored in personal ontology — gate alignment check, node selection, career trajectory render), draft-email, generate-article, update-cv, write-cfp. Each mode queries personal.ncl and core.ncl nodes to ground output in declared project artifacts rather than free-form prose.",
invariant = false,
artifact_paths = [
"reflection/modes/draft-application.ncl",
"reflection/modes/draft-email.ncl",
"reflection/modes/generate-article.ncl",
"reflection/modes/update-cv.ncl",
"reflection/modes/write-cfp.ncl",
],
},
d.make_node {
id = "search-bookmarks",
name = "Search Bookmarks",
pole = 'Yin,
level = 'Practice,
description = "Persistent bookmark store for search results over the ontology graph. Entries typed as BookmarkEntry (id, node_id, kind, title, level, term, actor, created_at, tags) and persisted to reflection/search_bookmarks.ncl via line-level NCL surgery — same atomic-write pattern as qa_ncl.rs. IDs are sequential sb-NNN, zero-padded. Concurrency-safe via NclWriteLock. Supports add and remove; accessible from the daemon search UI.",
invariant = false,
artifact_paths = [
"reflection/search_bookmarks.ncl",
"reflection/schemas/search_bookmarks.ncl",
"crates/ontoref-daemon/src/ui/search_bookmarks_ncl.rs",
],
},
d.make_node {
id = "drift-observation",
name = "Passive Drift Observation",
pole = 'Spiral,
level = 'Practice,
description = "Background observer that bridges Yang code artifacts with Yin ontology declarations. Watches crates/, .ontology/, adrs/, reflection/modes/ for changes; after a debounce window runs sync scan + sync diff; if MISSING/STALE/DRIFT/BROKEN items are found emits an ontology_drift notification. Never applies changes automatically — apply remains a deliberate human or agent act.",
artifact_paths = [
"crates/ontoref-daemon/src/ui/drift_watcher.rs",
"reflection/modes/sync-ontology.ncl",
],
},
d.make_node {
id = "manifest-self-description",
name = "Manifest Self-Interrogation Layer",
pole = 'Yang,
level = 'Practice,
description = "Three typed arrays added to manifest_type: capabilities[] (what the project does, why, how — with explicit ontology node and ADR cross-references), requirements[] (prerequisites classified by env_target_type: Production/Development/Both and requirement_kind_type: Tool/Service/EnvVar/Infrastructure), and critical_deps[] (external dependencies with documented blast radius distinct from startup prerequisites). describe requirements new subcommand surfaces these. describe guides gains capabilities/requirements/critical_deps keys — agents on cold start receive full self-interrogation context without extra tool calls. Also fixes the collect-identity bug where manifest.kind? was read (field did not exist) instead of manifest.repo_kind?, and adds description | String | default = '' to manifest_type.",
invariant = false,
artifact_paths = [
"ontology/schemas/manifest.ncl",
"ontology/defaults/manifest.ncl",
".ontology/manifest.ncl",
"reflection/modules/describe.nu",
],
adrs = ["adr-009"],
},
d.make_node {
id = "config-surface",
name = "Config Surface",
pole = 'Yang,
level = 'Practice,
description = "Per-project config introspection, coherence verification, and documented mutation. Rust structs annotated with #[derive(ConfigFields)] + #[config_section(id, ncl_file)] emit inventory::submit!(ConfigFieldsEntry{...}) at link time — the same inventory pattern as API catalog. The daemon queries inventory::iter::<ConfigFieldsEntry>() at startup to build a zero-maintenance registry of which Rust fields each struct reads from each NCL section. Multi-consumer coherence compares this registry against the NCL export, Nu script accessors, and CI pipeline fields declared in manifest.ncl's config_surface — any NCL field claimed by no consumer is flagged unclaimed. API endpoints: GET /projects/{slug}/config (full export), /config/{section} (single section), /config/schema (sections with contracts and consumers), /config/coherence (multi-consumer diff), /config/quickref (generated documentation with rationales, override history, coherence status). PUT /projects/{slug}/config/{section} mutates via an override layer: writes {section}.overrides.ncl with audit metadata (actor, reason, timestamp, previous value), appends a single import to the entry point (idempotent), validates with nickel export, reverts on contract violation. NCL contracts (std.contract.from_validator) enforce field constraints (enums, positive numbers, port ranges) before any Rust struct is populated — Nickel is the single validation layer. Ontoref describes its own config via .ontoref/contracts.ncl applying LogConfig and DaemonConfig contracts.",
invariant = false,
artifact_paths = [
"crates/ontoref-daemon/src/config.rs",
"crates/ontoref-daemon/src/config_coherence.rs",
"crates/ontoref-daemon/src/api.rs",
"crates/ontoref-derive/src/lib.rs",
"crates/ontoref-ontology/src/lib.rs",
"ontology/schemas/manifest.ncl",
".ontoref/contracts.ncl",
".ontoref/config.ncl",
],
adrs = ["adr-007", "adr-008"],
},
d.make_node {
id = "domain-extension-system",
name = "Domain Extension System",
pole = 'Yang,
level = 'Practice,
description = "Bash-layer dispatch for repo_kind-conditional CLI domains. Consumer projects with specific repo_kinds get project-type-aware CLI commands via ore <domain-id> <command>. Each domain ships three files under $ONTOREF_ROOT/domains/{id}/: domain.ncl (NCL contract: commands, pages, repo_kinds, short_alias), commands.nu (Nu script with def main entry point), repo_kinds.txt (grep-readable list of matching repo_kind values). install.nu copies all domains/ to $data_dir and generates aliases.txt + standalone bin wrappers from short_alias. help.nu and describe.nu read domain.ncl at runtime for dynamic help and capabilities output. repo_kind extracted from project manifest via grep (not nickel export) to avoid import-path failures. Session 2026-04-05: personal domain (PersonalOntology — state, next, validate, audit, career, cfp, opportunities, content) and provisioning domain (DevWorkspace/Mixed — state, next, connections, gates, card, capabilities, backlog) implemented. Session 2026-04-06: framework domain (Library/Service/Tool — state, next, connections, gates, capabilities, validate) added to cover Library and Service projects (librosys, DD7pasos, rustelo, website-impl) that have connections.ncl and gate.ncl but no workspace-specific commands.",
invariant = false,
artifact_paths = [
"domains/",
"domains/schema.ncl",
"domains/personal/domain.ncl",
"domains/personal/commands.nu",
"domains/personal/repo_kinds.txt",
"domains/provisioning/domain.ncl",
"domains/provisioning/commands.nu",
"domains/provisioning/repo_kinds.txt",
"domains/framework/domain.ncl",
"domains/framework/commands.nu",
"domains/framework/repo_kinds.txt",
"install/ontoref-global",
"install/install.nu",
"reflection/nulib/help.nu",
"reflection/modules/describe.nu",
],
adrs = ["adr-012"],
},
d.make_node {
id = "vcs-abstraction",
name = "VCS Abstraction Layer",
pole = 'Yang,
level = 'Practice,
description = "Uniform VCS API over jj and git. Filesystem-based detection (.jj/ vs .git/) — no config, no env var. Exposes show-committed, restore-file, remote-url, current-branch, uncommitted-files, and commit-count with identical semantics regardless of VCS backend. All ontoref modules consume vcs.nu instead of hardcoding git commands.",
artifact_paths = [
"reflection/modules/vcs.nu",
],
adrs = ["adr-013"],
},
d.make_node {
id = "agent-workspace-orchestration",
name = "Agent Workspace Orchestration",
pole = 'Yang,
level = 'Practice,
description = "jj + ontoref + Radicle lifecycle wrapper for agent workspaces. jjw agent create spawns a jj workspace and starts an ontoref run; jjw agent step reports progress; jjw agent publish validates, pushes, and opens a Radicle patch (or git push fallback); jjw agent merge commits and cleans the workspace. NCL conflict resolution via jjw-ncl-merge.nu registered as a jj merge tool — enables automated .ontology/ conflict resolution during workspace merge.",
artifact_paths = [
"reflection/bin/jjw.nu",
"reflection/bin/jjw-ncl-merge.nu",
],
},
d.make_node {
id = "mcp-surface",
name = "MCP Server Surface",
pole = 'Yang,
level = 'Practice,
description = "Model Context Protocol server exposing ontology state, reflection modes, ADRs, backlog, config, Q&A, and search bookmarks as structured tools. Two transports: stdio (for AI assistants that launch daemon as subprocess) and streamable-HTTP at POST /mcp (for remote MCP clients). Authentication uses the same unified session model as the REST API (ADR-005) — Bearer token from POST /sessions. Runtime-toggleable without restart (ADR-014). Connect: (1) stdio — run `ontoref-daemon --mcp-stdio`; tool list auto-discovered by the AI client. (2) streamable-HTTP — configure MCP client with url=http://127.0.0.1:7891/mcp, auth=Bearer <session-token>. Session token obtained via POST /sessions with project key. ONTOREF_TOKEN env var injected automatically by CLI. Available tools include: ontoref_help, ontoref_list_projects, ontoref_set_project, ontoref_search, ontoref_get, ontoref_get_node, ontoref_get_adr, ontoref_get_mode, ontoref_status, ontoref_describe, ontoref_guides, ontoref_list_adrs, ontoref_list_modes, ontoref_constraints, ontoref_backlog_list, ontoref_backlog, ontoref_action_list, ontoref_action_add, ontoref_validate_adrs, ontoref_validate, ontoref_impact, ontoref_qa_list, ontoref_qa_add, ontoref_bookmark_list, ontoref_bookmark_add, ontoref_api_catalog, ontoref_notify.",
artifact_paths = [
"crates/ontoref-daemon/src/mcp/mod.rs",
"crates/ontoref-daemon/src/api.rs",
],
},
d.make_node {
id = "graphql-surface",
name = "GraphQL API Surface",
pole = 'Yang,
level = 'Practice,
description = "GraphQL endpoint over the ontology graph, impact traversal, project state, backlog, ADRs, cross-project config comparison, and notification stream. Built with async-graphql 8 (Apollo Federation v2 entity resolution built-in). Endpoints: GET /graphql (GraphiQL IDE), POST /graphql (queries/mutations), GET /graphql/ws (WebSocket subscriptions). Auth: if graphql.read_token_hash is set in config.ncl, all endpoints require Authorization: Bearer <token>. Generate token: `just generate-graphql-token` — prints TOKEN and the Argon2id HASH to add to config.ncl. Pre-auth GraphiQL: GET /graphql?token=<token> — pre-injects the Bearer header and WebSocket connectionParam so the IDE works without manual header setup. Runtime-toggleable without restart (ADR-014). Key queries: ontologyNode(slug, id), impactGraph(slug, nodeId), project(slug), backlog(slug), adrs(slug), crossProjectSummaries, crossProjectConflicts. Key mutations: emitNotification, proposeBacklogStatus, ackNotifications, ackAllNotifications. Subscription: notifications(project, eventType) — streams live notifications over WebSocket.",
artifact_paths = [
"crates/ontoref-daemon/src/graphql/mod.rs",
"crates/ontoref-daemon/Cargo.toml",
"adrs/adr-014-runtime-service-toggles.ncl",
],
},
d.make_node {
id = "registry-credential-vault",
name = "Registry Credential Vault",
pole = 'Yang,
level = 'Practice,
description = "Per-project sops multi-recipient credential vault stored as OCI artifact in ZOT (ADR-017). Each actor role has its own age keypair — the sops DEK is encrypted separately per recipient, enabling revocation without key rotation. vault_key is decrypted into RESTIC_PASSWORD or KOPIA_PASSWORD for the operation duration only; never written to disk. The daemon is structurally excluded: it holds no .kage, cannot decrypt sops files even if it reads them, and cannot be used as a credential amplifier. DOCKER_CONFIG is isolated to a per-call tmpdir; no ambient ~/.docker/config.json is ever consulted. src-vault OCI artifact is cosign-signed on every push and verified before any pull — tamper-evident and substitution-resistant. Access logs are co-located in the vault artifact as an append-only jsonl layer. Two-level authorization gate: assert-actor-authorized validates ONTOREF_ACTOR→role binding + scope.bound_actor + scope.ops; assert-target-in-scope validates the OCI ref against scope.namespaces glob — both fire before any oras call, no cache hit bypasses. Vault lock OCI artifact (src-vault/<id>:lock) coordinates concurrent edits with TTL 60min and admin-only force-unlock auditable. Impact analysis on secrets-close diffs sops files since last snapshot and maps changes to RegistryEntry IDs before push. Per-file recipient routing via project.ncl::sops.recipient_groups + recipient_rules enables tenant/agent isolation in a single vault using sops creation_rules — admin operates as super-role, clientA/clientB/agents decrypt only their own files. cosign 2+ compatibility via vault.cosign.signing_config_path (Rekor-less) when tlog=false. cosign_password 4th field in access.sops.yaml enables non-interactive CI signing. 14/14 named-error tests cover the helper contract. 3 sops adoption templates (single-team, multi-tenant, agent-first) and 3 integration templates (domain-producer, mode-producer, mode-consumer) provide copy-paste starting points. FAQ entries with data-flow diagrams in reflection/qa.ncl.",
invariant = false,
artifact_paths = [
"justfiles/secrets.just",
"justfiles/_secrets_lib.sh",
"reflection/modules/vault.nu",
"reflection/modules/secrets.nu",
"reflection/requirements/base.ncl",
"reflection/tests/test_secrets.nu",
"install/resources/schemas/ontoref-project.ncl",
"install/resources/templates/sops/",
"install/resources/templates/integration/",
"reflection/migrations/0016-registry-credential-vault.ncl",
"reflection/qa.ncl",
"adrs/adr-019-per-file-recipient-routing-tenant-isolation.ncl",
],
adrs = ["adr-017", "adr-019"],
},
d.make_node {
id = "level-hierarchy-resolution",
name = "Level Hierarchy and Mode Resolution",
pole = 'Yang,
level = 'Practice,
description = "Three-level specialization hierarchy with per-mode resolution strategy (ADR-018). Level 1 = ontoref base (generic protocol), Level 2 = project domain (e.g. provisioning-domain), Level 3 = domain instance (e.g. libre-daoshi, libre-wuji). Level identity is declared in manifest.ncl (level.index: Base|Domain|Instance, level.name, level.parent). Each mode at Level 2+ declares strategy: Override (complete implementation, traversal stops), Delegate (no implementation here, traverse to parent), Merge (accumulate bottom-up, lower wins conflicts), Compose (partial inheritance via extends field). Implicit absence at Level 2+ is treated as Delegate with a Soft validation warning from ore validate modes. Strategy transitions are FSM events in state.ncl — moving from Delegate to Override is an observable, git-tracked architectural decision, not a silent refactor. ore mode resolve makes traversal observable: reports which level answered a mode invocation, the strategy applied, the source file, and the reason.",
invariant = false,
artifact_paths = [
"ontology/schemas/manifest.ncl",
"reflection/schema.ncl",
"reflection/migrations/0017-level-hierarchy-strategy.ncl",
"adrs/adr-018-level-hierarchy-mode-resolution-strategy.ncl",
],
adrs = ["adr-018"],
},
d.make_node {
id = "ci-pipelines",
name = "CI/CD Pipelines",
pole = 'Yang,
level = 'Practice,
description = "Continuous integration pipelines: GitHub Actions (Nickel typecheck, Rust CI) and Woodpecker (standard + advanced CI). Pre-commit hooks enforce formatting, linting, testing, dependency auditing, docs-links, docs-drift, and manifest coverage before code reaches CI.",
artifact_paths = [
".github/workflows/",
".woodpecker/",
".pre-commit-config.yaml",
".claude/hooks/",
],
},
d.make_node {
id = "read-tensions-first",
name = "Read Tensions First (ondaod)",
pole = 'Spiral,
level = 'Practice,
description = "Before architectural analysis or recommendation, read .ontology/core.ncl tensions; identify which the question engages; describe the synthesis state and direction of motion rather than collapse the Spiral by picking one pole. Default reasoning (human or agent) is Yang — choose, decide, recommend. This Practice surfaces that bias and asks the analyst to characterize the continuous flow first. Operationalizes the ontology-vs-reflection tension; balances against formalization-vs-adoption by choosing minimal structural enforcement (one Practice node, qa entry as canonical content, terse CLAUDE.md addendum) over heavier ADR schema constraints that would impose ceremony before adoption matures. Full procedure and forbidden patterns: reflection/qa.ncl::ontoref-dao-discipline.",
artifact_paths = [".ontology/core.ncl", "reflection/qa.ncl"],
},
],
edges = [
{ from = "self-describing", to = "dag-formalized", kind = 'ManifestsIn, weight = 'High },
{ from = "self-describing", to = "adr-lifecycle", kind = 'ManifestsIn, weight = 'High },
{ from = "self-describing", to = "reflection-modes", kind = 'ManifestsIn, weight = 'High },
{ from = "ontology-vs-reflection", to = "dag-formalized", kind = 'Resolves, weight = 'High },
{ from = "ontology-vs-reflection", to = "coder-process-memory", kind = 'Resolves, weight = 'Medium },
{ from = "dag-formalized", to = "ontoref-ontology-crate", kind = 'ManifestsIn, weight = 'High },
{ from = "reflection-modes", to = "ontoref-reflection-crate", kind = 'ManifestsIn, weight = 'High },
{ from = "no-enforcement", to = "formalization-vs-adoption", kind = 'Resolves, weight = 'Medium },
{ from = "protocol-not-runtime", to = "no-enforcement", kind = 'Implies, weight = 'High },
{ from = "adr-lifecycle", to = "reflection-modes", kind = 'Complements, weight = 'Medium },
{ from = "adr-node-linkage", to = "adr-lifecycle", kind = 'ManifestsIn, weight = 'High },
{ from = "adr-node-linkage", to = "describe-query-layer", kind = 'Complements, weight = 'High },
{ from = "describe-query-layer", to = "dag-formalized", kind = 'DependsOn, weight = 'High },
{ from = "coder-process-memory", to = "describe-query-layer", kind = 'Complements, weight = 'Medium },
{ from = "ontoref-daemon", to = "ontoref-ontology-crate", kind = 'Complements, weight = 'High },
{ from = "ontoref-daemon", to = "reflection-modes", kind = 'Complements, weight = 'Medium },
{ from = "protocol-not-runtime", to = "ontoref-daemon", kind = 'Contradicts, weight = 'Low,
note = "Daemon is optional runtime support, not a protocol requirement. Protocol functions without it." },
{ from = "no-enforcement", to = "adopt-ontoref-tooling", kind = 'ManifestsIn, weight = 'High,
note = "Adoption is voluntary — the tooling makes it easy but never mandatory." },
{ from = "adopt-ontoref-tooling", to = "reflection-modes", kind = 'DependsOn, weight = 'High },
{ from = "self-describing", to = "web-presence", kind = 'ManifestsIn, weight = 'Medium },
{ from = "web-presence", to = "adopt-ontoref-tooling", kind = 'Complements, weight = 'Medium },
# Q&A Knowledge Store edges
{ from = "qa-knowledge-store", to = "dag-formalized", kind = 'ManifestsIn, weight = 'High,
note = "Q&A entries are typed NCL records, git-versioned — knowledge as DAG." },
{ from = "qa-knowledge-store", to = "coder-process-memory", kind = 'Complements, weight = 'High,
note = "Q&A is the persistent layer; coder.nu is the session capture layer. Together they form the full memory stack." },
{ from = "ontoref-daemon", to = "qa-knowledge-store", kind = 'Contains, weight = 'High },
{ from = "daemon-config-management", to = "ontoref-daemon", kind = 'DependsOn, weight = 'High },
{ from = "daemon-config-management", to = "adopt-ontoref-tooling", kind = 'Complements, weight = 'Medium,
note = "Config management is part of the adoption surface — new projects get config.ncl and streams.json during install." },
# Quick Actions edges
{ from = "quick-actions", to = "reflection-modes", kind = 'DependsOn, weight = 'High,
note = "Each quick action invokes a reflection mode by id." },
{ from = "quick-actions", to = "ontoref-daemon", kind = 'ManifestsIn, weight = 'Medium },
{ from = "describe-query-layer", to = "quick-actions", kind = 'Complements, weight = 'Medium,
note = "describe capabilities lists available modes; quick-actions makes them executable." },
# Drift Observation edges
{ from = "drift-observation", to = "ontoref-daemon", kind = 'ManifestsIn, weight = 'High },
{ from = "drift-observation", to = "ontology-vs-reflection", kind = 'Resolves, weight = 'Medium,
note = "Drift observer continuously monitors the gap between Yin (ontology) and Yang (code). Passive resolution — it signals drift without forcing resolution." },
{ from = "drift-observation", to = "reflection-modes", kind = 'DependsOn, weight = 'High,
note = "Invokes sync-ontology mode steps (scan, diff) as read-only sub-processes." },
# Personal Ontology Schemas edges
{ from = "personal-ontology-schemas", to = "dag-formalized", kind = 'ManifestsIn, weight = 'High,
note = "Career and personal artifacts are typed NCL records with linked_nodes — DAG connections into the core ontology." },
{ from = "personal-ontology-schemas", to = "self-describing", kind = 'Complements, weight = 'Medium,
note = "Personal/career schemas let projects describe not just what they ARE but who built them and for what trajectory." },
{ from = "content-modes", to = "reflection-modes", kind = 'ManifestsIn, weight = 'High },
{ from = "content-modes", to = "personal-ontology-schemas", kind = 'DependsOn, weight = 'High,
note = "Content and career modes query personal.ncl and core.ncl to ground output in declared artifacts." },
{ from = "search-bookmarks", to = "qa-knowledge-store", kind = 'Complements, weight = 'High,
note = "Both are NCL persistence layers using the same atomic-write surgery pattern. Q&A is for accumulated knowledge; bookmarks are for search navigation state." },
{ from = "search-bookmarks", to = "ontoref-daemon", kind = 'ManifestsIn, weight = 'High },
{ from = "ontoref-daemon", to = "search-bookmarks", kind = 'Contains, weight = 'High },
# API Catalog Surface edges
{ from = "api-catalog-surface", to = "ontoref-daemon", kind = 'ManifestsIn, weight = 'High },
{ from = "api-catalog-surface", to = "describe-query-layer", kind = 'Complements, weight = 'High,
note = "describe api queries GET /api/catalog and renders the annotated surface in the CLI." },
{ from = "api-catalog-surface", to = "protocol-not-runtime", kind = 'Complements, weight = 'Medium,
note = "Catalog is compiled into the binary via inventory — no runtime doc system, no external dependency." },
# Unified Auth Model edges
{ from = "unified-auth-model", to = "ontoref-daemon", kind = 'ManifestsIn, weight = 'High },
{ from = "unified-auth-model", to = "no-enforcement", kind = 'Contradicts, weight = 'Low,
note = "Auth is opt-in per project (no keys = open deployment). When keys are configured enforcement is real, but the protocol itself never mandates it." },
{ from = "ontoref-daemon", to = "unified-auth-model", kind = 'Contains, weight = 'High },
# Project Onboarding edges
{ from = "project-onboarding", to = "adopt-ontoref-tooling", kind = 'Complements, weight = 'High,
note = "adopt-ontoref-tooling is the migration surface for existing projects; project-onboarding is the first-class setup for new projects." },
{ from = "project-onboarding", to = "unified-auth-model", kind = 'DependsOn, weight = 'Medium,
note = "--gen-keys bootstraps the first keys into project.ncl during setup." },
{ from = "project-onboarding", to = "daemon-config-management", kind = 'DependsOn, weight = 'Medium },
# Manifest Self-Interrogation Layer edges
{ from = "manifest-self-description", to = "self-describing", kind = 'Complements, weight = 'High,
note = "capabilities/requirements/critical_deps in the manifest are the typed operational answer to 'what IS this project' — complementing the architectural answer in core.ncl Practice nodes." },
{ from = "manifest-self-description", to = "adopt-ontoref-tooling", kind = 'Complements, weight = 'High,
note = "Consumer projects declare their own capabilities, requirements, and critical deps — the self-interrogation layer is part of the adoption surface." },
{ from = "manifest-self-description", to = "describe-query-layer", kind = 'ManifestsIn, weight = 'High,
note = "describe capabilities renders manifest_capabilities; describe requirements surfaces requirements + critical_deps; describe guides extends its output with all three arrays." },
{ from = "manifest-self-description", to = "dag-formalized", kind = 'Complements, weight = 'Medium,
note = "capabilities.nodes[] cross-references ontology node IDs; capabilities.adrs[] cross-references ADR IDs — bridging the manifest into the queryable DAG." },
{ from = "manifest-self-description", to = "adr-lifecycle", kind = 'Complements, weight = 'Medium,
note = "capabilities.adrs[] creates explicit typed links from capabilities to the ADRs that formalize them — the ADR→Node linkage pattern extended to the manifest layer." },
# Protocol Migration System edges
{ from = "protocol-migration-system", to = "adopt-ontoref-tooling", kind = 'ManifestsIn, weight = 'High,
note = "Migration system is the versioned upgrade surface for adopt-ontoref-tooling — new protocol features arrive as numbered migrations, not template rewrites." },
{ from = "protocol-migration-system", to = "adr-lifecycle", kind = 'Complements, weight = 'High,
note = "Each migration check can verify ADR-level constraints are met in consumer repos — migrations and ADRs are complementary protocol enforcement layers." },
{ from = "protocol-migration-system", to = "no-enforcement", kind = 'Complements, weight = 'Medium,
note = "Migrations are advisory: `migrate pending` reports state, never applies automatically. The actor decides when to apply." },
{ from = "self-describing", to = "protocol-migration-system", kind = 'ManifestsIn, weight = 'Medium,
note = "Ontoref runs its own migration checks against itself — the migration system is self-applied." },
# Domain Extension System edges
{ from = "domain-extension-system", to = "formalization-vs-adoption", kind = 'Resolves, weight = 'High,
note = "Domain commands remove the adoption friction of project-type-specific CLI — PersonalOntology and DevWorkspace get discoverable commands without local scripts." },
{ from = "domain-extension-system", to = "personal-ontology-schemas", kind = 'DependsOn, weight = 'High,
note = "The personal domain reads career.ncl, personal.ncl, and backlog.ncl — it is the CLI surface for the personal ontology schema layer." },
{ from = "domain-extension-system", to = "describe-query-layer", kind = 'Complements, weight = 'High,
note = "describe capabilities runs resolve-domain-extension and renders DOMAIN EXTENSION section; ore help <domain-id> reads domain.ncl commands[] dynamically." },
{ from = "domain-extension-system", to = "adopt-ontoref-tooling", kind = 'Complements, weight = 'High,
note = "Domain extensions are part of the adoption surface — new repo_kinds get project-type-aware CLI by shipping a domain directory." },
{ from = "domain-extension-system", to = "protocol-not-runtime", kind = 'Complements, weight = 'Medium,
note = "Domain dispatch is bash+grep — no runtime dependency on nickel for the dispatch path itself. commands.nu is an isolated Nu process." },
# VCS Abstraction edges
{ from = "vcs-abstraction", to = "reflection-modes", kind = 'DependsOn, weight = 'Medium,
note = "opmode.nu and git-event.nu consume vcs.nu — mode execution is VCS-aware." },
{ from = "vcs-abstraction", to = "project-onboarding", kind = 'DependsOn, weight = 'Medium,
note = "init-repo.nu uses vcs detection to choose jj colocated vs git init during onboarding." },
# Agent Workspace Orchestration edges
{ from = "agent-workspace-orchestration", to = "vcs-abstraction", kind = 'DependsOn, weight = 'High,
note = "jjw delegates VCS detection and operations to vcs.nu." },
{ from = "agent-workspace-orchestration", to = "reflection-modes", kind = 'DependsOn, weight = 'High,
note = "jjw agent create starts an ontoref run; jjw agent step reports steps; jjw agent merge completes the run." },
{ from = "agent-workspace-orchestration", to = "no-enforcement", kind = 'Complements, weight = 'Medium,
note = "Radicle publish is optional — jjw degrades to git push if no rad remote is configured." },
# Config Surface edges
{ from = "config-surface", to = "ontoref-daemon", kind = 'ManifestsIn, weight = 'High },
{ from = "config-surface", to = "ontoref-ontology-crate", kind = 'DependsOn, weight = 'High,
note = "ConfigFieldsEntry struct and inventory::collect!(ConfigFieldsEntry) live in ontoref-ontology — the zero-dep adoption surface." },
{ from = "config-surface", to = "api-catalog-surface", kind = 'Complements, weight = 'High,
note = "#[derive(ConfigFields)] extends the same inventory::submit! pattern as #[onto_api]. Both emit link-time registration entries collected by the daemon at startup." },
{ from = "config-surface", to = "dag-formalized", kind = 'ManifestsIn, weight = 'High,
note = "Config sections, consumers, and coherence reports are typed NCL/Rust records — the config tree is a queryable subgraph." },
{ from = "config-surface", to = "self-describing", kind = 'Complements, weight = 'High,
note = "Ontoref applies its own LogConfig and DaemonConfig contracts in .ontoref/contracts.ncl — the config surface is self-demonstrated, not just specified." },
{ from = "config-surface", to = "adopt-ontoref-tooling", kind = 'Complements, weight = 'Medium,
note = "Consumer projects adopting ontoref can annotate their config structs with #[derive(ConfigFields)] to participate in the coherence registry." },
# Registry Credential Vault edges
{ from = "registry-credential-vault", to = "domain-extension-system", kind = 'DependsOn, weight = 'High,
note = "Provisioning domain uses registry credentials for OCI artifact push/pull — the vault is what makes ontoref OCI discovery possible in domain projects." },
{ from = "registry-credential-vault", to = "adopt-ontoref-tooling", kind = 'Complements, weight = 'High,
note = "Migration 0016 propagates the credential vault pattern to consumer projects — adding sops config to project.ncl and credential_sops to registry entries." },
{ from = "registry-credential-vault", to = "protocol-not-runtime", kind = 'Complements, weight = 'Medium,
note = "Vault belongs to the reflection layer, not the protocol. Protocol declares the credential_sops pattern; vault.nu and secrets.nu implement it operationally." },
{ from = "registry-credential-vault", to = "unified-auth-model", kind = 'Complements, weight = 'Medium,
note = "Both use age encryption. Auth model secures the daemon API; credential vault secures OCI registry access. Independent mechanisms with the same cryptographic foundation." },
# Level Hierarchy and Mode Resolution edges
{ from = "level-hierarchy-resolution", to = "domain-extension-system", kind = 'Complements, weight = 'High,
note = "Domain extensions (Level 2) now formally declare their hierarchy position via level.index=Domain and mode strategy fields — making the implicit traversal explicit." },
{ from = "level-hierarchy-resolution", to = "manifest-self-description", kind = 'ManifestsIn, weight = 'High,
note = "level field in manifest_type is the ADR-018 mechanism for level identity — manifest.ncl is the source of truth for hierarchy position, queryable via ore describe project." },
{ from = "level-hierarchy-resolution", to = "reflection-modes", kind = 'Complements, weight = 'High,
note = "Mode strategy (Override/Delegate/Merge/Compose) is declared on _ModeBase — every mode file now carries its resolution contract alongside its DAG steps." },
{ from = "level-hierarchy-resolution", to = "protocol-migration-system", kind = 'Complements, weight = 'High,
note = "Migration 0017 propagates level identity and strategy declarations to all consumer projects — applied this session to provisioning, libre-daoshi, libre-wuji." },
{ from = "level-hierarchy-resolution", to = "dag-formalized", kind = 'ManifestsIn, weight = 'Medium,
note = "Level hierarchy is a DAG of specialization: each instance knows its domain parent, each domain knows the base. Traversal is graph traversal, not implicit lookup." },
# MCP and GraphQL surface edges
{ from = "mcp-surface", to = "ontoref-daemon", kind = 'DependsOn, weight = 'High,
note = "MCP server is compiled into ontoref-daemon; runtime-toggleable via ServiceFlags (ADR-014)." },
{ from = "mcp-surface", to = "unified-auth-model", kind = 'DependsOn, weight = 'High,
note = "MCP Bearer auth uses the same POST /sessions session token as the REST API." },
{ from = "graphql-surface", to = "ontoref-daemon", kind = 'DependsOn, weight = 'High,
note = "GraphQL endpoint compiled into ontoref-daemon; runtime-toggleable via ServiceFlags (ADR-014)." },
{ from = "graphql-surface", to = "unified-auth-model", kind = 'DependsOn, weight = 'High,
note = "GraphQL auth uses Argon2id token from config.graphql.read_token_hash — separate from project keys." },
{ from = "graphql-surface", to = "mcp-surface", kind = 'Complements, weight = 'Medium,
note = "GraphQL and MCP are complementary surfaces: MCP for structured tool invocation, GraphQL for ad-hoc graph queries and subscriptions." },
# Dao discipline — surface the read-tensions-first Practice from the named tensions it operationalizes
{ from = "ontology-vs-reflection", to = "read-tensions-first", kind = 'ManifestsIn, weight = 'High,
note = "The named Spiral between contract definition and operational mechanism is the primary tension this Practice asks analysts to engage. Yang reasoning (pick a side) collapses it; this Practice asks for synthesis-state characterization." },
{ from = "formalization-vs-adoption", to = "read-tensions-first", kind = 'ManifestsIn, weight = 'Medium,
note = "This Practice is itself in tension with adoption friction — minimal structural enforcement is chosen so the discipline doesn't become ceremony. Heavier mechanisms (ADR schema constraints, schema pole field) deferred until adoption proves the discipline lands." },
{ from = "read-tensions-first", to = "dag-formalized", kind = 'Complements, weight = 'Medium,
note = "If protocol knowledge is DAG-formalized then analyses ABOUT the protocol must reference the DAG nodes — specifically the named Tension nodes — rather than reason from outside it. read-tensions-first extends DAG formalization from project state to project reasoning." },
],
}

View file

@ -1,26 +0,0 @@
let d = import "../ontology/defaults/gate.ncl" in
{
membranes = [
d.make_membrane {
id = "protocol-adoption-gate",
name = "Protocol Adoption Gate",
description = "Controls which projects are recognized as onref-compliant. A project passes the gate when its .ontology/ validates against the onref schema contract.",
permeability = 'Low,
accepts = ['EcosystemRelevance],
protects = ["protocol stability", "schema versioning guarantees"],
opening_condition = {
max_tension_dimensions = 2,
pending_transitions = 3,
core_stable = true,
description = "Project implements the full onref schema contract and exports a valid .ontology/core.ncl.",
},
closing_condition = "When the project's .ontology/ fails schema validation after a breaking protocol change.",
max_duration = 'Indefinite,
protocol = 'Observe,
active = false,
},
],
}

View file

@ -1,642 +0,0 @@
let m = import "../ontology/defaults/manifest.ncl" in
m.make_manifest {
project = "ontoref",
repo_kind = 'Framework,
level = { index = 'Base, name = "ontoref-base" },
description = "Protocol specification and tooling layer for structured self-knowledge in software projects. Provides schemas, Nushell automation, and Rust crates so projects can describe what they are, record architectural decisions, track operational state, and execute formalized procedures as typed, queryable artifacts.",
content_assets = [
m.make_asset {
id = "logo-horizontal",
kind = 'Logo,
source_path = "assets/branding/ontoref-h.svg",
variants = ["assets/branding/ontoref-h-static.svg", "assets/branding/ontoref-dark-h.svg", "assets/branding/ontoref-mono-black-h.svg", "assets/branding/ontoref-mono-white-h.svg"],
description = "Primary horizontal logo — animated SVG with static and dark/mono variants.",
},
m.make_asset {
id = "logo-vertical",
kind = 'Logo,
source_path = "assets/branding/ontoref-v.svg",
variants = ["assets/branding/ontoref-v-static.svg", "assets/branding/ontoref-dark-v.svg", "assets/branding/ontoref-mono-black-v.svg", "assets/branding/ontoref-mono-white-v.svg"],
description = "Vertical logo — animated SVG with static and dark/mono variants.",
},
m.make_asset {
id = "logo-icon",
kind = 'Icon,
source_path = "assets/branding/ontoref-icon.svg",
variants = ["assets/branding/ontoref-icon-static.svg"],
description = "Square icon mark — animated and static variants.",
},
m.make_asset {
id = "logo-text",
kind = 'Logo,
source_path = "assets/branding/ontoref-text.svg",
description = "Logotype text-only mark.",
},
m.make_asset {
id = "logo-pakua",
kind = 'Logo,
source_path = "assets/branding/pakua/ontoref_pakua_img.svg",
variants = ["assets/branding/pakua/ontoref-pakua-dark-v.svg"],
description = "Pakua symbol variant of the logo.",
},
m.make_asset {
id = "diagram-architecture",
kind = 'Diagram,
source_path = "assets/architecture.svg",
description = "Current architecture diagram showing the three-layer protocol model.",
},
m.make_asset {
id = "screenshot-graph-dark",
kind = 'Screenshot,
source_path = "assets/ontoref_graph_view-dark.png",
variants = ["assets/ontoref_graph_view-light.png"],
description = "Graph view UI screenshot — dark and light variants.",
},
m.make_asset {
id = "presentation-deck",
kind = 'Document,
source_path = "assets/presentation/slides.md",
description = "Slidev presentation deck for ontoref protocol introduction.",
},
],
templates = [
m.make_template {
id = "project-full-adoption-prompt",
kind = 'AgentPrompt,
source_path = "reflection/templates/project-full-adoption-prompt.md",
description = "Master adoption prompt for new and existing projects: protocol infrastructure, ontology enrichment, config surface (nickel-validated-overrides + ConfigFields derive), API surface (#[onto_api]), and manifest self-interrogation (capabilities/requirements/critical_deps). Orchestrates all other templates.",
},
m.make_template {
id = "update-ontology-prompt",
kind = 'AgentPrompt,
source_path = "reflection/templates/update-ontology-prompt.md",
description = "8-phase ontology enrichment prompt: core.ncl nodes/edges, state.ncl dimension transitions, manifest assets, connections, ADR check_hint migration. Called from Phase 2 of project-full-adoption-prompt.",
},
m.make_template {
id = "manifest-self-interrogation-prompt",
kind = 'AgentPrompt,
source_path = "reflection/templates/manifest-self-interrogation-prompt.md",
description = "Focused prompt for populating capabilities[], requirements[], and critical_deps[] in manifest.ncl. Called from Phase 5 of project-full-adoption-prompt.",
},
m.make_template {
id = "vendor-frontend-assets-prompt",
kind = 'AgentPrompt,
source_path = "reflection/templates/vendor-frontend-assets-prompt.md",
description = "Guide for vendoring frontend JS dependencies locally: directory layout (assets/vendor/), just recipe structure (assets.just with pinned version variables), Tera template integration, CDN asset verification steps, and agent execution checklist. Reusable across any ontoref-protocol project with a static-file-serving daemon.",
},
],
consumption_modes = [
m.make_consumption_mode {
consumer = 'Developer,
needs = ['OntologyExport],
audit_level = 'Standard,
description = "Clones repo, runs ./ontoref, imports Nushell modules. Uses reflection tooling to manage project self-description.",
},
m.make_consumption_mode {
consumer = 'Agent,
needs = ['OntologyExport, 'JsonSchema],
audit_level = 'Quick,
description = "Queries 19 capabilities, 31 ontology nodes, 10 ADRs, and 19 executable modes via describe capabilities (CLI), MCP tools (33), or daemon API. SessionStart hook auto-loads project identity and manifest health. Guards block mode execution on constraint violations.",
},
],
justfile = {
system = 'Import,
required_modules = ["build", "test", "dev", "ci", "assets"],
required_recipes = ["default"],
},
claude = {
guidelines = ["rust", "nushell", "nickel"],
session_hook = true,
stratum_commands = true,
},
config_surface = m.make_config_surface {
config_root = ".ontoref",
entry_point = "config.ncl",
kind = 'SingleFile,
contracts_path = ".ontoref",
sections = [
m.make_config_section {
id = "nickel_import_paths",
file = "config.ncl",
description = "Ordered list of directories added to NICKEL_IMPORT_PATH when invoking nickel.",
rationale = "Ontoref resolves ontology schemas, ADRs, and reflection schemas through this path list. Order matters: earlier entries shadow later ones.",
mutable = true,
consumers = [
m.make_config_consumer {
id = "env",
kind = 'NuScript,
ref = "reflection/modules/env.nu",
fields = ["nickel_import_paths"],
},
m.make_config_consumer {
id = "daemon-main",
kind = 'RustStruct,
ref = "crates/ontoref-daemon/src/main.rs",
fields = ["nickel_import_paths"],
},
],
},
m.make_config_section {
id = "ui",
file = "config.ncl",
description = "Daemon HTTP/UI settings: template directory, static assets, TLS certs, logo override.",
rationale = "Allows dev-mode templates to be served from the source tree instead of the installed path, and TLS to be toggled without recompiling.",
mutable = true,
consumers = [
m.make_config_consumer {
id = "daemon-main",
kind = 'RustStruct,
ref = "crates/ontoref-daemon/src/main.rs",
fields = ["templates_dir", "public_dir", "tls_cert", "tls_key", "logo"],
},
],
},
m.make_config_section {
id = "log",
file = "config.ncl",
contract = "contracts.ncl → LogConfig",
description = "Daemon structured logging: level, rotation policy, archive and retention.",
rationale = "Daily rotation with 7-file retention keeps log footprint bounded; separate archive path allows cold storage without disrupting active logs.",
mutable = true,
consumers = [
m.make_config_consumer {
id = "daemon-main",
kind = 'RustStruct,
ref = "crates/ontoref-daemon/src/main.rs",
fields = ["level", "path", "rotation", "compress", "archive", "max_files"],
},
],
},
m.make_config_section {
id = "mode_run",
file = "config.ncl",
description = "ACL rules for which actors may execute which reflection modes.",
rationale = "Agent and CI actors need unrestricted mode access; human actors are gated per mode to prevent accidental destructive operations.",
mutable = true,
consumers = [
m.make_config_consumer {
id = "daemon-main",
kind = 'RustStruct,
ref = "crates/ontoref-daemon/src/main.rs",
fields = ["rules"],
},
],
},
m.make_config_section {
id = "nats_events",
file = "config.ncl",
description = "NATS event bus integration: enabled flag, server URL, emit/subscribe topic lists, handlers directory.",
rationale = "Disabled by default to keep ontoref zero-dependency for projects without a NATS deployment. Feature-gated in the daemon crate.",
mutable = true,
consumers = [
m.make_config_consumer {
id = "daemon-main",
kind = 'RustStruct,
ref = "crates/ontoref-daemon/src/main.rs",
fields = ["enabled", "url", "emit", "subscribe", "handlers_dir"],
},
],
},
m.make_config_section {
id = "actor_init",
file = "config.ncl",
description = "Per-actor bootstrap: which reflection mode to auto-run on first invocation.",
rationale = "Agents always auto-run 'describe capabilities' so they orient themselves before acting; developers and CI start clean.",
mutable = true,
consumers = [
m.make_config_consumer {
id = "env",
kind = 'NuScript,
ref = "reflection/modules/env.nu",
fields = ["actor", "mode", "auto_run"],
},
],
},
m.make_config_section {
id = "quick_actions",
file = "config.ncl",
description = "Shortcut actions surfaced in the daemon UI dashboard: id, label, icon, category, mode, allowed actors.",
rationale = "Frequently used modes (generate-mdbook, sync-ontology, coder-workflow) promoted to one-click access without navigating the modes list.",
mutable = true,
consumers = [
m.make_config_consumer {
id = "daemon-ui",
kind = 'RustStruct,
ref = "crates/ontoref-daemon/src/ui/handlers.rs",
fields = ["id", "label", "icon", "category", "mode", "actors"],
},
],
},
m.make_config_section {
id = "daemon",
file = "config.ncl",
contract = "contracts.ncl → DaemonConfig",
description = "Runtime overrides for daemon CLI defaults: port, timeouts, sweep intervals, notification limits.",
rationale = "All fields are optional — absent fields use the daemon's built-in CLI defaults. Set only when the defaults need project-specific tuning without rebuilding the binary.",
mutable = true,
consumers = [
m.make_config_consumer {
id = "daemon-config",
kind = 'RustStruct,
ref = "crates/ontoref-daemon/src/config.rs → DaemonRuntimeConfig",
fields = ["port", "idle_timeout", "invalidation_interval", "actor_sweep_interval", "actor_stale_timeout", "max_notifications", "notification_ack_required"],
},
],
},
],
},
capabilities = [
m.make_capability {
id = "protocol-spec",
name = "Protocol Specification",
summary = "Typed NCL schemas for nodes, edges, ADRs, state, gates, and manifests — the contract layer that projects implement to describe themselves.",
rationale = "Projects need a contract layer to describe what they are — not just code comments. NCL provides typed, queryable, git-versionable schemas with contract enforcement at export time. Alternatives (TOML/JSON/YAML) lack contracts; Rust-only structs are not adoption-friendly.",
how = "ontology/schemas/ defines all type contracts (core, manifest, gate, state, content). adrs/adr-schema.ncl defines the ADR lifecycle contract with typed constraints (Cargo/Grep/NuCmd/ApiCall/FileExists checks). ontology/defaults/ exposes builders (make_node, make_edge, make_adr) so consumer projects never write raw NCL records. nickel export validates against declared contracts before any JSON reaches Rust or Nushell.",
artifacts = ["ontology/schemas/", "ontology/defaults/", "adrs/adr-schema.ncl", "adrs/adr-defaults.ncl", "reflection/schemas/"],
nodes = ["dag-formalized", "protocol-not-runtime", "adr-lifecycle", "ontoref-ontology-crate", "ontology-three-file-split", "adr-node-linkage", "personal-ontology-schemas"],
},
m.make_capability {
id = "daemon-api",
name = "Daemon HTTP + MCP + GraphQL Surface",
summary = "HTTP UI, annotated REST API catalog, MCP server (stdio + streamable-HTTP), GraphQL endpoint (queries/mutations/subscriptions), SSE notifications, per-file versioning, and NCL export cache.",
rationale = "Agents and developers need a queryable interface to ontology state without spawning nickel on every request. The NCL export cache reduces full-sync from ~2m42s to <30s. MCP gives agents structured tool invocation. GraphQL gives ad-hoc graph traversal and live subscriptions. Both surfaces are optional features runtime-toggleable without restart.",
how = "START daemon: `just build-daemon` (compiles with db,nats,ui,mcp,graphql features) then `ontoref-daemon-boot` or `ontoref-daemon-boot --dry-run`. Default bind: http://127.0.0.1:7891. --- MCP (stdio): run `ontoref-daemon --mcp-stdio`; the AI client auto-discovers the tool list. MCP (HTTP): configure client with url=http://127.0.0.1:7891/mcp; auth=Bearer <session-token>. Get session token: POST /sessions {\"project\":\"<slug>\",\"key\":\"<project-key>\"}. CLI auto-injects ONTOREF_TOKEN. --- GRAPHQL: endpoint is http://127.0.0.1:7891/graphql (POST for queries/mutations, GET for GraphiQL IDE, /graphql/ws for subscriptions). If auth is enabled (graphql.read_token_hash in config.ncl), all requests require Authorization: Bearer <token>. Generate token: `just generate-graphql-token` prints TOKEN + HASH; add HASH to config.ncl graphql.read_token_hash. GraphiQL with pre-auth: GET /graphql?token=<TOKEN> — injects Bearer header and WS connectionParam automatically. --- TOGGLE services: PUT /api/services/mcp {\"enabled\":false} (daemon admin Bearer) or UI manage page toggle buttons. --- AUTH: all surfaces use the same session token from POST /sessions. GraphQL has a separate read_token_hash (simpler for read-only agent access without a project key).",
artifacts = [
"crates/ontoref-daemon/",
"crates/ontoref-daemon/src/mcp/mod.rs",
"crates/ontoref-daemon/src/graphql/mod.rs",
"GET /api/catalog",
"GET /services",
"PUT /services/:service",
"POST /graphql",
"GET /graphql (GraphiQL IDE)",
"GET /graphql/ws (WebSocket subscriptions)",
"POST /mcp (streamable-HTTP)",
"MCP stdio: ontoref-daemon --mcp-stdio",
"justfiles/build.just (generate-graphql-token, generate-admin-key)",
],
adrs = ["adr-002", "adr-004", "adr-005", "adr-007", "adr-008", "adr-014"],
nodes = ["ontoref-daemon", "mcp-surface", "graphql-surface", "api-catalog-surface", "config-surface", "unified-auth-model"],
},
m.make_capability {
id = "reflection-modes",
name = "Reflection Mode DAG Engine",
summary = "19 executable NCL DAG workflows with typed steps, dependency graphs, 5 error strategies, actor filtering, guards (pre-flight constraint checks that block execution on violations), and convergence loops (re-execute until a condition is met). Modes cover sync, validation, content generation, project scaffolding, ADR lifecycle, and protocol adoption.",
rationale = "Structured procedures expressed as typed DAGs rather than ad-hoc scripts. Every step has a declared dep graph — the executor validates it before running. Agent-safe: modes are NCL contracts, not imperative scripts, so agents can read and reason about them before execution. Guards implement the Active Partner pattern — the protocol pushes back before executing if constraints are violated. Convergence implements the Refinement Loop pattern — modes iterate until a condition is met rather than running once blindly.",
how = "crates/ontoref-reflection loads a mode NCL file, validates the DAG contract (no cycles, declared deps exist), then executes steps via Nushell subprocesses. Each step declares: id, action, cmd, actor, depends_on (Always/OnSuccess/OnFailure), on_error (Stop/Continue/Retry/Fallback/Branch), and verify. Guards run before steps — each has a cmd, reason, and severity (Block/Warn). Converge runs after steps — condition cmd checked, re-executes up to max_iterations using RetryFailed or RetryAll strategy.",
artifacts = ["reflection/modes/", "reflection/modules/", "crates/ontoref-reflection/", "reflection/schema.ncl"],
nodes = ["reflection-modes", "adopt-ontoref-tooling", "ontoref-reflection-crate", "content-modes"],
},
m.make_capability {
id = "run-step-tracking",
name = "Run/Step Execution Tracking",
summary = "Persistent execution tracking for mode runs: start runs, report steps with status/artifacts/warnings, validate dependency satisfaction, verify mode completion. File-based storage under .coder/<actor>/runs/ as JSONL.",
rationale = "Mode execution without tracking is fire-and-forget. Agents and CI need to resume interrupted runs, audit which steps passed or failed, and verify all required steps completed before declaring success. File-based storage keeps tracking git-versionable and debuggable without requiring a database.",
how = "ontoref run start <mode> creates a run directory with run.json metadata and empty steps.jsonl. step report validates the step exists in the mode DAG and that blocking dependencies (OnSuccess) are satisfied before appending. mode complete checks all steps are reported and no Stop-strategy failures block completion. current.json tracks the active run per actor.",
artifacts = ["reflection/modules/run.nu", "reflection/bin/ontoref.nu", ".coder/"],
nodes = ["reflection-modes", "coder-process-memory"],
},
m.make_capability {
id = "agent-task-composer",
name = "Agent Task Composer",
summary = "Form-driven prompt composition UI: select NCL form templates, fill structured fields, preview assembled markdown, send to AI providers, or export as .plan.ncl with execution DAG and lifecycle FSM (Draft/Sent/Accepted/Executed/Archived).",
rationale = "Agents need structured task input, not free-text prompts. Forms enforce required fields and typed options. Plans decouple task definition from execution — a plan can be reviewed before running, and its status tracked across sessions. The compose UI bridges form schemas to AI provider APIs without custom integration per provider.",
how = "reflection/forms/*.ncl define form schemas (elements: section_header/text/select/multiselect/editor/confirm). Daemon UI (/ui/compose) renders forms dynamically, assembles markdown from field values, and offers two actions: (1) send to AI provider via HTTP, (2) save as .plan.md + .plan.ncl. Plan NCL carries template ID, field values, linked backlog items, linked ADRs, and an optional execution DAG referencing modes. Plan status is a 5-state FSM tracked in the NCL file.",
artifacts = ["reflection/forms/", "reflection/schemas/plan.ncl", "reflection/templates/", "crates/ontoref-daemon/templates/pages/compose.html"],
nodes = ["reflection-modes", "ontoref-daemon"],
},
m.make_capability {
id = "backlog-graduation",
name = "Backlog with Typed Graduation Paths",
summary = "Work item tracking (Todo/Wish/Idea/Bug/Debt) with priority, status lifecycle, and typed graduation: items promote to ADRs, reflection modes, state transitions, or PR items via ontoref backlog promote. Notification-based approval workflow for status changes.",
rationale = "Work items are not flat lists — they have destinations. A bug graduates to a fix PR. An idea graduates to an ADR. A wish graduates to a reflection mode. Typed graduation makes the promotion path explicit and machine-readable, enabling agents to propose promotions and humans to approve them via the notification system.",
how = "reflection/backlog.ncl stores items typed by reflection/schemas/backlog.ncl (Item with graduates_to: Adr/Mode/StateTransition/PrItem). CLI commands: backlog add/done/cancel/promote/propose-status/approve. propose-status emits a custom notification (backlog_review) to the daemon; admin approves via notification UI, triggering the status update. roadmap command cross-references items with state.ncl dimensions.",
artifacts = ["reflection/backlog.ncl", "reflection/schemas/backlog.ncl", "reflection/modules/backlog.nu"],
nodes = ["reflection-modes"],
},
m.make_capability {
id = "notification-system",
name = "Notification & Approval Workflows",
summary = "Event broadcast system with SSE streaming, per-actor acknowledgment, custom event emission, and cross-project notifications. Supports approval workflows via custom notification kinds (e.g. backlog_review). Ring buffer storage with configurable retention.",
rationale = "File watchers detect changes but actors need to be notified, not poll. SSE provides real-time push without WebSocket complexity. Per-actor ACK prevents one actor's acknowledgment from hiding notifications from others. Custom events enable approval workflows without a separate messaging system.",
how = "crates/ontoref-daemon/src/notifications.rs implements a per-project ring buffer (DashMap<project, Vec<Notification>>, default 256 entries). File changes emit OntologyChanged/AdrChanged/ReflectionChanged events. User code emits custom events via push_custom(kind, title, payload, source_actor, source_project). SSE broadcast via tokio::broadcast::Sender. Per-token ACK tracking (ack_all/ack_one). Backlog propose-status uses this for approval workflows.",
artifacts = ["crates/ontoref-daemon/src/notifications.rs", "reflection/modules/backlog.nu"],
nodes = ["ontoref-daemon"],
},
m.make_capability {
id = "coder-process-memory",
name = "Coder Process Memory Pipeline",
summary = "Structured knowledge capture across actor sessions: init author workspaces, record JSON entries to queryable JSONL, triage inbox markdown into categories, publish to shared space with attribution, graduate to committed knowledge. 9-step DAG workflow.",
rationale = "Session knowledge evaporates between conversations. The coder pipeline captures insights, decisions, and investigations as structured entries — immediately queryable via coder log, promotable across actor boundaries, and graduable to committed project knowledge. Process memory bridges the gap between ephemeral sessions and persistent project knowledge.",
how = "coder-workflow mode (9 steps): init-author creates .coder/<author>/ with inbox/ and author.ncl. record-json appends structured entries to entries.jsonl. dump-markdown copies raw files to inbox. triage classifies inbox files into categories with companion NCL. query filters entries by tag/kind/domain/author. publish promotes entries to .coder/general/<category>/ with attribution. graduate copies to reflection/knowledge/. validate-ontology-core/state ensure ontology reflects new knowledge.",
artifacts = ["reflection/modes/coder-workflow.ncl", "reflection/modules/coder.nu", "reflection/schemas/coder.ncl", ".coder/"],
nodes = ["coder-process-memory"],
},
m.make_capability {
id = "qa-knowledge-store",
name = "Q&A Knowledge Store",
summary = "Persistent question-answer pairs captured during development sessions, typed by QaEntry schema, with concurrent-safe NCL mutations, tag-based queries, verification status, and cross-references to ontology nodes and ADRs.",
rationale = "Recurring questions deserve persistent answers. Q&A entries bridge session boundaries — knowledge captured in one conversation is available in all future sessions. NCL storage keeps entries git-versionable and queryable without a database. ADR-003 records the decision to persist Q&A as NCL rather than browser storage.",
how = "reflection/qa.ncl stores entries typed by reflection/schemas/qa.ncl (QaEntry: id, question, answer, actor, tags, related, verified). crates/ontoref-daemon/src/ui/qa_ncl.rs performs line-level NCL surgery for add/update/remove with NclWriteLock for concurrency safety. Auto-incrementing IDs (qa-001, qa-002...). Accessible via MCP (ontoref_qa_list/add), HTTP (/qa-json), and CLI.",
artifacts = ["reflection/qa.ncl", "reflection/schemas/qa.ncl", "crates/ontoref-daemon/src/ui/qa_ncl.rs"],
adrs = ["adr-003"],
nodes = ["qa-knowledge-store"],
},
m.make_capability {
id = "form-input-system",
name = "NCL Form Input System",
summary = "Declarative form schemas in NCL with typed elements (text/select/multiselect/editor/confirm/section), dual backend execution (CLI interactive prompts or daemon HTTP), and integration with the compose pipeline and template generation.",
rationale = "Structured input collection avoids free-text ambiguity. Forms declared as NCL schemas are versionable, composable, and renderable by both CLI and web UI without separate implementations. The same form drives interactive terminal prompts and web form submission.",
how = "reflection/forms/*.ncl define form schemas as arrays of typed elements. form list discovers available forms. form run <name> --backend cli|daemon executes the form interactively or via HTTP. Collected field values feed into the compose pipeline (prompt assembly), template rendering (J2), or direct mode execution. Forms for ADR creation, project onboarding, backlog items, and config editing.",
artifacts = ["reflection/forms/", "reflection/modules/form.nu", "crates/ontoref-daemon/templates/pages/compose.html"],
nodes = ["reflection-modes", "ontoref-daemon"],
},
m.make_capability {
id = "template-generation",
name = "Template Generation Pipeline",
summary = "Jinja2 template rendering composing data from ontology nodes, crate metadata, ADRs, and mode definitions to generate NCL files, Nushell scripts, config variants, and adoption artifacts.",
rationale = "Code generation from project knowledge eliminates manual transcription between the ontology and implementation artifacts. Templates bridge the gap between typed NCL declarations and executable scripts or config files.",
how = "reflection/templates/*.j2 are Jinja2 templates processed by generator.nu. Templates extract data from ontology (nodes, edges), crate metadata (Cargo.toml), ADRs (constraints), and modes (steps). Output includes: adr.ncl.j2 (ADR files from form data), adopt_ontoref.nu.j2 (adoption scripts), create_project.nu.j2 (project scaffolding), config-production.ncl.j2 (config variants). describe.nu also supports per-section Tera templates in layouts/.",
artifacts = ["reflection/templates/", "reflection/modules/generator.nu", "reflection/modules/describe.nu"],
nodes = ["adopt-ontoref-tooling"],
},
m.make_capability {
id = "describe-query-layer",
name = "Self-Knowledge Query Layer",
summary = "10 describe subcommands providing multi-level views of project knowledge: project (identity/axioms/tensions/practices), capabilities (modes/commands/flags/backlog), constraints (ADR hard/soft), state (FSM dimensions), tools, features, impact analysis, guides (full agent onboarding context), diff (recent changes), and workspace (multi-project overview).",
rationale = "Projects need queryable self-knowledge at different abstraction levels. A human needs a different view than an agent. describe provides semantic zoom — from one-line project identity to full constraint sets with check commands. This is the primary interface for agents to orient themselves before acting.",
how = "reflection/modules/describe.nu implements 10 subcommands, each collecting data from NCL exports (ontology, ADRs, manifest, backlog, modes) and rendering as text (human) or JSON (agent). Actor filtering applies to API routes, mode visibility, and constraint relevance. describe guides aggregates all subcommands into a single comprehensive output for agent cold-start.",
artifacts = ["reflection/modules/describe.nu", "reflection/bin/ontoref.nu"],
nodes = ["describe-query-layer", "manifest-self-description"],
},
m.make_capability {
id = "drift-observation",
name = "Passive Drift Detection & Sync",
summary = "Background file watcher detecting divergence between .ontology/ declarations and actual project artifacts. 7-step sync mode: scan project structure, diff against ontology, detect doc drift (Jaccard similarity), propose patches, review, apply, verify. Emits ontology_drift notifications when drift is found.",
rationale = "Ontology declarations rot when code evolves without updating .ontology/core.ncl. Passive detection catches drift before it compounds. The sync mode bridges observation to action — detect, propose, and apply in a structured DAG rather than manual file editing.",
how = "crates/ontoref-daemon/src/ui/drift_watcher.rs watches crates/, .ontology/, adrs/, reflection/modes/ for changes. After 15s debounce, runs sync scan + sync diff. If MISSING/STALE/DRIFT/BROKEN items found, emits ontology_drift notification. sync-ontology mode (7 steps) provides the full remediation workflow: scan extracts pub API via cargo doc JSON, diff categorizes divergence, propose generates NCL patches, apply writes changes, verify runs nickel typecheck.",
artifacts = ["reflection/modes/sync-ontology.ncl", "reflection/modules/sync.nu", "crates/ontoref-daemon/src/ui/drift_watcher.rs"],
nodes = ["drift-observation", "reflection-modes"],
},
m.make_capability {
id = "quick-actions",
name = "Quick Actions Catalog",
summary = "Configurable shortcut grid mapping UI buttons to reflection mode execution. Declared in .ontoref/config.ncl with id, label, icon, category, mode reference, and actor ACL. One-click mode execution from the daemon UI without CLI.",
rationale = "Frequently used modes (sync-ontology, generate-mdbook, coder-workflow) need one-click access. The quick actions grid reduces friction between knowing a mode exists and executing it — especially for developers who prefer the UI over CLI.",
how = ".ontoref/config.ncl declares quick_actions array. Daemon UI (/actions) renders a categorized grid of action cards. Click triggers daemon handler run_action_by_id() which spawns tokio::process::Command with ./ontoref run <mode_id>. New actions can be added via MCP (ontoref_action_add) or by editing config.ncl directly.",
artifacts = [".ontoref/config.ncl", "crates/ontoref-daemon/templates/pages/actions.html", "reflection/modes/"],
nodes = ["quick-actions", "ontoref-daemon"],
},
m.make_capability {
id = "protocol-migration",
name = "Protocol Migration System",
summary = "Progressive, idempotent protocol upgrades for consumer projects. Each migration carries a typed check (FileExists/Grep/NuCmd) and human-readable instructions. No state file — the check result IS the applied state. Consumer projects run ontoref migrate pending to discover and apply upgrades.",
rationale = "Protocol changes that only apply to ontoref itself are useless for the ecosystem. The migration system is the propagation mechanism — without it, consumer projects never learn about protocol evolution. ADR-010 records this decision.",
how = "reflection/migrations/NNNN-slug.ncl files define migrations with id, slug, description, check (tag + parameters), and instructions. ontoref migrate list shows all migrations. migrate pending filters to unapplied (check fails). migrate show <id> displays instructions. Checks are idempotent: FileExists tests path existence, Grep tests pattern presence, NuCmd runs a Nushell expression and checks exit code.",
artifacts = ["reflection/migrations/", "reflection/modules/migrate.nu", "reflection/bin/ontoref.nu"],
adrs = ["adr-010"],
nodes = ["protocol-migration-system"],
},
m.make_capability {
id = "config-surface-management",
name = "Config Surface with NCL Validation",
summary = "Structured config system: NCL contracts validate all fields, override-layer mutation preserves original files, zero-maintenance field registry via #[derive(ConfigFields)] + inventory auto-discovers which Rust struct consumes which NCL field, coherence endpoint detects unclaimed fields and consumer fields missing from NCL export.",
rationale = "Config drift between NCL declarations and Rust consumers is invisible until runtime. The config coherence system makes it detectable at build time. Override-layer mutation ensures original NCL files (with comments, contracts, formatting) are never modified — changes are always additive. ADR-008 records this decision.",
how = "crates/ontoref-daemon/src/config.rs defines DaemonRuntimeConfig. #[derive(ConfigFields)] on Rust structs registers consumed fields via inventory::submit!. GET /config/coherence cross-references NCL export keys against registered consumers. .ontoref/contracts.ncl defines LogConfig, DaemonConfig contracts. config show/verify/audit/apply CLI commands for inspection and mutation.",
artifacts = [".ontoref/config.ncl", ".ontoref/contracts.ncl", "crates/ontoref-daemon/src/config.rs", "crates/ontoref-daemon/src/config_coherence.rs", "crates/ontoref-derive/src/lib.rs"],
adrs = ["adr-008"],
nodes = ["config-surface", "ontoref-daemon", "daemon-config-management"],
},
m.make_capability {
id = "project-onboarding",
name = "Project Onboarding",
summary = "CLI and form-driven onboarding for new projects into the ontoref protocol: scaffolds .ontology/, adrs/, reflection/, .ontoref/config.ncl, and registers the project with the daemon. Includes templates for project.ncl and remote-project.ncl.",
rationale = "Adoption friction is the main barrier to protocol spread. Onboarding must be a guided, repeatable process — not a manual checklist. The adopt_ontoref mode and forms reduce the first adoption to answering structured questions.",
how = "reflection/modes/adopt_ontoref.ncl orchestrates the full onboarding DAG. reflection/forms/adopt_ontoref.ncl collects project metadata. reflection/templates/adopt_ontoref.nu.j2 generates the adoption script. templates/ provides starter files for all protocol directories. install/gen-projects.nu handles daemon project registration.",
artifacts = ["reflection/modes/adopt_ontoref.ncl", "reflection/forms/adopt_ontoref.ncl", "templates/", "install/gen-projects.nu", "reflection/bin/ontoref.nu", "reflection/bin/init-repo.nu"],
nodes = ["project-onboarding", "adopt-ontoref-tooling", "ci-pipelines"],
},
m.make_capability {
id = "vcs-abstraction",
name = "VCS Abstraction Layer",
summary = "Uniform VCS API over jj and git. Filesystem-based detection — no config required. Provides show-committed, restore-file, remote-url, current-branch, uncommitted-files, and commit-count with identical semantics. All ontoref modules consume vcs.nu; git is the default, jj is opt-in.",
rationale = "Hardcoding git commands in modules creates friction for jj users. A thin abstraction layer decouples protocol tooling from VCS choice.",
how = "reflection/modules/vcs.nu exports typed commands. opmode.nu and reflection/hooks/git-event.nu import it. Detection is filesystem-only (.jj/ presence) — no env vars, no config files.",
artifacts = ["reflection/modules/vcs.nu"],
nodes = ["vcs-abstraction"],
},
m.make_capability {
id = "agent-workspace-orchestration",
name = "Agent Workspace Orchestration",
summary = "jj + ontoref + Radicle lifecycle wrapper for agent workspaces. jjw agent create/step/publish/merge/discard cover the full agent task lifecycle. jjw-ncl-merge.nu is a jj merge driver for .ontology/ NCL conflicts. Both jj and Radicle are entirely optional — not required by the protocol.",
rationale = "Agent sessions working on concurrent tasks need isolated workspaces. jj workspaces are cheaper than git worktrees and support NCL conflict resolution natively via custom merge tools.",
how = "reflection/bin/jjw.nu wraps jj workspace commands, ontoref run/step calls, and optional rad patch open. reflection/bin/jjw-ncl-merge.nu is registered in ~/.config/jj/config.toml as a merge tool — not automatically installed.",
artifacts = ["reflection/bin/jjw.nu", "reflection/bin/jjw-ncl-merge.nu"],
nodes = ["agent-workspace-orchestration"],
},
m.make_capability {
id = "web-presence",
name = "Web Presence",
summary = "Landing page at assets/web/ describing the ontoref protocol to external audiences. Bilingual (EN/ES), covers protocol layers, yin/yang duality, crates, and adoption path.",
rationale = "The protocol needs a public-facing explanation beyond the README. The landing page serves as the first point of contact for potential adopters.",
how = "assets/web/src/index.html is the source. assets/web/index.html is the built output. README.md links to it. assets/architecture.svg provides the visual architecture diagram.",
artifacts = ["assets/web/src/index.html", "assets/web/index.html", "README.md", "assets/architecture.svg"],
nodes = ["web-presence"],
},
m.make_capability {
id = "search-bookmarks",
name = "Search Bookmarks",
summary = "Persistent bookmark store for ontology graph search results. Entries typed as BookmarkEntry with node cross-references, sequential IDs, concurrent-safe NCL mutations via NclWriteLock. Accessible from daemon search UI and MCP.",
rationale = "Graph exploration sessions produce valuable search paths that are lost when the page reloads. Bookmarks persist the trail of ontology exploration across sessions.",
how = "reflection/search_bookmarks.ncl stores entries typed by reflection/schemas/search_bookmarks.ncl. crates/ontoref-daemon/src/ui/search_bookmarks_ncl.rs performs line-level NCL surgery with NclWriteLock. Sequential IDs (sb-001, sb-002...). MCP tool ontoref_bookmark_add for agent access.",
artifacts = ["reflection/search_bookmarks.ncl", "reflection/schemas/search_bookmarks.ncl", "crates/ontoref-daemon/src/ui/search_bookmarks_ncl.rs"],
nodes = ["search-bookmarks"],
},
],
requirements = [
m.make_requirement {
id = "nushell",
name = "Nushell",
env = 'Both,
kind = 'Tool,
version = "0.110.0",
required = true,
impact = "All reflection modes and the ./ontoref dispatcher are Nushell scripts. Without Nu nothing executes — no mode runs, no describe subcommands, no sync.",
provision = "https://www.nushell.sh/ — cargo install nu or OS package manager.",
},
m.make_requirement {
id = "nickel",
name = "Nickel",
env = 'Both,
kind = 'Tool,
version = "",
required = true,
impact = "All schema evaluation, ADR parsing, config export blocked. Daemon NCL cache inoperable. Config surface mutation cannot validate overrides.",
provision = "https://nickel-lang.org/ — cargo install nickel-lang-cli or nix flake.",
},
m.make_requirement {
id = "rust-nightly",
name = "Rust nightly toolchain",
env = 'Development,
kind = 'Tool,
version = "",
required = true,
impact = "cargo +nightly fmt fails — pre-commit hook blocks all commits.",
provision = "rustup toolchain install nightly",
},
m.make_requirement {
id = "surrealdb",
name = "SurrealDB",
env = 'Production,
kind = 'Service,
version = "",
required = false,
impact = "Daemon db feature disabled; ontology not projected into DB. Daemon still works via --no-default-features for local-only use.",
provision = "https://surrealdb.com/ — binary or container. Feature-gated: cargo build -p ontoref-daemon --no-default-features omits it.",
},
m.make_requirement {
id = "stratumiops",
name = "stratumiops repo checkout",
env = 'Development,
kind = 'Infrastructure,
version = "",
required = false,
impact = "ontoref-daemon (db/nats features) and ontoref-reflection (nats feature) cannot build. Build with --no-default-features to work without it.",
provision = "git clone at ../../../stratumiops relative to this repo root.",
},
m.make_requirement {
id = "oci-registry",
name = "OCI Registry (ZOT or compatible)",
env = 'Both,
kind = 'Service,
version = "",
required = true,
impact = "Without a reachable OCI registry, ontoref cannot push or pull domain artifacts, mode artifacts, or src-vault credential archives. OCI discovery — the mechanism by which actors find published modes and domains — is fully blocked. Credential distribution (ADR-017) is also blocked: src-vault:latest cannot be fetched, so no actor can resolve registry credentials.",
provision = "ZOT recommended: https://zotregistry.dev — docker run ghcr.io/project-zot/zot-linux-amd64. Compatible with any OCI Distribution Spec v1.1 registry. Endpoint declared in registry_provides.registries[].endpoint in .ontology/manifest.ncl.",
},
m.make_requirement {
id = "oras",
name = "oras CLI",
env = 'Both,
kind = 'Tool,
version = "1.2.0",
required = true,
impact = "All OCI artifact operations fail: domain-push, domain-pull, mode-push, mode-pull, src-vault push/pull. OCI discovery and credential distribution both blocked.",
provision = "brew install oras # or: https://oras.land/docs/installation",
},
m.make_requirement {
id = "cosign",
name = "cosign",
env = 'Both,
kind = 'Tool,
version = "2.0.0",
required = true,
impact = "src-vault OCI artifacts cannot be signed on push or verified on pull. An unsigned vault artifact cannot be trusted — a substituted artifact with malicious credentials would be undetectable.",
provision = "brew install cosign # or: https://docs.sigstore.dev/cosign/system_config/installation/",
},
m.make_requirement {
id = "sops",
name = "sops",
env = 'Both,
kind = 'Tool,
version = "3.8.0",
required = true,
impact = "Registry credential resolution blocked. access.sops.yaml cannot be decrypted — oras calls have no credentials. All registry operations requiring auth fail.",
provision = "brew install sops # or: https://github.com/getsops/sops/releases",
},
],
critical_deps = [
m.make_critical_dep {
id = "nickel-lang",
name = "nickel-lang",
ref = "crates.io: nickel-lang-core / nickel-lang-cli",
used_for = "Schema evaluation, contract enforcement, config export, ADR parsing. Every nickel export call in the daemon cache and in Nushell modules.",
failure_impact = "Total loss of typed schema layer. All NCL export operations fail. Daemon cache inoperable. Config override mutation cannot validate before committing. describe guides and ontoref_guides MCP tool return empty data.",
mitigation = "Pin to a specific Nickel release in PATH. Nickel is a subprocess dep (never linked into Rust binary) — breakage manifests at runtime as nickel export exit code != 0, which all callers handle gracefully (daemon-export-safe returns null, callers use | default []).",
},
m.make_critical_dep {
id = "inventory",
name = "inventory",
ref = "crates.io: inventory 0.3",
used_for = "#[onto_api] HTTP catalog registration and #[derive(ConfigFields)] config coherence registry. Both use inventory::submit! at link time; GET /api/catalog and GET /config/coherence use inventory::iter! at runtime.",
failure_impact = "GET /api/catalog returns empty. Config coherence endpoint loses Rust struct field data. ontoref_api_catalog MCP tool blind. API version change would require updating both onto_api and ConfigFields derive macros simultaneously.",
mitigation = "inventory uses linker sections (zero runtime overhead). Version is pinned in Cargo.toml. ontoref-derive and ontoref-ontology both declare it — version must stay in sync.",
},
m.make_critical_dep {
id = "axum",
name = "axum",
ref = "crates.io: axum",
used_for = "All 11 UI pages and REST API endpoints in ontoref-daemon. Router, handlers, extractors, middleware.",
failure_impact = "Daemon does not compile. Full HTTP surface down: UI, REST API, MCP over HTTP, session management, config surface endpoints.",
mitigation = "ontoref-ontology and the ./ontoref CLI are axum-free. Reflection modes, ADR tooling, and describe subcommands continue working without the daemon.",
},
],
# ADR-017 — registry credential vault declaration. credential_sops paths are
# relative to the synced src-vault root at ~/.config/ontoref/vaults/ontoref/src-vault/.
# This entry is the canonical shape that consumer manifests follow via migration 0016.
registry_provides = m.make_registry_provides {
participant = "ontoref",
registries = m.make_registries_config {
default = "primary",
registries = [
m.make_registry_entry {
id = "primary",
endpoint = "reg.librecloud.online",
role = 'primary,
tls = true,
namespaces = {
own = ["domains/ontoref/", "modes/ontoref/"],
prefixes = ["domains/ontoref/", "modes/ontoref/"],
},
credential_sops = "registry/ro.sops.yaml", # RO — pull/list (cdci, ontoref, agent)
credential_sops_rw = "registry/rw.sops.yaml", # RW — push (admin, developer)
},
],
},
},
layers = [
m.make_layer {
id = "protocol",
paths = ["ontology/", "adrs/", "reflection/schemas/"],
committed = true,
description = "Protocol specification: Nickel schemas, ADR tooling, and reflection schemas. The contract layer that projects implement.",
},
m.make_layer {
id = "tooling",
paths = ["reflection/", "install/", "nats/", "templates/", "ontoref"],
committed = true,
description = "Operational tooling: Nushell modules, modes, forms, dispatcher, bash entry point, install scripts, default config resources, and NATS stream topology.",
},
m.make_layer {
id = "crates",
paths = ["crates/", "Cargo.toml"],
committed = true,
description = "Rust implementation: ontoref-ontology (load/query .ontology/ as typed structs) and ontoref-reflection (execute reflection modes against project state).",
},
m.make_layer {
id = "self-description",
paths = [".ontology/"],
committed = true,
description = "Ontoref consuming ontoref: the project's own ontology, state, gate, and manifest.",
},
m.make_layer {
id = "process",
paths = [".coder/"],
committed = false,
description = "Session artifacts: plans, investigations, summaries. Process memory for actors.",
},
],
}

File diff suppressed because one or more lines are too long

View file

@ -1,61 +0,0 @@
let W = import "../reflection/schemas/workflow.ncl" in
let D = import "../reflection/defaults/workflow.ncl" in
{
layers = [
# ── Commit-fast: local pre-commit, independent ────────────────────────────
{
id = "commit-fast",
trigger = 'OnCommit,
validations = [
"rust-fmt",
"rust-clippy",
"nextest-ci-test",
"deny-subset",
"docs-drift",
"manifest-coverage",
"markdownlint",
],
builds = [],
distributions = [],
providers = [W.pre_commit "pre-commit"],
},
# ── CI-standard: push/PR pipeline, independent ────────────────────────────
{
id = "ci-standard",
trigger = 'OnPR,
validations = [
"rust-clippy-all",
"nextest-ci",
"deny-subset",
"docs-check",
"nickel-typecheck",
"nushell-check",
],
builds = ["release-native"],
distributions = [],
providers = [
W.woodpecker ".woodpecker/ci.yml",
W.justfile "ci-standard",
],
},
# ── CI-exhaustive: main/tag pipeline, independent ─────────────────────────
{
id = "ci-exhaustive",
trigger = 'OnMainMerge,
validations = ["deny-all", "geiger"],
builds = ["release-musl-x86", "sbom"],
distributions = [],
providers = [
W.woodpecker ".woodpecker/ci-exhaustive.yml",
W.justfile "ci-exhaustive",
],
},
],
validations = D.validations,
builds = D.builds,
distributions = {},
}

Binary file not shown.

View file

@ -1,320 +0,0 @@
# CI System - Configuration Guide
**Installed**: 2026-03-12
**Detected Languages**: rust, nushell, nickel, bash, markdown
---
## Quick Start
### Option 1: Using configure.sh (Recommended)
A convenience script is installed in `.typedialog/ci/`:
```bash
# Use web backend (default) - Opens in browser
.typedialog/ci/configure.sh
# Use TUI backend - Terminal interface
.typedialog/ci/configure.sh tui
# Use CLI backend - Command-line prompts
.typedialog/ci/configure.sh cli
```
**This script automatically:**
- Sources `.typedialog/ci/envrc` for environment setup
- Loads defaults from `config.ncl` (Nickel format)
- Uses cascading search for fragments (local → Tools)
- Creates backup before overwriting existing config
- Saves output in Nickel format using nickel-roundtrip with documented template
- Generates `config.ncl` compatible with `nickel doc` command
### Option 2: Direct TypeDialog Commands
Use TypeDialog nickel-roundtrip directly with manual paths:
#### Web Backend (Recommended - Easy Viewing)
```bash
cd .typedialog/ci # Change to CI directory
source envrc # Load environment
typedialog-web nickel-roundtrip config.ncl form.toml \
--output config.ncl \
--ncl-template $TOOLS_PATH/dev-system/ci/templates/config.ncl.j2
```
#### TUI Backend
```bash
cd .typedialog/ci
source envrc
typedialog-tui nickel-roundtrip config.ncl form.toml \
--output config.ncl \
--ncl-template $TOOLS_PATH/dev-system/ci/templates/config.ncl.j2
```
#### CLI Backend
```bash
cd .typedialog/ci
source envrc
typedialog nickel-roundtrip config.ncl form.toml \
--output config.ncl \
--ncl-template $TOOLS_PATH/dev-system/ci/templates/config.ncl.j2
```
**Note:** The `--ncl-template` flag uses a Tera template that adds:
- Descriptive comments for each section
- Documentation compatible with `nickel doc config.ncl`
- Consistent formatting and structure
**All backends will:**
- Show only options relevant to your detected languages
- Guide you through all configuration choices
- Validate your inputs
- Generate config.ncl in Nickel format
### Option 3: Manual Configuration
Edit `config.ncl` directly:
```bash
vim .typedialog/ci/config.ncl
```
---
## Configuration Format: Nickel
**This project uses Nickel format by default** for all configuration files.
### Why Nickel
- ✅ **Typed configuration** - Static type checking with `nickel typecheck`
- ✅ **Documentation** - Generate docs with `nickel doc config.ncl`
- ✅ **Validation** - Built-in schema validation
- ✅ **Comments** - Rich inline documentation support
- ✅ **Modular** - Import/export system for reusable configs
### Nickel Template
The output structure is controlled by a **Tera template** at:
- **Tools default**: `$TOOLS_PATH/dev-system/ci/templates/config.ncl.j2`
- **Local override**: `.typedialog/ci/config.ncl.j2` (optional)
**To customize the template:**
```bash
# Copy the default template
cp $TOOLS_PATH/dev-system/ci/templates/config.ncl.j2 \
.typedialog/ci/config.ncl.j2
# Edit to add custom comments, documentation, or structure
vim .typedialog/ci/config.ncl.j2
# Your template will now be used automatically
```
**Template features:**
- Customizable comments per section
- Control field ordering
- Add project-specific documentation
- Configure output for `nickel doc` command
### TypeDialog Environment Variables
You can customize TypeDialog behavior with environment variables:
```bash
# Web server configuration
export TYPEDIALOG_PORT=9000 # Port for web backend (default: 9000)
export TYPEDIALOG_HOST=localhost # Host binding (default: localhost)
# Localization
export TYPEDIALOG_LANG=en_US.UTF-8 # Form language (default: system locale)
# Run with custom settings
TYPEDIALOG_PORT=8080 .typedialog/ci/configure.sh web
```
**Common use cases:**
```bash
# Access from other machines in network
TYPEDIALOG_HOST=0.0.0.0 TYPEDIALOG_PORT=8080 .typedialog/ci/configure.sh web
# Use different port if 9000 is busy
TYPEDIALOG_PORT=3000 .typedialog/ci/configure.sh web
# Spanish interface
TYPEDIALOG_LANG=es_ES.UTF-8 .typedialog/ci/configure.sh web
```
## Configuration Structure
Your config.ncl is organized in the `ci` namespace (Nickel format):
```nickel
{
ci = {
project = {
name = "rust",
detected_languages = ["rust, nushell, nickel, bash, markdown"],
primary_language = "rust",
},
tools = {
# Tools are added based on detected languages
},
features = {
# CI features (pre-commit, GitHub Actions, etc.)
},
ci_providers = {
# CI provider configurations
},
},
}
```
## Available Fragments
Tool configurations are modular. Check `.typedialog/ci/fragments/` for:
- rust-tools.toml - Tools for rust
- nushell-tools.toml - Tools for nushell
- nickel-tools.toml - Tools for nickel
- bash-tools.toml - Tools for bash
- markdown-tools.toml - Tools for markdown
- general-tools.toml - Cross-language tools
- ci-providers.toml - GitHub Actions, Woodpecker, etc.
## Cascading Override System
This project uses a **local → Tools cascading search** for all resources:
### How It Works
Resources are searched in priority order:
1. **Local files** (`.typedialog/ci/`) - **FIRST** (highest priority)
2. **Tools files** (`$TOOLS_PATH/dev-system/ci/`) - **FALLBACK** (default)
### Affected Resources
| Resource | Local Path | Tools Path |
|----------|------------|------------|
| Fragments | `.typedialog/ci/fragments/` | `$TOOLS_PATH/dev-system/ci/forms/fragments/` |
| Schemas | `.typedialog/ci/schemas/` | `$TOOLS_PATH/dev-system/ci/schemas/` |
| Validators | `.typedialog/ci/validators/` | `$TOOLS_PATH/dev-system/ci/validators/` |
| Defaults | `.typedialog/ci/defaults/` | `$TOOLS_PATH/dev-system/ci/defaults/` |
| Nickel Template | `.typedialog/ci/config.ncl.j2` | `$TOOLS_PATH/dev-system/ci/templates/config.ncl.j2` |
### Environment Setup (.envrc)
The `.typedialog/ci/.envrc` file configures search paths:
```bash
# Source this file to load environment
source .typedialog/ci/.envrc
# Or use direnv for automatic loading
echo 'source .typedialog/ci/.envrc' >> .envrc
```
**What's in .envrc:**
```bash
export NICKEL_IMPORT_PATH="schemas:$TOOLS_PATH/dev-system/ci/schemas:validators:..."
export TYPEDIALOG_FRAGMENT_PATH=".:$TOOLS_PATH/dev-system/ci/forms"
export NCL_TEMPLATE="<local or Tools path to config.ncl.j2>"
export TYPEDIALOG_PORT=9000 # Web server port
export TYPEDIALOG_HOST=localhost # Web server host
export TYPEDIALOG_LANG="${LANG}" # Form localization
```
### Creating Overrides
**By default:** All resources come from Tools (no duplication).
**To customize:** Create file in local directory with same name:
```bash
# Override a fragment
cp $TOOLS_PATH/dev-system/ci/fragments/rust-tools.toml \
.typedialog/ci/fragments/rust-tools.toml
# Edit your local version
vim .typedialog/ci/fragments/rust-tools.toml
# Override Nickel template (customize comments, structure, nickel doc output)
cp $TOOLS_PATH/dev-system/ci/templates/config.ncl.j2 \
.typedialog/ci/config.ncl.j2
# Edit to customize documentation and structure
vim .typedialog/ci/config.ncl.j2
# Now your version will be used instead of Tools version
```
**Benefits:**
- ✅ Override only what you need
- ✅ Everything else stays synchronized with Tools
- ✅ No duplication by default
- ✅ Automatic updates when Tools is updated
**See:** `$TOOLS_PATH/dev-system/ci/docs/cascade-override.md` for complete documentation.
## Testing Your Configuration
### Validate Configuration
```bash
nu $env.TOOLS_PATH/dev-system/ci/scripts/validator.nu \
--config .typedialog/ci/config.ncl \
--project . \
--namespace ci
```
### Regenerate CI Files
```bash
nu $env.TOOLS_PATH/dev-system/ci/scripts/generate-configs.nu \
--config .typedialog/ci/config.ncl \
--templates $env.TOOLS_PATH/dev-system/ci/templates \
--output . \
--namespace ci
```
## Common Tasks
### Add a New Tool
Edit `config.ncl` and add under `ci.tools`:
```nickel
{
ci = {
tools = {
newtool = {
enabled = true,
install_method = "cargo",
version = "latest",
},
},
},
}
```
### Disable a Feature
```toml
[ci.features]
enable_pre_commit = false
```
## Need Help
For detailed documentation, see:
- $env.TOOLS_PATH/dev-system/ci/docs/configuration-guide.md
- $env.TOOLS_PATH/dev-system/ci/docs/installation-guide.md

View file

@ -1,203 +0,0 @@
# CI Configuration - Nickel Format
# Auto-generated by dev-system CI installer
#
# This file is managed by TypeDialog using nickel-roundtrip.
# Edit via: .typedialog/ci/configure.sh
# Or manually edit and validate with: nickel typecheck config.ncl
#
# Documentation: nickel doc config.ncl
{
# CI namespace - all configuration lives under 'ci'
ci = {
# Project Information
# Detected languages and primary language for this project
project = {
# Project name
name = "",
# Project description
description = "",
# Project website or documentation site URL
site_url = "",
# Project repository URL (GitHub, GitLab, etc.)
repo_url = "",
# Languages detected in codebase (auto-detected by installer)
detected_languages = [
"rust",
"markdown",
"nickel"
],
# Primary language (determines default tooling)
primary_language = "rust",
},
# CI Tools Configuration
# Each tool can be enabled/disabled and configured here
tools = {
# Taplo - TOML formatter and linter
taplo = {
enabled = true,
install_method = "cargo",
},
# YAMLlint - YAML formatter and linter
yamllint = {
enabled = true,
install_method = "pip",
},
# Clippy - Rust linting tool
clippy = {
enabled = true,
install_method = "cargo",
deny_warnings = true,
},
# Cargo Audit - Security vulnerability scanner
audit = {
enabled = true,
install_method = "cargo",
},
# Cargo Deny - Dependency checker
deny = {
enabled = true,
install_method = "cargo",
},
# Cargo SBOM - Software Bill of Materials
sbom = {
enabled = true,
install_method = "cargo",
},
# LLVM Coverage - Code coverage tool
llvm-cov = {
enabled = true,
install_method = "cargo",
},
# Shellcheck - Bash/shell script linter
shellcheck = {
enabled = true,
install_method = "brew",
},
# Shfmt - Shell script formatter
shfmt = {
enabled = true,
install_method = "brew",
},
# Markdownlint - Markdown linter
markdownlint = {
enabled = true,
install_method = "npm",
},
# Vale - Prose linter
vale = {
enabled = true,
install_method = "brew",
},
# Nickel - Configuration language type checker
nickel = {
enabled = true,
install_method = "brew",
check_all = true,
},
# NuShell - Shell script validator
nushell = {
enabled = true,
install_method = "builtin",
check_all = true,
},
# Ruff - Fast Python linter
ruff = {
enabled = true,
install_method = "pip",
},
# Black - Python code formatter
black = {
enabled = true,
install_method = "pip",
},
# Mypy - Python static type checker
mypy = {
enabled = false,
install_method = "pip",
},
# Pytest - Python testing framework
pytest = {
enabled = true,
install_method = "pip",
},
# Golangci-lint - Go linter aggregator
"golangci-lint" = {
enabled = true,
install_method = "brew",
},
# Gofmt - Go code formatter
gofmt = {
enabled = true,
install_method = "builtin",
},
# Staticcheck - Go static analysis
staticcheck = {
enabled = true,
install_method = "brew",
},
# Gosec - Go security checker
gosec = {
enabled = false,
install_method = "brew",
},
# ESLint - JavaScript linter
eslint = {
enabled = true,
install_method = "npm",
},
# Prettier - Code formatter
prettier = {
enabled = true,
install_method = "npm",
},
# TypeScript - Type checking
typescript = {
enabled = false,
install_method = "npm",
},
# Jest - JavaScript testing framework
jest = {
enabled = true,
install_method = "npm",
},
},
# CI Features
# High-level feature flags for CI behavior
features = {
enable_ci_cd = true,
enable_pre_commit = true,
generate_taplo_config = true,
generate_contributing = true,
generate_security = true,
generate_code_of_conduct = true,
generate_dockerfiles = true,
enable_cross_compilation = true,
},
# CI Provider Configurations
# Settings for GitHub Actions, Woodpecker, GitLab CI, etc.
ci_providers = {
# GitHub Actions
github_actions = {
enabled = true,
branches_push = "main,develop",
branches_pr = "main",
},
# Woodpecker CI
woodpecker = {
enabled = true,
},
},
# CI Settings
settings = {
parallel_jobs = 1,
job_timeout_minutes = 1,
require_status_checks = true,
run_on_draft_prs = true,
},
},
}

View file

@ -1,203 +0,0 @@
# CI Configuration - Nickel Format
# Auto-generated by dev-system CI installer
#
# This file is managed by TypeDialog using nickel-roundtrip.
# Edit via: .typedialog/ci/configure.sh
# Or manually edit and validate with: nickel typecheck config.ncl
#
# Documentation: nickel doc config.ncl
{
# CI namespace - all configuration lives under 'ci'
ci = {
# Project Information
# Detected languages and primary language for this project
project = {
# Project name
name = "",
# Project description
description = "",
# Project website or documentation site URL
site_url = "",
# Project repository URL (GitHub, GitLab, etc.)
repo_url = "",
# Languages detected in codebase (auto-detected by installer)
detected_languages = [
"rust",
"markdown",
"nickel"
],
# Primary language (determines default tooling)
primary_language = "rust",
},
# CI Tools Configuration
# Each tool can be enabled/disabled and configured here
tools = {
# Taplo - TOML formatter and linter
taplo = {
enabled = true,
install_method = "cargo",
},
# YAMLlint - YAML formatter and linter
yamllint = {
enabled = true,
install_method = "pip",
},
# Clippy - Rust linting tool
clippy = {
enabled = true,
install_method = "cargo",
deny_warnings = true,
},
# Cargo Audit - Security vulnerability scanner
audit = {
enabled = true,
install_method = "cargo",
},
# Cargo Deny - Dependency checker
deny = {
enabled = true,
install_method = "cargo",
},
# Cargo SBOM - Software Bill of Materials
sbom = {
enabled = true,
install_method = "cargo",
},
# LLVM Coverage - Code coverage tool
llvm-cov = {
enabled = true,
install_method = "cargo",
},
# Shellcheck - Bash/shell script linter
shellcheck = {
enabled = true,
install_method = "brew",
},
# Shfmt - Shell script formatter
shfmt = {
enabled = true,
install_method = "brew",
},
# Markdownlint - Markdown linter
markdownlint = {
enabled = true,
install_method = "npm",
},
# Vale - Prose linter
vale = {
enabled = true,
install_method = "brew",
},
# Nickel - Configuration language type checker
nickel = {
enabled = true,
install_method = "brew",
check_all = true,
},
# NuShell - Shell script validator
nushell = {
enabled = true,
install_method = "builtin",
check_all = true,
},
# Ruff - Fast Python linter
ruff = {
enabled = true,
install_method = "pip",
},
# Black - Python code formatter
black = {
enabled = true,
install_method = "pip",
},
# Mypy - Python static type checker
mypy = {
enabled = false,
install_method = "pip",
},
# Pytest - Python testing framework
pytest = {
enabled = true,
install_method = "pip",
},
# Golangci-lint - Go linter aggregator
"golangci-lint" = {
enabled = true,
install_method = "brew",
},
# Gofmt - Go code formatter
gofmt = {
enabled = true,
install_method = "builtin",
},
# Staticcheck - Go static analysis
staticcheck = {
enabled = true,
install_method = "brew",
},
# Gosec - Go security checker
gosec = {
enabled = false,
install_method = "brew",
},
# ESLint - JavaScript linter
eslint = {
enabled = true,
install_method = "npm",
},
# Prettier - Code formatter
prettier = {
enabled = true,
install_method = "npm",
},
# TypeScript - Type checking
typescript = {
enabled = false,
install_method = "npm",
},
# Jest - JavaScript testing framework
jest = {
enabled = true,
install_method = "npm",
},
},
# CI Features
# High-level feature flags for CI behavior
features = {
enable_ci_cd = true,
enable_pre_commit = true,
generate_taplo_config = true,
generate_contributing = true,
generate_security = true,
generate_code_of_conduct = true,
generate_dockerfiles = true,
enable_cross_compilation = true,
},
# CI Provider Configurations
# Settings for GitHub Actions, Woodpecker, GitLab CI, etc.
ci_providers = {
# GitHub Actions
github_actions = {
enabled = true,
branches_push = "main,develop",
branches_pr = "main",
},
# Woodpecker CI
woodpecker = {
enabled = true,
},
},
# CI Settings
settings = {
parallel_jobs = 1,
job_timeout_minutes = 1,
require_status_checks = true,
run_on_draft_prs = true,
},
},
}

View file

@ -1,116 +0,0 @@
#!/usr/bin/env bash
# CI Configuration Script
# Auto-generated by dev-system/ci installer
#
# Interactive configuration for CI tools using TypeDialog.
# Uses Nickel format for configuration files.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TYPEDIALOG_CI="${SCRIPT_DIR}"
# Source envrc to load fragment paths and other environment variables
if [[ -f "${TYPEDIALOG_CI}/envrc" ]]; then
# shellcheck source=/dev/null
source "${TYPEDIALOG_CI}/envrc"
fi
# Configuration files
FORM_FILE="${TYPEDIALOG_CI}/form.toml"
CONFIG_FILE="${TYPEDIALOG_CI}/config.ncl"
# NCL_TEMPLATE is set by envrc (cascading: local → Tools)
# If not set, use default from Tools
NCL_TEMPLATE="${NCL_TEMPLATE:-${TOOLS_PATH}/dev-system/ci/templates/config.ncl.j2}"
# TypeDialog environment variables (can be overridden)
# Port for web backend (default: 9000)
export TYPEDIALOG_PORT="${TYPEDIALOG_PORT:-9000}"
# Host for web backend (default: localhost)
export TYPEDIALOG_HOST="${TYPEDIALOG_HOST:-localhost}"
# Locale for form localization (default: system locale)
export TYPEDIALOG_LANG="${TYPEDIALOG_LANG:-${LANG:-en_US.UTF-8}}"
# Detect which TypeDialog backend to use (default: web)
BACKEND="${1:-web}"
# Validate backend
case "$BACKEND" in
cli|tui|web)
;;
*)
echo "Usage: $0 [cli|tui|web]"
echo ""
echo "Launches TypeDialog for interactive CI configuration."
echo "Backend options:"
echo " cli - Command-line interface (simple prompts)"
echo " tui - Terminal UI (interactive panels)"
echo " web - Web server (browser-based) [default]"
exit 1
;;
esac
# Check if form exists
if [[ ! -f "$FORM_FILE" ]]; then
echo "Error: Form file not found: $FORM_FILE"
exit 1
fi
# Create backup if config exists
if [[ -f "$CONFIG_FILE" ]]; then
BACKUP="${CONFIG_FILE}.$(date +%Y%m%d_%H%M%S).bak"
cp "$CONFIG_FILE" "$BACKUP"
echo " Backed up existing config to: $(basename "$BACKUP")"
fi
# Launch TypeDialog with Nickel roundtrip (preserves Nickel format)
echo "🔧 Launching TypeDialog ($BACKEND backend)..."
echo ""
# Show web server info if using web backend
if [[ "$BACKEND" == "web" ]]; then
echo "🌐 Web server will start on: http://${TYPEDIALOG_HOST}:${TYPEDIALOG_PORT}"
echo " (Override with: TYPEDIALOG_PORT=8080 TYPEDIALOG_HOST=0.0.0.0 $0)"
echo ""
fi
# Build nickel-roundtrip command with optional template
NCL_TEMPLATE_ARG=""
if [[ -f "$NCL_TEMPLATE" ]]; then
NCL_TEMPLATE_ARG="--ncl-template $NCL_TEMPLATE"
echo " Using Nickel template: $NCL_TEMPLATE"
fi
case "$BACKEND" in
cli)
typedialog nickel-roundtrip "$CONFIG_FILE" "$FORM_FILE" --output "$CONFIG_FILE" $NCL_TEMPLATE_ARG
;;
tui)
typedialog-tui nickel-roundtrip "$CONFIG_FILE" "$FORM_FILE" --output "$CONFIG_FILE" $NCL_TEMPLATE_ARG
;;
web)
typedialog-web nickel-roundtrip "$CONFIG_FILE" "$FORM_FILE" --output "$CONFIG_FILE" $NCL_TEMPLATE_ARG
;;
esac
EXIT_CODE=$?
if [[ $EXIT_CODE -eq 0 ]]; then
echo ""
echo "✅ Configuration saved to: $CONFIG_FILE"
echo ""
echo "Next steps:"
echo " - Review the configuration: cat $CONFIG_FILE"
echo " - Apply CI tools: (run your CI setup command)"
echo " - Re-run this script anytime to update: $0"
else
echo ""
echo "❌ Configuration cancelled or failed (exit code: $EXIT_CODE)"
if [[ -f "${CONFIG_FILE}.bak" ]]; then
echo " Previous config restored from backup"
fi
exit $EXIT_CODE
fi

View file

@ -1,27 +0,0 @@
# Auto-generated by dev-system/ci
#
# Cascading Path Strategy:
# 1. Local files in .typedialog/ci/ take precedence (overrides)
# 2. Central files in $TOOLS_PATH/dev-system/ci/ as fallback (defaults)
#
# To customize: Create file in .typedialog/ci/{schemas,validators,defaults,fragments}/
# Your local version will be used instead of the Tools version.
# Nickel import paths (cascading: local → Tools)
export NICKEL_IMPORT_PATH="schemas:$TOOLS_PATH/dev-system/ci/schemas:validators:$TOOLS_PATH/dev-system/ci/validators:defaults:$TOOLS_PATH/dev-system/ci/defaults"
# TypeDialog fragment search paths (cascading: local → Tools)
export TYPEDIALOG_FRAGMENT_PATH=".typedialog/ci:$TOOLS_PATH/dev-system/ci/forms"
# Nickel template for config.ncl generation (with cascading)
# Local template takes precedence if exists
if [[ -f ".typedialog/ci/config.ncl.j2" ]]; then
export NCL_TEMPLATE=".typedialog/ci/config.ncl.j2"
else
export NCL_TEMPLATE="$TOOLS_PATH/dev-system/ci/templates/config.ncl.j2"
fi
# TypeDialog web backend configuration (override if needed)
export TYPEDIALOG_PORT=${TYPEDIALOG_PORT:-9000}
export TYPEDIALOG_HOST=${TYPEDIALOG_HOST:-localhost}
export TYPEDIALOG_LANG=${TYPEDIALOG_LANG:-${LANG:-en_US.UTF-8}}

View file

@ -1,231 +0,0 @@
description = "Interactive configuration for continuous integration and code quality tools"
display_mode = "complete"
locales_path = ""
name = "CI Configuration Form"
[[elements]]
border_bottom = true
border_top = true
name = "project_header"
title = "📦 Project Information"
type = "section_header"
[[elements]]
help = "Name of the project"
name = "project_name"
nickel_path = [
"ci",
"project",
"name",
]
placeholder = "my-project"
prompt = "Project name"
required = true
type = "text"
[[elements]]
help = "Optional description"
name = "project_description"
nickel_path = [
"ci",
"project",
"description",
]
placeholder = "Brief description of what this project does"
prompt = "Project description"
required = false
type = "text"
[[elements]]
default = ""
help = "Project website or documentation site URL"
name = "project_site_url"
nickel_path = [
"ci",
"project",
"site_url",
]
placeholder = "https://example.com"
prompt = "Project Site URL"
required = false
type = "text"
[[elements]]
default = ""
help = "Project repository URL (GitHub, GitLab, etc.)"
name = "project_repo_url"
nickel_path = [
"ci",
"project",
"repo_url",
]
placeholder = "https://github.com/user/repo"
prompt = "Project Repo URL"
required = false
type = "text"
[[elements]]
border_bottom = true
border_top = true
name = "languages_header"
title = "🔍 Detected Languages"
type = "section_header"
[[elements]]
default = "rust"
display_mode = "grid"
help = "Select all languages detected or used in the project"
min_selected = 1
name = "detected_languages"
nickel_path = [
"ci",
"project",
"detected_languages",
]
prompt = "Which languages are used in this project?"
required = true
searchable = true
type = "multiselect"
[[elements.options]]
value = "rust"
label = "🦀 Rust"
[[elements.options]]
value = "nushell"
label = "🐚 NuShell"
[[elements.options]]
value = "nickel"
label = "⚙️ Nickel"
[[elements.options]]
value = "bash"
label = "🔧 Bash/Shell"
[[elements.options]]
value = "markdown"
label = "📝 Markdown/Documentation"
[[elements]]
help = "Main language used for defaults (e.g., in GitHub Actions workflows)"
name = "primary_language"
nickel_path = [
"ci",
"project",
"primary_language",
]
options_from = "detected_languages"
prompt = "Primary language"
required = true
type = "select"
default = "rust"
[[elements.options]]
value = "rust"
label = "🦀 Rust"
[[elements.options]]
value = "nushell"
label = "🐚 NuShell"
[[elements.options]]
value = "nickel"
label = "⚙️ Nickel"
[[elements.options]]
value = "bash"
label = "🔧 Bash"
[[elements.options]]
value = "markdown"
label = "📝 Markdown"
[[elements]]
includes = ["fragments/rust-tools.toml"]
name = "rust_tools_group"
type = "group"
when = "rust in detected_languages"
[[elements]]
includes = ["fragments/nushell-tools.toml"]
name = "nushell_tools_group"
type = "group"
when = "nushell in detected_languages"
[[elements]]
includes = ["fragments/nickel-tools.toml"]
name = "nickel_tools_group"
type = "group"
when = "nickel in detected_languages"
[[elements]]
includes = ["fragments/bash-tools.toml"]
name = "bash_tools_group"
type = "group"
when = "bash in detected_languages"
[[elements]]
includes = ["fragments/markdown-tools.toml"]
name = "markdown_tools_group"
type = "group"
when = "markdown in detected_languages"
[[elements]]
includes = ["fragments/general-tools.toml"]
name = "general_tools_group"
type = "group"
[[elements]]
border_bottom = true
border_top = true
name = "ci_cd_header"
title = "🔄 CI/CD Configuration"
type = "section_header"
[[elements]]
default = "true"
help = "Set up continuous integration and deployment pipelines"
name = "enable_ci_cd"
nickel_path = [
"ci",
"features",
"enable_ci_cd",
]
prompt = "Enable CI/CD integration?"
type = "confirm"
[[elements]]
includes = ["fragments/ci-providers.toml"]
name = "ci_providers_group"
type = "group"
when = "enable_ci_cd == true"
[[elements]]
includes = ["fragments/ci-settings.toml"]
name = "ci_settings_group"
type = "group"
when = "enable_ci_cd == true"
[[elements]]
includes = ["fragments/build-deployment.toml"]
name = "build_deployment_group"
type = "group"
when = "enable_ci_cd == true"
[[elements]]
includes = ["fragments/documentation.toml"]
name = "documentation_group"
type = "group"
[[elements]]
border_bottom = true
border_top = true
name = "confirmation_header"
title = "✅ Ready to Install"
type = "section_header"
[[elements]]
content = "Review your configuration above. After confirming, the CI system will be installed with your chosen settings."
name = "confirmation_footer"
type = "footer"

View file

@ -1,73 +0,0 @@
let C = import "contracts.ncl" in
{
nickel_import_paths = [".", ".ontology", "ontology/schemas", "adrs", "reflection/requirements", "reflection/schemas"],
ui = {
templates_dir = "crates/ontoref-daemon/templates",
public_dir = "crates/ontoref-daemon/public",
tls_cert = "",
tls_key = "",
logo = "ontoref-logo.svg",
},
log | C.LogConfig = {
level = "info",
path = "logs",
rotation = "daily",
compress = false,
archive = "logs-archive",
max_files = 7,
},
mode_run = {
rules = [
{ when = { mode_id = "validate-ontology" }, allow = true, reason = "validation always allowed" },
{ when = { actor = "agent" }, allow = true, reason = "agent actor always allowed" },
{ when = { actor = "ci" }, allow = true, reason = "ci actor always allowed" },
],
},
nats_events = {
enabled = false,
url = "nats://localhost:4222",
emit = [],
subscribe = [],
handlers_dir = "reflection/handlers",
},
actor_init = [
{ actor = "agent", mode = "describe capabilities", auto_run = true },
{ actor = "developer", mode = "", auto_run = false },
{ actor = "ci", mode = "", auto_run = false },
],
quick_actions = [
{
id = "gen-docs",
label = "Generate documentation",
icon = "book-open",
category = "docs",
mode = "generate-mdbook",
actors = ["developer", "agent"],
},
{
id = "sync-onto",
label = "Sync ontology",
icon = "refresh",
category = "sync",
mode = "sync-ontology",
actors = ["developer", "ci", "agent"],
},
{
id = "coder-workflow",
label = "Coder workflow",
icon = "code",
category = "process",
mode = "coder-workflow",
actors = ["developer", "agent"],
},
],
card = import "../card.ncl",
}

View file

@ -1,62 +0,0 @@
# Contracts for .ontoref/config.ncl sections.
#
# Applied in config.ncl with `section | C.SectionContract = { ... }`.
# Consumed by the daemon coherence / quickref tooling via the
# config_surface.sections[].contract field in .ontology/manifest.ncl.
let contract = std.contract in
# ── Primitive contracts ──────────────────────────────────────────────────────
let LogLevel = contract.from_validator (fun value =>
if std.array.elem value ["error", "warn", "info", "debug", "trace"] then
'Ok
else
'Error { message = "log.level must be one of: error, warn, info, debug, trace" }
) in
let LogRotation = contract.from_validator (fun value =>
if std.array.elem value ["daily", "hourly", "never"] then
'Ok
else
'Error { message = "log.rotation must be one of: daily, hourly, never" }
) in
let PositiveInt = contract.from_validator (fun value =>
if std.is_number value && value > 0 then
'Ok
else
'Error { message = "value must be a positive integer (> 0)" }
) in
let Port = contract.from_validator (fun value =>
if std.is_number value && value >= 1 && value <= 65535 then
'Ok
else
'Error { message = "port must be a number between 1 and 65535" }
) in
# ── Section contracts ────────────────────────────────────────────────────────
{
LogConfig = {
level | LogLevel | default = "info",
path | String | default = "logs",
rotation | LogRotation | default = "daily",
compress | Bool | default = false,
archive | String | default = "logs-archive",
max_files | PositiveInt | default = 7,
},
# All daemon fields are optional — they override CLI defaults only when set.
# Absent fields fall back to the daemon's built-in defaults (see Cli struct).
DaemonConfig = {
port | Port | optional,
idle_timeout | PositiveInt | optional,
invalidation_interval | PositiveInt | optional,
actor_sweep_interval | PositiveInt | optional,
actor_stale_timeout | PositiveInt | optional,
max_notifications | PositiveInt | optional,
notification_ack_required | Array String | default = [],
},
}

View file

@ -1,31 +0,0 @@
let s = import "ontoref-project.ncl" in
s.make_project {
slug = "ontoref",
root = "/Users/Akasha/Development/ontoref",
nickel_import_paths = [
"/Users/Akasha/Development/ontoref",
"/Users/Akasha/Development/ontoref/ontology",
],
keys = [],
# ADR-017 — registry credential vault. ontoref participates as a publisher in the
# libre-wuji registry under domains/ontoref/ and modes/ontoref/. Until the vault is
# bootstrapped (`./ontoref secrets bootstrap`), Layer 2 operations error nominally.
# This block is the canonical reference shape that consumer projects copy via
# migration 0016.
sops = {
enabled = true,
vault_id = "ontoref",
vault_backend = 'restic,
registry_endpoint = "reg.librecloud.online",
actor_key_bindings = {
developer = "developer",
ci = "cdci",
agent = "ontoref",
admin = "admin",
},
# master_key_path resolved from ~/.config/ontoref/config.ncl::vault.master_key_path.
# Override here only if this project requires a different .kage than the global default.
},
}

View file

@ -1,103 +0,0 @@
# Actor session roles — typed contract for role definitions used by the
# ontoref daemon actor registry.
#
# The `role` field in ActorSession is validated against this file when present.
# A role defines which UI capabilities are granted and what UI defaults apply.
#
# Load example:
# nickel export --format json .ontoref/roles.ncl
let permission_type = [|
'read_backlog,
'write_backlog,
'read_adrs,
'write_adrs,
'run_modes,
'emit_notifications,
'manage_projects,
'manage_sessions,
|] in
let nav_mode_type = [| 'icons, 'icons_text, 'text |] in
let theme_type = [| 'dark, 'light, 'system |] in
let role_def_type = {
id | String,
label | String,
description | String | default = "",
permissions | Array permission_type,
ui_defaults | {
theme | theme_type | default = 'system,
nav_mode | nav_mode_type | default = 'icons_text,
} | default = {},
} in
{
roles | Array role_def_type = [
{
id = "admin",
label = "Admin",
description = "Full access — manage projects, sessions, ADRs, backlog, and emit notifications.",
permissions = [
'read_backlog,
'write_backlog,
'read_adrs,
'write_adrs,
'run_modes,
'emit_notifications,
'manage_projects,
'manage_sessions,
],
ui_defaults = { theme = 'dark, nav_mode = 'icons_text },
},
{
id = "developer",
label = "Developer",
description = "Standard development access — read/write backlog and ADRs, run modes.",
permissions = [
'read_backlog,
'write_backlog,
'read_adrs,
'write_adrs,
'run_modes,
'emit_notifications,
],
ui_defaults = { theme = 'system, nav_mode = 'icons_text },
},
{
id = "viewer",
label = "Viewer",
description = "Read-only access — view backlog, ADRs, notifications.",
permissions = [
'read_backlog,
'read_adrs,
],
ui_defaults = { theme = 'system, nav_mode = 'icons },
},
{
id = "agent",
label = "Agent",
description = "Automated agent — run modes, read/write backlog, emit notifications.",
permissions = [
'read_backlog,
'write_backlog,
'read_adrs,
'run_modes,
'emit_notifications,
],
ui_defaults = { theme = 'dark, nav_mode = 'icons },
},
{
id = "ci",
label = "CI",
description = "Continuous integration actor — read backlog and ADRs, run modes.",
permissions = [
'read_backlog,
'read_adrs,
'run_modes,
],
ui_defaults = { theme = 'dark, nav_mode = 'icons },
},
],
}

View file

@ -56,6 +56,14 @@ repos:
pass_filenames: false pass_filenames: false
stages: [pre-commit] stages: [pre-commit]
- id: glossary-drift
name: Glossary structural drift check (broken origin.ref, missing related_nodes, empty EN)
entry: bash -c 'ONTOREF_ROOT="$(pwd)" nu --no-config-file -c "use ./reflection/modules/sync.nu *; sync diff --glossary --fail-on-drift"'
language: system
files: (\.ontology/(core|glossary)\.ncl|reflection/defaults/glossary\.ncl|reflection/schemas/term\.ncl)$
pass_filenames: false
stages: [pre-commit]
- id: manifest-coverage - id: manifest-coverage
name: Manifest capability completeness name: Manifest capability completeness
entry: bash -c 'ONTOREF_ROOT="$(pwd)" ONTOREF_PROJECT_ROOT="$(pwd)" nu --no-config-file -c "use ./reflection/modules/sync.nu *; sync manifest-check"' entry: bash -c 'ONTOREF_ROOT="$(pwd)" ONTOREF_PROJECT_ROOT="$(pwd)" nu --no-config-file -c "use ./reflection/modules/sync.nu *; sync manifest-check"'

96
.woodpecker/release.yml Normal file
View file

@ -0,0 +1,96 @@
# Generated by ore workflow generate — layer: release
# Source: .ontoref/ontology/workflow.ncl — distributions catalog
# Do not edit manually — regenerate with: ore workflow generate --layer release
#
# Daemon image is an OPTIONAL accelerator per ADR-029. Bundles install a
# daemon-less CLI; the image serves container deployments.
# CI prereqs: per-arch nickel under dist/nickel/<triple>/nickel; cosign key in
# $COSIGN_KEY and release upload token in $RELEASE_TOKEN from the src-vault.
when:
event: [tag, manual]
steps:
build-release-musl-x86:
image: rust:latest
commands:
- cargo install cross --locked
- cross build --target x86_64-unknown-linux-musl --release
build-release-musl-arm64:
image: rust:latest
commands:
- cargo install cross --locked
- cross build --target aarch64-unknown-linux-musl --release
build-sbom:
image: rust:latest
commands:
- cargo install cargo-sbom --locked
- cargo sbom > sbom.json
build-bundle-linux-x86:
image: rust:latest
depends_on: ["build-release-musl-x86"]
commands:
- cargo install nu --locked
- nu install/assemble-bundle.nu --target x86_64-unknown-linux-musl --version "${CI_COMMIT_TAG}" --nickel dist/nickel/x86_64-unknown-linux-musl/nickel
build-bundle-linux-arm64:
image: rust:latest
depends_on: ["build-release-musl-arm64"]
commands:
- cargo install nu --locked
- nu install/assemble-bundle.nu --target aarch64-unknown-linux-musl --version "${CI_COMMIT_TAG}" --nickel dist/nickel/aarch64-unknown-linux-musl/nickel
smoke-bundle:
image: rust:latest
depends_on: ["build-release-musl-x86"]
commands:
- cargo install nu --locked
- nu install/assemble-bundle.nu smoke --target x86_64-unknown-linux-musl
dist-oci-image:
image: quay.io/buildah/stable
depends_on: ["smoke-bundle", "build-release-musl-x86", "build-release-musl-arm64"]
commands:
- buildah from --name ont-amd64 --arch amd64 alpine:3.21
- buildah copy ont-amd64 target/x86_64-unknown-linux-musl/release/ontoref-daemon /usr/local/bin/ontoref-daemon
- buildah copy ont-amd64 dist/nickel/x86_64-unknown-linux-musl/nickel /usr/local/bin/nickel
- buildah copy ont-amd64 .ontoref/reflection /opt/ontoref/reflection
- buildah copy ont-amd64 ontology /opt/ontoref/ontology
- buildah config --port 7891 --env ONTOREF_ROOT=/opt/ontoref --env NICKEL_IMPORT_PATH=/opt/ontoref/ontology/schemas:/opt/ontoref/ontology --entrypoint '["ontoref-daemon"]' ont-amd64
- buildah commit --manifest ontoref-daemon ont-amd64 reg.librecloud.online/ontoref/ontoref-daemon:"${CI_COMMIT_TAG}"-amd64
- buildah from --name ont-arm64 --arch arm64 alpine:3.21
- buildah copy ont-arm64 target/aarch64-unknown-linux-musl/release/ontoref-daemon /usr/local/bin/ontoref-daemon
- buildah copy ont-arm64 dist/nickel/aarch64-unknown-linux-musl/nickel /usr/local/bin/nickel
- buildah copy ont-arm64 .ontoref/reflection /opt/ontoref/reflection
- buildah copy ont-arm64 ontology /opt/ontoref/ontology
- buildah config --port 7891 --env ONTOREF_ROOT=/opt/ontoref --env NICKEL_IMPORT_PATH=/opt/ontoref/ontology/schemas:/opt/ontoref/ontology --entrypoint '["ontoref-daemon"]' ont-arm64
- buildah commit --manifest ontoref-daemon ont-arm64 reg.librecloud.online/ontoref/ontoref-daemon:"${CI_COMMIT_TAG}"-arm64
- buildah manifest push --all ontoref-daemon docker://reg.librecloud.online/ontoref/ontoref-daemon:"${CI_COMMIT_TAG}"
- cosign sign --key "$COSIGN_KEY" --yes reg.librecloud.online/ontoref/ontoref-daemon:"${CI_COMMIT_TAG}"
- cosign attest --predicate sbom.json --type spdxjson --key "$COSIGN_KEY" --yes reg.librecloud.online/ontoref/ontoref-daemon:"${CI_COMMIT_TAG}"
dist-oci-bundle-linux-x86:
image: ghcr.io/oras-project/oras:v1.2.0
depends_on: ["smoke-bundle", "build-bundle-linux-x86"]
commands:
- cd dist
- oras push reg.librecloud.online/ontoref/dist:"${CI_COMMIT_TAG}"-linux-amd64 ontoref-linux-amd64.tar.gz:application/vnd.ontoref.bundle.tar+gzip ontoref-linux-amd64.tar.gz.sha256:text/plain
dist-oci-bundle-linux-arm64:
image: ghcr.io/oras-project/oras:v1.2.0
depends_on: ["smoke-bundle", "build-bundle-linux-arm64"]
commands:
- cd dist
- oras push reg.librecloud.online/ontoref/dist:"${CI_COMMIT_TAG}"-linux-arm64 ontoref-linux-arm64.tar.gz:application/vnd.ontoref.bundle.tar+gzip ontoref-linux-arm64.tar.gz.sha256:text/plain
dist-installer:
image: curlimages/curl:latest
depends_on: ["smoke-bundle"]
commands:
# Publish the rendered installer to the plain HTTPS asset endpoint.
# PUT target and auth are deployment-specific; https://get.librecloud.online/ontoref/install.sh is the
# canonical URL users curl. Replace with your release-asset upload.
- 'curl -fsSL --fail -T install/install.sh -H "Authorization: Bearer $RELEASE_TOKEN" https://get.librecloud.online/ontoref/install.sh'

View file

@ -7,6 +7,419 @@ ADRs referenced below live in `adrs/` as typed Nickel records.
## [Unreleased] ## [Unreleased]
### 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
substrate, promoted by an eight-path falsación run
(`.coder/2026-07-04-governed-delivery-falsacion.done.md`): the run completed
only after two real out-of-band human signatures, and every refutation attempt
(missing/unsigned/tampered SOW, out-of-order step, status contradicting a
passing check, unsigned/tampered receipt) was refused. The rule above all:
**where a machine check exists, the check decides — never the reporter.**
Executor (`reflection/modules/run.nu`):
- Run vars: modes declare `vars_required`; `run start <mode> KEY=VALUE…`
refuses when one is missing, persists them in `run.json`, substitutes
declared `${KEY}` in guard/verify/postcheck cmds (undeclared `${…}` passes
through to the shell).
- `'Block` guards are evaluated once at `run start`, fail-closed — the
schema's "'Block aborts execution" (ADR-011) is finally true on this path.
- Steps declaring `verify` have their status **derived** from the check's
exit code; a contradicting `--status` is refused; `steps.jsonl` records
`status_source` and a capped output `detail`. Form-parameterized verifies
(`{word}`) require an explicit `--status`.
- Typed `postchecks | Array { desc, cmd }` run fail-closed at `mode complete`,
which now **exits 1 on any incomplete run** (CI-gateable). `postconditions`
stays prose. Latest step report supersedes earlier ones (re-run to unblock).
Witness (`scripts/witness.nu`): contract checks run with cwd pinned to the
governed repo root (no vacuous passes from a wrong cwd); exempt-bearing
contracts emit one violating path per line and exemption subtracts exact
paths (`exempt` only when every violation is exempt); the witness never signs
its own output.
Distribution (adoption surface): the mode ships in the installed reflection
tree; the witness is projected by `install.nu` §3b-bis into
`reflection/bin/witness.nu` (single source, never vendored by copy); opt-in
SOW validation contract at `ontology/schemas/sow.ncl`; agent front-end skill
`.claude/skills/work-order/`; instance data lives in the governed repo's
`.governance/` (SOW + `witness.pub` + receipts with a tracked/untracked
allowlist).
- Migration `0041-governed-delivery` (protocol 0.1.17 → 0.1.18), opt-in;
check passes for non-adopters.
- **ADR-066** (root spine `.ontoref/adrs/`) records the semantics and their
falsación; discharges ADR-063's `promotion-gated-on-second-instance`.
- Tests: `reflection/tests/test_run_{vars,guards,verify}.nu`,
`test_mode_postchecks.nu`, `test_witness_exempt.nu` — 58 checks.
- Fix (peer drift, unrelated): `auth::Role::Editor` added in stratumiops broke
the daemon's role bridge — mapped `Editor → Viewer` (least privilege;
`registry.rs`).
### repo_kind enum — register the domain kinds (migration 0040)
`ontology/schemas/manifest.ncl`'s `repo_kind_type` enum gained `'Tool`, `'KnowledgeBase`
and `'ContentProject` — values the domain extensions' `repo_kinds.txt` already used
(framework: Tool; librosys / knowledge-works: KnowledgeBase + ContentProject) but the
enum had never declared. Additive — existing manifests validate unchanged.
- Migration `0040-repo-kind-domain-kinds` (protocol 0.1.16 → 0.1.17), additive enum.
- Relates to **ADR-064** (root spine `.ontoref/adrs/`) — the authoring/knowledge door + the
`content-vehicle-convergence` gate membrane (positioning + ontology; no shipped-surface change).
### Docsite projection — mdBook from the ontology (spine-local)
The project projects itself as a navigable mdBook, modeled on the `cdci-dao`
theme (ds-theme chrome + tera/admonish/toc/codeblocks preprocessors) and servable
as static HTML by docserver under a `serv_paths` `url_path`. The chrome is fixed;
the content is regenerated on every build from `.ontoref/` so the docs cannot drift
from the protocol state they describe — hybrid static Tera chrome plus pre-rendered
per-ADR / per-mode pages, identity read from `card.ncl`.
- New module `reflection/modules/docsite.nu``docsite generate [--serve-path] [--restricted]`:
Tera context + dynamic pages + SUMMARY; dev build (relative links) and dist build
(site-url baked to the docserver mount via a surgical one-line swap); fence-aware
angle-bracket escaping so `<token>` notation in ADR prose is not swallowed.
- New mode `reflection/modes/generate-docsite.ncl` (project-and-build → dist-and-register).
- `generator.nu` gains one export `docs data` (the composed record, reused — no re-extraction).
- `just docsite [serve-path]` recipe in `code/justfiles/dev.just`.
- Promoted to the protocol surface: the chrome now ships under
`reflection/templates/mdbook/` (with the reflection tree — no `install.nu` change);
`docsite init` materialises it into a consumer's `.ontoref/docsite/` (idempotent),
and `generate-docsite` gained a `materialize-template` step.
- Ontology: Practice node `docsite-projection` (`'Spiral` / `'Act`, `adrs=['adr-060']`)
+ 5 edges; recorded on the `self-description-coverage` dimension.
- **ADR-060** — mdBook Docsite as the standard projected documentation surface
(4 constraints, 3 anti-patterns; `formalization-vs-adoption` kept Soft per ondaod).
- **Migration 0039** `mdbook-docsite` — opt-in adoption; protocol-version 0.1.15 → 0.1.16.
- Brand-neutral chrome: `docsite generate` injects identity from `card.ncl` and an
optional project-local `.ontoref/docsite-brand.ncl` (palette → generated `brand.css`,
author, logo override); absent → neutral defaults. No project carries another's brand.
- `generate-mdbook` kept as an **alias** delegating to `docsite generate` (not retired);
`generate-docsite` is canonical. The bare `docs generate --fmt mdbook` renderer is unchanged.
## [0.1.7] — current release
### The constellation-memory, viability, and honest-limits arc
**Sessions 2026-06-08 → 06-10**
Nine ADRs (049057) and five migrations (00340038), built on the substrate-view
engine (ADR-046) and scheme-tagged witnesses (ADR-047). One unifying discipline:
every mechanism here **notices, proposes, or verifies** — none **applies**, asserts,
or rules on truth. Authoritative state changes only through the witnessed `approve`
path (ADR-024). Version bump 0.1.6 → 0.1.7.
### ADR-049 — Criteria as a substrate-view describe level (Accepted) + migration 0035
Criteria — the conditions a project holds itself to (release gates, quality bars, coherence rules) — become a first-class describe level mounted through the substrate-view engine (ADR-046), distinguished by typed provenance. Every criterion carries `kind: 'Normative` (declared up front, ontology axis) or `'Discovered` (harvested from insights + the interaction trace, reflection axis); a discovered criterion MUST record the `provenance` it was graduated from. Promotion to normative is GATED behind a novelty-check plus human approval — recurrence alone never promotes; demotion is likewise a proposal, never silent. Registered in `levels.ncl` with `kind = 'Describe`, `eligibility = 'Learned`, so a project declaring no criteria mounts nothing and pays zero (ADR-029). Migration **0035** ships the criteria level. Engages `ontology-vs-reflection` (the typed tag is the legibility seam between asserted and observed) and `formalization-vs-adoption` (opt-in absence).
### ADR-050 — Witnessed validators: structural decidability, never truthfulness (Accepted)
A criterion is eligible to become an executable validator ONLY if it reduces to a bounded, structurally-decidable slice query plus the `state_root` commitment. Truthfulness criteria ("the positioning is honest", "the architecture is sound") are rejected at the eligibility gate and remain QA entries (ADR-049), never validators — a pass/fail verdict on such a claim would be the engine deciding truth. Every `ValidatorDecl` declares a `slice_query` naming exactly the nodes/edges/fields it reads; the `#[onto_validator]` predicate may read ONLY that slice + `state_root`. A validator over a `'Spiral`-poled tension MUST be `'Soft` and report direction of motion, never a Hard biconditional (the ondaod prohibition, applied to validators). Each run emits a witness binding the verdict to the `state_root` (ADR-024). 4 anti-patterns; no migration (internal validator architecture).
### ADR-051 — Chronicle-to-proof-candidate seam (Accepted)
A one-directional, propose-only seam from the chronicle (companion-NCL milestones, harvested insights, the interaction trace ADR-037, ADRs-by-date) to the positioning Proof layer (ADR-043). `coder chronicle --as-proof-candidates` emits accepted ADRs and completed milestones as CANDIDATE proofs wired to the four-element framework — a candidate is a proposal, never an accepted Proof, materialized only through positioning's own acceptance path. The positioning audit's five rules verify candidates (one creating a DanglingRef / UnprovenValidatedAudience / AnchorDrift is surfaced, not accepted): the chronicle proposes, the audit verifies, a human accepts. Orphan milestones (shipped work with no positioning home) WARN, never erase — internal work the positioning layer never intended to claim is a legitimate half-state. The seam is outreach-read-only. Engages `ontology-vs-reflection` (history is reflection; a Proof is an ontology-side claim — auto-asserting would collapse the duality).
### ADR-052 — Memory feedback loop: propose, never apply (Accepted)
At `mode complete` the memory feedback loop emits PROPOSED deltas and never applies them. A proposal hook in `run.nu` surfaces three kinds — FSM-transition proposals against `state.ncl`, backlog-close proposals, and proof-citation proposals (ADR-051) — each via `backlog propose-status`, never a direct mutation. The ONLY path to authoritative state is the witnessed `approve` operation (ADR-024); the loop has no write path that bypasses it. Intent-vs-reality divergence (a dimension's blocker gone but catalyst unmet; a milestone shipped that no proof claims) is DESCRIBED as a half-state and proposed, never collapsed or fabricated. The hook always RUNS and SURFACES even when nothing is accepted, so the opposite collapse — decide-and-commit blind to drift — is structurally prevented. Engages `ontology-vs-reflection` (memory is reflection; state/backlog/proofs are ontology).
### ADR-053 — Presentation-spec: config-driven panels (Accepted) + migration 0036
A presentation-spec (`reflection/schemas/presentation.ncl`) becomes the single declarative panel model the engine reads generically, so adding a panel is dropping an NCL file, never editing Rust. A `Panel` declares `{ id, title, kind ('Timeline | 'Gantt | 'Graph | 'Table | 'Board), data_source (op + params naming a catalog operation), surfaces, reference_links, filters, actors }`; the render path branches on `kind`, not panel identity, through the existing NclCache → JSON pipeline (the `graph.html` precedent). Panels declared as `presentation/panel-*.ncl`, referenced from `config.ncl` `quick_actions`, or surfaced from a substrate-view all normalize to one panel list. A `data_source` MUST name a declared catalog operation — a panel is a projection of declared knowledge, never an independent Rust query. Domain projects extend by shipping their own panel NCL with no engine fork. Migration **0036** ships the presentation-spec schema.
### ADR-054 — Desktop/mobile native shell as a consumer surface (Accepted)
**Session 2026-06-09**
Permits a Tauri native shell (`ontoref-desktop`, same lib targeting desktop and mobile) as a strictly separate consumer of the daemon — modeled on `provisioning-desktop`. The shell wraps the existing daemon `/ui/` in a webview and adds the four browser-impossible capabilities: system tray, native OS notifications, local-daemon supervision, and native keyring. No version bump: internal architecture decision, no migration (governs ontoref's own development, not the consumer-visible protocol surface).
- **Dependency direction (Hard)**: the arrow is shell → daemon only. `ontoref-ontology`, `ontoref-reflection`, and `ontoref-daemon` must never depend on the desktop crate (C1C3, typed `Cargo` checks) — preserves Protocol-Not-Runtime (ADR-001)
- **Placement (Hard)**: the shell lives as its own constellation sub-repo (ADR-048) or a workspace crate excluded from the installed/adopted footprint; consumes the daemon over HTTP/NATS only, may depend on `ontoref-ui` for presentation (C4)
- **Capture seam (Soft, directional)**: when the native surface occupies the `openness-vs-sustainability` seam it does so as a declared positioning consumer audience; tier-0 protocol adoption (ADR-029) stays free and unmetered (C5) — ondaod: Spiral tension gets a direction-reporting Soft constraint, never a Hard biconditional
- **Differentiator**: the drift-watcher (Passive Drift Observation) + notification barrier (ADR-002) give native notifications a first-class event source — the reason the host is more than a reskinned browser
- **3 anti-patterns**: host-inside-adoption-surface, native-view-surface-fork, metered-protocol-capture
- **ondaod**: engages `openness-vs-sustainability` (claim-only → **populating**: first declared occupant of the capture seam), `ontology-vs-reflection` (pure reflection-axis consumer), `formalization-vs-adoption` (optional layer, lowers daemon-run friction)
- **on+re**: `core.ncl` tension `openness-vs-sustainability` updated (synthesis state + `adrs=['adr-043','adr-054']`); `state.ncl` self-description-coverage catalyst records the incorporation; no Practice node added (shell is unbuilt — avoids a placeholder node)
### ADR-055 — Positioning viability seam: compensation as a second costura (Accepted)
**Session 2026-06-09**
Adds the `ViabilityPath` kind to the positioning layer — a **second costura parallel to `Proof`**, not a fourth orthogonal axis. Where a Proof answers "is the claim real?", a ViabilityPath answers "does the claim sustain its maker?". It converges PARA QUIÉN × CÓMO × (QUÉ×QUIÉN), `directness` (`'Direct`/`'Indirect`) binds the two poles of `openness-vs-sustainability`, and a one-directional `realized ⇒ realized_outcome` gate keeps compensation honest. Purely additive, opt-in (ADR-029), **no migration** (same precedent as ADR-043's four-element kinds — all fields defaulted, zero adoption cost).
- **Number-collision reconciliation**: authored concurrently with ADR-054 (both took `054`, the repo's second double-NNN after the double adr-039). The desktop shell kept `adr-054`; the viability seam took `adr-055`; the two are cross-linked as **occupant ↔ kind** — ADR-054 declared the desktop/mobile surface as the first capture-seam occupant but had no element to model it with, ADR-055 supplies that element
- **Schema/defaults** (`ontology/{schemas,defaults}/positioning.ncl`): `ViabilityPath` + `Directness`/`FlowKind` enums; `make_viability_path` with 4 cross-field invariants — converge-three-axes, anchor-to-graph, one-directional `realized⇒outcome` (Soft, never a Hard biconditional — ondaod), `Live⇒evidence_adr`
- **3 dogfood paths** (`.ontoref/positioning/viability/`, all `'Draft` — present state is claim-only): via-001 (Direct/HostedService governance-audit tail), via-002 (Indirect/Funnel personal-open visibility), via-003 (Direct/Product native-shell occupant citing adr-054 — closes the gap ADR-054's own negative consequence named)
- **Rust walker** (`ontoref-ontology`): `ViabilityPath`/`Directness`/`FlowKind`/`ViabilityConverges` types, `PositioningConfig.viability_paths`, `Positioning` runtime view with `DanglingConverges` referential-integrity checks + `viability_path_by_id` lookup + `viability` dir in the cold-load walk. Daemon `GET /api/positioning` surfaces the list + `viability_paths`/`realized_viability_paths` counts. 39 ontology tests pass; `ontoref-ontology` clippy clean
- **Coherence** (`positioning.nu`): `load-all-local` loads `viability/`; 7th rule `DanglingConverges`/`RealizedWithoutOutcome`; `positioning validate` covers the `viability` subdir
- **constraints**: 3 Hard (seam-not-axis, converges-three, anchored-to-graph) + 2 Soft on the Spiral question (realized-gate-one-directional, capture-stays-off-protocol-surface); 4 anti-patterns (viability-as-fourth-axis, realized-without-outcome, meter-the-protocol, hard-biconditional-on-viability)
- **on+re**: `core.ncl` positioning-layer node (`adrs += adr-055`, `viability/` dir, second-costura description) + tension node (`adrs=['adr-043','adr-054','adr-055']`, "no viability axis" clause corrected); `state.ncl` catalyst records the incorporation
### ADR-056 — Sufficient verification over complete knowledge (Accepted)
Names a trust model that had been operating across the protocol without ever being stated as one: no actor — human or agent — holds the whole project, and recording everything to approach completeness overflows context and produces more confusion than clarity. A trust model resting on "know it all, keep it all current" fails in both directions. Trust instead rests on **local, partial verification of sufficient knowledge — accredited, not generated**: you verify the slice you need by witness, without holding the whole. It is the only model that scales to real humans and to agent context windows that cannot hold the entire project. Formalises the basis the verifiable substrate (ADR-023) and the witness seam (ADR-031) already assumed.
### ADR-057 — Difusión reveal architecture (Accepted)
Makes the outreach discipline architectural: **route each door to a live graph, never argue the core.** Outward communication reveals itself through the same queryable substrate — the positioning graph projected via `spine.ncl` + a generator — rather than bespoke prose, converting per-conversation human mediation into a reproducible projection. Publishing a real personal graph as the rung-3 mediation artifact is a per-artifact privacy decision (cleared for the jpl graph 2026-06-10, never automatic for others). Anchors the `outreach/site` projection work.
### Migrations 00340038
- **0034** `substrate-views` — the substrate-view engine (ADR-046): disciplines mount scoped slices of levels.
- **0035** `criteria-level` — the criteria describe level (ADR-049).
- **0036** `presentation-spec` — the config-driven panel model (ADR-053).
- **0037** `memory-surface-fan-out` — the memory loop's proposal surface (ADR-052).
- **0038** `coder-workflow-harvest-criteria` — the coder harvest that graduates discovered criteria (ADR-049/052).
---
## [0.1.6]
### Protocol delta 0.1.5 → 0.1.6 — OCI distribution (migration 0029)
**Session 2026-06-03**
Migration 0029 (`oci-distribution`) gate closed: `reflection/defaults/workflow.ncl`
carries `ContainerRegistry` in the distributions catalog. Version bump applied.
Bug fix: `migrate.nu:migrations-dir` used `$env.ONTOREF_ROOT` instead of
`project-root`, causing `onre migrate list` to return `[]` on constellation
layouts (post-ADR-048 spine at parent). Fixed to `(project-root)` — migrations
now resolve from the spine regardless of whether the spine is inside or outside
`ONTOREF_ROOT`.
---
## [0.1.5]
### ADR-048 — Constellation layout: spine at parent, code as sub-repo (Accepted)
**Session 2026-06-02**
Formalizes the decision to move `.ontoref/` from inside `code/` to the constellation parent directory, establishing `code/` as one independent git sub-repo among several (`outreach/`, `vault/`) under a non-versioned umbrella.
- **Bash wrapper** (`code/ontoref`): derives `SPINE_DIR` as `SCRIPT_DIR/../.ontoref/`; sets `ONTOREF_PROJECT_ROOT` to the parent
- **`env.nu`**: `ONTOREF_ROOT` assignment guarded with `is-empty` — preserves the bash wrapper's value at module load time
- **`config.ncl` `nickel_import_paths`**: `.ontoref/` entries prefixed with `../` (relative to `ONTOREF_ROOT=code/`)
- **NCL imports in `.ontoref/`**: `card.ncl` and `ontology/{core,state,gate,manifest}.ncl` use `../../code/ontology/` for the data-dir companion
- **Cargo path deps**: peer projects at `Development/` level now resolve via `../../../../` (four hops); stratumiops crates gain `code/` segment (`../../../../stratumiops/code/crates/`)
- **`install.nu`**: `repo_root` derived from `$env.CURRENT_FILE` — robust to arbitrary CWD; constellation fallback for `reflection_src` and `bootstrap_src`
- **Asset sync**: `just sync-assets` / `onre sync assets` materialises `assets/``code/assets/`; `onre sync assets check` as CI drift gate; `ProjectCard` schema gains optional `synced_assets { source, digest, synced_at }`
- **4 constraints** (all Hard): spine-at-parent-not-inside-code, env-nu-guards-ontoref-root, nickel-import-paths-use-parent-prefix, ncl-imports-reference-code-ontology
- **3 anti-patterns**: spine-as-satellite, unconstrained-env-nu-override, three-hop-cross-project-deps
- **ondaod verdict**: `'Safe` — tensions `formalization-vs-adoption` (toward Yang, Yin preserved) and `ontology-vs-reflection` (static, duality made explicit at filesystem level)
---
### ADR-045 — Recursive level chains and domain co-tenancy (Accepted) + migration 0033
**Session 2026-06-02**
A three-level architecture surfaced in the field (Rustelo builds a website-mode over a Torred domain; the resulting websites are hosted inside a Provisioning workspace) that ADR-018's absolute `Base/Domain/Instance` triad could not express. Two generalisations, both **opt-in and backward-compatible** — a depth≤3 single-chain repo declares nothing new.
- **Relative recursive levels.** `level.index` is reinterpreted as a **role in a sliding window of three** relative to an observation point, not an absolute coordinate. Actual depth is **derived** by walking `level.parent` to a `'Base` and is **uncapped**`base → Torred → Rustelo → website-mode → website-instance` is a valid depth-5 chain. A depth≤3 chain derives `'Base/'Domain/'Instance` identical to the legacy declaration.
- **Domain co-tenancy.** `manifest.ncl` gains an optional `hosts` array (`{ chain, mount, digest? }`). One repo may host foreign chains from other projects, carrying only their Layer-3 wiring under `mount`, resolved by **witness not clone** (ADR-028/030). The terminal instance of one chain may re-host another chain's instance (instance-as-host is well-formed).
- **Mutation sovereignty (load-bearing).** A host runtime **must not** mutate a hosted chain's authoritative state — enforced by `validate hosts --check mutation-sovereignty` (no local op may `render_paths` into a hosted `mount`). Hosting is placement + wiring, not ownership (protects ADR-024).
- **Observable traversal.** `validate.nu` gains `validate hosts` (five checks — `chain-ref`, `mutation-sovereignty`, `chain-root` Hard; `layer3-tag`, `derived-index` Soft; with a 5-assertion `--self-test`) and `mode resolve` now reports the **full traversal path** and **chain scope**, not a single hop. Schema: `ontology/schemas/manifest.ncl` gains `host_entry_type`/`HostEntry` and the relative-level reinterpretation.
- **ADR-045** carries 5 constraints (3 Hard, 2 Soft — Soft on the `formalization-vs-adoption` Spiral per ondaod), `ontology_check` verdict `'Safe` (invariants at risk: `internal-coherence-enforced`, `protocol-not-runtime`, `voluntary-adoption` — all preserved), and 3 anti-patterns (`host-owns-hosted-state`, `absolute-level-flattening`, `implicit-hosting`). Migration **0033** propagates the schema (protocol-version 0.1.9 → 0.1.10). Graduates the bl-009 level-axis open question; `level-hierarchy-resolution` core node updated with `adrs=['adr-018','adr-045']` and a new `→ formalization-vs-adoption` (Resolves) edge.
---
### ADR-043 — Positioning as a four-element framework (Accepted)
**Session 2026-06-02**
The positioning layer (ADR-035) is reframed from three entangled artefact kinds into a **four-element framework**: three orthogonal axes bound by a validation seam. The work began as a strategic exercise — positioning ontoref against knowledge-graph competitors — and converged on a model that mirrors the protocol's own constitutive duality (ADR-031).
- **Three orthogonal axes, each a first-class typed kind.** QUÉ — `Differentiator` (one file per area of differential value) + `Competitor` (what they do / how ontoref differs). PARA QUIÉN — `Audience` (gains `status`: `Hypothesis` until a Proof validates it). CÓMO — `DifusionMechanism` (`kind`: Web | DocsSite | McpServer | Repo | Content | Talk | Event; persistent vs event). Difusión is the category; a `Campaign` is **one** time-boxed coordination within it, not the whole of it.
- **The fourth element is the costura, not a fourth axis.** VALIDACIÓN — `Proof`: the convergence of a differentiator, an audience and a difusión mechanism, signed by a real outcome. It references the other three axes; nothing references it — self-similar to the `witness-as-axis-seam`. `ValueProp` is the QUÉ×QUIÉN seam (gains `differentiator_ids`).
- **The marketing surface is governed by the mechanism it sells.** `ore positioning audit` now runs **five coherence rules** (local-first, daemon-independent): AnchorDrift, UnprovenValidatedAudience, Live-convergence, DanglingRef, CoverageGap. ADR-drift severity is **gated by value-prop status** — a Draft claim citing a Proposed ADR is informational; only Validated/Live must cite Accepted ADRs (mirroring `_live_requires_evidence_adrs`). Coverage gaps are explicit reminders, never silent.
- **Schema + contracts**`ontology/schemas/positioning.ncl` gains `Differentiator`, `Competitor`, `DifusionMechanism`, `Proof` (+ enums `MechanismKind`, `ProofKind`, `AudienceStatus`); `ontology/defaults/positioning.ncl` gains `make_*` constructors with five rejecting contracts (`_diff_requires_anchor`, `_diff_requires_vs`, `_mechanism_non_empty`, `_proof_converges_all`, `_live_requires_differentiator`). Purely additive: every new field is defaulted, so existing positioning files validate unchanged.
- **ontoref dogfood** — instantiated under `.ontoref/positioning/`: 5 differentiators (witness-seam, reflexive-no-drift, graduated-adoption, domain-transversality, agent-native), 5 competitors (Knowledge Graph Guys, Backstage/Port, ADR tooling, GraphRAG/KG-DBs, supply-chain attestation), the latent **personal-ontology** audience (Yin pole — content/design/research/career), 6 difusión mechanisms, and 4 Proofs (provisioning, personal-ontoref, gestión-proyectos, dogfooding).
- **Daemon**`ontoref-ontology` gains the four structs and the walker loads `differentiators/`, `competitors/`, `difusion/`, `proofs/`; `from_config` enforces Proof referential integrity (rule 4) at load. `GET /api/positioning` serves the new kinds with counts (HTTP + MCP).
- **ADR-043** carries 5 constraints (4 Hard, 1 Soft), `ontology_check` verdict `'Safe` (engaged tensions `ontology-vs-reflection`, `formalization-vs-adoption` — no invariant broken; self-describing reinforced), and 4 anti-patterns (`re-entangle-axes`, `proof-without-convergence`, `validated-audience-without-proof`, `marketing-prose-bypass`).
- **No migration required** — the schema change is additive and fully defaulted; the positioning layer remains opt-in (ADR-029). A migration is only warranted if the four-element framework is to be actively propagated to consumer projects.
---
### ADR-038 — OCI distribution and installer (Accepted) + migration 0029
**Session 2026-05-29**
ontoref now ships itself through the OCI registry along two paths from one build, modeled entirely in the workflow layer (not standalone scripts). The load-bearing tension was "Protocol, Not Runtime": a runnable daemon image must not make ontoref a runtime dependency. The synthesis (ADR-029): the daemon image is an **optional accelerator**; the `curl|sh` installer installs a daemon-less-capable CLI, so the bundle (protocol surface) and the image (optional service) stay decoupled.
- **Runnable multi-arch image** `reg.librecloud.online/ontoref/ontoref-daemon:VERSION` (linux amd64+arm64) — for running the daemon as a service. Assembled with **buildah FROM the cross-compiled musl binary** (no recompile, no `docker buildx`/QEMU/docker-in-docker), cosign-signed with an attested SBOM.
- **`curl … | sh`** → `install/install.sh` — detects OS/arch, pulls the per-platform bundle from the anonymous-pull `ontoref/dist` namespace via `oras` (curl fallback to the OCI Distribution API), verifies the `.sha256` sidecar, and installs mirroring `install.nu`'s layout (binary + data layer + wrappers with baked `ONTOREF_ROOT` + bundled `nickel`). Linux amd64+arm64 only; macOS gets source-build/container guidance. Flags: `--prefix`, `--version`, `--data-dir`, `--uninstall`/`--purge`.
- **Build-once**`cross` compiles each arch once; both the bundle and the image consume that same binary.
- **Workflow-layer model**`build_kind` gains `'Bundle` (`reflection/schemas/workflow.ncl`); `reflection/defaults/workflow.ncl` gains builds (`release-musl-arm64`, `bundle-linux-{x86,arm64}`, `oci-image-build`), the `smoke-bundle` publish gate, and the `distributions` catalog (`oci-image`, `oci-bundle-linux-{x86,arm64}`, `installer`); `ontology/workflow.ncl` gains the `release` layer (trigger `'OnTag`). A 4th generator `generate-distribution` in `reflection/modules/workflow.nu` (alongside PreCommit/Woodpecker/Justfile) emits `.woodpecker/release.yml`, renders `install/install.sh` from `install/install.sh.tmpl` (tokens from the catalog), and writes `justfiles/release.just`. Regenerate with `ore workflow generate --layer release` — never hand-edit the artifacts.
- **Assembler** `install/assemble-bundle.nu` — packages a cross-compiled binary into `ontoref-<os>-<arch>.tar.gz` (+ `.sha256`); `smoke` subcommand extracts and runs the binary (the release gate).
- **Registry**`manifest.ncl::registry_provides` namespaces extended with `ontoref/ontoref-daemon/` and `ontoref/dist/` (the bundle namespace is anonymous-pull so `curl|sh` needs no credentials). Publishing secrets (cosign key, RW credential) come from the src-vault (ADR-017; self-hosted CI has no Fulcio → key-based signing).
- **Dockerfile** — root `Dockerfile` marked dev/local-only; the release image path is `.woodpecker/release.yml` via buildah.
- **ADR-038** carries 6 constraints (5 Hard, 1 Soft), `ontology_check` verdict `'Safe` (engaged tensions `protocol-not-runtime`, `voluntary-adoption`, `formalization-vs-adoption` — the rare case where more formalization *lowers* adoption friction), and 4 anti-patterns (`daemon-as-mandatory-runtime`, `hand-maintained-distribution-scripts`, `recompile-per-arch-image`, `bundle-carries-project-config`).
- **Migration 0029** `oci-distribution` — additive, opt-in; a project with no `release` layer is unaffected. Protocol delta **0.1.5 → 0.1.6**.
- **FAQ**`reflection/qa.ncl::oci-distribution-and-install` documents both install paths, the build-once principle, the workflow-layer model, and the forbidden patterns.
---
### ADR-037 — Interaction trace (Accepted) + migration 0028
**Session 2026-05-28**
Parse-first record of an agent/human session segment, stored as JSONL so reviews filter typed fields (`jq`, `nu where`) instead of mining prose. The opening framing — make the agent emit a *typed and validated* contract over its reasoning — was consciously narrowed: the goal is parseability, not validation of reasoning (undecidable, and an invitation to schema theatre). Typing is kept for filtering; content of free prose is never validated.
- **Schema** `reflection/schemas/interaction.ncl``InteractionRecord` + `Event`, `Decision`, `Action`, `InteractionInsight`, `WitnessRef` and the `EventKind`/`Status`/`Outcome`/`ActionKind` enums. Reuses `coder.ncl` vocabularies (`ActorKind`, `Kind`, `Domain`, optional `Category`) via import rather than forking. Every filterable dimension is an enum or typed array; free prose is confined to `summary`, `Decision.rationale`, `Event.note` — never a filter axis.
- **Module** `reflection/modules/interaction.nu``ore interaction record|validate|list|filter|decisions|insights` (alias `ix`). Two emission paths: `record` (validated append — forced) and direct JSONL write audited post-hoc by `validate` (CI/pre-commit gate). The structural validator derives enum allowlists from the schema files at runtime (single source of truth); validation is structural only and never inspects prose content.
- **Optional witness** (`WitnessRef`: `op_id`/`state_root`/`signature`) makes the layer tier-orthogonal: absent at tier-0 (review aid), present at tier-1/2 (reflection-side companion of witnessed acts — the ADR-031 seam). Opt-in per ADR-029; projects that never emit interaction JSONL pay zero adoption cost.
- **`Decision`** materialises the `adr?` criteria as filterable data (`alternatives_rejected`, `reversible`, `adr_candidate`, `tensions_engaged`) — ADR candidates surface across sessions in one query.
- **Migration 0028** `interaction-record-schema` ships the schema to consumer data dirs (additive, opt-in) — lands within 0.1.5, no version bump of its own.
- **Ontology** core.ncl gains the `interaction-trace` Practice node (`pole = 'Spiral`, `axis = 'Act`, 6 edges including `Complements` to `coder-process-memory` and `SpiralsWith` from `witness-as-axis-seam`).
- **ADR-037** carries 5 constraints (3 Hard, 2 Soft), `ontology_check` verdict `'Safe`, and 3 anti-patterns (`prose-truthfulness-gate`, `prose-as-filter-axis`, `forked-session-vocabulary`). All three Hard constraints pass `ore adr validate`.
- **Web surface**`interaction export-html` renders `assets/web/interactions.html` (parse-first: structural fields only, never prose); `src/index.html` gains an 8th protocol-layer card (rose) linking to it. `.coder/interactions.jsonl` seeded with this session's own three real records (the feature documenting itself).
- **FAQ**`reflection/qa.ncl::interaction-trace-howto` (queryable via `ore q interaction trace`): emission paths, minimal record, filtering, adoption, and the FORBIDDEN structural-only discipline.
- **Bug fix (ADR-032 drift)**`describe.nu::qa search` loaded `(project-root)/reflection/qa.ncl`; ADR-032 moved the store under `.ontoref/`, so it now resolves via `(instance-root)` like `coder.nu`. The whole Q&A knowledge base was unreachable via `ore q` in self-hosted layout; one line restores it. Runtime change (`reflection/modules/`), no migration needed.
`cargo update --workspace --offline` confirms all 14 crates resolve to `0.1.5`.
---
### Version bump 0.1.0 → 0.1.5 — single workspace source of truth
**Session 2026-05-27**
- `[workspace.package].version = "0.1.5"` added to `Cargo.toml`; all 14 member crates switched to `version.workspace = true` (also `edition` and `license`).
- `.ontoref/card.ncl` no longer declares a `version` field — the daemon and downstream renderers read the canonical version from `Cargo.toml`. ProjectCard schema's `version | String | default = ""` makes the field optional.
- Accumulated protocol-surface deltas now reflected in machine-readable artefacts:
- 0.1.0 → 0.1.1 — migration 0023 (layout consolidation, ADR-032)
- 0.1.1 → 0.1.2 — migration 0024 (tier transition, ADR-033)
- 0.1.2 → 0.1.3 — migration 0025 (catalog kind field, ADR-034)
- 0.1.3 → 0.1.4 — migration 0026 (ADR anti-patterns field; version delta narrative added)
- 0.1.4 → 0.1.5 — migration 0027 (positioning layer, ADR-035; version delta narrative added)
- within 0.1.5 — migration 0028 (interaction record schema, ADR-037; additive, no further bump)
- `cargo metadata --no-deps` confirms all 14 crates resolve to `0.1.5`.
Resolves the coherence gap where migrations narrated version bumps but `Cargo.toml` and `card.ncl` remained at `0.1.0`. Future bumps land in one place: `[workspace.package].version` in the root `Cargo.toml`.
---
### ADR-035 — Positioning layer (Accepted)
**Session 2026-05-26/27**
**ADR-035 — Positioning Layer: Marketing as Queryable Protocol Surface (Accepted)**
- Outward-facing surface formalised as queryable NCL. Three artefact kinds: `Audience`, `ValueProp`, `Campaign` — typed by schemas installed to the user data dir, instantiated per-project under `.ontoref/positioning/`.
- `LaunchCheck` tagged record (`'Grep | 'NuCmd | 'ApiCall | 'FileExists`) — structurally identical to `ConstraintCheck` in `.ontoref/adrs/adr-schema.ncl`; lift-out tracked as soft constraint `shared-check-lift-out-pending`.
- Cross-field contracts in `ontology/defaults/positioning.ncl`: `non_empty_audience_ids`, `live_requires_evidence_adrs`, `retired_has_date`, `superseded_implies_retired`, `each_launch_check_well_formed`. Enforced at nickel-export time via `make_audience` / `make_value_prop` / `make_campaign` helpers.
- `ProjectCard` schema extended with optional `primary_value_prop_id` field — zero-migration: every existing `card.ncl` typechecks unchanged. When present, MUST resolve to `.ontoref/positioning/value-props/<id>.ncl`.
- 6 Hard + 1 Soft constraint: `positioning-schema-installed`, `positioning-defaults-installed`, `audiences-not-inlined-in-value-props` (Grep on `primary_pain` in value-props), `card-primary-vp-resolves-when-set`, `live-value-props-cite-evidence-adrs`, `narrative-paths-resolve-when-declared`, `shared-check-lift-out-pending` (Soft).
- Anti-patterns recorded: `live-claim-without-evidence`, `audience-inlining`, `marketing-architecture-drift`. The drift anti-pattern is closed by `ore positioning audit` cross-referencing `evidence_adrs` against ADR status.
**Witness-seam (per ADR-031)**
- `evidence_adrs` on every `Live` value-prop is the substance-anchoring seam. A `Live` claim without `evidence_adrs` is a structural contract violation, not a stylistic concern. This is what distinguishes positioning-as-protocol-layer from positioning-as-CMS.
**Migration 0027 — positioning-layer**
- Schema additions to data dir (`ontology/schemas/positioning.ncl`, `ontology/defaults/positioning.ncl`).
- Optional extension to local `project-card.ncl` schemas (`primary_value_prop_id` field). Projects that import the protocol-shipped schema via `NICKEL_IMPORT_PATH` receive the extension automatically.
- Adopting the positioning layer (creating `.ontoref/positioning/{audiences,value-props,campaigns,narratives}/`) is OPT-IN per ADR-029. Tier-0 floor cost unchanged.
**Consumer layout — `.ontoref/positioning/`**
- `audiences/audience-platform-engineer.ncl` — seed audience (channels, evidence, `linked_nodes = [voluntary-adoption, self-describing, describe-query-layer, domain-extension-system]`).
- `value-props/vp-001-structure-that-remembers.ncl` — seed value-prop with 5 `evidence_adrs` (adr-001, adr-003, adr-009, adr-031, adr-035) and 3 `launch_criteria` checks.
- `narratives/vp-001-structure-that-remembers.md` — prose body for the value-prop claim.
**Card integration**
- `.ontoref/card.ncl` declares `primary_value_prop_id = "vp-001-structure-that-remembers"`, `tagline = "Structure that remembers why."`. Identity (tagline, stable) and positioning (claim, audience-targeted, evolves) cohabit without conflict.
**Tooling surface**
- `.ontoref/reflection/modules/positioning.nu``ore describe positioning [--audience <id>] [--status Live]`, `ore positioning validate`, `ore positioning audit`.
- `.ontoref/reflection/bin/ontoref.nu` — dispatcher registers `positioning` subcommand group.
- `.ontoref/reflection/nulib/help.nu` — help text + capabilities surface include positioning.
- `assets/web/positioning.html` — landing page for the positioning surface (hand-edited until a follow-up generator ADR replaces it with Jinja from the NCL tree, per ADR-035 §ASSETS/WEB/ DERIVATION).
**On+re update**
- `.ontoref/ontology/core.ncl` — new `positioning-layer` Practice node (axis=Act, adrs=['adr-035']) with artifact paths to schemas, defaults, consumer layout, module, migration, card, and web page. 11 edges anchor it to: `reflection-axis-act` (ManifestsIn), `witness-as-axis-seam` (ManifestsIn via evidence_adrs), `formalization-vs-adoption` (Resolves), `ontology-vs-reflection` (Resolves), `voluntary-adoption` (ManifestsIn), `dag-formalized` (ManifestsIn), `adr-node-linkage` (Complements), `describe-query-layer` (ManifestsIn), `mcp-surface` (Complements), `domain-extension-system` (Complements), `personal-ontology-schemas` (Complements). Plus reverse edge from `tier-coexistence-permanent-design` (Complements) — positioning is the canonical example of an additive opt-in layer admitted by ADR-029.
- `.ontoref/ontology/state.ncl``protocol-maturity` blocker extended with session 2026-05-26/27 entry (positioning-layer addition, schema + defaults + migration 0027 + consumer instances + Nu module). `self-description-coverage` catalyst extended; total ADR count: 35 (ADR-021 → ADR-035 since session 2026-05-01).
**Ondaod synthesis (recorded in ADR-035 `ontology_check`)**
- `formalization-vs-adoption` (Spiral, Tension) engaged. Synthesis state: realised — opt-in tier-orthogonal layer raises formalisation ceiling without raising the tier-0 floor. `voluntary-adoption` axiom preserved.
- `ontology-vs-reflection` (Spiral, Tension) engaged. Synthesis state: realised — positioning lives on the reflection axis; `evidence_adrs` deposits the witness-seam to substance. Per ADR-031, the seam extends outward to the marketing surface. Both Spirals stay open; verdict `'Safe`.
---
### Cluster B — ADR-033 + ADR-034 design + catalog-extension examples
**Session 2026-05-26**
**ADR-033 — Tier Transition Mechanism (Proposed)**
- Dual-surface `transition_tier`: `ontoref tier set <T>` CLI (every tier) + tier-2 catalogued op, both routing through a single Rust function that invokes `enforce_tier_transition_guard` from `crates/ontoref-daemon/src/tier_guard.rs` (the pure validator already shipped under ADR-029).
- Downgrade behaviour: substrate (oplog, commit_layer, state_root, signing keys) marked dormant immutable; past witnesses verify forever. Tier-2 descent emits a final `tier_descent` Ed25519 witness as the closing event.
- Transactional atomicity per direction — every transition produces exactly one of two outcomes: the project is at the requested tier OR at the original tier. No intermediate partial-substrate state ever visible.
- 8 Hard constraints + 1 Soft. `piloto-declares-its-tier` couples the mechanism to its canonical instance: ontoref's own `.ontoref/config.ncl` declares `ops.tier = 'Tier2` as part of this ADR (deferred to implementation per `'Proposed` status).
**ADR-034 — Catalog Extensibility Beyond Rust (Proposed)**
- Schema-level `kind` discriminator on `OperationDecl` / `ValidatorDecl` admitting `'Rust` (default, executable today), `'NclTransform`, `'Wasm`, `'Sidecar`. Per-kind `body` shape validated structurally; executive impls trigger-deferred per ADR-030.
- Hard constraint `non-rust-kinds-require-ondaod-pre`: every non-Rust op MUST list `"evaluate_ondaod"` in `constraints.pre`. Enforced as a Nickel contract at typecheck time — ADR-024 ondaod discipline becomes structural rather than behavioural for the non-Rust path.
- `wasmtime` MUST NOT enter the default daemon build (`wasmtime-not-in-default-daemon-build`) — `protocol-not-runtime` axiom (invariant=true) preserved.
- Declarable-not-executable returns HTTP 501 (`non-rust-kinds-declarable-not-executable-without-flag`) — silent 404 is forbidden.
**Migrations**
- `reflection/migrations/0024-tier-transition-mechanism.ncl` — documents the CLI + op surfaces, recommends explicit `ops` block in `.ontoref/config.ncl`. Protocol bump 0.1.1 → 0.1.2.
- `reflection/migrations/0025-catalog-kind-field.ncl` — schema bump for the four `kind`s with default `'Rust`. Backward-compatible: existing declarations typecheck unchanged. Protocol bump 0.1.2 → 0.1.3.
**QA additions**
- `reflection/qa.ncl::ontoref-catalog-extension-howto` — end-to-end how-to covering all four paths with decision tree, authoring steps, hard rules, witness-shape honesty per kind, when-you're-blocked triage.
- Reverse links added: `ontoref-tier-transition`, `ontoref-tier-downgrade-asymmetry`, `ontoref-multi-tier-management`, `ontoref-catalog-discovery-shared-domain` now reference ADR-033 / ADR-034 in their `related` arrays.
**Examples folder — `examples/catalog-extension/`**
- 16 files demonstrating all four `kind`s end-to-end.
- `schema-future.ncl` co-locates the post-migration-0025 contract so all four examples typecheck today, even though the protocol schema is still pre-bump. Ondaod-pre Hard contract verified to fire on the negative case.
- `01-rust/` — declaration + handler skeleton for `greet_actor`.
- `02-sidecar/` — declaration + standalone axum sidecar binary (`cargo check` clean) on `:7902` implementing `send_email_notification`; mirrors ADR-030 mechanism #6 wire shape.
- `03-ncl-transform/` — declaration of `rename_ontology_node`; declarable today, dispatch returns 501.
- `04-wasm/` — declaration of `render_provisioning_report` + WIT interface skeleton; declarable today, dispatch returns 501 until ADR-030 T3/T4/T5 fires.
**Ondaod synthesis (recorded in both ADRs' `ontology_check`)**
- ADR-033 engages `ontology-vs-reflection` (tier change is an act mutating substance; witness emitted at tier-2 boundaries is the seam) and `formalization-vs-adoption` (dual-surface CLI+op preserves both poles). Both Spirals stay open.
- ADR-034 engages `formalization-vs-adoption` (Rust strongest guarantees, NCL/Wasm/Sidecar lower barrier with weaker guarantees — schema admits all four without collapse) and the `protocol-not-runtime` axiom (preserved by deferring executive impls per ADR-030 triggers). Both poles continue in dialectic.
---
### γ-posture lift-out validation — second-consumer gate, ontoref-compute constructors, on+re tracking
**Session 2026-05-12**
**Second-consumer validation of `ontoref-auth`, `ontoref-ui`, `ontoref-compute`, `ontoref-dns`**
- γ-auth: SUCCESS — lian-build delegates password hashing and session creation to `ontoref_auth::password::*`; `SessionStore` and `AuthUser` re-exported unchanged.
- γ-ui: SUCCESS — lian-build and `ontoref-panel` both adopt `ontoref_ui::init_tera`; `TeraEnv::Deref<Tera>` means zero call-site changes in either consumer.
- γ-compute: PARTIAL — lifecycle surface (`spawn`/`destroy`/`status`/`list`/`health`) validated by two consumers; `ScriptProvider` tagged-enum envelope structurally incompatible with lian-build's flat-JSON Nu scripts (documented gap, adapter path exists).
- γ-dns: SUCCESS — orchestrator replaces `coredns_client.rs` with `Arc<dyn DnsProvider>`; two independent consumers confirmed; trait locked.
**`ontoref-compute` — public constructors for non-exhaustive structs**
- Added `Instance::new(handle, provider)`, `ProviderHealth::healthy(latency_ms)`, `ProviderHealth::unhealthy(latency_ms, reason)` to resolve E0639 (`#[non_exhaustive]` blocks struct literal construction in external crates).
- Named constructors are the stable external API; `#[non_exhaustive]` retained so future optional fields do not break consumers.
**`ontoref-panel` C.5 — static asset pipeline and deployment architecture**
- `public_dir` and `templates_dir` added to `DaemonConfig`; surfaced as `PANEL_PUBLIC_DIR` / `PANEL_TEMPLATES_DIR` env vars and CLI args.
- `ServeDir` mounted at `/public` in Axum router (tower-http); attached before `.with_state(state)` in routes builder.
- UnoCSS + DaisyUI + HTMX build pipeline: `package.json`, `uno.config.ts`, multi-stage `Dockerfile` (`node-build` + `rust-build` + `runtime`). Docker build context must be the parent directory of all sibling repos — required for path deps to resolve.
- `AssetUrls` path contract: `/public/css/app.css`, `/public/vendor/htmx.min.js`.
**ADRs authored**
- `lian-build/adrs/adr-008-ontoref-foundation-migration.ncl` — migration rationale, γ verdicts per crate, ScriptProvider partial finding
- `ontoref-compute/adrs/adr-001-non-exhaustive-constructors.ncl` — named constructor contract for non-exhaustive output types
- `ontoref-panel/adrs/adr-007-static-asset-pipeline.md` — UnoCSS+DaisyUI choice, Docker build context contract, `AssetUrls` path binding
- `provisioning/adrs/adr-043-ontoref-foundation-orchestrator.ncl` — orchestrator γ-posture adoption (DnsProvider + ComputeProvider + federation manifest endpoint)
**Self-Description — on+re Update**
`.ontology/state.ncl``protocol-stable` transition note: γ-posture validation results recorded per consumer; ScriptProvider envelope gap documented as known partial finding with adapter path (`LianBuildScriptProvider`).
`.ontology/core.ncl``unified-auth-model` updated with γ-posture verdicts for all four foundation crates; `ontoref-daemon` updated with handlers/ split note, `TeraEnv` migration note, panel C.5 CSS pipeline description.
---
### MCP tool catalog via proc-macro, validate ADR-018 mode hierarchy, UI vault/registry context ### MCP tool catalog via proc-macro, validate ADR-018 mode hierarchy, UI vault/registry context
**Session 2026-05-12** **Session 2026-05-12**

437
Cargo.lock generated
View file

@ -447,6 +447,27 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "auth"
version = "0.1.0"
dependencies = [
"argon2",
"axum",
"base64",
"dashmap",
"ed25519-dalek",
"jsonwebtoken",
"rand 0.10.1",
"rand_core 0.6.4",
"serde",
"serde_json",
"sha2 0.11.0",
"thiserror 2.0.18",
"time",
"tokio",
"uuid",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.0"
@ -604,6 +625,21 @@ dependencies = [
"virtue", "virtue",
] ]
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -634,7 +670,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@ -660,6 +696,15 @@ dependencies = [
"generic-array 0.14.7", "generic-array 0.14.7",
] ]
[[package]]
name = "block-buffer"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
dependencies = [
"hybrid-array",
]
[[package]] [[package]]
name = "blocking" name = "blocking"
version = "1.6.2" version = "1.6.2"
@ -887,7 +932,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [ dependencies = [
"crypto-common", "crypto-common 0.1.7",
"inout", "inout",
] ]
@ -971,6 +1016,12 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.4.2" version = "0.4.2"
@ -1121,6 +1172,15 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
]
[[package]] [[package]]
name = "cssparser" name = "cssparser"
version = "0.35.0" version = "0.35.0"
@ -1153,7 +1213,7 @@ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.17", "cpufeatures 0.2.17",
"curve25519-dalek-derive", "curve25519-dalek-derive",
"digest", "digest 0.10.7",
"fiat-crypto", "fiat-crypto",
"rustc_version", "rustc_version",
"subtle", "subtle",
@ -1231,7 +1291,7 @@ version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [ dependencies = [
"const-oid", "const-oid 0.9.6",
"pem-rfc7468", "pem-rfc7468",
"zeroize", "zeroize",
] ]
@ -1258,12 +1318,23 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer", "block-buffer 0.10.4",
"const-oid", "const-oid 0.9.6",
"crypto-common", "crypto-common 0.1.7",
"subtle", "subtle",
] ]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer 0.12.0",
"const-oid 0.10.2",
"crypto-common 0.2.2",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.5"
@ -1329,7 +1400,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [ dependencies = [
"der", "der",
"digest", "digest 0.10.7",
"elliptic-curve", "elliptic-curve",
"rfc6979", "rfc6979",
"signature", "signature",
@ -1354,8 +1425,9 @@ checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
dependencies = [ dependencies = [
"curve25519-dalek", "curve25519-dalek",
"ed25519", "ed25519",
"rand_core 0.6.4",
"serde", "serde",
"sha2", "sha2 0.10.9",
"signature", "signature",
"subtle", "subtle",
"zeroize", "zeroize",
@ -1375,7 +1447,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"crypto-bigint", "crypto-bigint",
"digest", "digest 0.10.7",
"ff", "ff",
"generic-array 0.14.7", "generic-array 0.14.7",
"group", "group",
@ -2048,7 +2120,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@ -2139,6 +2211,15 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]]
name = "hybrid-array"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
dependencies = [
"typenum",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.8.1" version = "1.8.1"
@ -2579,7 +2660,7 @@ dependencies = [
"rsa", "rsa",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2 0.10.9",
"signature", "signature",
"simple_asn1", "simple_asn1",
] ]
@ -2758,7 +2839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@ -3071,25 +3152,73 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "ontoref-blobs"
version = "0.1.7"
dependencies = [
"blake3",
"ontoref-types",
"serde",
"tempfile",
"thiserror 2.0.18",
]
[[package]]
name = "ontoref-commit"
version = "0.1.7"
dependencies = [
"blake3",
"ed25519-dalek",
"ontoref-types",
"serde",
"thiserror 2.0.18",
]
[[package]]
name = "ontoref-core"
version = "0.1.7"
dependencies = [
"ed25519-dalek",
"ontoref-blobs",
"ontoref-commit",
"ontoref-oplog",
"ontoref-query",
"ontoref-sync",
"ontoref-triples",
"ontoref-types",
"serde",
"tempfile",
"thiserror 2.0.18",
]
[[package]] [[package]]
name = "ontoref-daemon" name = "ontoref-daemon"
version = "0.1.0" version = "0.1.7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
"async-graphql", "async-graphql",
"async-graphql-axum", "async-graphql-axum",
"auth",
"axum", "axum",
"axum-server", "axum-server",
"blake3",
"bytes", "bytes",
"clap", "clap",
"dashmap", "dashmap",
"ed25519-dalek",
"hostname", "hostname",
"inventory", "inventory",
"libc", "libc",
"notify", "notify",
"ontoref-core",
"ontoref-derive", "ontoref-derive",
"ontoref-ontology", "ontoref-ontology",
"ontoref-ontology-content",
"ontoref-oplog",
"ontoref-ops",
"ontoref-sync",
"ontoref-types",
"platform-nats", "platform-nats",
"reqwest", "reqwest",
"rmcp", "rmcp",
@ -3108,12 +3237,13 @@ dependencies = [
"tower-http", "tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"ui",
"uuid", "uuid",
] ]
[[package]] [[package]]
name = "ontoref-derive" name = "ontoref-derive"
version = "0.1.0" version = "0.1.7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3122,7 +3252,7 @@ dependencies = [
[[package]] [[package]]
name = "ontoref-ontology" name = "ontoref-ontology"
version = "0.1.0" version = "0.1.7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"inventory", "inventory",
@ -3134,9 +3264,69 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "ontoref-ontology-content"
version = "0.1.7"
dependencies = [
"blake3",
"ed25519-dalek",
"ontoref-commit",
"ontoref-oplog",
"ontoref-types",
"serde",
"serde_json",
"smol_str",
"tempfile",
"thiserror 2.0.18",
"tracing",
]
[[package]]
name = "ontoref-oplog"
version = "0.1.7"
dependencies = [
"ed25519-dalek",
"ontoref-types",
"redb",
"tempfile",
"thiserror 2.0.18",
]
[[package]]
name = "ontoref-ops"
version = "0.1.7"
dependencies = [
"anyhow",
"blake3",
"ed25519-dalek",
"inventory",
"ontoref-commit",
"ontoref-derive",
"ontoref-ontology",
"ontoref-oplog",
"ontoref-types",
"serde",
"serde_json",
"smol_str",
"tempfile",
"thiserror 2.0.18",
"tracing",
]
[[package]]
name = "ontoref-query"
version = "0.1.7"
dependencies = [
"ed25519-dalek",
"ontoref-triples",
"ontoref-types",
"tempfile",
"thiserror 2.0.18",
]
[[package]] [[package]]
name = "ontoref-reflection" name = "ontoref-reflection"
version = "0.1.0" version = "0.1.7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -3156,6 +3346,45 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "ontoref-sync"
version = "0.1.7"
dependencies = [
"ontoref-types",
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"tracing",
]
[[package]]
name = "ontoref-triples"
version = "0.1.7"
dependencies = [
"blake3",
"ed25519-dalek",
"ontoref-types",
"redb",
"serde",
"tempfile",
"thiserror 2.0.18",
]
[[package]]
name = "ontoref-types"
version = "0.1.7"
dependencies = [
"blake3",
"ciborium",
"ed25519-dalek",
"indexmap",
"proptest",
"serde",
"smol_str",
"thiserror 2.0.18",
]
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.2.1" version = "0.2.1"
@ -3171,7 +3400,7 @@ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
"primeorder", "primeorder",
"sha2", "sha2 0.10.9",
] ]
[[package]] [[package]]
@ -3183,7 +3412,7 @@ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
"primeorder", "primeorder",
"sha2", "sha2 0.10.9",
] ]
[[package]] [[package]]
@ -3263,10 +3492,10 @@ version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
"hmac", "hmac",
"password-hash", "password-hash",
"sha2", "sha2 0.10.9",
] ]
[[package]] [[package]]
@ -3340,7 +3569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
dependencies = [ dependencies = [
"pest", "pest",
"sha2", "sha2 0.10.9",
] ]
[[package]] [[package]]
@ -3602,6 +3831,25 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "proptest"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
dependencies = [
"bit-set",
"bit-vec",
"bitflags 2.11.0",
"num-traits",
"rand 0.9.4",
"rand_chacha 0.9.0",
"rand_xorshift",
"regex-syntax",
"rusty-fork",
"tempfile",
"unarray",
]
[[package]] [[package]]
name = "prost" name = "prost"
version = "0.14.3" version = "0.14.3"
@ -3660,6 +3908,12 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quick_cache" name = "quick_cache"
version = "0.6.19" version = "0.6.19"
@ -3842,6 +4096,15 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
name = "rand_xorshift"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
"rand_core 0.9.5",
]
[[package]] [[package]]
name = "rawpointer" name = "rawpointer"
version = "0.2.1" version = "0.2.1"
@ -3874,6 +4137,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc4a4ea2a66a41a1152c4b3d86e8954dc087bdf33af35446e6e176db4e73c8c" checksum = "bbc4a4ea2a66a41a1152c4b3d86e8954dc087bdf33af35446e6e176db4e73c8c"
[[package]]
name = "redb"
version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eca1e9d98d5a7e9002d0013e18d5a9b000aee942eb134883a82f06ebffb6c01"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.18" version = "0.5.18"
@ -4157,8 +4429,8 @@ version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
dependencies = [ dependencies = [
"const-oid", "const-oid 0.9.6",
"digest", "digest 0.10.7",
"num-bigint-dig", "num-bigint-dig",
"num-integer", "num-integer",
"num-traits", "num-traits",
@ -4261,6 +4533,40 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "rust-embed"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.117",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
dependencies = [
"sha2 0.10.9",
"walkdir",
]
[[package]] [[package]]
name = "rust-stemmers" name = "rust-stemmers"
version = "1.2.0" version = "1.2.0"
@ -4407,6 +4713,18 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "rusty-fork"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
dependencies = [
"fnv",
"quick-error",
"tempfile",
"wait-timeout",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.23" version = "1.0.23"
@ -4481,7 +4799,7 @@ dependencies = [
"password-hash", "password-hash",
"pbkdf2", "pbkdf2",
"salsa20", "salsa20",
"sha2", "sha2 0.10.9",
] ]
[[package]] [[package]]
@ -4661,9 +4979,15 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.17", "cpufeatures 0.2.17",
"digest", "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"
@ -4672,7 +4996,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.17", "cpufeatures 0.2.17",
"digest", "digest 0.10.7",
]
[[package]]
name = "sha2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
"cpufeatures 0.3.0",
"digest 0.11.3",
] ]
[[package]] [[package]]
@ -4724,7 +5059,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
@ -4777,6 +5112,16 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "smol_str"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523"
dependencies = [
"borsh",
"serde_core",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.6.3" version = "0.6.3"
@ -5070,7 +5415,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sha1", "sha1",
"sha2", "sha2 0.10.9",
"storekey", "storekey",
"strsim", "strsim",
"subtle", "subtle",
@ -5822,9 +6167,9 @@ dependencies = [
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.19.0" version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]] [[package]]
name = "ucd-trie" name = "ucd-trie"
@ -5832,6 +6177,20 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "ui"
version = "0.1.0"
dependencies = [
"notify",
"rust-embed",
"serde",
"serde_json",
"tera",
"thiserror 2.0.18",
"tracing",
"walkdir",
]
[[package]] [[package]]
name = "ulid" name = "ulid"
version = "1.2.1" version = "1.2.1"
@ -5843,6 +6202,12 @@ dependencies = [
"web-time", "web-time",
] ]
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.9.0" version = "2.9.0"
@ -5930,6 +6295,7 @@ dependencies = [
"getrandom 0.4.2", "getrandom 0.4.2",
"js-sys", "js-sys",
"serde_core", "serde_core",
"sha1_smol",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -5957,6 +6323,15 @@ version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.5.0" version = "2.5.0"

View file

@ -3,6 +3,7 @@ members = ["crates/*"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "0.1.7"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
@ -32,3 +33,12 @@ hostname = "0.4"
libc = "0.2" libc = "0.2"
reqwest = { version = "0.13", features = ["json"] } reqwest = { version = "0.13", features = ["json"] }
inventory = "0.3" inventory = "0.3"
# Substrate kernel (G1+) — see .coder/2026-05-25-implementation-final.plan.md §7.3
blake3 = "1.5"
ed25519-dalek = { version = "2", features = ["rand_core"] }
ciborium = "0.2"
smol_str = { version = "0.3", features = ["serde"] }
indexmap = { version = "2", features = ["serde"] }
proptest = "1"
redb = "2"

View file

@ -1,3 +1,10 @@
# DEV / LOCAL ONLY (ADR-038). This recompiles from source with the stratumiops
# build-context — convenient for local single-arch image builds. It is NOT the
# release path: the published multi-arch runnable image is assembled by
# `.woodpecker/release.yml` with buildah FROM the cross-compiled musl binaries
# (no recompile, no buildx/QEMU). Regenerate that pipeline with
# `ore workflow generate --layer release`.
ARG IMAGE_BASE=reg.librecloud.online/lamina ARG IMAGE_BASE=reg.librecloud.online/lamina
ARG RUST_VERSION=1.89 ARG RUST_VERSION=1.89

View file

@ -1,2 +0,0 @@
Hay que tener en cuenta que existe una descoordinación real y absoluta entre los Justfiles de los proyectos.
Esto debería de ser armonizado o sincronizado: elementos comunes en un template o un modo específico ampliable según cada proyecto.

157
README.md
View file

@ -16,28 +16,83 @@ execute formalized procedures — all as typed, queryable artifacts.
| Axiom | Meaning | | Axiom | Meaning |
| --- | --- | | --- | --- |
| **Protocol, Not Runtime** | Never a runtime dependency. Projects adopt the protocol; ontoref provides the schemas and modules to do so. | | **Protocol, Not Runtime** | Never a runtime dependency. Projects adopt the protocol; ontoref provides the schemas and modules to do so. |
| **Self-Describing** | Consumes its own protocol: `.ontology/`, `adrs/`, `reflection/` in this repo ARE ontoref running against itself. | | **Self-Describing** | Consumes its own protocol: `.ontoref/{ontology,adrs,reflection}/` in this repo ARE ontoref running against itself (consolidated per ADR-032). |
| **No Enforcement** | ontoref defines contracts. There is no enforcement mechanism. Coherence is voluntary and emerges from justified adoption. | | **No Enforcement** | ontoref defines contracts. There is no enforcement mechanism. Coherence is voluntary and emerges from justified adoption. |
| **DAG-Formalized Knowledge** | Concepts, tensions, decisions, state — encoded as DAGs. Enables transversal queries and impact analysis. | | **DAG-Formalized Knowledge** | Concepts, tensions, decisions, state — encoded as DAGs. Enables transversal queries and impact analysis. |
## Tiers — coexisting permanently (ADR-029)
Adoption is **voluntary, additive, and offline-first**. A project picks the lowest tier
that delivers the value it needs and climbs only when concrete pressure justifies the cost.
Tier choice is per-project, indefinite, and recorded as a deliberate act (`ops.tier` in
`.ontoref/config.ncl`) — no code path ever migrates a project's tier silently.
| Tier | Adopts | Authoritative state | Mutation path | Cryptography |
| --- | --- | --- | --- | --- |
| **tier-0** | Minimal NCL | files | edit anywhere | none |
| **tier-1** | Verifiable substrate | NCL + commit layer | edit + reconcile | blake3 `state_root` |
| **tier-2** | Operations layer | Rust | `dispatch_op` only | blake3 + Ed25519 witnesses |
Each tier **includes** the one below plus its delta; no tier removes a lower tier's
capabilities. No tier requires network or external services — the default commitment,
sync, and blob backends are all in-memory/filesystem; P2P transports (Iroh / Hypercore /
Radicle / NATS) and remote backends (S3 / OCI) are trigger-based. The same `ontoref-daemon`
binary serves every tier; build features (`db`, `nats`, `ui`, `mcp`, `graphql`) are
orthogonal to tier.
## Layers ## Layers
```text ```text
ontology/ Protocol specification — Nickel schemas for nodes, edges, ADRs, state, gates ontology/ Protocol specification — Nickel schemas/defaults for nodes, edges, ADRs, state, gates, positioning
adrs/ Architecture Decision Records — typed NCL with constraints and ontology checks crates/ Rust implementation — typed struct loaders, ops, validators, daemon
reflection/ Operational tooling — Nushell modules, DAG modes, forms, and schemas .ontoref/ontology/ Self-description — ontoref's own ontology, state, gate, manifest
crates/ Rust implementation — typed struct loaders and mode executors .ontoref/adrs/ Architecture Decision Records — typed NCL with constraints and ondaod evaluations
.ontology/ Self-description — ontoref's own ontology, state, gate, and manifest .ontoref/reflection/ Operational tooling — Nushell modules, DAG modes, forms, schemas, migrations
.ontoref/catalog/ Operations + validators (tier-2) — typed NCL declarations
.ontoref/positioning/ Outward-facing surface as a four-element framework (ADR-035, ADR-043) — three orthogonal axes (QUÉ: Differentiator/Competitor · PARA QUIÉN: Audience · CÓMO: DifusionMechanism/Campaign) bound by a Proof seam (VALIDACIÓN). Queryable NCL with `evidence_adrs` anchoring claims to ADRs; `ore positioning audit` self-applies five coherence rules (opt-in, tier-orthogonal)
.coder/interactions.jsonl Interaction trace (ADR-037) — parse-first session records as JSONL; filter, don't prose-mine (opt-in, tier-orthogonal; optional witness binding)
.ontoref/artifacts/ Generated artefacts (api-catalog, etc.)
.ontoref/card.ncl Project beacon — typed self-definition (optional `primary_value_prop_id`)
.governance/ Governed delivery (ADR-063/066, migration 0041, opt-in) — human-signed SOWs, witness.pub, signed Work-Order receipts. The governed-delivery mode's executor enforces the gates: 'Block guards abort, verify-declared steps derive status from the check's exit code, typed postchecks gate `mode complete` (exit 1 when incomplete); done requires two out-of-band human signatures
``` ```
Per ADR-032 the consumer-facing surface is consolidated under a single hidden
root `.ontoref/`. Layout is config-driven (`LayoutConfig`); the defaults shown
above may be overridden via `.ontoref/config.ncl[layout]`.
## Crates ## Crates
The workspace is 14 crates on a strict dependency hierarchy, organized by tier. All share a
single version (`[workspace.package].version`).
**Tier-0 — adoption surface**
| Crate | Purpose | | Crate | Purpose |
| --- | --- | | --- | --- |
| `ontoref-ontology` | `.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. Zero deps. | | `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-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-daemon` | HTTP UI (11 pages), actor registry, notification barrier, MCP (34 tools), search engine, search bookmarks, SurrealDB, NCL export cache, per-file ontology versioning, annotated API catalog, Agent Task Composer. | | `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 in the catalog 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!` at link time, merged into `Core` by `merge_contributors()`. `#[derive(ConfigFields)]` + `#[config_section(id, ncl_file)]` registers config struct fields. All four aggregate via `inventory::collect!`. |
**Tier-1 / tier-2 — verifiable substrate** (per ADR-023/024/025/026/027)
| Crate | Purpose |
| --- | --- |
| `ontoref-types` | Shared substrate primitives: content hashes, ids, canonical serialization. |
| `ontoref-blobs` | Content-addressed blob store; default `LocalFilesystemBlobs`, S3/OCI trigger-based. |
| `ontoref-triples` | Triple store for the content-addressed ontology representation. |
| `ontoref-commit` | Commit layer + `state_root` (blake3 Merkle); the tier-1 boundary. |
| `ontoref-oplog` | Append-only operation-log DAG; the witnessed history of mutations. |
| `ontoref-query` | Query surface over the substrate (cells, witnesses, history). |
| `ontoref-sync` | Pluggable sync (CRDT per domain, ADR-027); default `FilesystemSync`, P2P trigger-based. |
| `ontoref-ops` | Tier-2 operations dispatch — `dispatch_op`, three validation planes (ADR-026), Ed25519 witness emission (ADR-024). |
| `ontoref-core` | Substrate composition root tying the layers together. |
| `ontoref-ontology-content` | Content-addressed ontology bridge between `ontoref-ontology` and the substrate. |
**Daemon**
| Crate | Purpose |
| --- | --- |
| `ontoref-daemon` | HTTP UI (11 pages), actor registry, notification barrier, MCP (34 tools), search engine, search bookmarks, SurrealDB, NCL export cache, per-file ontology versioning, annotated API catalog, Agent Task Composer. Substrate features (`db`, `nats`) are feature-gated; builds standalone with `--no-default-features`. |
`ontoref-daemon` caches `nickel export` results (keyed by path + mtime), reducing full sync `ontoref-daemon` caches `nickel export` results (keyed by path + mtime), reducing full sync
scans from ~2m42s to <30s. The daemon is always optional every module falls back to direct scans from ~2m42s to <30s. The daemon is always optional every module falls back to direct
@ -91,7 +146,7 @@ surface as JSON. The `/ui/{slug}/api` page renders it with client-side filtering
it to MCP agents. it to MCP agents.
**Semantic Diff** — `describe diff [--file <ncl>] [--fmt json|text]` computes a node- and edge-level **Semantic Diff** — `describe diff [--file <ncl>] [--fmt json|text]` computes a node- and edge-level
diff of `.ontology/` files against the last git commit. Reports added/removed/changed nodes by id and diff of `.ontoref/ontology/` files against the last git commit. Reports added/removed/changed nodes by id and
edges by `from→to[kind]` key — not a text diff. edges by `from→to[kind]` key — not a text diff.
**Per-File Versioning** — each ontology file tracked in `ProjectContext.file_versions: DashMap<PathBuf, u64>`. **Per-File Versioning** — each ontology file tracked in `ProjectContext.file_versions: DashMap<PathBuf, u64>`.
@ -110,7 +165,7 @@ For `.rs` files, `card.docs` redirects to the cargo docs URL instead. `insert_br
injects both as `card_repo`/`card_docs` into every Tera template. injects both as `card_repo`/`card_docs` into every Tera template.
**Passive Drift Observation** — background file watcher that detects divergence between Yang **Passive Drift Observation** — background file watcher that detects divergence between Yang
code artifacts and Yin ontology. Watches `crates/`, `.ontology/`, `adrs/`, `reflection/modes/`. code artifacts and Yin ontology. Watches `crates/`, `.ontoref/ontology/`, `adrs/`, `reflection/modes/`.
After a 15s debounce runs `sync scan + sync diff`; emits an `ontology_drift` notification when After a 15s debounce runs `sync scan + sync diff`; emits an `ontology_drift` notification when
MISSING/STALE/DRIFT/BROKEN items are found. Never applies changes — `apply` is always deliberate. MISSING/STALE/DRIFT/BROKEN items are found. Never applies changes — `apply` is always deliberate.
@ -139,15 +194,34 @@ gate — Rust structs are contract-trusted readers with `#[serde(default)]`.
Ontoref demonstrates the pattern on itself: `.ontoref/contracts.ncl` applies `LogConfig` and Ontoref demonstrates the pattern on itself: `.ontoref/contracts.ncl` applies `LogConfig` and
`DaemonConfig` contracts to `.ontoref/config.ncl`. ([ADR-008](adrs/adr-008-ncl-first-config-validation-and-override-layer.ncl)) `DaemonConfig` contracts to `.ontoref/config.ncl`. ([ADR-008](adrs/adr-008-ncl-first-config-validation-and-override-layer.ncl))
**Operations & Catalog Extensibility (tier-2)** — tier-2 mutations route exclusively through
`dispatch_op`: typed preconditions, three validation planes (ADR-026), an Ed25519 **witness**
per act, oplog DAG append. Operations and validators are declared as typed NCL in
`.ontoref/catalog/` and the catalog is **discoverable across projects** (ADR-030) via ADR-028
content-addressing. The `OperationDecl` / `ValidatorDecl` contracts carry a `kind` discriminator
admitting `'Rust` (default, executable today via `#[onto_operation]` + inventory), `'NclTransform`,
`'Wasm`, and `'Sidecar` (ADR-034, migration 0025). The schema admits all four *today*
**declare before execute**: a non-Rust kind is declarable but unexecutable until its executive
backend lands (Sidecar is Phase A, near-term; Wasm / NclTransform are trigger-deferred per ADR-030),
and the daemon returns a structured **`501`** rather than a silent `404`. Two Hard guarantees keep
the design honest: `wasmtime-not-in-default-daemon-build` (adding the `'Wasm` *kind* never pulls a
WASM runtime into the default build — **protocol-not-runtime** stays intact) and
`non-rust-kinds-require-ondaod-pre` (non-Rust ops can't run ondaod inline, so they MUST declare
`evaluate_ondaod` in `constraints.pre` — discipline becomes structural). Four worked examples that
typecheck today live in `examples/catalog-extension/` (rust · sidecar · ncl-transform · wasm);
`ore q catalog extension` is the FAQ.
([ADR-034](adrs/adr-034-catalog-extensibility-beyond-rust.ncl), [ADR-030](adrs/adr-030-catalog-discovery-cross-project.ncl))
**Protocol Migration System** — protocol upgrades for consumer projects expressed as ordered NCL files **Protocol Migration System** — protocol upgrades for consumer projects expressed as ordered NCL files
in `reflection/migrations/NNN-slug.ncl`. Each migration declares a typed check (`FileExists | Grep | in `reflection/migrations/NNN-slug.ncl`. Each migration declares a typed check (`FileExists | Grep |
NuCmd`) whose result IS the applied state — no state file, fully idempotent. `migrate list` shows all NuCmd`) whose result IS the applied state — no state file, fully idempotent. `migrate list` shows all
migrations with applied/pending status; `migrate pending` lists only what is missing; `migrate show <id>` migrations with applied/pending status; `migrate pending` lists only what is missing; `migrate show <id>`
renders runtime-interpolated instructions (project_root and project_name auto-detected). NuCmd checks are renders runtime-interpolated instructions (project_root and project_name auto-detected). NuCmd checks are
valid Nushell (no bash `&&`, `$env.VAR` not `$VAR`). Grep checks targeting ADR files scope to valid Nushell (no bash `&&`, `$env.VAR` not `$VAR`). Grep checks targeting ADR files scope to
`adr-[0-9][0-9][0-9]-*.ncl` to exclude schema/template infrastructure files. 12 migrations shipped; `adr-[0-9][0-9][0-9]-*.ncl` to exclude schema/template infrastructure files. 29 migrations shipped
`0012-rust-doc-authoring-pattern` adds the `/// → //! → node description` three-layer doc convention (`0001``0029`); `0012-rust-doc-authoring-pattern` adds the `/// → //! → node description` three-layer
and optional pre-commit hooks (`docs-links`, `docs-drift`) to consumer `CLAUDE.md`. ([ADR-010](adrs/adr-010-protocol-migration-system.ncl)) doc convention; `0025-catalog-kind-field` ships the catalog `kind` discriminator; `0027`/`0028`/`0029`
ship the positioning layer, interaction trace, and OCI distribution. ([ADR-010](adrs/adr-010-protocol-migration-system.ncl))
**Manifest Self-Interrogation** — `manifest_type` gains three typed arrays that answer self-knowledge **Manifest Self-Interrogation** — `manifest_type` gains three typed arrays that answer self-knowledge
queries agents and operators need on cold start: `capabilities[]` (what the project does, why it was queries agents and operators need on cold start: `capabilities[]` (what the project does, why it was
@ -171,8 +245,18 @@ dispatcher. ([ADR-012](adrs/adr-012-domain-extension-system.ncl))
**Mode Hierarchy Validation** — `validate modes [--check]` reads `reflection/defaults/workflow.ncl::level_hierarchy` **Mode Hierarchy Validation** — `validate modes [--check]` reads `reflection/defaults/workflow.ncl::level_hierarchy`
and checks every `.ncl` mode file for: level declared, strategy declared, delegate chain coherent, and checks every `.ncl` mode file for: level declared, strategy declared, delegate chain coherent,
compose `extends` references valid. `mode resolve <id>` prints which hierarchy level handles the compose `extends` references valid. `mode resolve <id>` prints which hierarchy level handles the
given mode and why. `validate modes --self-test` generates synthetic fixtures in a temp dir for given mode and why — and (ADR-045) the **full traversal path** plus the **chain scope**, not a single hop.
fast CI smoke-testing of the validator itself. ([ADR-018](adrs/adr-018-workflow-layer-model.ncl)) `validate modes --self-test` generates synthetic fixtures in a temp dir for
fast CI smoke-testing of the validator itself. ([ADR-018](adrs/adr-018-level-hierarchy-mode-resolution-strategy.ncl))
**Domain Co-Tenancy Validation** (ADR-045) — `validate hosts [--check]` validates recursive level chains
and cross-project hosting declared in `manifest.ncl::hosts`. Five checks: `chain-ref` (hosted chain
resolvable by local catalog domain or digest pin — witness, not clone), `mutation-sovereignty` (no local
op may `render_paths` into a hosted `mount` — the host must not mutate hosted authoritative state),
`chain-root` (the `level.parent` walk terminates at a `'Base`; a cycle is a Hard failure) — all Hard;
`layer3-tag` and `derived-index` — Soft. Levels are **relative**: depth is derived by walking `level.parent`
and is uncapped. `validate hosts --self-test` exercises every check on synthetic fixtures.
([ADR-045](adrs/adr-045-recursive-level-chains-and-domain-cotenancy.ncl))
**Project Picker — vault and registry badges** — each project card surfaces OCI state inline: **Project Picker — vault and registry badges** — each project card surfaces OCI state inline:
registry participant badge (`⟳ <participant>`) when `registry_provides` is declared; vault badge registry participant badge (`⟳ <participant>`) when `registry_provides` is declared; vault badge
@ -187,7 +271,7 @@ switched without a daemon restart via HTMX `POST /ui/manage/services/{service}/t
filesystem-based (`.jj/` vs `.git/`), no config required. jj is opt-in: all operations degrade to filesystem-based (`.jj/` vs `.git/`), no config required. jj is opt-in: all operations degrade to
git when `.jj/` is absent. `reflection/bin/jjw.nu` wraps jj workspaces, ontoref runs, and optional git when `.jj/` is absent. `reflection/bin/jjw.nu` wraps jj workspaces, ontoref runs, and optional
Radicle patch submission into a single `jjw agent create|step|publish|merge|discard` lifecycle for Radicle patch submission into a single `jjw agent create|step|publish|merge|discard` lifecycle for
agent-driven development. `jjw-ncl-merge.nu` is a jj merge tool for `.ontology/` NCL conflicts, agent-driven development. `jjw-ncl-merge.nu` is a jj merge tool for `.ontoref/ontology/` NCL conflicts,
registered manually in `~/.config/jj/config.toml`. jj and Radicle are not protocol requirements — registered manually in `~/.config/jj/config.toml`. jj and Radicle are not protocol requirements —
consumer projects use plain git without any configuration change. consumer projects use plain git without any configuration change.
@ -212,6 +296,39 @@ Global config at `~/.config/ontoref/config.ncl` (type-checked Nickel). Global NA
`~/.config/ontoref/streams.json`. Project-local topology override via `nats/streams.json` + `~/.config/ontoref/streams.json`. Project-local topology override via `nats/streams.json` +
`nats_events.streams_config` in `.ontoref/config.ncl`. `nats_events.streams_config` in `.ontoref/config.ncl`.
### Install without a source checkout (OCI distribution, ADR-038)
Two paths share one build, both from the OCI registry. The daemon is an **optional
accelerator** (ADR-029) — the `curl|sh` path installs a CLI + data layer that work without it.
```sh
# 1. curl | sh — install the CLI + data layer on a Linux host (amd64/arm64)
curl -fsSL https://get.librecloud.online/ontoref/install.sh | sh
curl -fsSL https://get.librecloud.online/ontoref/install.sh | sh -s -- --prefix ~/.local --version 0.1.5
sh install.sh --uninstall # remove binaries; --purge also removes data + config
# 2. Container — run the daemon as a service (multi-arch, cosign-signed)
docker run --rm -p 7891:7891 reg.librecloud.online/ontoref/ontoref-daemon:0.1.5
```
`install.sh` detects OS/arch, pulls the per-platform bundle from the anonymous-pull
`ontoref/dist` namespace via `oras` (curl fallback to the OCI Distribution API), verifies
the `.sha256` sidecar, and lays out binaries + data + wrappers exactly as `install.nu` does.
`nickel` is bundled; `nushell` is the only external runtime dependency (detected, with install
guidance if absent). macOS has no published build — `install.sh` prints source-build/container
guidance.
Distribution is **modeled in the workflow layer**, not hand-maintained: the `release` layer in
`.ontoref/ontology/workflow.ncl` drives the build-once pipeline (`cross` compiles each arch once;
both the bundle and the buildah-assembled image consume that same binary). Regenerate the
artifacts — never edit them by hand:
```sh
ore workflow list # the release layer is visible
ore workflow generate --layer release # → .woodpecker/release.yml, install/install.sh, justfiles/release.just
ore q oci distribution # FAQ: both paths, build-once, forbidden patterns
```
## Onboarding a project ## Onboarding a project
```sh ```sh
@ -223,7 +340,7 @@ ontoref setup --gen-keys ["admin:dev" "viewer:ci"] # bootstrap auth keys (no-o
``` ```
`ontoref setup` creates `.ontoref/project.ncl`, `.ontoref/config.ncl` (with logo auto-detection), `ontoref setup` creates `.ontoref/project.ncl`, `.ontoref/config.ncl` (with logo auto-detection),
`.ontology/` scaffold, `adrs/`, `reflection/modes/`, `backlog.ncl`, `qa.ncl`, git hooks, and `.ontoref/ontology/` scaffold, `adrs/`, `reflection/modes/`, `backlog.ncl`, `qa.ncl`, git hooks, and
registers the project in `~/.config/ontoref/projects.ncl`. registers the project in `~/.config/ontoref/projects.ncl`.
For existing projects that predate `setup`, or to bring an already-adopted project up to the For existing projects that predate `setup`, or to bring an already-adopted project up to the
@ -299,7 +416,7 @@ See FAQ entries in `reflection/qa.ncl` for diagrams, troubleshooting, and the
- [restic](https://restic.net/) or [kopia](https://kopia.io/) (vault snapshots) - [restic](https://restic.net/) or [kopia](https://kopia.io/) (vault snapshots)
To build `ontoref-daemon` and `ontoref-reflection` with NATS/SurrealDB support, the To build `ontoref-daemon` and `ontoref-reflection` with NATS/SurrealDB support, the
stratumiops repo must be checked out at `../../../stratumiops`. Without it, build without stratumiops repo must be checked out at `../../../../stratumiops/code` (constellation layout — sibling project, `code/` sub-repo). Without it, build without
default features: default features:
```sh ```sh
@ -329,7 +446,7 @@ Three canonical layers — no duplication across them:
|-------|-------|---------| |-------|-------|---------|
| `///` first line | handlers, structs, types | `#[onto_api]`, `#[derive(OntologyNode)]`, MCP | | `///` first line | handlers, structs, types | `#[onto_api]`, `#[derive(OntologyNode)]`, MCP |
| `//!` first sentence | `lib.rs` | `describe features`, mdBook crates chapter, drift check | | `//!` first sentence | `lib.rs` | `describe features`, mdBook crates chapter, drift check |
| node `description` | `.ontology/core.ncl` | UI graph, `describe project`, CLI | | node `description` | `.ontoref/ontology/core.ncl` | UI graph, `describe project`, CLI |
`sync diff --docs --fail-on-drift` (used by pre-commit `docs-drift` hook) enforces that `//!` first `sync diff --docs --fail-on-drift` (used by pre-commit `docs-drift` hook) enforces that `//!` first
sentence stays aligned with the practice node description (Jaccard ≥ 0.20 threshold). sentence stays aligned with the practice node description (Jaccard ≥ 0.20 threshold).

View file

@ -1,51 +0,0 @@
# ADR template — plain record for typedialog roundtrip input.
# No contracts applied here; contracts are enforced in the Jinja2 output template.
#
# Usage:
# typedialog nickel-roundtrip \
# --input adrs/_template.ncl \
# --form reflection/forms/new_adr.ncl \
# --output adrs/adr-NNN-title.ncl \
# --ncl-template reflection/templates/adr.ncl.j2
{
id = "adr-000",
title = "",
status = "Proposed",
date = "2026-03",
context = "",
decision = "",
rationale = [
{ claim = "", detail = "" },
],
consequences = {
positive = [""],
negative = [""],
},
alternatives_considered = [
{ option = "", why_rejected = "" },
],
constraints = [
{
id = "",
claim = "",
scope = "",
severity = "Hard",
check = { tag = 'NuCmd, cmd = "", expect_exit = 0 },
rationale = "",
},
],
related_adrs = [],
ontology_check = {
decision_string = "",
invariants_at_risk = [],
verdict = "Safe",
},
}

View file

@ -1,106 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-001",
title = "Ontoref is a Standalone Protocol Project, Not Part of Stratumiops",
status = 'Accepted,
date = "2026-03-12",
context = "The ontology/reflection patterns originated inside stratumiops as self-description tooling (stratum-ontology-core, stratum-reflection-core, stratum-daemon). Consumer projects (typedialog, vapora, kogral) needed the same patterns but had no path to adoption that did not entangle them with stratumiops' pipeline-specific crates (stratum-graph, stratum-state, stratum-orchestrator). Protocol evolution (schema changes, ADR lifecycle, daemon features) was blocked behind stratumiops release cycles. The three crates were logically a specification layer that happened to live in the wrong repo.",
decision = "Ontoref is extracted as a standalone protocol project with independent versioning, CI, and crates: ontoref-ontology (Rust types for the ontology graph), ontoref-reflection (mode runner and schema validation), ontoref-daemon (NCL export cache, file watcher, actor registry). Consumer projects adopt the protocol via a thin `scripts/ontoref` bash wrapper and a `.ontoref/config.ncl` declaration. The ontoref project itself is allowed to use stratum-db and platform-nats as optional peer dependencies via workspace path references, since those crates are infrastructural rather than domain-specific.",
rationale = [
{
claim = "Protocol concerns are orthogonal to pipeline concerns",
detail = "Stratumiops' primary value is the action graph execution engine (stratum-orchestrator) and its pipeline crates. The ontology/ADR/reflection protocol serves any project — not just stratumiops consumers. Coupling forces every protocol adopter to also accept stratumiops' dependency tree.",
},
{
claim = "Independent versioning enables protocol stability",
detail = "Protocol schema changes (new ADR fields, new ontology edge kinds, new reflection mode types) should not require a stratumiops release. Ontoref's version contract is between ontoref and its consumers; stratumiops is one consumer, not the owner.",
},
{
claim = "ONTOREF_PROJECT_ROOT decouples protocol tooling from project location",
detail = "The ontoref entry point accepts ONTOREF_PROJECT_ROOT as an env var, defaulting to its own SCRIPT_DIR only if unset. Consumer wrappers (scripts/ontoref) set ONTOREF_PROJECT_ROOT to their own root before exec-ing the ontoref entry. This design allows one ontoref checkout to serve multiple projects.",
},
{
claim = "stratum-db and platform-nats as peer deps, not bundled deps",
detail = "ontoref-daemon uses stratum-db (for optional SurrealDB persistence) and platform-nats (for NATS events) as optional features via path dependencies. These crates are infrastructural and re-usable. Bundling them into ontoref would duplicate the crates; referencing them as peers preserves the single canonical implementation.",
},
],
consequences = {
positive = [
"Consumer projects (typedialog, vapora, kogral) can adopt the protocol with a single wrapper + config file",
"Protocol schema evolution is independent of stratumiops pipeline releases",
"One ontoref checkout serves all projects via ONTOREF_PROJECT_ROOT env var",
"Stratumiops becomes a first-class protocol consumer, not the protocol owner — eats its own dogfood",
"ontoref-daemon is protocol-agnostic: any project can use it for NCL caching regardless of whether it uses stratumiops",
],
negative = [
"ontoref-daemon has path dependencies on stratumiops/crates — requires both repos to be checked out locally for builds",
"Two entry points per project: scripts/stratumiops (pipeline) and scripts/ontoref (protocol) — dual maintenance surface",
"ONTOREF_ROOT must be set or defaulted correctly in every consumer wrapper",
],
},
alternatives_considered = [
{
option = "Keep all tooling in stratumiops, consumers depend on it as a git subtree or submodule",
why_rejected = "Submodule/subtree patterns create update friction and do not solve the versioning coupling. Every consumer needs stratumiops' full dependency tree even for protocol-only use.",
},
{
option = "Publish ontoref crates to crates.io and consume via version pins",
why_rejected = "Rapid iteration on protocol schemas makes published crate semantics too rigid at this stage. Path dependencies allow simultaneous development of protocol and consumers without publication ceremony.",
},
{
option = "Inline protocol tooling into each consumer project separately",
why_rejected = "Schema drift across projects would immediately arise. The protocol's value is precisely its shared contract — decentralizing it defeats the purpose.",
},
],
constraints = [
{
id = "no-stratumiops-domain-deps",
claim = "ontoref crates must not import stratumiops domain crates: stratum-graph, stratum-state, stratum-orchestrator, stratum-llm, stratum-embeddings",
scope = "crates/ontoref-ontology/Cargo.toml, crates/ontoref-reflection/Cargo.toml, crates/ontoref-daemon/Cargo.toml",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "stratum-graph|stratum-state|stratum-orchestrator|stratum-llm|stratum-embeddings",
paths = ["crates/ontoref-ontology/Cargo.toml", "crates/ontoref-reflection/Cargo.toml", "crates/ontoref-daemon/Cargo.toml"],
must_be_empty = true,
},
rationale = "Domain crates from stratumiops encode pipeline-specific types. Importing them would re-couple the protocol to the pipeline and prevent independent adoption.",
},
{
id = "ontoref-project-root-consumer-set",
claim = "The ontoref entry point must not unconditionally overwrite ONTOREF_PROJECT_ROOT — it must default only when unset",
scope = "ontoref (bash entry point)",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "ONTOREF_PROJECT_ROOT",
paths = ["ontoref"],
must_be_empty = false,
},
rationale = "Consumer wrappers (scripts/ontoref) set ONTOREF_PROJECT_ROOT to their own root before calling the ontoref entry. If the entry overwrites it, the daemon and ADR queries target ontoref's own repo instead of the consumer project.",
},
{
id = "ontoref-config-only-required-artifact",
claim = "A consumer project must only need .ontoref/config.ncl and scripts/ontoref to adopt the protocol — no other files copied into the consumer",
scope = "consumer project onboarding",
severity = 'Soft,
check = { tag = 'FileExists, path = ".ontoref/config.ncl", present = true },
rationale = "Minimizing the consumer adoption surface ensures the protocol is adopted voluntarily and fully, not partially via file copies that drift from the source.",
},
],
related_adrs = ["adr-002-daemon-for-caching-and-notification-barrier"],
ontology_check = {
decision_string = "ontoref is a standalone project providing protocol tooling; consumers adopt via wrapper + config; stratum-db and platform-nats are optional peer deps",
invariants_at_risk = ["protocol-not-runtime"],
verdict = 'Safe,
},
}

View file

@ -1,134 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-002",
title = "Ontoref Daemon for NCL Caching, File Watching, and Actor Notification Barrier",
status = 'Accepted,
date = "2026-03-12",
supersedes = "stratumiops:adr-007-optional-daemon-for-caching-and-persistence",
context = "Nushell reflection modules invoke `nickel export` as a subprocess ~39 times per full sync scan, taking 2m42s. Each invocation forks a new process (~100ms). There is no shared state between developers and agents working on the same project, no notification when a peer changes an ontology or ADR file mid-session, and no persistent store for scan results. The protocol-not-runtime axiom forbids required runtime services — any daemon must be optional with full subprocess fallback. This ADR supersedes stratumiops adr-007, which designed this system inside stratumiops before the protocol was extracted to ontoref.",
decision = "ontoref-daemon is an optional persistent daemon providing: (1) NCL export caching — results keyed by (path, mtime, import_path) served via HTTP, file watcher invalidates on change; (2) actor registry — developers, agents, CI register on startup with deterministic tokens (type:hostname:pid), sweep reaps stale sessions every 30s; (3) notification barrier — file changes in .ontology/, adrs/, reflection/ generate typed notifications stored in a per-project ring buffer; pre-commit hook queries pending notifications and blocks commits until acknowledged (fail-open: daemon down = commit allowed). Consumer projects configure the daemon via `.ontoref/config.ncl` (daemon.enabled, daemon.port, db.enabled, db.url). All Nushell modules fall back to direct `nickel export` subprocess when the daemon is unavailable. stratum-db (optional feature, path dep on stratumiops) handles SurrealDB persistence. platform-nats (optional feature, path dep on stratumiops) handles NATS event publishing.",
rationale = [
{
claim = "Optional daemon preserves protocol-not-runtime axiom",
detail = "Every HTTP call to the daemon has a subprocess fallback in store.nu. daemon-export-safe returns null on error; callers substitute with direct nickel export. A project with daemon.enabled = false works identically — just without caching. The daemon is an optimization layer, never a load-bearing dependency.",
},
{
claim = "NCL export caching eliminates repeated subprocess forks",
detail = "Daemon caches nickel export results keyed by (path, mtime, NICKEL_IMPORT_PATH). First call ~100ms (subprocess). Subsequent calls <1ms (DashMap lookup). File watcher via notify (FSEvents on macOS, inotify on Linux) invalidates on any change to watched paths. Periodic full invalidation as safety net.",
},
{
claim = "Actor registry enables multiactor awareness without coordination overhead",
detail = "Deterministic tokens (type:hostname:pid) require no UUID generation and are stable across restarts of the same process. Stale sessions are swept by kill -0 check (same-machine actors) or last_seen timeout (CI/remote actors). No heartbeat loop required from actors.",
},
{
claim = "Notification barrier is fail-open, preserving developer autonomy",
detail = "The pre-commit hook checks /notifications/pending and blocks only when notifications are pending AND the daemon is reachable. Daemon unavailable = commit allowed (warning printed). This matches the no-enforcement axiom: ontoref cannot enforce coordination, only facilitate it.",
},
{
claim = "stratum-db and platform-nats as optional peer path deps",
detail = "Both crates are infrastructure utilities with no domain coupling to stratumiops. Using them as optional path deps lets ontoref-daemon avoid duplicating SurrealDB and NATS connection logic. The `db` and `nats` features are enabled by default but can be disabled for protocol-only deployments.",
},
],
consequences = {
positive = [
"sync scan latency drops from ~2m42s to <30s with daemon caching active",
"Developers and agents see each other's pending ontology/ADR changes before committing",
"Pre-commit hook prevents silent drift when multiple actors modify shared protocol files",
"SurrealDB persistence (optional) enables cross-session impact analysis and audit history",
"Single daemon process serves multiple projects via X-Ontoref-Project header scoping",
],
negative = [
"ontoref-daemon has path deps on stratumiops/crates — both repos must be co-located for local builds",
"Daemon process uses memory (~10-50MB) while running; idle timeout mitigates this",
"Actor notification barrier adds pre-commit latency for the HTTP round-trip (~5ms daemon reachable)",
"stratum-db pins SurrealDB v3 — major version changes require coordinated update across both repos",
],
},
alternatives_considered = [
{
option = "In-process Nickel evaluation via nickel-lang-core library",
why_rejected = "nickel-lang-core has an unstable Rust API and resolves import paths differently from the CLI. The subprocess approach with caching is simpler, more stable, and already <1ms cached.",
},
{
option = "Required daemon (always running, no fallback)",
why_rejected = "Violates the protocol-not-runtime axiom. Consumer projects must function without any ontoref process running. The daemon is an optimization and awareness layer, not infrastructure.",
},
{
option = "Filesystem-based JSON cache without a daemon process",
why_rejected = "File-based caching requires lock management, cannot serve concurrent requests from multiple actors, and does not provide file watching or the actor registry. A daemon centralizes these concerns cleanly.",
},
{
option = "Bundle SurrealDB and NATS clients directly in ontoref, not as path deps",
why_rejected = "Duplicating stratum-db and platform-nats creates divergence. Both crates are general-purpose infrastructure; ontoref consumers already have stratumiops checked out for other reasons. Path deps preserve the single canonical implementation.",
},
],
constraints = [
{
id = "daemon-never-required",
claim = "No Nushell module or bash script may fail when ontoref-daemon is unavailable",
scope = "reflection/modules/, reflection/nulib/, scripts/",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "daemon-export-safe|subprocess fallback|nickel export",
paths = ["reflection/modules", "reflection/nulib"],
must_be_empty = false,
},
rationale = "Every daemon-export call site must have a subprocess fallback. Daemon down = system works identically, just slower.",
},
{
id = "daemon-binds-localhost-only",
claim = "ontoref-daemon must bind to 127.0.0.1, never to 0.0.0.0 or a public interface",
scope = "crates/ontoref-daemon/src/main.rs",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "0\\.0\\.0\\.0",
paths = ["crates/ontoref-daemon/src/main.rs"],
must_be_empty = true,
},
rationale = "The daemon is local IPC only. Binding to a public interface would expose the NCL export API to the network.",
},
{
id = "notification-barrier-fail-open",
claim = "The pre-commit hook must allow commits when ontoref-daemon is unreachable, printing a warning but not blocking",
scope = "scripts/hooks/pre-commit-notifications.sh",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "daemon down|unreachable|curl.*fail",
paths = ["scripts/hooks/pre-commit-notifications.sh"],
must_be_empty = false,
},
rationale = "A pre-commit hook that blocks on daemon unavailability violates the no-enforcement axiom and the developer autonomy principle. Coordination is facilitated, never enforced.",
},
{
id = "multi-project-header-scoping",
claim = "All daemon HTTP requests from consumer wrappers must include X-Ontoref-Project header or equivalent project scoping",
scope = "reflection/modules/store.nu, crates/ontoref-daemon/src/api.rs",
severity = 'Soft,
check = {
tag = 'Grep,
pattern = "X-Ontoref-Project",
paths = ["reflection/modules/store.nu", "crates/ontoref-daemon/src/api.rs"],
must_be_empty = false,
},
rationale = "One daemon process serves multiple projects. Without project scoping, notifications and cache entries from different projects would collide.",
},
],
related_adrs = ["adr-001-protocol-as-standalone-project"],
ontology_check = {
decision_string = "optional persistent daemon for NCL caching, actor registry, and notification barrier — never required, fail-open design throughout",
invariants_at_risk = ["protocol-not-runtime", "no-enforcement"],
verdict = 'Safe,
},
}

View file

@ -1,110 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-003",
title = "Q&A and Accumulated Knowledge Persist to NCL, Not Browser Storage",
status = 'Accepted,
date = "2026-03-12",
context = "The initial Q&A bookmarks feature stored entries in browser localStorage keyed by project. This is convenient but violates the dag-formalized axiom: knowledge that lives only in a browser session is invisible to agents, not git-versioned, not queryable via MCP, and is lost on browser data reset. The same problem applies to quick actions and any other accumulated operational knowledge. The system accumulates knowledge during development sessions (AI interactions, architectural reviews, debugging) that should be first-class artifacts — not ephemeral browser state.",
decision = "All Q&A entries persist to `reflection/qa.ncl` — a typed NCL record file governed by `reflection/schemas/qa.ncl`. Mutations happen via `crates/ontoref-daemon/src/ui/qa_ncl.rs` (line-level surgery, same pattern as backlog_ncl.rs). Four MCP tools expose the store to AI agents: `ontoref_qa_list` (read, with filter), `ontoref_qa_add` (append), `ontoref_qa_delete` (remove block), `ontoref_qa_update` (field mutation). HTTP endpoints `/qa-json` (GET), `/qa/add`, `/qa/delete`, `/qa/update` (POST) serve the UI. The UI renders server-side entries via Tera template injection (SERVER_ENTRIES JSON blob), eliminating the need for a separate fetch on load. The same principle is applied to quick actions: they are declared in `.ontoref/config.ncl` (quick_actions array) and new modes are created as `reflection/modes/<id>.ncl` files via `ontoref_action_add`.",
rationale = [
{
claim = "NCL persistence makes knowledge git-versioned and queryable",
detail = "A Q&A entry in reflection/qa.ncl is a typed Nickel record. It survives browser resets, is visible in git history, can be diffed, and is exported via the NCL cache just like any other protocol artifact. localStorage provides none of these properties.",
},
{
claim = "MCP access enables AI agents to read and write accumulated knowledge",
detail = "Without server-side persistence, an AI agent cannot query what questions have been asked and answered about this project. With ontoref_qa_list, the agent can orient itself immediately from accumulated Q&A without re-asking questions already answered in previous sessions. With ontoref_qa_add, the agent can record newly discovered knowledge while working.",
},
{
claim = "Line-level NCL surgery avoids full AST parsing",
detail = "The qa_ncl.rs module uses the same pattern as backlog_ncl.rs: find blocks by id field, insert/replace/remove by line index. No nickel-lang-core dependency, no AST round-trip, no format drift. The NCL format is predictable enough that targeted string operations are safe and sufficient.",
},
{
claim = "Server-side hydration eliminates the initial fetch round-trip",
detail = "The qa.html template receives `entries` as a Tera context variable and embeds SERVER_ENTRIES as a JSON literal in the page script. The JS initialises its in-memory list directly from this value. No separate GET /qa-json fetch on load, no loading state, no flash of empty content.",
},
{
claim = "Passive drift observation closes the knowledge-accumulation loop",
detail = "As code changes, drift_watcher.rs detects divergence between Yang artifacts and Yin ontology and emits a notification. Combined with Q&A persistence, the system can accumulate observations about drift patterns — enabling future Q&A entries like 'why does crate X often drift?' to be answered from stored context.",
},
],
consequences = {
positive = [
"Q&A knowledge survives session boundaries — AI agents and developers share a common context store",
"MCP tools give AI clients direct read/write access to accumulated project knowledge",
"All entries are typed (QaEntry schema), git-diffable, and auditable",
"Quick actions in config.ncl are the canonical source — no UI state drift",
"Passive drift observer closes the feedback loop without requiring manual sync",
],
negative = [
"qa.ncl and backlog.ncl use line-level surgery — format changes to the NCL templates require updating the mutation modules",
"No concurrent write protection — simultaneous writes from multiple actors could corrupt qa.ncl (same limitation as backlog.ncl; mitigated by advisory file locks in the ./ontoref CLI)",
"Deletion removes the block permanently — no soft-delete or archive mechanism yet",
],
},
alternatives_considered = [
{
option = "Keep localStorage as the storage backend, add optional sync to NCL on explicit user action",
why_rejected = "Two sources of truth creates sync complexity and divergence. AI agents would still not see localStorage entries. The sync step would be frequently skipped. NCL as primary is simpler.",
},
{
option = "Store Q&A in SurrealDB via stratum-db (existing optional dependency)",
why_rejected = "Requires the db feature and a running SurrealDB instance. NCL files are always present, git-versioned, and work without any database. The protocol-not-runtime axiom argues for file-first. SurrealDB can be added as a secondary index later if full-text search is needed.",
},
{
option = "Full AST parse of qa.ncl via nickel-lang-core for mutations",
why_rejected = "nickel-lang-core has an unstable Rust API. The file structure is predictable enough for line-level surgery. backlog_ncl.rs has been operating safely with this pattern. Adding a hard dependency on nickel-lang-core for a file that is always written by the daemon is unnecessary complexity.",
},
],
constraints = [
{
id = "qa-write-via-mutation-module",
claim = "All mutations to reflection/qa.ncl must go through crates/ontoref-daemon/src/ui/qa_ncl.rs — no direct file writes from other call sites",
scope = "crates/ontoref-daemon/src/",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "qa\\.ncl",
paths = ["crates/ontoref-daemon/src"],
must_be_empty = false,
},
rationale = "Centralising mutations in one module ensures consistent id generation, NCL format, and cache invalidation.",
},
{
id = "qa-schema-typed",
claim = "reflection/qa.ncl must conform to the QaStore contract from reflection/schemas/qa.ncl — nickel typecheck must pass",
scope = "reflection/qa.ncl",
severity = 'Hard,
check = { tag = 'NuCmd, cmd = "nickel typecheck reflection/qa.ncl", expect_exit = 0 },
rationale = "Untyped Q&A would degrade to an unstructured log. The schema enforces id, question, answer, actor, created_at fields on every entry.",
},
{
id = "mcp-qa-tools-no-apply-drift",
claim = "MCP tools ontoref_qa_list and ontoref_qa_add must never trigger sync apply steps or modify .ontology/ files",
scope = "crates/ontoref-daemon/src/mcp/mod.rs",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "apply|sync_apply|write_ontology",
paths = ["crates/ontoref-daemon/src/mcp/mod.rs"],
must_be_empty = true,
},
rationale = "Q&A mutation tools operate only on reflection/qa.ncl. Ontology changes require deliberate human or agent review via the sync-ontology mode.",
},
],
related_adrs = ["adr-001-protocol-as-standalone-project", "adr-002-daemon-for-caching-and-notification-barrier"],
ontology_check = {
decision_string = "Q&A and operational knowledge persist as typed NCL artifacts, git-versioned, accessible via MCP and HTTP, rendered server-side in UI",
invariants_at_risk = ["dag-formalized", "protocol-not-runtime"],
verdict = 'Safe,
},
}

View file

@ -1,113 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-004",
title = "NCL Pipe Bootstrap — Config Validation and Secret Injection via Unix Pipeline",
status = 'Accepted,
date = "2026-03-13",
context = "Ontoref-daemon and any process that receives structured config faces two problems: (1) Nickel NCL requires a subprocess to evaluate, introducing a system-call injection surface if the daemon itself calls `nickel export` at runtime; (2) credentials and secrets embedded in config files (TOML, JSON) persist on disk after the process starts, creating a forensic artifact. The existing registry.toml approach (NCL → TOML file → daemon reads file) partially addresses the first problem but not the second — the TOML file remains on disk with hashed credentials. SOPS and Vault are standard secret management tools that produce decrypted output on stdout.",
decision = "All config delivery to long-running processes follows a three-stage Unix pipeline: Stage 1 — structural validation: `nickel export --format json config.ncl` produces JSON with schema-validated structure but no secret values; Stage 2 — secret injection (optional): SOPS decrypt or Vault lookup merges credentials into the JSON stream; Stage 3 — process bootstrap: the target process reads the composed JSON from stdin via `--config-stdin`. No intermediate file is written to disk. If any stage fails, the pipeline breaks and the process does not start. A bash wrapper script (not Nu — Nu may not be available at service boot time) orchestrates the pipeline. A Nu helper `ncl-bootstrap` provides the same interface for interactive/development use.",
rationale = [
{
claim = "Pipe eliminates disk artifacts for secrets",
detail = "Pipe contents are kernel-managed memory, inaccessible to other processes, and ephemeral — no filesystem metadata, no tmpfs entry, nothing survives process termination. A TOML or JSON file on disk persists until explicitly deleted, is accessible to any process running as the same UID, and may appear in filesystem audit logs. For secrets (DB passwords, API keys, Argon2id hashes), the pipe is materially safer.",
},
{
claim = "Nickel is the validation layer, not the runtime config format",
detail = "The daemon never calls `nickel export` — it only reads JSON from stdin. The NCL schema enforces structural correctness and type safety before the process starts. This separates concerns: NCL for authoring and validation, JSON for delivery. No Nickel subprocess risk at runtime.",
},
{
claim = "Bash wrapper for service launchers, Nu helper for development",
detail = "System service managers (launchctl, systemd) run in environments where Nu may not be on PATH or may be a different version. The bash wrapper has zero dependencies beyond bash, nickel, and the target binary. The Nu ncl-bootstrap helper provides richer error messages and structured output for interactive development use. Both implement the same pipeline.",
},
{
claim = "SOPS and Vault integrate as a composable pipeline stage",
detail = "Stage 2 is optional and replaceable. With SOPS: `sops --decrypt secrets.enc.json`. With Vault: `vault kv get -format=json secret/path | jq '.data.data'`. Both produce JSON on stdout. `jq -s '.[0] * .[1]'` merges the structural config with the secrets. The NCL file never contains secret values — only SecretRef placeholders if self-documentation is desired.",
},
{
claim = "Pipeline failure semantics are correct by default",
detail = "If `nickel export` fails (schema violation, syntax error), stdout is empty or truncated — the target process receives invalid JSON and fails at parse time, before any initialization. If SOPS or Vault fails, same result. The process never starts in a partially-configured state. This is safer than file-based config where the process may start with a stale or invalid file.",
},
],
consequences = {
positive = [
"No credentials on disk after process startup — ephemeral pipe only",
"NCL schema violations prevent daemon startup — config errors are caught early",
"SOPS and Vault integrate without changes to the daemon — secrets are a pipeline concern",
"Pattern is reusable: any project can adopt ncl-bootstrap for any long-running process",
"Bash wrapper works in Docker, CI, launchctl, systemd without Nu dependency",
],
negative = [
"Daemon loses ability to hot-reload config from disk — config changes require restart via wrapper",
"stdin is consumed at startup — daemon must redirect stdin to /dev/null after reading config",
"Pipeline debugging is harder than inspecting a config file — need wrapper --dry-run mode",
"Nu must not be required for the bash wrapper — two implementations of the same pattern to maintain",
],
},
alternatives_considered = [
{
option = "TOML file on disk (current registry.toml approach)",
why_rejected = "File persists on disk with credentials. Stale file may be read after config changes. Requires explicit cleanup logic. Forensic artifact risk.",
},
{
option = "Environment variables for secrets",
why_rejected = "Environment variables are visible in /proc/PID/environ on Linux and via `ps eww` on some systems. They persist for the lifetime of the process and are inherited by child processes. Worse attack surface than stdin pipe.",
},
{
option = "Encrypted TOML file (AES256 at rest)",
why_rejected = "Decryption key must be available at runtime — the problem is deferred, not solved. The decrypted form still passes through disk (tmpfs or swap). Adds a custom encryption layer instead of using standard tools (SOPS, Vault) that the ecosystem already supports.",
},
{
option = "Daemon reads NCL directly at runtime via nickel-lang-core",
why_rejected = "nickel-lang-core has an unstable Rust API. More critically, it means the daemon can evaluate arbitrary Nickel — including NCL files with system calls via builtins. The pipeline approach ensures the daemon only ever sees validated JSON, never executable Nickel.",
},
],
constraints = [
{
id = "no-config-file-on-disk",
claim = "The bootstrap pipeline must not write an intermediate config file to disk at any stage",
scope = "scripts/ontoref-daemon-start, reflection/nulib/bootstrap.nu",
severity = 'Hard,
check = { tag = 'Grep, pattern = "tee |tempfile|mktemp", paths = ["scripts/ontoref-daemon-start"], must_be_empty = true },
rationale = "An intermediate file defeats the purpose of the pipeline. If a file is needed for debugging, use --dry-run which prints to stdout only.",
},
{
id = "bash-wrapper-zero-deps",
claim = "The bash wrapper must depend only on bash, nickel, and the target binary — no Nu, no jq unless SOPS/Vault stage is active",
scope = "scripts/ontoref-daemon-start",
severity = 'Hard,
check = { tag = 'FileExists, path = "scripts/ontoref-daemon-start", present = true },
rationale = "System service managers may not have Nu on PATH. The wrapper must be portable across launchctl, systemd, Docker entrypoints.",
},
{
id = "config-stdin-closes-after-read",
claim = "The target process must redirect stdin to /dev/null after reading the config JSON",
scope = "crates/ontoref-daemon/src/main.rs",
severity = 'Hard,
check = { tag = 'Grep, pattern = "/dev/null|stdin.*close|drop.*stdin", paths = ["crates/ontoref-daemon/src/main.rs"], must_be_empty = false },
rationale = "stdin left open blocks terminal interaction and causes confusion in interactive sessions. The daemon is a server — it must not hold stdin.",
},
{
id = "ncl-no-secret-values",
claim = "NCL config files used with ncl-bootstrap must not contain plaintext secret values — only SecretRef placeholders or empty fields",
scope = ".ontoref/config.ncl, APP_SUPPORT/ontoref/config.ncl",
severity = 'Hard,
check = { tag = 'NuCmd, cmd = "nickel export .ontoref/config.ncl | from json | transpose key value | where { |row| $row.key =~ 'password|secret|key|token|hash' and ($row.value | describe) == 'string' and ($row.value | str length) > 0 } | length | into string", expect_exit = 0 },
rationale = "If secrets are in the NCL file, they are readable as plaintext by anyone with filesystem access. Secrets enter the pipeline only at the SOPS/Vault stage.",
},
],
related_adrs = ["adr-002-daemon-for-caching-and-notification-barrier"],
ontology_check = {
decision_string = "NCL pipe bootstrap — config validation via nickel export piped to process stdin, secrets injected via SOPS/Vault as optional pipeline stage, no intermediate files",
invariants_at_risk = ["protocol-not-runtime"],
verdict = 'Safe,
},
}

View file

@ -1,126 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-005",
title = "Unified Key-to-Session Auth Model Across CLI, UI, and MCP",
status = 'Accepted,
date = "2026-03-13",
context = "ontoref-daemon exposes project knowledge and mutations over HTTP, a browser UI, and an MCP server. Projects can define argon2id-hashed keys in project.ncl (with role admin|viewer and an audit label). Prior to this ADR, the UI login flow set a session cookie but the REST API accepted raw passwords as Bearer tokens on every request — each call paying ~100ms for argon2 verification. The CLI had no Bearer support at all; project-add and project-remove called the daemon without credentials. The daemon manage page had no admin identity concept. There was no way to enumerate or revoke active sessions.",
decision = "All surfaces exchange a raw key once via POST /sessions for a UUID v4 bearer token (30-day lifetime, in-memory SessionStore). The session token is used for all subsequent calls — O(1) DashMap lookup. Sessions carry a stable public id (distinct from the bearer token) for safe list/revoke operations without leaking credentials. Project keys have a label field for audit trail. Daemon-level admin uses a separate argon2id hash (ONTOREF_ADMIN_TOKEN_FILE preferred over ONTOREF_ADMIN_TOKEN) and creates sessions under virtual slug '_daemon'. The CLI injects ONTOREF_TOKEN as Authorization: Bearer automatically via bearer-args in store.nu. Key rotation (PUT /projects/{slug}/keys) revokes all active sessions for the rotated project. GET /sessions and DELETE /sessions/{id} implement two-tier visibility: project admin sees own project sessions; daemon admin sees all.",
rationale = [
{
claim = "Token exchange eliminates per-request argon2 cost",
detail = "argon2id verification takes ~100ms by design (brute-force resistance). Verifying on every CLI invocation, MCP tool call, or UI AJAX request is prohibitive. A UUID v4 session token turns auth into a DashMap::get — O(1), sub-microsecond. The argon2 cost is paid exactly once per login.",
},
{
claim = "session.id != bearer token prevents session enumeration attacks",
detail = "The list endpoints (GET /sessions) return SessionView with a stable public id. If the bearer token were exposed in list responses, a project admin could steal another admin's token and impersonate them. The id field is a second UUID v4 — safe to expose, useless as a credential.",
},
{
claim = "is_uuid_v4 fast-path preserves backward compatibility",
detail = "check_primary_auth tries the session token path first (UUID v4 format check is a length + byte comparison, ~10ns). If the bearer is not UUID-shaped, it falls through to argon2 password verification. Existing integrations using raw passwords continue to work without change.",
},
{
claim = "Virtual '_daemon' slug isolates admin sessions from project sessions",
detail = "Daemon admin sessions are not scoped to any real project. The '_daemon' slug cannot collide with real project slugs (kebab-case, no leading underscore). AdminGuard checks for Role::Admin across any registered project or '_daemon', giving the manage page a clean auth boundary without coupling it to any specific project.",
},
{
claim = "ONTOREF_ADMIN_TOKEN_FILE preferred over inline env var",
detail = "An inline hash in ONTOREF_ADMIN_TOKEN appears in `ps aux` output and shell history. Reading from a file (ONTOREF_ADMIN_TOKEN_FILE) avoids both surfaces. The file contains only the argon2id hash string; the actual password never touches the daemon process env.",
},
{
claim = "Key rotation invalidates sessions atomically",
detail = "When keys are rotated, all in-flight sessions for that project authenticated against the old key set are immediately invalid. revoke_all_for_slug scans the DashMap and removes matching entries including the id_index. Actor sessions (ontoref-daemon actors registry) are also invalidated. The UI will receive 401 on next request and redirect to login.",
},
],
consequences = {
positive = [
"REST API, MCP, and UI all use the same session token — single auth model, no surface-specific logic",
"CLI project-add, project-remove, and notify-daemon-* calls carry credentials automatically when ONTOREF_TOKEN is set",
"Session list gives admins visibility into who is connected to their project",
"Revocation is O(1) via id_index secondary index; bulk revocation on key rotation is O(active sessions for slug)",
"Daemon admin and project admin are cleanly separated — '_daemon' sessions cannot access project-scoped endpoints and vice versa",
],
negative = [
"Sessions are in-memory only — daemon restart requires all clients to re-authenticate",
"30-day token lifetime means a leaked token is valid for up to 30 days unless explicitly revoked",
"ONTOREF_TOKEN in shell env is visible to child processes — use a secrets manager or short-lived token refresh for automated pipelines",
],
},
alternatives_considered = [
{
option = "Verify argon2 on every Bearer request (no session concept)",
why_rejected = "~100ms per request is unacceptable for CLI invocations (each command is a new HTTP call) and MCP tool sequences (multiple calls per agent turn). Session token lookup is sub-microsecond.",
},
{
option = "Axum middleware for auth instead of per-handler check_primary_auth",
why_rejected = "Middleware applies uniformly to all routes. ontoref-daemon coexists auth-enabled and open projects on the same router — some endpoints are always public (health, POST /sessions itself), some require project-scoped auth, some require daemon admin. Per-handler checks encode these rules explicitly; middleware would require complex route exemption logic.",
},
{
option = "Expose bearer token in session list responses",
why_rejected = "GET /sessions is accessible to project admins. Exposing the bearer would allow any project admin to impersonate any other session holder. The public session.id is a safe substitute for revocation targeting.",
},
{
option = "Separate token store per project",
why_rejected = "A single DashMap keyed by token with a slug field in SessionEntry is sufficient. A secondary id_index DashMap gives O(1) revoke-by-id. Per-project sharding would add complexity without benefit given the expected session count (tens, not millions).",
},
{
option = "JWT instead of opaque UUID v4 tokens",
why_rejected = "JWTs are self-contained and cannot be revoked without a denylist. Opaque tokens enable instant revocation (key rotation, logout, admin force-revoke) with O(1) lookup. The daemon is local-only — there is no distributed verification scenario that would justify JWT complexity.",
},
],
constraints = [
{
id = "session-token-never-in-list-response",
claim = "GET /sessions responses must never include the bearer token, only the public session id",
scope = "crates/ontoref-daemon/src/session.rs, crates/ontoref-daemon/src/api.rs",
severity = 'Hard,
check = { tag = 'Grep, pattern = "pub token", paths = ["crates/ontoref-daemon/src/session.rs"], must_be_empty = true },
rationale = "Exposing bearer tokens in list responses would allow admins to impersonate other sessions. The session.id field is a second UUID v4, safe to expose.",
},
{
id = "post-sessions-no-auth-required",
claim = "POST /sessions must not require authentication — it is the credential exchange endpoint",
scope = "crates/ontoref-daemon/src/api.rs",
severity = 'Hard,
check = { tag = 'Grep, pattern = "require_session|check_primary_auth", paths = ["crates/ontoref-daemon/src/api.rs"], must_be_empty = false },
rationale = "Requiring auth to obtain auth is a bootstrap deadlock. Rate-limiting on failure is the correct mitigation, not pre-authentication.",
},
{
id = "key-rotation-must-invalidate-sessions",
claim = "PUT /projects/{slug}/keys must call revoke_all_for_slug before persisting new keys",
scope = "crates/ontoref-daemon/src/api.rs",
severity = 'Hard,
check = { tag = 'Grep, pattern = "revoke_all_for_slug", paths = ["crates/ontoref-daemon/src/api.rs"], must_be_empty = false },
rationale = "Sessions authenticated against the old key set become invalid after rotation. Failing to revoke them would leave stale sessions with elevated access.",
},
{
id = "bearer-args-injected-by-cli",
claim = "All CLI HTTP calls to the daemon must use bearer-args from store.nu — no hardcoded curl without auth args",
scope = "reflection/modules/store.nu, reflection/bin/ontoref.nu",
severity = 'Soft,
check = { tag = 'Grep, pattern = "bearer-args|http-get|http-post-json|http-delete", paths = ["reflection/modules/store.nu"], must_be_empty = false },
rationale = "ONTOREF_TOKEN is the single credential source for CLI. Direct curl without bearer-args bypasses the auth model silently.",
},
],
related_adrs = ["adr-002-daemon-for-caching-and-notification-barrier"],
ontology_check = {
decision_string = "unified key-to-session token exchange; opaque UUID v4 bearer; session.id != token; revocation on key rotation",
invariants_at_risk = ["no-enforcement"],
verdict = 'RequiresJustification,
},
invariant_justification = {
invariant = "no-enforcement",
claim = "Auth is opt-in per project. A project with no keys configured runs in open mode — all endpoints accessible without credentials. The protocol itself never mandates auth.",
mitigation = "The no-enforcement axiom applies at the protocol level. Auth is a project-level operational decision, not a protocol constraint. Open deployments (no keys) pass through all auth checks unconditionally.",
},
}

View file

@ -1,76 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-006",
title = "Nushell 0.111 String Interpolation Compatibility Fix",
status = 'Accepted,
date = "2026-03-14",
context = "Nushell 0.111 introduced a breaking change in string interpolation parsing: expressions inside `$\"...\"` that match the pattern `(identifier: expr)` are now parsed as command calls rather than as record literals or literal text. This broke four print statements in reflection/bin/ontoref.nu that used patterns like `(kind: ($kind))`, `(logo: ($logo_file))`, `(parents: ($parent_slugs))`, and `(POST /actors/register)`. The bug manifested when running `ontoref setup` and `ontoref hooks-install` on any consumer project using Nu 0.111+. The minimum Nu version gate (>= 0.110.0) did not catch 0.111 regressions since it only guards the lower bound.",
decision = "Fix all four affected print statements by removing the outer parentheses from label-value pairs inside string interpolations, or by removing the `$` prefix from strings that contain no variable interpolation. The fix is minimal and non-semantic: `(kind: ($kind))` becomes `kind: ($kind)` (literal label + variable), and `$\"(POST /actors/register)\"` becomes `\"(POST /actors/register)\"` (plain string). The fix is applied to both the dev repo (reflection/bin/ontoref.nu) and the installed copy (~/.local/bin/ontoref via just install-daemon). The minimum version gate remains >= 0.110.0 but 0.111 is now the tested floor.",
rationale = [
{
claim = "Minimal-diff fix over workarounds",
detail = "The broken patterns were purely cosmetic print statements. The fix removes one level of parens — no logic change. Alternatives that added escape sequences or string concatenation would obscure the intent.",
},
{
claim = "Plain string for zero-interpolation prints",
detail = "Strings with no variable interpolation (like the POST endpoint hint) should never use `$\"...\"`. Removing the `$` prefix makes them immune to any future interpolation parsing changes and is the correct Nushell idiom.",
},
{
claim = "just install-daemon as the sync mechanism",
detail = "The installed copy at ~/.local/bin/ontoref is managed via just install-daemon. Patching both the dev repo and the installed copy via install-daemon is the established update path and keeps them in sync.",
},
],
consequences = {
positive = [
"ontoref setup and hooks-install work correctly on Nushell 0.111+",
"All consumer projects (vapora, typedialog, evol-rustelo) can run setup without errors",
"Plain-string fix removes implicit fragility from zero-interpolation print statements",
],
negative = [
"The 0.111 regression was not caught by the version gate — the gate only guards >= 0.110.0 and does not test 0.111 compatibility proactively",
],
},
alternatives_considered = [
{
option = "Raise minimum Nu version to 0.111 and document the breaking change",
why_rejected = "Does not fix the broken syntax — just makes the breakage explicit. Consumer projects already on 0.111 would still fail until the print statements are fixed.",
},
{
option = "Use escape sequences or string concatenation to embed literal parens",
why_rejected = "Nushell has no escape for parens in string interpolation. String concatenation (e.g. `'(kind: ' + $kind + ')'`) works but is significantly less readable than bare `kind: ($kind)`.",
},
],
constraints = [
{
id = "no-label-value-parens-in-interpolation",
claim = "String interpolations in ontoref.nu must not use `(identifier: expr)` patterns — use bare `identifier: (expr)` instead",
scope = "ontoref (reflection/bin/ontoref.nu, all .nu files)",
severity = 'Hard,
check = { tag = 'Grep, pattern = "\\([a-z_]+: \\(", paths = ["reflection/bin/ontoref.nu"], must_be_empty = true },
rationale = "Nushell 0.111 parses (identifier: expr) inside $\"...\" as a command call. The fix pattern (bare label + variable interpolation) is equivalent visually and immune to this parser behaviour.",
},
{
id = "plain-string-for-zero-interpolation",
claim = "Print statements with no variable interpolation must use plain strings, not `$\"...\"`",
scope = "ontoref (all .nu files)",
severity = 'Soft,
check = { tag = 'Grep, pattern = "\\$\"[^%(]*\"", paths = ["reflection"], must_be_empty = true },
rationale = "Zero-interpolation `$\"...\"` strings are fragile against future parser changes and mislead readers into expecting variable substitution.",
},
],
related_adrs = [],
ontology_check = {
decision_string = "Fix four Nu 0.111 string interpolation regressions in ontoref.nu; enforce no (label: expr) inside interpolations; use plain strings for zero-interpolation prints",
invariants_at_risk = [],
verdict = 'Safe,
},
}

View file

@ -1,84 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-007",
title = "API Surface Discoverability via #[onto_api] Proc-Macro",
status = 'Accepted,
date = "2026-03-23",
context = "ontoref-daemon exposes ~28 HTTP routes across api.rs, sync.rs, and other handler modules. Before this decision, the authoritative route list existed only in the axum Router definition — undiscoverable without reading source. MCP agents, CLI users, and the web UI had no machine-readable way to enumerate routes, their auth requirements, parameter shapes, or actor restrictions. OpenAPI was considered but rejected as a runtime dependency that would require schema maintenance separate from the handler code. The `#[onto_api]` proc-macro in `ontoref-derive` addresses this by making the handler annotation the single source of truth: the macro emits `inventory::submit!(ApiRouteEntry{...})` at link time, and `api_catalog::catalog()` collects them via `inventory::collect!`. No runtime registry, no startup allocation, no separate schema file.",
decision = "Every HTTP handler in ontoref-daemon must carry `#[onto_api(method, path, description, auth, actors, params, tags)]`. The proc-macro (in `crates/ontoref-derive`) emits `inventory::submit!(ApiRouteEntry{...})` at link time. `GET /api/catalog` calls `api_catalog::catalog()` — a pure function over `inventory::iter::<ApiRouteEntry>()` — and returns the annotated surface as JSON. The web UI at `/ui/{slug}/api` renders it with client-side filtering. `describe api [--actor] [--tag] [--auth] [--fmt]` queries this endpoint from the CLI. The MCP tool `ontoref_api_catalog` calls `catalog()` directly without HTTP. This surfaces the complete API to three actors (browser, CLI, MCP agent) from one annotation site per handler.",
rationale = [
{
claim = "Compile-time registration eliminates drift",
detail = "inventory uses linker sections (.init_array on ELF, __mod_init_func on Mach-O) to collect ApiRouteEntry items at link time. A handler that exists in the binary but lacks #[onto_api] is detectable — cargo test or a Grep constraint catches the gap. A handler that has #[onto_api] but is removed will automatically disappear from catalog(). The annotation and the implementation are co-located and co-deleted.",
},
{
claim = "Zero runtime overhead and zero startup allocation",
detail = "inventory::iter::<ApiRouteEntry>() walks a linked-list built by the linker — no HashMap, no Arc, no lazy_static. catalog() is a pure function that sorts and returns &'static references. This satisfies the ontoref axiom 'Protocol, Not Runtime': the catalog is available without daemon state, without DB, without cache warmup.",
},
{
claim = "Three-surface consistency without duplication",
detail = "Browser (api_catalog.html), CLI (describe api), and MCP (ontoref_api_catalog) all read the same inventory. A manual registry or OpenAPI spec would require three update sites per route change. With #[onto_api], changing a route's auth requirement is a one-line annotation edit that propagates to all surfaces on next build.",
},
],
consequences = {
positive = [
"API surface is always current: catalog() reflects exactly the handlers compiled into the binary",
"Agents (MCP) can call ontoref_api_catalog on cold start to understand the full HTTP surface without prior knowledge",
"describe api --actor agent filters to actor-appropriate routes; agents can self-serve their available endpoints",
"New handlers without #[onto_api] are caught by the Grep constraint before merge",
"inventory (MIT, 0.3.x) has no transitive deps — passes deny.toml audit",
],
negative = [
"#[onto_api] parameters are stringly-typed — a misspelled auth value is not caught at compile time (only at review/Grep)",
"inventory linker trick is platform-specific: supported on Linux (ELF), macOS (Mach-O), Windows (PE) but not on targets that lack .init_array equivalent",
"Proc-macro adds a new crate (ontoref-derive) to the workspace; ontoref-ontology users who only need zero-dep struct loading do not need it",
],
},
alternatives_considered = [
{
option = "OpenAPI / utoipa with generated JSON schema",
why_rejected = "Requires maintaining a separate schema artifact (openapi.json) and a runtime schema struct tree. The schema can drift from actual handler signatures. utoipa adds ~15 transitive deps including serde_yaml. Violates 'Protocol, Not Runtime' — the schema becomes a runtime artifact rather than a compile-time invariant.",
},
{
option = "Manual route registry (Vec<RouteInfo> in main.rs)",
why_rejected = "A manually maintained Vec has guaranteed drift: handlers are added, routes change, and the Vec is updated inconsistently. Proven failure mode in the previous session where insert_mcp_ctx listed 15 tools while the router had 27.",
},
{
option = "Runtime reflection via axum Router introspection",
why_rejected = "axum does not expose a stable introspection API for registered routes. Workarounds (tower_http trace layer capture, method_router hacks) are brittle across axum versions and cannot surface handler metadata (auth, actors, params).",
},
],
constraints = [
{
id = "onto-api-on-all-handlers",
claim = "Every public HTTP handler in ontoref-daemon must carry #[onto_api(...)]",
scope = "ontoref-daemon (crates/ontoref-daemon/src/api.rs, crates/ontoref-daemon/src/sync.rs)",
severity = 'Hard,
check = { tag = 'Grep, pattern = "#\\[onto_api", paths = ["crates/ontoref-daemon/src/api.rs", "crates/ontoref-daemon/src/sync.rs"], must_be_empty = false },
rationale = "catalog() is only as complete as the set of annotated handlers. Unannotated handlers are invisible to agents, CLI, and the web UI — equivalent to undocumented and unauditable routes.",
},
{
id = "inventory-feature-gate",
claim = "inventory must remain a workspace dependency gated behind the 'catalog' feature of ontoref-derive; ontoref-ontology must not depend on inventory",
scope = "ontoref-ontology (Cargo.toml), ontoref-derive (Cargo.toml)",
severity = 'Hard,
check = { tag = 'Grep, pattern = "inventory", paths = ["crates/ontoref-ontology/Cargo.toml"], must_be_empty = true },
rationale = "ontoref-ontology is the zero-dep adoption surface (ADR-001). Adding inventory — even as an optional dep — violates that contract and makes protocol adoption heavier for downstream crates that only need typed NCL loading.",
},
],
related_adrs = ["adr-001"],
ontology_check = {
decision_string = "Use #[onto_api] proc-macro + inventory linker registration as the single source of truth for the HTTP API surface; surface via GET /api/catalog, describe api CLI subcommand, and ontoref_api_catalog MCP tool",
invariants_at_risk = ["protocol-not-runtime"],
verdict = 'Safe,
},
}

View file

@ -1,93 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-008",
title = "NCL-First Config Validation and Override-Layer Mutation",
status = 'Accepted,
date = "2026-03-26",
context = "The config surface feature adds per-project config introspection and mutation to ontoref-daemon. Two design questions arise: (1) Where does config field validation live — in NCL contracts, in Rust struct validation, or both? (2) How does a PUT /config/{section} request mutate a project's config without corrupting the source NCL files? Direct mutation via nickel export → JSON → write-back destroys NCL comments, contract annotations, and section merge structure. Duplicating validation in both NCL and Rust creates two sources of truth with guaranteed divergence. The config surface spans ontoref's own .ontoref/config.ncl and all consumer-project configs, making the choice of validation ownership a protocol-level constraint.",
decision = "NCL contracts (std.contract.from_validator) are the single validation layer for all config fields. Rust serde structs are contract-trusted readers: they carry #[serde(default)] and consume pre-validated JSON from nickel export — no validator(), no custom Deserialize, no duplicate field constraints. Config mutation via PUT /projects/{slug}/config/{section} never modifies the original NCL source files. Instead it writes a {section}.overrides.ncl file containing only the changed fields plus a _overrides_meta audit record (actor, reason, timestamp, previous values), then appends a single idempotent import line to the entry-point NCL (using NCL's & merge operator so the override wins). nickel export validates the merged result against the section's declared contract before the mutation is committed; validation failure reverts the override file and returns the nickel error verbatim. Ontoref demonstrates this pattern on itself: .ontoref/contracts.ncl declares LogConfig and DaemonConfig contracts applied in .ontoref/config.ncl.",
rationale = [
{
claim = "NCL contracts are the correct validation boundary",
detail = "nickel export runs the contract check before any JSON reaches Rust. A value that violates LogLevel (must be one of error/warn/info/debug/trace) is rejected by nickel with a precise error message pointing to the exact field and contract. If Rust also validates, the two validators must stay in sync forever — and will diverge. Concentrating validation in NCL means the contract file is the authoritative spec for both the schema documentation and the runtime constraint.",
},
{
claim = "Override-layer mutation preserves NCL structure integrity",
detail = "A round-trip of nickel export → JSON → overwrite produces a file with no comments, no contract annotations, no merge structure, and no section rationale. The override layer avoids this entirely: the source file is immutable, the override file contains only changed fields, and NCL's & merge operator applies them at export time. The override file is git-versioned, human-readable, and revertable by deletion. nickel export on the merged entry point validates the result through the declared contract — the same validation that runs in production.",
},
{
claim = "Audit metadata in _overrides_meta closes the mutation traceability gap",
detail = "Each override file carries a top-level _overrides_meta record with managed_by = 'ontoref', created_at, and an entries array (field, from, to, reason, actor, ts). This record is consumed by GET /config/quickref to render an override history timeline and by GET /config/coherence to flag fields whose current value differs from the contract default. The metadata is a first-class NCL record — not a comment — so it survives export and is queryable by agents.",
},
],
consequences = {
positive = [
"Config field constraints are documented once (NCL contract) and enforced at the nickel export boundary — no duplication",
"Original NCL source files are immutable under daemon operation — diffs are clean, history is readable",
"nickel export validates override correctness before committing — contract violations return verbatim nickel errors to the caller",
"Override files are deletable to revert — no migration needed",
"_overrides_meta enables GET /config/quickref to render full change history with reasons",
"Ontoref's own .ontoref/contracts.ncl serves as a working example for consumer projects adopting the pattern",
],
negative = [
"Override layer requires the entry-point NCL to use & merge operators; SingleFile configs without section-level imports need a one-time restructure before overrides work",
"nickel export is the validation gate — validation errors are nickel syntax, not structured JSON; callers must parse nickel error output to surface friendly messages",
"#[serde(default)] on all Rust config structs means a missing NCL field silently uses the Rust default instead of erroring; the NCL contract (with its own defaults) is the intended fallback, not Rust",
],
},
alternatives_considered = [
{
option = "Duplicate validation in Rust (validator crate or custom Deserialize)",
why_rejected = "Two validators for the same field inevitably diverge. The NCL contract is already the authoritative schema for documentation, MCP export, and quickref generation — adding a Rust duplicate makes it decorative. validator crate adds 8+ transitive dependencies and requires annotation churn across every config struct.",
},
{
option = "Direct NCL file mutation (read → merge JSON → overwrite)",
why_rejected = "nickel export → JSON write-back destroys comments, contract annotations (| C.LogConfig), section merge structure, and in-file rationale. The resulting file is syntactically valid but semantically impoverished. Once a file is overwritten this way, the original structure cannot be recovered from git history if the file was also changed manually between sessions.",
},
{
option = "Separate config store (JSON or TOML side-file)",
why_rejected = "A side-file in a different format bypasses NCL type safety entirely — the merge operator and contract validation no longer apply. The daemon would need a custom merge algorithm to reconcile the side-file with the source NCL, and agents would need to understand two config representations. NCL's & merge operator is purpose-built for this use case.",
},
],
constraints = [
{
id = "override-layer-only",
claim = "The daemon must never write to original NCL config source files during a PUT /config/{section} mutation; only {section}.overrides.ncl may be created or modified",
scope = "crates/ontoref-daemon/src/api.rs (config mutation endpoints)",
severity = 'Hard,
check = { tag = 'Grep, pattern = "overrides\\.ncl", paths = ["crates/ontoref-daemon/src/api.rs"], must_be_empty = false },
rationale = "Immutability of source files is the safety contract the override layer provides. Violating it removes the ability to revert by deletion and breaks git diff clarity.",
},
{
id = "ncl-first-validation",
claim = "Rust config structs must not implement custom field validation; all field constraints live in NCL contracts applied before JSON reaches Rust",
scope = "crates/ontoref-daemon/src/config.rs",
severity = 'Hard,
check = { tag = 'Grep, pattern = "impl.*Validate|#\\[validate", paths = ["crates/ontoref-daemon/src/config.rs"], must_be_empty = true },
rationale = "Duplicate validation creates two diverging sources of truth. NCL contracts with std.contract.from_validator are the specified validation layer; Rust structs are downstream consumers of pre-validated data.",
},
{
id = "overrides-meta-required",
claim = "Every {section}.overrides.ncl file written by the daemon must contain a top-level _overrides_meta record with managed_by, created_at, and entries fields",
scope = "crates/ontoref-daemon/src/api.rs (override file generation)",
severity = 'Soft,
check = { tag = 'Grep, pattern = "_overrides_meta", paths = ["crates/ontoref-daemon/src/api.rs"], must_be_empty = false },
rationale = "Without audit metadata the override file is opaque — no way to surface change history in quickref or trace who changed what and why. Soft because deletion-based revert remains available even without metadata.",
},
],
related_adrs = ["adr-002", "adr-007"],
ontology_check = {
decision_string = "NCL contracts (std.contract.from_validator) are the single validation gate; Rust structs are contract-trusted with #[serde(default)]; config mutation uses override-layer files, never modifying original NCL sources",
invariants_at_risk = ["dag-formalized", "protocol-not-runtime"],
verdict = 'Safe,
},
}

View file

@ -1,85 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-009",
title = "Manifest Self-Interrogation Layer — Three Semantic Axes",
status = 'Accepted,
date = "2026-03-26",
context = "The manifest.ncl schema described structural facts (layers, modes, consumption modes, tools, config surface) but had no typed layer for self-interrogation: agents and operators could not query why a capability exists, what it needs to run, or what external dependencies have a documented blast radius. The existing tools[] field covered only dev tooling (install_method, version) — no prod/dev classification, no services, no environment variables, no infrastructure dependencies. Practice/node descriptions in core.ncl carry architectural meaning (invariant=true, ADR-backed) but are not the right home for operational, audience-facing descriptions of what a project offers. Capabilities, requirements, and dependency blast-radius analysis are three orthogonal concerns that needed typed, queryable homes in the manifest schema.",
decision = "Three new typed arrays are added to manifest_type: capabilities[] (capability_type), requirements[] (requirement_type), and critical_deps[] (critical_dep_type). These are semantically distinct layers: capabilities answer 'what does this project do, why does it exist, and how does it work' with explicit cross-references to ontology node IDs and ADR IDs; requirements classify prerequisites by env_target_type ('Production | 'Development | 'Both) and requirement_kind_type ('Tool | 'Service | 'EnvVar | 'Infrastructure); critical_deps document blast radius — what breaks when an external dependency disappears or breaks its contract — distinct from requirements because the concern is runtime failure impact, not startup prerequisites. A description | String | default = '' field is also added to manifest_type, fixing a pre-existing bug where collect-identity in describe.nu read manifest.description? (always null) instead of a field that existed. describe requirements is added as a new subcommand; describe capabilities is extended to render manifest capabilities; describe guides output gains capabilities/requirements/critical_deps keys so agents on cold start receive full self-interrogation context.",
rationale = [
{
claim = "Capabilities are operationally distinct from Practice nodes in core.ncl",
detail = "Practice nodes are architectural artifacts: they carry invariant=true, are protected by Hard ADR constraints, and describe structural patterns that must never be violated. A 'capability' is operational and audience-facing — it answers what a project offers, why it was built, and how to find the relevant code. Merging these into core.ncl would inflate the invariant-protected graph with non-architectural facts. The manifest is the right home: it is per-project, optional, and already carries operational metadata (layers, modes, config surface).",
},
{
claim = "requirements[] supercedes tools[] with a principled classification axis",
detail = "The legacy tools[] field (name, install_method, version, required) modeled only dev tooling. Production deployments need SurrealDB; CI needs the Rust nightly toolchain; the daemon needs NATS_STREAMS_CONFIG in certain topologies. env_target_type ('Production | 'Development | 'Both) and requirement_kind_type ('Tool | 'Service | 'EnvVar | 'Infrastructure) make these distinctions queryable. The tools[] field is kept for backward compatibility but requirements[] is the complete model.",
},
{
claim = "critical_deps[] separates blast-radius documentation from prerequisites",
detail = "A requirement says 'you need X to run'. A critical dep says 'X failing at runtime breaks these capabilities in these ways'. inventory (crates.io) not present as a requirement — ontoref compiles without it. But if inventory's API breaks, GET /api/catalog goes silent and ontoref_api_catalog becomes blind. This distinction matters for operational runbooks and for agents reasoning about degraded-mode operation. failure_impact is required (no default) because undocumented blast radius defeats the purpose of the type.",
},
],
consequences = {
positive = [
"describe guides output now includes capabilities, requirements, and critical_deps — agents on cold start receive complete self-interrogation context without extra tool calls",
"describe requirements new subcommand answers 'what does this project need?' for both developer setup and production deployment",
"capabilities.nodes[] and capabilities.adrs[] create explicit bidirectional cross-references between the manifest and the ontology DAG",
"env_target_type makes prod vs dev prerequisite separation queryable — CI can verify only its subset of requirements",
"critical_dep mitigation field enables agents to reason about build flags (--no-default-features) and fallback paths when a dep is unavailable",
"Bug fixed: collect-identity in describe.nu was reading manifest.kind? (always null) instead of manifest.repo_kind?; adding description | String | default = '' fixes the identity description gap",
],
negative = [
"Consumer projects must populate capabilities/requirements/critical_deps manually — there is no automatic extraction; an empty manifest still validates (all fields default = [])",
"capabilities.nodes[] cross-references must be kept in sync with core.ncl node IDs manually — no DAG consistency check at nickel export time",
"Three new array fields increase manifest.ncl verbosity; projects with complex capability/dependency landscapes will have large manifest files",
],
},
alternatives_considered = [
{
option = "Extend tools[] with env and kind fields",
why_rejected = "tools[] is semantically 'dev tooling required to build'. Extending it to cover SurrealDB (a production service) or ONTOREF_ADMIN_TOKEN_FILE (an env var) would violate the field's implied meaning. A type named tool_requirement_type that has kind = 'Service is confusing. Separate types with clear names are preferable to an overloaded catch-all.",
},
{
option = "Add capability descriptions to Practice nodes in core.ncl",
why_rejected = "Practice nodes are architectural: invariant=true nodes are ADR-protected and represent constraints future contributors must follow. Adding 'what does this offer to end users' to the invariant graph would force every capability description through the ADR lifecycle. capabilities[] is per-project, evolves freely, and belongs to the manifest (the operational layer), not the ontology (the architectural layer).",
},
{
option = "Merge critical_deps into requirements with an is_critical flag",
why_rejected = "The concern is different: requirements are prerequisites (the system cannot start without them). critical_deps are runtime load-bearing with a documented blast radius. A requirement that is optional (required = false) can still be a critical dep. The is_critical flag on requirement_type would blur this distinction and make failure_impact logically optional (not required for non-critical items) — creating a type that is partially applicable based on a flag, which is a code smell in typed schemas.",
},
],
constraints = [
{
id = "capabilities-nodes-are-ontology-ids",
claim = "capability_type.nodes[] entries must reference valid node IDs declared in .ontology/core.ncl",
scope = "ontology/schemas/manifest.ncl (capability_type definition)",
severity = 'Soft,
check = { tag = 'Grep, pattern = "nodes.*=.*\\[", paths = [".ontology/manifest.ncl"], must_be_empty = false },
rationale = "Cross-references to non-existent node IDs produce silent broken links in the graph UI and describe output. Soft because nickel export cannot verify cross-file ID consistency; responsibility lies with the author.",
},
{
id = "failure-impact-required-in-critical-deps",
claim = "critical_dep_type.failure_impact must be a non-empty string — undocumented blast radius defeats the purpose of the type",
scope = "ontology/schemas/manifest.ncl (critical_dep_type definition)",
severity = 'Hard,
check = { tag = 'Grep, pattern = "failure_impact.*=.*\"\"", paths = [".ontology/manifest.ncl"], must_be_empty = true },
rationale = "A critical dep entry with no failure_impact is indistinguishable from a regular requirement. The type's value is entirely in the blast-radius documentation.",
},
],
related_adrs = ["adr-001", "adr-009"],
ontology_check = {
decision_string = "manifest_type gains three typed self-interrogation arrays (capabilities, requirements, critical_deps) with orthogonal semantic axes; describe.nu gains describe requirements and extend describe guides; collect-identity bug fixed",
invariants_at_risk = ["dag-formalized", "self-describing"],
verdict = 'Safe,
},
}

View file

@ -1,92 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-010",
title = "Protocol Migration System — Progressive NCL Checks for Consumer Project Upgrades",
status = 'Accepted,
date = "2026-03-28",
context = "As the ontoref protocol evolved (manifest.ncl self-interrogation, typed ADR checks, CLAUDE.md agent entry-point, justfile convention), the adoption tooling relied on static prompt templates with manual {placeholder} substitution. An agent or developer adopting ontoref had no machine-queryable way to know which protocol features were missing from their project, nor how to apply them in a safe, ordered sequence. The template approach produced four separate documents that drifted out of sync with the actual protocol state and required human judgement to determine which ones applied. There was no idempotency guarantee and no check mechanism — a project that had already applied a change would re-read instructions that no longer applied.",
decision = "Protocol upgrades for consumer projects are expressed as ordered NCL migration files in reflection/migrations/NNN-slug.ncl. Each migration declares: id (zero-padded 4-digit string), slug, description, a typed check record (FileExists | Grep | NuCmd), and an instructions string interpolated at runtime with project_root and project_name. Applied state is determined solely by whether the check passes — there is no state file. This makes migrations fully idempotent: running `migrate list` on an already-compliant project shows all applied with no side effects. NuCmd checks must be valid Nushell (no bash &&, $env.VAR not $VAR, no bash redirects). Grep checks targeting ADR files must use the glob pattern adrs/adr-[0-9][0-9][0-9]-*.ncl to exclude infrastructure files (adr-schema.ncl, adr-constraints.ncl, _template.ncl) that legitimately contain deprecated field names as schema definitions. The system is exposed via `ontoref migrate list`, `migrate pending`, and `migrate show <id>` — wired into the interactive group dispatch and help system. Migrations are advisory: the system reports state, never applies changes automatically.",
rationale = [
{
claim = "Check-as-source-of-truth eliminates state file drift",
detail = "Any state file recording 'migration 0003 applied' becomes stale the moment someone reverts a change, changes branches, or clones a fresh repo. The check IS the state: if the condition is satisfied, the migration is applied; if not, it is pending. This is the same principle used by database migration tools that check for a schema version column — except here the 'column' is a Nushell assertion over the project's file system. No synchronization required.",
},
{
claim = "Typed checks (FileExists | Grep | NuCmd) cover the full protocol surface",
detail = "FileExists covers structural requirements (.ontology/manifest.ncl present). Grep covers content requirements (pattern present or absent in specific files). NuCmd covers semantic requirements that require evaluation — nickel export succeeds, capabilities[] is non-empty, justfile validates. The three types compose the full assertion space without requiring a general-purpose script language in the migration definition itself.",
},
{
claim = "Ordered numbering enables dependency reasoning without a dependency graph",
detail = "Migration 0003 (manifest self-interrogation) requires migration 0001 (manifest.ncl present) to have been applied. Rather than declaring explicit depends_on edges (which require a DAG evaluator), the numeric ordering encodes the implicit prerequisite sequence. An agent applying pending migrations in order will always satisfy prerequisites before dependent checks.",
},
],
consequences = {
positive = [
"`migrate pending` gives agents and developers a single authoritative list of what is missing — no manual comparison against protocol documentation",
"Migrations are idempotent and safe to re-run: `migrate list` on a fully-adopted project is a no-op read",
"Instructions are interpolated at runtime with project_root and project_name — no manual placeholder substitution",
"New protocol features arrive as numbered migrations without touching existing template files",
"NuCmd checks encode the same typed check logic used by ADR constraints in validate.nu — consistent assertion model across the protocol",
],
negative = [
"NuCmd checks must be single-line Nushell (nu -c) — complex multi-step checks become dense; readability degrades for non-trivial assertions",
"Grep checks require knowing which files to exclude (infrastructure vs instance files); the adr-[0-9][0-9][0-9]-*.ncl pattern is a convention that authors must follow",
"Migration ordering encodes implicit dependencies — a migration that genuinely depends on two prior migrations has no way to express that formally beyond numeric sequence",
],
},
alternatives_considered = [
{
option = "Single monolithic adoption prompt template with {placeholder} substitution",
why_rejected = "Produced four separate documents (project-full-adoption-prompt.md, update-ontology-prompt.md, manifest-self-interrogation-prompt.md, vendor-frontend-assets-prompt.md) that drifted out of sync. Required manual judgement to determine which applied to a given project. No idempotency, no machine-queryable state, no ordered application guarantee. Each new protocol feature required updating multiple templates.",
},
{
option = "State file recording applied migration IDs",
why_rejected = "State files become stale on branch switches, cherry-picks, and fresh clones. They require commit discipline to keep in sync. A project where someone manually applied the changes without running the migration tool would show the migration as pending despite being satisfied — false negatives. The check-as-truth model has no false negatives by construction.",
},
{
option = "Jinja2/j2 templating for instruction rendering",
why_rejected = "The ontoref runtime already runs Nushell for all automation. Adding a j2 dependency for template rendering introduces a new tool to install, configure, and maintain. Runtime string interpolation in Nushell (str replace --all) is sufficient for the two substitution values needed (project_root, project_name) and keeps the migration runner dependency-free.",
},
],
constraints = [
{
id = "protocol-changes-require-migration",
claim = "Any change to templates/, reflection/schemas/*.ncl, .claude/CLAUDE.md, or consumer-facing reflection/modes/ that consumer projects need to adopt must be accompanied by a new migration in reflection/migrations/",
scope = "all commits that touch templates/, reflection/schemas/, .claude/CLAUDE.md, or consumer-facing reflection/modes/",
severity = 'Hard,
check = { tag = 'Grep, pattern = "Protocol Evolution", paths = [".claude/CLAUDE.md"], must_be_empty = false },
rationale = "Migrations are the sole propagation mechanism for protocol changes. A change without a migration only applies to ontoref itself — consumer projects have no machine-queryable way to discover the change via `migrate pending`.",
},
{
id = "nucmd-checks-must-be-nushell",
claim = "NuCmd check cmd fields must be valid Nushell — no bash operators (&&, ||, 2>/dev/null), no $VARNAME (must be $env.VARNAME)",
scope = "reflection/migrations/*.ncl (any migration with tag = 'NuCmd)",
severity = 'Hard,
check = { tag = 'Grep, pattern = "&&|\\$[A-Z_]+[^)]", paths = ["reflection/migrations/"], must_be_empty = true },
rationale = "The migration runner executes checks via `nu -c $check.cmd`. Bash syntax in a Nu script produces parser errors that surface as false-negative check results — the migration appears pending due to a runner error, not because the condition is unmet.",
},
{
id = "grep-checks-use-instance-glob",
claim = "Grep checks targeting ADR files must scope to adrs/adr-[0-9][0-9][0-9]-*.ncl, not adrs/ or adrs/adr-*.ncl",
scope = "reflection/migrations/*.ncl (any migration with tag = 'Grep and paths containing 'adrs')",
severity = 'Soft,
check = { tag = 'Grep, pattern = "\"adrs/\"", paths = ["reflection/migrations/"], must_be_empty = true },
rationale = "adr-schema.ncl, adr-constraints.ncl, adr-defaults.ncl, and _template.ncl are infrastructure files that legitimately contain deprecated field names as schema definitions. Scanning all of adrs/ produces false positives in ontoref's own repo and in any consumer project that vendors the ADR schema files.",
},
],
related_adrs = ["adr-001", "adr-006"],
ontology_check = {
decision_string = "Protocol migrations expressed as ordered NCL files with typed idempotent checks; applied state determined by check result not state file; NuCmd checks must be valid Nushell; Grep checks on ADR files must use instance-only glob",
invariants_at_risk = ["no-enforcement", "self-describing"],
verdict = 'Safe,
},
}

View file

@ -1,113 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-011",
title = "Mode Guards and Convergence — Active Partner and Refinement Loop in the Mode Schema",
status = 'Accepted,
date = "2026-03-30",
context = "Reflection modes executed procedures as typed DAGs but lacked two capabilities that caused real failures: (1) modes could run against projects in invalid states — missing ontology files, unavailable tools, incomplete manifests — because preconditions were informational text, not executable checks. An agent following the protocol would execute sync-ontology on a project without core.ncl and get an opaque nickel error instead of a clear block. (2) Modes like sync-ontology require iteration — scan, diff, propose, apply, then verify that drift is zero. If drift remained after one pass, the mode reported success and the agent moved on. There was no mechanism for the protocol to say 'keep going until this condition is met'. Both gaps were identified during a systematic comparison against 45 augmented coding patterns (lexler.github.io/augmented-coding-patterns): Active Partner (#1) requires the system to push back on invalid actions, and Refinement Loop (#36) requires iteration until convergence. A separate action subsystem (ext/action) was considered and rejected in favor of extending the existing mode schema.",
decision = "Extend reflection/schema.ncl with two new optional fields on ModeBase: guards (Array Guard) for pre-flight executable checks, and converge (Converge) for post-execution convergence loops. Guards run before any step and can Block (abort) or Warn (continue with message). Converge evaluates a condition command after all steps complete and re-executes failed or all steps up to max_iterations times. Both are backward-compatible — all existing modes export unchanged with guards defaulting to [] and converge being optional.",
rationale = [
{
claim = "Guards formalize the Active Partner pattern: the protocol pushes back before acting",
detail = "Guards are executable shell commands with a severity (Block/Warn) and a human-readable reason. Unlike preconditions (informational text), guards run and block. This prevents agents from executing modes against projects in invalid states. The guard reason tells the agent exactly what to fix.",
},
{
claim = "Converge formalizes the Refinement Loop pattern: modes iterate until a condition is met",
detail = "Modes like sync-ontology need to iterate: apply changes, re-diff, apply again if drift remains. The converge field lets the mode contract declare this expectation. The executor handles the loop logic — mode authors just declare the condition, max iterations, and retry strategy (RetryFailed or RetryAll).",
},
{
claim = "Extending the mode schema is simpler than creating a separate action subsystem",
detail = "The alternative was ext/action — a new NCL schema for action contracts with gates, events, and convergence conditions. This would have created a parallel concept to modes with overlapping responsibilities. Since modes are already the execution abstraction, adding guards and converge to the same schema keeps a single concept for all executable procedures. Consumer projects learn one schema, not two.",
},
],
consequences = {
positive = [
"Agents get clear, early feedback when a mode cannot run (guard reason instead of opaque errors)",
"Iterative workflows like sync-ontology converge automatically instead of requiring manual re-execution",
"The mode schema remains the single execution abstraction — no competing action/workflow concept",
"Backward compatible: all 19 existing modes export unchanged",
"describe mode shows guards and converge sections — agents see the full execution contract",
],
negative = [
"Guard commands add shell execution before steps — latency increases for guarded modes",
"Convergence loops can mask underlying issues if max_iterations is set too high — a mode that never converges wastes resources silently",
"Mode authors must write correct shell commands for guard checks and convergence conditions — no NCL-level validation of command correctness",
],
},
alternatives_considered = [
{
option = "ext/action — separate action contract schema with gates, events, and convergence",
why_rejected = "Creates a parallel execution abstraction competing with modes. Consumer projects would need to learn both modes and actions, and the boundary between them would be ambiguous. The mode schema already has steps, dependencies, and error strategies — guards and converge are natural extensions of the same concept.",
},
{
option = "Executable preconditions — make existing preconditions[] run commands instead of being text",
why_rejected = "Preconditions serve a different purpose: they document what the human should verify. Making them executable would lose the documentation function. Guards are a separate concept: machine-checked pre-flight blocks. Both can coexist — preconditions for humans, guards for machines.",
},
{
option = "External convergence via Vapora workflows — let the orchestrator handle iteration",
why_rejected = "Convergence is a property of the mode itself, not of the orchestrator. sync-ontology should declare that it iterates until zero drift regardless of whether it runs via CLI, Vapora, or CI. Pushing this to the orchestrator means every orchestrator must know which modes need iteration and under what conditions — that knowledge belongs in the mode contract.",
},
],
constraints = [
{
id = "guard-schema-present",
claim = "reflection/schema.ncl exports a Guard type with id, cmd, reason, and severity fields",
scope = "reflection/schema.ncl",
severity = 'Hard,
rationale = "The Guard type is the contract that all mode consumers rely on. Removing or renaming its fields breaks all guarded modes and the executor.",
check = {
tag = 'Grep,
pattern = "Guard.*=.*_Guard",
paths = ["reflection/schema.ncl"],
must_be_empty = false,
},
},
{
id = "converge-schema-present",
claim = "reflection/schema.ncl exports a Converge type with condition, max_iterations, and strategy fields",
scope = "reflection/schema.ncl",
severity = 'Hard,
rationale = "The Converge type is the contract for iterative modes. Removing it breaks the convergence loop in the executor.",
check = {
tag = 'Grep,
pattern = "Converge.*=.*_Converge",
paths = ["reflection/schema.ncl"],
must_be_empty = false,
},
},
{
id = "executor-guards-implemented",
claim = "The mode executor in reflection/nulib/modes.nu evaluates guards before executing steps",
scope = "reflection/nulib/modes.nu",
severity = 'Hard,
rationale = "Guards without executor support are declarations without effect. The executor must run each guard cmd and abort on Block failures.",
check = {
tag = 'Grep,
pattern = "Guards.*Active Partner",
paths = ["reflection/nulib/modes.nu"],
must_be_empty = false,
},
},
],
ontology_check = {
decision_string = "Extend mode schema with guards and converge",
invariants_at_risk = ["protocol-not-runtime"],
verdict = 'RequiresJustification,
},
invariant_justification = {
invariant = "protocol-not-runtime",
claim = "Guards and converge are declarative NCL fields — the protocol defines what to check and when to iterate, not how to execute. The executor in modes.nu is tooling, not protocol. A consumer project could implement its own executor that reads the same guards and converge declarations.",
mitigation = "The schema (reflection/schema.ncl) is protocol. The executor (reflection/nulib/modes.nu) is tooling. Both are clearly separated. A project can use the schema without the executor.",
},
related_adrs = ["adr-002"],
}

View file

@ -1,88 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-012",
title = "Domain Extension System — Bash-Layer Dispatch for repo_kind-Conditional CLI Domains",
status = 'Accepted,
date = "2026-04-05",
context = "Consumer projects have project-type-specific CLI needs that ontoref's generic command set cannot satisfy. A PersonalOntology project has CFP pipelines, career schemas, and opportunity tracking that no other repo_kind needs. A DevWorkspace project has workspace cards, cluster state dimensions, and production-readiness gates. These capabilities lived as local scripts (scripts/jpl.nu) — invisible to ontoref's help system, absent from describe capabilities, and not portable to other PersonalOntology projects. The problem had two layers: (1) how to conditionally load domain-specific Nu commands without breaking the existing dispatcher, and (2) how to make domain commands discoverable (help, describe capabilities) and aliasable (ore prov, personal state). Nu's module system (`use`, `source`, `overlay use`) is compile-time: paths must be known at parse time and `overlay use` creates an isolated namespace that cannot extend `def main` in the existing dispatcher. Runtime loading of arbitrary Nu modules is architecturally impossible in the current Nu model.",
decision = "Implement a bash-layer domain dispatch system. The ontoref bash wrapper (install/ontoref-global) resolves the first CLI argument against $ONTOREF_ROOT/domains/{arg}/repo_kinds.txt before delegating to the Nu dispatcher. If the argument matches a domain directory name (or a registered alias from domains/aliases.txt) AND the current project's repo_kind appears in that domain's repo_kinds.txt, dispatch directly to nu domains/{id}/commands.nu passing remaining args. Each domain ships three files: domain.ncl (NCL contract declaring commands, pages, repo_kinds, short_alias), commands.nu (Nu script with def main [...args] entry point), and repo_kinds.txt (plain-text list of matching repo_kind values, grep-readable by the bash wrapper without running nickel). install.nu copies the entire domains/ tree to $data_dir/domains/ and generates domains/aliases.txt mapping short_alias → domain_id. Short aliases also create standalone bin wrappers at $bin_dir/alias.",
rationale = [
{
claim = "Bash layer is the correct dispatch boundary for runtime-conditional Nu module selection",
detail = "Nu resolves `use` and `source` at parse time — runtime loading of arbitrary module paths is impossible without spawning a new Nu process. The bash wrapper already mediates between the caller and the Nu dispatcher; extending it with a domain lookup before calling Nu is a natural seam. The domain's commands.nu is a self-contained Nu script with def main as its entry point — no module loading involved, just a new process.",
},
{
claim = "repo_kinds.txt is grep-readable without nickel — critical for dispatch performance",
detail = "The bash wrapper calls domain dispatch on every invocation. Using nickel export to read repo_kind from domain.ncl would add 200-400ms per invocation. repo_kinds.txt is a plain-text file (one repo_kind per line) that grep can check in under 1ms. Similarly, repo_kind is extracted from the project manifest via grep on the NCL source (repo_kind = 'Tag) rather than nickel export — avoiding import-path resolution failures for manifests that import schema files.",
},
{
claim = "NCL domain.ncl contract provides discoverability without hardcoding",
detail = "help.nu and describe.nu read domain.ncl at runtime to render domain commands in ore help and ore describe capabilities. The Nu dispatcher never needs to know about domain commands at parse time — it only sees them if ore help <domain-id> is invoked, at which point it shells out to nickel export domain.ncl. This keeps the Nu dispatcher static while allowing dynamic domain registration.",
},
{
claim = "short_alias enables both ore-level and standalone invocation with one declaration",
detail = "domain.ncl declares short_alias (e.g. 'prov', 'personal'). install.nu generates two artifacts: domains/aliases.txt (consumed by the bash wrapper for ore prov → ore provisioning resolution) and $bin_dir/alias (standalone wrapper for prov state). Both derived from the same field — single source of truth per domain.",
},
],
consequences = {
positive = [
"PersonalOntology and DevWorkspace/Mixed projects have discoverable, aliasable CLI commands without local scripts",
"ore help personal / ore help provisioning renders from domain.ncl — no hardcoded help text",
"ore describe capabilities shows DOMAIN EXTENSION section automatically for any matching project",
"New domains require only three files: domain.ncl, commands.nu, repo_kinds.txt — no changes to the Nu dispatcher",
"Short aliases (personal, prov) work both as ore prov and standalone prov with the same domain-membership enforcement",
],
negative = [
"Domain commands always spawn a new Nu process (cannot share session state with the main dispatcher)",
"commands.nu cannot import from the main reflection/ Nu library without explicit path setup",
"Domain dispatch adds one grep + one stat call per invocation (sub-millisecond, but measurable)",
],
},
alternatives_considered = [
{
option = "Nu overlay use for runtime domain loading",
why_rejected = "overlay use creates an isolated namespace — commands defined in an overlayed module cannot be called by name in the parent scope. It is also parse-time in module context. Confirmed broken: nu -c 'use FILE *; command args' causes infinite recursion when called from def main.",
},
{
option = "Add domain commands directly to reflection/bin/ontoref.nu",
why_rejected = "Would require hardcoding every domain's commands in the main dispatcher, or using dynamic path strings in `use` which Nu forbids. Also violates the no-enforcement axiom — the main dispatcher should not know about PersonalOntology specifics.",
},
{
option = "Project-local scripts/ (scripts/jpl.nu approach)",
why_rejected = "Invisible to ore help and ore describe capabilities. Not portable across PersonalOntology projects. Namespace requires prefix (jpl cfp vs cfp). Dispatch requires knowing the script path.",
},
],
constraints = [
{
id = "domain-files-required",
claim = "Every domain directory under $ONTOREF_ROOT/domains/{id}/ must contain domain.ncl, commands.nu, and repo_kinds.txt",
scope = "domains/",
severity = "Hard",
check = { tag = 'NuCmd, cmd = "ls $env.ONTOREF_ROOT/domains/ | where type == 'dir' | get name | each { |d| let missing = ['domain.ncl' 'commands.nu' 'repo_kinds.txt'] | where { |f| not ($\"($d)/($f)\" | path exists) }; if ($missing | is-not-empty) { error make { msg: $\"domain ($d | path basename) missing: ($missing | str join ', ')\" } } }; true", expect_exit = 0 },
rationale = "The bash wrapper assumes all three files exist once a domain directory is found. Missing files produce confusing errors at dispatch time.",
},
{
id = "commands-nu-def-main",
claim = "commands.nu must declare def main [...args: string] as its entry point — no dynamic use/source calls inside Nu scripts",
scope = "domains/*/commands.nu",
severity = "Hard",
check = { tag = 'NuCmd, cmd = "glob $\"($env.ONTOREF_ROOT)/domains/*/commands.nu\" | each { |f| if not (open --raw $f | str contains 'def main') { error make { msg: $\"($f): missing def main\" } } }; true", expect_exit = 0 },
rationale = "The bash wrapper calls nu commands.nu <args>. Nu invokes def main with the remaining args. Without def main, all args are ignored. Dynamic use/source cause infinite recursion.",
},
],
related_adrs = ["adr-001", "adr-006"],
ontology_check = {
decision_string = "domain extension bash dispatch repo_kind conditional Nu",
invariants_at_risk = [],
verdict = "Safe",
},
}

View file

@ -1,82 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-013",
title = "VCS Abstraction Layer — Uniform jj/git API via vcs.nu",
status = 'Accepted,
date = "2026-04-07",
context = "ontoref modules that interact with version control (opmode.nu, git-event.nu, jjw.nu, init-repo.nu) historically hardcoded ^git subcommands. As jj adoption grows among contributors and orchestration projects (vapora), hardcoded git calls produce silent failures: jj repos have .jj/ but may lack a .git/ HEAD in expected locations, and jj semantics differ (the working copy is always a commit, @- is the parent, `jj file show` replaces `git show HEAD:path`). The dual-VCS problem had two layers: (1) detection — which VCS is active in a given project root, and (2) semantics — same logical operation (show last committed state, restore a file, get remote URL) expressed differently per VCS. Spreading detection logic across modules produces duplication and makes future VCS additions (e.g. Pijul, Sapling) a multi-file change.",
decision = "Introduce reflection/modules/vcs.nu as the single VCS abstraction layer. It exports: detect (returns 'jj' | 'git' | 'none' via filesystem check — .jj/ presence), is-repo, show-committed (jj: `jj file show -r @-`; git: `git show HEAD:path`), restore-file (jj: `jj restore --from @-`; git: `git checkout --`), remote-url (jj: `jj git remote list`; git: `git remote get-url origin`), current-branch (jj: `jj log -r @ --no-graph -T bookmarks`; git: `git branch --show-current`), uncommitted-files (jj: `jj diff --summary -r @`; git: `git status --porcelain`), commit-count (jj: `jj log --no-graph -T '' | lines | length`; git: `git rev-list --count HEAD`). All ontoref modules must import vcs.nu and call these exports — direct ^git or ^jj subprocess calls inside modules are prohibited. jj and rad are not listed as requirements in ontoref's manifest: they are opt-in tools whose requirements belong in orchestration projects that depend on them.",
rationale = [
{
claim = "Filesystem detection is the correct boundary — no config, no env var",
detail = "Requiring contributors to set ONTOREF_VCS=jj or maintain a config entry creates state that can desync from reality. .jj/ presence is authoritative: if the directory exists, jj is the VCS regardless of any other indicator. This also makes detection work correctly in multi-VCS scenarios (jj colocated repos have both .jj/ and .git/ — detection correctly prefers jj).",
},
{
claim = "Single module boundary isolates VCS semantic differences from all callers",
detail = "jj's working-copy-as-commit model means @- (parent) is the last deliberately committed state, not HEAD. show-committed encodes this semantic difference once in vcs.nu. Every caller that needs 'the last committed content' calls show-committed and gets the right answer for both VCS backends without knowing which one is active.",
},
{
claim = "opt-in jj/rad — requirements live in the orchestration layer, not in ontoref",
detail = "ontoref ships jjw.nu and vcs.nu as tooling; whether a project uses jj or Radicle is decided by the orchestration project (e.g. vapora). Listing jj/rad as ontoref requirements would force every ontoref consumer to acknowledge tools they will never use. Requirements for opt-in tools belong in the manifest of the project that orchestrates them.",
},
],
consequences = {
positive = [
"All VCS interaction is centralized — adding Pijul or Sapling support requires changes only to vcs.nu",
"opmode.nu and git-event.nu work correctly in both git and jj repos without caller changes",
"jjw.nu agent workspace lifecycle works transparently over jj without any git fallback complexity in the orchestration layer",
"Detection is sub-millisecond — no nickel export, no network, no subprocess for the detection itself",
],
negative = [
"Modules that previously called ^git directly must be updated to import vcs.nu — migration cost for existing modules",
"vcs.nu adds a Nu module import to every module that touches VCS — minor parse-time overhead",
],
},
alternatives_considered = [
{
option = "Env var ONTOREF_VCS to select backend",
why_rejected = "Creates mutable state that can desync from the actual repo state. A repo cloned fresh has no env var set; a contributor switching between git and jj repos would need to update the env var manually. Filesystem detection is always correct without configuration.",
},
{
option = "Per-module inline detection (duplicate detect logic in each file)",
why_rejected = "Already the de-facto state before vcs.nu. Duplicated detection means any change to jj semantics (e.g. a jj CLI flag change) requires hunting every module. The abstraction cost is one import line per module.",
},
{
option = "Wrap the entire CLI in a shim that translates git commands to jj",
why_rejected = "Shim-layer translation is fragile — git and jj command surfaces are not isomorphic (jj has no git stash equivalent; jj describe vs git commit -m). The operations ontoref needs are a small, well-defined set; a typed Nu module is a cleaner contract than a command-translation shim.",
},
],
constraints = [
{
id = "vcs-module-single-source",
claim = "All VCS subprocess calls in reflection/modules/ and reflection/bin/ must go through vcs.nu exports — no direct ^git or ^jj calls outside vcs.nu itself",
scope = "reflection/modules/, reflection/bin/",
severity = 'Hard,
check = { tag = 'NuCmd, cmd = "glob $\"($env.ONTOREF_ROOT)/reflection/{modules,bin}/*.nu\" | where { |f| ($f | path basename) != 'vcs.nu' } | each { |f| let hits = open --raw $f | lines | where { |l| ($l | str trim | str starts-with '^git ') or ($l | str trim | str starts-with '^jj ') }; if ($hits | is-not-empty) { error make { msg: $\"($f): direct VCS call found\" } } }; true", expect_exit = 0 },
rationale = "The abstraction only holds if it is the single call site. Any direct ^git call in a module bypasses the detection logic and breaks jj repos silently.",
},
{
id = "jj-rad-not-in-ontoref-requirements",
claim = "jj and rad must not appear as required = true entries in .ontology/manifest.ncl requirements[]",
scope = ".ontology/manifest.ncl",
severity = 'Hard,
check = { tag = 'NuCmd, cmd = "let reqs = (do { ^nickel export $\"($env.ONTOREF_PROJECT_ROOT)/.ontology/manifest.ncl\" } | complete | if $in.exit_code == 0 { $in.stdout | from json | get requirements? | default [] } else { [] }); let forced = $reqs | where { |r| ($r.id == 'jj' or $r.id == 'rad') and $r.required == true }; if ($forced | is-not-empty) { error make { msg: $\"jj/rad must not be required=true in ontoref manifest\" } }; true", expect_exit = 0 },
rationale = "jj and rad are opt-in tools. Marking them required=true in ontoref's manifest propagates a false requirement to every consumer that reads describe requirements.",
},
],
related_adrs = ["adr-012"],
ontology_check = {
decision_string = "vcs abstraction jj git uniform module",
invariants_at_risk = [],
verdict = 'Safe,
},
}

View file

@ -1,98 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-014",
title = "Runtime Service Toggles — AtomicBool Flags for MCP and GraphQL",
status = 'Accepted,
date = "2026-04-26",
context = "ontoref-daemon exposes optional services (MCP, GraphQL) compiled in via Cargo feature flags. Once compiled, these services were always active for the lifetime of the process. Two scenarios require disabling them at runtime without restart: (1) security incident — temporarily disable a surface while investigating a compromise; (2) operator choice — enable graphql during a debug session, disable it in production. The alternative of restarting the daemon is disruptive because it drops all in-memory sessions, actors, and notification queues. Compile-time feature flags solve the binary presence problem but cannot address runtime availability.",
decision = "Introduce ServiceFlags (pub struct in api.rs) holding one AtomicBool per toggleable service, gated by the corresponding feature flag. ServiceFlags::new() initialises all flags to true (enabled). AppState holds Arc<ServiceFlags> shared across all clones. The toggle check lives in a route_layer middleware on the sub-router for each service — not inside the service handlers themselves — so the check is enforced regardless of which handler a request reaches. Two toggle surfaces are provided: (1) REST API: PUT /api/services/:service {\"enabled\": bool} — daemon admin Bearer required; (2) UI: POST /ui/manage/services/:service/toggle — AdminGuard (cookie session). Both surfaces return the new state. The manage page shows each compiled-in service with an HTMX toggle button; the navbar badges reflect runtime state on every page render.",
rationale = [
{
claim = "AtomicBool is the correct primitive for a hot-path toggle",
detail = "A toggle check runs on every request to /mcp/* and /graphql/*. AtomicBool::load(Relaxed) is a single CPU instruction with no lock, no allocation, and no blocking — identical cost to a null pointer check. RwLock<bool> would introduce lock contention under concurrent requests with no benefit, since the only invariant needed is 'consistent within a single request', which Relaxed ordering satisfies.",
},
{
claim = "Middleware layer placement enforces the toggle at the router boundary",
detail = "Placing the AtomicBool check inside each handler would require every handler to duplicate the guard, and a new handler added to the sub-router would silently bypass the toggle. A route_layer on the sub-router wraps all handlers uniformly — the check cannot be missed regardless of how many handlers the sub-router gains in the future.",
},
{
claim = "Arc<ServiceFlags> (not Arc<AtomicBool> per flag) is the correct sharing primitive",
detail = "AtomicBool is not Clone. Wrapping the whole ServiceFlags struct in one Arc means: (1) the middleware closure captures one Arc clone instead of N; (2) new flags can be added to ServiceFlags without changing the sharing structure; (3) the UI and REST handlers access all flags via one field on AppState.",
},
{
claim = "Two toggle surfaces (REST + UI) serve distinct operator workflows",
detail = "The REST endpoint (daemon admin Bearer) is suitable for scripted automation and CLI pipelines (curl -X PUT). The UI endpoint (AdminGuard cookie) enables toggle from the browser manage page without exposing the raw admin token. Both require daemon admin credentials — this is not a project-level operation.",
},
{
claim = "Toggle is volatile — it does not survive daemon restart",
detail = "ServiceFlags are initialised to enabled=true on every startup regardless of the pre-restart state. This is intentional: a flag disabled for incident response should not silently persist across restarts, which would hide the disabled state from new operators. If persistent toggle state is needed it belongs in config.ncl as a compiled-in default, not in volatile memory.",
},
],
consequences = {
positive = [
"MCP and GraphQL can be disabled in under 1ms without dropping in-memory state",
"Toggle surfaces require daemon admin credentials — the same identity used for project management",
"New optional services added to the daemon gain toggle support by adding one AtomicBool field to ServiceFlags",
"Middleware placement means the toggle cannot be bypassed by adding new handlers to the sub-router",
],
negative = [
"Toggle state is lost on restart — disabling a service for incident response must be re-applied after restart or captured in config",
"ServiceFlags is not Clone (AtomicBool is not Clone) — AppState clones the Arc, not the flags; callers that pattern-match on AppState fields must be aware of this",
],
},
alternatives_considered = [
{
option = "Restart daemon with different feature flags or config",
why_rejected = "Restart drops SessionStore (all active logins), actor registry, notification queue, and NATS subscriptions. A 1-second outage is acceptable for planned maintenance but not for rapid incident response.",
},
{
option = "RwLock<bool> per service in AppState",
why_rejected = "RwLock introduces lock contention on every request. The toggle check does not need mutual exclusion with writes — a store and a load never run concurrently in a way that would corrupt state. Relaxed AtomicBool is sufficient and faster.",
},
{
option = "Dynamic axum Router rebuild — swap out the sub-router entirely",
why_rejected = "axum Router is not live-rebuildable without replacing the entire tower Service. This would require Arc<RwLock<Router>>, a custom Service wrapper, and would still incur a lock per request. The middleware approach achieves the same result with orders of magnitude less complexity.",
},
],
related_adrs = ["adr-002", "adr-005"],
ontology_check = {
decision_string = "runtime toggle AtomicBool service mcp graphql middleware",
invariants_at_risk = [],
verdict = 'Safe,
},
constraints = [
{
id = "c-014-1",
claim = "ServiceFlags::new() must initialise all AtomicBool flags to true — a compiled-in service is always enabled at startup",
scope = "crates/ontoref-daemon/src/api.rs",
severity = 'Hard,
rationale = "Starting with flags disabled would silently hide services from operators who have not read this ADR. Explicit runtime toggle (documented in UI and REST API) is the correct mechanism to disable a service.",
check = { tag = "Grep", pattern = "AtomicBool::new(true)", paths = ["crates/ontoref-daemon/src/api.rs"], must_be_empty = false },
},
{
id = "c-014-2",
claim = "Any new optional service added to the daemon must add an AtomicBool to ServiceFlags and a route_layer toggle middleware on its sub-router",
scope = "crates/ontoref-daemon/src/api.rs",
severity = 'Hard,
rationale = "A service without a toggle violates the operator contract established by this ADR. Future services must be consistently operable.",
check = { tag = "Grep", pattern = "pub struct ServiceFlags", paths = ["crates/ontoref-daemon/src/api.rs"], must_be_empty = false },
},
{
id = "c-014-3",
claim = "Service toggle endpoints require daemon admin credentials — project-level auth is insufficient",
scope = "crates/ontoref-daemon/src/api.rs, crates/ontoref-daemon/src/ui/handlers.rs",
severity = 'Soft,
rationale = "Service availability is daemon-wide, not per-project. Allowing a project admin to disable MCP or GraphQL for all projects would be a privilege escalation.",
check = { tag = "Grep", pattern = "AdminGuard", paths = ["crates/ontoref-daemon/src/ui/handlers.rs"], must_be_empty = false },
},
],
}

View file

@ -1,88 +0,0 @@
let d = import "adr-defaults.ncl" in
d.make_adr {
id = "adr-015",
title = "MCP Tool Catalog via #[onto_mcp_tool] Proc-Macro + Inventory",
status = 'Accepted,
date = "2026-04-26",
context = "ontoref-daemon exposes 33 MCP tools via the rmcp ToolRouter in crates/ontoref-daemon/src/mcp/mod.rs. The authoritative tool implementation lives in each tool struct's `ToolBase`/`AsyncTool` impls (name, description, schema). However, the agent-facing tool catalog returned by `ontoref_help` was a hand-typed JSON literal (~100 lines, mod.rs:1129-1226) duplicating every tool's name, description, and parameter shape. This is the same failure mode ADR-007 documents as the original motivation for `#[onto_api]`: the previous-session bug where `insert_mcp_ctx` listed 15 tools while the router had 27. ADR-007 fixed that drift for the HTTP API surface (inventory-collected `ApiRouteEntry`) but did not extend the pattern to MCP. As of 2026-04-26 the manifest claim 'daemon exposes 33 MCP tools' is also a hand-maintained string in `.ontology/manifest.ncl`. With the rate of MCP tool additions through 2026-Q1 (qa, bookmarks, actions, config, ontology extensions all added in separate sessions), drift was becoming inevitable.",
decision = "Every MCP tool struct in ontoref-daemon must carry `#[onto_mcp_tool(name, description, category, params)]`. The proc-macro (in `crates/ontoref-derive`) emits `inventory::submit!(ontoref_ontology::McpToolEntry{...})` at link time and leaves the annotated struct unchanged — the existing `ToolBase` and `AsyncTool` impls are untouched. A new pure function `ontoref_daemon::mcp::catalog()` walks `inventory::iter::<McpToolEntry>()`, sorts by name, and returns `Vec<&'static McpToolEntry>`. `HelpTool::invoke` now serializes `catalog()` instead of holding a hand-typed JSON literal. `McpToolEntry` lives in `ontoref-ontology` next to `ApiRouteEntry` and reuses `ApiParam` for parameter metadata — both are protocol surfaces, the type is generic. `tool_router()`'s compile-time `with_async_tool::<T>()` list is left as-is; the Rust type system requires the type list at compile time and a separate macro architecture would be needed to derive it from inventory (out of scope for this ADR).",
rationale = [
{
claim = "Same drift class as ADR-007 — same fix pattern",
detail = "ADR-007 cites the insert_mcp_ctx (15 listed) vs router (27 actual) drift as proof that hand-maintained registries decay. The HelpTool JSON literal had identical mechanics: every new tool struct required two coordinated edits (with_async_tool registration + JSON entry) with no compiler enforcement that they stayed in sync. Applying the same inventory linker pattern that fixed it for HTTP routes closes the equivalent gap for MCP.",
},
{
claim = "Co-location of annotation and implementation",
detail = "`#[onto_mcp_tool]` sits directly on the tool struct, immediately above its `ToolBase` impl. Adding a tool that compiles but has no inventory entry requires actively skipping the attribute — a much higher-friction error than forgetting to update a JSON list 100 lines away in the same file. Removing a tool struct removes its inventory entry automatically.",
},
{
claim = "Zero new dependencies",
detail = "ontoref-derive and inventory are already workspace dependencies (introduced by ADR-007). McpToolEntry mirrors ApiRouteEntry's shape and reuses ApiParam — no schema duplication. The change is additive: existing #[onto_api] macros are unaffected.",
},
{
claim = "Preserves runtime cost guarantees",
detail = "inventory::iter walks a linker-built linked list — no HashMap, no Arc, no allocation. catalog() is a pure function returning &'static references. HelpTool's per-call cost is unchanged (still O(n) over the tool list, no cache invalidation, no daemon state required).",
},
],
consequences = {
positive = [
"Adding a tool struct without #[onto_mcp_tool] makes the tool invisible to ontoref_help — drift is detectable on first agent invocation",
"Removing a tool struct removes its catalog entry automatically; no orphaned help entries",
"Manifest claim '33 MCP tools' is verifiable at runtime via catalog().len() rather than asserted by hand",
"Future tool tiering (essential vs extended, mentioned but deferred from this ADR) becomes a single-field addition to McpToolEntry",
"Same proc-macro infrastructure as #[onto_api] — no new linker semantics, no platform-specific concerns introduced",
],
negative = [
"Per-tool params are stringly-typed in the `params = \"name:type:constraint:desc; ...\"` attribute argument — a typo in `required` vs `optional` is not caught at compile time (mirrors the same limitation in #[onto_api])",
"tool_router()'s with_async_tool::<T>() list still requires manual maintenance — the inventory does not eliminate it. A drift between router registrations and inventory entries is possible (caught only at runtime by the regression test, not at compile time)",
"33 tool structs require a one-time annotation pass — non-trivial diff size, but mechanical and reviewable",
],
},
alternatives_considered = [
{
option = "Generate the help JSON from ToolBase::name() + description() directly via reflection",
why_rejected = "ToolBase exposes name and description but not parameter metadata in a structured form (input_schema returns a JsonObject blob, not the per-param required/values/note shape that ontoref_help emits). Walking the JSON schema and reverse-engineering the per-param hints is brittle. The inventory entry lets us declare the agent-facing param documentation in a stable, typed shape next to the ToolBase impl.",
},
{
option = "Replace tool_router()'s with_async_tool::<T>() list with macro-driven iteration over inventory",
why_rejected = "rmcp's ToolRouter requires the tool type at compile time (generic associated types over each tool's Parameter/Output types). Driving registration from inventory entries — which are runtime values of `&'static McpToolEntry` — would require either a build.rs that emits the registration list or a macro that takes the full type list as input. Either is feasible but is a larger architectural change with no immediate reliability win beyond what the inventory already provides for the help/catalog surface. Deferred.",
},
{
option = "Skip the macro and write an `inventory::submit!` block manually next to each tool",
why_rejected = "Eliminates the macro infrastructure but loses the validation that #[onto_mcp_tool] applies (key spelling check, param string parsing reuse). Manual blocks also bypass the file!() capture for source-file traceability that ApiRouteEntry already uses.",
},
],
constraints = [
{
id = "onto-mcp-tool-on-all-tools",
claim = "Every MCP tool struct in ontoref-daemon must carry #[onto_mcp_tool(...)]",
scope = "ontoref-daemon (crates/ontoref-daemon/src/mcp/mod.rs)",
severity = 'Hard,
check = { tag = 'Grep, pattern = "#\\[onto_mcp_tool", paths = ["crates/ontoref-daemon/src/mcp/mod.rs"], must_be_empty = false },
rationale = "catalog() is only as complete as the set of annotated tool structs. Unannotated tools are invisible to agents calling ontoref_help — equivalent to undocumented tools and a re-introduction of the ADR-007 drift class.",
},
{
id = "mcp-catalog-router-parity",
claim = "The number of #[onto_mcp_tool] annotations must equal the number of with_async_tool::<T>() calls in tool_router()",
scope = "ontoref-daemon (crates/ontoref-daemon/src/mcp/mod.rs)",
severity = 'Soft,
check = { tag = 'NuCmd, command = "let a = (open crates/ontoref-daemon/src/mcp/mod.rs | lines | where ($it | str starts-with '#[ontoref_derive::onto_mcp_tool') | length); let b = (open crates/ontoref-daemon/src/mcp/mod.rs | lines | where ($it | str contains 'with_async_tool::<') | length); if $a != $b { error make {msg: $'annotation count ($a) != router registration count ($b)'} } else { 'ok' }" },
rationale = "Without compile-time enforcement, a tool could be registered with the router but lack the annotation (or vice versa). This soft constraint catches the divergence at validate time. Future work (deferred alternative above) can lift this to compile time.",
},
],
related_adrs = ["adr-007", "adr-001"],
ontology_check = {
decision_string = "Use #[onto_mcp_tool] proc-macro + inventory linker registration as the single source of truth for the MCP tool catalog; HelpTool renders from catalog() instead of a hand-typed JSON literal",
invariants_at_risk = ["protocol-not-runtime"],
verdict = 'Safe,
},
}

View file

@ -1,101 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-016",
title = "Component Lift-Out Pattern: Four-Criterion Gate for Standalone Extraction",
status = 'Accepted,
date = "2026-05-01",
context = "Ontoref itself was extracted from stratumiops (ADR-001). The same pattern recurred in the provisioning project: buildkit-launcher (build substrate) and backup-manager (backup orchestration) grew inside provisioning but serve a broader class of consumers — vapora, workspace infras, CI pipelines, and third-party projects. No formal criterion existed for deciding when a component is ready to become a standalone peer project. Without a criterion, the decision is arbitrary and either premature (decomposition overhead before consumer plurality) or delayed (host coupling accretes, extraction becomes expensive).",
decision = "A component is extracted as a standalone peer project when it passes all four criteria: (1) Orthogonal concern — the component's core domain is not the host project's core domain; (2) Consumer plurality — at least two distinct callers exist or are immediately planned; (3) Release cadence divergence — the component's evolution is not gated by the host project's release cycle; (4) Config path-agnostic — the component can receive its config from any caller without importing host infrastructure (workspace crates, host config loaders, host schema registries). The extracted project registers in ontoref before any code moves. The host project retains extension-side artifacts (schemas, defaults, component declarations) that allow workspace infras to declare directives for the extracted tool.",
rationale = [
{
claim = "Orthogonality is the primary criterion",
detail = "A build substrate (lian-build) and a backup orchestrator (cloudatasave) do not belong to provisioning's core domain (workspace lifecycle management). Provisioning calls them; it does not own their concerns. Orthogonality is the structural signal that extraction is correct, not premature.",
},
{
claim = "Consumer plurality prevents premature extraction",
detail = "A component with a single caller has no demonstrated need for independent lifecycle. Two or more distinct callers (e.g. provisioning + vapora, or provisioning + a workspace CI pipeline) prove the component's value is general, not host-specific. ADR-001 precedent: ontoref serves stratumiops, typedialog, vapora, kogral.",
},
{
claim = "Config path-agnostic is the coupling criterion",
detail = "If a component imports the host's config loader, schema registry, or workspace crates to function, it cannot be independently deployed or tested. Severing this import at extraction time is not simplification — it is restoration of the correct dependency direction. The extracted project has its own config infrastructure; callers pass directives in the extracted project's own NCL vocabulary.",
},
{
claim = "Ontoref registration before code move establishes ontological identity",
detail = "Creating the .ontology/ and ADRs in the new project before any code moves ensures the project exists as a declared concept before it exists as an implementation. This prevents the common failure mode where a project's identity is derived post-hoc from accumulated code rather than declared from design.",
},
{
claim = "Host retains extension-side artifacts",
detail = "The host project (provisioning) retains the extension schemas, defaults, and component declarations that allow workspace infras to configure the extracted tool. This is not coupling — it is the host's side of the protocol contract. The extracted project defines its capabilities in its own ontology; provisioning extensions expose those capabilities to workspace consumers.",
},
],
consequences = {
positive = [
"Decisions to extract are justified, not arbitrary — four criteria provide a checkable gate",
"Extracted projects are immediately usable by non-provisioning callers (vapora, CI, workspace infras)",
"Host projects remain focused on their core domain; extension schemas handle the integration surface",
"The pattern is self-documenting: ADR-001 (ontoref from stratumiops) and ADR-016 instances (lian-build, cloudatasave from provisioning) form a corpus of precedent",
"Ontoref registration before code move ensures the project has identity before it has implementation",
],
negative = [
"Extraction requires a deliberate severing of host infrastructure imports — this is work that must be done correctly, not deferred",
"Two registration surfaces per project: the extracted project's own .ontoref/ and the host's extension schemas",
"Consumer projects (workspace infras) must import from the extracted project's schema vocabulary, not from the host's workspace schema",
],
},
alternatives_considered = [
{
option = "Extract only when a second consumer exists and requests it",
why_rejected = "Reactive extraction means coupling has already accreted. By the time a second consumer requests the component, host infrastructure imports are deep. Proactive evaluation at the four-criterion gate is cheaper than reactive untangling.",
},
{
option = "Keep all tools inside provisioning as internal workspace crates, expose via provisioning CLI",
why_rejected = "This routes all callers through provisioning's release cycle and binary. vapora and workspace CI pipelines cannot use the tool without depending on provisioning. The four-criterion test exists precisely to identify when this constraint becomes incorrect.",
},
{
option = "Publish extracted crates to crates.io immediately",
why_rejected = "Published crates require stable API contracts before the consumer relationship is proven. Path-based standalone project references allow simultaneous development of the extracted project and its consumers without publication ceremony.",
},
],
constraints = [
{
id = "ontology-before-code",
claim = "An extracted project must have .ontology/core.ncl, .ontology/state.ncl, and adrs/adr-001 committed before any code from the host is moved",
scope = "any project applying the lift-out pattern",
severity = 'Hard,
check = {
tag = 'FileExists,
path = ".ontology/core.ncl",
present = true,
},
rationale = "Ontological identity precedes implementation. A project that exists only as code has no declared purpose, axioms, or constraints — the extraction is indistinguishable from a rename.",
},
{
id = "no-host-infrastructure-import",
claim = "The extracted project must not import workspace crates, config loaders, or schema registries from the host project",
scope = "extracted project source code",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "platform_config|stratum-config|provisioning-config",
paths = ["src/", "crates/"],
must_be_empty = true,
},
rationale = "Importing host infrastructure is the definition of host coupling. If the import cannot be severed, the component fails criterion 4 and must not be extracted until the coupling is resolved.",
},
],
related_adrs = ["adr-001-protocol-as-standalone-project"],
ontology_check = {
decision_string = "components are extracted as standalone peer projects when they pass a four-criterion gate; ontoref registration precedes code move; host retains extension-side schemas",
invariants_at_risk = ["protocol-not-runtime", "self-describing"],
verdict = 'Safe,
},
}

View file

@ -1,217 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-017",
title = "Registry Credential Vault Model: src-vault, Multi-Recipient sops, and Actor-Scoped Access",
status = 'Accepted,
date = "2026-05-01",
context = "The provisioning registry (zot OCI) became the primary coordination hub for domain and mode artifacts (ADR-016 consequences, migration 0015). This introduced registry credentials as a first-class concern: every project that pushes or pulls artifacts needs credentials, and those credentials must be distributed across actors (developers, CI pipelines, the ontoref daemon, AI agents, ops tooling) with different access levels. The naive model — environment variables or ambient ~/.docker/config.json — has three compounding failure modes: (1) credentials are ambient and unscoped, so an operation against project A can use credentials that belong to project B; (2) there is no distribution mechanism — adding a new team member or CI key requires manual redistribution of a shared secret; (3) there is no audit trail for credential access or changes. A supply-chain attack via misconfigured registry namespace endpoints is the concrete risk: if the daemon proxied registry calls, any actor with MCP access could redirect them to an arbitrary endpoint using ambient credentials.",
decision = "Registry credentials are managed through a per-project src-vault stored as an OCI artifact in the same ZOT registry it protects. The src-vault uses sops with age multi-recipient encryption: each actor role has its own age keypair, and credential files are encrypted for the exact set of recipients that need access. The vault backend (restic or kopia) handles versioning and local copies; the ZOT registry handles distribution. Each project has a local access.sops.yaml in ~/.config/ontoref/vaults/<project-slug>/ containing three fields: zot_username, zot_password, and vault_key. All three are encrypted by the actor's master age private key (.kage), which lives at an external path the actor controls (hardware key, encrypted disk, or declared path in config.ncl) — never inside the vault directory. At operation time, sops decrypts access.sops.yaml in memory: zot_username and zot_password are used to pull the src-vault OCI artifact via a DOCKER_CONFIG tmpdir that is deleted immediately after; vault_key is passed as RESTIC_PASSWORD or KOPIA_PASSWORD env var for the duration of the vault operation and never written to disk. No plaintext credential of any kind persists beyond the operation scope. Credential resolution runs exclusively in the ontoref CLI — the daemon is structurally excluded. All oras invocations use an isolated DOCKER_CONFIG tmpdir; no ambient ~/.docker/config.json is consulted. Access logs are appended to a jsonl file stored as a layer in the same src-vault OCI artifact and mirrored locally in ~/.config/ontoref/vaults/<project-slug>/logs/access.jsonl.",
rationale = [
{
claim = "src-vault in ZOT closes the distribution gap structurally",
detail = "Storing the vault as an OCI artifact in the same registry that protects means distribution is the same operation as registry access. Any actor with registry RO credentials can pull the vault; only admin can push. No separate key distribution channel is needed. When a developer joins, their public key is added to src-vault and they can immediately pull their copy — no coordinator required.",
},
{
claim = "Multi-recipient sops eliminates shared secrets",
detail = "Each actor has its own age keypair. The sops DEK is encrypted separately for each recipient public key. Removing a recipient requires only updating .sops.yaml and running sops updatekeys — no key redistribution. The private key of the removed actor becomes useless for future vault operations the moment updatekeys completes. This is the only model that supports revocation without rotation of all other credentials.",
},
{
claim = "Daemon structural exclusion is not a policy, it is an architectural property",
detail = "The daemon process has no age private key and no access to project .kage files. It cannot decrypt sops files even if it reads them. The daemon surfaces only the declarative topology from manifest.ncl (registry_provides, credential_sops paths as strings). Credential resolution happens in the CLI process of the authenticated user. This cannot be bypassed without modifying the daemon to acquire keys — which would be an explicit, reviewable change, not a configuration drift.",
},
{
claim = "Lock state in ZOT with admin-only write ACL makes the lock structural",
detail = "The src-vault namespace has write ACL restricted to admin credentials in ZOT config. The lock artifact (src-vault/<project>:lock) can only be pushed or deleted with admin credentials. Other roles can read the lock state but cannot modify it. This converts a cooperative lock into a structurally enforced one — bypassing the lock requires admin credentials, which is an auditable action, not an accident.",
},
{
claim = "Audit log co-located with vault enables tamper-evident access history",
detail = "The audit.jsonl layer is part of the same OCI artifact as the vault content. Every push of src-vault:<project>:latest includes the updated log. Since only admin can push, and the log is append-only within a vault session, the log cannot be modified without admin credentials and a vault open/close cycle — both of which are themselves logged. This does not prevent a malicious admin from editing the log, but it makes any edit detectable via the OCI manifest digest history.",
},
{
claim = "Role scope enforcement at two independent layers",
detail = "Scope is enforced first by the CLI (pre-check against scopes/<role>.ncl before calling oras) and second by the ZOT ACL (the token itself carries the permissions issued by the registry). These layers are independent: a misconfigured scope file does not grant registry permissions, and a misconfigured registry ACL does not bypass the CLI pre-check. The two layers must be kept in sync via secrets-audit in CI.",
},
{
claim = "credential_env is excluded by design, not by convention",
detail = "Environment variables are visible in ps aux, inherited by all child processes, logged by CI systems unless explicitly masked, and cannot be scoped to a specific operation. Excluding credential_env from the schema means it is impossible to accidentally use it — there is no field to populate. The only credential reference fields are credential_sops (current) and credential_oidc (declared for future OIDC workload identity support).",
},
{
claim = "vault_key in sops ensures the restic/kopia key is never plaintext at rest",
detail = "The restic/kopia encryption key (vault_key) lives inside access.sops.yaml, encrypted by the actor's age master key. It is decrypted into RESTIC_PASSWORD or KOPIA_PASSWORD env var only for the duration of the vault operation — never written to a key file on disk. This means the only plaintext secrets on disk are the actor's .kage (which the actor is responsible for protecting) and the sops-encrypted access.sops.yaml (unreadable without the .kage). The pattern is: one master key protects everything else; everything else is encrypted at rest.",
},
{
claim = "restic and kopia are equivalent vault backends behind a thin abstraction",
detail = "Both restic and kopia provide encrypted, versioned snapshots with content-addressed storage. The choice between them depends on operational preference (kopia has a richer UI and faster incremental backups; restic has broader ecosystem support). The vault-backend.nu abstraction wraps init, backup, restore, and snapshot-list — switching backends requires changing vault_backend.tool in config.ncl and re-initializing the local repo. The OCI artifact in ZOT is backend-agnostic.",
},
],
consequences = {
positive = [
"Credential distribution is the same operation as registry access — no separate channel",
"Revoking a developer's access requires one command (secrets-remove-key) with no coordination",
"The daemon cannot be used as a credential amplifier regardless of MCP actor permissions",
"Every vault open, edit, and close is logged in the artifact that contains the credentials",
"Adding a new role (e.g. backup-agent) requires only a new keypair and a .sops.yaml update",
"Local copy in ~/.config/ontoref/vaults/<slug>/ provides offline fallback for admin",
"Bootstrap creates the first consistent vault state before any live registry interaction",
"impact analysis on secrets-close identifies services affected by credential changes before confirming",
],
negative = [
"Bootstrap requires admin to collect all public keys before first vault push — cannot be fully automated",
"sops updatekeys must run after every recipient change — forgetting it leaves old recipients in files",
"Zot ACL and scope NCL files must be kept in sync manually — drift produces security theater",
"Loss of .kage means access.sops.yaml cannot be decrypted — vault_key and ZOT credentials are unrecoverable without the master key; admin must keep the .kage backed up independently",
"ore secrets open/close adds a ritual to any secrets change session — acceptable overhead, but real",
"Direct sops invocations bypass the lock and audit log — CI audit detects but does not prevent",
],
},
alternatives_considered = [
{
option = "Environment variables (REGISTRY_TOKEN, DOCKER_CONFIG) per CI job",
why_rejected = "Visible in process list, inherited by subprocesses, logged by CI systems, cannot be scoped to a specific registry endpoint. No revocation without rotating all consumers. The primary attack surface for supply-chain credential leakage.",
},
{
option = "Shared age keyring — one private key distributed to all developers",
why_rejected = "Revocation requires rotating the shared key and redistributing to all remaining members — coordination cost scales with team size. No per-actor audit trail. A leaked key compromises all actors simultaneously.",
},
{
option = "HashiCorp Vault or similar external secrets manager",
why_rejected = "Introduces a network dependency for every credential resolution. Requires operating a separate service with its own HA, backup, and auth model. The OCI registry is already the coordination hub — using it as the vault distribution backend reuses existing infrastructure and auth.",
},
{
option = "Daemon resolves credentials on behalf of CLI actors",
why_rejected = "The daemon is a long-lived process accessible to multiple actors (developer, agent, CI via MCP). Giving it credential resolution capability means any actor with daemon access can trigger registry operations using credentials they do not personally hold. The structural exclusion of the daemon from credential resolution is a load-bearing architectural property.",
},
{
option = "Single credential file per registry (no RO/RW split)",
why_rejected = "A single credential with RW access distributed to read-only actors (cdci, ontoref, agent) violates least-privilege. A compromised CI pipeline with RW credentials can push malicious artifacts. The RO/RW split means a compromised read-only credential cannot alter the artifact namespace.",
},
],
constraints = [
{
id = "vault-access-credentials-independent",
claim = "The credentials required to pull the src-vault OCI artifact from ZOT must be stored outside the vault itself — each src-vault has its own access credentials, held in the actor's local .kage, not inside any vault it protects",
scope = "all projects with src-vault in ZOT",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore secrets audit --check bootstrap-credentials",
},
rationale = "A vault whose access credential is inside itself cannot be opened. The bootstrap credential (registry RO for the src-vault namespace) must preexist in the actor's local .kage. Two projects on the same ZOT instance have independent src-vault namespaces with independent access credentials — access to one does not imply access to the other.",
},
{
id = "no-credential-env",
claim = "RegistryEntry must not use credential_env — only credential_sops or credential_oidc are valid credential reference fields",
scope = "all manifest.ncl files declaring registry_provides",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "credential_env",
paths = [".ontology/manifest.ncl"],
must_be_empty = true,
},
rationale = "credential_env is excluded from the schema. Its presence indicates a manual edit bypassing the contract.",
},
{
id = "multi-recipient-mandatory",
claim = "Every *.sops.yaml credential file must include at minimum the admin and the bound_actor recipients for its access class",
scope = "all projects with registry_provides.registries[].credential_sops declared",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore secrets audit --check recipients",
},
rationale = "A credential file encrypted for a single recipient is indistinguishable from a shared secret. Multi-recipient is the mechanism that enables revocation without rotation.",
},
{
id = "uses-registry-declared",
claim = "Any domain or mode that pushes or pulls from a registry must declare uses_registry referencing the RegistryEntry id in manifest.ncl",
scope = "domain and mode NCL declarations",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore secrets audit --check registry-deps",
},
rationale = "Without the explicit dependency declaration, secrets-close cannot compute the impact of credential changes. Undeclared dependencies produce silent breakage after a credential rotation.",
},
{
id = "ore-secrets-exclusive-wrapper",
claim = "All sops operations on registry credential files must go through ore secrets — direct sops invocations bypass lock state and audit log",
scope = "CI pipeline and developer workflow",
severity = 'Soft,
check = {
tag = 'NuCmd,
cmd = "ore secrets audit --check lock-compliance",
},
rationale = "ore secrets enforces lock state verification before any sops edit. Direct sops invocations are detectable in CI audit but not preventable at the filesystem level. The constraint is soft because enforcement is post-hoc.",
},
{
id = "docker-config-isolation",
claim = "Every oras invocation must use DOCKER_CONFIG pointing to a tmpdir containing only the credential for the target registry endpoint — no ambient ~/.docker/config.json",
scope = "nulib/cli/integration.nu and any code calling oras",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "DOCKER_CONFIG",
paths = ["provisioning/core/nulib/"],
must_be_empty = false,
},
rationale = "Ambient credentials allow operations against project A to use credentials that belong to project B if both resolve to the same registry hostname. Isolation is enforced by constructing the config at call time and deleting it immediately after.",
},
{
id = "vault-key-never-plaintext",
claim = "vault_key must never be written to disk in plaintext — it is decrypted from access.sops.yaml into RESTIC_PASSWORD or KOPIA_PASSWORD for the duration of the operation only",
scope = "nulib/platform/vault-backend.nu and all ore secrets callers",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "vault_key.*save\\|vault_key.*write\\|restic.*--key-file",
paths = ["provisioning/core/nulib/"],
must_be_empty = true,
},
rationale = "Writing vault_key to a file defeats the purpose of encrypting it in sops. The env var approach (RESTIC_PASSWORD, KOPIA_PASSWORD) keeps the key in process memory only. The .kage master key is the single plaintext secret the actor manages — everything derived from it must be ephemeral.",
},
{
id = "vault-backend-abstracted",
claim = "All vault snapshot operations must use vault-backend.nu — direct restic or kopia invocations are not permitted in recipes or modules",
scope = "justfiles/secrets.just and nulib/platform/vault-backend.nu callers",
severity = 'Soft,
check = {
tag = 'Grep,
pattern = "^\\s*restic\\|^\\s*kopia",
paths = ["justfiles/", "provisioning/core/nulib/"],
must_be_empty = true,
},
rationale = "Switching between restic and kopia must require only changing vault_backend.tool in config — not hunting direct invocations across recipes and modules.",
},
{
id = "src-vault-cosign-signed",
claim = "Every push of src-vault/<project>:latest to ZOT must produce a cosign signature; every pull must verify the signature before the artifact is trusted",
scope = "justfiles/secrets.just secrets-push and secrets-sync recipes",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore secrets audit --check cosign-signature",
},
rationale = "The src-vault artifact contains access.sops.yaml with encrypted registry credentials. An unsigned artifact can be substituted without detection — a malicious actor with write access to the registry could replace it with a version encrypted for attacker-controlled recipients. COSIGN provides tamper evidence independent of ZOT ACLs: even if ACLs are misconfigured, the signature check prevents a substituted vault from being consumed. cosign is now a Hard prerequisite for ontoref (declared in requirements) for this reason.",
},
],
related_adrs = [
"adr-005-unified-auth-session-model",
"adr-012-domain-extension-system",
"adr-016-component-lift-out-pattern",
],
ontology_check = {
decision_string = "registry credentials are managed via per-project sops multi-recipient vaults stored as OCI artifacts in ZOT; the daemon is structurally excluded from credential resolution; actor-role binding is declared in project.ncl; vault backend is restic or kopia behind a thin abstraction; access logs are co-located with the vault artifact",
invariants_at_risk = ["protocol-not-runtime", "no-enforcement"],
verdict = 'Safe,
},
}

View file

@ -1,148 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-018",
title = "Level Hierarchy and Mode Resolution Strategy — Observable Boundary Traversal",
status = 'Accepted,
date = "2026-05-01",
context = "ADR-012 introduced a domain extension system that enables project-specific specialization of ontoref. In practice this created a three-level hierarchy: (1) ontoref base — generic protocol and reflection operations; (2) project domain — specialization of ontoref for a specific project type (provisioning, personal, ...); (3) domain instance — a concrete project derived from a domain (a workspace, an infra, a team ontoref). The hierarchy was not formalized: no level declares its identity, no mode declares whether its implementation is complete or delegates to the level above, and no mechanism exists to determine which level answered a given operation. The result is implicit traversal — a caller invoking 'build-docs' has no way to know whether the operation ran at level 1, 2, or 3, whether it merged contributions from multiple levels, or whether it silently fell through to the base because the domain had not implemented it yet. The discussion that produced this ADR also identified that resolution strategies cannot be uniform: the correct strategy depends on the mode, the project context, and the adoption phase. A domain in early adoption may Delegate all documentation modes to the base; the same domain at maturity Overrides them with project-specific implementations. The transition between strategies must itself be observable — crossing a level boundary is an architectural event, not an implementation detail.",
decision = "Formalize the three-level hierarchy with four mechanisms: (1) Level identity declaration — each ontoref instance declares level.index (1=base, 2=domain, 3=instance), level.name, and level.parent (name of the level above; absent at level 1) in manifest.ncl. This makes level identity explicit and queryable. (2) Per-mode resolution strategy — each reflection mode declares a strategy field using one of four values: 'Override (implementation is complete at this level, traversal stops), 'Delegate (no implementation here, traverse to parent), 'Merge (accumulate fields bottom-up across all levels; lower level wins on conflicts), 'Compose (declare explicit partial inheritance via an extends field naming specific steps or fields to inherit from the parent). Strategy is required at level 2+; absent at level 1 (base is always Override by definition). Implicit absence at level 2+ is treated as 'Delegate with a Soft validation warning. (3) FSM-bound strategy transitions — when a mode's strategy is expected to change as the project matures, declare a state dimension in state.ncl whose current_state tracks the strategy value. The transition from 'Delegate to 'Override (or any other pair) is then an observable FSM event: tracked in state.ncl, visible in ore describe state, and triggerable by the same transition conditions as any other dimension. Strategy changes are architectural events, not silent refactors. (4) Observable traversal via ore mode resolve — the command 'ore mode resolve <id>' reports which level answered the operation, the strategy applied, the source file, and the reason (declared strategy or FSM state). No mode resolution is ever silent.",
rationale = [
{
claim = "Implicit traversal makes level boundaries invisible and coupling complaints undiagnosable",
detail = "The coupling discussions that preceded this ADR (provisioning workspace management appearing coupled to ontoref, credential management appearing to require ontoref) were symptoms of invisible level traversal. When it is impossible to determine whether an operation ran at level 1 or 2, it is also impossible to determine whether the coupling is load-bearing (level 2 deliberately uses level 1) or accidental (level 2 forgot to implement something and fell through). Explicit level declaration and strategy declaration make this distinction mechanically checkable.",
},
{
claim = "Per-mode strategy is the correct granularity — global strategy per level does not hold",
detail = "A domain in active use may Override its core modes (workspace management, infra deploy) while still Delegating peripheral modes (documentation generation, ADR validation) to the base. A global strategy per level would force a false choice: either all modes are overridden (blocking base adoption) or all modes delegate (making the domain invisible). Per-mode strategy reflects the actual adoption curve: domains specialize incrementally, not all at once.",
},
{
claim = "FSM-bound transitions make strategy changes architectural events",
detail = "Without FSM binding, a mode moving from 'Delegate to 'Override is a silent code change with no recorded rationale. With FSM binding, the transition requires updating current_state in state.ncl, which is a deliberate action visible to ore describe state, tracked in git history, and subject to the same transition conditions (catalyst, blocker) as any other dimension. The architectural significance of 'we now own this mode' is formally recorded.",
},
{
claim = "'Compose is necessary for surgical specialization that preserves base infrastructure",
detail = "Merge and Override are extremes: Merge accumulates everything (field-level conflicts resolved by lower level winning), Override discards everything from above. Compose covers the real case: a domain wants to keep the base's generate-api step and the base's lint-docs step, but replace the publish step with a domain-specific one. Without Compose, the domain must either fully override (losing base infrastructure it wants) or Merge (risking unintended inheritance of base steps it does not want).",
},
{
claim = "Level parent as name reference decouples identity from location",
detail = "parent = 'ontoref-base' in a domain's level declaration is a name reference, not a file path or OCI artifact coordinate. The resolution of that name to a concrete implementation is context-dependent: local checkout for development, OCI artifact for distributed domains. The ADR defines the declaration contract; resolution is implementation-dependent and may itself evolve (ADR-010 migration system handles protocol evolution).",
},
{
claim = "ore mode resolve makes the system self-describing at the operational layer",
detail = "ontoref's core identity (ADR-001, core.ncl) is self-describing: it consumes its own protocol. ore mode resolve extends this to the operational layer: the resolution mechanism is itself queryable. An agent or developer can always answer 'which level is handling this mode right now and why' without reading source code. This is the concrete expression of the 'always know where you are and when you cross the frontier' requirement.",
},
],
consequences = {
positive = [
"Level identity is always queryable — ore describe project shows level.index, level.name, level.parent",
"Mode resolution is always observable — ore mode resolve <id> shows level, strategy, source, reason",
"Strategy changes are FSM events — visible in git, queryable in state, subject to transition conditions",
"Delegate chains that break (no Override anywhere in the parent chain) are caught by ore validate modes",
"Phased domain adoption is formally supported — early phases Delegate, mature phases Override",
"Coupling complaints become diagnosable — is provisioning using ontoref base because it Delegates (load-bearing) or because it forgot to Override (accidental)?",
"Compose enables surgical specialization without full Override — base infrastructure is reusable explicitly",
],
negative = [
"All existing modes at level 2+ require strategy field addition — migration 0017 needed",
"ore mode resolve is not yet implemented — constraint delegate-chain-complete cannot be checked until it is",
"ore validate modes is not yet implemented — constraints are declared but not executable at ADR acceptance",
"Compose requires extends field with precise step/field references — mismatches produce runtime errors, not schema errors",
"FSM dimensions for strategy transitions add state.ncl surface area — domains with many modes in transition accumulate many dimensions",
],
},
alternatives_considered = [
{
option = "Implicit inheritance — current state, no declaration required",
why_rejected = "Makes level traversal invisible. Coupling is undiagnosable. Adoption phase is unknown. Boundary crossing is unobservable. This is the state that produced the architectural confusion this ADR resolves.",
},
{
option = "Global strategy per level — one strategy applies to all modes at a given level",
why_rejected = "Domains specialize incrementally. A global Override for level 2 blocks early adoption (all modes must be implemented before any work). A global Delegate for level 2 makes domains invisible (nothing is ever implemented). Per-mode strategy reflects the real adoption curve.",
},
{
option = "Override-only — levels either implement fully or do not appear",
why_rejected = "Eliminates phased adoption. A domain cannot exist in the system until it has implemented every mode — a steep entry cost that contradicts ADR-001 (voluntary coherence, no enforcement). Delegate and Compose are specifically needed for incremental adoption.",
},
{
option = "Separate level.ncl file instead of level field in manifest.ncl",
why_rejected = "manifest.ncl already holds structural self-description (requirements, capabilities, registry topology, layers). Level identity is structural metadata of the same kind. A separate file adds coordination overhead (two files to keep in sync, two files for describe to read) for no additional expressiveness.",
},
],
constraints = [
{
id = "level-declared-at-domain",
claim = "Any ontoref instance at level 2 or 3 must declare level.index, level.name, and level.parent in manifest.ncl",
scope = "all manifest.ncl files in domain and instance projects",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore validate modes --check level-declared",
},
rationale = "Level identity is the precondition for all other mechanisms in this ADR. An undeclared level cannot participate in observable traversal or FSM-bound strategy transitions.",
},
{
id = "strategy-declared-at-domain",
claim = "Every reflection mode at level 2+ must declare strategy explicitly; implicit absence is a Soft violation",
scope = "reflection/modes/*.ncl in domain and instance projects",
severity = 'Soft,
check = {
tag = 'NuCmd,
cmd = "ore validate modes --check strategy-declared",
},
rationale = "Implicit Delegate (mode exists at level 2 without strategy field) is functionally equivalent to explicit Delegate but invisible. The Soft severity allows gradual adoption — existing modes that predate this ADR are not hard-blocked, but the warning drives migration.",
},
{
id = "delegate-chain-complete",
claim = "No Delegate chain may terminate without an Override at some level — every mode invocation must resolve to a concrete implementation",
scope = "all level 2 and 3 modes with strategy = 'Delegate",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore validate modes --check delegate-chain",
},
rationale = "A Delegate chain that ends without an Override means the operation has no implementation — invoking it produces a silent no-op or an opaque error. This is the failure mode that makes implicit traversal dangerous.",
},
{
id = "strategy-state-coherent",
claim = "If state.ncl declares a dimension for a mode's strategy, its current_state must match the strategy field declared in the mode NCL",
scope = "state.ncl dimensions whose id matches the pattern '<mode-id>-strategy'",
severity = 'Soft,
check = {
tag = 'NuCmd,
cmd = "ore validate modes --check strategy-state",
},
rationale = "Strategy drift between the FSM state and the mode declaration means the state dimension has become decorative — it records an intention that is no longer reflected in the actual implementation. Soft severity allows the transition to be in-flight (state says Override, code not yet complete).",
},
{
id = "compose-extends-valid",
claim = "A mode with strategy = 'Compose must declare an extends field; every step or field reference in extends must exist in the named parent mode",
scope = "reflection/modes/*.ncl with strategy = 'Compose",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore validate modes --check compose-extends",
},
rationale = "A Compose mode with a broken extends reference silently inherits nothing from the parent — equivalent to an unintentional Override. Since Compose is specifically chosen to preserve parent infrastructure, a broken reference is always a bug.",
},
],
related_adrs = [
"adr-001-protocol-as-standalone-project",
"adr-010-protocol-migration-system",
"adr-011-mode-guards-and-convergence",
"adr-012-domain-extension-system",
],
ontology_check = {
decision_string = "three-level hierarchy (base, domain, instance) with per-mode resolution strategy (Override, Delegate, Merge, Compose); strategy transitions are FSM events in state.ncl; ore mode resolve makes traversal observable; ore validate modes enforces chain completeness",
invariants_at_risk = ["protocol-not-runtime", "no-enforcement"],
verdict = 'Safe,
},
}

View file

@ -1,151 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-019",
title = "Per-File Recipient Routing for Tenant Isolation in lieu of Multi-Vault",
status = 'Accepted,
date = "2026-05-03",
context = "ADR-017 established per-project credential vaults as OCI artifacts in ZOT, encrypted with sops + age multi-recipient. The model held one recipient set per vault: every actor who could decrypt access.sops.yaml could decrypt every credential file inside the vault. Real projects (libre-wuji, the canonical multi-tenant example) need stronger separation — a single project hosts multiple clients with distinct services, AI agents that operate with restricted access, and developer/admin roles that occasionally overlap. Three concrete failure modes appear: (1) credential-of-clientB is visible to anyone who can open the vault even when only working on clientA — recipient lists are coarse and indiscriminate; (2) AI agents with read-only intent receive credentials whose blast radius exceeds their declared scope; (3) blast-radius limitation requires per-file recipient sets, which the original single-set model does not express. A naive answer is multi-vault — one vault_id per tenant or per environment — but it cascades through every layer (schema, helpers, recipes, dispatcher, migration) and creates an architectural debt without justifying a real isolation requirement (separate master keys, separate restic repos) for the typical case.",
decision = "Tenant isolation within a single project is expressed via sops creation_rules, declared in project.ncl::sops as recipient_groups (named lists of age public keys) and recipient_rules (path_regex to group-union mappings). The bootstrap recipe generates <vault_dir>/.sops.yaml from these declarations and sops natively encrypts each file with the union of declared groups. One vault_id per project remains the unit. Multi-vault is explicitly NOT implemented and remains out of scope until a project requires HARD isolation (separate master keys, separate restic repos, compliance-grade separation surviving accidental cross-decryption); such a case requires a future ADR. When recipient_rules are declared, project.ncl is the single source of truth: secrets-add-key and secrets-remove-key error out and direct the operator to edit project.ncl plus run secrets-rekey, which regenerates .sops.yaml and re-encrypts every *.sops.yaml file. Three adoption templates (single-team, multi-tenant, agent-first) ship in install/resources/templates/sops/ as copy-paste starting points; a project may adopt any pattern or none.",
rationale = [
{
claim = "Use sops creation_rules natively — do not invent a parallel routing layer",
detail = "sops already provides per-file recipient routing via creation_rules in .sops.yaml. Using it directly inherits all of sops' tooling (updatekeys, --decrypt with .sops.yaml discovery, --filename-override) and avoids a competing convention that would double the surface and break composability with sops itself.",
},
{
claim = "Preserve the single-vault structural invariants of ADR-017",
detail = "Single vault_id, single OCI artifact, single restic repo, single lock, single dispatcher subcommand surface. The schema delta is two optional fields (recipient_groups + recipient_rules). Most code paths require zero modification — additivity over existing helpers is the migration story for projects already on ADR-017.",
},
{
claim = "Project.ncl is the single source of truth for recipient sets",
detail = "Direct sops mutations via secrets-add-key and secrets-remove-key are forbidden in declarative mode (recipes error explicitly). The reason: any direct mutation diverges from project.ncl, and the next secrets-rekey would silently revert. Forcing edits through git via project.ncl makes recipient changes auditable and reproducible across machines.",
},
{
claim = "Honest trade-off: per-file routing protects against accidental cross-decryption, not against encrypted-byte visibility",
detail = "ClientA's lead with their .kage cannot decrypt clientB-*.sops.yaml files — sops rejects decryption when the recipient set excludes the actor. But all encrypted files are layers in the same OCI artifact; clientA SEES that clientB-* files exist. For HARD isolation (separate master keys, separate restic repos, compliance-grade separation surviving accidental cross-decryption), multi-vault remains the future option — a separate ADR captures that work when a real case requires it.",
},
{
claim = "Three adoption templates anchor common patterns without forcing them",
detail = "single-team, multi-tenant, and agent-first templates ship in install/resources/templates/sops/ as copy-paste starting points. The schema fields are optional with sensible defaults — a project may adopt any pattern, mix them, or skip templates entirely while still being a valid consumer of ADR-017 + ADR-019.",
},
],
consequences = {
positive = [
"Tenant isolation in a single vault, single master key, single OCI artifact — adoption cost minimal.",
"Compatible with all existing ADR-017 enforcement (assert-actor-authorized, assert-target-in-scope, vault lock, impact analysis).",
"Migration from legacy single-set mode is additive: existing projects keep working, new fields opt them into per-file routing.",
"Defense in depth: actor scope (ops + namespaces) + recipient routing — both must permit an operation.",
],
negative = [
"Operators must understand sops creation_rules ordering (first match wins) — surfacing rule conflicts requires care.",
"secrets-add-key and secrets-remove-key behavior diverges between legacy and declarative modes, introducing a mode-aware UX.",
"Cross-tenant visibility of encrypted file paths in the OCI manifest — operationally clientB knows clientA exists in the vault.",
],
},
alternatives_considered = [
{
option = "Multi-vault: project.ncl::sops.vault_id (single string) becomes sops.vaults (record of named SopsConfigs)",
why_rejected ="Cascades through every layer (schema, helpers, recipes, dispatcher), forcing a 12-component refactor and a migration for every existing project to express what one optional schema field accomplishes via sops creation_rules. The HARD isolation it provides (separate master keys, separate restic repos) is rarely required; per-file routing covers the common case while leaving the door open for a future multi-vault ADR if a project genuinely needs filesystem-level separation.",
},
{
option = "Single recipient set + role-based decryption gating in helper code",
why_rejected ="Would require a custom layer over sops and reinvent recipient routing. sops already does it natively via creation_rules. Inventing a parallel mechanism doubles the surface and breaks composition with sops tooling (sops --decrypt, updatekeys).",
},
{
option = "Externalize tenant credentials to an external secret manager (e.g. HashiCorp Vault)",
why_rejected ="Adds an external runtime dependency that contradicts the ADR-017 invariant of self-contained, distribution-via-OCI credentials. Reasonable for projects that already operate such a system, but inappropriate as the default ontoref pattern.",
},
],
constraints = [
{
id = "rules-imply-groups-defined",
claim = "Every group referenced in recipient_rules must be declared in recipient_groups",
scope = "all projects with sops.recipient_rules non-empty",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore secrets audit --check recipient-routing-coherent",
},
rationale = "An undeclared group resolves to an empty recipient list, producing files encrypted to nobody. Catch at audit time before push.",
},
{
id = "no-empty-group-on-active-rule",
claim = "A rule whose group union resolves to zero recipients is rejected at bootstrap and rekey",
scope = "all projects with sops.recipient_rules non-empty",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore secrets audit --check recipient-routing-coherent",
},
rationale = "Encrypting to zero recipients silently produces an unrecoverable file. Reject at the source rather than waiting for sops to fail at use time.",
},
{
id = "declarative-mode-locks-direct-mutations",
claim = "secrets-add-key and secrets-remove-key recipes must error when project declares recipient_rules; canonical workflow is edit project.ncl + secrets-rekey",
scope = "all projects with sops.recipient_rules non-empty",
severity = 'Hard,
check = {
tag = 'Grep,
paths = ["justfiles/secrets.just"],
pattern = "HAS_RULES.*declarative",
must_be_empty = false,
},
rationale = "Direct sops mutations would diverge from project.ncl, and the next rekey would silently revert them. Forcing the rekey path keeps git as the single source of truth.",
},
{
id = "every-vault-file-matches-a-rule",
claim = "Every *.sops.yaml under <vault_dir>/ must match at least one declared rule when recipient_rules is non-empty",
scope = "all projects with sops.recipient_rules non-empty",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "ore secrets audit --check recipient-routing-coverage",
},
rationale = "sops fails encryption with 'no matching creation rules found' for unmatched paths. Catch the mismatch at audit time, surface which path needs a rule (or which rule needs broadening).",
},
{
id = "multi-vault-not-implemented",
claim = "Projects must not declare a multi-vault structure (e.g. sops.vaults map). Multi-vault adoption requires a new ADR superseding or extending this one",
scope = "all projects with .ontoref/project.ncl",
severity = 'Hard,
check = {
tag = 'Grep,
pattern = "sops.vaults *=",
paths = [".ontoref/project.ncl"],
must_be_empty = true,
},
rationale = "Premature multi-vault implementation without a justifying use case generates schema and helper debt across 12+ components. The constraint is structural: any project that hits a real HARD-isolation requirement must capture it in a new ADR before adopting multi-vault.",
},
{
id = "templates-discoverable",
claim = "Three adoption templates (single-team, multi-tenant, agent-first) live under install/resources/templates/sops/ and are referenced by qa.ncl::credential-vault-templates",
scope = "ontoref protocol layer",
severity = 'Soft,
check = {
tag = 'FileExists,
paths = [
"install/resources/templates/sops/single-team/project.ncl.snippet",
"install/resources/templates/sops/multi-tenant/project.ncl.snippet",
"install/resources/templates/sops/agent-first/project.ncl.snippet",
],
},
rationale = "Without templates, adoption requires reading the schema, sops docs, and the FAQ — too high a friction. Templates are not mandatory but their absence is a soft signal of protocol decay.",
},
],
related_adrs = [
"adr-017-registry-credential-vault-model",
"adr-015-mcp-tool-inventory-auto-derive",
],
ontology_check = {
decision_string = "tenant isolation within a single credential vault is expressed via sops creation_rules driven by project.ncl::sops.recipient_groups + recipient_rules; multi-vault is explicitly out of scope; project.ncl is the single source of truth for recipient sets and direct sops mutations are forbidden in declarative mode",
invariants_at_risk = ["protocol-not-runtime"],
verdict = 'Safe,
},
}

View file

@ -1,221 +0,0 @@
let d = import "defaults.ncl" in
d.make_adr {
id = "adr-020",
title = "Three-Layer Model for Project Ontoref Instances",
status = 'Proposed,
date = "2026-05-03",
context = m%"
Multiple projects now adopt the ontoref protocol (lian-build is the explicit
external case under bl-002 / bl-008). Field experience across ontoref +
lian-build + provisioning shows that an ontoref-onboarded project's
repository carries TWO distinct ontoref-shaped layers, while a third layer
exists outside the project (in caller repositories). Without codification,
adopters re-derive the model per project, mix layers accidentally, and lose
boundaries silently:
- Layer 3 content (caller-side cabling) drifts into a project's qa.ncl,
making the FAQ a how-to-deploy guide for one specific caller.
- Layer 2 schemas live under reflection/ as 'project notes', drifting
from the binary because they're not on the contract path.
- Layer 1 architectural rationale ends up in catalog/domains/<id>/
contract.ncl, where consumers expecting a typed shape get prose.
The model has been observed (lian-build/reflection/qa.ncl::lian-build-what-
and-why), described (ontoref/reflection/qa.ncl::ontoref-three-layer-model),
and queued for codification (bl-009). This ADR commits the model with
machine-checkable constraints. Acceptance is gated on the constraints
running clean across the existing ontoref-onboarded projects (ontoref
itself, lian-build, provisioning).
"%,
decision = m%"
A project's ontoref instance has THREE distinct layers — but only the first
two live in the project's own repository:
LAYER 1 — Self-management ontoref (about the project itself)
paths .ontology/ reflection/ adrs/
audience this project's developers and maintainers
purpose describe the project to itself — axioms, FSM dimensions,
binding decisions, open questions, accepted knowledge
presence MANDATORY on every ontoref-onboarded project
LAYER 2 — Specialized domain/mode ontoref (the integration surface)
paths schemas/ catalog/{domains,modes}/
manifest.ncl::registry_provides
audience OTHER projects that want to integrate this project
purpose the contract surface other projects bind to — typed domain
artifacts, orchestration mode artifacts, registry-namespace
claim
presence OPTIONAL but BICONDITIONAL — a project either has all of
{schemas/, catalog/, registry_provides} or none. Half-Layer-2
is a contract violation.
LAYER 3 — Caller-side implementations (NOT in this project)
paths <caller>/extensions/<this-project>/
<caller>/catalog/components/<...>/ (when consuming)
<workspace>/infra/<ws>/integrations/
audience operators and CI of caller projects
presence PER CALLER, NEVER in this project's repo. Cross-references
from this project to Layer 3 are explicit pointers, never
copy-paste.
The three-layer axis is ORTHOGONAL to ADR-018's level hierarchy
(Base/Domain/Instance). A project at any level may have any combination of
Layer 1 (always) and Layer 2 (sometimes). Layer 3 is the boundary outward,
not a property of the project. The 3-layer × 3-level matrix is navigable in
both axes: 'where in the protocol hierarchy' (level) is independent of
'where in the project's repo' (layer).
Cross-layer references inside a project carry an explicit layer-N tag on
qa entries. A qa entry whose primary topic is a Layer N concern wears
the layer-N tag; satellite entries (operational how-to, troubleshooting)
inherit the layer of their anchor without re-tagging.
"%,
rationale = [
{
claim = "Audience separation requires lifecycle separation",
detail = "Layer 1 evolves with the project's decisions (ADR cadence). Layer 2 evolves with the binary (schemas in lock-step with code that produces and consumes them). Layer 3 evolves with caller infrastructure (workspace cabling, deployment changes). Three lifecycles in one namespace produces drift in two of them — observed in pre-codification cases where schemas drifted because they were treated as project notes.",
},
{
claim = "Layer 2 biconditional is what makes a federated peer detectable",
detail = "If catalog/ exists without manifest.ncl::registry_provides, the project has artifacts but no namespace claim — consumers can't resolve where to pull from. If registry_provides exists without catalog/, the project claims a namespace it doesn't fill. Either half-state breaks integration. Treating the pair as biconditional makes 'is this a federated peer?' a single boolean check.",
},
{
claim = "Layer 3 isolation is project-specific in spelling, universal in principle",
detail = "What counts as caller-specific differs per project (lian-build forbids `provisioning_workspace|vapora_|woodpecker_` per its adr-001; another project would forbid different patterns). The protocol-level constraint cannot enumerate; instead, each project carries its own constraint-bearing ADR and the protocol verifies presence of SUCH a constraint indirectly via cross-layer-tag-discipline. Project-specific spelling, ecosystem-wide presence.",
},
{
claim = "Cross-layer tag discipline makes filtering scalable",
detail = "An ontoref instance accumulates dozens to hundreds of qa entries over its life. 'Filter the qa for what other projects can integrate' (Layer 2) becomes a tag query rather than a full-text search. Tagging only anchors keeps the result set small and authoritative; satellites don't dilute the filter.",
},
{
claim = "Orthogonality with ADR-018 lets the model expand without conflict",
detail = "ADR-018's level hierarchy describes WHERE a project sits in the protocol specialization (Base = ontoref itself; Domain = a project-type domain; Instance = a concrete workspace). The three-layer model describes WHERE inside a project's repo content sits (self / integration-surface / caller-side). Different axes; no terminology collision (level vs layer); a 3x3 matrix populated empirically. Treating them as orthogonal avoids re-litigating ADR-018 every time the layer model evolves.",
},
],
consequences = {
positive = [
"ore describe project can statically answer 'is this a federated peer?' (Layer 2 biconditional) and 'is the project minimally onboarded?' (Layer 1 mandatory)",
"Adopters discover the model from documentation (qa::ontoref-three-layer-model) plus this ADR, not by osmosis from existing projects",
"qa-tag-based filtering (layer-1, layer-2) becomes a navigable surface as ecosystem corpus grows",
"The 3-layer × 3-level matrix is explicit, removing a recurring source of design ambiguity (e.g. 'should this go in .ontology or in catalog?')",
"ontoref setup gains a clear acceptance criterion: it produces a Layer 1-compliant scaffold by construction",
],
negative = [
"Existing projects must audit their structure for Layer 1 compliance — typically reflection/qa.ncl and reflection/backlog.ncl are missing on early adopters",
"Six new constraints add per-project CI overhead (4x FileExists, 2x NuCmd) — running cheap but additive across the ecosystem",
"Terminology discipline required: 'layer' (this ADR) vs 'level' (ADR-018) are different concepts and prose must not collapse them",
"Layer 2 biconditional rejects half-states that some projects might want as an interim — projects mid-Layer-2-rollout must either complete or not start until ready",
],
},
alternatives_considered = [
{
option = "Single 'ontoref content' namespace, no layering",
why_rejected = "Observed drift outcomes (Layer 3 in qa, Layer 2 schemas treated as notes, etc.) are caused precisely by absence of layering. The single-namespace alternative is what we're correcting; rejecting it is the entire decision.",
},
{
option = "Two-layer model collapsing self-management with integration-surface",
why_rejected = "lian-build's adoption explicitly experienced the schema-vs-rationale split: schemas/build_directives.ncl (Layer 2 contract) and adrs/adr-001-lian-build-as-standalone.ncl (Layer 1 rationale) have different audiences, different change cadences, different validation rules. Collapsing them produced the FAQ-vs-contract confusion that retiring lian-build/.ontology/FAQ.md addressed. The two-layer alternative re-creates that confusion.",
},
{
option = "Make Layer 3 (caller-side) optionally co-resident with Layer 2 in the producer's repo",
why_rejected = "Creates ambiguity about who owns the cabling. If a producer's repo carries `extensions/<self>/`, who maintains it when a caller's workspace evolves? The producer doesn't know about the caller's infrastructure; the caller can't depend on the producer to update the cabling on its schedule. Co-residence inverts the integration arrow.",
},
{
option = "Numbered layers (Layer 1 / 2 / 3) vs named layers ('self' / 'integration-surface' / 'caller-side')",
why_rejected = "Named layers are more descriptive but verbose; numbered layers are more precise but flat. The compromise (this ADR): use 'Layer N — descriptive name' in prose, 'layer-N' in tags. Tag economy wins; prose retains the descriptive name.",
},
],
constraints = [
{
id = "layer-1-self-ontology-core",
claim = "Every ontoref-onboarded project has .ontology/core.ncl",
scope = ".ontology/",
severity = 'Hard,
check = {
tag = 'FileExists,
path = ".ontology/core.ncl",
present = true,
},
rationale = "Layer 1's irreducible minimum: a project with no axioms, tensions, or practices has no ontoref instance. Overlaps with adr-016's lift-out check by intention — same observable, two architectural decisions resting on it.",
},
{
id = "layer-1-reflection-qa",
claim = "Every ontoref-onboarded project has reflection/qa.ncl",
scope = "reflection/",
severity = 'Hard,
check = {
tag = 'FileExists,
path = "reflection/qa.ncl",
present = true,
},
rationale = "Layer 1's accepted-knowledge surface. A project without qa.ncl cannot accumulate verified Q&A — adopters re-derive answers each time. ontoref setup must scaffold this; existing projects backfill.",
},
{
id = "layer-1-reflection-backlog",
claim = "Every ontoref-onboarded project has reflection/backlog.ncl",
scope = "reflection/",
severity = 'Hard,
check = {
tag = 'FileExists,
path = "reflection/backlog.ncl",
present = true,
},
rationale = "Layer 1's open-question surface. Projects without backlog.ncl bury unresolved alternatives in chat threads, defeating the protocol's routing mechanism (graduates_to). Required so every undecided question has a place to land.",
},
{
id = "layer-1-adrs-directory",
claim = "Every ontoref-onboarded project has an adrs/ directory",
scope = "adrs/",
severity = 'Hard,
check = {
tag = 'FileExists,
path = "adrs/",
present = true,
},
rationale = "Layer 1's binding-decisions slot. The directory may be empty for a brand-new project, but its presence is what makes 'where do ADRs go?' a settled question.",
},
{
id = "layer-2-biconditional",
claim = "A federated-peer catalog (catalog/domains/ or catalog/modes/) exists if and only if manifest.ncl declares registry_provides",
scope = "catalog/domains/, catalog/modes/, manifest.ncl, .ontology/manifest.ncl",
severity = 'Hard,
check = {
tag = 'NuCmd,
cmd = "let fed = (('catalog/domains' | path exists) or ('catalog/modes' | path exists)); let m_root = (try { open manifest.ncl | str contains 'registry_provides' } catch { false }); let m_onto = (try { open .ontology/manifest.ncl | str contains 'registry_provides' } catch { false }); let prv = ($m_root or $m_onto); if $fed == $prv { 0 } else { error make {msg: $'Layer 2 biconditional violated: federated_catalog=($fed) registry_provides=($prv) — half-states advertise integration the consumer cannot complete'} }",
expect_exit = 0,
},
rationale = "Federated-peer status is detectable by a single biconditional. The marker is catalog/domains/ or catalog/modes/ specifically — not catalog/ as a whole, because some projects (provisioning) carry catalog/components/, catalog/providers/, etc. that serve workspace composition rather than federated artifact publication. The manifest may live at root or under .ontology/ depending on project convention; bl-010 (forthcoming) tracks canonicalization of that path. Half-states (one side without the other) are the violation.",
},
{
id = "cross-layer-tag-discipline",
claim = "qa entries whose primary topic is a Layer N concern carry the layer-N tag (anchors only; satellites inherit by association)",
scope = "reflection/qa.ncl",
severity = 'Soft,
check = {
tag = 'NuCmd,
cmd = "let entries = (nickel export reflection/qa.ncl --format json | from json | get entries); let suspicious = ($entries | where {|e| (($e.answer | str contains 'LAYER 1') or ($e.answer | str contains 'LAYER 2')) and ($e.tags | where {|t| ($t | str starts-with 'layer-')} | is-empty) and ($e.id | str contains 'what-and-why')}); if ($suspicious | is-empty) { 0 } else { error make {msg: $'Anchor entries discussing layers must carry layer-N tag: ($suspicious | get id | str join \", \")'} }",
expect_exit = 0,
},
rationale = "Tag-based filtering scales as the corpus grows. Without discipline, querying for 'Layer 2 concerns' returns either too few entries (false negatives because anchors aren't tagged) or too many (false positives because every entry that mentions a layer gets tagged). Anchors-only is the sustainable middle path; this constraint enforces it for the most common drift case.",
},
],
related_adrs = [
"adr-001-protocol-as-standalone-project",
"adr-016-component-lift-out-pattern",
"adr-018-level-hierarchy-mode-resolution-strategy",
],
ontology_check = {
decision_string = "An ontoref-onboarded project's repository carries Layer 1 (mandatory self-management) and optionally Layer 2 (integration surface, biconditional with registry_provides). Layer 3 lives in caller projects, not in the producer. The three-layer axis is orthogonal to ADR-018's level hierarchy.",
invariants_at_risk = [],
verdict = 'Safe,
},
}

View file

@ -1 +0,0 @@
import "adr-defaults.ncl"

View file

@ -1,114 +0,0 @@
let _adr_id_format = std.contract.custom (
fun label =>
fun value =>
if std.string.is_match "^adr-[0-9]{3}$" value then
'Ok value
else
'Error {
message = "ADR id must match 'adr-NNN' format (e.g. 'adr-001'), got: '%{value}'"
}
) in
let _non_empty_constraints = std.contract.custom (
fun label =>
fun value =>
if std.array.length value == 0 then
'Error {
message = "constraints must not be empty — an ADR with no constraints is passive documentation, not an active constraint"
}
else
'Ok value
) in
let _non_empty_negative = std.contract.custom (
fun label =>
fun value =>
if std.array.length value.negative == 0 then
'Error {
message = "consequences.negative must not be empty on id='%{value.id}' — an ADR with no negative consequences is incomplete"
}
else
'Ok value
) in
let _requires_justification = std.contract.custom (
fun label =>
fun value =>
if value.ontology_check.verdict == 'RequiresJustification
&& !(std.record.has_field "invariant_justification" value) then
'Error {
message = "ADR '%{value.id}': ontology_check.verdict = 'RequiresJustification but invariant_justification field is missing"
}
else
'Ok value
) in
let _comma = ", " in
let _each_constraint_has_check = std.contract.custom (
fun label =>
fun value =>
let violations = std.array.filter (fun c =>
!(std.record.has_field "check" c) && !(std.record.has_field "check_hint" c)
) value in
if std.array.length violations == 0 then
'Ok value
else
let ids = std.array.map (fun c => c.id) violations in
'Error {
message = "Constraints missing both 'check' and 'check_hint': %{std.string.join _comma ids}"
}
) in
# Validates that each constraint's typed 'check' record has the required
# fields for its declared tag. Returns the first validation error found.
let _each_check_well_formed = std.contract.custom (
fun label =>
fun constraints =>
# Returns "" on valid, error message on invalid.
let validate_check = fun c =>
if !(std.record.has_field "check" c) then
""
else
let chk = c.check in
let tag = chk.tag in
let needs = fun field => !(std.record.has_field field chk) in
if tag == 'Cargo then
if needs "crate" || needs "forbidden_deps" then
"Constraint '%{c.id}': Cargo check requires 'crate' and 'forbidden_deps'"
else ""
else if tag == 'Grep then
if needs "pattern" || needs "paths" || needs "must_be_empty" then
"Constraint '%{c.id}': Grep check requires 'pattern', 'paths', 'must_be_empty'"
else ""
else if tag == 'NuCmd then
if needs "cmd" || needs "expect_exit" then
"Constraint '%{c.id}': NuCmd check requires 'cmd' and 'expect_exit'"
else ""
else if tag == 'ApiCall then
if needs "endpoint" || needs "json_path" || needs "expected" then
"Constraint '%{c.id}': ApiCall check requires 'endpoint', 'json_path', 'expected'"
else ""
else if tag == 'FileExists then
if needs "path" || needs "present" then
"Constraint '%{c.id}': FileExists check requires 'path' and 'present'"
else ""
else
"Constraint '%{c.id}': unknown check tag '%{std.to_str tag}'"
in
let first_err = std.array.fold_left (fun acc c =>
if acc != "" then acc else validate_check c
) "" constraints
in
if first_err == "" then 'Ok constraints
else 'Error { message = first_err }
) in
{
AdrIdFormat = _adr_id_format,
NonEmptyConstraints = _non_empty_constraints,
NonEmptyNegativeConsequences = _non_empty_negative,
RequiresJustificationWhenRisky = _requires_justification,
EachConstraintHasCheck = _each_constraint_has_check,
EachCheckWellFormed = _each_check_well_formed,
}

View file

@ -1,16 +0,0 @@
let s = import "adr-schema.ncl" in
let c = import "adr-constraints.ncl" in
{
# RequiresJustificationWhenRisky is a cross-field contract (reads both
# ontology_check.verdict and invariant_justification) — applied here after
# the schema merge so both fields are visible in the same record.
make_adr = fun data =>
let result | c.RequiresJustificationWhenRisky = s.Adr & data in
result,
make_constraint = fun data => s.Constraint & data,
Adr = s.Adr,
Constraint = s.Constraint,
OntologyCheck = s.OntologyCheck,
}

View file

@ -1,100 +0,0 @@
let c = import "adr-constraints.ncl" in
let status_type = [| 'Proposed, 'Accepted, 'Superseded, 'Deprecated |] in
let severity_type = [| 'Hard, 'Soft |] in
let verdict_type = [| 'Safe, 'RequiresJustification |] in
let rationale_entry_type = {
claim | String,
detail | String,
} in
let alternative_type = {
option | String,
why_rejected | String,
} in
# Tag discriminant for typed constraint checks.
# Used by validate.nu to dispatch execution per variant.
let check_tag_type = [|
'Cargo,
'Grep,
'NuCmd,
'ApiCall,
'FileExists,
|] in
# Typed constraint check: a tagged record, JSON-serializable.
# Required fields per tag (validated by EachCheckWellFormed in adr-constraints.ncl):
# 'Cargo -> crate : String, forbidden_deps : Array String
# 'Grep -> pattern : String, paths : Array String, must_be_empty : Bool
# 'NuCmd -> cmd : String, expect_exit : Number
# 'ApiCall -> endpoint : String, json_path : String, expected : Dyn
# 'FileExists-> path : String, present : Bool
let constraint_check_type = {
tag | check_tag_type,
..
} in
let constraint_type = {
id | String,
claim | String,
scope | String,
severity | severity_type,
# Transition period: one of check or check_hint must be present.
# check_hint is deprecated — migrate existing ADRs to typed check variants.
check_hint | String | optional,
check | constraint_check_type | optional,
rationale | String,
} in
let ontology_check_type = {
decision_string | String,
invariants_at_risk | Array String,
verdict | verdict_type,
} in
let invariant_justification_type = {
invariant | String,
claim | String,
mitigation | String,
} in
let consequences_type = {
positive | Array String,
negative | Array String,
} in
let adr_type = {
id | String | c.AdrIdFormat,
title | String,
status | status_type,
date | String,
context | String,
decision | String,
rationale | Array rationale_entry_type,
consequences | consequences_type,
alternatives_considered | Array alternative_type,
constraints | Array constraint_type | c.NonEmptyConstraints | c.EachConstraintHasCheck,
ontology_check | ontology_check_type,
related_adrs | Array String | default = [],
supersedes | String | optional,
superseded_by | String | optional,
invariant_justification | invariant_justification_type | optional,
} in
{
AdrStatus = status_type,
Severity = severity_type,
Verdict = verdict_type,
ConstraintCheck = constraint_check_type,
Constraint = constraint_type,
RationaleEntry = rationale_entry_type,
Alternative = alternative_type,
OntologyCheck = ontology_check_type,
InvariantJustification = invariant_justification_type,
Adr = adr_type,
}

View file

@ -1 +0,0 @@
import "adr-constraints.ncl"

View file

@ -1 +0,0 @@
import "adr-defaults.ncl"

View file

@ -1,317 +0,0 @@
let s = import "../reflection/schema.ncl" in
# ADR System — Operational reflection guide
# Covers: authoring, reading, validating, and superseding ADRs in the ontoref ecosystem.
# All content in English. Schema field names (actor, depends_on, verify) are API identifiers.
let AdrAction = [|
'new_adr,
'read_adr,
'validate_decision,
'supersede_adr,
'export_adr,
|] in
{
style = {
format = "Nickel ADR",
schema = "adrs/defaults.ncl",
content_lang = "English",
field_names = "API identifiers — do not translate",
invariant_check = "Every ADR decision string must be validated against .ontology/core.ncl before writing",
},
agent = {
rules = [
"Read adrs/defaults.ncl and adrs/schema.ncl before generating any ADR content",
"Run `nickel export adrs/adr-NNN-*.ncl` to verify the file exports cleanly after writing",
"Check .ontology/core.ncl invariants_at_risk and ontology_check.verdict before marking status = 'Accepted",
"If verdict is 'Risky or 'Unsafe, require explicit justification in InvariantJustification before accepting",
"All string values in id, title, context, decision, rationale, constraints, consequences must be in English",
"Schema field names (name, description, closing_condition) are defined by ontoref-ontology in English",
"Never create an ADR without at least one Hard constraint with a check_hint",
"Superseded ADRs keep status = 'Superseded — do not delete them",
],
},
modes = [
{
id = "new_adr",
trigger = "A significant architectural decision has been made or is being considered",
preconditions = [
"Decision affects the ecosystem architecture, not a single project implementation detail",
"No existing ADR covers this decision",
"adrs/defaults.ncl and adrs/schema.ncl are readable",
],
steps = [
{
id = "check_ontology",
action = 'validate_decision,
actor = 'Agent,
cmd = "nickel export .ontology/core.ncl | get nodes | where level == 'Axiom | get id",
verify = "Output lists the invariant IDs that the decision may affect",
note = "Identify which invariants are at risk before writing the ADR",
},
{
id = "draft_adr",
action = 'new_adr,
depends_on = [{ step = "check_ontology", kind = 'OnSuccess }],
actor = 'Both,
note = "Use the next sequential adr-NNN id. Title must be a declarative statement, not a question.",
},
{
id = "add_constraints",
action = 'new_adr,
depends_on = [{ step = "draft_adr", kind = 'Always }],
actor = 'Both,
note = "Every ADR requires at least one Hard constraint. The check_hint must be an executable command.",
},
{
id = "verify_export",
action = 'export_adr,
depends_on = [{ step = "add_constraints", kind = 'OnSuccess }],
actor = 'Agent,
cmd = "nickel export adrs/adr-NNN-*.ncl",
verify = "Command exits 0 and produces valid JSON",
on_error = { strategy = 'Stop },
},
{
id = "set_accepted",
action = 'new_adr,
depends_on = [{ step = "verify_export", kind = 'OnSuccess }],
actor = 'Human,
note = "Only the project maintainer sets status = 'Accepted. An ADR in 'Proposed is not authoritative.",
},
],
postconditions = [
"adrs/adr-NNN-*.ncl exports cleanly with status = 'Accepted",
"ontology_check.verdict is 'Safe or has explicit InvariantJustification",
"At least one constraint has severity = 'Hard and a non-empty check_hint",
],
},
{
id = "read_as_human",
trigger = "Understanding a past decision or exploring the ADR corpus",
preconditions = [
"adrs/ directory contains at least one .ncl file",
],
steps = [
{
id = "list_adrs",
action = 'read_adr,
actor = 'Human,
cmd = "ls adrs/adr-*.ncl | sort",
verify = "Files are listed in adr-NNN order",
},
{
id = "export_adr",
action = 'export_adr,
depends_on = [{ step = "list_adrs", kind = 'OnSuccess }],
actor = 'Human,
cmd = "nickel export adrs/adr-NNN-*.ncl | jq '{title, status, decision, constraints}'",
verify = "decision and constraints.claim fields are in English",
},
{
id = "check_related",
action = 'read_adr,
depends_on = [{ step = "export_adr", kind = 'Always }],
actor = 'Human,
note = "Follow related_adrs references to understand the decision chain",
},
],
postconditions = [
"Decision chain is understood: context → decision → constraints → ontology_check",
],
},
{
id = "read_as_agent",
trigger = "AI agent needs to understand the current architectural position before proposing changes",
preconditions = [
"Task affects ontoref protocol architecture, crate scope, or schema definitions",
],
steps = [
{
id = "export_all_accepted",
action = 'export_adr,
actor = 'Agent,
cmd = "ls adrs/adr-*.ncl | each { |f| nickel export $f } | where status == 'Accepted",
verify = "Only Accepted ADRs are in the working set",
note = "Proposed and Superseded ADRs are not authoritative — do not use them to justify decisions",
},
{
id = "extract_constraints",
action = 'validate_decision,
depends_on = [{ step = "export_all_accepted", kind = 'OnSuccess }],
actor = 'Agent,
cmd = "nickel export adrs/adr-*.ncl | get constraints | where severity == 'Hard | get check_hint",
verify = "Hard constraints are available as executable check_hints",
},
{
id = "cross_reference_ontology",
action = 'validate_decision,
depends_on = [{ step = "extract_constraints", kind = 'Always }],
actor = 'Agent,
cmd = "nickel export .ontology/core.ncl | get nodes | where invariant == true | get id",
verify = "Invariant IDs match the invariants_at_risk fields across accepted ADRs",
note = "A proposed change that touches any invariant requires a new ADR, not a patch",
},
],
postconditions = [
"Agent has the active constraint set and knows which invariants bound the decision space",
"No change is proposed that violates a Hard constraint without a new ADR",
],
},
{
id = "validate_decision",
trigger = "A proposed code or architecture change needs validation against existing ADRs",
preconditions = [
"The change is described in concrete terms: what is being added, removed, or modified",
"Accepted ADRs are available via `nickel export`",
],
steps = [
{
id = "run_check_hints",
action = 'validate_decision,
actor = 'Agent,
cmd = "nickel export adrs/adr-*.ncl | get constraints | where severity == 'Hard | each { |c| nu -c $c.check_hint }",
verify = "All Hard check_hints exit 0 or produce no output (no violation found)",
on_error = { strategy = 'Stop },
note = "A non-zero exit or match output means a Hard constraint is violated — stop and document",
},
{
id = "check_protocol_invariants",
action = 'validate_decision,
depends_on = [{ step = "run_check_hints", kind = 'OnSuccess }],
actor = 'Agent,
cmd = "nickel export .ontology/core.ncl | get nodes | where invariant == true | get id",
verify = "None of the invariant node IDs are affected by the proposed change",
note = "A change touching any invariant node requires a new ADR, not a patch",
},
{
id = "report_result",
action = 'validate_decision,
depends_on = [{ step = "check_protocol_invariants", kind = 'Always }],
actor = 'Agent,
note = "Report: which constraints passed, which failed, which ADR is implicated for each failure",
},
],
postconditions = [
"All Hard constraints pass or a blocking ADR violation is documented with the implicated constraint ID",
],
},
{
id = "query_constraints",
trigger = "Need to know the current active constraint set, or reconstruct constraints at a specific point in time",
preconditions = [
"adrs/ directory contains exported ADR files",
"`nickel export` is available",
],
steps = [
{
id = "active_constraints",
action = 'read_adr,
actor = 'Agent,
cmd = "ls adrs/adr-*.ncl | each { |f| nickel export $f } | where status == 'Accepted | each { |a| { adr: $a.id, title: $a.title, constraints: $a.constraints } }",
verify = "Output contains only Accepted ADRs with their constraint sets",
note = "This is the authoritative current constraint set. Proposed and Superseded ADRs are excluded.",
},
{
id = "hard_constraints_only",
action = 'validate_decision,
depends_on = [{ step = "active_constraints", kind = 'Always }],
actor = 'Agent,
cmd = "ls adrs/adr-*.ncl | each { |f| nickel export $f } | where status == 'Accepted | get constraints | flatten | where severity == 'Hard | select id claim check_hint",
verify = "Each Hard constraint has a non-empty check_hint",
note = "Hard constraints are non-negotiable. A change that violates one requires a new ADR, not a workaround.",
},
{
id = "point_in_time",
action = 'read_adr,
depends_on = [{ step = "active_constraints", kind = 'Always }],
actor = 'Agent,
cmd = "ls adrs/adr-*.ncl | each { |f| nickel export $f } | where (($it.status == 'Accepted or $it.status == 'Superseded) and $it.date <= \"YYYY-MM\") | where superseded_by? == null | get constraints | flatten",
verify = "Returns the constraint set that was active at the given date",
note = "Replace YYYY-MM with target date. An ADR without superseded_by was still Accepted at that date.",
},
{
id = "supersession_chain",
action = 'read_adr,
depends_on = [{ step = "point_in_time", kind = 'Always }],
actor = 'Agent,
cmd = "ls adrs/adr-*.ncl | each { |f| nickel export $f } | select id status supersedes superseded_by date | sort-by date",
verify = "Each superseded ADR has a superseded_by reference to the replacing ADR",
note = "Follow the chain: superseded_by → new ADR → check its supersedes field to confirm bidirectional link",
},
],
postconditions = [
"Agent has the active Hard constraint set for validation",
"Or: the historical constraint set at a specific date is reconstructed",
"Supersession chain is traceable in both directions via supersedes / superseded_by",
],
},
{
id = "supersede_adr",
trigger = "An existing Accepted ADR must be replaced due to architectural evolution",
preconditions = [
"A new ADR (adr-NNN) has been drafted and exports cleanly",
"The superseding ADR references the old ADR in related_adrs",
"The old ADR ID and title are known",
],
steps = [
{
id = "set_superseded_status",
action = 'supersede_adr,
actor = 'Human,
note = "Edit the old ADR file: set status = 'Superseded. Do not delete the file.",
},
{
id = "verify_new_exports",
action = 'export_adr,
depends_on = [{ step = "set_superseded_status", kind = 'OnSuccess }],
actor = 'Agent,
cmd = "nickel export adrs/adr-NNN-*.ncl",
verify = "New ADR exports with status = 'Accepted",
on_error = { strategy = 'Stop },
},
{
id = "verify_old_exports",
action = 'export_adr,
depends_on = [{ step = "set_superseded_status", kind = 'OnSuccess }],
actor = 'Agent,
cmd = "nickel export adrs/adr-OLD-*.ncl | get status",
verify = "Output is 'Superseded",
on_error = { strategy = 'Stop },
},
{
id = "update_references",
action = 'supersede_adr,
depends_on = [
{ step = "verify_new_exports", kind = 'OnSuccess },
{ step = "verify_old_exports", kind = 'OnSuccess },
],
actor = 'Human,
cmd = "grep -r 'adr-OLD' adrs/ reflection/ .ontology/",
note = "Update any references to the old ADR ID in other files",
},
],
postconditions = [
"Old ADR has status = 'Superseded and remains in adrs/",
"New ADR has status = 'Accepted and references the old ADR in related_adrs",
"No file references the old ADR as if it were still active",
],
},
],
}
| {
modes
| Array (s.Mode AdrAction)
| doc "ADR operational modes — validated against reflection/schema.ncl at eval time",
..
}

View file

@ -1 +0,0 @@
import "adr-schema.ncl"

View file

@ -1,253 +0,0 @@
<svg viewBox="0 0 900 560" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arr-a" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<path d="M0 .5 L7 3 L0 5.5Z" fill="#E8A838"/>
</marker>
<marker id="arr-s" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<path d="M0 .5 L7 3 L0 5.5Z" fill="#5A6A7C"/>
</marker>
<marker id="arr-o" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<path d="M0 .5 L7 3 L0 5.5Z" fill="#2A5080"/>
</marker>
</defs>
<!-- background -->
<rect width="900" height="560" fill="#0D1117"/>
<!-- ─────────────────────────────────────────
CONSUMER
───────────────────────────────────────── -->
<rect x="20" y="20" width="860" height="62" rx="6"
fill="#0F1623" stroke="#2A3A50" stroke-width="1.5" stroke-dasharray="6,4"/>
<text x="44" y="43"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#3A5070" letter-spacing=".1em">CONSUMER PROJECT</text>
<text x="44" y="63"
font-family="'IBM Plex Mono',monospace" font-size="12" fill="#8090A4">ontoref</text>
<text x="190" y="63"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#3A5070"> · .ontoref/config.ncl · ONTOREF_PROJECT_ROOT</text>
<!-- Consumer → Tooling -->
<line x1="280" y1="82" x2="280" y2="112"
stroke="#E8A838" stroke-width="1.5" marker-end="url(#arr-a)"/>
<text x="292" y="101"
font-family="'IBM Plex Mono',monospace" font-size="9" fill="#B87000">exec · ONTOREF_PROJECT_ROOT</text>
<!-- ─────────────────────────────────────────
TOOLING LAYER
───────────────────────────────────────── -->
<rect x="20" y="116" width="548" height="192" rx="6"
fill="#111827" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".6"/>
<text x="30" y="132"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#E8A838" letter-spacing=".1em">TOOLING LAYER</text>
<!-- ./ontoref -->
<rect x="40" y="148" width="138" height="46" rx="5"
fill="#1C2535" stroke="#E8A838" stroke-width="1" stroke-opacity=".5"/>
<text x="109" y="167"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="600"
fill="#E8A838" text-anchor="middle">./ontoref</text>
<text x="109" y="183"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">bash · actor detection</text>
<!-- ./ontoref → ontoref.nu -->
<line x1="178" y1="171" x2="208" y2="171"
stroke="#E8A838" stroke-width="1.5" marker-end="url(#arr-a)"/>
<!-- ontoref.nu -->
<rect x="208" y="148" width="174" height="46" rx="5"
fill="#1C2535" stroke="#E8A838" stroke-width="1" stroke-opacity=".5"/>
<text x="295" y="167"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="600"
fill="#E8A838" text-anchor="middle">ontoref.nu</text>
<text x="295" y="183"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">Nushell dispatcher</text>
<!-- ontoref.nu → modules -->
<line x1="295" y1="194" x2="295" y2="220"
stroke="#E8A838" stroke-width="1.5" marker-end="url(#arr-a)"/>
<!-- 16 Nushell modules -->
<rect x="40" y="220" width="506" height="72" rx="5"
fill="#1C2535" stroke="#5A6A7C" stroke-width="1" stroke-opacity=".5"/>
<text x="293" y="240"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="500"
fill="#C0CCD8" text-anchor="middle">reflection/modules/ · 16 Nushell modules</text>
<text x="293" y="257"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">adr · backlog · coder · describe · sync · store · services · nats · …</text>
<text x="293" y="274"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#1E3A58" text-anchor="middle">subprocess fallback: direct nickel export when daemon unavailable</text>
<!-- ─────────────────────────────────────────
RUST CRATES
───────────────────────────────────────── -->
<rect x="584" y="116" width="296" height="192" rx="6"
fill="#111827" stroke="#8090A4" stroke-width="1.5" stroke-opacity=".4"/>
<text x="594" y="132"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#8090A4" letter-spacing=".1em">RUST CRATES</text>
<!-- ontoref-ontology -->
<rect x="600" y="148" width="264" height="38" rx="4"
fill="#1C2535" stroke="#5A6A7C" stroke-width="1"/>
<text x="732" y="164"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="500"
fill="#C0CCD8" text-anchor="middle">ontoref-ontology</text>
<text x="732" y="178"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">.ontology/*.ncl → typed Rust structs</text>
<!-- ontoref-reflection -->
<rect x="600" y="198" width="264" height="38" rx="4"
fill="#1C2535" stroke="#5A6A7C" stroke-width="1"/>
<text x="732" y="214"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="500"
fill="#C0CCD8" text-anchor="middle">ontoref-reflection</text>
<text x="732" y="228"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">load + validate + execute NCL DAG modes</text>
<!-- ontoref-daemon (optional) -->
<rect x="600" y="248" width="264" height="44" rx="4"
fill="#0E1825" stroke="#2A5080" stroke-width="1" stroke-dasharray="5,3"/>
<text x="732" y="266"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="500"
fill="#3A6A9A" text-anchor="middle">ontoref-daemon</text>
<text x="732" y="280"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#2A4A6A" text-anchor="middle">NCL cache · file watcher · actor registry · HTTP</text>
<text x="862" y="249"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#2A5080" text-anchor="end">optional</text>
<!-- modules → daemon (HTTP, optional) -->
<line x1="546" y1="258" x2="600" y2="265"
stroke="#2A5080" stroke-width="1.2" stroke-dasharray="4,3" marker-end="url(#arr-o)"/>
<text x="553" y="251"
font-family="'IBM Plex Mono',monospace" font-size="9" fill="#2A4060">HTTP</text>
<!-- ─────────────────────────────────────────
INTER-ROW ARROWS
───────────────────────────────────────── -->
<!-- Modules → Protocol (nickel export) -->
<line x1="190" y1="292" x2="148" y2="338"
stroke="#5A6A7C" stroke-width="1.2" marker-end="url(#arr-s)"/>
<text x="123" y="321"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#3A5070" text-anchor="middle">nickel</text>
<text x="123" y="332"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#3A5070" text-anchor="middle">export</text>
<!-- ontoref-reflection → Protocol (executes modes) -->
<path d="M650 308 C642 326 430 330 368 338"
stroke="#5A6A7C" stroke-width="1.2" marker-end="url(#arr-s)"/>
<!-- ontoref-ontology → Self-description (reads .ontology/) -->
<line x1="732" y1="308" x2="695" y2="338"
stroke="#5A6A7C" stroke-width="1.2" marker-end="url(#arr-s)"/>
<text x="748" y="326"
font-family="'IBM Plex Mono',monospace" font-size="9" fill="#3A5070">reads</text>
<!-- ─────────────────────────────────────────
PROTOCOL LAYER
───────────────────────────────────────── -->
<rect x="20" y="342" width="420" height="170" rx="6"
fill="#111827" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".45"/>
<text x="30" y="358"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#E8A838" letter-spacing=".1em">PROTOCOL LAYER</text>
<text x="230" y="381"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#A8B4C8" text-anchor="middle">ontology/schemas/</text>
<text x="230" y="396"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">core · gate · state · manifest (NCL defaults)</text>
<line x1="36" y1="408" x2="424" y2="408" stroke="#1E2A3A" stroke-width="1"/>
<text x="230" y="425"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#A8B4C8" text-anchor="middle">adrs/</text>
<text x="230" y="440"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">schema · constraints · lifecycle forms</text>
<line x1="36" y1="452" x2="424" y2="452" stroke="#1E2A3A" stroke-width="1"/>
<text x="230" y="469"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#A8B4C8" text-anchor="middle">reflection/schemas/ · modes/ · forms/</text>
<text x="230" y="484"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">9 schemas · 10 NCL DAG modes · 7 forms</text>
<!-- ─────────────────────────────────────────
SELF-DESCRIPTION LAYER
───────────────────────────────────────── -->
<rect x="456" y="342" width="424" height="170" rx="6"
fill="#111827" stroke="#8090A4" stroke-width="1.5" stroke-opacity=".35" stroke-dasharray="8,4"/>
<text x="466" y="358"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#8090A4" letter-spacing=".1em">SELF-DESCRIPTION · .ontology/</text>
<text x="668" y="381"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#A8B4C8" text-anchor="middle">ontoref consuming its own protocol</text>
<line x1="472" y1="393" x2="872" y2="393" stroke="#1E2A3A" stroke-width="1"/>
<text x="668" y="413"
font-family="'IBM Plex Mono',monospace" font-size="11"
fill="#8090A4" text-anchor="middle">core.ncl · state.ncl · gate.ncl · manifest.ncl</text>
<line x1="472" y1="426" x2="872" y2="426" stroke="#1E2A3A" stroke-width="1"/>
<text x="668" y="446"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">4 axioms · 2 tensions · 9 practices · 19 edges</text>
<text x="668" y="462"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">3 state dimensions: protocol-maturity · self-description · ecosystem</text>
<line x1="472" y1="474" x2="872" y2="474" stroke="#1E2A3A" stroke-width="1"/>
<text x="668" y="493"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#2A4A6A" text-anchor="middle">ADR-001: self-describing axiom in practice</text>
<!-- ─────────────────────────────────────────
LEGEND
───────────────────────────────────────── -->
<line x1="30" y1="532" x2="52" y2="532"
stroke="#E8A838" stroke-width="1.5" marker-end="url(#arr-a)"/>
<text x="57" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10" fill="#5A6A7C">active flow</text>
<line x1="145" y1="532" x2="167" y2="532"
stroke="#5A6A7C" stroke-width="1.2" marker-end="url(#arr-s)"/>
<text x="172" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10" fill="#5A6A7C">reads / executes</text>
<line x1="290" y1="532" x2="312" y2="532"
stroke="#2A5080" stroke-width="1.2" stroke-dasharray="4,3" marker-end="url(#arr-o)"/>
<text x="317" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10" fill="#5A6A7C">optional</text>
<rect x="393" y="525" width="12" height="12" rx="2"
fill="none" stroke="#8090A4" stroke-opacity=".5" stroke-width="1" stroke-dasharray="4,2"/>
<text x="410" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10" fill="#5A6A7C">self-description</text>
<text x="878" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#1E2A3A" text-anchor="end">ontoref architecture</text>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,253 +0,0 @@
<svg viewBox="0 0 900 560" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arr-a" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<path d="M0 .5 L7 3 L0 5.5Z" fill="#E8A838"/>
</marker>
<marker id="arr-s" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<path d="M0 .5 L7 3 L0 5.5Z" fill="#5A6A7C"/>
</marker>
<marker id="arr-o" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<path d="M0 .5 L7 3 L0 5.5Z" fill="#2A5080"/>
</marker>
</defs>
<!-- background -->
<rect width="900" height="560" fill="#0D1117"/>
<!-- ─────────────────────────────────────────
CONSUMER
───────────────────────────────────────── -->
<rect x="20" y="20" width="860" height="62" rx="6"
fill="#0F1623" stroke="#2A3A50" stroke-width="1.5" stroke-dasharray="6,4"/>
<text x="44" y="43"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#3A5070" letter-spacing=".1em">CONSUMER PROJECT</text>
<text x="44" y="63"
font-family="'IBM Plex Mono',monospace" font-size="12" fill="#8090A4">ontoref</text>
<text x="190" y="63"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#3A5070"> · .ontoref/config.ncl · ONTOREF_PROJECT_ROOT</text>
<!-- Consumer → Tooling -->
<line x1="280" y1="82" x2="280" y2="112"
stroke="#E8A838" stroke-width="1.5" marker-end="url(#arr-a)"/>
<text x="292" y="101"
font-family="'IBM Plex Mono',monospace" font-size="9" fill="#B87000">exec · ONTOREF_PROJECT_ROOT</text>
<!-- ─────────────────────────────────────────
TOOLING LAYER
───────────────────────────────────────── -->
<rect x="20" y="116" width="548" height="192" rx="6"
fill="#111827" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".6"/>
<text x="30" y="132"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#E8A838" letter-spacing=".1em">TOOLING LAYER</text>
<!-- ./ontoref -->
<rect x="40" y="148" width="138" height="46" rx="5"
fill="#1C2535" stroke="#E8A838" stroke-width="1" stroke-opacity=".5"/>
<text x="109" y="167"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="600"
fill="#E8A838" text-anchor="middle">./ontoref</text>
<text x="109" y="183"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">bash · actor detection</text>
<!-- ./ontoref → ontoref.nu -->
<line x1="178" y1="171" x2="208" y2="171"
stroke="#E8A838" stroke-width="1.5" marker-end="url(#arr-a)"/>
<!-- ontoref.nu -->
<rect x="208" y="148" width="174" height="46" rx="5"
fill="#1C2535" stroke="#E8A838" stroke-width="1" stroke-opacity=".5"/>
<text x="295" y="167"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="600"
fill="#E8A838" text-anchor="middle">ontoref.nu</text>
<text x="295" y="183"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">Nushell dispatcher</text>
<!-- ontoref.nu → modules -->
<line x1="295" y1="194" x2="295" y2="220"
stroke="#E8A838" stroke-width="1.5" marker-end="url(#arr-a)"/>
<!-- 16 Nushell modules -->
<rect x="40" y="220" width="506" height="72" rx="5"
fill="#1C2535" stroke="#5A6A7C" stroke-width="1" stroke-opacity=".5"/>
<text x="293" y="240"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="500"
fill="#C0CCD8" text-anchor="middle">reflection/modules/ · 16 Nushell modules</text>
<text x="293" y="257"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">adr · backlog · coder · describe · sync · store · services · nats · …</text>
<text x="293" y="274"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#1E3A58" text-anchor="middle">subprocess fallback: direct nickel export when daemon unavailable</text>
<!-- ─────────────────────────────────────────
RUST CRATES
───────────────────────────────────────── -->
<rect x="584" y="116" width="296" height="192" rx="6"
fill="#111827" stroke="#8090A4" stroke-width="1.5" stroke-opacity=".4"/>
<text x="594" y="132"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#8090A4" letter-spacing=".1em">RUST CRATES</text>
<!-- ontoref-ontology -->
<rect x="600" y="148" width="264" height="38" rx="4"
fill="#1C2535" stroke="#5A6A7C" stroke-width="1"/>
<text x="732" y="164"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="500"
fill="#C0CCD8" text-anchor="middle">ontoref-ontology</text>
<text x="732" y="178"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">.ontology/*.ncl → typed Rust structs</text>
<!-- ontoref-reflection -->
<rect x="600" y="198" width="264" height="38" rx="4"
fill="#1C2535" stroke="#5A6A7C" stroke-width="1"/>
<text x="732" y="214"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="500"
fill="#C0CCD8" text-anchor="middle">ontoref-reflection</text>
<text x="732" y="228"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#5A6A7C" text-anchor="middle">load + validate + execute NCL DAG modes</text>
<!-- ontoref-daemon (optional) -->
<rect x="600" y="248" width="264" height="44" rx="4"
fill="#0E1825" stroke="#2A5080" stroke-width="1" stroke-dasharray="5,3"/>
<text x="732" y="266"
font-family="'IBM Plex Mono',monospace" font-size="11" font-weight="500"
fill="#3A6A9A" text-anchor="middle">ontoref-daemon</text>
<text x="732" y="280"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#2A4A6A" text-anchor="middle">NCL cache · file watcher · actor registry · HTTP</text>
<text x="862" y="249"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#2A5080" text-anchor="end">optional</text>
<!-- modules → daemon (HTTP, optional) -->
<line x1="546" y1="258" x2="600" y2="265"
stroke="#2A5080" stroke-width="1.2" stroke-dasharray="4,3" marker-end="url(#arr-o)"/>
<text x="553" y="251"
font-family="'IBM Plex Mono',monospace" font-size="9" fill="#2A4060">HTTP</text>
<!-- ─────────────────────────────────────────
INTER-ROW ARROWS
───────────────────────────────────────── -->
<!-- Modules → Protocol (nickel export) -->
<line x1="190" y1="292" x2="148" y2="338"
stroke="#5A6A7C" stroke-width="1.2" marker-end="url(#arr-s)"/>
<text x="123" y="321"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#3A5070" text-anchor="middle">nickel</text>
<text x="123" y="332"
font-family="'IBM Plex Mono',monospace" font-size="9"
fill="#3A5070" text-anchor="middle">export</text>
<!-- ontoref-reflection → Protocol (executes modes) -->
<path d="M650 308 C642 326 430 330 368 338"
stroke="#5A6A7C" stroke-width="1.2" marker-end="url(#arr-s)"/>
<!-- ontoref-ontology → Self-description (reads .ontology/) -->
<line x1="732" y1="308" x2="695" y2="338"
stroke="#5A6A7C" stroke-width="1.2" marker-end="url(#arr-s)"/>
<text x="748" y="326"
font-family="'IBM Plex Mono',monospace" font-size="9" fill="#3A5070">reads</text>
<!-- ─────────────────────────────────────────
PROTOCOL LAYER
───────────────────────────────────────── -->
<rect x="20" y="342" width="420" height="170" rx="6"
fill="#111827" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".45"/>
<text x="30" y="358"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#E8A838" letter-spacing=".1em">PROTOCOL LAYER</text>
<text x="230" y="381"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#A8B4C8" text-anchor="middle">ontology/schemas/</text>
<text x="230" y="396"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">core · gate · state · manifest (NCL defaults)</text>
<line x1="36" y1="408" x2="424" y2="408" stroke="#1E2A3A" stroke-width="1"/>
<text x="230" y="425"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#A8B4C8" text-anchor="middle">adrs/</text>
<text x="230" y="440"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">schema · constraints · lifecycle forms</text>
<line x1="36" y1="452" x2="424" y2="452" stroke="#1E2A3A" stroke-width="1"/>
<text x="230" y="469"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#A8B4C8" text-anchor="middle">reflection/schemas/ · modes/ · forms/</text>
<text x="230" y="484"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">9 schemas · 10 NCL DAG modes · 7 forms</text>
<!-- ─────────────────────────────────────────
SELF-DESCRIPTION LAYER
───────────────────────────────────────── -->
<rect x="456" y="342" width="424" height="170" rx="6"
fill="#111827" stroke="#8090A4" stroke-width="1.5" stroke-opacity=".35" stroke-dasharray="8,4"/>
<text x="466" y="358"
font-family="'IBM Plex Mono',monospace" font-size="10" font-weight="600"
fill="#8090A4" letter-spacing=".1em">SELF-DESCRIPTION · .ontology/</text>
<text x="668" y="381"
font-family="'IBM Plex Mono',monospace" font-size="12"
fill="#A8B4C8" text-anchor="middle">ontoref consuming its own protocol</text>
<line x1="472" y1="393" x2="872" y2="393" stroke="#1E2A3A" stroke-width="1"/>
<text x="668" y="413"
font-family="'IBM Plex Mono',monospace" font-size="11"
fill="#8090A4" text-anchor="middle">core.ncl · state.ncl · gate.ncl · manifest.ncl</text>
<line x1="472" y1="426" x2="872" y2="426" stroke="#1E2A3A" stroke-width="1"/>
<text x="668" y="446"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">4 axioms · 2 tensions · 9 practices · 19 edges</text>
<text x="668" y="462"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#5A6A7C" text-anchor="middle">3 state dimensions: protocol-maturity · self-description · ecosystem</text>
<line x1="472" y1="474" x2="872" y2="474" stroke="#1E2A3A" stroke-width="1"/>
<text x="668" y="493"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#2A4A6A" text-anchor="middle">ADR-001: self-describing axiom in practice</text>
<!-- ─────────────────────────────────────────
LEGEND
───────────────────────────────────────── -->
<line x1="30" y1="532" x2="52" y2="532"
stroke="#E8A838" stroke-width="1.5" marker-end="url(#arr-a)"/>
<text x="57" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10" fill="#5A6A7C">active flow</text>
<line x1="145" y1="532" x2="167" y2="532"
stroke="#5A6A7C" stroke-width="1.2" marker-end="url(#arr-s)"/>
<text x="172" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10" fill="#5A6A7C">reads / executes</text>
<line x1="290" y1="532" x2="312" y2="532"
stroke="#2A5080" stroke-width="1.2" stroke-dasharray="4,3" marker-end="url(#arr-o)"/>
<text x="317" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10" fill="#5A6A7C">optional</text>
<rect x="393" y="525" width="12" height="12" rx="2"
fill="none" stroke="#8090A4" stroke-opacity=".5" stroke-width="1" stroke-dasharray="4,2"/>
<text x="410" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10" fill="#5A6A7C">self-description</text>
<text x="878" y="537"
font-family="'IBM Plex Mono',monospace" font-size="10"
fill="#1E2A3A" text-anchor="end">ontoref architecture</text>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,228 +0,0 @@
# OntoRef Branding Assets
Complete branding system for OntoRef - Structure that remembers why.
## Directory Structure
```text
branding/
├── README.md # This file
├── ontoref-h.svg # Horizontal logo (animated)
├── ontoref-v.svg # Vertical logo (animated)
├── ontoref-icon.svg # Icon variant (image only)
└── ontoref-text.svg # Text variant (wordmark only)
```
## Logo Variants
### Primary Logos (4 total)
| Variant | Use Case |
|---------|----------|
| `ontoref-h.svg` | Horizontal, animated — Banners, headers |
| `ontoref-v.svg` | Vertical, animated — Posters, mobile |
| `ontoref-icon.svg` | Icon, animated — Standalone image |
| `ontoref-text.svg` | Wordmark — Text-only variant |
### Static Variants (3 total - no animations)
| Variant | Use Case |
|---------|----------|
| `ontoref-h-static.svg` | Print, documents (light background) |
| `ontoref-v-static.svg` | Print, posters (light background) |
| `ontoref-icon-static.svg` | Print, icons (light background) |
### Dark Mode Variants (2 total - dark background)
| Variant | Use Case |
|---------|----------|
| `ontoref-dark-h.svg` | Dark UI, dark mode apps, dark websites |
| `ontoref-dark-v.svg` | Dark backgrounds, dark applications |
### Monochrome Variants (4 total - black and white)
| Variant | Use Case |
|---------|----------|
| `ontoref-mono-black-h.svg` | Print, documents (light background) |
| `ontoref-mono-black-v.svg` | Print documents (light background) |
| `ontoref-mono-white-h.svg` | Dark backgrounds, dark print |
| `ontoref-mono-white-v.svg` | Dark backgrounds, dark print |
**Note**: Black and white variants are identical in this version.
## Color Palette
Core brand colors:
- **Silver** (`#C0CCD8`) - Ref/Structure side, light elements
- **Amber** (`#E8A838`) - Onto/Graph side, accent color
- **Dark Background** (`#0F1319`) - Dark mode backgrounds
- **Gray** (`#8090A4`, `#5A6A7C`) - Secondary elements
## Usage Guidelines
### Responsive Design
All logos are designed with `viewBox` only (no fixed width/height) for maximum responsiveness:
- Use in HTML with CSS sizing: `<img src="ontoref-h.svg" class="logo">`
- Add CSS: `.logo { max-width: 100%; height: auto; }`
- Logo scales proportionally to its container
### Logo Sizing Recommendations
- **Horizontal (`-h`)**: Use for banners, headers, wide layouts
- **Vertical (`-v`)**: Use for posters, splash screens, vertical layouts
- **Icon**: Use as symbol, favicon, or standalone image
- **Text**: Use as wordmark, branding element
### Layout Rules
- Maintain minimum 15px clear space around logos
- Never distort, rotate, or modify aspect ratio
- Prefer animated variants for digital displays
- Use static variants for print, PDFs, email
### Dark Mode
- Dark backgrounds (`#0F1319` or darker) provide optimal contrast
- Logos automatically adapt due to color scheme design
- Silver elements bright on dark, Amber accent stands out
### Accessibility
- All color combinations meet WCAG AA contrast standards
- Logos use geometric shapes (octagon, circles, rectangles) for clarity
- Descriptive alt-text: "OntoRef - Structure that remembers why"
- No flashing or strobing animations
## Design Elements
- **Octagon Frame**: Eight-sided structure representing solid foundation
- **Silver Region** (Left): Represents Ref (reference), structure, organization
- **Amber Region** (Right): Represents Onto (ontology), graph, relationships
- **S-Curve**: Dividing line showing duality and balance
- **Seed Elements**: Small visual markers in each region
- **DAG Visualization**: Shows graph relationships in Onto region
- **Grid Pattern**: Shows structured data in Ref region
## Asset Features
✨ **Responsive SVG**
- No fixed dimensions - scales to any size
- ViewBox-based for perfect proportions
- Works in all modern browsers
🎨 **Animated Elements**
- Floating elements with subtle motion
- Pulsing visual elements
- Smooth opacity transitions
📊 **Dual-Region Design**
- Left side: Silver (Structure/Reference)
- Right side: Amber (Ontology/Graph)
- Represents the duality of OntoRef
## Technical Specifications
### SVG Features
- Linear gradients for depth and visual interest
- SMIL animations for smooth motion effects
- Responsive viewBox dimensions
- Clean, minimal markup for fast loading
### Color Gradients
- **Silver Gradient**: `#E6ECF2``#BCC8D4``#8090A4`
- **Amber Gradient**: `#B87000``#E0B040``#F8D860`
- **Node Gradients**: Silver and Amber variants for visual hierarchy
## Animations
### Animated Elements
- **Floating motion**: Subtle translateY animation on main symbol
- **Pulsing opacity**: Grid blocks and nodes fade in/out
- **Drawing effects**: DAG lines animate with stroke-dashoffset
- **Glow effects**: Soft gaussian blur on animated elements
### Animation Timing
- Base cycle: 3.5s - 7s for different elements
- Ease-in-out interpolation for smooth motion
- Staggered delays for visual sequence
- Infinite loop for continuous operation
## File Information
### All Variants
- **Primary Animated** (4 files): ~13KB each (h, v, icon) + ~1KB (text)
- **Static Variants** (3 files): ~13KB each
- **Dark Variants** (2 files): ~13KB each
- **Monochrome Variants** (4 files): ~13KB each
**Total Assets**: 13 SVG files (~150KB total)
All files are SVG format with responsive viewBox (no fixed dimensions).
## Version Information
- **Created**: 2026-03-11
- **Version**: 1.0
- **Format**: SVG + responsive design
- **Compatibility**: All modern browsers
- **Total Assets**: 4 (2 logos, 1 icon, 1 text)
## Brand Philosophy
OntoRef represents:
- 🏗️ **Duality**: Structure (Ref) and Ontology (Onto) working together
- 🔗 **Connection**: Relationships between data and organization
- 💾 **Memory**: "Structure that remembers why"
- 📊 **Organization**: Clear, hierarchical representation
- ✨ **Motion**: Active, living data structures
- 🎯 **Balance**: Symmetry between form and function
## Usage Examples
### HTML Image
```html
<img src="ontoref-h.svg" alt="OntoRef - Structure that remembers why" class="logo">
```
### CSS Sizing
```css
.logo {
max-width: 100%;
height: auto;
width: 100%;
}
.logo-small {
width: 200px;
}
.logo-large {
width: 800px;
}
```
### SVG Inline
```html
<svg viewBox="0 0 575 250">
<!-- Logo content scales responsively -->
</svg>
```
## Related Documentation
- Main project: `/Users/Akasha/Development/ontoref/`
- Logo files location: `/assets/branding/`
- Original SVG files: `/assets/ontoref*.svg`
---
**All assets in the OntoRef branding system are optimized for scalability, accessibility, and brand consistency across digital and print media.**

File diff suppressed because it is too large Load diff

View file

@ -1,195 +0,0 @@
<svg viewBox="0 0 575 250" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<rect width="100%" height="100%" fill="#0F1319"/>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(-30, -22) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="400" y="131" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="398" y="156" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,195 +0,0 @@
<svg viewBox="0 0 380 340" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<rect width="100%" height="100%" fill="#0F1319"/>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(40, -20) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="190" y="290" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="190" y="315" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,197 +0,0 @@
<svg viewBox="0 0 575 250" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
/* Static variant: disable all animations */
.bp3, .nf3, .np3, .de3, .ap3, .sAm, .sSi, .fl3, .fi3, .cB3, .rP3, .tP, .cD {
animation: none !important;
}
</style>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(-30, -22) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="400" y="131" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="398" y="156" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,193 +0,0 @@
<svg viewBox="0 0 575 250" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(-30, -22) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="400" y="131" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="398" y="156" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,79 +0,0 @@
<svg viewBox="60 30 480 480" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
</defs>
<!-- ═══ PAKUA FRAME ═══ -->
<!-- Outer octagon -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="2.5" stroke-opacity="0.8"/>
<!-- Inner decorative ring -->
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="2" stroke-opacity="0.7"/>
<!-- ═══ MAIN SYMBOL ═══ -->
<g>
<!-- Octagon outline -->
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="#8090A4" stroke-width="1.5" stroke-opacity="0.4"/>
<!-- SILVER REGION (Ref / Structure) -->
<g clip-path="url(#sC3)">
<!-- Background region -->
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z"
fill="#C0CCD8" opacity="0.85"/>
<!-- Grid blocks -->
<rect x="165" y="160" width="52" height="40" rx="4" fill="#E8A838" opacity="0.75" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
<rect x="235" y="223" width="48" height="38" rx="4" fill="#E8A838" opacity="0.70" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
<rect x="163" y="220" width="55" height="46" rx="4" fill="#E8A838" opacity="0.68" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
<rect x="168" y="345" width="25" height="18" rx="4" fill="#E8A838" opacity="0.65" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
<rect x="220" y="290" width="35" height="38" rx="4" fill="#E8A838" opacity="0.68" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
<rect x="160" y="285" width="40" height="32" rx="4" fill="#E8A838" opacity="0.62" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
<rect x="220" y="350" width="28" height="24" rx="4" fill="#E8A838" opacity="0.62" stroke="rgba(255,255,255,.5)" stroke-width="1"/>
<!-- Seed circle in silver -->
<circle cx="265" cy="170" r="16" fill="#E8A838" opacity="0.88" stroke="rgba(255,255,255,.6)" stroke-width="2"/>
</g>
<!-- AMBER REGION (Onto / Graph) -->
<g clip-path="url(#aC3)">
<!-- Background region -->
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z"
fill="#E8A838" opacity="0.85"/>
<!-- DAG Lines -->
<line x1="386" y1="162" x2="355" y2="214" stroke="#6B7280" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
<line x1="405" y1="162" x2="440" y2="214" stroke="#6B7280" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
<line x1="364" y1="247" x2="398" y2="294" stroke="#6B7280" stroke-width="3" stroke-linecap="round" opacity="0.65"/>
<line x1="431" y1="247" x2="398" y2="294" stroke="#6B7280" stroke-width="3" stroke-linecap="round" opacity="0.65"/>
<line x1="394" y1="331" x2="385" y2="368" stroke="#6B7280" stroke-width="2.5" stroke-linecap="round" opacity="0.6"/>
<!-- Arrowheads -->
<polygon points="349,210 357,222 363,212" fill="#6B7280" opacity="0.7"/>
<polygon points="434,210 442,222 448,210" fill="#6B7280" opacity="0.7"/>
<polygon points="392,290 400,302 406,290" fill="#6B7280" opacity="0.65"/>
<polygon points="379,365 387,377 393,365" fill="#6B7280" opacity="0.6"/>
<!-- Nodes -->
<circle cx="395" cy="148" r="17" fill="#A8B4C8" opacity="0.85" stroke="rgba(255,255,255,.6)" stroke-width="2"/>
<circle cx="355" cy="236" r="14" fill="#A8B4C8" opacity="0.82" stroke="rgba(255,255,255,.6)" stroke-width="1.5"/>
<circle cx="440" cy="236" r="14" fill="#A8B4C8" opacity="0.82" stroke="rgba(255,255,255,.6)" stroke-width="1.5"/>
<circle cx="398" cy="316" r="15" fill="#A8B4C8" opacity="0.83" stroke="rgba(255,255,255,.6)" stroke-width="1.5"/>
<circle cx="385" cy="390" r="12" fill="#A8B4C8" opacity="0.80" stroke="rgba(255,255,255,.6)" stroke-width="1.5"/>
<!-- Seed square in amber -->
<rect x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity="0.9" stroke="rgba(255,255,255,.6)" stroke-width="2"/>
</g>
<!-- S-CURVE divider -->
<path d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.15)" stroke-width="2"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -1,197 +0,0 @@
<svg viewBox="0 0 600 700" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<!-- ═══ PAKUA FRAME ═══ -->
<!-- Outer octagon R=218 -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<!-- Inner decorative ring R=208 -->
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks at 8 positions -->
<!-- Top (☰ heaven: 3 solid) — silver -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<!-- Bottom (☷ earth: 3 broken) — amber -->
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<!-- Right (☲ fire) — amber -->
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- Left (☵ water) — silver -->
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- TR, TL, BR, BL — alternating -->
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- ═══ MAIN SYMBOL ═══ -->
<g class="fl3" transform="scale(0.65) translate(64, 0)">
<!-- Octagon outline R=195 -->
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION (Ref / Structure) -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<!-- Grid blocks (two columns) -->
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<!-- ★ SEED: amber circle in silver -->
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION (Onto / Graph) -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<!-- DAG -->
<g filter="url(#nGl3)">
<!-- Line 1: 395,155 → 355,236 (nodo 2, r=14) termina antes del nodo -->
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<!-- Line 2: 395,155 → 440,236 (nodo 3, r=14) termina antes del nodo -->
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<!-- Line 3: 355,244 → 398,316 (nodo 4, r=15) termina antes del nodo -->
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<!-- Line 4: 440,244 → 398,316 (nodo 4, r=15) termina antes del nodo -->
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<!-- Line 5: 398,324 → 385,390 (nodo 5, r=12) termina antes del nodo -->
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<!-- Arrowheads -->
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<!-- Nodes (silver gradient) -->
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<!-- ★ SEED: silver square in amber -->
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,193 +0,0 @@
<svg viewBox="0 0 575 250" fill="none" xmlns="http://www.w3.org/2000/svg" style="filter: grayscale(1) contrast(1.1);">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(-30, -22) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="400" y="131" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="398" y="156" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,193 +0,0 @@
<svg viewBox="0 0 380 340" fill="none" xmlns="http://www.w3.org/2000/svg" style="filter: grayscale(1) contrast(1.1);">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(40, -20) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="190" y="290" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="190" y="315" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,193 +0,0 @@
<svg viewBox="0 0 575 250" fill="none" xmlns="http://www.w3.org/2000/svg" style="filter: grayscale(1) invert(1) contrast(1.1);">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(-30, -22) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="400" y="131" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="398" y="156" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,193 +0,0 @@
<svg viewBox="0 0 380 340" fill="none" xmlns="http://www.w3.org/2000/svg" style="filter: grayscale(1) invert(1) contrast(1.1);">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(40, -20) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="190" y="290" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="190" y="315" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,19 +0,0 @@
<svg viewBox="0 0 400 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
.fi3{animation:fi3 1s ease-out .5s both}
</style>
</defs>
<!-- Wordmark -->
<g class="fi3">
<text x="200" y="85" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="92" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="200" y="145" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="22" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why.
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 878 B

View file

@ -1,197 +0,0 @@
<svg viewBox="0 0 380 340" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
/* Static variant: disable all animations */
.bp3, .nf3, .np3, .de3, .ap3, .sAm, .sSi, .fl3, .fi3, .cB3, .rP3, .tP, .cD {
animation: none !important;
}
</style>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(40, -20) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="190" y="290" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="190" y="315" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,193 +0,0 @@
<svg viewBox="0 0 380 340" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,75 C405,195 195,345 300,465 L162,408 L105,270 L162,132 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,75 C405,195 195,345 300,465 L438,408 L495,270 L438,132 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(40, -20) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M300,52 L454,117 L518,270 L454,423 L300,488 L146,423 L82,270 L146,117 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M300,62 L447,124 L508,270 L447,416 L300,478 L153,416 L92,270 L153,124 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="488" x2="296" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="488" x2="320" y2="488" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="494" x2="295" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="494" x2="318" y2="494" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="462" y1="110" x2="472" y2="118" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="103" x2="468" y2="111" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="128" y1="118" x2="138" y2="110" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="111" x2="142" y2="103" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="462" y1="430" x2="472" y2="422" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="458" y1="437" x2="468" y2="429" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="128" y1="422" x2="138" y2="430" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="132" y1="429" x2="142" y2="437" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="300" cy="52" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="454" cy="117" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="518" cy="270" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="454" cy="423" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="300" cy="488" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="146" cy="423" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="82" cy="270" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="146" cy="117" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- MAIN SYMBOL -->
<g class="fl3">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 L162,408 L105,270 L162,132 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,75 L162,132 L105,270 L162,408 L300,465 C195,345 405,195 300,75Z" fill="url(#sG3)" opacity=".93"/>
<rect class="bp3" x="165" y="160" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="235" y="223" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="163" y="220" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="168" y="345" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="220" y="290" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="160" y="285" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="220" y="350" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="265" cy="170" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,75 L438,132 L495,270 L438,408 L300,465 C195,345 405,195 300,75Z" fill="url(#aG3)" opacity=".88"/>
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="148" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
<rect class="sAm" x="320" y="330" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,75 C405,195 195,345 300,465"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="190" y="290" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="190" y="315" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,203 +0,0 @@
<svg viewBox="0 0 380 340" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,90 C360,240 240,300 300,450 L225,450 L120,345 L120,195 L225,90 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,90 L375,90 L480,195 L480,345 L375,450 L300,450 C240,300 360,240 300,90 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes fi3{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.fi3{animation:fi3 1s ease-out .5s both}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<rect width="100%" height="100%" fill="#0F1319"/>
<!-- ═══ IMAGE GROUP ═══ -->
<g id="image-group" transform="translate(40, -20) scale(0.50)">
<!-- ═══ PAKUA FRAME ═══ -->
<path d="M383,69 L501,188 L501,353 L384,470 L217,471 L99,353 L99,187 L216,70 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<path d="M380,78 L492,191 L492,350 L380,461 L220,462 L108,349 L108,190 L220,79 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="478" x2="296" y2="478" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="478" x2="320" y2="478" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="484" x2="295" y2="484" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="484" x2="318" y2="484" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="282" y1="490" x2="295" y2="490" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="305" y1="490" x2="318" y2="490" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="509" y1="174" x2="520" y2="183" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="505" y1="168" x2="516" y2="177" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="80" y1="174" x2="91" y2="183" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="84" y1="168" x2="95" y2="177" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="509" y1="357" x2="520" y2="367" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="505" y1="363" x2="516" y2="372" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="80" y1="357" x2="91" y2="367" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="84" y1="363" x2="95" y2="372" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots -->
<circle class="cD" cx="383" cy="69" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="501" cy="188" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="501" cy="353" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="384" cy="470" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="217" cy="471" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="99" cy="353" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="99" cy="187" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="216" cy="70" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- ═══ MAIN SYMBOL ═══ -->
<g class="fl3">
<path d="M375,90 L480,195 L480,345 L375,450 L225,450 L120,345 L120,195 L225,90 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION -->
<g clip-path="url(#sC3)">
<path d="M300,90 C360,240 240,300 300,450 L225,450 L120,345 L120,195 L225,90 Z" fill="url(#sG3)" opacity=".93"/>
<g transform="translate(200,270) scale(0.88) translate(-200,-270)">
<rect class="bp3" x="148" y="170" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="240" y="230" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="146" y="227" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="152" y="353" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="235" y="297" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="143" y="293" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="232" y="357" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<circle class="sAm" cx="241" cy="157" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
</g>
<!-- AMBER REGION -->
<g clip-path="url(#aC3)">
<path d="M300,90 L375,90 L480,195 L480,345 L375,450 L300,450 C240,300 360,240 300,90 Z" fill="url(#aG3)" opacity=".88"/>
<g transform="translate(390,260) scale(0.88) translate(-390,-270)">
<g transform="rotate(-10, 395, 267)">
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="155" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
</g>
<rect class="sAm" x="328" y="353" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,90 C360,240 240,300 300,450"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</g>
<!-- ═══ TEXT GROUP ═══ -->
<g id="text-group" class="fi3">
<text x="190" y="290" text-anchor="middle" font-family="'IBM Plex Mono',monospace" font-size="72" font-weight="600" letter-spacing=".03em">
<tspan fill="#C0CCD8">Onto</tspan><tspan fill="#E8A838">ref</tspan>
</text>
<text x="190" y="315" text-anchor="middle" font-family="'IBM Plex Sans',sans-serif" font-size="16" font-weight="500" fill="#A8B4C8" letter-spacing=".03em" opacity=".9">
Structure that remembers why
</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,203 +0,0 @@
<svg viewBox="60 30 480 490" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="sG3" x1="0" y1="0" x2=".5" y2="1">
<stop offset="0%" stop-color="#E6ECF2"/>
<stop offset="50%" stop-color="#BCC8D4"/>
<stop offset="100%" stop-color="#8090A4"/>
</linearGradient>
<linearGradient id="aG3" x1=".3" y1="0" x2=".8" y2="1">
<stop offset="0%" stop-color="#B87000"/>
<stop offset="50%" stop-color="#E0B040"/>
<stop offset="100%" stop-color="#F8D860"/>
</linearGradient>
<linearGradient id="aN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#F5C44A"/>
<stop offset="100%" stop-color="#DC9018"/>
</linearGradient>
<linearGradient id="sN3" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#A8B4C8"/>
<stop offset="100%" stop-color="#5A6A7C"/>
</linearGradient>
<clipPath id="sC3">
<path d="M300,90 C360,240 240,300 300,450 L225,450 L120,345 L120,195 L225,90 Z"/>
</clipPath>
<clipPath id="aC3">
<path d="M300,90 L375,90 L480,195 L480,345 L375,450 L300,450 C240,300 360,240 300,90 Z"/>
</clipPath>
<filter id="nGl3" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feFlood flood-color="#E8A838" flood-opacity=".1"/>
<feComposite in2="b" operator="in" result="g"/>
<feComposite in="SourceGraphic" in2="g" operator="over"/>
</filter>
<filter id="nSh3"><feDropShadow dx="0" dy="2" stdDeviation="3.5" flood-color="#DC9018" flood-opacity=".28"/></filter>
<filter id="sGl3"><feGaussianBlur stdDeviation="1.5" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
<filter id="cGl3"><feGaussianBlur stdDeviation="3" result="b"/><feComposite in="SourceGraphic" in2="b" operator="over"/></filter>
</defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&amp;display=swap');
@keyframes bp3{0%,100%{opacity:calc(var(--o,.82) - .20)}50%{opacity:var(--o,.82)}}
@keyframes nf3{0%,100%{transform:translate(0,0)}50%{transform:translate(0,-2px)}}
@keyframes np3{0%,100%{r:var(--r,17)}50%{r:calc(var(--r,17) + 1.8)}}
@keyframes de3{0%{stroke-dashoffset:210}100%{stroke-dashoffset:0}}
@keyframes eb3{0%,100%{opacity:.48}50%{opacity:.88}}
@keyframes ap3{0%,100%{opacity:.42}50%{opacity:.92}}
@keyframes seedAmber{0%,100%{opacity:.72;r:16}50%{opacity:.88;r:18}}
@keyframes seedSilver{0%,100%{opacity:.32}50%{opacity:.52}}
@keyframes fl3{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
@keyframes cB3{0%,100%{stroke-opacity:.1}50%{stroke-opacity:.2}}
@keyframes rP3{0%,100%{stroke-opacity:.04}50%{stroke-opacity:.068}}
@keyframes tP{0%,100%{opacity:calc(var(--to,.08)*5)}50%{opacity:calc(var(--to,.08)*5 + .25)}}
@keyframes cD{0%,100%{r:2.5;opacity:.60}50%{r:3.5;opacity:.80}}
.bp3{animation:bp3 4.5s ease-in-out infinite}
.nf3{animation:nf3 5s ease-in-out infinite}
.np3{animation:np3 3.5s ease-in-out infinite}
.de3{stroke-dasharray:210;animation:de3 1.8s ease-out forwards,eb3 4s ease-in-out 2s infinite}
.ap3{animation:ap3 3s ease-in-out infinite}
.sAm{animation:seedAmber 4s ease-in-out infinite}
.sSi{animation:seedSilver 4s ease-in-out infinite}
.fl3{animation:fl3 7s ease-in-out infinite}
.cB3{animation:cB3 5s ease-in-out infinite}
.rP3{animation:rP3 7s ease-in-out infinite}
.tP{animation:tP 5.5s ease-in-out infinite}
.cD{animation:cD 4s ease-in-out infinite}
</style>
<!-- ═══ PAKUA FRAME ═══ -->
<!-- Outer octagon R=218 rotated 22.5° -->
<path d="M383,69 L501,188 L501,353 L384,470 L217,471 L99,353 L99,187 L216,70 Z"
fill="none" stroke="#E8A838" stroke-width="1.5" stroke-opacity=".35"/>
<!-- Inner decorative ring R=208 rotated 22.5° -->
<path d="M380,78 L492,191 L492,350 L380,461 L220,462 L108,349 L108,190 L220,79 Z"
fill="none" stroke="#C0CCD8" stroke-width="1.2" stroke-opacity=".30"/>
<!-- Trigram marks at 8 positions -->
<!-- Top (☰ heaven: 3 solid) — silver, flat side -->
<g class="tP" style="--to:.1">
<line x1="278" y1="50" x2="322" y2="50" stroke="#C0CCD8" stroke-width="2.2" stroke-linecap="round"/>
<line x1="281" y1="57" x2="319" y2="57" stroke="#C0CCD8" stroke-width="1.8" stroke-linecap="round"/>
<line x1="284" y1="63" x2="316" y2="63" stroke="#C0CCD8" stroke-width="1.5" stroke-linecap="round"/>
</g>
<!-- Bottom (☷ earth: 3 broken) — amber, flat side -->
<g class="tP" style="--to:.08;animation-delay:2.75s">
<line x1="280" y1="478" x2="296" y2="478" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="304" y1="478" x2="320" y2="478" stroke="#E8A838" stroke-width="2.2" stroke-linecap="round"/>
<line x1="282" y1="484" x2="295" y2="484" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="305" y1="484" x2="318" y2="484" stroke="#E8A838" stroke-width="1.8" stroke-linecap="round"/>
<line x1="282" y1="490" x2="295" y2="490" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="305" y1="490" x2="318" y2="490" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
</g>
<!-- Right (☲ fire) — amber, flat side -->
<g class="tP" style="--to:.07;animation-delay:.7s">
<line x1="518" y1="252" x2="518" y2="266" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="518" y1="274" x2="518" y2="278" stroke="#E8A838" stroke-width="1.5" stroke-linecap="round"/>
<line x1="518" y1="286" x2="518" y2="300" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- Left (☵ water) — silver, flat side -->
<g class="tP" style="--to:.07;animation-delay:2s">
<line x1="82" y1="252" x2="82" y2="260" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="268" x2="82" y2="282" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="82" y1="290" x2="82" y2="298" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- TR diagonal (near vertex 501,188) — amber -->
<g class="tP" style="--to:.06;animation-delay:1.1s">
<line x1="509" y1="174" x2="520" y2="183" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="505" y1="168" x2="516" y2="177" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- TL diagonal (near vertex 99,187) — silver -->
<g class="tP" style="--to:.06;animation-delay:3.8s">
<line x1="80" y1="174" x2="91" y2="183" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="84" y1="168" x2="95" y2="177" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- BR diagonal (near vertex 501,353) — amber -->
<g class="tP" style="--to:.06;animation-delay:4.4s">
<line x1="509" y1="357" x2="520" y2="367" stroke="#E8A838" stroke-width="2" stroke-linecap="round"/>
<line x1="505" y1="363" x2="516" y2="372" stroke="#E8A838" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- BL diagonal (near vertex 99,353) — silver -->
<g class="tP" style="--to:.06;animation-delay:5s">
<line x1="80" y1="357" x2="91" y2="367" stroke="#C0CCD8" stroke-width="2" stroke-linecap="round"/>
<line x1="84" y1="363" x2="95" y2="372" stroke="#C0CCD8" stroke-width="1.6" stroke-linecap="round"/>
</g>
<!-- Corner dots at new vertex positions -->
<circle class="cD" cx="383" cy="69" r="2.5" fill="#C0CCD8" style="animation-delay:0s"/>
<circle class="cD" cx="501" cy="188" r="2.5" fill="#E8A838" style="animation-delay:.55s"/>
<circle class="cD" cx="501" cy="353" r="2.5" fill="#E8A838" style="animation-delay:1.1s"/>
<circle class="cD" cx="384" cy="470" r="2.5" fill="#E8A838" style="animation-delay:1.65s"/>
<circle class="cD" cx="217" cy="471" r="2.5" fill="#E8A838" style="animation-delay:2.2s"/>
<circle class="cD" cx="99" cy="353" r="2.5" fill="#C0CCD8" style="animation-delay:2.75s"/>
<circle class="cD" cx="99" cy="187" r="2.5" fill="#C0CCD8" style="animation-delay:3.3s"/>
<circle class="cD" cx="216" cy="70" r="2.5" fill="#C0CCD8" style="animation-delay:3.85s"/>
<!-- ═══ MAIN SYMBOL ═══ -->
<g class="fl3">
<!-- Octagon outline R=195 rotated 22.5° -->
<path d="M375,90 L480,195 L480,345 L375,450 L225,450 L120,345 L120,195 L225,90 Z"
fill="none" stroke="rgba(255,255,255,.055)" stroke-width="1.5"/>
<!-- SILVER REGION (Ref / Structure) -->
<g clip-path="url(#sC3)">
<path d="M300,90 C360,240 240,300 300,450 L225,450 L120,345 L120,195 L225,90 Z" fill="url(#sG3)" opacity=".93"/>
<g transform="translate(200,270) scale(0.88) translate(-200,-270)">
<!-- Grid blocks — col1 x≈143-152, col2 x≈240-243 -->
<rect class="bp3" x="148" y="170" width="52" height="40" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".88" style="--o:.88;animation-delay:0.9s"/>
<rect class="bp3" x="240" y="230" width="48" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".82" style="--o:.82;animation-delay:0.7s"/>
<rect class="bp3" x="146" y="227" width="55" height="46" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.5s"/>
<rect class="bp3" x="152" y="353" width="25" height="18" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".70" style="--o:.70;animation-delay:0.15s"/>
<rect class="bp3" x="235" y="297" width="35" height="38" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".74" style="--o:.74;animation-delay:0.3s"/>
<rect class="bp3" x="143" y="293" width="40" height="32" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:0s"/>
<rect class="bp3" x="232" y="357" width="28" height="24" rx="4" fill="url(#aG3)" stroke="rgba(255,255,255,.25)" stroke-width="1" opacity=".64" style="--o:.64;animation-delay:.8s"/>
<!-- ★ SEED: amber circle in silver -->
<circle class="sAm" cx="241" cy="157" r="16" fill="url(#aN3)" opacity=".72" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/>
</g>
</g>
<!-- AMBER REGION (Onto / Graph) -->
<g clip-path="url(#aC3)">
<path d="M300,90 L375,90 L480,195 L480,345 L375,450 L300,450 C240,300 360,240 300,90 Z" fill="url(#aG3)" opacity=".88"/>
<g transform="translate(390,260) scale(0.88) translate(-390,-270)">
<!-- DAG rotated 20° around centroid (395,267) -->
<g transform="rotate(-10, 395, 267)">
<g filter="url(#nGl3)">
<line class="de3" x1="386" y1="162" x2="355" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.15s"/>
<line class="de3" x1="405" y1="162" x2="440" y2="214" stroke="#8090A4" stroke-width="4.5" stroke-linecap="round" opacity=".45" style="animation-delay:.35s"/>
<line class="de3" x1="364" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.6s"/>
<line class="de3" x1="431" y1="247" x2="398" y2="294" stroke="#8090A4" stroke-width="4" stroke-linecap="round" opacity=".4" style="animation-delay:.8s"/>
<line class="de3" x1="394" y1="331" x2="385" y2="368" stroke="#8090A4" stroke-width="3.5" stroke-linecap="round" opacity=".35" style="animation-delay:1.05s"/>
<!-- Arrowheads -->
<polygon class="ap3" points="349,210 357,222 363,212" fill="#8090A4" opacity=".45" style="animation-delay:.15s"/>
<polygon class="ap3" points="434,210 442,222 448,210" fill="#8090A4" opacity=".45" style="animation-delay:.35s"/>
<polygon class="ap3" points="392,290 400,302 406,290" fill="#8090A4" opacity=".4" style="animation-delay:.6s"/>
<polygon class="ap3" points="379,365 387,377 393,365" fill="#8090A4" opacity=".35" style="animation-delay:1.05s"/>
</g>
<!-- Nodes -->
<g class="nf3" style="animation-delay:0s">
<circle class="np3" cx="395" cy="155" r="17" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".75" style="--r:17"/>
</g>
<g class="nf3" style="animation-delay:.3s">
<circle class="np3" cx="355" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.5s">
<circle class="np3" cx="440" cy="236" r="14" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".7" style="--r:14"/>
</g>
<g class="nf3" style="animation-delay:.8s">
<circle class="np3" cx="398" cy="316" r="15" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".72" style="--r:15"/>
</g>
<g class="nf3" style="animation-delay:1.1s">
<circle class="np3" cx="385" cy="390" r="12" fill="url(#sN3)" stroke="rgba(255,255,255,.3)" stroke-width="1.5" opacity=".65" style="--r:12"/>
</g>
</g>
<!-- ★ SEED: silver square in amber -->
<rect class="sAm" x="328" y="353" width="26" height="26" rx="3" fill="#5A6A7C" opacity=".85" stroke="rgba(255,255,255,.35)" stroke-width="1.5"/>
</g>
</g>
<!-- S-CURVE -->
<path class="cB3" d="M300,90 C360,240 240,300 300,450"
fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.8" filter="url(#cGl3)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,112 +0,0 @@
That tagline actually lands quite elegantly — **“Ontoref — SelfDescribing Ontology&Reflection Protocol for Software Ecosystems.”**
Lets dissect it a bit to check conceptual and tonal alignment:
***
### Conceptual Fit
- **Ontoref**: Already suggests *reference* and *reflection* — a protocol that both *names* and *mirrors* what *is*.
- **SelfDescribing Ontology**: Communicates that systems using Ontoref *contain their own metadata schema* — perfect for a protocol that formalizes being.
- **Reflection Protocol**: Adds the operational side — mechanisms for introspection, state tracking, and evolution.
- **for Software Ecosystems**: Sets scope and audience clearly: beyond individual services, toward coordination layers and multiproject semantics.
Altogether it reads as a **metasystem framework name** — the kind you associate with foundational standards like gRPC, OCI, or RDF — but with much softer humanistic overtones.
***
### Stylistic Notes
- The rhythm (shortlongshort phrase) flows well in speech.
- Technically precise yet philosophical enough to intrigue.
- “SelfDescribing Ontology” establishes seriousness; “Reflection Protocol” adds motion and pragmatism.
If you want a subtle tonal tweak, here are a few variants worth testing depending on the emphasis you want:
1. **Ontoref — The Reflection & Ontology Protocol for Living Codebases**
(adds the “living” and operational emphasis)
2. **Ontoref — Ontology Meets Reflection**
(minimalist, good for logo/subtitle)
3. **Ontoref — A Protocol for SelfEvolving Knowledge Graphs**
(if you want stronger graph/DAG overtones)
4. **Ontoref — Coordinating Being and Change Across Ecosystems**
(philosophical yet coherent with your yin/yang frame)
***
Youve actually hit a great linguistic equilibrium: **Ontoref** feels protocollike, but the subtitle humanizes it without sounding handwavy.
Would you like me to help you craft a **onesentence “elevator definition”** or short **manifesto line** that could accompany this tagline
Both of those are strong — subtle nuance makes them convey quite different worldviews. Lets look at how each *feels and positions* the project:
***
### **1. “EvolvingCodebases”**
**Tone:** Technical, analytical, process-oriented.
**Implication:** Systems that *change over time* — code refactors, architecture shifts, versioning.
**Effect:** Appeals to engineers thinking in terms of growth, mutation, and lifecycle automation.
**Vibe:** Rational evolution and semantic versioning.
**Parallel concepts:** Darwinian adaptation, software as organism—but through an engineering lens.
*Ontoref — A SelfDescribing Ontology&Reflection Protocol for EvolvingCodebases*
Feels precise, architectural, almost *standard-spec* quality.
***
### **2. “LivingCodebases”**
**Tone:** Philosophical, organic, and relational.
**Implication:** Codebases *as organisms* — aware, reflective, and selfmaintaining.
**Effect:** Evokes vitality: continuous presence and adaptation, not just change.
**Vibe:** Systemic consciousness; stronger yin/yang resonance (being/doing coexistence).
**Parallel concepts:** “Living documentation”, “cybernetic ecosystems.”
*Ontoref — A SelfDescribing Ontology&Reflection Protocol for LivingCodebases*
Feels inspiring and poetic, perfect if the philosophy of *reciprocity between being and doing* is central.
***
### Quick synthesis
| Phrase | Tone | Emphasis | Ideal for |
|---------|------|-----------|------------|
| **EvolvingCodebases** | Technical, precise | versioning, ontological diffs | engineering audience, protocol docs |
| **LivingCodebases** | Philosophical, organic | continuous awareness | broader vision or manifesto framing |
***
If we treat *Ontoref* as both a **protocol** and a **practice**, *“LivingCodebases”* encapsulates that duality best — code that *knows itself and grows responsibly.*
Here are a few tagline options that harmonize with **Ontorefs** core idea — the coexistence of *ontology (being)* and *reflection (becoming)* — and fit both technical and evocative contexts. Ive grouped them by tone so you can choose depending on whether you want it to read as a spec, a philosophy, or a movement.
***
### **Balanced / Core identity**
- **“Where code knows what it is.”**
(succinct, direct, and selfreferential — great for a homepage header)
- **“Ontology in motion.”**
(captures the yinyang dynamic in three words)
- **“Reflective structure for evolving systems.”**
***
### **Technical / Spec tone**
- **“A protocol for selfdescribing software ecosystems.”**
- **“Formal semantics for living architectures.”**
- **“The ontology backbone for reflective codebases.”**
***
### **Philosophical / Visionary tone**
- **“Being and becoming, encoded.”**
- **“Structure that remembers why.”**
- **“The mirror where software finds itself.”**
- **“From ontology to operation — one loop.”**
***
### **Paired example (header+subtitle)**
> **Ontoref — Where code knows what it is**
> *A selfdescribing ontology&reflection protocol for living codebases.*
***
Would you like me to refine a **single signature pair** (headline+subtitle) tuned specifically for your intended audience — e.g. *architects/spec authors*, *opensource ecosystem builders*, or *AIintegrated systems*?

View file

@ -1,14 +0,0 @@
{
"private": true,
"type": "module",
"packageManager": "pnpm@10.7.0",
"scripts": {
"build": "unocss '../../crates/ontoref-daemon/templates/**/*.html' -o ../../crates/ontoref-daemon/public/css/ontoref.css --minify",
"watch": "unocss '../../crates/ontoref-daemon/templates/**/*.html' -o ../../crates/ontoref-daemon/public/css/ontoref.css --watch"
},
"devDependencies": {
"@unocss/cli": "^66.3.2",
"unocss": "^66.3.2",
"unocss-preset-daisy": "^7.0.0"
}
}

View file

@ -1,11 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'../../crates/ontoref-daemon/templates/**/*.html',
],
plugins: [require('daisyui')],
daisyui: {
themes: ['dark', 'light'],
logs: false,
},
};

View file

@ -1,58 +0,0 @@
import { defineConfig, presetUno, transformerDirectives, transformerVariantGroup } from 'unocss'
import { presetDaisy } from 'unocss-preset-daisy'
import { readFileSync } from 'fs'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const daisyuiThemes = readFileSync(require.resolve('daisyui/dist/themes.css'), 'utf-8')
const basePreflight = `
*,::before,::after{box-sizing:border-box}
html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4}
body{margin:0;padding:0;line-height:inherit}
a{color:inherit;text-decoration:inherit}
img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}
img,video{max-width:100%;height:auto}
h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}
ol,ul{list-style:none;margin:0;padding:0}
button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}
button,select{text-transform:none}
`
// DaisyUI v3 sets --btn-text-case:uppercase per [data-theme=*] in themes.css and
// drives text-transform via that variable in component styles. Both come after
// preflights, so !important is the only reliable escape.
// svg sizing is handled in base.html via higher-specificity (.btn svg.w-N) rules.
const daisyV3Overrides = `
.btn{text-transform:none!important;letter-spacing:normal!important}
`
export default defineConfig({
preflights: [
{ getCSS: () => basePreflight },
{ getCSS: () => daisyuiThemes },
{ getCSS: () => daisyV3Overrides },
],
content: {
filesystem: [
'../../crates/ontoref-daemon/templates/**/*.html',
],
},
presets: [
presetUno(),
presetDaisy({ themes: ['dark', 'light'] }),
],
transformers: [
transformerDirectives(),
transformerVariantGroup(),
],
safelist: [
// DaisyUI component classes assembled dynamically in JS (authBadge, statusBadge)
'badge', 'badge-xs', 'badge-ghost', 'badge-info', 'badge-error',
'badge-success', 'badge-warning', 'badge-neutral', 'badge-lg', 'badge-outline',
'loading', 'loading-spinner', 'loading-sm',
// Utility classes assembled dynamically in JS
'font-mono', 'hidden', 'line-through',
'text-orange-400', 'text-cyan-400', 'text-purple-400', 'text-yellow-400',
],
})

View file

@ -1,119 +0,0 @@
Design a minimalist technical logo for a protocol project called “Ontoref”.
Ontoref is a self-describing ontology and reflection protocol for evolving codebases.
It models how software projects describe their structure and observe their own evolution using directed acyclic graphs (DAGs).
Tagline:
“Structure that remembers why.”
Concept to visualize
The logo should express a balanced duality between:
Ontology
• structure
• nodes
• invariants
• static form
• what is
Reflection
• flow
• edges
• transitions
• evolution
• what becomes
The symbol should feel like structure and reflection at the same time.
Examples of visual metaphors:
• a graph node that mirrors itself
• a minimal DAG structure with reflective symmetry
• a directed edge suggesting motion and observation
• negative space completing the structure
The mark should imply a system that observes itself.
Style
• minimalist
• geometric
• flat vector logo
• strong silhouette
• balanced symmetry
• technical and precise
• no decorative elements
• readable at small sizes (1632px)
The logo must feel like infrastructure software, not a startup brand.
Symbol constraints
• abstract DAG-inspired symbol
• maximum 24 nodes
• suggest directed flow
• clean geometric shapes
• avoid literal graph diagrams
• reflection implied via symmetry or mirroring
Avoid:
• neural networks
• brain imagery
• infinity loops
• cloud / SaaS icons
• gradients
• complex network diagrams
• mascots
Color palette
Dark primary version:
background: deep slate / near-black (#0D1117 range)
Accent color (choose one):
• deep teal
• indigo-violet
• amber
Maximum two colors + background.
Must also work in monochrome.
Typography
Wordmark: Ontoref
Style:
• geometric monospace or semi-monospace
• technical, calm, restrained
• similar spirit to developer tools and programming languages
Optional tagline under the wordmark.
Deliver
• symbol + wordmark
• symbol-only version
• dark background version
• light background version
• clean vector style
Generate three distinct logo directions:
1. Mirror Node — a node reflecting itself through symmetry or negative space
2. Acyclic Flow — minimal DAG structure with a directional edge suggesting motion
3. Reflective Structure — geometric symbol where half implies structure and half implies reflection
Flat vector logo, precise geometry, minimal tech aesthetic.
vector logo, flat design, minimal geometry, centered symbol, white space, professional software project identity

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>URL</key>
<string>https://atlan.com/know/what-is-ontology-in-ai/</string>
</dict>
</plist>

View file

@ -1,21 +0,0 @@
This is a great question — let me build this explanation visually so that the concepto quede claro de forma intuitiva.Voy a construirte una explicación visual e intuitiva de este concepto. La "ontología operacional" suena intimidante, pero en esencia responde a una pregunta simple: **¿qué cosas existen cuando usas IA, y cómo se relacionan entre sí?**
Piénsalo como el "mapa del territorio" que necesitas para moverte con soltura. Vamos por capas:Ahora te lo desgloso en lenguaje llano. La ontología operacional es simplemente **el inventario de todo lo que participa cuando usas IA**, organizado para que entiendas qué puedes controlar y qué no.
**La columna izquierda es el flujo principal** — lo que tú vives directamente:
**Tú** eres el punto de partida. Llegas con una intención ("necesito un resumen", "quiero ideas para un nombre") y un contexto personal (tu industria, tu nivel de conocimiento, tus preferencias). La IA no sabe nada de esto salvo que tú se lo digas.
**El prompt** es tu única vía de comunicación. No es solo "lo que escribes", es *cómo* lo escribes. Un prompt vago produce resultados vagos. Uno específico, con rol, contexto y formato deseado, produce resultados útiles. Es como la diferencia entre decirle a un taxista "llévame por ahí" versus darle la dirección exacta.
**El modelo** es la caja que procesa tu mensaje. Tú no ves qué pasa dentro, pero sí puedes influir en su comportamiento a través del prompt, las herramientas que activas y el contexto que proporcionas.
**El output** es lo que recibes. Puede ser texto, una tabla, un archivo, código, un diagrama. La clave es que no es "la verdad" — es una *generación* basada en probabilidades que necesitas evaluar.
**La flecha punteada que sube** es quizás lo más importante: la iteración. Rara vez el primer resultado es perfecto. Lees el output, ajustas tu prompt, y vuelves a intentar. Este ciclo es donde realmente se aprende a usar IA.
**La columna derecha son las dimensiones invisibles** — lo que existe detrás de escena y afecta todo:
El **contexto** incluye el historial de tu conversación, las instrucciones del sistema y cualquier documento que hayas subido. El **conocimiento base** es lo que la IA aprendió en su entrenamiento más lo que puede buscar en internet. Las **herramientas** son capacidades extras (buscar en la web, crear archivos, hacer cálculos). Las **restricciones** son los límites éticos y técnicos. Y el **formato de salida** determina cómo se presenta la respuesta.
¿Te gustaría que profundice en alguna de estas piezas? Puedes tocar cualquier caja del diagrama para explorar más, o preguntarme directamente.

View file

@ -1,44 +0,0 @@
# Cómo la IA usa la estocástica
Imagina que estás escribiendo un mensaje de texto en el teléfono y el teclado te sugiere la siguiente palabra. Eso, en esencia, es el corazón de cómo funciona gran parte de la IA moderna. Vamos paso a paso.
**¿Qué significa "estocástico"?**
Es simplemente una palabra elegante para decir "con algo de azar controlado". Un proceso estocástico es aquel donde el resultado no está 100% determinado, sino que depende de probabilidades. Lanzar un dado es estocástico: no sabes qué saldrá, pero sí sabes que cada cara tiene ~16.7% de probabilidad.
**¿Cómo lo usa la IA al generar texto?**
Cuando un modelo como yo genera una respuesta, no "piensa" una frase completa de golpe. Genera **una palabra (o fragmento) a la vez**, eligiendo entre miles de candidatas. Para cada posición, el modelo calcula una distribución de probabilidades. Por ejemplo, si la frase empieza con "El gato se subió al…", las probabilidades podrían verse así:
- "tejado" → 30%
- "árbol" → 25%
- "muro" → 15%
- "sofá" → 10%
- miles de otras opciones → el resto
Aquí es donde entra lo estocástico: el modelo **no siempre elige la palabra más probable**. En lugar de eso, "lanza un dado cargado" donde las opciones con más probabilidad tienen más chances de salir, pero las menos probables también pueden aparecer.
**¿Por qué no elegir siempre la más probable?**
Si siempre eligiera la palabra con mayor probabilidad, las respuestas serían repetitivas, predecibles y aburridas. El componente aleatorio es lo que le da creatividad, variedad y naturalidad al texto. Es como la diferencia entre un músico que toca siempre las mismas notas "seguras" y uno que improvisa dentro de una escala.
**El "termostato" de la aleatoriedad: la temperatura**
Los ingenieros controlan *cuánta* aleatoriedad hay con un parámetro llamado **temperatura**:
- **Temperatura baja** (cerca de 0): el modelo se vuelve casi determinista, elige casi siempre la palabra más probable. Útil para respuestas precisas, como resolver un cálculo.
- **Temperatura alta** (cerca de 1 o más): el modelo se atreve más, elige palabras menos obvias. Útil para escritura creativa o lluvia de ideas.
Piénsalo como un chef: con temperatura baja, sigue la receta al pie de la letra; con temperatura alta, improvisa y experimenta con ingredientes inesperados.
**¿Dónde más aparece la estocástica en la IA?**
No solo en la generación de texto. Aparece en varias etapas:
- **Entrenamiento**: los datos se presentan al modelo en orden aleatorio (barajados como un mazo de cartas) para que aprenda mejor y no memorice patrones superficiales.
- **Dropout**: durante el entrenamiento, se "apagan" neuronas al azar para que la red no dependa demasiado de ninguna conexión específica. Es como entrenar a un equipo de fútbol obligando a distintos jugadores a sentarse en cada práctica para que todos sepan cubrir todas las posiciones.
- **Exploración**: en IA para juegos o robótica, el agente a veces toma decisiones al azar para descubrir estrategias nuevas en vez de quedarse con la primera que funcionó.
**En resumen**
La IA no es una calculadora que siempre da la misma respuesta. Es más parecida a un músico de jazz muy bien entrenado: conoce las reglas de la armonía (las probabilidades aprendidas de millones de textos), pero en cada actuación introduce pequeñas variaciones aleatorias que hacen que el resultado sea único, natural y, con suerte, interesante.

View file

@ -1,206 +0,0 @@
An ontology defines the formal vocabulary of a domain: the classes of things that exist, the properties that describe them, and the relationships that connect them. In data management and AI, ontologies make business meaning machine-readable so that AI agents, governance policies, and analytics tools operate from a single, consistent understanding of enterprise concepts.
Core Components:
1. Structure: Classes, properties, and relationships in machine-readable formats like RDF and OWL
2. Reasoning: Enables inference — AI derives new facts from stated facts using formal logic
3. Governance: Defines what business terms mean, how they relate, and who owns them
4. Interoperability: Shared vocabulary allowing different AI systems and platforms to communicate
Attribute Detail
Definition A formal model of concepts, properties, and relationships within a domain
Origin Philosophy (Aristotle); formalized for computer science by Tom Gruber (1993)
Key standards RDF (Resource Description Framework), OWL (Web Ontology Language), both W3C
Primary use in AI Grounding AI agents with structured business context for accurate outputs
Related concepts Taxonomy (hierarchical classification), Schema (data structure), Semantic layer (query abstraction), Knowledge graph (data instantiation)
Enterprise value Reduces AI hallucination, enables cross-platform governance, accelerates AI agent deployment
Governance role Defines what business terms mean, how they relate, and who owns them
Modern implementation Active metadata platforms bootstrap ontology incrementally, not through multi-year modeling
When did ontology become important?
The idea is not new. Philosophers have debated ontology since Aristotle. Computer scientists formalized it in the 1990s. What changed is the stakes. 88% of organizations now use AI in at least one business function, but fewer than 40% have scaled beyond pilot. That gap between pilot and production is, in large part, a context gap. Ontology fills it.
Ontology formalizes “what things are” and “how they relate” in a domain
Taxonomies classify; ontologies define meaning, constraints, and reasoning rules
In AI, ontology is the structural layer between raw metadata and intelligent action
Enterprise ontology covers business terms, data assets, policies, and their connections
Modern ontologies are living, evolving structures, not static academic models
Taxonomies classify things. Ontologies define meaning, relationships, and reasoning rules.
Taxonomies classify things. Ontologies define meaning, relationships, and reasoning rules. Image by Atlan.
How ontology works in AI and data management
Ontology works by defining classes (categories of entities), properties (attributes and data types), and relationships (how entities connect) in machine-readable formats like RDF and OWL. AI systems use this formal structure to perform inference, resolve ambiguity, and navigate enterprise data without requiring manual mapping for every new use case.
What are classes, properties, and relationships in ontology?
Every ontology rests on three building blocks. Classes are the categories: “Revenue,” “Customer,” “Data Pipeline.” Properties describe each class: Revenue has a currency, a fiscal period, a flag for whether it includes returns. Relationships connect classes to each other: Revenue belongs to Financial Metrics, Customer owns Account, Account generates Revenue.
This is not abstract. Consider a financial reporting team and a sales ops team that both use the word “revenue.” Finance means GAAP-recognized revenue. Sales means bookings. Without an ontology that defines both as distinct classes with explicit properties and constraints, an AI agent has no way to know which one a query refers to. It picks whichever definition it encounters first. Gartner predicts 40% of enterprise applications will feature task-specific AI agents by 2026, up from less than 5% in 2025. Each of those agents needs this kind of formal semantic structure to function. A business glossary defines the human-readable layer; ontology makes that layer machine-readable.
How do RDF and OWL make ontology machine-readable?
RDF (Resource Description Framework) represents facts as triples: subject, predicate, object. “Revenue belongs_to Financial_Metrics” is one triple. Stack enough triples and you have a graph of your domain. The W3C published RDF 1.1 as a formal recommendation in 2014, and it remains the foundational standard for linked data and the Semantic Web.
OWL (Web Ontology Language) adds expressive logic on top of RDF. Class hierarchies, cardinality constraints (“a Customer must have exactly one primary Account”), equivalence declarations (“Bookings_Revenue is equivalent to Sales_Revenue”). SPARQL is the query language for traversing these structures. These are W3C standards, which means ontology built on RDF and OWL is interoperable across tools and platforms. That interoperability is what separates open ontology from proprietary approaches that lock semantic models into a single vendor. A semantic layer maps business terms to physical data for querying; ontology defines what those terms mean at a deeper structural level. dbt semantic layer integration bridges the two in practice.
How does ontology enable inference and reasoning?
Ontology enables machines to derive new facts from stated facts. If the ontology says “Revenue includes Subscription_Revenue” and “Subscription_Revenue excludes One_Time_Fees,” an AI agent infers that Revenue excludes One_Time_Fees. No one programmed that rule explicitly. The ontologys formal logic made it derivable.
This is what separates ontology-grounded retrieval from pure vector search. Embedding-based retrieval finds documents that are statistically similar to a query. Ontology-grounded retrieval finds answers that are logically consistent with defined business meaning. RAG systems with structured knowledge context reduce AI hallucinations by over 40% compared to traditional approaches, according to the MEGA-RAG study published in PubMed Central. Enterprise RAG deployments frequently underperform when they lack semantic structure, producing answers that are statistically plausible but logically inconsistent with business definitions. A context graph unifies semantic meaning with operational lineage, giving AI agents both the “what” and the “where” of enterprise data.
Comparison: ontology vs. taxonomy vs. schema vs. semantic layer vs. knowledge graph
Concept What it defines Structure Machine-readable? Supports reasoning? Example
Ontology Formal model of concepts, properties, and relationships in a domain Graph (classes + properties + axioms) Yes (RDF/OWL) Yes, inference and logical constraints “Revenue is a Financial Metric that includes Subscription Revenue and excludes One-Time Fees”
Taxonomy Hierarchical classification of terms Tree (parent-child) Partially (SKOS) No, classification only “Financial Metrics > Revenue > Subscription Revenue”
Schema Structure and data types of a dataset Tabular (columns + types + constraints) Yes (DDL, JSON Schema) No, structural validation only “revenue_amount: DECIMAL(18,2), NOT NULL”
Semantic layer Abstraction mapping business terms to physical data for querying Mapping layer (metrics + dimensions) Yes (dbt, LookML) No, query translation only “Revenue = SUM(order_amount) WHERE status = completed
Knowledge graph Data instances organized by an ontologys structure Graph (nodes + edges with real data) Yes (RDF triples, property graphs) Yes, if backed by an ontology “Acme Corp [has_revenue] $4.2M [in_period] Q1-2026”
Why enterprise AI needs ontology
Enterprise AI needs ontology because AI agents cannot reliably interpret data without a formal model of business meaning. Ontology grounds AI outputs in defined concepts and relationships, reduces hallucination by providing structured context for retrieval, and enables governance to scale across platforms without manual re-mapping.
How do ontologies ground AI agents in business meaning?
AI agents retrieve data. They do not understand it. When an agent queries “revenue,” it pulls whatever matches from the nearest data source. If your finance team defines revenue as GAAP-recognized net revenue, your sales team defines it as bookings, and your product team defines it as MRR, the agent picks whichever definition it hits first.
A separate Deloitte survey found that 62% of data leaders cite “lack of semantic consistency” as the primary barrier to scaling AI across business units. An ontology prevents this by giving agents an explicit map of what “revenue” means in context — what it includes, what it excludes, and which teams definition applies. Active metadata enriches every data asset with governed meaning, turning static documentation into live signals that agents query at inference time.
How does ontology reduce AI hallucination?
Start with the cost: poor data quality costs the average organization $12.9 million annually, according to Gartner. Hallucination compounds that by generating confident but incorrect outputs that propagate through downstream decisions.
RAG (Retrieval-Augmented Generation) helps, but RAG alone retrieves documents — it does not understand concepts. An embedding search for “quarterly revenue” returns statistically similar text. An ontology-grounded search returns the specific definition of quarterly revenue, its component metrics, its exclusions, and its relationship to annual revenue. The difference is the gap between plausible and correct. A data catalog for AI provides the operational layer for ontology-grounded retrieval.
How does ontology enable cross-platform governance?
Here is the scenario most governance teams recognize: you define a masking policy for PII fields in Snowflake, then redefine it in Databricks, then again in your BI layer. Each platform has its own naming conventions, its own metadata structure, its own interpretation of what “PII” means. Divergence is inevitable.
Ontology breaks the cycle. When “PII_Field” is defined as a class with a “requires_masking” constraint, that rule propagates across every platform that reads the ontology. Define once, enforce everywhere. By 2027, organizations adopting active metadata practices will increase by more than 75% across data, analytics, and AI, according to Gartners Market Guide for Active Metadata Management. See data governance platforms that operationalize this approach.
The Data Stack Is Being Rebuilt for AI. Here's What's Coming.
What happens when AI isn't just a feature on top of data, but a consumer of it? Based on insights from 550+ data leaders, Prukalpa Sankar (Co-Founder & Co-CEO, Atlan) shares seven predictions—from the rise of data agents to why the context layer becomes the new infrastructure.
How to build an enterprise ontology
Building an enterprise ontology starts with a focused domain. Identify 3-5 high-value business domains, define core classes and relationships in each, connect them to existing metadata in your data catalog, and iterate. Active metadata platforms bootstrap ontology incrementally from existing lineage, glossary terms, and usage patterns.
Prerequisites:
Data catalog with asset inventory across key platforms
Business glossary with at least core terms defined
Column-level lineage mapped for priority data assets
Executive sponsor (CDO or VP Data) who owns semantic standards
Cross-functional stakeholders identified (data engineering, governance, analytics, AI/ML)
Seven steps to your first production ontology:
Select 3-5 priority domains. Choose domains where AI agents are active or planned: financial metrics, customer entities, product hierarchies. Start narrow. A focused ontology that covers one domain well is worth more than a sprawling model that covers everything poorly.
Audit existing semantic assets. Inventory business glossary terms, dbt semantic models, schema documentation, and tribal knowledge. Most organizations already have 40-60% of an ontology scattered across tools. MuleSofts 2025 Connectivity Benchmark found that only 28% of enterprise applications are integrated, despite organizations averaging 897 apps — semantic definitions fragment across these disconnected systems. The work is consolidation, not creation from scratch.
Define classes and properties. For each domain, identify the core classes (entities), their properties (attributes), and data types. Use existing glossary terms as the starting vocabulary. “Revenue” becomes a class. “Currency,” “fiscal_period,” and “includes_returns” become its properties.
Map relationships and constraints. Define how classes connect: “Revenue includes Subscription_Revenue,” “Customer owns Account,” “PII_Field requires Masking_Policy.” Add cardinality constraints where business logic demands them. Teams that define explicit cardinality constraints in their ontology reduce downstream data quality incidents by 34%, according to Gartner.
Connect to live metadata. Link ontology classes to physical data assets in your catalog. Each class maps to tables, columns, dashboards, and pipelines through column-level lineage. This is what separates a useful ontology from a theoretical model.
Validate with AI agent use cases. Test ontology coverage by running target AI agent queries against it. Where the agent cannot resolve a concept, the ontology has a gap. Fill it.
Establish governance and iteration cadence. Assign domain owners, set review cycles (monthly minimum), and automate drift detection. Ontology is never “done.” Business definitions change. New data assets appear. The ontology must keep pace.
Build your first production ontology in seven clear, repeatable steps.
Build your first production ontology in seven clear, repeatable steps. Image by Atlan.
Organizations with active metadata management reduce time to deliver new data assets by up to 70%, according to Gartner. An enterprise context layer operationalizes ontology so it stays connected to the living data stack.
Common pitfalls:
Boil-the-ocean scope: trying to model the entire enterprise in one initiative instead of starting with 3-5 domains
Treating ontology as a one-time project instead of a living, governed structure with ownership and review cycles
Building ontology in isolation from the data catalog and lineage, which creates a disconnected model nobody uses
Over-engineering with academic OWL constructs when simpler property-graph models serve the same purpose for your use cases
How to evaluate ontology approaches
Evaluating ontology approaches requires assessing five criteria: openness of architecture, integration with existing data stack tools, support for incremental adoption, governance lifecycle capabilities, and AI agent compatibility.
Criterion What to assess Red flag Green flag
Architecture openness Does the ontology layer work across your full data stack or only within one vendor? Proprietary ontology locked to a single platform Open APIs, MCP support, multi-platform connectors
Incremental adoption Can you start with 3-5 domains and expand, or does it require full upfront modeling? Requires complete enterprise model before delivering value Domain-by-domain rollout with immediate value
Governance lifecycle Does ontology evolve as business context changes, with versioning and ownership? Static model with no change management Automated drift detection, domain ownership, review cadence
AI agent compatibility Can AI agents query the ontology dynamically for context during inference? Ontology is a reference document, not a queryable layer Real-time API or MCP access for AI agents
Stack integration depth Does it connect to your existing catalog, lineage, glossary, and semantic layer? Standalone ontology tool requiring separate data entry Native integration with catalog, lineage, dbt, BI tools
Total cost of adoption What is the time-to-value: weeks, months, or years? 12+ month implementation before first use case Weeks to first domain, months to cross-domain coverage
More than 80% of enterprises will have used generative AI APIs or deployed generative AI-enabled applications by 2026, according to Gartner. The ontology approach you choose now determines whether those AI deployments produce correct answers or plausible ones.
Questions to ask vendors:
How does your ontology layer integrate with our existing data catalog and lineage?
Can AI agents query ontology classes and relationships at inference time via API or MCP?
What does the governance model look like? Who owns ontology domains, and how are changes reviewed?
Can we start with a single domain and expand, or is full enterprise modeling required upfront?
How does your ontology handle evolution when business definitions change?
Does your ontology interoperate with open standards (RDF, OWL, property graphs) or is it proprietary?
What is the typical time-to-value for a first production ontology domain?
See how a context layer for Snowflake shows open ontology working in a real production environment.
Adaptive Data Governance for the AI Era
Comprehensive resource on adaptive data governance strategies for the AI era, covering modern approaches to unifying context across data and AI systems, enabling collaboration, and activating metadata for both human and AI-powered experiences.
Enter your email
How Atlan approaches ontology
Atlan operationalizes ontology through its metadata lakehouse and 4-graph architecture: a data graph, governance graph, knowledge graph, and active ontology graph that evolve as the data stack and business context change. AI agents access this living ontology through Atlans MCP server, grounding outputs in governed, structured context without requiring static upfront modeling.
Most organizations have semantic meaning scattered across glossaries, dbt models, schema documentation, and tribal knowledge. AI agents cannot access this fragmented context at inference time. Closed-architecture ontology platforms require migrating to a single vendor stack, which means ripping out existing tools before seeing any value.
Atlan takes a different approach. The active ontology is not a standalone modeling tool. It is a living graph within a metadata lakehouse that connects to 100+ data stack integrations. The four graphs work together: a data graph maps assets, schemas, and lineage across every platform. A governance graph tracks policies, ownership, quality rules, and compliance. A knowledge graph stores business terms, classifications, and semantic relationships. The active ontology graph maintains formal classes, properties, constraints, and reasoning rules that evolve automatically as the underlying data changes.
AI agents access this ontology through the MCP server (Model Context Protocol), which makes enterprise ontology queryable by any AI system. Every AI application in your stack can pull structured business context at inference time. Organizations bootstrap ontology from existing catalog metadata, glossary terms, and lineage. No multi-year modeling project. Domain-by-domain rollout with AI agents grounding outputs in governed context from day one.
Atlan is recognized as a Leader in the Gartner Magic Quadrant for Metadata Management Solutions 2025, a Leader in the Gartner Magic Quadrant for Data and Analytics Governance 2026, and a Leader in the Forrester Wave Q3 2024. Explore Atlans modern data catalog to see how it works.
See how Atlan operationalizes ontology for your AI agents.
FAQs about ontology
What is ontology in artificial intelligence and why does it matter?
Ontology in AI is a formal model defining concepts, properties, and relationships so machines interpret data accurately. AI agents need structured context to ground outputs in business meaning. Without ontology, AI treats every query as a new problem with no shared understanding of what terms mean.
What is the difference between ontology and taxonomy in data management?
A taxonomy organizes terms into parent-child hierarchies. An ontology goes further: it defines properties, constraints, and logical relationships between concepts, enabling machine reasoning. A taxonomy tells you “Revenue” is a “Financial Metric.” An ontology tells you what Revenue includes, excludes, and how it connects to other entities.
How do ontologies help AI agents understand enterprise data?
Ontologies give AI agents a formal map of business meaning. When an agent queries “revenue,” the ontology specifies which definition applies, what it includes or excludes, and how it relates to concepts like bookings or ARR. This prevents the agent from hallucinating or retrieving the wrong definition.
What is the difference between an ontology and a semantic layer?
A semantic layer maps business terms to physical data so analysts query metrics without writing SQL. An ontology defines formal meaning, relationships, and constraints between concepts at a deeper level. The semantic layer answers “where is this metric?” The ontology answers “what does this metric mean and how does it relate to other concepts?”
How do you build an enterprise ontology without a multi-year project?
Start with 3-5 priority domains where AI agents are active. Audit semantic assets in your data catalog, glossary, and schema documentation. Define core classes and relationships per domain. Connect to live metadata through lineage. Iterate monthly. Active metadata platforms bootstrap ontology incrementally from existing assets.
What role does ontology play in data governance?
Ontology provides governance with a formal semantic backbone. Policies reference ontology classes to define what terms mean, who owns them, and what rules apply. When ontology defines “PII_Field” with a “requires_masking” constraint, that rule propagates automatically across every platform that reads the ontology.
Is ontology the same as a knowledge graph?
Ontology and knowledge graph are related but distinct. An ontology defines formal structure: classes, properties, relationships, and constraints. A knowledge graph instantiates that structure with real data. The ontology says “Customer is a class with properties name, industry, and account_tier.” The knowledge graph says “Acme Corp is a Customer in Manufacturing with Enterprise tier.”
How does ontology reduce AI hallucination in enterprise applications?
Ontology reduces hallucination by providing a verified, structured layer of business meaning that agents consult before generating outputs. Instead of relying on pattern matching over unstructured documents, the agent queries the ontology to confirm what concepts mean, how they connect, and what constraints apply.
Why ontology is the missing layer between metadata and AI action
Ontology is the formal semantic layer that transforms disconnected metadata into a machine-readable model AI agents can reason over. Organizations that operationalize ontology through active metadata platforms reduce hallucination, scale governance across platforms, and deploy AI agents faster.
The pattern is consistent across the data in this guide. 88% of organizations use AI, but fewer than 40% have scaled past pilot. RAG systems with structured knowledge context reduce AI hallucinations by over 40% compared to traditional approaches, according to the MEGA-RAG study published in PubMed Central. The gap between pilot and production is a context gap. Ontology fills it by giving AI agents a formal model of what business terms mean, how they connect, and what constraints apply.
The key is starting incrementally. Pick 3-5 high-value domains. Audit existing semantic assets. Define classes, properties, and relationships. Connect them to live metadata through column-level lineage. Validate against real AI agent queries. Iterate monthly.
Evaluate any ontology approach on openness, incremental adoption, governance lifecycle, and AI agent compatibility. The right approach connects to your existing data catalog and lineage rather than replacing them. Active metadata platforms make ontology operational without the multi-year academic modeling project that stalled previous generations.
Your AI agents are already answering questions about your data. The question is whether those answers are grounded in verified meaning or statistical approximation.

View file

@ -1,72 +0,0 @@
<svg width="100%" viewBox="0 0 680 480" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</marker>
<mask id="imagine-text-gaps-qqsl7n" maskUnits="userSpaceOnUse"><rect x="0" y="0" width="680" height="480" fill="white"/><rect x="199.20655822753906" y="14.305530548095703" width="281.5868835449219" height="21.47225570678711" fill="black" rx="2"/><rect x="291.94305419921875" y="63.263877868652344" width="96.11387634277344" height="21.47225570678711" fill="black" rx="2"/><rect x="186.9730224609375" y="84.4444351196289" width="306.053955078125" height="19.11113929748535" fill="black" rx="2"/><rect x="239.27911376953125" y="138.26388549804688" width="201.4417724609375" height="21.47225570678711" fill="black" rx="2"/><rect x="221.48590087890625" y="159.44442749023438" width="237.0281982421875" height="19.11113929748535" fill="black" rx="2"/><rect x="303.5567932128906" y="219.2638702392578" width="72.88640594482422" height="21.47225570678711" fill="black" rx="2"/><rect x="154.2199249267578" y="242.44442749023438" width="371.5601501464844" height="19.11113929748535" fill="black" rx="2"/><rect x="207.94638061523438" y="258.4444580078125" width="264.10723876953125" height="19.11113929748535" fill="black" rx="2"/><rect x="260.5107116699219" y="308.26385498046875" width="158.97857666015625" height="21.47225570678711" fill="black" rx="2"/><rect x="141.7613525390625" y="329.4444580078125" width="396.477294921875" height="19.11113929748535" fill="black" rx="2"/><rect x="293.0756530761719" y="383.26385498046875" width="93.84868621826172" height="21.47225570678711" fill="black" rx="2"/><rect x="154.87661743164062" y="404.4444274902344" width="370.24676513671875" height="19.11113929748535" fill="black" rx="2"/><rect x="26" y="440.1944274902344" width="235.28688049316406" height="19.11113929748535" fill="black" rx="2"/><rect x="396" y="440.1944274902344" width="261.1263427734375" height="19.11113929748535" fill="black" rx="2"/><rect x="361.4732971191406" y="445.15277099609375" width="17.053401947021484" height="17.694470405578613" fill="black" rx="2"/><rect x="631.119140625" y="445.15277099609375" width="17.76173686981201" height="17.694470405578613" fill="black" rx="2"/></mask></defs>
<text x="340" y="30" text-anchor="middle" style="fill:rgb(250, 249, 245);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:auto">Dónde encaja la ontología en el flujo de IA</text>
<!-- Layer 1: Datos crudos -->
<g onclick="sendPrompt('¿Qué son los datos crudos y por qué no bastan para la IA?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="55" width="600" height="52" rx="10" stroke-width="0.5" style="fill:rgb(68, 68, 65);stroke:rgb(180, 178, 169);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="74" text-anchor="middle" dominant-baseline="central" style="fill:rgb(211, 209, 199);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Datos crudos</text>
<text x="340" y="94" text-anchor="middle" dominant-baseline="central" style="fill:rgb(180, 178, 169);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Tablas, columnas, archivos — sin contexto de negocio</text>
</g>
<!-- Arrow -->
<line x1="340" y1="107" x2="340" y2="125" marker-end="url(#arrow)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Layer 2: Esquema + Catálogo -->
<g onclick="sendPrompt('¿Qué es un esquema y un catálogo de datos?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="130" width="600" height="52" rx="10" stroke-width="0.5" style="fill:rgb(12, 68, 124);stroke:rgb(133, 183, 235);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="149" text-anchor="middle" dominant-baseline="central" style="fill:rgb(181, 212, 244);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Esquema + catálogo de datos</text>
<text x="340" y="169" text-anchor="middle" dominant-baseline="central" style="fill:rgb(133, 183, 235);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Estructura técnica: tipos, nombres, linaje</text>
</g>
<!-- Arrow -->
<line x1="340" y1="182" x2="340" y2="200" marker-end="url(#arrow)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Layer 3: Ontología — highlighted -->
<g onclick="sendPrompt('¿Cómo se construye una ontología empresarial paso a paso?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="205" width="600" height="72" rx="10" stroke-width="1" style="fill:rgb(60, 52, 137);stroke:rgb(175, 169, 236);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="230" text-anchor="middle" dominant-baseline="central" style="fill:rgb(206, 203, 246);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Ontología</text>
<text x="340" y="252" text-anchor="middle" dominant-baseline="central" style="fill:rgb(175, 169, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Significado formal: clases, propiedades, relaciones, reglas lógicas</text>
<text x="340" y="268" text-anchor="middle" dominant-baseline="central" style="fill:rgb(175, 169, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">La máquina entiende QUÉ significan los datos</text>
</g>
<!-- Arrow -->
<line x1="340" y1="277" x2="340" y2="295" marker-end="url(#arrow)" mask="url(#imagine-text-gaps-qqsl7n)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Layer 4: Grafo de conocimiento -->
<g onclick="sendPrompt('¿Qué es un grafo de conocimiento y cómo usa la ontología?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="300" width="600" height="52" rx="10" stroke-width="0.5" style="fill:rgb(8, 80, 65);stroke:rgb(93, 202, 165);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="319" text-anchor="middle" dominant-baseline="central" style="fill:rgb(159, 225, 203);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Grafo de conocimiento</text>
<text x="340" y="339" text-anchor="middle" dominant-baseline="central" style="fill:rgb(93, 202, 165);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Datos reales instanciados: "Acme Corp es un Cliente en Manufactura"</text>
</g>
<!-- Arrow -->
<line x1="340" y1="352" x2="340" y2="370" marker-end="url(#arrow)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Layer 5: Agente de IA -->
<g onclick="sendPrompt('¿Cómo usa un agente de IA la ontología para responder preguntas?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="375" width="600" height="52" rx="10" stroke-width="0.5" style="fill:rgb(113, 43, 19);stroke:rgb(240, 153, 123);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="394" text-anchor="middle" dominant-baseline="central" style="fill:rgb(245, 196, 179);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Agente de IA</text>
<text x="340" y="414" text-anchor="middle" dominant-baseline="central" style="fill:rgb(240, 153, 123);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Consulta la ontología antes de responder → menos alucinaciones</text>
</g>
<!-- Side annotations -->
<text x="30" y="454" text-anchor="start" style="fill:rgb(194, 192, 182);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:start;dominant-baseline:auto">Sin ontología: la IA adivina significados →</text>
<text x="330" y="454" text-anchor="start" style="fill:rgb(194, 192, 182);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:start;dominant-baseline:auto">Con ontología: la IA razona sobre significados →</text>
<!-- Visual indicator: X vs Check -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<circle cx="280" cy="450" r="10" stroke-width="0.5" style="fill:rgb(121, 31, 31);stroke:rgb(240, 149, 149);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="280" y="450" text-anchor="middle" dominant-baseline="central" style="font-size:11px;fill:rgb(247, 193, 193);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:11px;font-weight:500;text-anchor:middle;dominant-baseline:central"></text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<circle cx="625" cy="450" r="10" stroke-width="0.5" style="fill:rgb(39, 80, 10);stroke:rgb(151, 196, 89);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="625" y="450" text-anchor="middle" dominant-baseline="central" style="font-size:11px;fill:rgb(192, 221, 151);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:11px;font-weight:500;text-anchor:middle;dominant-baseline:central"></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,97 +0,0 @@
<svg width="100%" viewBox="0 0 680 540" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</marker>
</defs>
<!-- Title region -->
<text x="340" y="30" text-anchor="middle" style="fill:rgb(250, 249, 245);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:auto">Los tres bloques de toda ontología</text>
<!-- CLASES -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="60" width="180" height="170" rx="14" stroke-width="0.5" style="fill:rgb(60, 52, 137);stroke:rgb(175, 169, 236);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="130" y="88" text-anchor="middle" dominant-baseline="central" style="fill:rgb(206, 203, 246);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Clases</text>
<text x="130" y="108" text-anchor="middle" dominant-baseline="central" style="fill:rgb(175, 169, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Categorías de cosas</text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="60" y="130" width="140" height="36" rx="6" stroke-width="0.5" style="fill:rgb(60, 52, 137);stroke:rgb(175, 169, 236);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="130" y="148" text-anchor="middle" dominant-baseline="central" style="fill:rgb(175, 169, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Cliente</text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="60" y="176" width="140" height="36" rx="6" stroke-width="0.5" style="fill:rgb(60, 52, 137);stroke:rgb(175, 169, 236);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="130" y="194" text-anchor="middle" dominant-baseline="central" style="fill:rgb(175, 169, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Ingreso</text>
</g>
<!-- PROPIEDADES -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="250" y="60" width="180" height="170" rx="14" stroke-width="0.5" style="fill:rgb(8, 80, 65);stroke:rgb(93, 202, 165);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="88" text-anchor="middle" dominant-baseline="central" style="fill:rgb(159, 225, 203);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Propiedades</text>
<text x="340" y="108" text-anchor="middle" dominant-baseline="central" style="fill:rgb(93, 202, 165);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Atributos que describen</text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="270" y="130" width="140" height="36" rx="6" stroke-width="0.5" style="fill:rgb(8, 80, 65);stroke:rgb(93, 202, 165);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="148" text-anchor="middle" dominant-baseline="central" style="fill:rgb(93, 202, 165);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">nombre, industria</text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="270" y="176" width="140" height="36" rx="6" stroke-width="0.5" style="fill:rgb(8, 80, 65);stroke:rgb(93, 202, 165);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="194" text-anchor="middle" dominant-baseline="central" style="fill:rgb(93, 202, 165);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">moneda, período</text>
</g>
<!-- RELACIONES -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="460" y="60" width="180" height="170" rx="14" stroke-width="0.5" style="fill:rgb(113, 43, 19);stroke:rgb(240, 153, 123);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="550" y="88" text-anchor="middle" dominant-baseline="central" style="fill:rgb(245, 196, 179);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Relaciones</text>
<text x="550" y="108" text-anchor="middle" dominant-baseline="central" style="fill:rgb(240, 153, 123);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Cómo se conectan</text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="480" y="130" width="140" height="36" rx="6" stroke-width="0.5" style="fill:rgb(113, 43, 19);stroke:rgb(240, 153, 123);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="550" y="148" text-anchor="middle" dominant-baseline="central" style="fill:rgb(240, 153, 123);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Cliente → Cuenta</text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="480" y="176" width="140" height="36" rx="6" stroke-width="0.5" style="fill:rgb(113, 43, 19);stroke:rgb(240, 153, 123);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="550" y="194" text-anchor="middle" dominant-baseline="central" style="fill:rgb(240, 153, 123);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Cuenta → Ingreso</text>
</g>
<!-- Arrows between columns -->
<line x1="222" y1="148" x2="268" y2="148" marker-end="url(#arrow)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="432" y1="148" x2="478" y2="148" marker-end="url(#arrow)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Separator -->
<line x1="40" y1="260" x2="640" y2="260" stroke="var(--color-border-tertiary)" stroke-width="0.5" style="fill:rgb(0, 0, 0);stroke:rgba(222, 220, 209, 0.15);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Comparison table header -->
<text x="340" y="290" text-anchor="middle" style="fill:rgb(250, 249, 245);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:auto">Ontología vs. sus conceptos vecinos</text>
<!-- Row headers -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="310" width="120" height="36" rx="6" stroke-width="0.5" style="fill:rgb(68, 68, 65);stroke:rgb(180, 178, 169);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="100" y="328" text-anchor="middle" dominant-baseline="central" style="fill:rgb(211, 209, 199);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Taxonomía</text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="356" width="120" height="36" rx="6" stroke-width="0.5" style="fill:rgb(68, 68, 65);stroke:rgb(180, 178, 169);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="100" y="374" text-anchor="middle" dominant-baseline="central" style="fill:rgb(211, 209, 199);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Esquema</text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="402" width="120" height="36" rx="6" stroke-width="0.5" style="fill:rgb(68, 68, 65);stroke:rgb(180, 178, 169);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="100" y="420" text-anchor="middle" dominant-baseline="central" style="fill:rgb(211, 209, 199);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Capa semántica</text>
</g>
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="448" width="120" height="36" rx="6" stroke-width="0.5" style="fill:rgb(68, 68, 65);stroke:rgb(180, 178, 169);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="100" y="466" text-anchor="middle" dominant-baseline="central" style="fill:rgb(211, 209, 199);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Grafo de conoc.</text>
</g>
<!-- Descriptions -->
<text x="180" y="332" dominant-baseline="central" style="fill:rgb(194, 192, 182);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:start;dominant-baseline:central">Clasifica en árbol: Métrica → Ingreso → Suscripción</text>
<text x="180" y="378" dominant-baseline="central" style="fill:rgb(194, 192, 182);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:start;dominant-baseline:central">Estructura de datos: columna, tipo, restricción</text>
<text x="180" y="424" dominant-baseline="central" style="fill:rgb(194, 192, 182);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:start;dominant-baseline:central">Traduce términos de negocio a consultas de datos</text>
<text x="180" y="470" dominant-baseline="central" style="fill:rgb(194, 192, 182);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:start;dominant-baseline:central">Datos reales organizados según la ontología</text>
<!-- Ontology highlight -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="494" width="120" height="36" rx="6" stroke-width="0.5" style="fill:rgb(60, 52, 137);stroke:rgb(175, 169, 236);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="100" y="512" text-anchor="middle" dominant-baseline="central" style="fill:rgb(206, 203, 246);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Ontología</text>
</g>
<text x="180" y="516" dominant-baseline="central" style="fill:rgb(194, 192, 182);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:start;dominant-baseline:central">Define significado formal + relaciones + reglas lógicas</text>
</svg>

Before

Width:  |  Height:  |  Size: 22 KiB

View file

@ -1,94 +0,0 @@
<svg width="100%" viewBox="0 0 680 620" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</marker>
<mask id="imagine-text-gaps-ug4a1o" maskUnits="userSpaceOnUse"><rect x="0" y="0" width="680" height="620" fill="white"/><rect x="269.4939270019531" y="34.30551528930664" width="141.0120849609375" height="21.472270965576172" fill="black" rx="2"/><rect x="109.16131591796875" y="101.26386260986328" width="101.67733764648438" height="21.472270965576172" fill="black" rx="2"/><rect x="97.87960052490234" y="126.44442749023438" width="124.24076843261719" height="19.11115264892578" fill="black" rx="2"/><rect x="123.49036407470703" y="231.2638702392578" width="73.01927947998047" height="21.472270965576172" fill="black" rx="2"/><rect x="106.5972900390625" y="256.44439697265625" width="106.80538940429688" height="19.11115264892578" fill="black" rx="2"/><rect x="105.02567291259766" y="361.26385498046875" width="109.9486312866211" height="21.472270965576172" fill="black" rx="2"/><rect x="107.89221954345703" y="386.4444274902344" width="104.21554565429688" height="19.11115264892578" fill="black" rx="2"/><rect x="125.44197082519531" y="491.26385498046875" width="69.11605453491211" height="21.472270965576172" fill="black" rx="2"/><rect x="91.95098114013672" y="516.4443969726562" width="136.09800720214844" height="19.11115264892578" fill="black" rx="2"/><rect x="-24.514450073242188" y="306.1944274902344" width="97.02889251708984" height="19.11115264892578" fill="black" rx="2"/><rect x="444.6635437011719" y="113.26387023925781" width="70.67291641235352" height="21.472270965576172" fill="black" rx="2"/><rect x="384.66900634765625" y="136.44442749023438" width="190.66195678710938" height="19.11115264892578" fill="black" rx="2"/><rect x="430.5447998046875" y="213.26385498046875" width="98.91040802001953" height="21.472270965576172" fill="black" rx="2"/><rect x="395.35675048828125" y="236.44442749023438" width="169.28646850585938" height="19.11115264892578" fill="black" rx="2"/><rect x="430.4119873046875" y="313.26385498046875" width="99.17603302001953" height="21.472270965576172" fill="black" rx="2"/><rect x="409.8481140136719" y="336.4444274902344" width="140.3037567138672" height="19.11115264892578" fill="black" rx="2"/><rect x="417.3557434082031" y="413.26385498046875" width="125.28851318359375" height="21.472270965576172" fill="black" rx="2"/><rect x="399.03125" y="436.44439697265625" width="161.93748474121094" height="19.11115264892578" fill="black" rx="2"/><rect x="411.8144836425781" y="513.2638549804688" width="136.3710174560547" height="21.472270965576172" fill="black" rx="2"/><rect x="386.0414123535156" y="536.4443969726562" width="187.9171600341797" height="19.11115264892578" fill="black" rx="2"/></mask></defs>
<!-- Outer container: El sistema completo -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="30" y="20" width="620" height="580" rx="20" stroke-width="0.5" style="fill:rgb(68, 68, 65);stroke:rgb(180, 178, 169);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="50" text-anchor="middle" style="fill:rgb(211, 209, 199);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:auto">El sistema completo</text>
</g>
<!-- Layer 1: Tú -->
<g onclick="sendPrompt('Explícame más sobre el rol del usuario en la ontología operacional')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="60" y="80" width="200" height="90" rx="12" stroke-width="0.5" style="fill:rgb(60, 52, 137);stroke:rgb(175, 169, 236);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="160" y="112" text-anchor="middle" dominant-baseline="central" style="fill:rgb(206, 203, 246);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Tú (el usuario)</text>
<text x="160" y="136" text-anchor="middle" dominant-baseline="central" style="fill:rgb(175, 169, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Intención + contexto</text>
</g>
<!-- Layer 2: El prompt -->
<g onclick="sendPrompt('¿Cómo construir mejores prompts?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="60" y="210" width="200" height="90" rx="12" stroke-width="0.5" style="fill:rgb(8, 80, 65);stroke:rgb(93, 202, 165);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="160" y="242" text-anchor="middle" dominant-baseline="central" style="fill:rgb(159, 225, 203);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">El prompt</text>
<text x="160" y="266" text-anchor="middle" dominant-baseline="central" style="fill:rgb(93, 202, 165);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Tu mensaje a la IA</text>
</g>
<!-- Layer 3: El modelo -->
<g onclick="sendPrompt('¿Cómo procesa la IA mi mensaje?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="60" y="340" width="200" height="90" rx="12" stroke-width="0.5" style="fill:rgb(113, 43, 19);stroke:rgb(240, 153, 123);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="160" y="372" text-anchor="middle" dominant-baseline="central" style="fill:rgb(245, 196, 179);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">El modelo de IA</text>
<text x="160" y="396" text-anchor="middle" dominant-baseline="central" style="fill:rgb(240, 153, 123);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Procesa y genera</text>
</g>
<!-- Layer 4: El output -->
<g onclick="sendPrompt('¿Cómo evaluar la calidad de un output de IA?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="60" y="470" width="200" height="90" rx="12" stroke-width="0.5" style="fill:rgb(99, 56, 6);stroke:rgb(239, 159, 39);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="160" y="502" text-anchor="middle" dominant-baseline="central" style="fill:rgb(250, 199, 117);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">El output</text>
<text x="160" y="526" text-anchor="middle" dominant-baseline="central" style="fill:rgb(239, 159, 39);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">La respuesta generada</text>
</g>
<!-- Arrows down the left column -->
<line x1="160" y1="170" x2="160" y2="208" marker-end="url(#arrow)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="160" y1="300" x2="160" y2="338" marker-end="url(#arrow)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="160" y1="430" x2="160" y2="468" marker-end="url(#arrow)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Return arrow (feedback loop) -->
<path d="M60 515 L36 515 L36 125 L58 125" fill="none" marker-end="url(#arrow)" stroke-dasharray="4 4" mask="url(#imagine-text-gaps-ug4a1o)" style="fill:none;stroke:rgb(156, 154, 146);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-dasharray:4px, 4px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="24" y="320" text-anchor="middle" transform="rotate(-90, 24, 320)" style="fill:rgb(194, 192, 182);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:auto">Iteras y mejoras</text>
<!-- Right side: Las dimensiones invisibles -->
<!-- Contexto -->
<g onclick="sendPrompt('¿Qué es el contexto en una conversación con IA?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="340" y="100" width="280" height="70" rx="12" stroke-width="0.5" style="fill:rgb(12, 68, 124);stroke:rgb(133, 183, 235);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="480" y="124" text-anchor="middle" dominant-baseline="central" style="fill:rgb(181, 212, 244);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Contexto</text>
<text x="480" y="146" text-anchor="middle" dominant-baseline="central" style="fill:rgb(133, 183, 235);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Memoria, historial, instrucciones</text>
</g>
<!-- Herramientas -->
<g onclick="sendPrompt('¿Qué herramientas puede usar la IA?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="340" y="200" width="280" height="70" rx="12" stroke-width="0.5" style="fill:rgb(39, 80, 10);stroke:rgb(151, 196, 89);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="480" y="224" text-anchor="middle" dominant-baseline="central" style="fill:rgb(192, 221, 151);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Herramientas</text>
<text x="480" y="246" text-anchor="middle" dominant-baseline="central" style="fill:rgb(151, 196, 89);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Búsqueda, archivos, cálculos</text>
</g>
<!-- Restricciones -->
<g onclick="sendPrompt('¿Qué limitaciones tiene la IA?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="340" y="300" width="280" height="70" rx="12" stroke-width="0.5" style="fill:rgb(114, 36, 62);stroke:rgb(237, 147, 177);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="480" y="324" text-anchor="middle" dominant-baseline="central" style="fill:rgb(244, 192, 209);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Restricciones</text>
<text x="480" y="346" text-anchor="middle" dominant-baseline="central" style="fill:rgb(237, 147, 177);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Límites, ética, precisión</text>
</g>
<!-- Formato -->
<g onclick="sendPrompt('¿Cómo pedir el formato correcto a la IA?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="340" y="400" width="280" height="70" rx="12" stroke-width="0.5" style="fill:rgb(68, 68, 65);stroke:rgb(180, 178, 169);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="480" y="424" text-anchor="middle" dominant-baseline="central" style="fill:rgb(211, 209, 199);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Formato de salida</text>
<text x="480" y="446" text-anchor="middle" dominant-baseline="central" style="fill:rgb(180, 178, 169);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Texto, tabla, código, archivo</text>
</g>
<!-- Conocimiento -->
<g onclick="sendPrompt('¿Qué sabe y qué no sabe la IA?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="340" y="500" width="280" height="70" rx="12" stroke-width="0.5" style="fill:rgb(113, 43, 19);stroke:rgb(240, 153, 123);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="480" y="524" text-anchor="middle" dominant-baseline="central" style="fill:rgb(245, 196, 179);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Conocimiento base</text>
<text x="480" y="546" text-anchor="middle" dominant-baseline="central" style="fill:rgb(240, 153, 123);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Entrenamiento + búsqueda web</text>
</g>
<!-- Connecting lines from right column to model -->
<line x1="338" y1="135" x2="262" y2="255" stroke="var(--color-border-secondary)" stroke-width="0.5" stroke-dasharray="3 3" style="fill:rgb(0, 0, 0);stroke:rgba(222, 220, 209, 0.3);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-dasharray:3px, 3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="338" y1="235" x2="262" y2="355" stroke="var(--color-border-secondary)" stroke-width="0.5" stroke-dasharray="3 3" style="fill:rgb(0, 0, 0);stroke:rgba(222, 220, 209, 0.3);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-dasharray:3px, 3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="338" y1="335" x2="262" y2="375" stroke="var(--color-border-secondary)" stroke-width="0.5" stroke-dasharray="3 3" style="fill:rgb(0, 0, 0);stroke:rgba(222, 220, 209, 0.3);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-dasharray:3px, 3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="338" y1="435" x2="262" y2="395" stroke="var(--color-border-secondary)" stroke-width="0.5" stroke-dasharray="3 3" style="fill:rgb(0, 0, 0);stroke:rgba(222, 220, 209, 0.3);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-dasharray:3px, 3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="338" y1="535" x2="262" y2="385" stroke="var(--color-border-secondary)" stroke-width="0.5" stroke-dasharray="3 3" style="fill:rgb(0, 0, 0);stroke:rgba(222, 220, 209, 0.3);color:rgb(255, 255, 255);stroke-width:0.5px;stroke-dasharray:3px, 3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
</svg>

Before

Width:  |  Height:  |  Size: 23 KiB

View file

@ -1,23 +0,0 @@
Ahora entiendo mucho mejor lo que buscas. No era "cómo funciona usar IA", sino el concepto formal de **ontología en IA**: cómo se estructura el conocimiento para que las máquinas entiendan significado, no solo datos. Vamos a ello.Ahora sí vamos con la explicación correcta.
**Ontología en IA** no es "cómo usas la IA". Es el **modelo formal que le dice a una máquina qué cosas existen en un dominio, qué las describe y cómo se conectan entre sí**. Viene de la filosofía (Aristóteles ya se preguntaba "¿qué cosas existen?"), pero en los años 90 los informáticos lo adaptaron para que las máquinas pudieran "entender" significado, no solo procesar texto.
Toda ontología se construye con tres bloques. Las **clases** son las categorías de entidades de tu mundo: "Cliente", "Ingreso", "Producto", "Pedido". Las **propiedades** son los atributos que describen cada clase: un Ingreso tiene una moneda, un período fiscal, un indicador de si incluye devoluciones. Y las **relaciones** conectan las clases entre sí: un Cliente posee una Cuenta, esa Cuenta genera Ingresos.
¿Por qué esto importa para la IA? Porque sin ontología, cuando le preguntas a un agente de IA "¿cuántos ingresos tuvimos este trimestre?", el agente no sabe si te refieres a ingresos reconocidos según normas contables, a reservas de ventas, o a facturación recurrente mensual. Simplemente toma la primera definición que encuentra. La ontología le da al agente un mapa explícito de lo que "ingreso" significa en contexto: qué incluye, qué excluye y qué definición aplica según el equipo.
Ahora veamos **por qué la IA empresarial necesita esto** y cómo encaja en el panorama más amplio:Piénsalo como las capas de un edificio, de abajo hacia arriba:
**Datos crudos** son las tablas y archivos en bruto. No tienen contexto — es como tener miles de cajas sin etiquetas en un almacén.
**Esquema y catálogo** le ponen estructura técnica: nombres de columnas, tipos de datos, de dónde viene cada dato. Ahora las cajas tienen etiquetas, pero las etiquetas son códigos técnicos.
**La ontología** es la capa que falta en la mayoría de las organizaciones. Es donde defines formalmente que "Ingreso" es una clase que incluye "Ingreso por suscripción" pero excluye "Pagos únicos", que pertenece a la categoría "Métricas financieras" y está vinculada a "Cuenta" que a su vez pertenece a "Cliente". Esto permite algo crucial: **la inferencia**. Si la ontología dice que el Ingreso incluye Ingreso por Suscripción y que este excluye Pagos Únicos, la máquina puede deducir *sin que nadie se lo programe* que el Ingreso también excluye Pagos Únicos.
**El grafo de conocimiento** toma esa estructura y la llena con datos reales: "Acme Corp es un Cliente de la industria Manufactura con nivel Enterprise".
**El agente de IA** consulta todo esto cuando le haces una pregunta. En vez de adivinar qué significa "ingreso", navega la ontología, encuentra la definición correcta y responde con precisión.
Para resumir la diferencia clave entre los "primos" de la ontología: la taxonomía solo clasifica en árbol (padre-hijo); el esquema define estructura técnica; la capa semántica traduce términos de negocio a consultas; el grafo de conocimiento almacena datos reales. La ontología es la única que define **significado formal con reglas lógicas que permiten razonamiento automático**.
¿Quieres que profundice en alguna de las capas? Puedes tocar cualquier caja del diagrama, o preguntarme sobre cómo se construye una ontología paso a paso, o cómo se ve en formatos técnicos como RDF y OWL explicados de forma sencilla.

View file

@ -1,27 +0,0 @@
# Ontología en IA
Cómo se estructura el conocimiento para que las máquinas entiendan significado, no solo datos. Explicación correcta.
<img src="ontologia_ia_clases_propiedades_relaciones.svg">
**Ontología en IA** no es "cómo usas la IA". Es el **modelo formal que le dice a una máquina qué cosas existen en un dominio, qué las describe y cómo se conectan entre sí**. Viene de la filosofía (Aristóteles ya se preguntaba "¿qué cosas existen?"), pero en los años 90 los informáticos lo adaptaron para que las máquinas pudieran "entender" significado, no solo procesar texto.
Toda ontología se construye con tres bloques. Las **clases** son las categorías de entidades de tu mundo: "Cliente", "Ingreso", "Producto", "Pedido". Las **propiedades** son los atributos que describen cada clase: un Ingreso tiene una moneda, un período fiscal, un indicador de si incluye devoluciones. Y las **relaciones** conectan las clases entre sí: un Cliente posee una Cuenta, esa Cuenta genera Ingresos.
¿Por qué esto importa para la IA? Porque sin ontología, cuando le preguntas a un agente de IA "¿cuántos ingresos tuvimos este trimestre?", el agente no sabe si te refieres a ingresos reconocidos según normas contables, a reservas de ventas, o a facturación recurrente mensual. Simplemente toma la primera definición que encuentra. La ontología le da al agente un mapa explícito de lo que "ingreso" significa en contexto: qué incluye, qué excluye y qué definición aplica según el equipo.
Ahora veamos **por qué la IA empresarial necesita esto** y cómo encaja en el panorama más amplio:Piénsalo como las capas de un edificio, de abajo hacia arriba:
<img src="ontologia_cadena_contexto_ia.svg">
**Datos crudos** son las tablas y archivos en bruto. No tienen contexto — es como tener miles de cajas sin etiquetas en un almacén.
**Esquema y catálogo** le ponen estructura técnica: nombres de columnas, tipos de datos, de dónde viene cada dato. Ahora las cajas tienen etiquetas, pero las etiquetas son códigos técnicos.
**La ontología** es la capa que falta en la mayoría de las organizaciones. Es donde defines formalmente que "Ingreso" es una clase que incluye "Ingreso por suscripción" pero excluye "Pagos únicos", que pertenece a la categoría "Métricas financieras" y está vinculada a "Cuenta" que a su vez pertenece a "Cliente". Esto permite algo crucial: **la inferencia**. Si la ontología dice que el Ingreso incluye Ingreso por Suscripción y que este excluye Pagos Únicos, la máquina puede deducir *sin que nadie se lo programe* que el Ingreso también excluye Pagos Únicos.
**El grafo de conocimiento** toma esa estructura y la llena con datos reales: "Acme Corp es un Cliente de la industria Manufactura con nivel Enterprise".
**El agente de IA** consulta todo esto cuando le haces una pregunta. En vez de adivinar qué significa "ingreso", navega la ontología, encuentra la definición correcta y responde con precisión.
Para resumir la diferencia clave entre los "primos" de la ontología: la taxonomía solo clasifica en árbol (padre-hijo); el esquema define estructura técnica; la capa semántica traduce términos de negocio a consultas; el grafo de conocimiento almacena datos reales. La ontología es la única que define **significado formal con reglas lógicas que permiten razonamiento automático**.

Some files were not shown because too many files have changed in this diff Show more