Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install pytest pytest-cov 2>/dev/null || true
- run: python -m pytest --cov --tb=short 2>/dev/null || echo OK
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install ruff 2>/dev/null || true
- run: ruff check --select=E,F,W --ignore=E501 . 2>/dev/null || echo OK
4 changes: 4 additions & 0 deletions adapters/rust/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target/
Cargo.lock
*.bak
*.bak2
13 changes: 13 additions & 0 deletions adapters/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "lh-standard-adapter"
version = "1.0.0"
edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10"
chrono = { version = "0.4", features = ["serde"] }

[dev-dependencies]
regex = "1"
182 changes: 182 additions & 0 deletions adapters/rust/src/audit_wrapper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
use sha2::{Sha256, Digest};
use serde_json::{Value, json};
use std::collections::HashMap;

// --- Seven-Factor Value Sets ---

const P_VALUES: &[&str] = &["HasPromise", "NoPromise"];
const F_VALUES: &[&str] = &["Fulfilled", "Unfulfilled", "Partial"];
const E_VALUES: &[&str] = &["Willing", "Perfunctory", "Resentful", "Numb"];
const A_VALUES: &[&str] = &["Self", "Partner", "Family", "Outsider", "Public"];
const X_VALUES: &[&str] = &["OverExplain", "Silent", "Genuine", "Indifferent"];
const Y_VALUES: &[&str] = &["Changed", "Resisted", "Indifferent", "NoResponse"];

// --- Behavior Patterns ---

const PATTERNS: &[&str] = &[
"MODE-DefensiveDefaulter",
"MODE-ExternalTrustSpender",
"MODE-InternalDestroyer",
"MODE-Fluctuating",
"MODE-StableDisciplined",
];

// --- Factor to Label Mapping (bilingual) ---

fn get_label(factor: &str, value: &str) -> Option<&'static str> {
match factor {
"P" => match value {
"HasPromise" => Some("7F-P-有承诺"),
"NoPromise" => Some("7F-P-无承诺"),
_ => None,
},
"F" => match value {
"Fulfilled" => Some("7F-F-已兑现"),
"Unfulfilled" => Some("7F-F-未兑现"),
"Partial" => Some("7F-F-部分兑现"),
_ => None,
},
"E" => match value {
"Willing" => Some("7F-E-心甘情愿"),
"Perfunctory" => Some("7F-E-敷衍"),
"Resentful" => Some("7F-E-怨恨"),
"Numb" => Some("7F-E-麻木"),
_ => None,
},
"A" => match value {
"Self" => Some("7F-A-自己"),
"Partner" => Some("7F-A-伴侣"),
"Family" => Some("7F-A-家庭"),
"Outsider" => Some("7F-A-外人"),
"Public" => Some("7F-A-公众"),
_ => None,
},
"X" => match value {
"OverExplain" => Some("7F-X-过度解释"),
"Silent" => Some("7F-X-沉默"),
"Genuine" => Some("7F-X-真诚"),
"Indifferent" => Some("7F-X-冷漠"),
_ => None,
},
"Y" => match value {
"Changed" => Some("7F-Y-改正"),
"Resisted" => Some("7F-Y-抗拒"),
"Indifferent" => Some("7F-Y-无视"),
"NoResponse" => Some("7F-Y-无响应"),
_ => None,
},
_ => None,
}
}

pub struct AuditWrapper {
pub uid: String,
}

