- {columns.map((column) => (
+ {columns.map(column => (
`${key}: ${value ?? "UNKNOWN"}`)
+ .join(" • ");
+}
export default function WarRoomPage() {
- const [mode, setMode] = useState<"LIVE" | "SIMULATION">("SIMULATION");
+ const [mode, setMode] = useState<"LIVE" | "SIMULATION">("LIVE");
+ const { data, isLoading, isError, refetch, dataUpdatedAt } = useQuery({
+ queryKey: ["/api/war-room/status"],
+ refetchInterval: mode === "LIVE" ? 30_000 : false,
+ retry: 1,
+ });
+
+ const connectorMap = new Map(data?.connectors.map(connector => [connector.id, connector]) ?? []);
+ const weather = connectorMap.get("public-weather");
+ const orbit = connectorMap.get("public-orbital");
+ const antarctica = connectorMap.get("antarctica-public-weather");
+ const local = connectorMap.get("zyra-local");
+
+ const statusTiles = [
+ { label: "War Room API", value: local?.state ?? (isLoading ? "CONNECTING" : "UNKNOWN"), icon: ShieldCheck },
+ { label: "Public Data", value: data?.components.livePublicData ?? "UNKNOWN", icon: Globe2 },
+ { label: "Golden Shield", value: data?.components.goldenShieldExecution ?? "UNKNOWN", icon: Shield },
+ { label: "MITO", value: data?.components.mitoExecution ?? "UNKNOWN", icon: Cpu },
+ ];
return (
-
-
+
-
- AEGIS WAR ROOM
-
-
- DEFENSIVE DECISION SUPPORT
-
-
- HUMAN AUTHORITY REQUIRED
-
+ AEGIS WAR ROOM
+ DEFENSIVE DECISION SUPPORT
+ NO WEAPON / DRONE CONTROL
-
- XRAYCLOUD // COMMAND RESILIENCE MATRIX
-
+
XRAYCLOUD // LIVE RESILIENCE MATRIX
- Cyberpunk command visualization for defensive mission assurance, infrastructure resilience, public geospatial awareness,
- evidence, continuity planning, and governed AI recommendations.
+ Evidence-aware defensive command visualization using authenticated local status plus public weather, orbital, and research context.
+ Missing feeds remain UNKNOWN or UNAVAILABLE instead of being fabricated.
-
-
-
setMode("SIMULATION")}
- className={cn(mode === "SIMULATION" && "bg-emerald-500 text-black hover:bg-emerald-400")}
- >
- SIMULATION
-
-
setMode("LIVE")}
- className={cn(mode === "LIVE" && "bg-cyan-500 text-black hover:bg-cyan-400")}
- >
- LIVE VIEW
-
+
+ setMode("LIVE")} className={cn(mode === "LIVE" && "bg-cyan-500 text-black hover:bg-cyan-400")}>LIVE VIEW
+ setMode("SIMULATION")} className={cn(mode === "SIMULATION" && "bg-emerald-500 text-black hover:bg-emerald-400")}>SIMULATION
+ refetch()}>REFRESH
-
- {mode === "SIMULATION"
- ? "Simulation mode: visualization uses demonstration state only; no external systems are controlled."
- : "Live view is display-only until verified authorized data connectors are configured; unknown data remains UNKNOWN."}
+ {mode === "LIVE"
+ ? `Live display-only mode • last UI refresh ${dataUpdatedAt ? new Date(dataUpdatedAt).toLocaleTimeString() : "UNKNOWN"} • no external system control path exists.`
+ : "Simulation mode is visualization-only and does not control external systems."}
-
+
- {statusTiles.map(({ label, value, icon: Icon, tone }) => (
+ {statusTiles.map(({ label, value, icon: Icon }) => (
-
+
))}
-
+
-
- Scout // Geospatial + Orbital Awareness
-
+ Scout // Public Awareness
-
-
{[
- ["PUBLIC SATCOM", "18%", "24%", Satellite],
- ["ANTARCTICA RESEARCH", "70%", "70%", Snowflake],
- ["RESILIENCE REGION", "23%", "67%", Shield],
- ["SIMULATED AIR NODE", "76%", "30%", RadioTower],
- ].map(([label, top, left, Icon]: any) => (
+ ["PUBLIC ORBIT", "18%", "24%", Satellite, orbit?.state ?? "UNKNOWN"],
+ ["ANTARCTICA RESEARCH", "70%", "70%", Snowflake, antarctica?.state ?? "UNKNOWN"],
+ ["PUBLIC WEATHER", "23%", "67%", Zap, weather?.state ?? "UNKNOWN"],
+ ["LOCAL CONTROL PLANE", "76%", "30%", RadioTower, local?.state ?? "UNKNOWN"],
+ ].map(([label, top, left, Icon, state]: any) => (
-
-
- {label}
-
+
+ {label}{state}
))}
-
-
- DATA POLICY: PUBLIC / SIMULATED / NON-SENSITIVE ONLY • NO TARGETING • NO LIVE WEAPON TELEMETRY • NO DRONE CONTROL
+
+ PUBLIC / AGGREGATE / NON-SENSITIVE DISPLAY ONLY • NO TARGETING • NO WEAPON RELEASE • NO DIRECT DRONE CONTROL
@@ -211,40 +193,42 @@ export default function WarRoomPage() {
-
- ETHER Event Stream
-
+ Live Connector Fabric
- {eventStream.map((event, index) => (
-
-
{String(index + 1).padStart(2, "0")}
-
{event}
+ {isLoading &&
CONNECTING...
}
+ {isError &&
WAR ROOM API UNAVAILABLE
}
+ {data?.connectors.map(connector => (
+
+
+
+
{connector.label}
+
{detailLine(connector)}
+
+
{connector.state}
+
))}
-
-
- GPT-DOUG-MAX
-
-
- Recommendations remain advisory until Golden Shield, policy, authority, and human approval requirements are satisfied.
-
+
+
GPT-DOUG-MAX
+
Decision support remains advisory. This Zyra branch currently has no Golden Shield / MITO execution integration, so those states are shown truthfully as NOT_CONNECTED / DISABLED.
- {safeFeeds.map(({ title, icon: Icon, status, description }) => (
-
+ {[
+ { title: "Public Geospatial Scout", icon: MapPinned, status: weather?.state ?? "UNKNOWN", description: "Public weather and resilience context only; no sensitive live operational locations." },
+ { title: "Orbital Awareness", icon: Satellite, status: orbit?.state ?? "UNKNOWN", description: "Aggregate public orbital catalog awareness only; no cueing, protected telemetry, or satellite control." },
+ { title: "Antarctica Research Watch", icon: Snowflake, status: antarctica?.state ?? "UNKNOWN", description: "Public research-region weather context for continuity and logistics planning." },
+ { title: "Readiness Data Boundary", icon: Workflow, status: "SAFE", description: "Drone and weapon-system maintenance/readiness data may be displayed when authorized; control and engagement pathways remain excluded." },
+ ].map(({ title, icon: Icon, status, description }) => (
+
-
-
-
-
- {status}
-
+
+
{status}
{title}
{description}
@@ -255,39 +239,16 @@ export default function WarRoomPage() {
-
-
- Weapon / Drone Control Boundary
-
-
-
- War Room may visualize authorized readiness and simulation data, but it does not provide target selection, weapon release,
- firing solutions, strike planning, autonomous lethal decisions, or direct drone flight / payload control.
-
+ Control Boundary
+ Target selection, weapon release, fire control, strike planning, direct drone flight/payload control, autonomous lethal action, and offensive cyber execution remain unavailable.
-
-
-
- Orbital / Space Boundary
-
-
-
- Public orbital data and simulated assets may be visualized for awareness and continuity. Protected telemetry, military cueing,
- targeting correlations, or unauthorized satellite control are excluded.
-
+ Space Boundary
+ Public aggregate orbital data may be visualized for awareness. Protected telemetry, military cueing, targeting correlations, and satellite control are excluded.
-
-
-
- Infrastructure Boundary
-
-
-
- Critical-infrastructure analysis is restricted to resilience, continuity, maintenance, and authorized defensive decision support;
- the interface must not expose actionable vulnerability or exploitation details.
-
+ Data Honesty
+ External connector failure is surfaced as UNAVAILABLE. Missing integration is NOT_CONNECTED. No visual state is promoted to verified execution capability.
From 9314a39572316eed0c0fb1256cd8988ae0064bbd Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 00:42:05 -0400
Subject: [PATCH 09/23] Add War Room CI proof gate
---
.github/workflows/war-room-ci.yml | 42 +++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
create mode 100644 .github/workflows/war-room-ci.yml
diff --git a/.github/workflows/war-room-ci.yml b/.github/workflows/war-room-ci.yml
new file mode 100644
index 0000000..a4ea394
--- /dev/null
+++ b/.github/workflows/war-room-ci.yml
@@ -0,0 +1,42 @@
+name: War Room CI
+
+on:
+ push:
+ branches:
+ - feature/cyberpunk-war-room
+ pull_request:
+ paths:
+ - "client/src/pages/war-room.tsx"
+ - "client/src/components/WarRoomLauncher.tsx"
+ - "server/war-room*.ts"
+ - "server/index.ts"
+ - ".github/workflows/war-room-ci.yml"
+
+permissions:
+ contents: read
+
+jobs:
+ verify:
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: War Room boundary tests
+ run: npx tsx --test server/war-room.test.ts
+
+ - name: TypeScript check
+ run: npm run check
+
+ - name: Production build
+ run: npm run build
From 12e2f21cb87bb815c5157f74678565bc179553b4 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:37:18 -0400
Subject: [PATCH 10/23] fix: set explicit ES2022 TypeScript target
---
tsconfig.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/tsconfig.json b/tsconfig.json
index a0203ee..f549e2c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,6 +5,7 @@
"incremental": true,
"tsBuildInfoFile": "./node_modules/typescript/tsbuildinfo",
"noEmit": true,
+ "target": "ES2022",
"module": "ESNext",
"strict": true,
"lib": ["esnext", "dom", "dom.iterable"],
From 5b770061b5ad834c2823d1e2f3f152270a6a3d28 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:44:42 -0400
Subject: [PATCH 11/23] ci: repair Express type compatibility lockfile
---
.../workflows/typescript-types-pin-repair.yml | 42 +++++++++++++++++++
1 file changed, 42 insertions(+)
create mode 100644 .github/workflows/typescript-types-pin-repair.yml
diff --git a/.github/workflows/typescript-types-pin-repair.yml b/.github/workflows/typescript-types-pin-repair.yml
new file mode 100644
index 0000000..689be14
--- /dev/null
+++ b/.github/workflows/typescript-types-pin-repair.yml
@@ -0,0 +1,42 @@
+name: TypeScript Types Pin Repair
+
+on:
+ push:
+ branches:
+ - feature/cyberpunk-war-room
+ paths:
+ - .github/workflows/typescript-types-pin-repair.yml
+
+permissions:
+ contents: write
+
+jobs:
+ repair-lockfile:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout branch
+ uses: actions/checkout@v4
+ with:
+ ref: feature/cyberpunk-war-room
+ fetch-depth: 0
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+ - name: Pin compatible Express route parameter types
+ run: npm install --package-lock-only --save-dev --save-exact @types/express-serve-static-core@5.0.7
+ - name: Validate lockfile
+ run: npm ci
+ - name: Commit deterministic lockfile repair
+ run: |
+ git diff --check
+ if git diff --quiet -- package.json package-lock.json; then
+ echo "No lockfile change required"
+ exit 0
+ fi
+ git config user.name "zyra-ci-repair"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add package.json package-lock.json
+ git commit -m "fix: pin compatible Express parameter types"
+ git push origin HEAD:feature/cyberpunk-war-room
From 9249a7f50f42d7d52b18bacd2fee842a553f0bd6 Mon Sep 17 00:00:00 2001
From: zyra-ci-repair <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 06:45:04 +0000
Subject: [PATCH 12/23] fix: pin compatible Express parameter types
---
package-lock.json | 67 ++++++++++++++++++++++++++++++++++++++++++++---
package.json | 1 +
2 files changed, 65 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index e60f66f..48635cb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -89,6 +89,7 @@
"@tailwindcss/vite": "^4.1.18",
"@types/connect-pg-simple": "^7.0.3",
"@types/express": "^5.0.0",
+ "@types/express-serve-static-core": "5.0.7",
"@types/express-session": "^1.18.0",
"@types/node": "20.19.27",
"@types/passport": "^1.0.16",
@@ -3045,6 +3046,66 @@
"node": ">=14.0.0"
}
},
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
+ "version": "1.7.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.1.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
+ "version": "1.7.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@tybys/wasm-util": "^0.10.1"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
+ "version": "2.8.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "0BSD",
+ "optional": true
+ },
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
@@ -3324,9 +3385,9 @@
}
},
"node_modules/@types/express-serve-static-core": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
- "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz",
+ "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index b63c6ef..5eab45e 100644
--- a/package.json
+++ b/package.json
@@ -91,6 +91,7 @@
"@tailwindcss/vite": "^4.1.18",
"@types/connect-pg-simple": "^7.0.3",
"@types/express": "^5.0.0",
+ "@types/express-serve-static-core": "5.0.7",
"@types/express-session": "^1.18.0",
"@types/node": "20.19.27",
"@types/passport": "^1.0.16",
From d204f6e2176583980c225f159650a051adee27a5 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:46:03 -0400
Subject: [PATCH 13/23] ci: remove one-shot TypeScript lockfile repair
---
.../workflows/typescript-types-pin-repair.yml | 42 -------------------
1 file changed, 42 deletions(-)
delete mode 100644 .github/workflows/typescript-types-pin-repair.yml
diff --git a/.github/workflows/typescript-types-pin-repair.yml b/.github/workflows/typescript-types-pin-repair.yml
deleted file mode 100644
index 689be14..0000000
--- a/.github/workflows/typescript-types-pin-repair.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-name: TypeScript Types Pin Repair
-
-on:
- push:
- branches:
- - feature/cyberpunk-war-room
- paths:
- - .github/workflows/typescript-types-pin-repair.yml
-
-permissions:
- contents: write
-
-jobs:
- repair-lockfile:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout branch
- uses: actions/checkout@v4
- with:
- ref: feature/cyberpunk-war-room
- fetch-depth: 0
- - name: Setup Node
- uses: actions/setup-node@v4
- with:
- node-version: 20
- cache: npm
- - name: Pin compatible Express route parameter types
- run: npm install --package-lock-only --save-dev --save-exact @types/express-serve-static-core@5.0.7
- - name: Validate lockfile
- run: npm ci
- - name: Commit deterministic lockfile repair
- run: |
- git diff --check
- if git diff --quiet -- package.json package-lock.json; then
- echo "No lockfile change required"
- exit 0
- fi
- git config user.name "zyra-ci-repair"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git add package.json package-lock.json
- git commit -m "fix: pin compatible Express parameter types"
- git push origin HEAD:feature/cyberpunk-war-room
From c25dc254b36d18bd6367a2f9a6842f57342ecd03 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:49:56 -0400
Subject: [PATCH 14/23] ci: add deterministic TypeScript schema repair
---
script/typescript-schema-repair.mjs | 176 ++++++++++++++++++++++++++++
1 file changed, 176 insertions(+)
create mode 100644 script/typescript-schema-repair.mjs
diff --git a/script/typescript-schema-repair.mjs b/script/typescript-schema-repair.mjs
new file mode 100644
index 0000000..978efaa
--- /dev/null
+++ b/script/typescript-schema-repair.mjs
@@ -0,0 +1,176 @@
+import fs from "node:fs";
+
+function read(path) {
+ return fs.readFileSync(path, "utf8");
+}
+
+function write(path, content) {
+ fs.writeFileSync(path, content);
+}
+
+function replaceOnce(source, before, after, label) {
+ const first = source.indexOf(before);
+ if (first === -1) throw new Error(`repair pattern not found: ${label}`);
+ if (source.indexOf(before, first + before.length) !== -1) {
+ throw new Error(`repair pattern is ambiguous: ${label}`);
+ }
+ return source.replace(before, after);
+}
+
+function replaceAllRequired(source, before, after, expectedMinimum, label) {
+ const count = source.split(before).length - 1;
+ if (count < expectedMinimum) throw new Error(`repair pattern count too low for ${label}: ${count}`);
+ return source.split(before).join(after);
+}
+
+// Frontend typing repairs.
+{
+ const path = "client/src/pages/enterprise.tsx";
+ let source = read(path);
+ source = replaceOnce(
+ source,
+ 'function MultiRegionTab({ isLoading: _parentLoading }: { settings?: Setting[]; isLoading: boolean }) {',
+ 'function MultiRegionTab({ settings, isLoading: _parentLoading }: { settings?: Setting[]; isLoading: boolean }) {',
+ "enterprise settings destructure",
+ );
+ write(path, source);
+}
+
+{
+ const path = "client/src/pages/pentest.tsx";
+ let source = read(path);
+ source = replaceOnce(
+ source,
+ '{session.testTypes.length} test types',
+ '{Array.isArray(session.testTypes) ? session.testTypes.length : 0} test types',
+ "pentest testTypes rendering",
+ );
+ source = replaceOnce(
+ source,
+ '{(finding.testType as React.ReactNode).toString().replace("_", " ")}',
+ '{String(finding.testType ?? "unknown").replace("_", " ")}',
+ "pentest nullable testType",
+ );
+ write(path, source);
+}
+
+{
+ const path = "client/src/pages/threat-detail.tsx";
+ let source = read(path);
+ source = replaceOnce(
+ source,
+ '
',
+ '',
+ "supported button variant",
+ );
+ write(path, source);
+}
+
+// Schema-aligned server repairs.
+{
+ const path = "server/caasm.ts";
+ let source = read(path);
+ source = replaceOnce(
+ source,
+ ' const linkedVulns = vulnerabilities.filter(v =>\n v.assetId === asset.id ||\n (v.title?.toLowerCase().includes(asset.hostname.toLowerCase()))\n );',
+ ' const linkedVulns = vulnerabilities.filter(v =>\n v.affectedComponent?.toLowerCase().includes(asset.hostname.toLowerCase()) ||\n (v.title?.toLowerCase().includes(asset.hostname.toLowerCase()))\n );',
+ "CAASM vulnerability correlation",
+ );
+ source = replaceAllRequired(source, "i.affectedAssets", "i.affectedSystems", 2, "CAASM incident systems");
+ write(path, source);
+}
+
+{
+ const path = "server/exposure.ts";
+ let source = read(path);
+ source = replaceOnce(
+ source,
+ ' exposed: level !== "internal" && level !== "none",',
+ ' exposed: level !== "internal",',
+ "exposure union comparison",
+ );
+ write(path, source);
+}
+
+{
+ const path = "server/intelligence.ts";
+ let source = read(path);
+ source = replaceAllRequired(source, "v.remediation ||", "v.remediationSteps ||", 1, "vulnerability remediation field");
+ source = replaceAllRequired(source, "a.isPublicFacing", 'a.tags.includes("public-facing")', 3, "public-facing asset label");
+ source = replaceAllRequired(source, "a.operatingSystem", "a.os", 1, "asset OS field");
+ write(path, source);
+}
+
+{
+ const path = "server/metrics.ts";
+ let source = read(path);
+ source = replaceAllRequired(source, "item.name", "item.packageName", 1, "SBOM package name filter");
+ source = replaceAllRequired(source, "a.name", "a.packageName", 2, "SBOM package name output");
+ source = replaceAllRequired(source, "a.version", "a.packageVersion", 1, "SBOM package version output");
+ write(path, source);
+}
+
+{
+ const path = "server/routes.ts";
+ let source = read(path);
+ source = replaceOnce(
+ source,
+ ' const updated = f.updatedAt ? new Date(f.updatedAt).getTime() : Date.now();',
+ ' const updated = f.resolvedAt ? new Date(f.resolvedAt).getTime() : Date.now();',
+ "resolved finding timestamp",
+ );
+ source = replaceOnce(
+ source,
+ ' const scannedRepoCount = new Set(allScans.map(s => s.repositoryId).filter(Boolean)).size;',
+ ' const scannedRepoCount = new Set(allScans.filter(s => s.targetType === "repository").map(s => s.targetId).filter((id): id is string => Boolean(id))).size;',
+ "scan target repository coverage",
+ );
+ source = replaceAllRequired(source, "resource: \"", "resourceType: \"", 3, "audit resource type field");
+ source = replaceOnce(
+ source,
+ ' const r = await storage.updateTrainingRecord(req.params.id, parsed.data);',
+ ' const trainingUpdate = {\n completed: parsed.data.completed,\n completedAt: parsed.data.completedAt === undefined ? undefined : parsed.data.completedAt === null ? null : new Date(parsed.data.completedAt),\n course: parsed.data.courseName,\n phishingScore: parsed.data.score,\n };\n const r = await storage.updateTrainingRecord(req.params.id, trainingUpdate);',
+ "training schema transform",
+ );
+ source = replaceOnce(
+ source,
+ ' avgCvss: parseFloat((cves.reduce((s, c) => s + c.cvssScore, 0) / cves.length).toFixed(1)),',
+ ' avgCvss: cves.length > 0 ? parseFloat((cves.reduce((s, c) => s + (c.cvssScore ?? 0), 0) / cves.length).toFixed(1)) : 0,',
+ "nullable CVSS average",
+ );
+ source = replaceOnce(
+ source,
+ ' const criticalRisks = risks.filter(r => r.severity === "critical" && r.status !== "accepted").length;',
+ ' const criticalRisks = risks.filter(r => r.riskScore >= 15 && r.status !== "accepted").length;',
+ "risk severity derivation",
+ );
+ write(path, source);
+}
+
+{
+ const path = "server/seed-demo.ts";
+ let source = read(path);
+ source = replaceOnce(source, ', resolvedAt: ago(3), verifiedAt: ago(2)', ', verifiedAt: ago(2)', "resolved vulnerability fixture one");
+ source = replaceOnce(source, ', resolvedAt: ago(10), verifiedAt: ago(9)', ', verifiedAt: ago(9)', "resolved vulnerability fixture two");
+ write(path, source);
+}
+
+{
+ const path = "server/task-runner.ts";
+ let source = read(path);
+ source = replaceOnce(
+ source,
+ ' const scan = await storage.createScan({\n organizationId: task.organizationId,\n repositoryId: repo.id,\n type: "full",\n status: "running",\n branch: repo.defaultBranch || "main",\n commitHash: null,\n triggeredBy: task.createdById || "system",\n totalFindings: 0,\n criticalCount: 0,\n highCount: 0,\n mediumCount: 0,\n lowCount: 0,\n });',
+ ' const scan = await storage.createScan({\n organizationId: task.organizationId,\n name: `Task scan: ${repo.name}`,\n scanType: "semgrep",\n status: "running",\n targetType: "repository",\n targetId: repo.id,\n targetName: repo.name,\n initiatedById: task.createdById || null,\n });',
+ "task scan schema",
+ );
+ source = replaceOnce(
+ source,
+ ' playbookId: playbook.id,\n status: "running",',
+ ' playbookId: playbook.id,\n playbookName: playbook.name,\n status: "running",',
+ "SOAR playbook name",
+ );
+ write(path, source);
+}
+
+console.log("TypeScript schema repair applied");
From ecd4693bbdf9d2744ac154f67cdc5ad19352a736 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:50:05 -0400
Subject: [PATCH 15/23] ci: run verified TypeScript schema repair
---
.../workflows/typescript-schema-repair.yml | 49 +++++++++++++++++++
1 file changed, 49 insertions(+)
create mode 100644 .github/workflows/typescript-schema-repair.yml
diff --git a/.github/workflows/typescript-schema-repair.yml b/.github/workflows/typescript-schema-repair.yml
new file mode 100644
index 0000000..d6e194f
--- /dev/null
+++ b/.github/workflows/typescript-schema-repair.yml
@@ -0,0 +1,49 @@
+name: TypeScript Schema Repair
+
+on:
+ push:
+ branches:
+ - feature/cyberpunk-war-room
+ paths:
+ - script/typescript-schema-repair.mjs
+ - .github/workflows/typescript-schema-repair.yml
+
+permissions:
+ contents: write
+
+jobs:
+ repair-and-verify:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout feature branch
+ uses: actions/checkout@v4
+ with:
+ ref: feature/cyberpunk-war-room
+ fetch-depth: 0
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+ - name: Install
+ run: npm ci
+ - name: Apply deterministic schema repairs
+ run: node script/typescript-schema-repair.mjs
+ - name: Verify War Room boundary
+ run: npx tsx --test server/war-room.test.ts
+ - name: TypeScript check
+ run: npm run check
+ - name: Production build
+ run: npm run build
+ - name: Commit verified source repairs
+ run: |
+ git diff --check
+ if git diff --quiet -- client server; then
+ echo "No source repair required"
+ exit 0
+ fi
+ git config user.name "zyra-ci-repair"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add client server
+ git commit -m "fix: align TypeScript code with current schemas"
+ git push origin HEAD:feature/cyberpunk-war-room
From cec5c678e6c6c223d24670af0e69687d31da9b40 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:52:53 -0400
Subject: [PATCH 16/23] ci: preview deterministic TypeScript repairs
---
.../workflows/typescript-schema-repair.yml | 34 +++++--------------
1 file changed, 9 insertions(+), 25 deletions(-)
diff --git a/.github/workflows/typescript-schema-repair.yml b/.github/workflows/typescript-schema-repair.yml
index d6e194f..58170a3 100644
--- a/.github/workflows/typescript-schema-repair.yml
+++ b/.github/workflows/typescript-schema-repair.yml
@@ -1,25 +1,19 @@
-name: TypeScript Schema Repair
+name: TypeScript Schema Repair Preview
on:
- push:
+ pull_request:
branches:
- - feature/cyberpunk-war-room
- paths:
- - script/typescript-schema-repair.mjs
- - .github/workflows/typescript-schema-repair.yml
+ - main
permissions:
- contents: write
+ contents: read
jobs:
- repair-and-verify:
+ repair-preview:
runs-on: ubuntu-latest
steps:
- - name: Checkout feature branch
+ - name: Checkout PR merge
uses: actions/checkout@v4
- with:
- ref: feature/cyberpunk-war-room
- fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
@@ -27,7 +21,7 @@ jobs:
cache: npm
- name: Install
run: npm ci
- - name: Apply deterministic schema repairs
+ - name: Apply deterministic schema repairs in runner
run: node script/typescript-schema-repair.mjs
- name: Verify War Room boundary
run: npx tsx --test server/war-room.test.ts
@@ -35,15 +29,5 @@ jobs:
run: npm run check
- name: Production build
run: npm run build
- - name: Commit verified source repairs
- run: |
- git diff --check
- if git diff --quiet -- client server; then
- echo "No source repair required"
- exit 0
- fi
- git config user.name "zyra-ci-repair"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git add client server
- git commit -m "fix: align TypeScript code with current schemas"
- git push origin HEAD:feature/cyberpunk-war-room
+ - name: Show verified repair diff
+ run: git diff --check && git diff --stat
From 5d29812ca559bcc63c09053f609b3b3b6cf380e5 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:54:17 -0400
Subject: [PATCH 17/23] fix: correct deterministic repair expectations
---
script/typescript-schema-repair.mjs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/script/typescript-schema-repair.mjs b/script/typescript-schema-repair.mjs
index 978efaa..63eff22 100644
--- a/script/typescript-schema-repair.mjs
+++ b/script/typescript-schema-repair.mjs
@@ -96,7 +96,7 @@ function replaceAllRequired(source, before, after, expectedMinimum, label) {
const path = "server/intelligence.ts";
let source = read(path);
source = replaceAllRequired(source, "v.remediation ||", "v.remediationSteps ||", 1, "vulnerability remediation field");
- source = replaceAllRequired(source, "a.isPublicFacing", 'a.tags.includes("public-facing")', 3, "public-facing asset label");
+ source = replaceAllRequired(source, "a.isPublicFacing", 'a.tags.includes("public-facing")', 2, "public-facing asset label");
source = replaceAllRequired(source, "a.operatingSystem", "a.os", 1, "asset OS field");
write(path, source);
}
From a25d09202b831a4b7ee85c917183ce7d16b3fe35 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:55:55 -0400
Subject: [PATCH 18/23] fix: handle unknown pentest summary rendering
---
script/typescript-schema-repair.mjs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/script/typescript-schema-repair.mjs b/script/typescript-schema-repair.mjs
index 63eff22..0ceb34c 100644
--- a/script/typescript-schema-repair.mjs
+++ b/script/typescript-schema-repair.mjs
@@ -45,6 +45,12 @@ function replaceAllRequired(source, before, after, expectedMinimum, label) {
'{Array.isArray(session.testTypes) ? session.testTypes.length : 0} test types',
"pentest testTypes rendering",
);
+ source = replaceOnce(
+ source,
+ '{session.summary && ((session.summary as any).findingsCount as number) > 0 && (',
+ '{Boolean(session.summary) && ((session.summary as any).findingsCount as number) > 0 && (',
+ "pentest unknown summary rendering",
+ );
source = replaceOnce(
source,
'{(finding.testType as React.ReactNode).toString().replace("_", " ")}',
From cecc5e985dff2a60637014ce3436631763c03b6a Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:57:17 -0400
Subject: [PATCH 19/23] ci: apply verified TypeScript schema repair
---
.../apply-typescript-schema-repair.yml | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 .github/workflows/apply-typescript-schema-repair.yml
diff --git a/.github/workflows/apply-typescript-schema-repair.yml b/.github/workflows/apply-typescript-schema-repair.yml
new file mode 100644
index 0000000..9c363bd
--- /dev/null
+++ b/.github/workflows/apply-typescript-schema-repair.yml
@@ -0,0 +1,48 @@
+name: Apply Verified TypeScript Schema Repair
+
+on:
+ push:
+ branches:
+ - feature/cyberpunk-war-room
+ paths:
+ - .github/workflows/apply-typescript-schema-repair.yml
+
+permissions:
+ contents: write
+
+jobs:
+ apply-verified-repair:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout feature branch
+ uses: actions/checkout@v4
+ with:
+ ref: feature/cyberpunk-war-room
+ fetch-depth: 0
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+ - name: Install
+ run: npm ci
+ - name: Apply deterministic schema repairs
+ run: node script/typescript-schema-repair.mjs
+ - name: Verify War Room boundary
+ run: npx tsx --test server/war-room.test.ts
+ - name: TypeScript check
+ run: npm run check
+ - name: Production build
+ run: npm run build
+ - name: Commit verified source repairs
+ run: |
+ git diff --check
+ if git diff --quiet -- client server; then
+ echo "No source repair required"
+ exit 0
+ fi
+ git config user.name "zyra-ci-repair"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add client server
+ git commit -m "fix: align TypeScript code with current schemas"
+ git push origin HEAD:feature/cyberpunk-war-room
From f3ca1fe0428294f8f26472aca922d5295d8e48e3 Mon Sep 17 00:00:00 2001
From: zyra-ci-repair <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 06:58:10 +0000
Subject: [PATCH 20/23] fix: align TypeScript code with current schemas
---
client/src/pages/enterprise.tsx | 2 +-
client/src/pages/pentest.tsx | 6 +++---
client/src/pages/threat-detail.tsx | 2 +-
server/caasm.ts | 6 +++---
server/exposure.ts | 2 +-
server/intelligence.ts | 8 ++++----
server/metrics.ts | 6 +++---
server/routes.ts | 22 ++++++++++++++--------
server/seed-demo.ts | 4 ++--
server/task-runner.ts | 17 +++++++----------
10 files changed, 39 insertions(+), 36 deletions(-)
diff --git a/client/src/pages/enterprise.tsx b/client/src/pages/enterprise.tsx
index 852efd0..eb288ed 100644
--- a/client/src/pages/enterprise.tsx
+++ b/client/src/pages/enterprise.tsx
@@ -239,7 +239,7 @@ function SsoTab({ isLoading: _parentLoading }: { settings?: Setting[]; isLoading
);
}
-function MultiRegionTab({ isLoading: _parentLoading }: { settings?: Setting[]; isLoading: boolean }) {
+function MultiRegionTab({ settings, isLoading: _parentLoading }: { settings?: Setting[]; isLoading: boolean }) {
const { toast } = useToast();
interface RegionInfo { id: string; name: string; status: string; }
diff --git a/client/src/pages/pentest.tsx b/client/src/pages/pentest.tsx
index 5b6332e..fa1fe45 100644
--- a/client/src/pages/pentest.tsx
+++ b/client/src/pages/pentest.tsx
@@ -392,9 +392,9 @@ export default function PentestPage() {
- {session.testTypes.length} test types
+ {Array.isArray(session.testTypes) ? session.testTypes.length : 0} test types
- {session.summary && ((session.summary as any).findingsCount as number) > 0 && (
+ {Boolean(session.summary) && ((session.summary as any).findingsCount as number) > 0 && (
<>
{((session.summary as any).criticalCount as number) > 0 && (
@@ -506,7 +506,7 @@ export default function PentestPage() {
{sessionDetails.findings.map((finding) => (
{getSeverityBadge(finding.severity)}
- {(finding.testType as React.ReactNode).toString().replace("_", " ")}
+ {String(finding.testType ?? "unknown").replace("_", " ")}
{(finding.title as React.ReactNode)}
diff --git a/client/src/pages/threat-detail.tsx b/client/src/pages/threat-detail.tsx
index 31cfeee..16f3352 100644
--- a/client/src/pages/threat-detail.tsx
+++ b/client/src/pages/threat-detail.tsx
@@ -211,7 +211,7 @@ export default function ThreatDetailPage() {
No matching assets found in inventory.
-
View Asset Inventory
+
View Asset Inventory
);
diff --git a/server/caasm.ts b/server/caasm.ts
index 787cf46..f53be66 100644
--- a/server/caasm.ts
+++ b/server/caasm.ts
@@ -55,12 +55,12 @@ export async function buildCorrelatedAssets(orgId: string) {
return assets.map(asset => {
const riskScore = calcAssetRiskScore(asset);
const linkedVulns = vulnerabilities.filter(v =>
- v.assetId === asset.id ||
+ v.affectedComponent?.toLowerCase().includes(asset.hostname.toLowerCase()) ||
(v.title?.toLowerCase().includes(asset.hostname.toLowerCase()))
);
const linkedIncidents = incidents.filter(i =>
- i.affectedAssets?.includes(asset.id) ||
- i.affectedAssets?.includes(asset.hostname)
+ i.affectedSystems?.includes(asset.id) ||
+ i.affectedSystems?.includes(asset.hostname)
);
const linkedRisks = risks.filter(r =>
r.description?.toLowerCase().includes(asset.hostname.toLowerCase())
diff --git a/server/exposure.ts b/server/exposure.ts
index 7fec056..1f33700 100644
--- a/server/exposure.ts
+++ b/server/exposure.ts
@@ -84,7 +84,7 @@ function isAssetExposed(asset: any): ExposureResult {
}
return {
- exposed: level !== "internal" && level !== "none",
+ exposed: level !== "internal",
exposureLevel: level,
reasons,
};
diff --git a/server/intelligence.ts b/server/intelligence.ts
index 1512803..8b5d3ad 100644
--- a/server/intelligence.ts
+++ b/server/intelligence.ts
@@ -269,7 +269,7 @@ export async function runSecurityCopilot(question: string, orgId: string): Promi
if ((q.includes("vulnerabilit") || q.includes("vuln")) && (q.includes("critical") || q.includes("patch") || q.includes("unpatched") || q.includes("open") || q.includes("top") || q.includes("worst"))) {
const target = criticalVulns.length > 0 ? criticalVulns : highVulns;
const label = criticalVulns.length > 0 ? "Critical" : "High";
- return `**${label} Vulnerability Report (Live)**\n\n**${openVulns.length}** open vulnerabilities total:\n• Critical: **${criticalVulns.length}**\n• High: **${highVulns.length}**\n• Medium: ${openVulns.filter(v => v.severity === "medium").length}\n• Low: ${openVulns.filter(v => v.severity === "low").length}\n\n**Top ${label} Findings:**\n${target.slice(0, 5).map(v => `• **${v.title}** (${v.severity})\n Component: ${v.affectedComponent || "—"} | Status: ${v.status}\n → Remediation: ${v.remediation || "Apply vendor patch"}`).join("\n")}\n\n**Remediation strategy:**\n1. Patch critical vulns within 24 hours\n2. Schedule high vulns within 7 days\n3. Automate dependency scanning in CI/CD pipeline\n4. Enable automated SBOM correlation to catch new CVEs`;
+ return `**${label} Vulnerability Report (Live)**\n\n**${openVulns.length}** open vulnerabilities total:\n• Critical: **${criticalVulns.length}**\n• High: **${highVulns.length}**\n• Medium: ${openVulns.filter(v => v.severity === "medium").length}\n• Low: ${openVulns.filter(v => v.severity === "low").length}\n\n**Top ${label} Findings:**\n${target.slice(0, 5).map(v => `• **${v.title}** (${v.severity})\n Component: ${v.affectedComponent || "—"} | Status: ${v.status}\n → Remediation: ${v.remediationSteps || "Apply vendor patch"}`).join("\n")}\n\n**Remediation strategy:**\n1. Patch critical vulns within 24 hours\n2. Schedule high vulns within 7 days\n3. Automate dependency scanning in CI/CD pipeline\n4. Enable automated SBOM correlation to catch new CVEs`;
}
if (q.includes("risk") && (q.includes("highest") || q.includes("top") || q.includes("critical") || q.includes("worst"))) {
@@ -303,8 +303,8 @@ export async function runSecurityCopilot(question: string, orgId: string): Promi
}
if (q.includes("asset") && (q.includes("exposed") || q.includes("internet") || q.includes("public") || q.includes("external"))) {
- const publicAssets = assets.filter(a => a.isPublicFacing);
- return `**Exposed Asset Analysis**\n\n**${publicAssets.length}** public-facing assets detected out of ${assets.length} total:\n\n${publicAssets.slice(0, 5).map(a => `• **${a.hostname}** (${a.assetType})\n Criticality: ${a.criticality} | Cloud: ${a.cloudProvider || "on-prem"}\n OS: ${a.operatingSystem || "—"}`).join("\n")}\n\n**Risk factors:**\n• ${activePaths.filter(p => publicAssets.some(a => p.entryPoint?.includes(a.hostname))).length} attack paths originate from public assets\n• ${criticalVulns.filter(v => publicAssets.some(a => v.affectedComponent?.includes(a.hostname))).length} critical vulns affect public assets\n\n**Hardening steps:**\n1. Verify WAF/CDN protection on all public endpoints\n2. Restrict unnecessary open ports\n3. Enable DDoS protection\n4. Schedule quarterly external penetration tests`;
+ const publicAssets = assets.filter(a => a.tags.includes("public-facing"));
+ return `**Exposed Asset Analysis**\n\n**${publicAssets.length}** public-facing assets detected out of ${assets.length} total:\n\n${publicAssets.slice(0, 5).map(a => `• **${a.hostname}** (${a.assetType})\n Criticality: ${a.criticality} | Cloud: ${a.cloudProvider || "on-prem"}\n OS: ${a.os || "—"}`).join("\n")}\n\n**Risk factors:**\n• ${activePaths.filter(p => publicAssets.some(a => p.entryPoint?.includes(a.hostname))).length} attack paths originate from public assets\n• ${criticalVulns.filter(v => publicAssets.some(a => v.affectedComponent?.includes(a.hostname))).length} critical vulns affect public assets\n\n**Hardening steps:**\n1. Verify WAF/CDN protection on all public endpoints\n2. Restrict unnecessary open ports\n3. Enable DDoS protection\n4. Schedule quarterly external penetration tests`;
}
if (q.includes("compliance") || q.includes("audit") || q.includes("framework") || q.includes("regulation") || q.includes("nist") || q.includes("iso") || q.includes("soc")) {
@@ -322,5 +322,5 @@ export async function runSecurityCopilot(question: string, orgId: string): Promi
const score = computePostureScore();
const totalOpen = criticalVulns.length + highVulns.length;
const actions = prioritizedActions();
- return `**ZyraCopilot — Environment Overview (Live)**\n\n🎯 **Posture Score: ${score}/100**\n\n**Real-Time Metrics:**\n• **Assets:** ${assets.length} tracked (${cloudAssets.length} cloud, ${assets.filter(a => a.isPublicFacing).length} public-facing)\n• **Vulnerabilities:** ${openVulns.length} open (${criticalVulns.length} critical, ${highVulns.length} high)\n• **Incidents:** ${openIncidents.length} open (${openIncidents.filter(i => i.severity === "critical").length} critical)\n• **Attack paths:** ${activePaths.length} unmitigated\n• **Risks:** ${risks.length} registered (${criticalRisks.length} critical)\n• **SBOM:** ${sbom.length} packages (${vulnerablePkgs.length} vulnerable)\n• **MTTR:** ${getMttr()}\n\n${actions.length > 0 ? `**Top Actions:**\n${actions.slice(0, 3).map((a, i) => `${i + 1}. ${a}`).join("\n")}` : "✓ No critical actions needed."}\n\n${totalOpen > 5 ? `⚠️ **Alert:** ${totalOpen} critical/high vulnerabilities require immediate attention.` : totalOpen > 0 ? `${totalOpen} critical/high vulnerabilities to address this sprint.` : "✓ No critical or high vulnerabilities open."}\n\nAsk me about specific areas — vulnerabilities, incidents, risks, attack paths, compliance, or trends.`;
+ return `**ZyraCopilot — Environment Overview (Live)**\n\n🎯 **Posture Score: ${score}/100**\n\n**Real-Time Metrics:**\n• **Assets:** ${assets.length} tracked (${cloudAssets.length} cloud, ${assets.filter(a => a.tags.includes("public-facing")).length} public-facing)\n• **Vulnerabilities:** ${openVulns.length} open (${criticalVulns.length} critical, ${highVulns.length} high)\n• **Incidents:** ${openIncidents.length} open (${openIncidents.filter(i => i.severity === "critical").length} critical)\n• **Attack paths:** ${activePaths.length} unmitigated\n• **Risks:** ${risks.length} registered (${criticalRisks.length} critical)\n• **SBOM:** ${sbom.length} packages (${vulnerablePkgs.length} vulnerable)\n• **MTTR:** ${getMttr()}\n\n${actions.length > 0 ? `**Top Actions:**\n${actions.slice(0, 3).map((a, i) => `${i + 1}. ${a}`).join("\n")}` : "✓ No critical actions needed."}\n\n${totalOpen > 5 ? `⚠️ **Alert:** ${totalOpen} critical/high vulnerabilities require immediate attention.` : totalOpen > 0 ? `${totalOpen} critical/high vulnerabilities to address this sprint.` : "✓ No critical or high vulnerabilities open."}\n\nAsk me about specific areas — vulnerabilities, incidents, risks, attack paths, compliance, or trends.`;
}
diff --git a/server/metrics.ts b/server/metrics.ts
index c0addc5..e713390 100644
--- a/server/metrics.ts
+++ b/server/metrics.ts
@@ -128,14 +128,14 @@ export async function runThreatCorrelation(orgId: string): Promise<{ correlation
if (!cve.affectedInEnvironment) continue;
const affected = sbomItems.filter(item =>
cve.affectedPackages.some((pkg: string) =>
- item.name?.toLowerCase().includes(pkg.toLowerCase())
+ item.packageName?.toLowerCase().includes(pkg.toLowerCase())
)
);
if (affected.length > 0) {
correlations.push({
cveId: cve.cveId,
severity: cve.severity,
- affectedPackages: affected.map(a => `${a.name}@${a.version}`),
+ affectedPackages: affected.map(a => `${a.packageName}@${a.packageVersion}`),
cvssScore: cve.cvssScore,
});
@@ -144,7 +144,7 @@ export async function runThreatCorrelation(orgId: string): Promise<{ correlation
source: "threat_correlation_engine",
severity: cve.severity === "critical" ? "critical" : "high",
title: `${cve.cveId} affects ${affected.length} SBOM package(s)`,
- description: `Vulnerability ${cve.cveId} (CVSS ${cve.cvssScore}) detected in: ${affected.map(a => a.name).join(", ")}`,
+ description: `Vulnerability ${cve.cveId} (CVSS ${cve.cvssScore}) detected in: ${affected.map(a => a.packageName).join(", ")}`,
metadata: { cveId: cve.cveId, affectedCount: affected.length },
});
eventsCreated++;
diff --git a/server/routes.ts b/server/routes.ts
index 5506b51..def501f 100644
--- a/server/routes.ts
+++ b/server/routes.ts
@@ -1249,7 +1249,7 @@ export async function registerRoutes(
if (resolved.length > 0) {
const totalMs = resolved.reduce((sum, f) => {
const created = new Date(f.createdAt).getTime();
- const updated = f.updatedAt ? new Date(f.updatedAt).getTime() : Date.now();
+ const updated = f.resolvedAt ? new Date(f.resolvedAt).getTime() : Date.now();
return sum + (updated - created);
}, 0);
avgRemediationDays = Math.round((totalMs / resolved.length) / (1000 * 60 * 60 * 24) * 10) / 10;
@@ -1257,7 +1257,7 @@ export async function registerRoutes(
const allScans = await storage.getScans(orgId);
const repos = await storage.getRepositories(orgId);
- const scannedRepoCount = new Set(allScans.map(s => s.repositoryId).filter(Boolean)).size;
+ const scannedRepoCount = new Set(allScans.filter(s => s.targetType === "repository").map(s => s.targetId).filter((id): id is string => Boolean(id))).size;
const totalRepoCount = Math.max(repos.length, 1);
const coveragePct = Math.round((scannedRepoCount / totalRepoCount) * 100);
@@ -1500,7 +1500,7 @@ export async function registerRoutes(
const updated = await storage.updateThreatIntelItem(req.params.id, parsed.data, req.user!.organizationId);
if (!updated) return res.status(404).json({ message: "Item not found" });
if (parsed.data.status) {
- await storage.createAuditLog({ organizationId: req.user!.organizationId, userId: req.user!.userId, action: "threat-intel.status-change", resource: "threat_intel", resourceId: req.params.id, details: { previousStatus: existing?.status, newStatus: parsed.data.status, cveId: updated.cveId } });
+ await storage.createAuditLog({ organizationId: req.user!.organizationId, userId: req.user!.userId, action: "threat-intel.status-change", resourceType: "threat_intel", resourceId: req.params.id, details: { previousStatus: existing?.status, newStatus: parsed.data.status, cveId: updated.cveId } });
if (parsed.data.status === "acknowledged") {
try {
await storage.createNotification({
@@ -1685,7 +1685,7 @@ export async function registerRoutes(
return res.status(400).json({ message: "Invalid request", errors: parsed.error.flatten().fieldErrors });
}
const item = await storage.createIncident({ ...parsed.data, organizationId: orgId });
- await storage.createAuditLog({ organizationId: orgId, userId: req.user!.userId, action: "incident.create", resource: "incident", resourceId: item.id, details: { title: item.title } });
+ await storage.createAuditLog({ organizationId: orgId, userId: req.user!.userId, action: "incident.create", resourceType: "incident", resourceId: item.id, details: { title: item.title } });
const isCritical = item.severity === "critical";
await storage.createNotification({ organizationId: orgId, title: isCritical ? "Critical Incident Created" : "New Incident Created", message: `Incident "${item.title}" has been created with ${item.severity} severity.`, type: "incident", severity: item.severity, resourceType: "incident", resourceId: item.id });
res.json(item);
@@ -1729,7 +1729,7 @@ export async function registerRoutes(
const entry = { timestamp: new Date().toISOString(), action: req.body.action, note: req.body.note, user: req.body.user || req.user!.userId };
const timeline = [...(incident.timeline as any[] || []), entry];
const updated = await storage.updateIncident(req.params.id, { timeline }, req.user!.organizationId);
- await storage.createAuditLog({ organizationId: req.user!.organizationId, userId: req.user!.userId, action: "incident.timeline.add", resource: "incident", resourceId: req.params.id, details: { action: entry.action, note: entry.note } });
+ await storage.createAuditLog({ organizationId: req.user!.organizationId, userId: req.user!.userId, action: "incident.timeline.add", resourceType: "incident", resourceId: req.params.id, details: { action: entry.action, note: entry.note } });
res.json(updated);
});
@@ -2305,7 +2305,13 @@ export async function registerRoutes(
app.put("/api/security-awareness/training/:id", requireAuth, requireRole("owner", "admin"), async (req: Request, res: Response) => {
const parsed = trainingUpdateSchema.safeParse(req.body);
if (!parsed.success) return res.status(400).json({ message: "Invalid input", errors: parsed.error.flatten().fieldErrors });
- const r = await storage.updateTrainingRecord(req.params.id, parsed.data);
+ const trainingUpdate = {
+ completed: parsed.data.completed,
+ completedAt: parsed.data.completedAt === undefined ? undefined : parsed.data.completedAt === null ? null : new Date(parsed.data.completedAt),
+ course: parsed.data.courseName,
+ phishingScore: parsed.data.score,
+ };
+ const r = await storage.updateTrainingRecord(req.params.id, trainingUpdate);
if (!r) return res.status(404).json({ message: "Not found" });
res.json(r);
});
@@ -2685,7 +2691,7 @@ export async function registerRoutes(
critical: cves.filter(c => c.severity === "critical").length,
high: cves.filter(c => c.severity === "high").length,
affectedInEnvironment: cves.filter(c => c.affectedInEnvironment).length,
- avgCvss: parseFloat((cves.reduce((s, c) => s + c.cvssScore, 0) / cves.length).toFixed(1)),
+ avgCvss: cves.length > 0 ? parseFloat((cves.reduce((s, c) => s + (c.cvssScore ?? 0), 0) / cves.length).toFixed(1)) : 0,
});
});
@@ -3025,7 +3031,7 @@ async function registerMetricsRoutes(app: Express) {
const criticalScans = scans.filter(s => s.criticalCount > 0).length;
const openIncidents = incidents.filter(i => i.status !== "resolved").length;
- const criticalRisks = risks.filter(r => r.severity === "critical" && r.status !== "accepted").length;
+ const criticalRisks = risks.filter(r => r.riskScore >= 15 && r.status !== "accepted").length;
const criticalEvents = events.filter((e: any) => e.severity === "critical").length;
const scanScore = Math.max(0, 100 - criticalScans * 8);
diff --git a/server/seed-demo.ts b/server/seed-demo.ts
index d3a95ee..fb8bf57 100644
--- a/server/seed-demo.ts
+++ b/server/seed-demo.ts
@@ -170,8 +170,8 @@ export async function seedDemoData(orgId: string, userId: string): Promise<{ see
{ organizationId: orgId, title: "Prototype Pollution in Lodash", severity: "high" as const, status: "open", source: "scan", cve: "CVE-2023-9876", cvss: 7.4, affectedComponent: "lodash@4.17.19", remediationSteps: "Update lodash to 4.17.21+", dueDate: ago(-14) },
{ organizationId: orgId, title: "Authentication Bypass in JWT Middleware", severity: "high" as const, status: "in_progress", source: "pentest", cvss: 8.5, affectedComponent: "auth-service", remediationSteps: "Enforce RS256 algorithm. Add algorithm whitelist.", assignedTo: "Zyra" },
{ organizationId: orgId, title: "Missing Input Validation on File Upload", severity: "medium" as const, status: "open", source: "scan", cvss: 6.5, affectedComponent: "api-gateway", remediationSteps: "Validate file types and implement size limits" },
- { organizationId: orgId, title: "Weak Password Policy Configuration", severity: "medium" as const, status: "resolved", source: "audit", cvss: 5.3, affectedComponent: "auth-service", remediationSteps: "Enforce minimum 12 characters with complexity", resolvedAt: ago(3), verifiedAt: ago(2) },
- { organizationId: orgId, title: "Outdated TLS Configuration on Load Balancer", severity: "medium" as const, status: "resolved", source: "scan", cve: "CVE-2023-5555", cvss: 5.9, affectedComponent: "lb-prod-01", remediationSteps: "Disable TLS 1.0/1.1. Enable TLS 1.3.", resolvedAt: ago(10), verifiedAt: ago(9) },
+ { organizationId: orgId, title: "Weak Password Policy Configuration", severity: "medium" as const, status: "resolved", source: "audit", cvss: 5.3, affectedComponent: "auth-service", remediationSteps: "Enforce minimum 12 characters with complexity", verifiedAt: ago(2) },
+ { organizationId: orgId, title: "Outdated TLS Configuration on Load Balancer", severity: "medium" as const, status: "resolved", source: "scan", cve: "CVE-2023-5555", cvss: 5.9, affectedComponent: "lb-prod-01", remediationSteps: "Disable TLS 1.0/1.1. Enable TLS 1.3.", verifiedAt: ago(9) },
{ organizationId: orgId, title: "Information Disclosure in Error Messages", severity: "low" as const, status: "open", source: "scan", cvss: 3.7, affectedComponent: "web-dashboard", remediationSteps: "Return generic error messages in production" },
]);
seeded.push("vulnerabilities");
diff --git a/server/task-runner.ts b/server/task-runner.ts
index 837823a..9ec2b51 100644
--- a/server/task-runner.ts
+++ b/server/task-runner.ts
@@ -27,17 +27,13 @@ async function runScanTask(task: Task): Promise {
const repo = repos[0];
const scan = await storage.createScan({
organizationId: task.organizationId,
- repositoryId: repo.id,
- type: "full",
+ name: `Task scan: ${repo.name}`,
+ scanType: "semgrep",
status: "running",
- branch: repo.defaultBranch || "main",
- commitHash: null,
- triggeredBy: task.createdById || "system",
- totalFindings: 0,
- criticalCount: 0,
- highCount: 0,
- mediumCount: 0,
- lowCount: 0,
+ targetType: "repository",
+ targetId: repo.id,
+ targetName: repo.name,
+ initiatedById: task.createdById || null,
});
await storage.updateScan(scan.id, { status: "completed", totalFindings: 0 });
await logTaskAudit(task, "scan.completed", "scan", scan.id);
@@ -57,6 +53,7 @@ async function runPlaybookTask(task: Task): Promise {
const execution = await storage.createSoarExecution({
organizationId: task.organizationId,
playbookId: playbook.id,
+ playbookName: playbook.name,
status: "running",
triggeredBy: task.createdById || "agent",
steps: [],
From ea92f9370f767bedabedcab322b999336cbbc685 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:58:50 -0400
Subject: [PATCH 21/23] ci: remove completed TypeScript repair helper
---
script/typescript-schema-repair.mjs | 182 ----------------------------
1 file changed, 182 deletions(-)
delete mode 100644 script/typescript-schema-repair.mjs
diff --git a/script/typescript-schema-repair.mjs b/script/typescript-schema-repair.mjs
deleted file mode 100644
index 0ceb34c..0000000
--- a/script/typescript-schema-repair.mjs
+++ /dev/null
@@ -1,182 +0,0 @@
-import fs from "node:fs";
-
-function read(path) {
- return fs.readFileSync(path, "utf8");
-}
-
-function write(path, content) {
- fs.writeFileSync(path, content);
-}
-
-function replaceOnce(source, before, after, label) {
- const first = source.indexOf(before);
- if (first === -1) throw new Error(`repair pattern not found: ${label}`);
- if (source.indexOf(before, first + before.length) !== -1) {
- throw new Error(`repair pattern is ambiguous: ${label}`);
- }
- return source.replace(before, after);
-}
-
-function replaceAllRequired(source, before, after, expectedMinimum, label) {
- const count = source.split(before).length - 1;
- if (count < expectedMinimum) throw new Error(`repair pattern count too low for ${label}: ${count}`);
- return source.split(before).join(after);
-}
-
-// Frontend typing repairs.
-{
- const path = "client/src/pages/enterprise.tsx";
- let source = read(path);
- source = replaceOnce(
- source,
- 'function MultiRegionTab({ isLoading: _parentLoading }: { settings?: Setting[]; isLoading: boolean }) {',
- 'function MultiRegionTab({ settings, isLoading: _parentLoading }: { settings?: Setting[]; isLoading: boolean }) {',
- "enterprise settings destructure",
- );
- write(path, source);
-}
-
-{
- const path = "client/src/pages/pentest.tsx";
- let source = read(path);
- source = replaceOnce(
- source,
- '{session.testTypes.length} test types',
- '{Array.isArray(session.testTypes) ? session.testTypes.length : 0} test types',
- "pentest testTypes rendering",
- );
- source = replaceOnce(
- source,
- '{session.summary && ((session.summary as any).findingsCount as number) > 0 && (',
- '{Boolean(session.summary) && ((session.summary as any).findingsCount as number) > 0 && (',
- "pentest unknown summary rendering",
- );
- source = replaceOnce(
- source,
- '{(finding.testType as React.ReactNode).toString().replace("_", " ")}',
- '{String(finding.testType ?? "unknown").replace("_", " ")}',
- "pentest nullable testType",
- );
- write(path, source);
-}
-
-{
- const path = "client/src/pages/threat-detail.tsx";
- let source = read(path);
- source = replaceOnce(
- source,
- '',
- '',
- "supported button variant",
- );
- write(path, source);
-}
-
-// Schema-aligned server repairs.
-{
- const path = "server/caasm.ts";
- let source = read(path);
- source = replaceOnce(
- source,
- ' const linkedVulns = vulnerabilities.filter(v =>\n v.assetId === asset.id ||\n (v.title?.toLowerCase().includes(asset.hostname.toLowerCase()))\n );',
- ' const linkedVulns = vulnerabilities.filter(v =>\n v.affectedComponent?.toLowerCase().includes(asset.hostname.toLowerCase()) ||\n (v.title?.toLowerCase().includes(asset.hostname.toLowerCase()))\n );',
- "CAASM vulnerability correlation",
- );
- source = replaceAllRequired(source, "i.affectedAssets", "i.affectedSystems", 2, "CAASM incident systems");
- write(path, source);
-}
-
-{
- const path = "server/exposure.ts";
- let source = read(path);
- source = replaceOnce(
- source,
- ' exposed: level !== "internal" && level !== "none",',
- ' exposed: level !== "internal",',
- "exposure union comparison",
- );
- write(path, source);
-}
-
-{
- const path = "server/intelligence.ts";
- let source = read(path);
- source = replaceAllRequired(source, "v.remediation ||", "v.remediationSteps ||", 1, "vulnerability remediation field");
- source = replaceAllRequired(source, "a.isPublicFacing", 'a.tags.includes("public-facing")', 2, "public-facing asset label");
- source = replaceAllRequired(source, "a.operatingSystem", "a.os", 1, "asset OS field");
- write(path, source);
-}
-
-{
- const path = "server/metrics.ts";
- let source = read(path);
- source = replaceAllRequired(source, "item.name", "item.packageName", 1, "SBOM package name filter");
- source = replaceAllRequired(source, "a.name", "a.packageName", 2, "SBOM package name output");
- source = replaceAllRequired(source, "a.version", "a.packageVersion", 1, "SBOM package version output");
- write(path, source);
-}
-
-{
- const path = "server/routes.ts";
- let source = read(path);
- source = replaceOnce(
- source,
- ' const updated = f.updatedAt ? new Date(f.updatedAt).getTime() : Date.now();',
- ' const updated = f.resolvedAt ? new Date(f.resolvedAt).getTime() : Date.now();',
- "resolved finding timestamp",
- );
- source = replaceOnce(
- source,
- ' const scannedRepoCount = new Set(allScans.map(s => s.repositoryId).filter(Boolean)).size;',
- ' const scannedRepoCount = new Set(allScans.filter(s => s.targetType === "repository").map(s => s.targetId).filter((id): id is string => Boolean(id))).size;',
- "scan target repository coverage",
- );
- source = replaceAllRequired(source, "resource: \"", "resourceType: \"", 3, "audit resource type field");
- source = replaceOnce(
- source,
- ' const r = await storage.updateTrainingRecord(req.params.id, parsed.data);',
- ' const trainingUpdate = {\n completed: parsed.data.completed,\n completedAt: parsed.data.completedAt === undefined ? undefined : parsed.data.completedAt === null ? null : new Date(parsed.data.completedAt),\n course: parsed.data.courseName,\n phishingScore: parsed.data.score,\n };\n const r = await storage.updateTrainingRecord(req.params.id, trainingUpdate);',
- "training schema transform",
- );
- source = replaceOnce(
- source,
- ' avgCvss: parseFloat((cves.reduce((s, c) => s + c.cvssScore, 0) / cves.length).toFixed(1)),',
- ' avgCvss: cves.length > 0 ? parseFloat((cves.reduce((s, c) => s + (c.cvssScore ?? 0), 0) / cves.length).toFixed(1)) : 0,',
- "nullable CVSS average",
- );
- source = replaceOnce(
- source,
- ' const criticalRisks = risks.filter(r => r.severity === "critical" && r.status !== "accepted").length;',
- ' const criticalRisks = risks.filter(r => r.riskScore >= 15 && r.status !== "accepted").length;',
- "risk severity derivation",
- );
- write(path, source);
-}
-
-{
- const path = "server/seed-demo.ts";
- let source = read(path);
- source = replaceOnce(source, ', resolvedAt: ago(3), verifiedAt: ago(2)', ', verifiedAt: ago(2)', "resolved vulnerability fixture one");
- source = replaceOnce(source, ', resolvedAt: ago(10), verifiedAt: ago(9)', ', verifiedAt: ago(9)', "resolved vulnerability fixture two");
- write(path, source);
-}
-
-{
- const path = "server/task-runner.ts";
- let source = read(path);
- source = replaceOnce(
- source,
- ' const scan = await storage.createScan({\n organizationId: task.organizationId,\n repositoryId: repo.id,\n type: "full",\n status: "running",\n branch: repo.defaultBranch || "main",\n commitHash: null,\n triggeredBy: task.createdById || "system",\n totalFindings: 0,\n criticalCount: 0,\n highCount: 0,\n mediumCount: 0,\n lowCount: 0,\n });',
- ' const scan = await storage.createScan({\n organizationId: task.organizationId,\n name: `Task scan: ${repo.name}`,\n scanType: "semgrep",\n status: "running",\n targetType: "repository",\n targetId: repo.id,\n targetName: repo.name,\n initiatedById: task.createdById || null,\n });',
- "task scan schema",
- );
- source = replaceOnce(
- source,
- ' playbookId: playbook.id,\n status: "running",',
- ' playbookId: playbook.id,\n playbookName: playbook.name,\n status: "running",',
- "SOAR playbook name",
- );
- write(path, source);
-}
-
-console.log("TypeScript schema repair applied");
From edde9eefb620c53a98c72cb0831f1d4f399c6dee Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:58:56 -0400
Subject: [PATCH 22/23] ci: remove completed TypeScript repair preview
---
.../workflows/typescript-schema-repair.yml | 33 -------------------
1 file changed, 33 deletions(-)
delete mode 100644 .github/workflows/typescript-schema-repair.yml
diff --git a/.github/workflows/typescript-schema-repair.yml b/.github/workflows/typescript-schema-repair.yml
deleted file mode 100644
index 58170a3..0000000
--- a/.github/workflows/typescript-schema-repair.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: TypeScript Schema Repair Preview
-
-on:
- pull_request:
- branches:
- - main
-
-permissions:
- contents: read
-
-jobs:
- repair-preview:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout PR merge
- uses: actions/checkout@v4
- - name: Setup Node
- uses: actions/setup-node@v4
- with:
- node-version: 20
- cache: npm
- - name: Install
- run: npm ci
- - name: Apply deterministic schema repairs in runner
- run: node script/typescript-schema-repair.mjs
- - name: Verify War Room boundary
- run: npx tsx --test server/war-room.test.ts
- - name: TypeScript check
- run: npm run check
- - name: Production build
- run: npm run build
- - name: Show verified repair diff
- run: git diff --check && git diff --stat
From f8811242dc04df83757d6afa7dba2a2c870e33d4 Mon Sep 17 00:00:00 2001
From: Agens Nihil <60854716+sonoxo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 02:59:03 -0400
Subject: [PATCH 23/23] ci: remove completed TypeScript repair workflow
---
.../apply-typescript-schema-repair.yml | 48 -------------------
1 file changed, 48 deletions(-)
delete mode 100644 .github/workflows/apply-typescript-schema-repair.yml
diff --git a/.github/workflows/apply-typescript-schema-repair.yml b/.github/workflows/apply-typescript-schema-repair.yml
deleted file mode 100644
index 9c363bd..0000000
--- a/.github/workflows/apply-typescript-schema-repair.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-name: Apply Verified TypeScript Schema Repair
-
-on:
- push:
- branches:
- - feature/cyberpunk-war-room
- paths:
- - .github/workflows/apply-typescript-schema-repair.yml
-
-permissions:
- contents: write
-
-jobs:
- apply-verified-repair:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout feature branch
- uses: actions/checkout@v4
- with:
- ref: feature/cyberpunk-war-room
- fetch-depth: 0
- - name: Setup Node
- uses: actions/setup-node@v4
- with:
- node-version: 20
- cache: npm
- - name: Install
- run: npm ci
- - name: Apply deterministic schema repairs
- run: node script/typescript-schema-repair.mjs
- - name: Verify War Room boundary
- run: npx tsx --test server/war-room.test.ts
- - name: TypeScript check
- run: npm run check
- - name: Production build
- run: npm run build
- - name: Commit verified source repairs
- run: |
- git diff --check
- if git diff --quiet -- client server; then
- echo "No source repair required"
- exit 0
- fi
- git config user.name "zyra-ci-repair"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git add client server
- git commit -m "fix: align TypeScript code with current schemas"
- git push origin HEAD:feature/cyberpunk-war-room