Skip to content
Merged
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
13,651 changes: 7,266 additions & 6,385 deletions cockpit/public/aiwar_graph.json

Large diffs are not rendered by default.

52 changes: 46 additions & 6 deletions crates/cockpit-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,57 @@ use include_dir::{include_dir, Dir};
#[cfg(feature = "embed-cockpit")]
static COCKPIT_DIST: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../../cockpit/dist");

/// Pre-baked enriched OSINT SoA wire buffer (`osint_gotham::osint_soa_bytes`,
/// baked offline via the `bake_osint_soa` test). Served at `/osint.soa` and
/// decoded to a 3D scene client-side — no cypher at runtime, no JSON.
static OSINT_SOA: &[u8] =
/// Committed fallback OSINT SoA (used only if the harvest graph isn't on disk).
static OSINT_SOA_FALLBACK: &[u8] =
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/osint_scene.soa"));

/// Serve the pre-baked OSINT SoA as raw bytes (octet-stream) for `/osint3d`.
/// OSINT SoA wire buffer, **baked at startup from the on-disk enriched harvest**
/// (`osint_gotham::osint_soa_bytes`) so the served 3D scene reflects the CURRENT
/// `aiwar_graph.json` — the full 11-dim V3 stacked cascade (militaryUse:civicUse,
/// MLTask:MLType, purpose:capacity, output:impact, currentStatus:type,
/// stakeholder:airo:type), not a stale pre-bake. Falls back to the committed
/// asset when the harvest is absent (e.g. a minimal image). One-time init.
static OSINT_SOA: std::sync::LazyLock<Vec<u8>> = std::sync::LazyLock::new(|| {
let candidates = [
// Anchored on the crate dir (baked at build) so it resolves regardless
// of the runtime cwd Railway launches from.
concat!(env!("CARGO_MANIFEST_DIR"), "/../../cockpit/public/aiwar_graph.json"),
"cockpit/public/aiwar_graph.json",
Comment on lines +68 to +69

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prefer the enriched bake over the base public graph

In a source checkout like this one, cockpit/public/aiwar_graph.json exists, has only the 221 base nodes, and there is no cockpit/cypher; because these public-graph candidates are tried before the enriched harvest paths or the committed 920-node fallback asset, /osint.soa now runtime-bakes the base graph and silently drops the enriched scene the route used to serve. This affects any deployment that ships the repo/public files alongside the binary.

Useful? React with 👍 / 👎.

"/home/user/aiwar-neo4j-harvest/data/aiwar_graph.json",
"../aiwar-neo4j-harvest/data/aiwar_graph.json",
];
let Some(path) = candidates.iter().find(|p| std::path::Path::new(p).exists()) else {
tracing::warn!("osint: no aiwar_graph.json on disk — serving committed fallback asset");
return OSINT_SOA_FALLBACK.to_vec();
};
let cypher = std::path::Path::new(path)
.parent()
.and_then(|p| p.parent())
.map(|r| r.join("cypher"));
let graph = match &cypher {
Some(d) if d.is_dir() => aiwar_ingest::load_with_enrichment(path, d),
_ => aiwar_ingest::load_from_file(path),
};
let Ok(graph) = graph else {
tracing::warn!("osint: harvest at {path} failed to load — serving fallback asset");
return OSINT_SOA_FALLBACK.to_vec();
};
let rounds = cypher
.as_ref()
.and_then(|d| aiwar_ingest::encounter_round::load_encounter_rounds(d).ok())
.unwrap_or_default();
tracing::info!(
"osint: baked {} nodes from {path} (11-dim V3 stacked cascade)",
graph.nodes.len()
);
osint_gotham::osint_soa_bytes(&graph, &rounds)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve tenant bytes in the runtime SoA bake

When a runtime graph candidate is found, this returns osint_soa_bytes, but that serializer stops after the label tail and does not append the nodeCount * 6 tenant bytes that the existing assets/osint_scene.soa contains. In that path cockpit/src/OsintGraph.tsx decodes tenants as null, so the facet-lens buttons are shown but cannot recolor or produce legends; append the tenant tail here or keep serving a bake that includes it.

Useful? React with 👍 / 👎.

});

/// Serve the baked OSINT SoA as raw bytes (octet-stream) for `/osint3d`.
async fn osint_soa_handler() -> impl axum::response::IntoResponse {
(
[(axum::http::header::CONTENT_TYPE, "application/octet-stream")],
OSINT_SOA,
OSINT_SOA.as_slice(),
)
}