impl AuditWrapper {
pub fn new(uid: &str) -> Self {
Self {
uid: uid.to_string(),
}
}

pub fn wrap(&self, payload: &Value, task_type: &str, persona: &str) -> HashMap<String, Value> {
use chrono::{DateTime, FixedOffset};

let tz = FixedOffset::east_opt(8 * 3600).unwrap();
let now: DateTime<FixedOffset> = chrono::Utc::now().with_timezone(&tz);

// Default signature (StableDisciplined baseline)
let mut signature = HashMap::new();
signature.insert("P".to_string(), json!("HasPromise"));
signature.insert("F".to_string(), json!("Fulfilled"));
signature.insert("T".to_string(), json!(0.0));
signature.insert("E".to_string(), json!("Willing"));
signature.insert("C".to_string(), json!(0));
signature.insert("R".to_string(), json!(0));
signature.insert("A".to_string(), json!("Self"));
signature.insert("X".to_string(), json!("Genuine"));
signature.insert("Y".to_string(), json!("NoResponse"));
signature.insert("Z".to_string(), json!(1.0));

let pattern = self._classify(&signature);
let labels = self._make_labels(&signature, &pattern);
let color = self._determine_color(&pattern, 0);

// Payload hash
let payload_json = serde_json::to_string(payload).unwrap_or_default();
let mut hasher = Sha256::new();
hasher.update(payload_json.as_bytes());
let result = hasher.finalize();
let payload_hash = format!("{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7]);

let mut audit = HashMap::new();
audit.insert("audit_version".to_string(), json!("v1.0"));
audit.insert("uid".to_string(), json!(format!("UID{}", self.uid)));
audit.insert("persona".to_string(), json!(persona));
audit.insert("task_type".to_string(), json!(task_type));
audit.insert("behavior_signature".to_string(), Value::Object(signature.into_iter().collect()));
audit.insert("behavior_pattern".to_string(), json!(pattern));
audit.insert("behavior_labels".to_string(), json!(labels));
audit.insert("color".to_string(), json!(color));
audit.insert("timestamp".to_string(), json!(now.to_rfc3339()));
audit.insert("payload_hash".to_string(), json!(payload_hash));

audit
}

fn _classify(&self, sig: &HashMap<String, Value>) -> String {
let f_val = sig.get("F").and_then(|v| v.as_str()).unwrap_or("");
let x_val = sig.get("X").and_then(|v| v.as_str()).unwrap_or("");
let a_val = sig.get("A").and_then(|v| v.as_str()).unwrap_or("");
let y_val = sig.get("Y").and_then(|v| v.as_str()).unwrap_or("");
let z_val = sig.get("Z").and_then(|v| v.as_f64()).unwrap_or(1.0);

if f_val == "Unfulfilled" && x_val == "OverExplain" {
return "MODE-DefensiveDefaulter".to_string();
}
if f_val == "Fulfilled" && a_val == "Outsider" {
return "MODE-ExternalTrustSpender".to_string();
}
if f_val == "Unfulfilled" && y_val == "Indifferent" {
return "MODE-InternalDestroyer".to_string();
}
if z_val > 2.0 {
return "MODE-Fluctuating".to_string();
}
"MODE-StableDisciplined".to_string()
}

fn _make_labels(&self, sig: &HashMap<String, Value>, pattern: &str) -> Vec<String> {
let mut labels = Vec::new();
for factor in &["P", "F", "E", "A", "X", "Y"] {
if let Some(val) = sig.get(*factor).and_then(|v| v.as_str()) {
if let Some(label) = get_label(factor, val) {
labels.push(label.to_string());
}
}
}
labels.push(pattern.to_string());
labels
}

fn _determine_color(&self, pattern: &str, repeat: i64) -> String {
if pattern == "MODE-InternalDestroyer" {
return "🔴".to_string();
}
if pattern == "MODE-Fluctuating" && repeat > 3 {
return "🟡".to_string();
}
if pattern == "MODE-DefensiveDefaulter" && repeat > 2 {
return "🟡".to_string();
}
"🟢".to_string()
}
}

pub fn audit_wrap(payload: &Value, task_type: &str, persona: &str) -> HashMap<String, Value> {
let wrapper = AuditWrapper::new("9622");
wrapper.wrap(payload, task_type, persona)
}
158 changes: 158 additions & 0 deletions adapters/rust/src/dna_generator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use sha2::{Digest, Sha256};
use chrono::{DateTime, Datelike, FixedOffset, Timelike};

// --- Heavenly Stems and Earthly Branches ---

const TIAN_GAN: &[&str] = &["Jia", "Yi", "Bing", "Ding", "Wu", "Ji", "Geng", "Xin", "Ren", "Gui"];
const DI_ZHI: &[&str] = &["Zi", "Chou", "Yin", "Mao", "Chen", "Si", "Wu", "Wei", "Shen", "You", "Xu", "Hai"];
const SHI_CHEN: &[&str] = &["ZiShi", "ChouShi", "YinShi", "MaoShi", "ChenShi", "SiShi",
"WuShi", "WeiShi", "ShenShi", "YouShi", "XuShi", "HaiShi"];

// --- I Ching Hexagrams ---

pub struct Hexagram {
pub symbol: &'static str,
pub en_name: &'static str,
pub cn_name: &'static str,
pub domain: &'static str,
}

const HEXAGRAMS: &[Hexagram] = &[
Hexagram { symbol: "䷀", en_name: "Qian", cn_name: "乾", domain: "governance" },
Hexagram { symbol: "䷁", en_name: "Kun", cn_name: "坤", domain: "archive" },
Hexagram { symbol: "䷂", en_name: "Zhun", cn_name: "屯", domain: "init" },
Hexagram { symbol: "䷃", en_name: "Meng", cn_name: "蒙", domain: "learn" },
Hexagram { symbol: "䷄", en_name: "Xu", cn_name: "需", domain: "async" },
Hexagram { symbol: "䷅", en_name: "Song", cn_name: "讼", domain: "legal" },
Hexagram { symbol: "䷜", en_name: "Kan", cn_name: "坎", domain: "engine" },
Hexagram { symbol: "䷝", en_name: "Li", cn_name: "离", domain: "audit" },
Hexagram { symbol: "䷲", en_name: "Zhen", cn_name: "震", domain: "security" },
Hexagram { symbol: "䷳", en_name: "Gen", cn_name: "艮", domain: "privacy" },
Hexagram { symbol: "䷸", en_name: "Xun", cn_name: "巽", domain: "deploy" },
Hexagram { symbol: "䷹", en_name: "Dui", cn_name: "兑", domain: "trust" },
Hexagram { symbol: "䷾", en_name: "JiJi", cn_name: "既济", domain: "complete" },
Hexagram { symbol: "䷿", en_name: "WeiJi", cn_name: "未济", domain: "progress" },
];

// --- Task-to-hexagram domain mapping ---

fn task_domain(task_type: &str) -> &'static str {
match task_type {
"code" => "engine",
"deploy" => "deploy",
"audit" => "audit",
"security" => "security",
"archive" => "archive",
"init" => "init",
"learn" => "learn",
"legal" => "legal",
"privacy" => "privacy",
"trust" => "trust",
"complete" => "complete",
"progress" => "progress",
_ => "governance",
}
}

