Skip to content
Open
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
16 changes: 16 additions & 0 deletions adapters/ruby/lh-standard-adapter.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

Gem::Specification.new do |spec|
spec.name = "lh-standard-adapter"
spec.version = "1.0.0"
spec.authors = ["LongHun Core"]
spec.summary = "LongHun Standard Adapter — v∞ DNA traceability + seven-factor behavioral audit for Ruby"
spec.description = "Ruby implementation of the LongHun standard adapter with DNA generation, audit wrapping, and validation."
spec.license = "CC-BY-NC-SA-4.0"
spec.required_ruby_version = ">= 2.7"

spec.files = Dir["lib/**/*.rb", "README.md", "LICENSE"]
spec.require_paths = ["lib"]

spec.add_development_dependency "minitest", "~> 5.0"
end
45 changes: 45 additions & 0 deletions adapters/ruby/lib/lh_standard_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require_relative "lh_standard_adapter/dna_generator"
require_relative "lh_standard_adapter/audit_wrapper"
require_relative "lh_standard_adapter/validator"

module LhStandardAdapter
VERSION = "1.0.0"

class LongHunAdapter
attr_reader :uid, :device, :locale

def initialize(uid: "9622", device: "HM-9622-001", locale: "Asia/Shanghai")
@uid = uid; @device = device; @locale = locale
@dna_gen = DNAGenerator.new(uid: uid, device: device, locale: locale)
@audit = AuditWrapper.new(uid: uid)
@validator = Validator.new
end

def wrap(data, task_type: "default", persona: "P04", action: "WRAP", version: nil)
dna = @dna_gen.generate(task_type: task_type, action: action, version: version)
audit = @audit.wrap(data, task_type: task_type, persona: persona)
{
dna: dna, audit: audit, payload: data,
meta: {
adapter_version: VERSION, uid: @uid, device: @device,
task_type: task_type, persona: persona,
generated_at: Time.now.utc.iso8601, format: "longhun-v∞"
}
}
end

def validate(wrapped)
@validator.validate(wrapped)
end

def schemas
{
dna_schema: { type: "string", description: "v∞ DNA traceability code" },
audit_schema: {
type: "object",
required: %w[audit_version uid behavior_signature behavior_pattern behavior_labels color]
}
}
end
end
end
67 changes: 67 additions & 0 deletions adapters/ruby/lib/lh_standard_adapter/audit_wrapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
require "digest"
require "json"
require "time"

module LhStandardAdapter
LABEL_MAP = {
"P" => {"HasPromise"=>"7F-P-有承诺","NoPromise"=>"7F-P-无承诺"},
"F" => {"Fulfilled"=>"7F-F-已兑现","Unfulfilled"=>"7F-F-未兑现","Partial"=>"7F-F-部分兑现"},
"E" => {"Willing"=>"7F-E-心甘情愿","Perfunctory"=>"7F-E-敷衍","Resentful"=>"7F-E-怨恨","Numb"=>"7F-E-麻木"},
"A" => {"Self"=>"7F-A-自己","Partner"=>"7F-A-伴侣","Family"=>"7F-A-家庭","Outsider"=>"7F-A-外人","Public"=>"7F-A-公众"},
"X" => {"OverExplain"=>"7F-X-过度解释","Silent"=>"7F-X-沉默","Genuine"=>"7F-X-真诚","Indifferent"=>"7F-X-冷漠"},
"Y" => {"Changed"=>"7F-Y-改正","Resisted"=>"7F-Y-抗拒","Indifferent"=>"7F-Y-无视","NoResponse"=>"7F-Y-无响应"}
}.freeze

class AuditWrapper
def initialize(uid: "9622")
@uid = uid
end

def wrap(payload, task_type: "default", persona: "P04")
task_type = "default" if task_type.nil? || task_type.empty?
persona = "P04" if persona.nil? || persona.empty?

sig = {"P"=>"HasPromise","F"=>"Fulfilled","T"=>0.0,"E"=>"Willing",
"C"=>0,"R"=>0,"A"=>"Self","X"=>"Genuine","Y"=>"NoResponse","Z"=>1.0}
pattern = classify(sig)
labels = make_labels(sig, pattern)
color = determine_color(pattern, sig["R"])
payload_json = JSON.generate(payload)
payload_hash = Digest::SHA256.hexdigest(payload_json)[0,16]

{
"audit_version"=>"v1.0","uid"=>"UID#{@uid}","persona"=>persona,"task_type"=>task_type,
"behavior_signature"=>sig,"behavior_pattern"=>pattern,"behavior_labels"=>labels,
"color"=>color,"timestamp"=>Time.now.utc.iso8601,"payload_hash"=>payload_hash
}
end

private

def classify(sig)
f, x, a, y, z = sig["F"], sig["X"], sig["A"], sig["Y"], sig["Z"]
return "MODE-DefensiveDefaulter" if f == "Unfulfilled" && x == "OverExplain"
return "MODE-ExternalTrustSpender" if f == "Fulfilled" && a == "Outsider"
return "MODE-InternalDestroyer" if f == "Unfulfilled" && y == "Indifferent"
return "MODE-Fluctuating" if z > 2.0
"MODE-StableDisciplined"
end

def make_labels(sig, pattern)
labels = []
%w[P F E A X Y].each do |factor|
val = sig[factor]
labels << LABEL_MAP[factor][val] if LABEL_MAP[factor] && LABEL_MAP[factor][val]
end
labels << pattern
labels
end

def determine_color(pattern, repeat)
return "🔴" if pattern == "MODE-InternalDestroyer"
return "🟡" if pattern == "MODE-Fluctuating" && repeat > 3
return "🟡" if pattern == "MODE-DefensiveDefaulter" && repeat > 2
"🟢"
end
end
end
76 changes: 76 additions & 0 deletions adapters/ruby/lib/lh_standard_adapter/dna_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require "digest"
require "time"

module LhStandardAdapter
TIAN_GAN = %w[Jia Yi Bing Ding Wu Ji Geng Xin Ren Gui].freeze
DI_ZHI = %w[Zi Chou Yin Mao Chen Si Wu Wei Shen You Xu Hai].freeze
SHI_CHEN = %w[ZiShi ChouShi YinShi MaoShi ChenShi SiShi WuShi WeiShi ShenShi YouShi XuShi HaiShi].freeze

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

TASK_HEXAGRAM_MAP = {
"default"=>"governance","code"=>"engine","deploy"=>"deploy","audit"=>"audit",
"security"=>"security","archive"=>"archive","init"=>"init","learn"=>"learn",
"legal"=>"legal","privacy"=>"privacy","trust"=>"trust","complete"=>"complete","progress"=>"progress"
}.freeze

class DNAGenerator
CYCLE_YEAR = 1984
CYCLE_MONTH = [2,4,6,8,10,0,2,4,6,8,10,0].freeze

def initialize(uid: "9622", device: "HM-9622-001", locale: "Asia/Shanghai")
@uid, @device, @locale = uid, device, locale
end

def generate(task_type: "default", action: "WRAP", version: nil)
task_type = "default" if task_type.nil? || task_type.empty?
action = "WRAP" if action.nil? || action.empty?
version = "V1.0" if version.nil? || version.empty?

now = Time.now.utc
stem = compute_stem_branch(now)
hex = select_hexagram(task_type)
body = "ADAPTER-#{task_type.upcase}-#{action.upcase}-#{version}"
raw = "#{stem[:year]}#{stem[:month]}#{stem[:day]}#{stem[:shichen]}#{hex[:symbol]}#{hex[:en]}#{body}#{@device}#{now.iso8601}"
hash8 = Digest::SHA256.hexdigest(raw)[0,8]
"#LongHun⚡️#{stem[:year]}·#{stem[:month]}·#{stem[:day]}·#{stem[:shichen]}·#{hex[:symbol]}#{hex[:en]}-#{body}-#{hash8}"
end

private

def compute_stem_branch(dt)
y, m, doy, h = dt.year, dt.month, dt.yday, dt.hour
ys = (y - CYCLE_YEAR) % 10; yb = (y - CYCLE_YEAR) % 12
mb = CYCLE_MONTH[(y - CYCLE_YEAR) % 10]
ms = (mb + m - 1) % 10; mbr = (m + 1) % 12
ds = (y - 1900 + (y - 1900) / 4 + doy) % 10
db = (y - 1900 + (y - 1900) / 4 + doy) % 12
si = h / 2

{ year: TIAN_GAN[ys] + DI_ZHI[yb],
month: TIAN_GAN[ms] + DI_ZHI[mbr],
day: TIAN_GAN[ds] + DI_ZHI[db],
shichen: SHI_CHEN[si] }
end