Expand Down
140 changes: 140 additions & 0 deletions crates/cockpit-server/src/osint_gotham.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ const FACET_AIRO_ROLE: usize = 3; // airo:type — u8 bitset (compound)
const FACET_MLTYPE: usize = 4; // MLTask/MLTasks primary token, u8 code
const FACET_PURPOSE: usize = 5; // purpose / purpose:vair — u8 code
const FACET_CAPACITY: usize = 6; // capacity / capacity:airo — u8 code
// V3 6×(8:8) completion (bytes 7..=11) — the six AIRO/VAIR dimensions the
// original 1..=6 tenant left unstacked. Additive: byte offsets 1..=6 are
// unchanged (client + committed asset stay compatible); these complete the
// stacked cascade so every used property is a hot, groupable value tenant —
// HEEL currentStatus:type · family output:impact · identity stakeholder:airo.
const FACET_STATUS: usize = 7; // currentStatus — lifecycle stage, u8 code
const FACET_TYPE: usize = 8; // type — system/actor class, u8 code
const FACET_OUTPUT: usize = 9; // output:airo — u8 code
const FACET_IMPACT: usize = 10; // impact:vair — primary harm token, u8 code
const FACET_STAKEHOLDER: usize = 11; // stakeholder — owning actor class, u8 code

/// `airo:type` actor roles in bit order. The four canonical AIRO players plus
/// the two rare variants the harvest carries; the game-theory structure is
Expand Down Expand Up @@ -275,6 +285,107 @@ const CAPACITY_AIRO: &[&str] = &[
"SignalTracking",
];

/// `currentStatus:airo` lifecycle stage (aiwc.ods column ∪ actor states).
const CURRENT_STATUS: &[&str] = &[
"Development",
"Deployment",
"Operation",
"Retirement",
"Active",
"Historical",
"Deployed",
];

/// `type` — the system/actor class (aiwc.ods `type` column ∪ common instances).
const NODE_TYPE: &[&str] = &[
"IntelligentControlSystem",
"GenerativeModel",
"GenerativeAI",
"NarrowAI",
"TrainingDatabase",
"ExpertSystem",
"ServiceRobot",
"MultiAgentSystem",
"Dashboard",
"Nation",
"TechCompany",
"DefenseCompany",
"Military",
"Investor",
"Institution",
"Utility",
"SurveillanceSystem",
"FacialRecognition",
"AutonomousWeapon",
"TargetingSystem",
"RoboticSystem",
"LoiteringMunition",
"PredictiveAnalytics",
"DataAnalyticsCompany",
"FoundationModelProvider",
"SurveillanceVendor",
"AILab",
"GovernmentOfficial",
"HeadOfState",
"Investor",
"Financier",
"TechExecutive",
];

/// `output:airo` — what the system emits.
const OUTPUT_AIRO: &[&str] = &[
"Action",
"Content",
"Decision",
"Prediction",
"Recommendation",
"Detection",
"Monitoring",
"Identification",
"Generation",
"Investment",
"Influence",
];

/// `impact:vair` — the VAIR harm (primary token of a compound list).
const IMPACT_VAIR: &[&str] = &[
"PsychologicalHarm",
"PhysicalInjury",
"PhysicalHarm",
"WellbeingImpact",
"DistortionInHumanBehavior",
"Overreliance",
"UnderminingFreedom",
"LossOfLife",
"LossOfHumanControl",
"LossOfPrivacy",
"DiscriminationBias",
"ConcentrationOfPower",
"Escalation",
"PolicyCapture",
"Deregulation",
];

/// `stakeholder` — the owning/operating actor class.
const STAKEHOLDER: &[&str] = &[
"Nation",
"TechCompany",
"DefenseCompany",
"Institution",
"Military",
"Police",
"Company",
"Investor",
"Utility",
"Owner",
"CEO",
"Politician",
"Agency",
"Person",
"NGO",
"Consortium",
];

/// Code a single facet value (`1 + index` in its codebook; `0` = absent /
/// unknown). Compound axes (comma-joined) code their **primary** token; the
/// match is case-insensitive so harvest casing drift never silently drops.
Expand Down Expand Up @@ -326,6 +437,22 @@ fn write_facet_tenant(value: &mut [u8; 480], props: &HashMap<String, Value>) {
if let Some(v) = s("capacity").or_else(|| s("capacity:airo")) {
value[FACET_CAPACITY] = facet_code(CAPACITY_AIRO, v);
}
// V3 6×(8:8) completion — the six dims the original tenant left unstacked.
if let Some(v) = s("currentStatus").or_else(|| s("currentStatus:airo")) {
value[FACET_STATUS] = facet_code(CURRENT_STATUS, v);
}
if let Some(v) = s("type") {
value[FACET_TYPE] = facet_code(NODE_TYPE, v);
}
if let Some(v) = s("output").or_else(|| s("output:airo")) {
value[FACET_OUTPUT] = facet_code(OUTPUT_AIRO, v);
}
if let Some(v) = s("impact").or_else(|| s("impact:vair")) {
value[FACET_IMPACT] = facet_code(IMPACT_VAIR, v);
}
if let Some(v) = s("stakeholder") {
value[FACET_STAKEHOLDER] = facet_code(STAKEHOLDER, v);
}
}