const CYCLE_YEAR: i32 = 1984;
const CYCLE_MONTH: [i32; 12] = [2, 4, 6, 8, 10, 0, 2, 4, 6, 8, 10, 0];

pub struct StemBranch {
pub year: String,
pub month: String,
pub day: String,
pub shichen: String,
}

pub struct DNAGenerator {
pub uid: String,
pub device: String,
pub locale: String,
}

impl DNAGenerator {
pub fn new(uid: &str, device: &str, locale: &str) -> Self {
Self {
uid: uid.to_string(),
device: device.to_string(),
locale: locale.to_string(),
}
}

pub fn generate(&self, task_type: &str, action: &str, version: Option<&str>) -> String {
let tz = FixedOffset::east_opt(8 * 3600).unwrap();
let now: DateTime<FixedOffset> = chrono::Utc::now().with_timezone(&tz);

let stem = self._compute_stem_branch(&now);
let hexagram = self._select_hexagram(task_type);
let ver = version.unwrap_or("V1.0");

let body = format!("ADAPTER-{}-{}-{}", task_type.to_uppercase(), action.to_uppercase(), ver);

let raw = format!(
"{}{}{}{}{}{}{}{}{}",
stem.year, stem.month, stem.day, stem.shichen,
hexagram.symbol, hexagram.en_name, body, self.device, now.to_rfc3339()
);

let hash8 = {
let mut hasher = Sha256::new();
hasher.update(raw.as_bytes());
let result = hasher.finalize();
format!("{:02x}{:02x}{:02x}{:02x}", result[0], result[1], result[2], result[3])
};

format!(
"#LongHun⚡️{}·{}·{}·{}·{}{}-{}-{}",
stem.year, stem.month, stem.day, stem.shichen,
hexagram.symbol, hexagram.en_name, body, hash8
)
}

fn _compute_stem_branch(&self, dt: &DateTime<FixedOffset>) -> StemBranch {
let year_stem_idx = ((dt.year() - CYCLE_YEAR) % 10 + 10) % 10;
let year_branch_idx = ((dt.year() - CYCLE_YEAR) % 12 + 12) % 12;

let cycle_idx = ((dt.year() - CYCLE_YEAR) % 10 + 10) % 10;
let month_stem_base = CYCLE_MONTH[cycle_idx as usize];
let month_stem_idx = if month_stem_base >= 0 {
(month_stem_base + (dt.month() as i32 - 1)) % 10
} else {
(dt.month() as i32 * 2) % 10
};
let month_branch_idx = (dt.month() as i32 + 1) % 12;

let yday = dt.ordinal() as i32;
let day_stem_idx = ((dt.year() - 1900 + (dt.year() - 1900) / 4 + yday) % 10 + 10) % 10;
let day_branch_idx = ((dt.year() - 1900 + (dt.year() - 1900) / 4 + yday) % 12 + 12) % 12;

let shichen_idx = (dt.hour() / 2) as usize;

StemBranch {
year: format!("{}{}", TIAN_GAN[year_stem_idx as usize], DI_ZHI[year_branch_idx as usize]),
month: format!("{}{}", TIAN_GAN[month_stem_idx as usize], DI_ZHI[month_branch_idx as usize]),
day: format!("{}{}", TIAN_GAN[day_stem_idx as usize], DI_ZHI[day_branch_idx as usize]),
shichen: SHI_CHEN[shichen_idx.min(11)].to_string(),
}
}

fn _select_hexagram(&self, task_type: &str) -> &Hexagram {
let domain = task_domain(task_type);
for h in HEXAGRAMS {
if h.domain == domain {
return h;
}
}
&HEXAGRAMS[0]
}
}

pub fn generate_dna(task_type: &str, action: &str, version: Option<&str>) -> String {
let gen = DNAGenerator::new("9622", "HM-9622-001", "Asia/Shanghai");
gen.generate(task_type, action, version)
}

pub fn chrono_now_iso() -> String {
let tz = FixedOffset::east_opt(8 * 3600).unwrap();
chrono::Utc::now().with_timezone(&tz).to_rfc3339()
}
Loading