def select_hexagram(task_type)
domain = TASK_HEXAGRAM_MAP[task_type] || "governance"
HEXAGRAMS.find { |h| h[:domain] == domain } || HEXAGRAMS.first
end
end
end
97 changes: 97 additions & 0 deletions adapters/ruby/lib/lh_standard_adapter/validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
module LhStandardAdapter
DNA_REGEX = /^#LongHun⚡️([A-Z][a-zA-Z]+)·([A-Z][a-zA-Z]+)·([A-Z][a-zA-Z]+)·([A-Z][a-zA-Z]+)·([\u{4DC0}-\u{4DFF}][A-Za-z]+)-(.+)-([a-f0-9]{8})$/

REQUIRED_TOP = %w[dna audit payload meta].freeze
REQUIRED_AUDIT = %w[audit_version uid behavior_signature behavior_pattern behavior_labels color].freeze
REQUIRED_SIG = %w[P F T E C R A X Y Z].freeze

VALID_P = %w[HasPromise NoPromise].freeze
VALID_F = %w[Fulfilled Unfulfilled Partial].freeze
VALID_E = %w[Willing Perfunctory Resentful Numb].freeze
VALID_A = %w[Self Partner Family Outsider Public].freeze
VALID_X = %w[OverExplain Silent Genuine Indifferent].freeze
VALID_Y = %w[Changed Resisted Indifferent NoResponse].freeze
VALID_COLORS = %w[🟢 🟡 🔴].freeze
VALID_PATTERNS = %w[MODE-DefensiveDefaulter MODE-ExternalTrustSpender MODE-InternalDestroyer MODE-Fluctuating MODE-StableDisciplined].freeze

class Validator
attr_reader :errors, :warnings

def initialize
@errors = []; @warnings = []
end

def validate(wrapped)
@errors.clear; @warnings.clear
unless wrapped.is_a?(Hash) && !wrapped.empty?
@errors << "Input not a non-empty Hash"
return result
end

missing = REQUIRED_TOP - wrapped.keys
@errors << "Missing top keys: #{missing.join(',')}" unless missing.empty?

dna = wrapped["dna"] || wrapped[:dna] || ""
if dna.empty?
@errors << "DNA empty"
elsif !DNA_REGEX.match?(dna)
@errors << "DNA regex fail: #{dna[0..59]}"
end

audit = wrapped["audit"] || wrapped[:audit] || {}
if audit.is_a?(Hash) && !audit.empty?
validate_audit(audit)
else
@errors << "Audit not a Hash"
end

result
end

private

def validate_audit(audit)
missing = REQUIRED_AUDIT - audit.keys.map(&:to_s)
@errors << "Missing audit keys: #{missing.join(',')}" unless missing.empty?

sig = audit["behavior_signature"] || audit[:behavior_signature] || {}
if sig.is_a?(Hash)
missing_sig = REQUIRED_SIG - sig.keys.map(&:to_s)
@errors << "Missing sig keys: #{missing_sig.join(',')}" unless missing_sig.empty?
validate_sig_values(sig)
else
@errors << "sig not Hash"
end

validate_optional(audit)
end

def validate_sig_values(sig)
@warnings << "Invalid P: #{sig['P']||sig[:P]}" unless VALID_P.include?(sig["P"] || sig[:P])
@warnings << "Invalid F: #{sig['F']||sig[:F]}" unless VALID_F.include?(sig["F"] || sig[:F])
@warnings << "Invalid E: #{sig['E']||sig[:E]}" unless VALID_E.include?(sig["E"] || sig[:E])
@warnings << "Invalid A: #{sig['A']||sig[:A]}" unless VALID_A.include?(sig["A"] || sig[:A])
@warnings << "Invalid X: #{sig['X']||sig[:X]}" unless VALID_X.include?(sig["X"] || sig[:X])
@warnings << "Invalid Y: #{sig['Y']||sig[:Y]}" unless VALID_Y.include?(sig["Y"] || sig[:Y])
end

def validate_optional(audit)
pattern = audit["behavior_pattern"] || audit[:behavior_pattern] || ""
@warnings << "Unknown pattern: #{pattern}" if !pattern.empty? && !VALID_PATTERNS.include?(pattern)

color = audit["color"] || audit[:color]
@warnings << "Unknown color: #{color}" if color && !VALID_COLORS.include?(color)

ph = audit["payload_hash"] || audit[:payload_hash]
@warnings << "Bad payload_hash: #{ph}" if ph && ph.length != 16
end

def result
valid = @errors.empty?
{
valid: valid, errors: @errors, warnings: @warnings,
summary: valid ? "✅ VALID — #{@warnings.length} warning(s)" : "❌ INVALID — #{@errors.length} error(s)"
}
end
end
end
Loading