/// Golden angle (radians) — the φ-spiral / Vogel constant.
Expand Down Expand Up @@ -858,6 +985,14 @@ const REL_FACET_AIRO: u8 = 12;
const REL_FACET_MLTYPE: u8 = 13;
const REL_FACET_PURPOSE: u8 = 14;
const REL_FACET_CAPACITY: u8 = 15;
// V3 6×(8:8) completion — the remaining stacked dimensions as traversable facet
// edges (rel 16..20). Same distinct-layer contract as 10..15 (toggleable in the
// client); together the eleven rels put the WHOLE cascade in the schema graph.
const REL_FACET_STATUS: u8 = 16;
const REL_FACET_TYPE: u8 = 17;
const REL_FACET_OUTPUT: u8 = 18;
const REL_FACET_IMPACT: u8 = 19;
const REL_FACET_STAKEHOLDER: u8 = 20;

/// (property-key candidates, facet rel) per dual-use axis. First matching key
/// wins (e.g. `MLTask` before `MLTasks`). Mirrors the facet tenant axes.
Expand All @@ -868,6 +1003,11 @@ const FACET_AXES: &[(&[&str], u8)] = &[
(&["MLTask", "MLTasks"], REL_FACET_MLTYPE),
(&["purpose", "purpose:vair"], REL_FACET_PURPOSE),
(&["capacity", "capacity:airo"], REL_FACET_CAPACITY),
(&["currentStatus", "currentStatus:airo"], REL_FACET_STATUS),
(&["type"], REL_FACET_TYPE),
(&["output", "output:airo"], REL_FACET_OUTPUT),
(&["impact", "impact:vair"], REL_FACET_IMPACT),
(&["stakeholder"], REL_FACET_STAKEHOLDER),
Comment on lines +1006 to +1010

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep new facet rels in the dimension layer

These new rel codes 16–20 are described as toggleable facet edges, but the UI still classifies only 8 and 10..15 as facet/dimension rels while rendering every r >= 2 in the semantic graph. As soon as V3 data emits currentStatus, type, output, impact, or stakeholder edges, they stay visible even when the “family concepts”/dimension layer is hidden and are labeled as generic related, cluttering the default entity graph.

Useful? React with 👍 / 👎.

];

/// Entity → `SchemaValue` facet edges: for each node carrying a dual-use facet
Expand Down
30 changes: 30 additions & 0 deletions data/osint-v3/OSINT_V3_BAKE_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# OSINT-V3 SoA Bake — aiwar-neo4j-harvest → 6×(8:8) substrate

Baked from `AdaWorldAPI/aiwar-neo4j-harvest` (611 nodes) into the V3 SoA
`6×(8:8) part_of:is_a` centroid cascade (CAM-PQ-shaped) under classid
`0x1000_0700` (System) / `0x1000_0701` (Person, McClelland).

## Assets
- `osint_v3.soa` — binary SoA. Header `OSINTV3\0` + `ver(u16=3) count(u32) stride(u16=36)`,
then `count` records of `row_id(u32) | GUID1(16B) | GUID2(16B)` little-endian.
- `osint_v3_codebook.json` — the aiwc.ods controlled vocab → byte map, tier layout, classids.
- `osint_v3_nodes.json` — per-node index (row, id, name, guid1/guid2 hex, is_person).

## GUID1 (identity+location, 6×(8:8), CAM-PQ-shaped)
`[classid u32][HEEL|HIP|TWIG|LEAF|family|identity]` — each tier `hi:lo` byte-pair:
HEEL currentStatus:type · HIP militaryUse:civicUse · TWIG MLTask:MLType ·
LEAF purpose:capacity · family output:impact · identity stakeholder:airo_type.

## GUID2 (relationships, McClelland, persons)
`[classid u32][stage:need|receptor:rubicon|motive:_|...]`.

## Findings (this bake)
- Codebook max cardinality = 30 → every dim fits u8 (u16 is 8× overkill).
- 45 dual-use HIP basins (militaryUse:civicUse prefix) emerge with zero hub nodes —
the AIRO "island" axes collapse into prefix-adjacency.
- 78 collision groups = same-type basins (finance/politician/…), u32 row = local identity.
- 133 persons carry a McClelland GUID2.

Provisional: enrichment for unwired dims was inferred by a 15-agent sweep against
the aiwc.ods controlled vocabulary; base dims (type/currentStatus/stakeholder) are
from the harvest. Reconcile against ground-truth cypher enrichments before locking.
Loading