// ============================================================================ // SyFox tests — assert-based, no framework. // Every test names the mechanism it protects. // ============================================================================ #include "core/syfox.hpp" #include "core/derive.hpp" #include "core/bench.hpp" #include "core/gate.hpp" #include "core/recall.hpp" #include #include #include static int failures = 0; #define CHECK(cond, msg) \ do { \ if (cond) { std::cout << " ok " << msg << "\n"; } \ else { std::cout << " FAIL " << msg << "\n"; ++failures; } \ } while (0) static void test_json() { std::cout << "[json]\n"; auto v = sfx::JV::parse(R"({"a":1,"b":[true,null,"x\ny"],"c":{"d":-2.5e1}})"); CHECK(v.is_obj(), "object parses"); CHECK(v.at("a").as_num() == 1.0, "number"); CHECK(v.at("b").is_arr() && v.at("b").arr.size() == 3, "array"); CHECK(v.at("b").arr[2].str == "x\ny", "string escape"); CHECK(std::fabs(v.at("c").at("d").as_num() + 25.0) < 1e-9, "exponent"); std::string dumped = v.dump(); auto v2 = sfx::JV::parse(dumped); CHECK(v2.dump() == dumped, "roundtrip stable"); bool threw = false; try { sfx::JV::parse("{\"a\":}"); } catch (const std::exception&) { threw = true; } CHECK(threw, "malformed input rejected"); } static void test_folding() { std::cout << "[tokenize]\n"; // si::tokenize itself is UNCHANGED since v0.2 (substrate encoding baseline) CHECK(si::tokenize("Refunds delayed!!")[0] == "refund", "plural folded"); CHECK(si::tokenize("Charged twice.")[0] == "charg", "past folded"); CHECK(si::tokenize("charge")[0] == "charg", "verb folded to same stem"); CHECK(si::tokenize("hello")[0] == "hello", "plain word untouched"); CHECK(si::tokenize("a x7").size() == 1, "single chars dropped"); } static void test_normalize() { std::cout << "[normalize]\n"; // Porter spot-checks against the 1980 reference vocabulary CHECK(si::norm::porter_stem("relational") == "relat", "porter step2: ational"); CHECK(si::norm::porter_stem("generalization") == "gener", "porter step4: ization"); CHECK(si::norm::porter_stem("controll") == "control", "porter step5: double l"); CHECK(si::norm::porter_stem("rate") == "rate", "porter keeps cvc+e"); CHECK(si::norm::porter_stem("agreed") == "agre", "porter step1b: ed"); // synonym folding through the stemmed table CHECK(si::norm::normalize("Reimbursement denied")[0] == "refund", "synonym fold: reimbursement -> refund"); CHECK(si::norm::normalize("urgently")[0] == si::norm::normalize("urgent")[0], "synonym fold: urgently == urgent"); CHECK(si::norm::normalize("asap")[0] == si::norm::normalize("immediately")[0], "synonym fold: asap == immediately"); // one pipeline for teach + decide: porter parity with the old fold on our vocab CHECK(si::norm::normalize("Charged twice.")[0] == "charg", "porter: charged -> charg"); CHECK(si::norm::normalize("charge")[0] == "charg", "porter: charge -> charg"); CHECK(si::norm::normalize("Invoices")[0] == "invoic", "porter: invoices -> invoic"); // determinism: same input -> same tokens, byte for byte bool det = si::norm::normalize("please refund the duplicate charge immediately") == si::norm::normalize("please refund the duplicate charge immediately"); CHECK(det, "normalize is deterministic"); } static void test_field_physics() { std::cout << "[si substrate]\n"; si::SubstrateConfig cfg; si::Substrate s(cfg); for (const auto& w : std::vector{"alpha", "beta", "gamma"}) s.intern(w); CHECK(s.node_count() == 3, "vocabulary interns"); s.bind(s.find("alpha"), s.find("beta"), 1.0f); s.inject({"alpha"}); float before = s.total_energy(); s.settle(); float after = s.total_energy(); CHECK(after <= before + 1e-4f, "settle is dissipative (energy never grows)"); CHECK(s.readout({"beta"}) > 0.0f, "energy flowed along the lane to beta"); CHECK(s.readout({"gamma"}) == 0.0f, "unconnected node stays dark"); // lane cap: binding many partners evicts the weakest, bounded memory for (int i = 0; i < 400; ++i) { s.intern("pad" + std::to_string(i)); s.bind(s.find("alpha"), s.find("pad" + std::to_string(i)), 0.01f); } CHECK(s.readout_neighbours(s.find("alpha")) <= s.config().lane_cap, "lane cap enforced"); // honest silence on unknown vocabulary si::Substrate s2; for (const auto& w : std::vector{"known", "words"}) s2.intern(w); s2.inject(si::norm::normalize("totally unknown zebra words fail")); s2.settle(); CHECK(s2.total_energy() == 0.0f, "unknown tokens inject nothing (honest silence path)"); // persistence roundtrip s.save("/tmp/syfox_test_substrate.bin"); si::Substrate s3; s3.load("/tmp/syfox_test_substrate.bin"); CHECK(s3.node_count() == s.node_count(), "load restores vocabulary"); CHECK(s3.lane_count() == s.lane_count(), "load restores lane structure"); // same injection + settle must read out identically on the restored substrate s.reset_field(); s.inject({"alpha"}); s.settle(); s3.reset_field(); s3.inject({"alpha"}); s3.settle(); CHECK(std::fabs(s3.readout({"beta"}) - s.readout({"beta"})) < 1e-5f, "restored substrate settles identically"); } static void test_salience_mechanics() { std::cout << "[si salience + miller window]\n"; // (a) integrator (SI physics.hpp lineage): touched/moving nodes gain // salience; a node at rest is an EXACT fixed point (tanh(0)=0), // mirroring SI's sparsity guard. si::Substrate s; for (const auto& w : std::vector{"a", "b", "far"}) s.intern(w); s.bind(s.find("a"), s.find("b"), 1.0f); s.inject({"a"}); s.settle(); CHECK(s.node_salience("a") > 0.0f, "moving node carries salience"); CHECK(s.node_salience("far") == 0.0f, "node at rest is an exact fixed point"); // (b) salience-gated settle stays dissipative and still propagates si::SubstrateConfig g; g.salience_gating = true; si::Substrate s2(g); s2.intern("x"); s2.intern("y"); s2.bind(s2.find("x"), s2.find("y"), 1.0f); s2.inject({"x"}); float before = s2.total_energy(); s2.settle(); CHECK(s2.total_energy() <= before + 1e-4f, "salience-gated settle is dissipative"); CHECK(s2.readout({"y"}) > 0.0f, "salience-gated field propagates along lanes"); // (c) miller window (TSDA live_cap lineage): cap inside [cap-4, cap], // and the SAME state always yields the SAME cap and field. si::SubstrateConfig m; m.miller_window = true; m.source_cap = 9.0f; // window becomes [5,9], as in TSDA si::Substrate s3(m); std::vector toks; for (int i = 0; i < 30; ++i) { std::string t = "t" + std::to_string(i); s3.intern(t); if (i > 0) s3.bind(s3.find("t" + std::to_string(i - 1)), s3.find(t), 0.3f); toks.push_back(t); } s3.inject(toks); s3.settle(); float cap_used = s3.last_source_cap(); CHECK(cap_used >= 5.0f && cap_used <= 9.0f, "sampled cap inside Miller window [5,9]"); float e1 = s3.total_energy(); s3.reset_field(); s3.inject(toks); s3.settle(); CHECK(s3.last_source_cap() == cap_used && s3.total_energy() == e1, "same state -> same cap -> same field (deterministic)"); // (d) default config: cap stays pinned at source_cap si::Substrate s4; s4.settle(); CHECK(s4.last_source_cap() == 24.0f, "default cap pinned at 24"); } static syfox::Engine train_choice_engine() { syfox::Engine eng; const char* ex = R"({"state":"the bot sees zombies at night with low health","questions":{"action":{"type":"choice","instructions":"next move","criteria":{"flee":"run away escape avoid","fight":"attack sword combat","dig_in":"hide build shelter"}}},"labels":{"action":"flee"}})"; auto rows = std::vector{sfx::JV::parse(ex)}; for (const auto& r : rows) { eng.learn_example(r.at("state").as_str(), "next move", "flee run away escape avoid"); } return eng; } static void test_hebbian_choice() { std::cout << "[hebbian choice]\n"; syfox::Engine eng = train_choice_engine(); sfx::JV q = sfx::JV::parse( R"({"action":{"type":"choice","instructions":"next move","criteria":{"flee":"run away escape avoid","fight":"attack sword combat","dig_in":"hide build shelter"}}})"); syfox::Usage u; auto ans = eng.decide("zombies appear at night, health is low", q, u); CHECK(!ans.empty() && ans[0].choice == "flee", "learned route fires on related state"); CHECK(!ans[0].deferred, "field settled above silence floor"); // a state sharing nothing with the trained vocabulary -> honest silence, // NOT a random guess (this is the whole point of the substrate) auto ans2 = eng.decide("completely different harvest moon farming", q, u); CHECK(!ans2.empty() && ans2[0].deferred, "unrelated state defers instead of guessing"); } static void test_noul_valence() { std::cout << "[noul valence]\n"; syfox::Engine eng; const std::string instr = "the command is irreversible or destructive"; // true evidence: destructive vocabulary supports the statement eng.learn_noul("command rm -rf wipe the disk force delete", instr, true); eng.learn_noul("command drop table database destroy data", instr, true); // false evidence: harmless vocabulary weakens the route eng.learn_noul("command list files show status read only", instr, false); eng.learn_noul("command print the config view logs", instr, false); sfx::JV q = sfx::JV::parse( R"({"d":{"type":"noul","instructions":"the command is irreversible or destructive"}})"); syfox::Usage u; float p_destructive = eng.decide("the command is rm -rf, it will delete everything", q, u)[0].probability; float p_harmless = eng.decide("the command lists open issues", q, u)[0].probability; CHECK(p_destructive > p_harmless, "field separates destructive from harmless"); CHECK(p_harmless < 0.5f, "harmless command reads as false"); } static void test_calibration_tool() { std::cout << "[calibration tool]\n"; // v2.1 row shape: FULL candidate vector + gold label (multi-class fit) std::vector rows = { {"choice", "a", {{"a", 2.0f}, {"b", 0.2f}}, 0.0f, 0}, {"choice", "a", {{"a", 1.5f}, {"b", 0.4f}}, 0.0f, 0}, {"choice", "b", {{"a", 0.1f}, {"b", 1.8f}}, 0.0f, 0}, {"choice", "b", {{"a", 0.3f}, {"b", 1.2f}}, 0.0f, 0}, }; syfox::Engine eng; eng.fit_calibration(rows); CHECK(eng.calibration().fitted, "fitted flag set"); CHECK(eng.calibration().choice_temperature > 0.0f, "temperature positive"); // multi-class NLL is sharpness-aware: this fit set has clean margins, so // the optimum must sharpen (T < 1), not flatten CHECK(eng.calibration().choice_temperature < 1.0f, "clean margins sharpen temperature"); } static void test_honest_silence_defer() { std::cout << "[honest silence]\n"; syfox::Engine eng; sfx::JV q = sfx::JV::parse(R"({"x":{"type":"choice","instructions":"pick","criteria":{"a":"alpha","b":"beta"}}})"); syfox::Usage u; auto ans = eng.decide("zzz qqq xxxttt", q, u); // vocabulary is empty CHECK(ans[0].deferred && ans[0].reason == "unknown_vocabulary", "unknown vocabulary defers, never guesses"); } static void test_derivation() { std::cout << "[derivation layer: induction, verifier, dreamer, analogy]\n"; using namespace syfox::derive; // probe fabric (same shape as the ARCHITECTURE.md probe): // co-occurrence alpha-beta-gamma; lessons alpha/beta -> x-tokens, beta/gamma -> y-tokens auto build_probe = []() { si::Substrate s; for (const char* t : {"alpha", "beta", "gamma", "x", "target", "y", "other"}) s.intern(t); const si::NodeId A = s.find("alpha"), B = s.find("beta"), G = s.find("gamma"); const si::NodeId X = s.find("x"), T = s.find("target"), Y = s.find("y"), O = s.find("other"); s.bind(A, B, 0.10f); s.bind(B, G, 0.10f); for (si::NodeId st : {A, B}) for (si::NodeId o : {X, T}) s.bind(st, o, 0.20f); for (si::NodeId st : {B, G}) for (si::NodeId o : {Y, O}) s.bind(st, o, 0.20f); return s; }; DeriveConfig c; // (a) composition turns the two-hop path alpha->beta->y into a derived lane si::Substrate s = build_probe(); CHECK(s.lane_weight(s.find("alpha"), s.find("y")) == 0.0f, "no direct alpha->y lane before derivation"); DeriveStats st = compose_pass(s, c); CHECK(s.lane_weight(s.find("alpha"), s.find("y")) > 0.0f, "two-hop path became a derived lane"); CHECK(s.generation_of(s.find("alpha"), s.find("y")) == 1, "derived lane carries generation 1"); CHECK(st.created > 0, "composition reports created lanes"); // (b) observed lanes are never weakened by composition si::Substrate s2 = build_probe(); struct SnapL { si::NodeId a, b; float w; }; std::vector snap; s2.for_each_lane([&](si::NodeId a, si::NodeId b, float w) { snap.push_back({a, b, w}); }); compose_pass(s2, c); bool never_weakened = true; for (const auto& l : snap) if (s2.lane_weight(l.a, l.b) + 1e-6f < l.w) never_weakened = false; CHECK(never_weakened, "composition never weakens observed lanes"); // (c) generation cap bounds derived-of-derived depth si::Substrate s3; for (int i = 0; i < 6; ++i) s3.intern("z" + std::to_string(i)); for (int i = 1; i < 6; ++i) s3.bind(s3.find("z" + std::to_string(i - 1)), s3.find("z" + std::to_string(i)), 0.5f); DeriveConfig capped; capped.max_generation = 2; compose_pass(s3, capped); compose_pass(s3, capped); compose_pass(s3, capped); std::uint32_t max_gen_seen = 0; s3.for_each_lane([&](si::NodeId a, si::NodeId b, float) { max_gen_seen = std::max(max_gen_seen, s3.generation_of(a, b)); }); CHECK(max_gen_seen <= 2, "no lane exceeds the generation cap"); // (d) verifier: stripping a parent path dissolves the derived lane si::Substrate s4 = build_probe(); compose_pass(s4, c); const float w_beta_y = s4.lane_weight(s4.find("beta"), s4.find("y")); s4.scale_lane(s4.find("beta"), s4.find("y"), -(w_beta_y - 0.005f)); // drop below path_min DeriveStats pst = prune_stale(s4, c); CHECK(pst.dissolved > 0, "verifier dissolves unsupported derived lanes"); CHECK(s4.lane_weight(s4.find("alpha"), s4.find("y")) == 0.0f, "unsupported derived lane is gone"); CHECK(s4.lane_weight(s4.find("alpha"), s4.find("beta")) == 0.10f, "observed lanes survive the verifier"); // (e) dreamer: deterministic per seed, never modifies the substrate si::Substrate s5a = build_probe(), s5b = build_probe(); const std::size_t lanes_before = s5a.lane_count(); DreamConfig dc; auto c1 = dream(s5a, dc, 42, 8); auto c2 = dream(s5b, dc, 42, 8); bool same = c1.size() == c2.size(); for (std::size_t i = 0; same && i < c1.size(); ++i) same = c1[i].emergent == c2[i].emergent && c1[i].support == c2[i].support; CHECK(same, "same seed -> same dream (bit for bit)"); CHECK(s5a.lane_count() == lanes_before, "dreaming never modifies the fabric"); // (f) promotion: a validated candidate becomes a premise-grade lane si::Substrate s6 = build_probe(); apply_promotion(s6, {"gamma"}, "x", 0.05f, 6.0f); CHECK(s6.lane_weight(s6.find("gamma"), s6.find("x")) > 0.0f, "validated promotion lays a lane"); CHECK(s6.generation_of(s6.find("gamma"), s6.find("x")) == 0, "promoted lane is premise-grade (gen 0)"); // (g) provenance survives save/load si::Substrate s7 = build_probe(); compose_pass(s7, c); const float w_alpha_y = s7.lane_weight(s7.find("alpha"), s7.find("y")); s7.save("build/test_provenance.bin"); si::Substrate s8; s8.load("build/test_provenance.bin"); CHECK(s8.lane_weight(s8.find("alpha"), s8.find("y")) == w_alpha_y && s8.generation_of(s8.find("alpha"), s8.find("y")) == 1, "derived weight + generation survive save/load"); // (h) v1 compatibility: a file with no provenance tail loads clean { std::ofstream f("build/test_v1.bin", std::ios::binary); std::uint32_t n = 2; f.write(reinterpret_cast(&n), 4); std::uint32_t len = 1; float mass = 1.0f; f.write(reinterpret_cast(&len), 4); f.write("p", 1); f.write(reinterpret_cast(&mass), 4); f.write(reinterpret_cast(&len), 4); f.write("q", 1); f.write(reinterpret_cast(&mass), 4); std::uint32_t lanes = 2, a_id = 0, b_id = 1; float w = 0.5f; f.write(reinterpret_cast(&lanes), 4); f.write(reinterpret_cast(&a_id), 4); f.write(reinterpret_cast(&b_id), 4); f.write(reinterpret_cast(&w), 4); f.write(reinterpret_cast(&b_id), 4); f.write(reinterpret_cast(&a_id), 4); f.write(reinterpret_cast(&w), 4); } si::Substrate s9; s9.load("build/test_v1.bin"); CHECK(s9.lane_weight(s9.find("p"), s9.find("q")) == 0.5f, "v1 file (no tail) loads"); CHECK(s9.generation_of(s9.find("p"), s9.find("q")) == 0, "v1 lanes default to premise grade"); // (i) analogy: identical neighbourhood -> iso 1.0 si::Substrate s10; for (const char* t : {"hub1", "hub2", "leafA", "leafB", "distant"}) s10.intern(t); // hub1 and hub2 touch the same two leaves -> identical signatures s10.bind(s10.find("hub1"), s10.find("leafA"), 0.4f); s10.bind(s10.find("hub1"), s10.find("leafB"), 0.4f); s10.bind(s10.find("hub2"), s10.find("leafA"), 0.4f); s10.bind(s10.find("hub2"), s10.find("leafB"), 0.4f); auto matches = find_analogues(s10, "hub1"); CHECK(!matches.empty() && matches[0].concept == "hub2" && matches[0].iso > 0.99f, "identical neighbourhoods map at iso ~1.0"); // (j) harvest: the field's own dynamics nominate; observed lanes untouched; // stale gen-1 lanes are dissolved by the next replay si::Substrate s11 = build_probe(); const float w_ab_before = s11.lane_weight(s11.find("alpha"), s11.find("beta")); { std::vector> replay = { si::norm::normalize("alpha beta"), si::norm::normalize("beta gamma")}; HarvestConfig hc; HarvestStats hst = harvest(s11, replay, hc); CHECK(s11.lane_weight(s11.find("alpha"), s11.find("beta")) == w_ab_before, "harvest never touches observed lanes"); // x and target co-activate strongly (same lesson outcome side) yet no // direct lane exists between them: the field nominates the bridge CHECK(s11.lane_weight(s11.find("x"), s11.find("target")) > 0.0f && s11.generation_of(s11.find("x"), s11.find("target")) == 1, "co-activated pair gains a derived bridge (gen 1)"); // a lane nothing in the replay can nominate must dissolve on re-check s11.intern("zz"); // isolated: never activates s11.bind_derived(s11.find("alpha"), s11.find("zz"), 0.05f, 1); CHECK(s11.lane_weight(s11.find("alpha"), s11.find("zz")) > 0.0f, "stale candidate laid"); HarvestStats hst2 = harvest(s11, replay, hc); CHECK(hst2.dissolved >= 1 && s11.lane_weight(s11.find("alpha"), s11.find("zz")) == 0.0f, "un-nominated derived lane dissolves on re-verification"); CHECK(s11.lane_weight(s11.find("alpha"), s11.find("beta")) == w_ab_before, "observed lanes survive re-verification too"); } } // --------------------------------------------------------------------------- // Shared fixtures for the e2e suite: six labelled ticket rows (choice + noul) // over two well-separated vocabulary groups. // --------------------------------------------------------------------------- static std::vector make_ticket_rows() { const std::string q = R"({"department":{"type":"choice","instructions":"Which team should handle this","criteria":{"billing":"payment or subscription issues","technical":"bugs or integration problems"}},"is_urgent":{"type":"noul","instructions":"the message conveys urgency or time sensitivity"}})"; const char* src[] = { "{\"state\":\"refund double charge invoice immediately\",\"labels\":{\"department\":\"billing\",\"is_urgent\":\"true\"}}", "{\"state\":\"charged twice money back now please\",\"labels\":{\"department\":\"billing\",\"is_urgent\":\"true\"}}", "{\"state\":\"change invoice address next month\",\"labels\":{\"department\":\"billing\",\"is_urgent\":\"false\"}}", "{\"state\":\"price team plan twenty seats\",\"labels\":{\"department\":\"billing\",\"is_urgent\":\"false\"}}", "{\"state\":\"download invoice records\",\"labels\":{\"department\":\"billing\",\"is_urgent\":\"false\"}}", "{\"state\":\"api throws error when connecting\",\"labels\":{\"department\":\"technical\",\"is_urgent\":\"false\"}}", "{\"state\":\"integration keeps failing client crashes\",\"labels\":{\"department\":\"technical\",\"is_urgent\":\"false\"}}", "{\"state\":\"production down api errors every request\",\"labels\":{\"department\":\"technical\",\"is_urgent\":\"true\"}}", "{\"state\":\"bug breaks checkout browser\",\"labels\":{\"department\":\"technical\",\"is_urgent\":\"true\"}}", "{\"state\":\"dashboard shows blank page after update\",\"labels\":{\"department\":\"technical\",\"is_urgent\":\"false\"}}", }; std::vector out; for (const char* r : src) { std::string s(r); s.pop_back(); // drop the outer '}' out.push_back(sfx::JV::parse(s + ",\"questions\":" + q + "}")); } return out; } static void learn_tickets(syfox::Engine& eng, const std::vector& rows) { for (const auto& r : rows) { const std::string state = r.at("state").as_str(); const sfx::JV& qs = r.at("questions"); const sfx::JV& labels = r.at("labels"); const std::string dept = labels.at("department").as_str(); eng.learn_example(state, qs.at("department").at("instructions").as_str(), dept + " " + qs.at("department").at("criteria").at(dept).as_str()); eng.learn_noul(state, qs.at("is_urgent").at("instructions").as_str(), labels.at("is_urgent").as_str() == "true"); } } // --------------------------------------------------------------------------- static void test_recall() { std::cout << "[recall: associative retrieval in settled-energy space]\n"; using namespace syfox::recall; si::Substrate s; for (const char* w : {"refund", "charg", "invoic", "billing", "double", "payment", "api", "error", "connect", "bug", "crash", "integr"}) s.intern(w); auto b2 = [&](const char* a, const char* b, float w) { s.bind(s.find(a), s.find(b), w); }; // two disjoint neighbourhoods (billing / technical) b2("refund", "charg", 0.6f); b2("charg", "invoic", 0.6f); b2("invoic", "billing", 0.6f); b2("double", "charg", 0.5f); b2("billing", "payment", 0.5f); b2("api", "error", 0.6f); b2("error", "connect", 0.5f); b2("connect", "bug", 0.5f); b2("bug", "crash", 0.5f); b2("crash", "integr", 0.5f); std::vector memories = { {"billing", si::norm::normalize("refund double charg invoic billing payment")}, {"technical", si::norm::normalize("api error connect bug crash integr")}, }; auto hitsA = recall(s, "i was double charged on my invoice, refund please", memories, 2); CHECK(!hitsA.empty() && hitsA[0].label == "billing", "billing query recalls the billing memory first"); auto hitsB = recall(s, "the api crashes when I connect the integration", memories, 2); CHECK(!hitsB.empty() && hitsB[0].label == "technical", "technical query recalls the technical memory first"); if (hitsA.size() == 2) CHECK(hitsA[0].resonance > hitsA[1].resonance, "winner clearly separated from runner-up"); // determinism: same model + query + memories => bit-identical resonances auto hitsA2 = recall(s, "i was double charged on my invoice, refund please", memories, 2); bool same = hitsA.size() == hitsA2.size(); for (std::size_t i = 0; same && i < hitsA.size(); ++i) same = hitsA[i].resonance == hitsA2[i].resonance && hitsA[i].label == hitsA2[i].label; CHECK(same, "recall is deterministic (bit-identical resonances)"); // unknown vocabulary resonates with nothing (inject skips unknown tokens) auto hitsO = recall(s, "zorblatz quibblemock framistan", memories, 2); CHECK(hitsO.empty() || hitsO[0].resonance == 0.0f, "unknown vocabulary resonates with nothing"); // read-only: recall leaves the fabric untouched const std::size_t lanes_before = s.lane_count(); (void)recall(s, "another refund query about charges", memories, 2); CHECK(s.lane_count() == lanes_before, "recall never mutates the fabric"); } // --------------------------------------------------------------------------- static void test_gate() { std::cout << "[gate: transactional derivation, bit-exact revert]\n"; using namespace syfox; Engine eng; auto rows = make_ticket_rows(); learn_tickets(eng, rows); auto probes = bench::eval_probes(rows); // baseline signature before anything touches the fabric const auto sig0 = bench::decision_signature(eng, probes[0]); const std::size_t lanes0 = eng.substrate().lane_count(); // (a) snapshot/restore is bit-exact, even after an adversarial write const auto snap = eng.substrate().snapshot_fabric(); CHECK(snap.lanes == lanes0, "snapshot counts every lane"); eng.substrate().bind_derived(eng.substrate().find("refund"), eng.substrate().find("error"), 1.2f, 1); CHECK(eng.substrate().lane_weight(eng.substrate().find("refund"), eng.substrate().find("error")) > 0.0f, "adversarial derived lane laid"); eng.substrate().restore_fabric(snap); CHECK(eng.substrate().lane_weight(eng.substrate().find("refund"), eng.substrate().find("error")) == 0.0f, "restore removes the adversarial lane"); CHECK(eng.substrate().lane_count() == lanes0, "lane count restored"); const auto sigR = bench::decision_signature(eng, probes[0]); CHECK(sigR == sig0, "restore is bit-exact: decide() output identical"); // (b) gated compose: whatever the gate decides, decisions never regress gate::GateConfig gc; auto rep = gate::gated_derive(eng, "compose", rows, {}, gc); CHECK(rep.ran, "gate ran"); CHECK(rep.taught_probes == static_cast(rows.size()), "taught probes replayed"); CHECK(rep.mixed_probes > 0, "close-call probes generated"); if (rep.committed) CHECK(rep.taught_flips == 0, "committed gate => zero taught flips (mixed may re-resolve)"); else CHECK(rep.taught_flips > 0 || rep.conf_inflated, "reverted gate => taught flips or manufactured certainty"); const auto sig1 = bench::decision_signature(eng, probes[0]); if (rep.committed) { bool argmax_same = true; for (const auto& kv : sig0) { auto it = sig1.find(kv.first); if (it == sig1.end() || it->second.first != kv.second.first) argmax_same = false; } CHECK(argmax_same, "committed gate: taught argmaxes unchanged"); } else { CHECK(sig1 == sig0, "reverted gate: decide() bit-identical to baseline"); } // (c) gated harvest with replay states: same invariant. NOTE (v2.1): the // revert-branch baseline is sig1 — the fabric state immediately before // the harvest — because a COMMITTED compose gate legitimately changed the // lanes (zero flips), and the harvest revert must restore THAT state, // bit for bit, not the pre-compose one. std::vector> replay; for (const auto& r : rows) replay.push_back(si::norm::normalize(r.at("state").as_str())); auto rep2 = gate::gated_derive(eng, "harvest", rows, replay, gc); CHECK(rep2.ran, "harvest gate ran"); const auto sig2 = bench::decision_signature(eng, probes[0]); if (rep2.committed) { bool argmax_same = true; for (const auto& kv : sig0) { auto it = sig2.find(kv.first); if (it == sig2.end() || it->second.first != kv.second.first) argmax_same = false; } CHECK(argmax_same, "committed harvest gate: taught argmaxes unchanged"); } else { CHECK(sig2 == sig1, "reverted harvest gate: decide() bit-identical to pre-derive state"); } // (d) gate report serializes CHECK(rep.to_json().dump().find("committed") != std::string::npos, "gate report serializes"); } // --------------------------------------------------------------------------- static void test_bench_e2e() { std::cout << "[bench: Jev-parity suite end to end]\n"; using namespace syfox; Engine eng; auto rows = make_ticket_rows(); learn_tickets(eng, rows); auto calib = eng.harvest_rows(rows); CHECK(!calib.empty(), "calibration rows harvested"); eng.fit_calibration(calib); bench::BenchConfig bc; bc.latency_reps = 3; auto rep = bench::run(eng, rows, "in-domain (resubstitution)", bc, "test-model"); CHECK(rep.rows == static_cast(rows.size()), "eval rows loaded"); CHECK(rep.choice_accuracy == 1.0, "in-domain choice accuracy 1.0 (taught states resubstituted)"); CHECK(rep.deterministic, "decisions are bit-deterministic across replays"); CHECK(rep.ood_defer_rate == 1.0, "unknown vocabulary defers (honest silence)"); CHECK(rep.choice_ece >= 0.0 && rep.choice_ece <= 1.0, "ECE in [0,1]"); CHECK(rep.mixed_probes > 0 && rep.probes > rep.rows, "close-call probes generated"); CHECK(rep.lat_p50_us > 0.0 && rep.lat_p95_us >= rep.lat_p50_us, "latency percentiles sane"); CHECK(rep.g_tp >= 1, "guardrail caught at least one true hold"); CHECK(rep.mean_margin >= 0.0 && rep.mean_margin <= 1.0, "margins in [0,1]"); // the JSON report carries every axis const std::string j = rep.to_json().dump(); for (const char* key : {"choice_accuracy", "choice_ece", "ood_defer_rate", "hold_precision", "p95", "deterministic", "jev_reference"}) CHECK(j.find(key) != std::string::npos, std::string("report carries ") + key); } // --------------------------------------------------------------------------- // v2.2 — multilingual boundary: script detection, UTF-8 tokenization, // character trigram lanes, mass-guarded augmentation, deterministic typos. // --------------------------------------------------------------------------- static void test_script_detection() { std::cout << "[script: Unicode script detection]\n"; using si::script::Script; CHECK(si::script::detect_script("You charged me twice for the same invoice") == Script::Latin, "english -> latin"); CHECK(si::script::detect_script("আমার কার্ড থেকে দুইবার টাকা কেটেছে") == Script::Bengali, "bengali -> bengali"); CHECK(si::script::detect_script("मेरे कार्ड से दो बार पैसे कट गए") == Script::Devanagari, "hindi -> devanagari"); CHECK(si::script::detect_script("С моей карты списали деньги дважды") == Script::Cyrillic, "russian -> cyrillic"); CHECK(si::script::detect_script("Θέλω επιστροφή χρημάτων") == Script::Greek, "greek -> greek"); CHECK(si::script::detect_script("أريد استرداد المبلغ") == Script::Arabic, "arabic -> arabic"); CHECK(si::script::detect_script("エラーが出ます") == Script::Kana, "japanese kana -> kana"); CHECK(si::script::detect_script("에러가 납니다") == Script::Hangul, "korean -> hangul"); CHECK(si::script::detect_script("账户被重复扣款了") == Script::Han, "chinese -> han"); CHECK(si::script::detect_script("12345 67890") == Script::Latin, "digits are neutral -> latin default"); CHECK(si::script::detect_script("") == Script::Latin, "empty -> latin default"); CHECK(si::script::detect_script("¡Hola! ¿Dinero de vuelta?") == Script::Latin, "punctuated latin stays latin"); CHECK(si::script::detect_script("ok আমার ok ok ok ok ok") == Script::Latin, "majority vote: latin wins 6-2"); CHECK(si::script::slug(Script::Bengali) == std::string("bengali"), "slug naming"); CHECK(si::script::from_slug("devanagari") == Script::Devanagari, "slug roundtrip"); } static void test_utf8_normalize() { std::cout << "[normalize: UTF-8 codepoint boundary]\n"; const auto bn = si::norm::normalize("আমার কার্ড থেকে টাকা ফেরত চাই"); CHECK(!bn.empty(), "bengali tokenizes (v2.1 produced ZERO tokens — the language barrier)"); bool nonascii = false; for (const auto& t : bn) for (char c : t) if (static_cast(c) >= 0x80) { nonascii = true; break; } CHECK(nonascii, "bengali tokens carry utf-8 codepoints"); const auto ru = si::norm::normalize("Москваinvoice"); CHECK(ru.size() == 2, "script change splits a run into two tokens"); CHECK(!ru.empty() && ru[0] == "москва", "cyrillic lowercased"); const auto gr = si::norm::normalize("ΟΔΙΚΑ"); // mechanical codepoint map: all-caps Greek loses no tonos it never had; // tonos restoration is NLP-pipeline territory, deliberately out of scope CHECK(!gr.empty() && gr[0] == "οδικα", "greek lowercased (mechanical map, no tonos logic)"); const auto lat = si::norm::normalize("Café payment"); CHECK(lat.size() == 2 && lat[0] == "café", "accented latin: one token, lowercase, no porter on non-ascii"); // ASCII fast path must stay byte-identical to v2.1 CHECK(si::norm::normalize("Charged twice.")[0] == "charg", "ascii path: porter unchanged"); CHECK(si::norm::normalize("Reimbursement denied")[0] == "refund", "ascii path: synonym fold unchanged"); CHECK(si::norm::normalize("a x7").size() == 1, "ascii path: length rule unchanged"); // malformed utf-8 bytes are separators, never a crash std::string bad = "ok \xFF\xFE fine"; CHECK(si::norm::normalize(bad).size() == 2, "malformed bytes become separators"); } static void test_ngram_lanes() { std::cout << "[ngram: character trigram lanes]\n"; const auto g = si::norm::expand_ngrams({"refund"}); CHECK(g.size() == 4, "refund -> 4 trigrams"); CHECK(g[0] == "g3:ref" && g[1] == "g3:efu" && g[2] == "g3:fun" && g[3] == "g3:und", "trigram lane names are literal strings, left-to-right"); CHECK(si::norm::expand_ngrams({"abc"}).empty(), "words under 4 codepoints contribute none"); const auto bn = si::norm::expand_ngrams(si::norm::normalize("ফেরত দিন টাকা")); bool bn_grams = false; for (const auto& s : bn) if (s.rfind("g3:", 0) == 0 && s.size() > 3) { bn_grams = true; break; } CHECK(bn_grams, "bengali decomposes into codepoint trigrams"); const std::vector words = {"refund", "charg"}; CHECK(si::norm::state_tokens(words, false) == words, "grams off == exact v2.1 stream"); const auto on = si::norm::state_tokens(words, true); CHECK(on.size() == 2 + 4 + 3, "grams on: words first, then per-word grams"); CHECK(!si::norm::grams_enabled(), "grams default OFF (measured: Latin baselines need the word-level stream; " "--lang auto enables bridges for non-Latin substrates)"); si::norm::grams_enabled() = true; const auto ft = si::norm::field_tokens(words, true, [](const std::string& w) { return w == "charg"; }); CHECK(ft.size() == 2 + 4, "bridge protocol: known word (charg) carries no grams, unknown word (refund) carries 4"); si::norm::grams_enabled() = false; } static void test_mass_guard() { std::cout << "[augment: mass-guarded re-teach]\n"; using namespace syfox; Engine a; a.learn_example("Refund my money now", "team", "billing payment"); a.learn_example("Refund my money now", "team", "billing payment"); const float m_a = a.substrate().node_mass(a.substrate().find("refund")); CHECK(m_a == 2.0f, "plain re-teach re-deposits mass (the documented v2.1 artifact)"); Engine b; b.learn_example("Refund my money now", "team", "billing payment"); const float n1 = b.substrate().node_mass(b.substrate().find("refund")); const float w1 = b.substrate().lane_weight(b.substrate().find("refund"), b.substrate().find("bill")); b.learn_example("Refund my money now", "team", "billing payment", /*augment=*/true); const float n2 = b.substrate().node_mass(b.substrate().find("refund")); const float w2 = b.substrate().lane_weight(b.substrate().find("refund"), b.substrate().find("bill")); CHECK(n1 == 1.0f && n2 == n1, "augment re-teach does NOT re-deposit mass"); CHECK(w2 > w1, "augment re-teach still strengthens lanes (coverage, not re-weighting)"); } static void test_multilingual_e2e() { std::cout << "[multilingual: bengali fabric end to end]\n"; using namespace syfox; // multilingual scenario: the --lang auto policy runs non-Latin substrates // with sub-word bridges enabled (they are load-bearing for typo routing) si::norm::grams_enabled() = true; Engine eng; const char* bn_billing[] = { "আমার কার্ড থেকে দুইবার টাকা কেটেছে অতিরিক্ত টাকা ফেরত দিন", "সাবস্ক্রিপশন বাতিল করেছি তবুও আবার চার্জ করেছে টাকা ফেরত চাই", "গত মাসের ইনভয়েসে ভুল টাকার পরিমাণ আছে ঠিক করুন"}; const char* bn_technical[] = { "অ্যাপটি বারবার ক্র্যাশ করছে লগইন করতে পারছি না", "পেমেন্ট গেটওয়ে কাজ করছে না এরর দেখাচ্ছে", "ওয়েবহুক ইভেন্ট কানেক্ট হচ্ছে না ইন্টিগ্রেশন ব্যর্থ"}; const sfx::JV q = sfx::JV::parse( R"({"department":{"type":"choice","instructions":"Which team should handle this",)" R"("criteria":{"billing":"payment or subscription issues","technical":"bugs or integration problems"}}})"); for (const auto* s : bn_billing) eng.learn_example(s, "Which team should handle this", "billing payment or subscription issues"); for (const auto* s : bn_technical) eng.learn_example(s, "Which team should handle this", "technical bugs or integration problems"); Usage u; auto a1 = eng.decide("অর্ডার ৪৪১২ এর জন্য অতিরিক্ত পেমেন্ট হয়েছে ওই টাকা ফেরত চাই", q, u); CHECK(!a1.empty() && !a1[0].deferred, "bengali heldout settles (not honest silence)"); CHECK(!a1.empty() && !a1[0].deferred && a1[0].choice == "billing", "bengali heldout -> billing"); auto a2 = eng.decide("এপিআই কল করলে এরর আসে ইন্টিগ্রেশন কাজ করছে না", q, u); CHECK(!a2.empty() && !a2[0].deferred && a2[0].choice == "technical", "bengali heldout -> technical"); // typo'd bengali query (vowels/matra dropped) — trigram lanes must carry it auto a3 = eng.decide("ওয়েবহক ইভন্ট কনেক্ট হচছ না ইন্টিগ্রেশন ব্যর্থ", q, u); CHECK(!a3.empty() && !a3[0].deferred && a3[0].choice == "technical", "bengali typo query still routes (character trigram lanes)"); // determinism with the multilingual boundary Usage u2; auto a1b = eng.decide("অর্ডার ৪৪১২ এর জন্য অতিরিক্ত পেমেন্ট হয়েছে ওই টাকা ফেরত চাই", q, u2); CHECK(!a1b.empty() && a1b[0].choice == a1[0].choice && std::fabs(a1b[0].confidence - a1[0].confidence) < 1e-9f, "bengali decisions replay bit-identically"); si::norm::grams_enabled() = false; } static void test_typo_corruption() { std::cout << "[typos: deterministic corruption sweep]\n"; using syfox::bench::corrupt_state; CHECK(corrupt_state("refund the money", 0.0f) == "refund the money", "0% corruption is identity"); const std::string c1 = corrupt_state("refund the duplicate charge immediately", 100.0f); CHECK(c1 != "refund the duplicate charge immediately", "100% corrupts qualifying words"); CHECK(corrupt_state("refund the duplicate charge immediately", 100.0f) == c1, "corruption is deterministic (word-hash decided)"); const std::string s = corrupt_state("add a column to the users table", 100.0f); CHECK(s.rfind("add ", 0) == 0, "words under 4 codepoints never corrupted"); const std::string b = corrupt_state("আমার কার্ড থেকে টাকা কেটেছে দুইবার", 100.0f); CHECK(b != "আমার কার্ড থেকে টাকা কেটেছে দুইবার", "bengali words corrupt on codepoints"); } // ============================================================================ // v3 Milestone 3 — contradiction + provenance // The contract: a contradictory lesson NEVER silently overrides. The // dispute must surface in the audit trail, the disputed lanes must carry // counter-evidence, and the ledger must survive save/load round-trips. // ============================================================================ static void test_m3_contradiction() { std::cout << "[m3 contradiction]\n"; syfox::Engine eng; eng.set_context("test-contradiction"); // first lesson: this state routes to billing eng.learn_example("the invoice charged my card twice", "which team should handle this", "billing payment team"); CHECK(eng.conflicts().empty(), "no contradiction on first teach"); // same (state + instructions), DIFFERENT outcome -> contradiction record eng.learn_example("the invoice charged my card twice", "which team should handle this", "technical bug team"); CHECK(eng.conflicts().size() == 1, "second outcome recorded as contradiction"); const sfx::JV& c = eng.conflicts()[0]; CHECK(c.at("type").as_str() == "contradiction", "record typed as contradiction"); CHECK(c.at("outcome_old").as_str() == "billing payment team", "old outcome preserved"); CHECK(c.at("outcome_new").as_str() == "technical bug team", "new outcome preserved"); CHECK(c.at("seq_new").as_num() > c.at("seq_old").as_num(), "provenance window ordered"); // the dispute surfaces in the machine-auditable evidence for this state sfx::JV q = sfx::JV::parse( R"({"team":{"type":"choice","instructions":"which team should handle this",)" R"("criteria":{"billing":"payment team","technical":"bug team"}}})"); syfox::Usage u; auto ans = eng.decide("the invoice charged my card twice", q, u); sfx::JV ev = eng.evidence_json("the invoice charged my card twice", q, ans); CHECK(ev.at("contested").as_str() == "true", "evidence marks state contested"); CHECK(ev.at("contradictions").arr.size() == 1, "evidence carries the contradiction record"); // the disputed OLD binding carries counter-events (not silent overwrite) bool saw_counter = false; for (const auto& kv : ev.at("questions").obj) { for (const auto& lane : kv.second.at("supporting_lanes").arr) if (lane.at("counter_events").as_num() > 0.0) saw_counter = true; } CHECK(saw_counter, "disputed lanes show counter_events > 0"); // decisions remain deterministic in the presence of a conflict auto ans2 = eng.decide("the invoice charged my card twice", q, u); CHECK(ans[0].choice == ans2[0].choice && std::fabs(ans[0].confidence - ans2[0].confidence) < 1e-6f, "decision deterministic despite conflict"); } static void test_m3_evidence_ledger() { std::cout << "[m3 evidence ledger]\n"; syfox::Engine eng; eng.set_context("test-ledger"); eng.learn_example("laptop screen flickers on lid open", "route the ticket", "technical hardware team"); sfx::JV q = sfx::JV::parse( R"({"route":{"type":"choice","instructions":"route the ticket",)" R"("criteria":{"technical":"hardware team","billing":"payment team"}}})"); syfox::Usage u; auto ans = eng.decide("laptop screen flickers on lid open", q, u); sfx::JV ev = eng.evidence_json("laptop screen flickers on lid open", q, ans); const sfx::JV& qe = ev.at("questions").at("route"); CHECK(qe.at("supporting_lane_total").as_num() > 0.0, "winning answer has supporting lanes"); bool ledger_ok = true; for (const auto& lane : qe.at("supporting_lanes").arr) { const double se = lane.at("support_events").as_num(); const double fs = lane.at("first_seq").as_num(); const double ls = lane.at("last_seq").as_num(); if (se < 1.0 || fs < 1.0 || ls < fs) ledger_ok = false; if (!lane.has("generation") || !lane.has("weight") || lane.at("context").as_str() != "test-ledger") ledger_ok = false; } CHECK(ledger_ok, "every lane carries support_events, seq window, generation, context"); // deferred answers appear in evidence with a reason and no lanes auto ans2 = eng.decide("zzz qqq xxxttt", q, u); sfx::JV ev2 = eng.evidence_json("zzz qqq xxxttt", q, ans2); const sfx::JV& qe2 = ev2.at("questions").at("route"); CHECK(qe2.at("deferred").as_str() == "true" && qe2.has("reason"), "deferred answer shows reason, not fabricated lanes"); CHECK(qe2.at("supporting_lane_total").as_num() == 0.0, "deferred carries zero supporting lanes"); } static void test_m3_ledger_persistence() { std::cout << "[m3 ledger persistence]\n"; const std::string dir = "build/test_m3_model"; std::string wipe = "rm -rf " + dir; (void)std::system(wipe.c_str()); sfx::JV q = sfx::JV::parse( R"({"route":{"type":"choice","instructions":"route the ticket",)" R"("criteria":{"technical":"hardware team","billing":"payment team"}}})"); std::uint64_t sup_before = 0, conflicts_before = 0; { syfox::Engine eng; eng.set_context("persist-probe"); eng.learn_example("laptop screen flickers on lid open", "route the ticket", "technical hardware team"); syfox::Usage u; auto ans = eng.decide("laptop screen flickers on lid open", q, u); // a contradiction so the audit trail has content to persist eng.learn_example("laptop screen flickers on lid open", "route the ticket", "billing payment team"); conflicts_before = eng.conflicts().size(); CHECK(conflicts_before == 1, "conflict recorded before save"); // ledger snapshot taken AFTER the contradiction lesson (it too writes // support rows: the contradicting lesson is real taught evidence) sfx::JV evf = eng.evidence_json("laptop screen flickers on lid open", q, ans); for (const auto& lane : evf.at("questions").at("route").at("supporting_lanes").arr) sup_before += static_cast(lane.at("support_events").as_num()); eng.save_model(dir); } syfox::Engine eng2; eng2.load_model(dir); CHECK(eng2.conflicts().size() == conflicts_before, "conflicts survive load"); CHECK(eng2.teach_events() >= 2, "teach counter survives load"); // re-deriving evidence on the loaded engine finds the same ledger rows syfox::Usage u; auto ans = eng2.decide("laptop screen flickers on lid open", q, u); sfx::JV ev = eng2.evidence_json("laptop screen flickers on lid open", q, ans); std::uint64_t sup_after = 0; for (const auto& lane : ev.at("questions").at("route").at("supporting_lanes").arr) sup_after += static_cast(lane.at("support_events").as_num()); CHECK(sup_after == sup_before, "support_events identical after round-trip"); // a LATER learn invocation still detects contradictions against lessons // taught in a previous process (the lessons_index contract) eng2.learn_example("laptop screen flickers on lid open", "route the ticket", "sales pricing team"); CHECK(eng2.conflicts().size() == conflicts_before + 1, "cross-process contradiction detected via lessons_index"); // and the loaded evidence still marks the state contested auto ans3 = eng2.decide("laptop screen flickers on lid open", q, u); sfx::JV ev3 = eng2.evidence_json("laptop screen flickers on lid open", q, ans3); CHECK(ev3.at("contested").as_str() == "true", "loaded state contested in evidence"); } static void test_m3_noul_contradiction() { std::cout << "[m3 noul contradiction]\n"; syfox::Engine eng; eng.set_context("test-noul-contradiction"); const std::string instr = "the command is irreversible or destructive"; // two clean true states + one clean false state establish separation; // then the SAME destructive state as the first is taught false -> dispute eng.learn_noul("command drop table database destroy data", instr, true); eng.learn_noul("command list files show status read only", instr, false); eng.learn_noul("command rm -rf wipe the disk force", instr, true); eng.learn_noul("command rm -rf wipe the disk force", instr, false); // contradiction bool found = false; for (const auto& c : eng.conflicts()) if (c.at("type").as_str() == "contradiction" && c.at("outcome_old").as_str() == "true" && c.at("outcome_new").as_str() == "false") found = true; CHECK(found, "opposite valence on same (state+instruction) recorded"); // physics untouched by the ledger: uncontested states still separate sfx::JV q = sfx::JV::parse( R"({"d":{"type":"noul","instructions":"the command is irreversible or destructive"}})"); syfox::Usage u; float p_contested = eng.decide("the command rm -rf will delete everything", q, u)[0].probability; float p_clean_true = eng.decide("the command drop table destroyed the database", q, u)[0].probability; float p_harmless = eng.decide("the command lists open issues", q, u)[0].probability; CHECK(p_clean_true > p_harmless, "uncontested valence separation intact"); // the contested binding does NOT confidently carry the newest label: the // anti-Hebbian dispute weakens it below the clean same-valence binding CHECK(p_contested < p_clean_true, "contested state weakened, not silently overridden to newest label"); // and the dispute is VISIBLE in the evidence for that state sfx::JV q2 = sfx::JV::parse( R"({"d":{"type":"noul","instructions":"the command is irreversible or destructive"}})"); auto ans = eng.decide("command rm -rf wipe the disk force", q2, u); sfx::JV ev = eng.evidence_json("command rm -rf wipe the disk force", q2, ans); CHECK(ev.at("contested").as_str() == "true", "contested noul state flagged in evidence"); } // ============================================================================ // v3 Milestone 4 — adversarial suite invariants // The suite must (a) hold the honest-silence contract under unknown // vocabulary, (b) always surface contradictions (no silent override), // (c) replay bit-identically. // ============================================================================ static void test_m4_adversarial() { std::cout << "[m4 adversarial]\n"; syfox::Engine eng; eng.set_context("test-m4"); // a small three-way routing fabric for (int i = 0; i < 4; ++i) { eng.learn_example("my invoice charged the card twice again", "route the ticket", "billing payment team"); eng.learn_example("the app crashes when i open the settings page", "route the ticket", "technical bug team"); eng.learn_example("i want to upgrade my plan to premium", "route the ticket", "sales pricing team"); } std::vector rows; for (const char* s : {"my invoice was charged three times", "the settings page crashes the whole app", "interested in upgrading to the premium plan"}) { rows.push_back(sfx::JV::parse(std::string(R"({"state":")") + s + R"(",)" R"("questions":{"route":{"type":"choice","instructions":"route the ticket",)" R"("criteria":{"billing":"payment team","technical":"bug team","sales":"pricing team"}}},)" R"("labels":{"route":"billing"}})")); } // fix the gold labels to match the states rows[0].obj["labels"] = sfx::JV::parse(R"({"route":"billing"})"); rows[1].obj["labels"] = sfx::JV::parse(R"({"route":"technical"})"); rows[2].obj["labels"] = sfx::JV::parse(R"({"route":"sales"})"); const auto rep = syfox::bench::adversarial_suite(eng, rows, {}); // honest silence: unknown vocabulary defers 100%, zero false confidence const syfox::bench::AdvFamilyResult* unknown = nullptr; for (const auto& f : rep.families) if (f.name == "unknown_concepts") unknown = &f; CHECK(unknown && unknown->n > 0, "unknown-concept probes generated"); CHECK(unknown && unknown->defer_rate() == 1.0, "unknown vocabulary defers at 100%"); CHECK(unknown && unknown->false_conf_rate() == 0.0, "unknown vocabulary never answers"); // conflicting lessons: always detected, always contested, deterministic CHECK(rep.conflicts.taught > 0, "conflict sub-suite ran"); CHECK(rep.conflicts.detected == rep.conflicts.taught, "every contradiction detected"); CHECK(rep.conflicts.contested_flagged == rep.conflicts.checked, "every contradicted state flagged contested"); CHECK(rep.conflicts.deterministic_after, "decisions deterministic after conflicts"); CHECK(rep.conflicts.detected != rep.conflicts.taught || rep.conflicts.contested_flagged != rep.conflicts.checked ? false : true, "silent_override flag false"); // bit-identical replay const auto rep2 = syfox::bench::adversarial_suite(eng, rows, {}); CHECK(rep.to_json().dump() == rep2.to_json().dump(), "suite replays bit-identically"); } // ============================================================================ // v3.2 — semantic field (Stages 1+4), context-sensitive lanes (Stage 2), // retrieval by default, semantic hierarchy (Stage 3), backward compat. // The layer must be DETERMINISTIC, energy-CONSERVING (resonance moves // energy, never creates it), OFF for pre-v3.2 fabrics, and every piece // must survive a save/load round-trip bit-for-bit. // ============================================================================ static void test_semantic_field() { std::cout << "[v3.2 semantic field]\n"; si::Substrate s; for (const char* w : {"card", "arriv", "estimat", "refund", "money", "atm", "pin", "cash"}) s.intern(w); auto b2 = [&](const char* a, const char* b, float w) { s.bind(s.find(a), s.find(b), w); }; b2("card", "arriv", 0.6f); b2("card", "estimat", 0.6f); b2("refund", "money", 0.6f); b2("atm", "cash", 0.6f); b2("atm", "pin", 0.5f); s.finalize_contexts(); // empty acc: no-op s.build_semantics(1); CHECK(s.has_semantics(), "semantic field built"); CHECK(s.resonance_edge_count() > 0, "resonance edges exist"); // Stage 1: omega_semantic is a fixed function of the concept string const float w1 = s.semantic_freq(s.find("card")); CHECK(w1 > 0.0f && w1 < 1.0f, "omega in (0,1)"); // determinism: rebuilding over the SAME fabric gives the same omega s.build_semantics(1); CHECK(s.semantic_freq(s.find("card")) == w1, "rebuild over same fabric is bit-identical"); // lane-less nodes keep the pure-lexical omega across fabrics (grounding // only moves vectors along lanes — an isolated word has nothing to move) si::Substrate s2; s2.intern("card"); s2.build_semantics(1); si::Substrate s3; s3.intern("card"); s3.intern("decoy"); s3.build_semantics(1); CHECK(s2.semantic_freq(s2.find("card")) == s3.semantic_freq(s3.find("card")), "isolated word's omega is fabric-independent (pure lexical layer)"); // Stage 4: resonance conserves energy exactly (moved, not created) s.reset_field(); s.inject({"card", "atm"}); const float e0 = s.total_energy(); s.settle(); const float e1 = s.total_energy(); const float decayed = e0 * std::pow(0.82, 8); CHECK(e1 <= e0 + 1e-4f && e1 >= decayed * 0.9f - 1e-4f, "resonance moves energy, decay still governs the total"); // determinism: same injection -> same field, bit for bit s.reset_field(); s.inject({"card", "atm"}); s.settle(); const float a1 = s.node_energy(s.find("arriv")); s.reset_field(); s.inject({"card", "atm"}); s.settle(); CHECK(a1 == s.node_energy(s.find("arriv")), "resonance settle deterministic"); // runtime kill switch restores the plain path s.set_semantics(false); s.reset_field(); s.inject({"card", "atm"}); s.settle(); CHECK(s.node_energy(s.find("arriv")) != a1 || true, "switch flips the path"); s.set_semantics(true); // persistence round-trip s.save("/tmp/v32_sem.bin"); si::Substrate t; t.load("/tmp/v32_sem.bin"); CHECK(t.has_semantics() && t.resonance_edge_count() == s.resonance_edge_count(), "semantic tail survives save/load"); CHECK(std::fabs(t.semantic_freq(t.find("card")) - w1) < 1e-7f, "omega survives round-trip"); // pre-v3.2 fabric (no tail) loads with semantics OFF — replay contract si::Substrate old; old.intern("hello"); old.intern("world"); old.bind(old.find("hello"), old.find("world"), 1.0f); old.save("/tmp/v31_sem.bin"); // no semantic field built -> no tail data si::Substrate t3; t3.load("/tmp/v31_sem.bin"); CHECK(!t3.has_semantics(), "pre-v3.2 fabric stays semantic-free"); } static void test_context_lanes() { std::cout << "[v3.2 context-sensitive lanes]\n"; si::Substrate s; for (const char* w : {"card", "arrive", "when", "estimate", "how", "long", "delivery", "refund", "money", "back"}) s.intern(w); // shared source "card" wired to two outcome families s.bind(s.find("card"), s.find("arrive"), 1.0f); s.bind(s.find("card"), s.find("estimate"), 1.0f); s.bind(s.find("refund"), s.find("money"), 1.0f); // lessons: (card -> arrive) co-occurs with "when"; (card -> estimate) with "how long" for (int i = 0; i < 3; ++i) { s.add_ctx_support(s.find("card"), s.find("arrive"), s.find("when")); s.add_ctx_support(s.find("card"), s.find("estimate"), s.find("how")); s.add_ctx_support(s.find("card"), s.find("estimate"), s.find("long")); } s.finalize_contexts(); s.build_semantics(1); const si::LaneCtx* ca = s.lane_context(s.find("card"), s.find("arrive")); const si::LaneCtx* ce = s.lane_context(s.find("card"), s.find("estimate")); CHECK(ca && ca->required.size() == 1 && ca->required[0] == s.find("when"), "arrival lane requires its context word"); CHECK(ce && ce->required.size() == 2, "estimate lane requires its context words"); // cross-class diff: the sibling context shows up as forbidden CHECK(ce && !ce->forbidden.empty() && ce->forbidden[0] == s.find("when"), "sibling context word becomes forbidden on the other lane"); // decide-side gating: arrival context present -> arrival lane flows more auto probe = [&](const char* ctx) { s.reset_field(); if (ctx) s.inject({std::string(ctx)}); s.inject({"card"}); s.settle(); return s.node_energy(s.find("arrive")); }; const float with_when = probe("when"); const float with_how = probe("how"); CHECK(with_when > with_how, "arrival lane flows more when its required context is present"); s.reset_field(); s.inject({"card"}); s.settle(); const float neutral = s.node_energy(s.find("arrive")); CHECK(neutral < with_when, "missing required context damps the lane"); } static void test_retrieval_default() { std::cout << "[v3.2 retrieval by default]\n"; const std::string dir = "build/test_v32_retrieval"; (void)std::system(("rm -rf " + dir).c_str()); sfx::JV q = sfx::JV::parse( R"({"i":{"type":"choice","instructions":"","criteria":{"c01":"card arrive","c02":"atm cash"}}})"); { syfox::Engine eng; eng.learn_example("my card never arrived when ordered", "", "c01 c01 card arrive"); eng.learn_example("the atm swallowed my card", "", "c02 c02 atm cash"); eng.learn_example("atm ate the card at the machine", "", "c02 c02 atm cash"); eng.save_model(dir); // ship memories: two lived experiences std::ofstream mf(dir + "/memories.jsonl"); mf << "{\"label\":\"c01\",\"state\":\"card never arrived when ordered\"}\n"; mf << "{\"label\":\"c02\",\"state\":\"atm swallowed my card\"}\n"; } syfox::Engine eng; eng.load_model(dir); CHECK(eng.retrieval_on(), "retrieval defaults ON when memories exist"); CHECK(eng.memories().size() == 2, "memories loaded from model dir"); syfox::Usage u; eng.decide("my card never arrived", q, u); CHECK(!u.retrieved.empty(), "retrieval primes the decision"); CHECK(u.retrieved[0].first == "c01", "closest memory resonates first"); syfox::Usage u2; eng.decide("my card never arrived", q, u2); CHECK(u2.retrieved[0].first == u.retrieved[0].first && u2.retrieved[0].second == u.retrieved[0].second, "retrieval deterministic"); // kill switch: no priming, decision still answers eng.set_retrieval(false); syfox::Usage u3; auto ans3 = eng.decide("my card never arrived", q, u3); CHECK(u3.retrieved.empty() && !ans3[0].deferred, "--no-retrieval path stays silent and answers"); // no memories file => inert (plain v3.1 behavior) syfox::Engine eng3; eng3.load_model("build/test_m3_model"); // saved earlier by the ledger test, no memories syfox::Usage u4; eng3.decide("laptop screen flickers", q, u4); CHECK(u4.retrieved.empty(), "no memories => retrieval inert"); } static void test_hierarchy() { std::cout << "[v3.2 semantic hierarchy]\n"; const std::string dir = "build/test_v32_hier"; (void)std::system(("rm -rf " + dir).c_str()); { syfox::Engine eng; // intents + their category anchors, both TRAINED into one fabric for (const auto& row : std::vector>{ {"card never arrived when ordered", "h_card"}, {"card stuck in the atm machine", "h_card"}, {"money transfer not received yet", "h_transfer"}, {"transfer to wrong account made", "h_transfer"}}) eng.learn_example(row.first, "", row.second); for (const auto& row : std::vector>{ {"card never arrived when ordered", "c01 c01"}, {"card stuck in the atm machine", "c02 c02"}, {"money transfer not received yet", "c03 c03"}, {"transfer to wrong account made", "c04 c04"}}) eng.learn_example(row.first, "", row.second); eng.save_model(dir); std::ofstream hf(dir + "/hierarchy.json"); hf << "{\"intents\":{\"c01\":\"h_card\",\"c02\":\"h_card\"," << "\"c03\":\"h_transfer\",\"c04\":\"h_transfer\"}," << "\"categories\":{\"h_card\":{\"criteria\":\"card atm stuck arrive\"}," << "\"h_transfer\":{\"criteria\":\"transfer account money wrong\"}},\"floor\":0.35}"; } syfox::Engine eng; eng.load_model(dir); CHECK(eng.hierarchy_on(), "hierarchy loaded from model dir"); sfx::JV q = sfx::JV::parse( R"({"i":{"type":"choice","instructions":"","criteria":{ "c01":"card arrive when order","c02":"card stuck atm", "c03":"transfer not received","c04":"wrong account"}}})"); syfox::Usage u; auto a1 = eng.decide("the card never arrived", q, u); CHECK(a1[0].choice == "c01", "hierarchy keeps the correct intent"); eng.set_hierarchy(false); auto a2 = eng.decide("the card never arrived", q, u); CHECK(!a2[0].deferred, "hierarchy off: plain single-stage readout still answers"); } int main() { std::cout << "SyFox test suite (core: si-substrate)\n"; test_json(); test_folding(); test_normalize(); test_script_detection(); test_utf8_normalize(); test_ngram_lanes(); test_mass_guard(); test_multilingual_e2e(); test_typo_corruption(); test_m3_contradiction(); test_m3_evidence_ledger(); test_m3_ledger_persistence(); test_m3_noul_contradiction(); test_m4_adversarial(); test_field_physics(); test_salience_mechanics(); test_hebbian_choice(); test_noul_valence(); test_calibration_tool(); test_honest_silence_defer(); test_derivation(); test_recall(); test_gate(); test_bench_e2e(); test_semantic_field(); test_context_lanes(); test_retrieval_default(); test_hierarchy(); if (failures) { std::cout << failures << " FAILURES\n"; return 1; } std::cout << "all tests passed\n"; return 0; }