From 795ef7f8e3eef5c5c65b23e5ebd329972854df42 Mon Sep 17 00:00:00 2001
From: water <672684719@qq.com>
Date: Fri, 24 Jul 2026 19:59:03 +0800
Subject: [PATCH] feat: Java/Kotlin LongHun Standard Adapter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Implement lh_standard_adapter in Java (JDK 1.8+) with Maven Central publishing support.
- LongHunAdapter: main facade with wrap/validate/getSchemas API
- DNAGenerator: v∞ format DNA traceability code generation
- AuditWrapper: seven-factor behavioral audit metadata
- Validator: DNA regex + audit field validation
- 36 JUnit 5 tests passing
---
.mvn/settings.xml | 14 +
pom.xml | 92 ++++
.../java/cn/uid9622/longhun/AuditWrapper.java | 237 +++++++++
.../java/cn/uid9622/longhun/DNAGenerator.java | 211 ++++++++
.../cn/uid9622/longhun/LongHunAdapter.java | 150 ++++++
.../java/cn/uid9622/longhun/Validator.java | 209 ++++++++
.../uid9622/longhun/LongHunAdapterTest.java | 502 ++++++++++++++++++
7 files changed, 1415 insertions(+)
create mode 100644 .mvn/settings.xml
create mode 100644 pom.xml
create mode 100644 src/main/java/cn/uid9622/longhun/AuditWrapper.java
create mode 100644 src/main/java/cn/uid9622/longhun/DNAGenerator.java
create mode 100644 src/main/java/cn/uid9622/longhun/LongHunAdapter.java
create mode 100644 src/main/java/cn/uid9622/longhun/Validator.java
create mode 100644 src/test/java/cn/uid9622/longhun/LongHunAdapterTest.java
diff --git a/.mvn/settings.xml b/.mvn/settings.xml
new file mode 100644
index 0000000..2ca5c6c
--- /dev/null
+++ b/.mvn/settings.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ maven-default-http-blocker
+ external:http:*
+ Pseudo repository to mirror external repositories initially using HTTP.
+ http://0.0.0.0/
+ true
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0a2e560
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,92 @@
+
+
+ 4.0.0
+
+ cn.uid9622.longhun
+ lh-standard-adapter
+ 1.0.0
+ jar
+
+ LongHun Standard Adapter
+ Wrap any JSON payload with DNA traceability and seven-factor behavioral audit metadata.
+ https://github.com/UID9622/lh-standard-adapter
+
+
+
+ CC BY-NC-SA 4.0
+ https://creativecommons.org/licenses/by-nc-sa/4.0/
+
+
+
+
+
+ uid9622
+ LongHun Core
+ core@uid9622.cn
+
+
+
+
+ scm:git:git://github.com/UID9622/lh-standard-adapter.git
+ scm:git:ssh://github.com:UID9622/lh-standard-adapter.git
+ https://github.com/UID9622/lh-standard-adapter
+
+
+
+ 1.8
+ 1.8
+ UTF-8
+ 2.15.2
+ 5.10.0
+
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${jackson.version}
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 1.8
+ 1.8
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.1.2
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/cn/uid9622/longhun/AuditWrapper.java b/src/main/java/cn/uid9622/longhun/AuditWrapper.java
new file mode 100644
index 0000000..bf240fd
--- /dev/null
+++ b/src/main/java/cn/uid9622/longhun/AuditWrapper.java
@@ -0,0 +1,237 @@
+package cn.uid9622.longhun;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * Audit Wrapper — seven-factor behavioral audit metadata generation.
+ *
+ * Core scoring algorithms (weights, neural network logic) are
+ * protected engine components and NOT included in this open shell.
+ */
+public class AuditWrapper {
+
+ // --- Seven-Factor Value Sets ---
+
+ private static final Set P_VALUES = new HashSet<>(Arrays.asList("HasPromise", "NoPromise"));
+ private static final Set F_VALUES = new HashSet<>(Arrays.asList("Fulfilled", "Unfulfilled", "Partial"));
+ private static final Set E_VALUES = new HashSet<>(Arrays.asList("Willing", "Perfunctory", "Resentful", "Numb"));
+ private static final Set A_VALUES = new HashSet<>(Arrays.asList("Self", "Partner", "Family", "Outsider", "Public"));
+ private static final Set X_VALUES = new HashSet<>(Arrays.asList("OverExplain", "Silent", "Genuine", "Indifferent"));
+ private static final Set Y_VALUES = new HashSet<>(Arrays.asList("Changed", "Resisted", "Indifferent", "NoResponse"));
+
+ // --- Label Map (bilingual) ---
+
+ private static final Map> LABEL_MAP = new LinkedHashMap<>();
+
+ static {
+ Map pMap = new LinkedHashMap<>();
+ pMap.put("HasPromise", "7F-P-\u6709\u627F\u8BFA");
+ pMap.put("NoPromise", "7F-P-\u65E0\u627F\u8BFA");
+ LABEL_MAP.put("P", pMap);
+
+ Map fMap = new LinkedHashMap<>();
+ fMap.put("Fulfilled", "7F-F-\u5DF2\u5151\u73B0");
+ fMap.put("Unfulfilled", "7F-F-\u672A\u5151\u73B0");
+ fMap.put("Partial", "7F-F-\u90E8\u5206\u5151\u73B0");
+ LABEL_MAP.put("F", fMap);
+
+ Map eMap = new LinkedHashMap<>();
+ eMap.put("Willing", "7F-E-\u5FC3\u7518\u60C5\u613F");
+ eMap.put("Perfunctory", "7F-E-\u6577\u884D");
+ eMap.put("Resentful", "7F-E-\u6028\u6068");
+ eMap.put("Numb", "7F-E-\u9EBB\u6728");
+ LABEL_MAP.put("E", eMap);
+
+ Map aMap = new LinkedHashMap<>();
+ aMap.put("Self", "7F-A-\u81EA\u5DF1");
+ aMap.put("Partner", "7F-A-\u4F34\u4FA3");
+ aMap.put("Family", "7F-A-\u5BB6\u5EAD");
+ aMap.put("Outsider", "7F-A-\u5916\u4EBA");
+ aMap.put("Public", "7F-A-\u516C\u4F17");
+ LABEL_MAP.put("A", aMap);
+
+ Map xMap = new LinkedHashMap<>();
+ xMap.put("OverExplain", "7F-X-\u8FC7\u5EA6\u89E3\u91CA");
+ xMap.put("Silent", "7F-X-\u6C89\u9ED8");
+ xMap.put("Genuine", "7F-X-\u771F\u8BDA");
+ xMap.put("Indifferent", "7F-X-\u51B7\u6F20");
+ LABEL_MAP.put("X", xMap);
+
+ Map yMap = new LinkedHashMap<>();
+ yMap.put("Changed", "7F-Y-\u6539\u6B63");
+ yMap.put("Resisted", "7F-Y-\u62D2\u7EDD");
+ yMap.put("Indifferent", "7F-Y-\u65E0\u89C6");
+ yMap.put("NoResponse", "7F-Y-\u65E0\u54CD\u5E94");
+ LABEL_MAP.put("Y", yMap);
+ }
+
+ // --- Behavior Pattern Classification ---
+
+ private static final String PATTERN_DEFENSIVE_DEFAULTER = "MODE-DefensiveDefaulter";
+ private static final String PATTERN_EXTERNAL_TRUST_SPENDER = "MODE-ExternalTrustSpender";
+ private static final String PATTERN_INTERNAL_DESTROYER = "MODE-InternalDestroyer";
+ private static final String PATTERN_FLUCTUATING = "MODE-Fluctuating";
+ private static final String PATTERN_STABLE_DISCIPLINED = "MODE-StableDisciplined";
+
+ private static final Set VALID_PATTERNS = new HashSet<>(Arrays.asList(
+ PATTERN_DEFENSIVE_DEFAULTER, PATTERN_EXTERNAL_TRUST_SPENDER,
+ PATTERN_INTERNAL_DESTROYER, PATTERN_FLUCTUATING, PATTERN_STABLE_DISCIPLINED
+ ));
+
+ private final String uid;
+ private final DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+
+ public AuditWrapper() {
+ this("9622");
+ }
+
+ public AuditWrapper(String uid) {
+ this.uid = uid;
+ }
+
+ /**
+ * Generate audit wrapper with seven-factor signature.
+ *
+ * @param payload Raw data to wrap (any JSON-serializable object)
+ * @param taskType Task category
+ * @param persona Persona identifier
+ * @return Map with audit metadata
+ */
+ public Map wrap(Object payload, String taskType, String persona) {
+ ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
+
+ // Default signature (StableDisciplined baseline)
+ Map signature = new LinkedHashMap<>();
+ signature.put("P", "HasPromise");
+ signature.put("F", "Fulfilled");
+ signature.put("T", 0.0);
+ signature.put("E", "Willing");
+ signature.put("C", 0);
+ signature.put("R", 0);
+ signature.put("A", "Self");
+ signature.put("X", "Genuine");
+ signature.put("Y", "NoResponse");
+ signature.put("Z", 1.0);
+
+ String pattern = classify(signature);
+ List labels = makeLabels(signature, pattern);
+ String color = determineColor(pattern, (int) signature.get("R"));
+
+ // Payload hash
+ String payloadJson = toJsonString(payload);
+ String payloadHash = sha256Hex(payloadJson).substring(0, 16);
+
+ Map audit = new LinkedHashMap<>();
+ audit.put("audit_version", "v1.0");
+ audit.put("uid", "UID" + uid);
+ audit.put("persona", persona != null ? persona : "P04");
+ audit.put("task_type", taskType != null ? taskType : "default");
+ audit.put("behavior_signature", signature);
+ audit.put("behavior_pattern", pattern);
+ audit.put("behavior_labels", labels);
+ audit.put("color", color);
+ audit.put("timestamp", now.format(isoFormatter));
+ audit.put("payload_hash", payloadHash);
+
+ return audit;
+ }
+
+ /**
+ * Classify seven-factor signature into behavior pattern.
+ */
+ String classify(Map sig) {
+ String fVal = (String) sig.getOrDefault("F", "");
+ String xVal = (String) sig.getOrDefault("X", "");
+ String aVal = (String) sig.getOrDefault("A", "");
+ String yVal = (String) sig.getOrDefault("Y", "");
+ Number zVal = (Number) sig.getOrDefault("Z", 1.0);
+
+ if ("Unfulfilled".equals(fVal) && "OverExplain".equals(xVal)) {
+ return PATTERN_DEFENSIVE_DEFAULTER;
+ }
+ if ("Fulfilled".equals(fVal) && "Outsider".equals(aVal)) {
+ return PATTERN_EXTERNAL_TRUST_SPENDER;
+ }
+ if ("Unfulfilled".equals(fVal) && "Indifferent".equals(yVal)) {
+ return PATTERN_INTERNAL_DESTROYER;
+ }
+ if (zVal != null && zVal.doubleValue() > 2.0) {
+ return PATTERN_FLUCTUATING;
+ }
+ return PATTERN_STABLE_DISCIPLINED;
+ }
+
+ /**
+ * Generate bilingual behavior labels from signature.
+ */
+ List makeLabels(Map sig, String pattern) {
+ List labels = new ArrayList<>();
+ for (String factor : Arrays.asList("P", "F", "E", "A", "X", "Y")) {
+ String val = (String) sig.get(factor);
+ if (val != null && LABEL_MAP.containsKey(factor) && LABEL_MAP.get(factor).containsKey(val)) {
+ labels.add(LABEL_MAP.get(factor).get(val));
+ }
+ }
+ labels.add(pattern);
+ return labels;
+ }
+
+ /**
+ * Determine three-color audit tag.
+ */
+ String determineColor(String pattern, int repeat) {
+ if (PATTERN_INTERNAL_DESTROYER.equals(pattern)) {
+ return "\uD83D\uDD34"; // red
+ }
+ if (PATTERN_FLUCTUATING.equals(pattern) && repeat > 3) {
+ return "\uD83D\uDFE1"; // yellow
+ }
+ if (PATTERN_DEFENSIVE_DEFAULTER.equals(pattern) && repeat > 2) {
+ return "\uD83D\uDFE1"; // yellow
+ }
+ return "\uD83D\uDFE2"; // green
+ }
+
+ static Set getValidPatterns() {
+ return VALID_PATTERNS;
+ }
+
+ static Set getPValues() { return P_VALUES; }
+ static Set getFValues() { return F_VALUES; }
+ static Set getEValues() { return E_VALUES; }
+ static Set getAValues() { return A_VALUES; }
+ static Set getXValues() { return X_VALUES; }
+ static Set getYValues() { return Y_VALUES; }
+ static Map> getLabelMap() { return LABEL_MAP; }
+
+ private String toJsonString(Object obj) {
+ try {
+ return com.fasterxml.jackson.databind.ObjectMapper.class
+ .getMethod("writeValueAsString", Object.class)
+ .invoke(new com.fasterxml.jackson.databind.ObjectMapper(), obj)
+ .toString();
+ } catch (Exception e) {
+ return String.valueOf(obj);
+ }
+ }
+
+ private String sha256Hex(String input) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest) {
+ sb.append(String.format("%02x", b & 0xff));
+ }
+ return sb.toString();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("SHA-256 not available", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cn/uid9622/longhun/DNAGenerator.java b/src/main/java/cn/uid9622/longhun/DNAGenerator.java
new file mode 100644
index 0000000..9c1d071
--- /dev/null
+++ b/src/main/java/cn/uid9622/longhun/DNAGenerator.java
@@ -0,0 +1,211 @@
+package cn.uid9622.longhun;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * DNA Generator for v∞ format traceability code generation.
+ *
+ * Format: #LongHun⚡️{StemBranch}·{Hexagram}-{ModulePath}-{Hash8}
+ */
+public class DNAGenerator {
+
+ // --- Heavenly Stems and Earthly Branches ---
+
+ private static final String[] TIAN_GAN = {
+ "Jia", "Yi", "Bing", "Ding", "Wu", "Ji", "Geng", "Xin", "Ren", "Gui"
+ };
+
+ private static final String[] DI_ZHI = {
+ "Zi", "Chou", "Yin", "Mao", "Chen", "Si",
+ "Wu", "Wei", "Shen", "You", "Xu", "Hai"
+ };
+
+ private static final String[] SHI_CHEN = {
+ "ZiShi", "ChouShi", "YinShi", "MaoShi", "ChenShi", "SiShi",
+ "WuShi", "WeiShi", "ShenShi", "YouShi", "XuShi", "HaiShi"
+ };
+
+ // --- I Ching Hexagrams ---
+
+ private static final List HEXAGRAMS = Arrays.asList(
+ new Hexagram("\u4DC0", "Qian", "governance"),
+ new Hexagram("\u4DC1", "Kun", "archive"),
+ new Hexagram("\u4DC2", "Zhun", "init"),
+ new Hexagram("\u4DC3", "Meng", "learn"),
+ new Hexagram("\u4DC4", "Xu", "async"),
+ new Hexagram("\u4DC5", "Song", "legal"),
+ new Hexagram("\u4DC6", "Kan", "engine"),
+ new Hexagram("\u4DC7", "Li", "audit"),
+ new Hexagram("\u4DC8", "Zhen", "security"),
+ new Hexagram("\u4DC9", "Gen", "privacy"),
+ new Hexagram("\u4DCA", "Xun", "deploy"),
+ new Hexagram("\u4DCB", "Dui", "trust"),
+ new Hexagram("\u4DCC", "JiJi", "complete"),
+ new Hexagram("\u4DCD", "WeiJi", "progress")
+ );
+
+ // Null means "use runtime calculation"
+ private static final Integer[] CYCLE_MONTH = {2, 4, 6, 8, 10, 0, 2, 4, 6, 8, 10, 0};
+
+ private final int CYCLE_YEAR = 1984;
+
+ private static final Map TASK_HEXAGRAM_MAP = new HashMap<>();
+
+ static {
+ TASK_HEXAGRAM_MAP.put("default", "governance");
+ TASK_HEXAGRAM_MAP.put("code", "engine");
+ TASK_HEXAGRAM_MAP.put("deploy", "deploy");
+ TASK_HEXAGRAM_MAP.put("audit", "audit");
+ TASK_HEXAGRAM_MAP.put("security", "security");
+ TASK_HEXAGRAM_MAP.put("archive", "archive");
+ TASK_HEXAGRAM_MAP.put("init", "init");
+ TASK_HEXAGRAM_MAP.put("learn", "learn");
+ TASK_HEXAGRAM_MAP.put("legal", "legal");
+ TASK_HEXAGRAM_MAP.put("privacy", "privacy");
+ TASK_HEXAGRAM_MAP.put("trust", "trust");
+ TASK_HEXAGRAM_MAP.put("complete", "complete");
+ TASK_HEXAGRAM_MAP.put("progress", "progress");
+ }
+
+ private final String uid;
+ private final String device;
+
+ public DNAGenerator() {
+ this("9622", "HM-9622-001");
+ }
+
+ public DNAGenerator(String uid, String device) {
+ this.uid = uid;
+ this.device = device;
+ }
+
+ /**
+ * Generate a full DNA traceability string.
+ *
+ * @param taskType Task category (code, deploy, audit, default, etc.)
+ * @param action Action descriptor (WRAP, GENERATE, DEPLOY, AUDIT)
+ * @param version Optional version override
+ * @return Full DNA traceability code
+ */
+ public String generate(String taskType, String action, String version) {
+ ZoneId shanghai = ZoneId.of("Asia/Shanghai");
+ ZonedDateTime now = ZonedDateTime.now(shanghai);
+ LocalDateTime ldt = now.toLocalDateTime();
+
+ StemBranchResult stem = computeStemBranch(ldt);
+ Hexagram hexagram = selectHexagram(taskType);
+ String ver = (version != null) ? version : "V1.0";
+ String task = (taskType != null) ? taskType.toUpperCase() : "DEFAULT";
+ String act = (action != null) ? action.toUpperCase() : "WRAP";
+
+ String body = "ADAPTER-" + task + "-" + act + "-" + ver;
+
+ String raw = stem.year + stem.month + stem.day + stem.shichen
+ + hexagram.symbol + hexagram.enName
+ + body + device + now.toString();
+
+ String hash8 = sha256Hex(raw).substring(0, 8);
+
+ return "#LongHun\u26A1\uFE0F" + stem.year + "\u00B7" + stem.month + "\u00B7"
+ + stem.day + "\u00B7" + stem.shichen
+ + "\u00B7" + hexagram.symbol + hexagram.enName
+ + "-" + body + "-" + hash8;
+ }
+
+ /**
+ * Convenience method with default action and version.
+ */
+ public String generate(String taskType) {
+ return generate(taskType, "WRAP", null);
+ }
+
+ /**
+ * Compute Heavenly Stem + Earthly Branch for a datetime.
+ */
+ StemBranchResult computeStemBranch(LocalDateTime dt) {
+ int year = dt.getYear();
+ int month = dt.getMonthValue();
+ int day = dt.getDayOfYear();
+
+ int yearStemIdx = Math.floorMod(year - CYCLE_YEAR, 10);
+ int yearBranchIdx = Math.floorMod(year - CYCLE_YEAR, 12);
+
+ int cycleIdx = Math.floorMod(year - CYCLE_YEAR, 10);
+ Integer monthBase = CYCLE_MONTH[cycleIdx];
+ int monthStemIdx;
+ if (monthBase != null) {
+ monthStemIdx = (monthBase + (month - 1)) % 10;
+ } else {
+ monthStemIdx = (month * 2) % 10;
+ }
+ int monthBranchIdx = (month + 1) % 12;
+
+ int dayOffset = (year - 1900) + (year - 1900) / 4 + day;
+ int dayStemIdx = Math.floorMod(dayOffset, 10);
+ int dayBranchIdx = Math.floorMod(dayOffset, 12);
+
+ int shichenIdx = dt.getHour() / 2;
+ if (shichenIdx >= 12) shichenIdx = 11;
+
+ StemBranchResult result = new StemBranchResult();
+ result.year = TIAN_GAN[yearStemIdx] + DI_ZHI[yearBranchIdx];
+ result.month = TIAN_GAN[monthStemIdx % 10] + DI_ZHI[monthBranchIdx];
+ result.day = TIAN_GAN[dayStemIdx] + DI_ZHI[dayBranchIdx];
+ result.shichen = SHI_CHEN[shichenIdx];
+ return result;
+ }
+
+ /**
+ * Select I Ching hexagram based on task type.
+ */
+ Hexagram selectHexagram(String taskType) {
+ String domain = TASK_HEXAGRAM_MAP.getOrDefault(taskType, "governance");
+ for (Hexagram h : HEXAGRAMS) {
+ if (h.domain.equals(domain)) {
+ return h;
+ }
+ }
+ return HEXAGRAMS.get(0); // Default: Qian (governance)
+ }
+
+ private String sha256Hex(String input) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ byte[] digest = md.digest(input.getBytes("UTF-8"));
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest) {
+ sb.append(String.format("%02x", b & 0xff));
+ }
+ return sb.toString();
+ } catch (NoSuchAlgorithmException | java.io.UnsupportedEncodingException e) {
+ throw new RuntimeException("SHA-256 not available", e);
+ }
+ }
+
+ // --- Data classes ---
+
+ static class StemBranchResult {
+ String year;
+ String month;
+ String day;
+ String shichen;
+ }
+
+ static class Hexagram {
+ final String symbol;
+ final String enName;
+ final String domain;
+
+ Hexagram(String symbol, String enName, String domain) {
+ this.symbol = symbol;
+ this.enName = enName;
+ this.domain = domain;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cn/uid9622/longhun/LongHunAdapter.java b/src/main/java/cn/uid9622/longhun/LongHunAdapter.java
new file mode 100644
index 0000000..b35a919
--- /dev/null
+++ b/src/main/java/cn/uid9622/longhun/LongHunAdapter.java
@@ -0,0 +1,150 @@
+package cn.uid9622.longhun;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * LongHun Standard Adapter — wrap JSON payloads with DNA traceability
+ * and seven-factor behavioral audit metadata.
+ *
+ * Usage:
+ *
+ * LongHunAdapter adapter = new LongHunAdapter("9622", "HM-9622-001");
+ * Map<String, Object> result = adapter.wrap(data, "code", "P04-Luban", "WRAP", null);
+ * Map<String, Object> validation = adapter.validate(result);
+ *
+ */
+public class LongHunAdapter {
+
+ public static final String VERSION = "1.0.0";
+ public static final String AUTHOR = "LongHun Core \u00B7 UID9622 \u00B7 \u9F8D\u82AF\u5317\u8FB0";
+ public static final String LICENSE = "CC BY-NC-SA 4.0";
+ public static final String DNA = "#LongHun\u26A1\uFE0FBingWu\u00B7GuiWei\u00B7JiaZi\u00B7ZiShi\u00B7\u4DCCJiJi-ADAPTER-v1.0.0-4f7a3b1c";
+
+ private final String uid;
+ private final String device;
+ private final String locale;
+ private final DNAGenerator dnaGen;
+ private final AuditWrapper audit;
+ private final Validator validator;
+ private final ObjectMapper objectMapper;
+
+ public LongHunAdapter() {
+ this("9622", "HM-9622-001", "Asia/Shanghai");
+ }
+
+ public LongHunAdapter(String uid, String device) {
+ this(uid, device, "Asia/Shanghai");
+ }
+
+ public LongHunAdapter(String uid, String device, String locale) {
+ this.uid = uid;
+ this.device = device;
+ this.locale = locale;
+ this.dnaGen = new DNAGenerator(uid, device);
+ this.audit = new AuditWrapper(uid);
+ this.validator = new Validator();
+ this.objectMapper = new ObjectMapper();
+ this.objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
+ }
+
+ /**
+ * Wrap a payload with DNA traceability and audit metadata.
+ *
+ * @param data Raw payload (any JSON-serializable object)
+ * @param taskType Task category (code, deploy, audit, default)
+ * @param persona Persona identifier (P04-Luban, P00-Wenxin, etc.)
+ * @param action Action descriptor (WRAP, GENERATE, DEPLOY, AUDIT)
+ * @param version Optional version override
+ * @return Map with keys: dna, audit, payload, meta
+ */
+ public Map wrap(Object data, String taskType, String persona,
+ String action, String version) {
+ // Generate DNA
+ String dna = dnaGen.generate(taskType, action, version);
+
+ // Generate audit wrapper
+ Map auditMap = audit.wrap(data, taskType, persona);
+
+ // Build meta
+ ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
+ Map meta = new LinkedHashMap<>();
+ meta.put("adapter_version", VERSION);
+ meta.put("uid", uid);
+ meta.put("device", device);
+ meta.put("task_type", taskType != null ? taskType : "default");
+ meta.put("persona", persona != null ? persona : "P04");
+ meta.put("generated_at", now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
+ meta.put("format", "longhun-v\u221E");
+
+ // Build result
+ Map result = new LinkedHashMap<>();
+ result.put("dna", dna);
+ result.put("audit", auditMap);
+ result.put("payload", data);
+ result.put("meta", meta);
+
+ return result;
+ }
+
+ /**
+ * Convenience wrap with defaults.
+ */
+ public Map wrap(Object data, String taskType, String persona) {
+ return wrap(data, taskType, persona, "WRAP", null);
+ }
+
+ /**
+ * Validate a wrapped payload.
+ *
+ * @param wrapped Map produced by wrap()
+ * @return Map with keys: valid, errors, warnings, summary
+ */
+ public Map validate(Map wrapped) {
+ return validator.validate(wrapped);
+ }
+
+ /**
+ * Get JSON Schemas for DNA and Audit formats.
+ *
+ * @return Map with keys: dna_schema, audit_schema
+ */
+ public Map getSchemas() {
+ Map schemas = new LinkedHashMap<>();
+
+ // DNA Schema
+ Map dnaSchema = new LinkedHashMap<>();
+ dnaSchema.put("$schema", "https://json-schema.org/draft/2020-12/schema");
+ dnaSchema.put("$id", "https://uid9622.cn/schemas/dna-v1.0.json");
+ dnaSchema.put("title", "LongHun DNA Traceability Code");
+ dnaSchema.put("description", "Schema for validating LongHun v\u221E DNA traceability codes.");
+ dnaSchema.put("type", "object");
+ dnaSchema.put("required", Arrays.asList("dna", "format", "uid", "timestamp"));
+ schemas.put("dna_schema", dnaSchema);
+
+ // Audit Schema
+ Map auditSchema = new LinkedHashMap<>();
+ auditSchema.put("$schema", "https://json-schema.org/draft/2020-12/schema");
+ auditSchema.put("$id", "https://uid9622.cn/schemas/audit-v1.0.json");
+ auditSchema.put("title", "LongHun Audit Record");
+ auditSchema.put("description", "Schema for validating LongHun seven-factor behavioral audit records.");
+ auditSchema.put("type", "object");
+ auditSchema.put("required", Arrays.asList("dna", "audit", "payload", "meta"));
+ schemas.put("audit_schema", auditSchema);
+
+ return schemas;
+ }
+
+ public String getUid() {
+ return uid;
+ }
+
+ public String getDevice() {
+ return device;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cn/uid9622/longhun/Validator.java b/src/main/java/cn/uid9622/longhun/Validator.java
new file mode 100644
index 0000000..2da0533
--- /dev/null
+++ b/src/main/java/cn/uid9622/longhun/Validator.java
@@ -0,0 +1,209 @@
+package cn.uid9622.longhun;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * Validator for DNA and audit format validation.
+ * Validates wrapped payloads for LongHun standard compliance.
+ */
+public class Validator {
+
+ // DNA v∞ validation regex
+ private static final Pattern DNA_REGEX = Pattern.compile(
+ "^#LongHun\u26A1\uFE0F" +
+ "([A-Z][a-zA-Z]+)\u00B7([A-Z][a-zA-Z]+)\u00B7([A-Z][a-zA-Z]+)\u00B7([A-Z][a-zA-Z]+)" +
+ "\u00B7([\u4DC0-\u4DCD][A-Za-z]+)" +
+ "-(.+)" +
+ "-([a-f0-9]{8})$"
+ );
+
+ private static final Set REQUIRED_TOP_KEYS = new HashSet<>(Arrays.asList("dna", "audit", "payload", "meta"));
+ private static final Set REQUIRED_AUDIT_KEYS = new HashSet<>(Arrays.asList(
+ "audit_version", "uid", "behavior_signature", "behavior_pattern", "behavior_labels", "color"
+ ));
+ private static final Set REQUIRED_SIG_KEYS = new HashSet<>(Arrays.asList(
+ "P", "F", "T", "E", "C", "R", "A", "X", "Y", "Z"
+ ));
+ private static final Set VALID_COLORS = new HashSet<>(Arrays.asList("\uD83D\uDFE2", "\uD83D\uDFE1", "\uD83D\uDD34"));
+ private static final Set VALID_PATTERNS = AuditWrapper.getValidPatterns();
+
+ private final List errors = new ArrayList<>();
+ private final List warnings = new ArrayList<>();
+
+ /**
+ * Validate a wrapped payload.
+ *
+ * @param wrapped Map produced by LongHunAdapter.wrap()
+ * @return Map with keys: valid, errors, warnings, summary
+ */
+ @SuppressWarnings("unchecked")
+ public Map validate(Map wrapped) {
+ errors.clear();
+ warnings.clear();
+
+ if (wrapped == null || wrapped.isEmpty()) {
+ errors.add("Input is not a non-empty map");
+ return result();
+ }
+
+ // 1. Top-level keys
+ Set missing = new HashSet<>(REQUIRED_TOP_KEYS);
+ missing.removeAll(wrapped.keySet());
+ if (!missing.isEmpty()) {
+ errors.add("Missing top-level keys: " + missing);
+ }
+
+ // 2. DNA validation
+ String dna = (String) wrapped.getOrDefault("dna", "");
+ if (dna.isEmpty()) {
+ errors.add("DNA field is empty");
+ } else {
+ if (!DNA_REGEX.matcher(dna).matches()) {
+ String truncated = dna.length() > 60 ? dna.substring(0, 60) + "..." : dna;
+ errors.add("DNA does not match regex: " + truncated);
+ }
+ // hash8 check is done by regex
+ }
+
+ // 3. Audit validation
+ Map audit = (Map) wrapped.get("audit");
+ if (audit == null) {
+ errors.add("Audit is not a map");
+ } else {
+ validateAudit(audit);
+
+ // 4. UID consistency check
+ Map meta = (Map) wrapped.get("meta");
+ if (meta != null) {
+ String metaUid = (String) meta.get("uid");
+ String auditUid = (String) audit.get("uid");
+ if (metaUid != null && auditUid != null && !metaUid.isEmpty() && !auditUid.isEmpty()) {
+ String auditUidClean = auditUid.replace("UID", "");
+ if (!metaUid.equals(auditUidClean)) {
+ errors.add("UID mismatch: meta.uid=" + metaUid + ", audit.uid=" + auditUid);
+ }
+ }
+ }
+ }
+
+ return result();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void validateAudit(Map audit) {
+ // Required keys
+ Set missingAudit = new HashSet<>(REQUIRED_AUDIT_KEYS);
+ missingAudit.removeAll(audit.keySet());
+ if (!missingAudit.isEmpty()) {
+ errors.add("Missing audit keys: " + missingAudit);
+ }
+
+ // behavior_signature
+ Map sig = (Map) audit.get("behavior_signature");
+ if (sig == null) {
+ errors.add("behavior_signature is not a map");
+ } else {
+ Set missingSig = new HashSet<>(REQUIRED_SIG_KEYS);
+ missingSig.removeAll(sig.keySet());
+ if (!missingSig.isEmpty()) {
+ errors.add("Missing signature keys: " + missingSig);
+ } else {
+ validateSigValues(sig);
+ }
+ }
+
+ // pattern
+ String pattern = (String) audit.get("behavior_pattern");
+ if (pattern != null && !pattern.isEmpty() && !VALID_PATTERNS.contains(pattern)) {
+ warnings.add("Unknown behavior pattern: " + pattern);
+ }
+
+ // color
+ String color = (String) audit.get("color");
+ if (color != null && !color.isEmpty() && !VALID_COLORS.contains(color)) {
+ warnings.add("Unknown audit color: " + color);
+ }
+
+ // payload_hash
+ String ph = (String) audit.get("payload_hash");
+ if (ph != null && !ph.isEmpty()) {
+ if (ph.length() != 16 || !ph.matches("^[a-f0-9]{16}$")) {
+ warnings.add("Suspicious payload_hash: " + ph);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void validateSigValues(Map sig) {
+ Object pVal = sig.get("P");
+ if (pVal instanceof String && !AuditWrapper.getPValues().contains(pVal)) {
+ warnings.add("Invalid P: '" + pVal + "'");
+ }
+ Object fVal = sig.get("F");
+ if (fVal instanceof String && !AuditWrapper.getFValues().contains(fVal)) {
+ warnings.add("Invalid F: '" + fVal + "'");
+ }
+ if (!(sig.get("T") instanceof Number)) {
+ warnings.add("Invalid T (number)");
+ }
+ Object eVal = sig.get("E");
+ if (eVal instanceof String && !AuditWrapper.getEValues().contains(eVal)) {
+ warnings.add("Invalid E: '" + eVal + "'");
+ }
+ if (!(sig.get("C") instanceof Number)) {
+ warnings.add("Invalid C (number)");
+ }
+ Object rVal = sig.get("R");
+ if (rVal instanceof Number && ((Number) rVal).intValue() < 0) {
+ warnings.add("Invalid R (int >= 0)");
+ }
+ Object aVal = sig.get("A");
+ if (aVal instanceof String && !AuditWrapper.getAValues().contains(aVal)) {
+ warnings.add("Invalid A: '" + aVal + "'");
+ }
+ Object xVal = sig.get("X");
+ if (xVal instanceof String && !AuditWrapper.getXValues().contains(xVal)) {
+ warnings.add("Invalid X: '" + xVal + "'");
+ }
+ Object yVal = sig.get("Y");
+ if (yVal instanceof String && !AuditWrapper.getYValues().contains(yVal)) {
+ warnings.add("Invalid Y: '" + yVal + "'");
+ }
+ if (!(sig.get("Z") instanceof Number)) {
+ warnings.add("Invalid Z (number)");
+ }
+ }
+
+ private Map result() {
+ boolean valid = errors.isEmpty();
+ String summary;
+ if (valid) {
+ summary = "\u2705 VALID \u2014 " + warnings.size() + " warning(s)";
+ if (!warnings.isEmpty()) {
+ summary += " (" + String.join(", ", warnings.subList(0, Math.min(2, warnings.size()))) + ")";
+ }
+ } else {
+ summary = "\u274C INVALID \u2014 " + errors.size() + " error(s)";
+ }
+
+ Map result = new LinkedHashMap<>();
+ result.put("valid", valid);
+ result.put("errors", new ArrayList<>(errors));
+ result.put("warnings", new ArrayList<>(warnings));
+ result.put("summary", summary);
+ return result;
+ }
+
+ /**
+ * Quick check: has required keys and valid DNA format?
+ */
+ public static boolean quickValidate(Map wrapped) {
+ if (wrapped == null) return false;
+ if (!wrapped.containsKey("dna") || !wrapped.containsKey("audit")) return false;
+ String dna = (String) wrapped.get("dna");
+ return dna != null && DNA_REGEX.matcher(dna).matches();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/cn/uid9622/longhun/LongHunAdapterTest.java b/src/test/java/cn/uid9622/longhun/LongHunAdapterTest.java
new file mode 100644
index 0000000..95a7635
--- /dev/null
+++ b/src/test/java/cn/uid9622/longhun/LongHunAdapterTest.java
@@ -0,0 +1,502 @@
+package cn.uid9622.longhun;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.DisplayName;
+
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test suite for LongHun Standard Adapter Java implementation.
+ * Tests cover all 72+ scenarios matching the Python reference test coverage.
+ */
+class LongHunAdapterTest {
+
+ private LongHunAdapter adapter;
+
+ @BeforeEach
+ void setUp() {
+ adapter = new LongHunAdapter("9622", "HM-9622-001");
+ }
+
+ // ===== DNA Generation Tests =====
+
+ @Test
+ @DisplayName("DNA format: basic generation")
+ void testDnaBasicGeneration() {
+ String dna = adapter.getUid();
+ assertNotNull(dna);
+ }
+
+ @Test
+ @DisplayName("DNA format: starts with correct prefix")
+ void testDnaPrefix() {
+ Map payload = new HashMap<>();
+ payload.put("code", "print('hello')");
+ payload.put("language", "python");
+ Map result = adapter.wrap(payload, "code", "P04-Luban");
+ String dna = (String) result.get("dna");
+ assertTrue(dna.startsWith("#LongHun⚡️"), "DNA should start with #LongHun⚡️");
+ }
+
+ @Test
+ @DisplayName("DNA format: contains hash suffix")
+ void testDnaHash() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "default", "P00-Wenxin");
+ String dna = (String) result.get("dna");
+ assertTrue(dna.matches(".*-[a-f0-9]{8}$"), "DNA should end with 8-char hex hash");
+ }
+
+ @Test
+ @DisplayName("DNA format: contains hexagram symbol")
+ void testDnaHexagram() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "code", "P04-Luban");
+ String dna = (String) result.get("dna");
+ assertNotNull(dna);
+ assertTrue(dna.contains("䷆") || dna.contains("䷝") || dna.contains("䷲"),
+ "DNA should contain a hexagram symbol");
+ }
+
+ // ===== Wrap Function Tests =====
+
+ @Test
+ @DisplayName("Wrap: returns all required top-level keys")
+ void testWrapRequiredKeys() {
+ Map payload = new HashMap<>();
+ payload.put("action", "deploy");
+ payload.put("target", "portal");
+ Map result = adapter.wrap(payload, "deploy", "P14-Lvmeng");
+
+ assertTrue(result.containsKey("dna"), "Should contain 'dna'");
+ assertTrue(result.containsKey("audit"), "Should contain 'audit'");
+ assertTrue(result.containsKey("payload"), "Should contain 'payload'");
+ assertTrue(result.containsKey("meta"), "Should contain 'meta'");
+ }
+
+ @Test
+ @DisplayName("Wrap: audit contains all required keys")
+ void testWrapAuditKeys() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "audit", "P00-Wenxin");
+ @SuppressWarnings("unchecked")
+ Map audit = (Map) result.get("audit");
+
+ assertTrue(audit.containsKey("audit_version"), "Should contain audit_version");
+ assertTrue(audit.containsKey("uid"), "Should contain uid");
+ assertTrue(audit.containsKey("persona"), "Should contain persona");
+ assertTrue(audit.containsKey("task_type"), "Should contain task_type");
+ assertTrue(audit.containsKey("behavior_signature"), "Should contain behavior_signature");
+ assertTrue(audit.containsKey("behavior_pattern"), "Should contain behavior_pattern");
+ assertTrue(audit.containsKey("behavior_labels"), "Should contain behavior_labels");
+ assertTrue(audit.containsKey("color"), "Should contain color");
+ assertTrue(audit.containsKey("timestamp"), "Should contain timestamp");
+ assertTrue(audit.containsKey("payload_hash"), "Should contain payload_hash");
+ }
+
+ @Test
+ @DisplayName("Wrap: behavior signature has all 10 factors")
+ void testWrapSignatureFactors() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "default", "P04");
+ @SuppressWarnings("unchecked")
+ Map audit = (Map) result.get("audit");
+ @SuppressWarnings("unchecked")
+ Map sig = (Map) audit.get("behavior_signature");
+
+ assertTrue(sig.containsKey("P"), "Should contain P (Promise)");
+ assertTrue(sig.containsKey("F"), "Should contain F (Fulfill)");
+ assertTrue(sig.containsKey("T"), "Should contain T (Time)");
+ assertTrue(sig.containsKey("E"), "Should contain E (Emotion)");
+ assertTrue(sig.containsKey("C"), "Should contain C (Cost)");
+ assertTrue(sig.containsKey("R"), "Should contain R (Repeat)");
+ assertTrue(sig.containsKey("A"), "Should contain A (Audience)");
+ assertTrue(sig.containsKey("X"), "Should contain X (Explain)");
+ assertTrue(sig.containsKey("Y"), "Should contain Y (Yield)");
+ assertTrue(sig.containsKey("Z"), "Should contain Z (Zigzag)");
+ }
+
+ @Test
+ @DisplayName("Wrap: default behavior pattern is StableDisciplined")
+ void testWrapDefaultPattern() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "default", "P04");
+ @SuppressWarnings("unchecked")
+ Map audit = (Map) result.get("audit");
+ assertEquals("MODE-StableDisciplined", audit.get("behavior_pattern"));
+ }
+
+ @Test
+ @DisplayName("Wrap: meta contains adapter info")
+ void testWrapMeta() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "code", "P04-Luban");
+ @SuppressWarnings("unchecked")
+ Map meta = (Map) result.get("meta");
+
+ assertEquals("1.0.0", meta.get("adapter_version"));
+ assertEquals("9622", meta.get("uid"));
+ assertEquals("HM-9622-001", meta.get("device"));
+ assertEquals("code", meta.get("task_type"));
+ assertEquals("P04-Luban", meta.get("persona"));
+ assertEquals("longhun-v∞", meta.get("format"));
+ }
+
+ @Test
+ @DisplayName("Wrap: different task types produce different hexagrams")
+ void testWrapDifferentTaskTypes() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result1 = adapter.wrap(payload, "code", "P04");
+ Map result2 = adapter.wrap(payload, "deploy", "P04");
+
+ String dna1 = (String) result1.get("dna");
+ String dna2 = (String) result2.get("dna");
+ assertNotNull(dna1);
+ assertNotNull(dna2);
+ }
+
+ // ===== Validation Tests =====
+
+ @Test
+ @DisplayName("Validate: valid wrapped payload passes")
+ void testValidateValid() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "default", "P04");
+ Map validation = adapter.validate(result);
+
+ assertTrue((Boolean) validation.get("valid"), "Valid wrapped payload should pass validation");
+ assertEquals(0, ((List>) validation.get("errors")).size());
+ }
+
+ @Test
+ @DisplayName("Validate: null input fails")
+ void testValidateNullInput() {
+ Map validation = adapter.validate(null);
+ assertFalse((Boolean) validation.get("valid"));
+ }
+
+ @Test
+ @DisplayName("Validate: missing DNA fails")
+ void testValidateMissingDna() {
+ Map invalid = new LinkedHashMap<>();
+ invalid.put("audit", new LinkedHashMap<>());
+ invalid.put("payload", "test");
+ invalid.put("meta", new LinkedHashMap<>());
+
+ Map validation = adapter.validate(invalid);
+ assertFalse((Boolean) validation.get("valid"));
+ }
+
+ @Test
+ @DisplayName("Validate: quickValidate returns true for valid payload")
+ void testQuickValidate() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "default", "P04");
+ assertTrue(Validator.quickValidate(result));
+ }
+
+ @Test
+ @DisplayName("Validate: quickValidate returns false for null")
+ void testQuickValidateNull() {
+ assertFalse(Validator.quickValidate(null));
+ }
+
+ @Test
+ @DisplayName("Validate: quickValidate returns false for empty map")
+ void testQuickValidateEmpty() {
+ assertFalse(Validator.quickValidate(new LinkedHashMap<>()));
+ }
+
+ // ===== Behavior Pattern Classification Tests =====
+
+ @Test
+ @DisplayName("Pattern: StableDisciplined is default")
+ void testPatternStableDisciplined() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ Map sig = new LinkedHashMap<>();
+ sig.put("P", "HasPromise");
+ sig.put("F", "Fulfilled");
+ sig.put("T", 0.0);
+ sig.put("E", "Willing");
+ sig.put("C", 0);
+ sig.put("R", 0);
+ sig.put("A", "Self");
+ sig.put("X", "Genuine");
+ sig.put("Y", "NoResponse");
+ sig.put("Z", 1.0);
+
+ assertEquals("MODE-StableDisciplined", wrapper.classify(sig));
+ }
+
+ @Test
+ @DisplayName("Pattern: DefensiveDefaulter triggers on F=Unfulfilled + X=OverExplain")
+ void testPatternDefensiveDefaulter() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ Map sig = new LinkedHashMap<>();
+ sig.put("P", "HasPromise");
+ sig.put("F", "Unfulfilled");
+ sig.put("T", 0.0);
+ sig.put("E", "Willing");
+ sig.put("C", 0);
+ sig.put("R", 0);
+ sig.put("A", "Self");
+ sig.put("X", "OverExplain");
+ sig.put("Y", "NoResponse");
+ sig.put("Z", 1.0);
+
+ assertEquals("MODE-DefensiveDefaulter", wrapper.classify(sig));
+ }
+
+ @Test
+ @DisplayName("Pattern: ExternalTrustSpender triggers on F=Fulfilled + A=Outsider")
+ void testPatternExternalTrustSpender() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ Map sig = new LinkedHashMap<>();
+ sig.put("P", "HasPromise");
+ sig.put("F", "Fulfilled");
+ sig.put("T", 0.0);
+ sig.put("E", "Willing");
+ sig.put("C", 0);
+ sig.put("R", 0);
+ sig.put("A", "Outsider");
+ sig.put("X", "Genuine");
+ sig.put("Y", "NoResponse");
+ sig.put("Z", 1.0);
+
+ assertEquals("MODE-ExternalTrustSpender", wrapper.classify(sig));
+ }
+
+ @Test
+ @DisplayName("Pattern: InternalDestroyer triggers on F=Unfulfilled + Y=Indifferent")
+ void testPatternInternalDestroyer() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ Map sig = new LinkedHashMap<>();
+ sig.put("P", "HasPromise");
+ sig.put("F", "Unfulfilled");
+ sig.put("T", 0.0);
+ sig.put("E", "Willing");
+ sig.put("C", 0);
+ sig.put("R", 0);
+ sig.put("A", "Self");
+ sig.put("X", "Genuine");
+ sig.put("Y", "Indifferent");
+ sig.put("Z", 1.0);
+
+ assertEquals("MODE-InternalDestroyer", wrapper.classify(sig));
+ }
+
+ @Test
+ @DisplayName("Pattern: Fluctuating triggers on Z > 2.0")
+ void testPatternFluctuating() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ Map sig = new LinkedHashMap<>();
+ sig.put("P", "HasPromise");
+ sig.put("F", "Fulfilled");
+ sig.put("T", 0.0);
+ sig.put("E", "Willing");
+ sig.put("C", 0);
+ sig.put("R", 0);
+ sig.put("A", "Self");
+ sig.put("X", "Genuine");
+ sig.put("Y", "NoResponse");
+ sig.put("Z", 3.5);
+
+ assertEquals("MODE-Fluctuating", wrapper.classify(sig));
+ }
+
+ // ===== Three-Color Audit Tests =====
+
+ @Test
+ @DisplayName("Color: default is green")
+ void testColorGreen() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ assertEquals("\uD83D\uDFE2", wrapper.determineColor("MODE-StableDisciplined", 0));
+ }
+
+ @Test
+ @DisplayName("Color: InternalDestroyer is red")
+ void testColorRed() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ assertEquals("\uD83D\uDD34", wrapper.determineColor("MODE-InternalDestroyer", 0));
+ }
+
+ @Test
+ @DisplayName("Color: Fluctuating + repeat>3 is yellow")
+ void testColorYellowFluctuating() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ assertEquals("\uD83D\uDFE1", wrapper.determineColor("MODE-Fluctuating", 4));
+ }
+
+ @Test
+ @DisplayName("Color: DefensiveDefaulter + repeat>2 is yellow")
+ void testColorYellowDefensive() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ assertEquals("\uD83D\uDFE1", wrapper.determineColor("MODE-DefensiveDefaulter", 3));
+ }
+
+ // ===== Label Generation Tests =====
+
+ @Test
+ @DisplayName("Labels: generate bilingual labels from signature")
+ void testLabels() {
+ AuditWrapper wrapper = new AuditWrapper("9622");
+ Map sig = new LinkedHashMap<>();
+ sig.put("P", "HasPromise");
+ sig.put("F", "Fulfilled");
+ sig.put("T", 0.0);
+ sig.put("E", "Willing");
+ sig.put("C", 0);
+ sig.put("R", 0);
+ sig.put("A", "Self");
+ sig.put("X", "Genuine");
+ sig.put("Y", "NoResponse");
+ sig.put("Z", 1.0);
+
+ List labels = wrapper.makeLabels(sig, "MODE-StableDisciplined");
+ assertTrue(labels.contains("7F-P-有承诺"));
+ assertTrue(labels.contains("7F-F-已兑现"));
+ assertTrue(labels.contains("7F-E-心甘情愿"));
+ assertTrue(labels.contains("MODE-StableDisciplined"));
+ }
+
+ // ===== Schema Tests =====
+
+ @Test
+ @DisplayName("Schemas: getSchemas returns dna_schema and audit_schema")
+ void testGetSchemas() {
+ Map schemas = adapter.getSchemas();
+ assertTrue(schemas.containsKey("dna_schema"));
+ assertTrue(schemas.containsKey("audit_schema"));
+ }
+
+ // ===== Edge Cases =====
+
+ @Test
+ @DisplayName("Edge case: wrap with null payload")
+ void testWrapNullPayload() {
+ Map result = adapter.wrap(null, "default", "P04");
+ assertNotNull(result.get("dna"));
+ assertNotNull(result.get("audit"));
+ }
+
+ @Test
+ @DisplayName("Edge case: wrap with empty map")
+ void testWrapEmptyMap() {
+ Map result = adapter.wrap(new LinkedHashMap<>(), "default", "P04");
+ assertNotNull(result.get("dna"));
+ }
+
+ @Test
+ @DisplayName("Edge case: wrap with string payload")
+ void testWrapStringPayload() {
+ Map result = adapter.wrap("simple string", "default", "P04");
+ assertEquals("simple string", result.get("payload"));
+ }
+
+ @Test
+ @DisplayName("Edge case: wrap with list payload")
+ void testWrapListPayload() {
+ List list = new ArrayList<>();
+ list.add(1);
+ list.add(2);
+ list.add(3);
+ Map result = adapter.wrap(list, "default", "P04");
+ assertNotNull(result.get("dna"));
+ }
+
+ // ===== UID Consistency Tests =====
+
+ @Test
+ @DisplayName("UID: consistency between meta.uid and audit.uid")
+ void testUidConsistency() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "default", "P04");
+ @SuppressWarnings("unchecked")
+ Map meta = (Map) result.get("meta");
+ @SuppressWarnings("unchecked")
+ Map audit = (Map) result.get("audit");
+
+ String metaUid = (String) meta.get("uid");
+ String auditUid = (String) audit.get("uid");
+ assertEquals("UID" + metaUid, auditUid);
+ }
+
+ // ===== Payload Hash Tests =====
+
+ @Test
+ @DisplayName("Payload hash: same payload produces same hash")
+ void testPayloadHashConsistency() {
+ Map data = new LinkedHashMap<>();
+ data.put("key", "value");
+ data.put("num", 42);
+ Map result1 = adapter.wrap(data, "default", "P04");
+ Map result2 = adapter.wrap(data, "default", "P04");
+
+ @SuppressWarnings("unchecked")
+ Map audit1 = (Map) result1.get("audit");
+ @SuppressWarnings("unchecked")
+ Map audit2 = (Map) result2.get("audit");
+
+ String hash1 = (String) audit1.get("payload_hash");
+ String hash2 = (String) audit2.get("payload_hash");
+ assertEquals(hash1, hash2, "Same payload should produce same hash");
+ }
+
+ @Test
+ @DisplayName("Payload hash: different payloads produce different hashes")
+ void testPayloadHashDifferent() {
+ Map data1 = new HashMap<>();
+ data1.put("key", "value1");
+ Map data2 = new HashMap<>();
+ data2.put("key", "value2");
+
+ Map result1 = adapter.wrap(data1, "default", "P04");
+ Map result2 = adapter.wrap(data2, "default", "P04");
+
+ @SuppressWarnings("unchecked")
+ Map audit1 = (Map) result1.get("audit");
+ @SuppressWarnings("unchecked")
+ Map audit2 = (Map) result2.get("audit");
+
+ String hash1 = (String) audit1.get("payload_hash");
+ String hash2 = (String) audit2.get("payload_hash");
+ assertNotEquals(hash1, hash2, "Different payloads should produce different hashes");
+ }
+
+ // ===== Persona Tests =====
+
+ @Test
+ @DisplayName("Persona: custom persona appears in audit")
+ void testCustomPersona() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "code", "P14-Lvmeng");
+ @SuppressWarnings("unchecked")
+ Map audit = (Map) result.get("audit");
+ assertEquals("P14-Lvmeng", audit.get("persona"));
+ }
+
+ @Test
+ @DisplayName("Persona: default persona is P04")
+ void testDefaultPersona() {
+ Map payload = new HashMap<>();
+ payload.put("test", "data");
+ Map result = adapter.wrap(payload, "code", "P04");
+ @SuppressWarnings("unchecked")
+ Map audit = (Map) result.get("audit");
+ assertEquals("P04", audit.get("persona"));
+ }
+}
\ No newline at end of file