Initial commit: content-driven onboarding portal

Leptos/Axum app that renders a Question/Alternative/Feature schema
loaded from a sibling content repo (portal-content). Kanidm OIDC login,
content-driven authorization (Question.qualifies), a generic NATS
KV-backed resource + state-transition mechanism (no bespoke "applicant"
concept baked into the runtime - it's all content), a SHA-256 DAG chain
tying submissions and decisions together, and the "YES - Rasterized
Lines" piece (ported from the live uhhm.no site) as the landing hero.
This commit is contained in:
Bendik Aagaard Lynghaug
2026-07-29 19:38:40 +02:00
commit aa1a7fa572
21 changed files with 7873 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
//! SHA-256 hashed DAG chain, ported from dodrenett's lib/challenge.ts.
//! Kept deliberately plain: every submission hashes in its parent(s), but
//! nothing here produces more than one parent yet - that only starts
//! mattering once something (an LLM, or a human) posts follow-up
//! questions back into a visitor's chain instead of the fixed content in
//! ../portal-content. The DAG-shaped `parent_hashes` field exists so that
//! can slot in later without a schema change; the hashing itself doesn't
//! need to.
#![cfg(feature = "ssr")]
use sha2::{Digest, Sha256};
/// Hashes one chain node from its question, its parent hash(es), the
/// submitted responses, and a timestamp. Parents are sorted first so the
/// hash doesn't depend on the order multiple parents happened to arrive
/// in.
pub fn hash_node(
question_id: &str,
parent_hashes: &[String],
responses: &serde_json::Value,
timestamp_ms: i64,
) -> String {
let mut parents = parent_hashes.to_vec();
parents.sort();
let payload = serde_json::json!({
"questionId": question_id,
"parentHashes": parents,
"responses": responses,
"timestamp": timestamp_ms,
});
let data = serde_json::to_vec(&payload).expect("json values always serialize");
let mut hasher = Sha256::new();
hasher.update(&data);
format!("{:x}", hasher.finalize())
}