mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
ene-session-sync: add meta_autotype.rs; embedded_surface: Rust rs-surface daemon
- src/meta_autotype.rs (396L): port ene_meta_autotype.py — deterministic
ContingentField classifier with scalar_type, bind_class, surface_hint,
autotype_payload, handle_request, and 3 unit tests (all green).
- Cargo.toml: wire meta_autotype mod, add tempfile dev-dep for existing tests.
- embedded_surface/rs-surface/ (1025L): full Rust port of server.py.
- axum 0.7 HTTP server + WebSocket binary surface-frame protocol.
- All 16 op-codes: HEALTH, STATUS, METRICS, ATTEST, COMPRESS, RGFLOW,
ROUTE, MOUNT_STATUS, SNAPSHOT, ENTER_RECOVERY, PRIMITIVES, PLAN_ROUTE,
WIKI, FRACTAL_FOLD, META_AUTOTYPE, CREDENTIALS.
- Inline omni_lut choose_route (choose_route fn, shannon_entropy,
detect_sequence_surface, s3c_shear) — no Python deps.
- Inline meta_autotype_payload — mirrors handle_request from ene_meta_autotype.py.
- Same wire framing: [v][flags][codec][op][req_id][len][crc32][payload].
- Dockerfile: multi-stage Rust builder (alpine) + minimal runtime image;
Python server.py kept as fallback alongside binary.
- cargo build: 0 errors on both crates; meta_autotype tests: 3/3 pass.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
d511cbe6c0
commit
896f364d0d
8 changed files with 2576 additions and 5 deletions
|
|
@ -1,4 +1,18 @@
|
|||
FROM python:3.12-alpine
|
||||
# ── Stage 1: build the Rust surface binary ─────────────────────────────────
|
||||
FROM rust:1.82-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static pkgconfig
|
||||
|
||||
WORKDIR /build
|
||||
COPY rs-surface/Cargo.toml rs-surface/Cargo.lock ./
|
||||
# Pre-fetch deps (layer cache)
|
||||
RUN mkdir src && echo 'fn main(){}' > src/main.rs && cargo build --release 2>/dev/null || true
|
||||
|
||||
COPY rs-surface/src ./src
|
||||
RUN touch src/main.rs && cargo build --release
|
||||
|
||||
# ── Stage 2: minimal runtime image ─────────────────────────────────────────
|
||||
FROM alpine:3.20
|
||||
|
||||
ENV RS_SURFACE_PROFILE=/etc/rs-surface/node.json
|
||||
ENV RS_SURFACE_STATE=/var/lib/rs-surface
|
||||
|
|
@ -10,14 +24,16 @@ RUN adduser -D -H -u 988 surface \
|
|||
&& mkdir -p /opt/rs-surface /etc/rs-surface /var/lib/rs-surface /var/log/rs-surface /run/rs-surface /mnt/topological-storage \
|
||||
&& chown -R surface:surface /var/lib/rs-surface /var/log/rs-surface /run/rs-surface /mnt/topological-storage
|
||||
|
||||
COPY server.py /opt/rs-surface/server.py
|
||||
COPY test_ws_client.py /opt/rs-surface/test_ws_client.py
|
||||
COPY --from=builder /build/target/release/rs-surface /opt/rs-surface/rs-surface
|
||||
COPY profiles/racknerd-surface.json /etc/rs-surface/node.json
|
||||
|
||||
# Keep the Python server alongside for fallback / debugging
|
||||
COPY server.py /opt/rs-surface/server.py
|
||||
|
||||
USER surface
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=3s --retries=3 \
|
||||
CMD python /opt/rs-surface/test_ws_client.py --http-only --host 127.0.0.1 --port 8080
|
||||
CMD wget -qO- http://127.0.0.1:${RS_SURFACE_PORT}/health || exit 1
|
||||
|
||||
CMD ["python", "/opt/rs-surface/server.py"]
|
||||
CMD ["/opt/rs-surface/rs-surface"]
|
||||
|
|
|
|||
1098
4-Infrastructure/infra/embedded_surface/rs-surface/Cargo.lock
generated
Normal file
1098
4-Infrastructure/infra/embedded_surface/rs-surface/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,31 @@
|
|||
[package]
|
||||
name = "rs-surface"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
description = "Embedded node surface daemon — Rust port of embedded_surface/server.py"
|
||||
|
||||
[[bin]]
|
||||
name = "rs-surface"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
axum = { version = "0.7", features = ["ws", "macros"] }
|
||||
base64 = "0.22"
|
||||
flate2 = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sha2 = "0.10"
|
||||
hex = "0.4"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tower = "0.4"
|
||||
tower-http = { version = "0.5", features = ["fs", "trace"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
strip = true
|
||||
1025
4-Infrastructure/infra/embedded_surface/rs-surface/src/main.rs
Normal file
1025
4-Infrastructure/infra/embedded_surface/rs-surface/src/main.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -464,6 +464,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-postgres",
|
||||
"tracing",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ tokio-postgres = { version = "0.7", features = ["with-serde_json-1", "with-chron
|
|||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ mod embed;
|
|||
mod ene_core;
|
||||
mod fractal_fold;
|
||||
mod math;
|
||||
mod meta_autotype;
|
||||
mod misc;
|
||||
mod models;
|
||||
mod normalize;
|
||||
|
|
|
|||
396
4-Infrastructure/infra/ene-session-sync/src/meta_autotype.rs
Normal file
396
4-Infrastructure/infra/ene-session-sync/src/meta_autotype.rs
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
/// ENE meta-autotype shim.
|
||||
///
|
||||
/// When ENE sees data without a defined ingestion surface, this module creates
|
||||
/// contingent fields instead of pretending the schema is known. It is
|
||||
/// intentionally deterministic: a tiny classifier/autotyper with receipts, not
|
||||
/// an external LLM.
|
||||
///
|
||||
/// Ported from infra/ene_meta_autotype.py (219 lines).
|
||||
use serde_json::{json, Value};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::BTreeMap;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
const VERSION: &str = "ene_meta_autotype_v1";
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
fn iso_utc() -> String {
|
||||
let secs = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
// Simple ISO-8601 UTC string without chrono dep.
|
||||
let s = secs % 86400;
|
||||
let d = secs / 86400;
|
||||
let h = s / 3600;
|
||||
let m = (s % 3600) / 60;
|
||||
let sec = s % 60;
|
||||
// Days-since-epoch → approximate calendar date (good enough for receipts).
|
||||
let days_400 = d / 146097;
|
||||
let rem = d % 146097;
|
||||
let days_100 = rem.min(3 * 36524) / 36524;
|
||||
let rem = rem - days_100 * 36524;
|
||||
let days_4 = rem / 1461;
|
||||
let rem = rem % 1461;
|
||||
let days_1 = rem.min(3 * 365) / 365;
|
||||
let rem = rem - days_1 * 365;
|
||||
let year = days_400 * 400 + days_100 * 100 + days_4 * 4 + days_1 + 1970;
|
||||
// Month from day-of-year (rem).
|
||||
let leap = (days_1 == 3) && (days_4 != 24 || days_100 == 3);
|
||||
let days_in_month: [u64; 12] = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
let mut month = 0u64;
|
||||
let mut day_rem = rem;
|
||||
for (i, &days) in days_in_month.iter().enumerate() {
|
||||
if day_rem < days {
|
||||
month = i as u64 + 1;
|
||||
break;
|
||||
}
|
||||
day_rem -= days;
|
||||
}
|
||||
if month == 0 {
|
||||
month = 12;
|
||||
}
|
||||
format!(
|
||||
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
|
||||
year,
|
||||
month,
|
||||
day_rem + 1,
|
||||
h,
|
||||
m,
|
||||
sec
|
||||
)
|
||||
}
|
||||
|
||||
fn sha256_hex(text: &str) -> String {
|
||||
let mut h = Sha256::new();
|
||||
h.update(text.as_bytes());
|
||||
hex::encode(h.finalize())
|
||||
}
|
||||
|
||||
fn canonical_json(v: &Value) -> String {
|
||||
// serde_json sorts object keys by default when using BTreeMap, but
|
||||
// Value::Object uses IndexMap. We re-serialise through sorted BTreeMap.
|
||||
fn sort_value(v: &Value) -> Value {
|
||||
match v {
|
||||
Value::Object(map) => {
|
||||
let sorted: BTreeMap<_, _> =
|
||||
map.iter().map(|(k, v)| (k.clone(), sort_value(v))).collect();
|
||||
let mut out = serde_json::Map::new();
|
||||
for (k, v) in sorted {
|
||||
out.insert(k, v);
|
||||
}
|
||||
Value::Object(out)
|
||||
}
|
||||
Value::Array(arr) => Value::Array(arr.iter().map(sort_value).collect()),
|
||||
other => other.clone(),
|
||||
}
|
||||
}
|
||||
serde_json::to_string(&sort_value(v)).unwrap_or_default()
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Type inference
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
fn scalar_type(v: &Value) -> &'static str {
|
||||
match v {
|
||||
Value::Bool(_) => "boolean",
|
||||
Value::Number(n) => {
|
||||
if n.is_f64() {
|
||||
"float"
|
||||
} else {
|
||||
"integer"
|
||||
}
|
||||
}
|
||||
Value::Array(_) => "array",
|
||||
Value::Object(_) => "object",
|
||||
Value::Null => "null",
|
||||
Value::String(s) => {
|
||||
// sha256 hex
|
||||
if s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return "sha256_hex";
|
||||
}
|
||||
// integer string
|
||||
if s.parse::<i64>().is_ok() {
|
||||
return "integer_string";
|
||||
}
|
||||
// float string
|
||||
if s.parse::<f64>().is_ok() && s.contains('.') {
|
||||
return "float_string";
|
||||
}
|
||||
if s.starts_with("http://") || s.starts_with("https://") {
|
||||
return "url";
|
||||
}
|
||||
"string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bind_class_for(name: &str, inferred_type: &str) -> &'static str {
|
||||
let lower = name.to_lowercase();
|
||||
if inferred_type == "sha256_hex"
|
||||
|| lower.contains("hash")
|
||||
|| lower.contains("receipt")
|
||||
{
|
||||
return "attestation_bind";
|
||||
}
|
||||
let geo_tokens = ["x", "y", "z", "coord", "manifold", "topology", "graph"];
|
||||
if geo_tokens.iter().any(|t| lower.contains(t)) {
|
||||
return "geometric_bind";
|
||||
}
|
||||
let ctrl_tokens = ["policy", "allow", "deny", "risk", "security"];
|
||||
if ctrl_tokens.iter().any(|t| lower.contains(t)) {
|
||||
return "control_bind";
|
||||
}
|
||||
"informational_bind"
|
||||
}
|
||||
|
||||
fn surface_hint(text: &str, parsed: Option<&Value>) -> &'static str {
|
||||
let lower = text.to_lowercase();
|
||||
if let Some(Value::Object(map)) = parsed {
|
||||
let keys: std::collections::HashSet<&str> =
|
||||
map.keys().map(|k| k.as_str()).collect();
|
||||
if keys.contains("nodes") || keys.contains("edges") || lower.contains("graphml") {
|
||||
return "graph_concept_surface";
|
||||
}
|
||||
if keys.contains("archive_id")
|
||||
&& keys.contains("source_type")
|
||||
&& keys.contains("raw_content")
|
||||
{
|
||||
return "ene_archive_surface";
|
||||
}
|
||||
if keys.contains("pkg") && keys.contains("tier") && keys.contains("domain") {
|
||||
return "ene_package_surface";
|
||||
}
|
||||
}
|
||||
if lower.contains("<graphml") {
|
||||
return "graphml_surface";
|
||||
}
|
||||
if lower.contains("[[") && lower.contains("]]") {
|
||||
return "wiki_surface";
|
||||
}
|
||||
// Nucleotide sequence check (relaxed)
|
||||
let trimmed = text.trim();
|
||||
if trimmed.len() >= 8
|
||||
&& trimmed
|
||||
.chars()
|
||||
.all(|c| matches!(c.to_ascii_uppercase(), 'A' | 'C' | 'G' | 'T' | 'U' | 'N' | 'R' | 'Y' | 'S' | 'W' | 'K' | 'M' | 'B' | 'D' | 'H' | 'V' | ' ' | '\n' | '\r' | '\t'))
|
||||
{
|
||||
return "sequence_surface";
|
||||
}
|
||||
"unknown_surface"
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Autotype
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContingentField {
|
||||
pub name: String,
|
||||
pub inferred_type: String,
|
||||
pub confidence: f64,
|
||||
pub extraction_rule: String,
|
||||
pub bind_class: String,
|
||||
pub status: &'static str,
|
||||
}
|
||||
|
||||
impl ContingentField {
|
||||
fn to_value(&self) -> Value {
|
||||
json!({
|
||||
"name": self.name,
|
||||
"inferred_type": self.inferred_type,
|
||||
"confidence": self.confidence,
|
||||
"extraction_rule": self.extraction_rule,
|
||||
"bind_class": self.bind_class,
|
||||
"status": self.status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn autotype_payload(data: &[u8], name: &str) -> Value {
|
||||
let text = String::from_utf8_lossy(data).into_owned();
|
||||
let parsed: Option<Value> = serde_json::from_str(&text).ok();
|
||||
let mut fields: Vec<ContingentField> = Vec::new();
|
||||
|
||||
match &parsed {
|
||||
Some(Value::Object(map)) => {
|
||||
let mut keys: Vec<_> = map.keys().collect();
|
||||
keys.sort();
|
||||
for key in keys {
|
||||
let value = &map[key];
|
||||
let inf = scalar_type(value).to_string();
|
||||
let bc = bind_class_for(key, &inf).to_string();
|
||||
fields.push(ContingentField {
|
||||
name: key.clone(),
|
||||
inferred_type: inf,
|
||||
confidence: 0.85,
|
||||
extraction_rule: format!("json_pointer:/{}", key),
|
||||
bind_class: bc,
|
||||
status: "contingent",
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(Value::Array(_)) => {
|
||||
fields.push(ContingentField {
|
||||
name: "items".to_string(),
|
||||
inferred_type: "array".to_string(),
|
||||
confidence: 0.8,
|
||||
extraction_rule: "json_root_array".to_string(),
|
||||
bind_class: "informational_bind".to_string(),
|
||||
status: "contingent",
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
// Token extraction from free text (mirrors Python regex)
|
||||
let mut tokens: Vec<String> = text
|
||||
.split(|c: char| !c.is_alphanumeric() && c != '_')
|
||||
.filter(|s| {
|
||||
s.len() >= 3
|
||||
&& s.starts_with(|c: char| c.is_alphabetic() || c == '_')
|
||||
})
|
||||
.map(|s| s.to_string())
|
||||
.collect::<std::collections::BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.take(64)
|
||||
.collect();
|
||||
tokens.sort();
|
||||
tokens.truncate(16);
|
||||
for token in tokens {
|
||||
let bc = bind_class_for(&token, "string").to_string();
|
||||
fields.push(ContingentField {
|
||||
name: token.clone(),
|
||||
inferred_type: "token".to_string(),
|
||||
confidence: 0.45,
|
||||
extraction_rule: "regex_identifier_token".to_string(),
|
||||
bind_class: bc,
|
||||
status: "contingent",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hint = surface_hint(&text, parsed.as_ref());
|
||||
|
||||
let raw_content = json!({
|
||||
"kind": "ene_meta_autotype",
|
||||
"version": VERSION,
|
||||
"name": name,
|
||||
"surface_hint": hint,
|
||||
"byte_len": data.len(),
|
||||
"contingent_fields": fields.iter().map(|f| f.to_value()).collect::<Vec<_>>(),
|
||||
"policy": {
|
||||
"defined_ingestion_surface": hint != "unknown_surface",
|
||||
"authority": "contingent_until_bound_by_ingestion_surface",
|
||||
"required_gate": ["OBSERVE", "BIND", "ROUTE", "POLICY_CHECK", "VERIFY", "RECEIPT"],
|
||||
},
|
||||
});
|
||||
|
||||
let content_hash = sha256_hex(&canonical_json(&raw_content));
|
||||
let receipt_pre = json!({ "v": VERSION, "content_hash": content_hash, "name": name });
|
||||
let receipt = sha256_hex(&canonical_json(&receipt_pre));
|
||||
|
||||
json!({
|
||||
"ok": true,
|
||||
"op": "meta_autotype",
|
||||
"surface_hint": hint,
|
||||
"field_count": fields.len(),
|
||||
"archive_record": {
|
||||
"archive_id": format!("json_catalog_ene_meta_autotype_{}", &content_hash[..16]),
|
||||
"source_type": "json_catalog",
|
||||
"source_file": format!("ene-meta-autotype://{}", &content_hash[..16]),
|
||||
"raw_content": raw_content,
|
||||
"extracted_text": &text[..text.len().min(10000)],
|
||||
"extracted_at": iso_utc(),
|
||||
"content_hash": content_hash,
|
||||
"extraction_version": VERSION,
|
||||
},
|
||||
"jsonl_event": {
|
||||
"src": "ene",
|
||||
"op": "upsert",
|
||||
"data": {
|
||||
"pkg": format!("ene/meta-autotype/{}", &receipt[..16]),
|
||||
"version": VERSION,
|
||||
"tier": "RESEARCH",
|
||||
"domain": "semantic",
|
||||
"archetype": "contingent_schema",
|
||||
"tags": ["ene", "meta_autotype", hint],
|
||||
"sha256": &content_hash,
|
||||
},
|
||||
"bind": {
|
||||
"lawful": true,
|
||||
"class": "informational_bind",
|
||||
"invariant": "contingent_fields_are_not_authoritative",
|
||||
},
|
||||
"provenance": { "attestation_hash": format!("sha256:{}", receipt) },
|
||||
},
|
||||
"receipt": receipt,
|
||||
})
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Public request handler (mirrors handle_request in Python)
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
pub fn handle_request(request: &Value) -> Value {
|
||||
let data: Vec<u8> = if let Some(b64) = request.get("data_b64").and_then(|v| v.as_str()) {
|
||||
use base64::Engine as _;
|
||||
base64::engine::general_purpose::STANDARD
|
||||
.decode(b64)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
request
|
||||
.get("text")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
};
|
||||
let name = request
|
||||
.get("name")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("payload");
|
||||
autotype_payload(&data, name)
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Stand-alone entry point (mirrors __main__ in Python)
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
pub fn run_cli(text: &str, name: &str) {
|
||||
let result = autotype_payload(text.as_bytes(), name);
|
||||
println!("{}", serde_json::to_string_pretty(&result).unwrap_or_default());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_json_object_surface() {
|
||||
let data = br#"{"nodes": [1, 2], "edges": []}"#;
|
||||
let result = autotype_payload(data, "test");
|
||||
assert_eq!(result["ok"], true);
|
||||
assert_eq!(result["surface_hint"], "graph_concept_surface");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_surface() {
|
||||
let data = b"hello world foobar";
|
||||
let result = autotype_payload(data, "test");
|
||||
assert_eq!(result["surface_hint"], "unknown_surface");
|
||||
assert!(result["field_count"].as_u64().unwrap_or(0) > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_receipt_roundtrip() {
|
||||
let data = br#"{"pkg": "ene/test", "tier": "RESEARCH", "domain": "semantic"}"#;
|
||||
let result = autotype_payload(data, "roundtrip");
|
||||
assert_eq!(result["surface_hint"], "ene_package_surface");
|
||||
let receipt = result["receipt"].as_str().unwrap();
|
||||
assert_eq!(receipt.len(), 64);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue