[object Object]

← back to Dw Boardroom Governance

initial scaffold (gitify-all 2026-05-06)

1e62bab28c517b6392714f1632c4901a61ff3a3d · 2026-05-06 10:25:14 -0700 · Steve Abrams

Files touched

Diff

commit 1e62bab28c517b6392714f1632c4901a61ff3a3d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 6 10:25:14 2026 -0700

    initial scaffold (gitify-all 2026-05-06)
---
 .gitignore                                  |   15 +
 ecosystem.config.cjs                        |   28 +
 frontend/index.html                         |   20 +
 frontend/package-lock.json                  | 1777 ++++++++++++++++++++++++
 frontend/package.json                       |   21 +
 frontend/serve.cjs                          |   93 ++
 frontend/src/App.tsx                        |   94 ++
 frontend/src/api.ts                         |   60 +
 frontend/src/components/AgendaBuilder.tsx   |  226 ++++
 frontend/src/components/AutonomyControl.tsx |   92 ++
 frontend/src/components/Boardroom.tsx       |  157 +++
 frontend/src/components/DecisionPanel.tsx   |  104 ++
 frontend/src/components/DelegationForm.tsx  |  124 ++
 frontend/src/components/EscalationQueue.tsx |   53 +
 frontend/src/components/Initiatives.tsx     |  114 ++
 frontend/src/components/Ledger.tsx          |   79 ++
 frontend/src/components/TabNav.tsx          |   34 +
 frontend/src/hooks/useApi.ts                |   26 +
 frontend/src/hooks/useWebSocket.ts          |   48 +
 frontend/src/main.tsx                       |    9 +
 frontend/src/types.ts                       |   92 ++
 frontend/tsconfig.json                      |   16 +
 frontend/vite.config.ts                     |   18 +
 package-lock.json                           | 1957 +++++++++++++++++++++++++++
 package.json                                |   31 +
 src/agents/escalation.ts                    |  129 ++
 src/agents/president.ts                     |   57 +
 src/agents/vp.ts                            |   47 +
 src/api/middleware/auth.ts                  |   22 +
 src/api/routes/decisions.ts                 |   89 ++
 src/api/routes/delegations.ts               |   77 ++
 src/api/routes/escalations.ts               |   36 +
 src/api/routes/governance.ts                |   80 ++
 src/api/routes/initiatives.ts               |  109 ++
 src/api/routes/meetings.ts                  |  109 ++
 src/api/server.ts                           |   80 ++
 src/db/db.ts                                |   30 +
 src/db/migrate.ts                           |   37 +
 src/db/schema.sql                           |  117 ++
 src/engine/decisionEngine.ts                |  217 +++
 src/engine/delegationEngine.ts              |  148 ++
 src/engine/meetingEngine.ts                 |  204 +++
 src/types.ts                                |  205 +++
 src/ws/broadcast.ts                         |   53 +
 tsconfig.json                               |   18 +
 45 files changed, 7152 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e21e61d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+node_modules/
+dist/
+frontend/node_modules/
+frontend/dist/
+.env
+*.js.map
+.env.local
+.env.*.local
+.env.*
+tmp/
+*.log
+.DS_Store
+build/
+.next/
+*.bak
diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs
new file mode 100644
index 0000000..f5232a6
--- /dev/null
+++ b/ecosystem.config.cjs
@@ -0,0 +1,28 @@
+module.exports = {
+  apps: [
+    {
+      name: 'dw-governance-api',
+      script: 'dist/api/server.js',
+      cwd: '/root/Projects/dw-boardroom-governance',
+      env: {
+        PORT: 4020,
+        NODE_ENV: 'production',
+        DATABASE_URL: 'postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/dw_unified',
+        AUTH_USER: 'admin',
+        AUTH_PASS: 'DWSecure2024!',
+        FRONTEND_URL: 'http://45.61.58.125:4030',
+      },
+      node_args: '--max-old-space-size=128',
+      max_memory_restart: '150M',
+    },
+    {
+      name: 'dw-governance-ui',
+      script: './serve.cjs',
+      cwd: '/root/Projects/dw-boardroom-governance/frontend',
+      env: {
+        NODE_ENV: 'production',
+      },
+      max_memory_restart: '100M',
+    }
+  ]
+};
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..0d37b77
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta name="theme-color" content="#0a0a12">
+  <title>DW Boardroom Governance</title>
+  <link rel="preconnect" href="https://fonts.googleapis.com">
+  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
+  <style>
+    * { margin: 0; padding: 0; box-sizing: border-box; }
+    body { font-family: 'Inter', -apple-system, sans-serif; background: #0a0a12; color: #e8eaf0; }
+  </style>
+</head>
+<body>
+  <div id="root"></div>
+  <script type="module" src="/src/main.tsx"></script>
+</body>
+</html>
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..4e36e8c
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,1777 @@
+{
+  "name": "dw-governance-ui",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "dw-governance-ui",
+      "version": "1.0.0",
+      "dependencies": {
+        "react": "^18.2.0",
+        "react-dom": "^18.2.0"
+      },
+      "devDependencies": {
+        "@types/react": "^18.2.43",
+        "@types/react-dom": "^18.2.17",
+        "@vitejs/plugin-react": "^4.2.1",
+        "typescript": "^5.3.3",
+        "vite": "^5.0.8"
+      }
+    },
+    "node_modules/@babel/core": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+      "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.29.0",
+        "@babel/generator": "^7.29.0",
+        "@babel/helper-compilation-targets": "^7.28.6",
+        "@babel/helper-module-transforms": "^7.28.6",
+        "@babel/helpers": "^7.28.6",
+        "@babel/parser": "^7.29.0",
+        "@babel/template": "^7.28.6",
+        "@babel/traverse": "^7.29.0",
+        "@babel/types": "^7.29.0",
+        "@jridgewell/remapping": "^2.3.5",
+        "convert-source-map": "^2.0.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/babel"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/code-frame": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+      "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.28.5",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/compat-data": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+      "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/generator": {
+      "version": "7.29.1",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+      "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.29.0",
+        "@babel/types": "^7.29.0",
+        "@jridgewell/gen-mapping": "^0.3.12",
+        "@jridgewell/trace-mapping": "^0.3.28",
+        "jsesc": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/helper-compilation-targets": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+      "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/compat-data": "^7.28.6",
+        "@babel/helper-validator-option": "^7.27.1",
+        "browserslist": "^4.24.0",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/helper-globals": {
+      "version": "7.28.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+      "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/helper-module-imports": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+      "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/traverse": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/helper-module-transforms": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+      "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.28.6",
+        "@babel/helper-validator-identifier": "^7.28.5",
+        "@babel/traverse": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/helper-validator-option": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+      "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/helpers": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+      "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/template": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/parser": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+      "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/template": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+      "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.28.6",
+        "@babel/parser": "^7.28.6",
+        "@babel/types": "^7.28.6"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/traverse": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+      "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.29.0",
+        "@babel/generator": "^7.29.0",
+        "@babel/helper-globals": "^7.28.0",
+        "@babel/parser": "^7.29.0",
+        "@babel/template": "^7.28.6",
+        "@babel/types": "^7.29.0",
+        "debug": "^4.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@babel/types": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.13",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+      "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@jridgewell/remapping": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+      "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@babel/core/node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.31",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+      "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@babel/core/node_modules/baseline-browser-mapping": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
+      "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "baseline-browser-mapping": "dist/cli.cjs"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/browserslist": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+      "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "baseline-browser-mapping": "^2.9.0",
+        "caniuse-lite": "^1.0.30001759",
+        "electron-to-chromium": "^1.5.263",
+        "node-releases": "^2.0.27",
+        "update-browserslist-db": "^1.2.0"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/@babel/core/node_modules/caniuse-lite": {
+      "version": "1.0.30001770",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
+      "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "CC-BY-4.0"
+    },
+    "node_modules/@babel/core/node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@babel/core/node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@babel/core/node_modules/electron-to-chromium": {
+      "version": "1.5.286",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+      "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/@babel/core/node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/@babel/core/node_modules/gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@babel/core/node_modules/jsesc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/@babel/core/node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/@babel/core/node_modules/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/@babel/core/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@babel/core/node_modules/node-releases": {
+      "version": "2.0.27",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+      "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@babel/core/node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/@babel/core/node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/@babel/core/node_modules/update-browserslist-db": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+      "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/@babel/core/node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-self": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+      "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-self/node_modules/@babel/helper-plugin-utils": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+      "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-source": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+      "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-source/node_modules/@babel/helper-plugin-utils": {
+      "version": "7.28.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+      "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-beta.27",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+      "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/babel__core": {
+      "version": "7.20.5",
+      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+      "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.20.7",
+        "@babel/types": "^7.20.7",
+        "@types/babel__generator": "*",
+        "@types/babel__template": "*",
+        "@types/babel__traverse": "*"
+      }
+    },
+    "node_modules/@types/babel__core/node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@types/babel__core/node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@types/babel__core/node_modules/@babel/parser": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+      "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.29.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@types/babel__core/node_modules/@babel/types": {
+      "version": "7.29.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@types/babel__generator": {
+      "dev": true
+    },
+    "node_modules/@types/babel__template": {
+      "dev": true
+    },
+    "node_modules/@types/babel__traverse": {
+      "dev": true
+    },
+    "node_modules/@types/prop-types": {
+      "dev": true
+    },
+    "node_modules/@types/react": {
+      "version": "18.3.28",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+      "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/prop-types": "*",
+        "csstype": "^3.2.2"
+      }
+    },
+    "node_modules/@types/react-dom": {
+      "version": "18.3.7",
+      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+      "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "^18.0.0"
+      }
+    },
+    "node_modules/@vitejs/plugin-react": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+      "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/core": "^7.28.0",
+        "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+        "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+        "@rolldown/pluginutils": "1.0.0-beta.27",
+        "@types/babel__core": "^7.20.5",
+        "react-refresh": "^0.17.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/loose-envify/node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "license": "MIT"
+    },
+    "node_modules/react": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+      "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+      "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.2"
+      },
+      "peerDependencies": {
+        "react": "^18.3.1"
+      }
+    },
+    "node_modules/react-refresh": {
+      "version": "0.17.0",
+      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+      "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.23.2",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+      "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/vite": {
+      "version": "5.4.21",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+      "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.21.3",
+        "postcss": "^8.4.43",
+        "rollup": "^4.20.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || >=20.0.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.4.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/android-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/android-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/android-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/linux-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+      "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@esbuild/win32-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+      "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+      "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+      "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+      "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+      "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+      "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+      "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+      "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+      "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+      "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+      "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-loong64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+      "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+      "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-ppc64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+      "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+      "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+      "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+      "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+      "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+      "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-openbsd-x64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+      "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+      "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+      "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+      "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+      "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/vite/node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+      "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/vite/node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/vite/node_modules/esbuild": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+      "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.21.5",
+        "@esbuild/android-arm": "0.21.5",
+        "@esbuild/android-arm64": "0.21.5",
+        "@esbuild/android-x64": "0.21.5",
+        "@esbuild/darwin-arm64": "0.21.5",
+        "@esbuild/darwin-x64": "0.21.5",
+        "@esbuild/freebsd-arm64": "0.21.5",
+        "@esbuild/freebsd-x64": "0.21.5",
+        "@esbuild/linux-arm": "0.21.5",
+        "@esbuild/linux-arm64": "0.21.5",
+        "@esbuild/linux-ia32": "0.21.5",
+        "@esbuild/linux-loong64": "0.21.5",
+        "@esbuild/linux-mips64el": "0.21.5",
+        "@esbuild/linux-ppc64": "0.21.5",
+        "@esbuild/linux-riscv64": "0.21.5",
+        "@esbuild/linux-s390x": "0.21.5",
+        "@esbuild/linux-x64": "0.21.5",
+        "@esbuild/netbsd-x64": "0.21.5",
+        "@esbuild/openbsd-x64": "0.21.5",
+        "@esbuild/sunos-x64": "0.21.5",
+        "@esbuild/win32-arm64": "0.21.5",
+        "@esbuild/win32-ia32": "0.21.5",
+        "@esbuild/win32-x64": "0.21.5"
+      }
+    },
+    "node_modules/vite/node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/vite/node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/vite/node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/vite/node_modules/postcss": {
+      "version": "8.5.6",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/vite/node_modules/rollup": {
+      "version": "4.57.1",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+      "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.57.1",
+        "@rollup/rollup-android-arm64": "4.57.1",
+        "@rollup/rollup-darwin-arm64": "4.57.1",
+        "@rollup/rollup-darwin-x64": "4.57.1",
+        "@rollup/rollup-freebsd-arm64": "4.57.1",
+        "@rollup/rollup-freebsd-x64": "4.57.1",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+        "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+        "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+        "@rollup/rollup-linux-arm64-musl": "4.57.1",
+        "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+        "@rollup/rollup-linux-loong64-musl": "4.57.1",
+        "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+        "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+        "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+        "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+        "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+        "@rollup/rollup-linux-x64-gnu": "4.57.1",
+        "@rollup/rollup-linux-x64-musl": "4.57.1",
+        "@rollup/rollup-openbsd-x64": "4.57.1",
+        "@rollup/rollup-openharmony-arm64": "4.57.1",
+        "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+        "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+        "@rollup/rollup-win32-x64-gnu": "4.57.1",
+        "@rollup/rollup-win32-x64-msvc": "4.57.1",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/vite/node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    }
+  }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..50660b7
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,21 @@
+{
+  "name": "dw-governance-ui",
+  "version": "1.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite --host",
+    "build": "tsc && vite build",
+    "preview": "vite preview --host"
+  },
+  "dependencies": {
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0"
+  },
+  "devDependencies": {
+    "@types/react": "^18.2.43",
+    "@types/react-dom": "^18.2.17",
+    "@vitejs/plugin-react": "^4.2.1",
+    "typescript": "^5.3.3",
+    "vite": "^5.0.8"
+  }
+}
diff --git a/frontend/serve.cjs b/frontend/serve.cjs
new file mode 100644
index 0000000..896960c
--- /dev/null
+++ b/frontend/serve.cjs
@@ -0,0 +1,93 @@
+// Static server with Basic Auth + API Proxy for Governance UI
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+const PORT = 4030;
+const API_PORT = 4020;
+const USER = 'admin';
+const PASS = 'DWSecure2024!';
+const DIST = path.join(__dirname, 'dist');
+
+const MIME = {
+  '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css',
+  '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg',
+  '.svg': 'image/svg+xml', '.woff2': 'font/woff2', '.woff': 'font/woff',
+};
+
+const server = http.createServer((req, res) => {
+  const auth = req.headers.authorization;
+  if (!auth || !auth.startsWith('Basic ')) {
+    res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="DW Governance"' });
+    return res.end('Unauthorized');
+  }
+  const [u, p] = Buffer.from(auth.slice(6), 'base64').toString().split(':');
+  if (u !== USER || p !== PASS) {
+    res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="DW Governance"' });
+    return res.end('Unauthorized');
+  }
+
+  // ── API Proxy: Forward /api/* requests to backend on port 4020 ──
+  if (req.url.startsWith('/api/')) {
+    const proxyOpts = {
+      hostname: '127.0.0.1',
+      port: API_PORT,
+      path: req.url,
+      method: req.method,
+      headers: { ...req.headers, host: `127.0.0.1:${API_PORT}` },
+    };
+
+    const proxyReq = http.request(proxyOpts, (proxyRes) => {
+      res.writeHead(proxyRes.statusCode, proxyRes.headers);
+      proxyRes.pipe(res);
+    });
+
+    proxyReq.on('error', (err) => {
+      console.error('[Proxy] Error forwarding to API:', err.message);
+      res.writeHead(502, { 'Content-Type': 'application/json' });
+      res.end(JSON.stringify({ error: 'API backend unavailable', detail: err.message }));
+    });
+
+    // Forward request body for POST/PUT/PATCH
+    req.pipe(proxyReq);
+    return;
+  }
+
+  // ── WebSocket upgrade: Forward /ws to backend ──
+  // (handled by 'upgrade' event below)
+
+  // ── Static file serving ──
+  let filePath = path.join(DIST, req.url === '/' ? 'index.html' : req.url);
+  if (!fs.existsSync(filePath)) filePath = path.join(DIST, 'index.html');
+  const ext = path.extname(filePath);
+  const mime = MIME[ext] || 'application/octet-stream';
+  try {
+    const content = fs.readFileSync(filePath);
+    res.writeHead(200, { 'Content-Type': mime });
+    res.end(content);
+  } catch {
+    res.writeHead(404);
+    res.end('Not found');
+  }
+});
+
+// ── WebSocket Proxy: Forward upgrade requests to backend ──
+const net = require('net');
+server.on('upgrade', (req, socket, head) => {
+  if (!req.url.startsWith('/ws')) return socket.destroy();
+
+  const proxy = net.connect(API_PORT, '127.0.0.1', () => {
+    proxy.write(
+      `${req.method} ${req.url} HTTP/1.1\r\n` +
+      Object.entries(req.headers).map(([k, v]) => `${k}: ${v}`).join('\r\n') +
+      '\r\n\r\n'
+    );
+    if (head && head.length) proxy.write(head);
+    socket.pipe(proxy).pipe(socket);
+  });
+
+  proxy.on('error', () => socket.destroy());
+  socket.on('error', () => proxy.destroy());
+});
+
+server.listen(PORT, '0.0.0.0', () => console.log(`[Governance UI] Serving on port ${PORT} with auth + API proxy to ${API_PORT}`));
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 0000000..70d6a55
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,94 @@
+import { useState, useEffect } from 'react';
+import { api } from './api';
+import { useWebSocket } from './hooks/useWebSocket';
+import TabNav from './components/TabNav';
+import Boardroom from './components/Boardroom';
+import DecisionPanel from './components/DecisionPanel';
+import Ledger from './components/Ledger';
+import DelegationForm from './components/DelegationForm';
+import Initiatives from './components/Initiatives';
+import EscalationQueue from './components/EscalationQueue';
+import AutonomyControl from './components/AutonomyControl';
+import type { GovernanceStatus } from './types';
+
+const TABS = ['Meeting Room', 'Decisions', 'Delegation', 'Initiatives', 'Escalations'];
+
+export default function App() {
+  const [activeTab, setActiveTab] = useState(0);
+  const [status, setStatus] = useState<GovernanceStatus | null>(null);
+  const { events, connected } = useWebSocket();
+
+  useEffect(() => {
+    api.getStatus().then(setStatus).catch(() => {});
+    const interval = setInterval(() => {
+      api.getStatus().then(setStatus).catch(() => {});
+    }, 15000);
+    return () => clearInterval(interval);
+  }, []);
+
+  return (
+    <div style={styles.app}>
+      {/* Header */}
+      <header style={styles.header}>
+        <div style={styles.headerLeft}>
+          <div style={styles.logo}>G</div>
+          <div>
+            <h1 style={styles.title}>DW Boardroom Governance</h1>
+            <div style={styles.subtitle}>
+              Decision Engine &bull; Delegation &bull; Escalation
+              <span style={{ ...styles.dot, background: connected ? '#22c55e' : '#ef4444' }} />
+              {connected ? 'Live' : 'Disconnected'}
+              {status && ` | ${status.uptime ? Math.round(status.uptime / 60) + 'm uptime' : ''}`}
+            </div>
+          </div>
+        </div>
+        <div style={styles.headerRight}>
+          {status && (
+            <>
+              <span style={styles.badge}>{status.decisions?.pending || 0} pending</span>
+              <span style={{ ...styles.badge, background: '#7c3aed33', borderColor: '#7c3aed' }}>
+                {status.meeting?.active ? 'Meeting Active' : 'No Meeting'}
+              </span>
+            </>
+          )}
+        </div>
+      </header>
+
+      {/* Tabs */}
+      <TabNav tabs={TABS} active={activeTab} onChange={setActiveTab} />
+
+      {/* Content */}
+      <main style={styles.main}>
+        {activeTab === 0 && <Boardroom events={events} />}
+        {activeTab === 1 && (
+          <div style={styles.split}>
+            <DecisionPanel />
+            <Ledger />
+          </div>
+        )}
+        {activeTab === 2 && <DelegationForm />}
+        {activeTab === 3 && <Initiatives />}
+        {activeTab === 4 && (
+          <div style={styles.split}>
+            <EscalationQueue />
+            <AutonomyControl />
+          </div>
+        )}
+      </main>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  app: { minHeight: '100vh', background: '#0a0a12', color: '#e8eaf0', fontFamily: "'Inter', -apple-system, sans-serif" },
+  header: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 24px', borderBottom: '1px solid #252540', background: '#12121f' },
+  headerLeft: { display: 'flex', alignItems: 'center', gap: 12 },
+  logo: { width: 40, height: 40, borderRadius: 10, background: 'linear-gradient(135deg, #8b5cf6, #6366f1)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 800, fontSize: 20 },
+  title: { margin: 0, fontSize: 20, fontWeight: 700, letterSpacing: '-0.02em' },
+  subtitle: { fontSize: 12, color: '#7a7f96', display: 'flex', alignItems: 'center', gap: 6, marginTop: 2 },
+  dot: { width: 6, height: 6, borderRadius: '50%', display: 'inline-block' },
+  headerRight: { display: 'flex', gap: 8 },
+  badge: { padding: '4px 10px', borderRadius: 6, fontSize: 11, fontWeight: 600, background: '#22c55e22', border: '1px solid #22c55e44', color: '#e8eaf0' },
+  main: { padding: 24, maxWidth: 1400, margin: '0 auto' },
+  split: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 },
+};
diff --git a/frontend/src/api.ts b/frontend/src/api.ts
new file mode 100644
index 0000000..28330af
--- /dev/null
+++ b/frontend/src/api.ts
@@ -0,0 +1,60 @@
+import type { Decision, DelegationContract, Initiative, Meeting, GovernanceStatus } from './types';
+
+const BASE = '';
+const AUTH = btoa('admin:DWSecure2024!');
+
+async function fetchApi<T>(path: string, options?: RequestInit): Promise<T> {
+  const res = await fetch(`${BASE}${path}`, {
+    ...options,
+    headers: {
+      'Authorization': `Basic ${AUTH}`,
+      'Content-Type': 'application/json',
+      ...options?.headers,
+    },
+  });
+  if (!res.ok) {
+    const text = await res.text();
+    throw new Error(`API ${res.status}: ${text}`);
+  }
+  return res.json();
+}
+
+export const api = {
+  // Governance
+  getStatus: () => fetchApi<GovernanceStatus>('/api/governance/status'),
+  getConfig: () => fetchApi<Record<string, any>>('/api/governance/config'),
+  updateConfig: (data: Record<string, any>) => fetchApi<Record<string, any>>('/api/governance/config', { method: 'POST', body: JSON.stringify(data) }),
+
+  // Meetings
+  getMeetings: () => fetchApi<Meeting[]>('/api/meetings'),
+  getActiveMeeting: () => fetchApi<any>('/api/meetings/active'),
+  startMeeting: (type: string, agendaItems?: any[]) => fetchApi<Meeting>(`/api/meetings/${type}`, { method: 'POST', body: JSON.stringify({ agendaItems }) }),
+  haltMeeting: (id: string) => fetchApi<any>(`/api/meetings/${id}/halt`, { method: 'POST' }),
+  advanceMeeting: (id: string) => fetchApi<any>(`/api/meetings/${id}/advance`, { method: 'POST' }),
+  getMeetingMessages: (id: string) => fetchApi<any[]>(`/api/meetings/${id}/messages`),
+
+  // Decisions
+  getDecisions: (status?: string) => fetchApi<Decision[]>(`/api/decisions${status ? `?status=${status}` : ''}`),
+  getDecisionStats: () => fetchApi<Record<string, number>>('/api/decisions/stats'),
+  createDecision: (data: any) => fetchApi<Decision>('/api/decisions', { method: 'POST', body: JSON.stringify(data) }),
+  approveDecision: (id: string, notes?: string) => fetchApi<Decision>(`/api/decisions/${id}/approve`, { method: 'POST', body: JSON.stringify({ notes }) }),
+  rejectDecision: (id: string, notes?: string) => fetchApi<Decision>(`/api/decisions/${id}/reject`, { method: 'POST', body: JSON.stringify({ notes }) }),
+  deferDecision: (id: string, notes?: string) => fetchApi<Decision>(`/api/decisions/${id}/defer`, { method: 'POST', body: JSON.stringify({ notes }) }),
+
+  // Delegations
+  getDelegations: (status?: string) => fetchApi<DelegationContract[]>(`/api/delegations${status ? `?status=${status}` : ''}`),
+  createDelegation: (data: any) => fetchApi<DelegationContract>('/api/delegations', { method: 'POST', body: JSON.stringify(data) }),
+  suggestDelegate: (description: string) => fetchApi<any>('/api/delegations/suggest', { method: 'POST', body: JSON.stringify({ description }) }),
+  completeDelegation: (id: string) => fetchApi<DelegationContract>(`/api/delegations/${id}/complete`, { method: 'POST' }),
+  revokeDelegation: (id: string) => fetchApi<DelegationContract>(`/api/delegations/${id}/revoke`, { method: 'POST' }),
+
+  // Initiatives
+  getInitiatives: () => fetchApi<Initiative[]>('/api/initiatives'),
+  createInitiative: (data: any) => fetchApi<Initiative>('/api/initiatives', { method: 'POST', body: JSON.stringify(data) }),
+  updateInitiative: (id: string, data: any) => fetchApi<Initiative>(`/api/initiatives/${id}`, { method: 'PATCH', body: JSON.stringify(data) }),
+
+  // Escalations
+  getEscalations: () => fetchApi<any[]>('/api/escalations'),
+  getActiveEscalations: () => fetchApi<any[]>('/api/escalations/active'),
+  triggerEscalationCheck: () => fetchApi<any>('/api/escalations/check', { method: 'POST' }),
+};
diff --git a/frontend/src/components/AgendaBuilder.tsx b/frontend/src/components/AgendaBuilder.tsx
new file mode 100644
index 0000000..cd1b594
--- /dev/null
+++ b/frontend/src/components/AgendaBuilder.tsx
@@ -0,0 +1,226 @@
+import { useState } from 'react';
+import { api } from '../api';
+import type { DecisionType } from '../types';
+
+interface AgendaItem {
+  driver: string;
+  topic: string;
+  recommendation: string;
+  decisionType: DecisionType;
+  decisionRequired: boolean;
+}
+
+interface TopicSuggestion {
+  topic: string;
+  recommendation: string;
+  decisionType: DecisionType;
+  source: string;
+}
+
+// Smart topic suggestions based on live system state
+async function fetchSuggestions(): Promise<TopicSuggestion[]> {
+  const suggestions: TopicSuggestion[] = [];
+
+  try {
+    // Pull pending decisions that need discussion
+    const decisions = await api.getDecisions('pending');
+    if (decisions && decisions.length > 0) {
+      decisions.slice(0, 3).forEach(d => {
+        suggestions.push({
+          topic: d.title || d.description?.slice(0, 60) || 'Pending Decision',
+          recommendation: d.description || 'Review and decide',
+          decisionType: (d.decision_type as DecisionType) || 'B',
+          source: 'pending-decision',
+        });
+      });
+    }
+  } catch {}
+
+  try {
+    // Pull active escalations
+    const escalations = await api.getActiveEscalations();
+    if (escalations && escalations.length > 0) {
+      escalations.slice(0, 2).forEach(e => {
+        suggestions.push({
+          topic: `Escalation: ${e.title || e.description?.slice(0, 50) || 'Review needed'}`,
+          recommendation: 'Requires immediate executive attention',
+          decisionType: 'C',
+          source: 'escalation',
+        });
+      });
+    }
+  } catch {}
+
+  try {
+    // Pull initiative status updates
+    const initiatives = await api.getInitiatives();
+    if (initiatives && initiatives.length > 0) {
+      const active = initiatives.filter(i => i.status === 'active' || i.status === 'planning');
+      active.slice(0, 2).forEach(i => {
+        suggestions.push({
+          topic: `Initiative Update: ${i.title || i.name || 'Unnamed'}`,
+          recommendation: `Status: ${i.status} — review progress`,
+          decisionType: 'B',
+          source: 'initiative',
+        });
+      });
+    }
+  } catch {}
+
+  // Always add some standing agenda items if we have room
+  const standing: TopicSuggestion[] = [
+    { topic: 'Agent Performance Review', recommendation: 'Review uptime, task completion, and efficiency metrics', decisionType: 'B', source: 'standing' },
+    { topic: 'Budget & Spend Analysis', recommendation: 'Review API costs, infrastructure spend, ROI', decisionType: 'C', source: 'standing' },
+    { topic: 'Product Pipeline Status', recommendation: 'New SKUs, catalog imports, vendor updates', decisionType: 'B', source: 'standing' },
+    { topic: 'Customer Issues & Zendesk', recommendation: 'Open tickets, response times, escalations', decisionType: 'B', source: 'standing' },
+    { topic: 'Security & Compliance Check', recommendation: 'Review access logs, settlement compliance, data protection', decisionType: 'C', source: 'standing' },
+    { topic: 'Vendor Relationship Updates', recommendation: 'New contracts, pricing changes, catalog access', decisionType: 'B', source: 'standing' },
+    { topic: 'Marketing & SEO Performance', recommendation: 'Traffic trends, conversion rates, campaign results', decisionType: 'A', source: 'standing' },
+    { topic: 'Infrastructure & Uptime Report', recommendation: 'Server health, PM2 processes, error rates', decisionType: 'A', source: 'standing' },
+  ];
+
+  // Add standing items to fill up to 8 total suggestions
+  const remaining = 8 - suggestions.length;
+  if (remaining > 0) {
+    // Shuffle standing items for variety
+    const shuffled = standing.sort(() => Math.random() - 0.5);
+    suggestions.push(...shuffled.slice(0, remaining));
+  }
+
+  return suggestions;
+}
+
+const SOURCE_COLORS: Record<string, { bg: string; label: string; color: string }> = {
+  'pending-decision': { bg: '#f59e0b22', label: 'PENDING', color: '#f59e0b' },
+  'escalation': { bg: '#ef444422', label: 'ESCALATION', color: '#ef4444' },
+  'initiative': { bg: '#8b5cf622', label: 'INITIATIVE', color: '#8b5cf6' },
+  'standing': { bg: '#22c55e22', label: 'SUGGESTED', color: '#22c55e' },
+};
+
+export default function AgendaBuilder({ onRun, disabled }: { onRun: (items: AgendaItem[]) => void; disabled: boolean }) {
+  const [items, setItems] = useState<AgendaItem[]>([]);
+  const [topic, setTopic] = useState('');
+  const [recommendation, setRecommendation] = useState('');
+  const [decisionType, setDecisionType] = useState<DecisionType>('B');
+  const [suggestions, setSuggestions] = useState<TopicSuggestion[]>([]);
+  const [showSuggestions, setShowSuggestions] = useState(false);
+  const [loadingSuggestions, setLoadingSuggestions] = useState(false);
+
+  function addItem() {
+    if (!topic.trim()) return;
+    if (items.length >= 5) { alert('Max 5 agenda items (President rule).'); return; }
+    setItems([...items, { driver: 'operator', topic, recommendation, decisionType, decisionRequired: true }]);
+    setTopic('');
+    setRecommendation('');
+  }
+
+  function addSuggestion(s: TopicSuggestion) {
+    if (items.length >= 5) { alert('Max 5 agenda items.'); return; }
+    setItems([...items, { driver: 'system', topic: s.topic, recommendation: s.recommendation, decisionType: s.decisionType, decisionRequired: true }]);
+    // Remove from suggestions list
+    setSuggestions(prev => prev.filter(x => x !== s));
+  }
+
+  function removeItem(idx: number) {
+    setItems(items.filter((_, i) => i !== idx));
+  }
+
+  async function loadSuggestions() {
+    setLoadingSuggestions(true);
+    setShowSuggestions(true);
+    try {
+      const s = await fetchSuggestions();
+      setSuggestions(s);
+    } catch {
+      setSuggestions([]);
+    }
+    setLoadingSuggestions(false);
+  }
+
+  return (
+    <div>
+      {/* Current agenda items */}
+      {items.map((item, i) => (
+        <div key={i} style={styles.item}>
+          <span style={styles.itemType}>{item.decisionType}</span>
+          <span style={{ flex: 1, fontSize: 12 }}>{item.topic}</span>
+          <button style={styles.removeBtn} onClick={() => removeItem(i)}>x</button>
+        </div>
+      ))}
+
+      {/* Manual topic entry */}
+      <div style={styles.form}>
+        <input style={styles.input} placeholder="Topic" value={topic} onChange={e => setTopic(e.target.value)}
+          onKeyDown={e => { if (e.key === 'Enter') addItem(); }} />
+        <input style={styles.input} placeholder="Recommendation" value={recommendation} onChange={e => setRecommendation(e.target.value)}
+          onKeyDown={e => { if (e.key === 'Enter') addItem(); }} />
+        <select style={styles.select} value={decisionType} onChange={e => setDecisionType(e.target.value as DecisionType)}>
+          <option value="A">A - Autonomous</option>
+          <option value="B">B - Advisory</option>
+          <option value="C">C - Controlled</option>
+        </select>
+        <button style={styles.addBtn} onClick={addItem} disabled={items.length >= 5}>+ Add</button>
+      </div>
+
+      {/* Suggest Topics button */}
+      <div style={{ marginTop: 10 }}>
+        <button style={styles.suggestBtn} onClick={loadSuggestions} disabled={loadingSuggestions}>
+          {loadingSuggestions ? '... Loading' : '💡 Suggest Topics'}
+        </button>
+      </div>
+
+      {/* Topic suggestions panel */}
+      {showSuggestions && suggestions.length > 0 && (
+        <div style={styles.suggestPanel}>
+          <div style={styles.suggestHeader}>
+            <span>Suggested Topics</span>
+            <button style={{ ...styles.removeBtn, fontSize: 12 }} onClick={() => setShowSuggestions(false)}>Close</button>
+          </div>
+          {suggestions.map((s, i) => {
+            const sc = SOURCE_COLORS[s.source] || SOURCE_COLORS.standing;
+            return (
+              <div key={i} style={styles.suggestItem} onClick={() => addSuggestion(s)}>
+                <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
+                  <span style={{ ...styles.sourceBadge, background: sc.bg, color: sc.color, borderColor: sc.color + '44' }}>
+                    {sc.label}
+                  </span>
+                  <span style={{ ...styles.itemType, fontSize: 9 }}>{s.decisionType}</span>
+                  <span style={{ fontSize: 12, fontWeight: 600, color: '#e8eaf0' }}>{s.topic}</span>
+                </div>
+                <div style={{ fontSize: 11, color: '#7a7f96', marginTop: 2, marginLeft: 28 }}>{s.recommendation}</div>
+              </div>
+            );
+          })}
+        </div>
+      )}
+
+      {/* Run button */}
+      <div style={{ marginTop: 12, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+        <span style={{ fontSize: 11, color: '#7a7f96' }}>{items.length}/5 items</span>
+        <button
+          style={{ ...styles.runBtn, opacity: items.length === 0 || disabled ? 0.4 : 1 }}
+          onClick={() => onRun(items)}
+          disabled={items.length === 0 || disabled}
+        >
+          Run Meeting with Agenda
+        </button>
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  item: { display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', background: '#12121f', borderRadius: 6, marginBottom: 4, border: '1px solid #252540' },
+  itemType: { width: 22, height: 22, borderRadius: 4, background: '#8b5cf633', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, fontWeight: 700, color: '#8b5cf6', flexShrink: 0 },
+  removeBtn: { background: 'none', border: 'none', color: '#ef4444', cursor: 'pointer', fontWeight: 700, fontSize: 14, fontFamily: 'inherit' },
+  form: { display: 'flex', gap: 6, marginTop: 12 },
+  input: { flex: 1, padding: '8px 10px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit' },
+  select: { padding: '8px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit' },
+  addBtn: { padding: '8px 14px', borderRadius: 6, border: '1px solid #8b5cf6', background: '#8b5cf633', color: '#e8eaf0', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' },
+  runBtn: { padding: '10px 20px', borderRadius: 8, border: 'none', background: 'linear-gradient(135deg, #8b5cf6, #6366f1)', color: '#fff', fontWeight: 700, fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' },
+  suggestBtn: { padding: '8px 16px', borderRadius: 8, border: '1px solid #f59e0b44', background: '#f59e0b11', color: '#f59e0b', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit', width: '100%' },
+  suggestPanel: { marginTop: 8, padding: 10, background: '#0a0a14', borderRadius: 8, border: '1px solid #252540', maxHeight: 280, overflowY: 'auto' as const },
+  suggestHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, fontSize: 11, fontWeight: 700, color: '#7a7f96', textTransform: 'uppercase' as const, letterSpacing: 0.5 },
+  suggestItem: { padding: '8px 10px', borderRadius: 6, border: '1px solid #252540', marginBottom: 4, cursor: 'pointer', transition: 'background 0.15s', background: '#12121f' },
+  sourceBadge: { display: 'inline-block', padding: '2px 6px', borderRadius: 4, fontSize: 9, fontWeight: 700, letterSpacing: 0.3, border: '1px solid' },
+};
diff --git a/frontend/src/components/AutonomyControl.tsx b/frontend/src/components/AutonomyControl.tsx
new file mode 100644
index 0000000..5c0c447
--- /dev/null
+++ b/frontend/src/components/AutonomyControl.tsx
@@ -0,0 +1,92 @@
+import { useState, useEffect } from 'react';
+import { api } from '../api';
+
+export default function AutonomyControl() {
+  const [config, setConfig] = useState<Record<string, any>>({});
+  const [loading, setLoading] = useState(true);
+
+  useEffect(() => {
+    api.getConfig().then(c => { setConfig(c); setLoading(false); }).catch(() => setLoading(false));
+  }, []);
+
+  async function updateSetting(key: string, value: any) {
+    const updated = { ...config, [key]: value };
+    setConfig(updated);
+    await api.updateConfig({ [key]: value });
+  }
+
+  const mode = (typeof config.governance_mode === 'string' ? config.governance_mode : 'supervised').replace(/"/g, '');
+  const autonomyDefault = Number(config.autonomy_default) || 1;
+  const threshold = Number(config.escalation_threshold_hours) || 12;
+
+  if (loading) return <div style={styles.card}><div style={styles.empty}>Loading config...</div></div>;
+
+  return (
+    <div style={styles.card}>
+      <h3 style={styles.cardTitle}>Governance Controls</h3>
+
+      {/* Mode toggle */}
+      <div style={styles.setting}>
+        <div style={styles.settingLabel}>Governance Mode</div>
+        <div style={styles.toggleRow}>
+          <button
+            style={{ ...styles.modeBtn, ...(mode === 'supervised' ? styles.modeBtnActive : {}) }}
+            onClick={() => updateSetting('governance_mode', 'supervised')}
+          >
+            Supervised
+          </button>
+          <button
+            style={{ ...styles.modeBtn, ...(mode === 'autonomous' ? { ...styles.modeBtnActive, background: '#22c55e33', borderColor: '#22c55e', color: '#22c55e' } : {}) }}
+            onClick={() => updateSetting('governance_mode', 'autonomous')}
+          >
+            Autonomous
+          </button>
+        </div>
+        <div style={styles.hint}>
+          {mode === 'supervised' ? 'All decisions require human approval.' : 'Type A decisions auto-execute at autonomy level 4+.'}
+        </div>
+      </div>
+
+      {/* Default autonomy */}
+      <div style={styles.setting}>
+        <div style={styles.settingLabel}>Default Autonomy Level: {autonomyDefault}</div>
+        <input type="range" min={1} max={5} value={autonomyDefault}
+          onChange={e => updateSetting('autonomy_default', Number(e.target.value))}
+          style={{ width: '100%' }}
+        />
+        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: '#7a7f96' }}>
+          <span>1 — Full oversight</span>
+          <span>5 — Full autonomy</span>
+        </div>
+      </div>
+
+      {/* Escalation threshold */}
+      <div style={styles.setting}>
+        <div style={styles.settingLabel}>Escalation Threshold: {threshold}h</div>
+        <input type="range" min={1} max={48} value={threshold}
+          onChange={e => updateSetting('escalation_threshold_hours', Number(e.target.value))}
+          style={{ width: '100%' }}
+        />
+        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: '#7a7f96' }}>
+          <span>1 hour</span>
+          <span>48 hours</span>
+        </div>
+        <div style={styles.hint}>
+          Pending decisions older than {threshold}h are auto-escalated to Type C (human-required).
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
+  cardTitle: { margin: '0 0 20px', fontSize: 15, fontWeight: 700 },
+  setting: { marginBottom: 24 },
+  settingLabel: { fontSize: 12, fontWeight: 700, marginBottom: 8, color: '#e8eaf0' },
+  toggleRow: { display: 'flex', gap: 8 },
+  modeBtn: { padding: '8px 20px', borderRadius: 8, border: '1px solid #252540', background: 'transparent', color: '#7a7f96', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.2s' },
+  modeBtnActive: { background: '#8b5cf633', borderColor: '#8b5cf6', color: '#8b5cf6' },
+  hint: { fontSize: 11, color: '#7a7f96', marginTop: 6, fontStyle: 'italic' },
+  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 20 },
+};
diff --git a/frontend/src/components/Boardroom.tsx b/frontend/src/components/Boardroom.tsx
new file mode 100644
index 0000000..9e85621
--- /dev/null
+++ b/frontend/src/components/Boardroom.tsx
@@ -0,0 +1,157 @@
+import { useState, useEffect } from 'react';
+import { api } from '../api';
+import AgendaBuilder from './AgendaBuilder';
+import type { Meeting, MeetingMessage, WSEvent } from '../types';
+
+const PHASES = ['Caucus', 'Call to Order', 'Old Business', 'Current Business', 'Breakout', 'New Business', 'Minutes', 'Adjournment'];
+const PHASE_KEYS = ['caucus', 'call_to_order', 'old_business', 'current_business', 'breakout', 'new_business', 'minutes', 'adjournment'];
+
+export default function Boardroom({ events }: { events: WSEvent[] }) {
+  const [meeting, setMeeting] = useState<Meeting | null>(null);
+  const [messages, setMessages] = useState<MeetingMessage[]>([]);
+  const [loading, setLoading] = useState(false);
+
+  useEffect(() => {
+    loadActive();
+  }, []);
+
+  // React to WS events
+  useEffect(() => {
+    const last = events[events.length - 1];
+    if (!last) return;
+    if (last.type === 'meeting_started' || last.type === 'meeting_phase' || last.type === 'meeting_halted' || last.type === 'meeting_completed') {
+      loadActive();
+    }
+    if (last.type === 'meeting_message' && meeting) {
+      setMessages(prev => [...prev, last.payload as MeetingMessage]);
+    }
+  }, [events]);
+
+  async function loadActive() {
+    try {
+      const data = await api.getActiveMeeting();
+      if (data.active) {
+        setMeeting(data.meeting);
+        setMessages(data.messages || []);
+      } else {
+        setMeeting(null);
+        setMessages([]);
+      }
+    } catch {}
+  }
+
+  async function startMeeting(type: string, agendaItems?: any[]) {
+    setLoading(true);
+    try {
+      await api.startMeeting(type, agendaItems);
+      await loadActive();
+    } catch (err: any) {
+      alert(err.message);
+    }
+    setLoading(false);
+  }
+
+  async function halt() {
+    if (!meeting) return;
+    await api.haltMeeting(meeting.id);
+    await loadActive();
+  }
+
+  async function advance() {
+    if (!meeting) return;
+    await api.advanceMeeting(meeting.id);
+    await loadActive();
+  }
+
+  const currentPhaseIdx = meeting ? PHASE_KEYS.indexOf(meeting.phase) : -1;
+
+  return (
+    <div>
+      {/* Phase Bar */}
+      <div style={styles.phaseBar}>
+        {PHASES.map((phase, i) => (
+          <div key={phase} style={{
+            ...styles.phaseItem,
+            background: i === currentPhaseIdx ? '#8b5cf6' : i < currentPhaseIdx ? '#22c55e33' : '#1a1a2e',
+            color: i === currentPhaseIdx ? '#fff' : i < currentPhaseIdx ? '#22c55e' : '#7a7f96',
+            borderColor: i === currentPhaseIdx ? '#8b5cf6' : '#252540',
+          }}>
+            {phase}
+          </div>
+        ))}
+      </div>
+
+      {/* Controls */}
+      <div style={styles.controls}>
+        {!meeting ? (
+          <>
+            <button style={{ ...styles.btn, background: '#8b5cf6' }} onClick={() => startMeeting('strategic')} disabled={loading}>
+              🏛️ Strategic
+            </button>
+            <button style={{ ...styles.btn, background: '#3b82f6' }} onClick={() => startMeeting('ops')} disabled={loading}>
+              ⚙️ Ops
+            </button>
+            <button style={{ ...styles.btn, background: '#f59e0b', color: '#000' }} onClick={() => startMeeting('initiative')} disabled={loading}>
+              🚀 Initiative
+            </button>
+            <button style={{ ...styles.btn, background: '#ef4444' }} onClick={() => startMeeting('emergency')} disabled={loading}>
+              🚨 Emergency
+            </button>
+          </>
+        ) : (
+          <>
+            <button style={{ ...styles.btn, background: '#6366f1' }} onClick={advance}>
+              ⏭️ Next Phase
+            </button>
+            <button style={{ ...styles.btn, background: '#ef4444' }} onClick={halt}>
+              🛑 Halt
+            </button>
+            <span style={styles.meetingLabel}>
+              {meeting.meeting_type.toUpperCase()} — Phase: {PHASES[currentPhaseIdx] || meeting.phase}
+            </span>
+          </>
+        )}
+      </div>
+
+      <div style={styles.split}>
+        {/* Live Feed */}
+        <div style={styles.card}>
+          <h3 style={styles.cardTitle}>Live Feed</h3>
+          <div style={styles.feed}>
+            {messages.length === 0 && <div style={styles.empty}>No messages yet. Start a meeting.</div>}
+            {messages.map((msg, i) => (
+              <div key={i} style={styles.msg}>
+                <span style={styles.msgAgent}>{msg.agent_name}</span>
+                <span style={styles.msgText}>{msg.message}</span>
+                <span style={styles.msgTime}>{new Date(msg.created_at).toLocaleTimeString('en-US', { timeZone: 'America/Los_Angeles', hour: '2-digit', minute: '2-digit' })}</span>
+              </div>
+            ))}
+          </div>
+        </div>
+
+        {/* Agenda Builder */}
+        <div style={styles.card}>
+          <h3 style={styles.cardTitle}>Agenda Builder</h3>
+          <AgendaBuilder onRun={(items: any[]) => startMeeting('strategic', items)} disabled={!!meeting} />
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  phaseBar: { display: 'flex', gap: 4, marginBottom: 16, flexWrap: 'wrap' },
+  phaseItem: { padding: '8px 14px', borderRadius: 6, fontSize: 11, fontWeight: 600, border: '1px solid', transition: 'all 0.3s' },
+  controls: { display: 'flex', gap: 8, marginBottom: 20, alignItems: 'center', flexWrap: 'wrap' },
+  btn: { padding: '8px 16px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' },
+  meetingLabel: { fontSize: 13, fontWeight: 700, color: '#8b5cf6', marginLeft: 12 },
+  split: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 },
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
+  cardTitle: { margin: '0 0 12px', fontSize: 15, fontWeight: 700, color: '#e8eaf0' },
+  feed: { maxHeight: 400, overflowY: 'auto' as const },
+  empty: { color: '#7a7f96', fontSize: 13, padding: 20, textAlign: 'center' as const },
+  msg: { display: 'flex', gap: 8, padding: '6px 0', borderBottom: '1px solid #252540', fontSize: 12, alignItems: 'baseline' },
+  msgAgent: { fontWeight: 700, color: '#8b5cf6', minWidth: 80, flexShrink: 0 },
+  msgText: { flex: 1, color: '#e8eaf0' },
+  msgTime: { color: '#7a7f96', fontSize: 10, flexShrink: 0 },
+};
diff --git a/frontend/src/components/DecisionPanel.tsx b/frontend/src/components/DecisionPanel.tsx
new file mode 100644
index 0000000..59adf50
--- /dev/null
+++ b/frontend/src/components/DecisionPanel.tsx
@@ -0,0 +1,104 @@
+import { useState } from 'react';
+import { api } from '../api';
+import { useApi } from '../hooks/useApi';
+import type { Decision } from '../types';
+
+const SCORE_COLORS: Record<string, string> = {
+  veto: '#ef4444', resolve: '#f59e0b', escalate: '#8b5cf6',
+};
+
+function getScoreLabel(score: number | null): { label: string; color: string } {
+  if (score === null) return { label: '—', color: '#7a7f96' };
+  if (score <= 2) return { label: 'VETO', color: SCORE_COLORS.veto };
+  if (score <= 6) return { label: 'RESOLVE', color: SCORE_COLORS.resolve };
+  return { label: 'ESCALATE', color: SCORE_COLORS.escalate };
+}
+
+export default function DecisionPanel() {
+  const { data: decisions, refresh } = useApi(() => api.getDecisions('pending'), []);
+  const [topic, setTopic] = useState('');
+  const [recommendation, setRecommendation] = useState('');
+  const [decisionType, setDecisionType] = useState('B');
+  const [creating, setCreating] = useState(false);
+
+  async function create() {
+    if (!topic.trim()) return;
+    setCreating(true);
+    try {
+      await api.createDecision({ topic, recommendation, decisionType, owner: 'operator' });
+      setTopic('');
+      setRecommendation('');
+      refresh();
+    } catch (err: any) {
+      alert(err.message);
+    }
+    setCreating(false);
+  }
+
+  async function resolve(id: string, action: 'approve' | 'reject' | 'defer') {
+    const fn = action === 'approve' ? api.approveDecision : action === 'reject' ? api.rejectDecision : api.deferDecision;
+    await fn(id);
+    refresh();
+  }
+
+  return (
+    <div style={styles.card}>
+      <h3 style={styles.cardTitle}>Pending Decisions</h3>
+
+      {/* Create form */}
+      <div style={styles.form}>
+        <input style={styles.input} placeholder="Topic" value={topic} onChange={e => setTopic(e.target.value)} />
+        <input style={styles.input} placeholder="Recommendation" value={recommendation} onChange={e => setRecommendation(e.target.value)} />
+        <select style={styles.select} value={decisionType} onChange={e => setDecisionType(e.target.value)}>
+          <option value="A">A</option><option value="B">B</option><option value="C">C</option>
+        </select>
+        <button style={styles.createBtn} onClick={create} disabled={creating}>+ Create</button>
+      </div>
+
+      {/* Decision list */}
+      <div style={styles.list}>
+        {(!decisions || decisions.length === 0) && <div style={styles.empty}>No pending decisions.</div>}
+        {decisions?.map((d: Decision) => {
+          const { label, color } = getScoreLabel(d.score);
+          return (
+            <div key={d.id} style={styles.decision}>
+              <div style={styles.decisionHeader}>
+                <span style={{ ...styles.scoreBadge, background: color + '22', color, borderColor: color }}>
+                  {d.score ?? '?'} {label}
+                </span>
+                <span style={styles.typeBadge}>{d.decision_type}</span>
+                <span style={{ flex: 1, fontWeight: 600, fontSize: 13 }}>{d.topic}</span>
+              </div>
+              {d.recommendation && <div style={styles.rec}>{d.recommendation}</div>}
+              {d.reasoning && <div style={styles.reasoning}>{d.reasoning}</div>}
+              <div style={styles.actions}>
+                <button style={{ ...styles.actionBtn, background: '#22c55e33', color: '#22c55e' }} onClick={() => resolve(d.id, 'approve')}>Approve</button>
+                <button style={{ ...styles.actionBtn, background: '#ef444433', color: '#ef4444' }} onClick={() => resolve(d.id, 'reject')}>Reject</button>
+                <button style={{ ...styles.actionBtn, background: '#3b82f633', color: '#3b82f6' }} onClick={() => resolve(d.id, 'defer')}>Defer</button>
+              </div>
+            </div>
+          );
+        })}
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
+  cardTitle: { margin: '0 0 12px', fontSize: 15, fontWeight: 700 },
+  form: { display: 'flex', gap: 6, marginBottom: 16 },
+  input: { flex: 1, padding: '8px 10px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit' },
+  select: { padding: '8px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit' },
+  createBtn: { padding: '8px 14px', borderRadius: 6, border: 'none', background: '#8b5cf6', color: '#fff', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' },
+  list: { display: 'flex', flexDirection: 'column', gap: 8 },
+  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 20 },
+  decision: { background: '#12121f', borderRadius: 8, padding: 12, border: '1px solid #252540' },
+  decisionHeader: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 6 },
+  scoreBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 700, border: '1px solid' },
+  typeBadge: { padding: '2px 6px', borderRadius: 4, fontSize: 10, fontWeight: 700, background: '#3b82f622', color: '#3b82f6' },
+  rec: { fontSize: 12, color: '#b0b3c0', marginBottom: 4, paddingLeft: 4 },
+  reasoning: { fontSize: 11, color: '#7a7f96', marginBottom: 8, fontStyle: 'italic', paddingLeft: 4 },
+  actions: { display: 'flex', gap: 6 },
+  actionBtn: { padding: '6px 14px', borderRadius: 6, border: 'none', fontWeight: 600, fontSize: 11, cursor: 'pointer', fontFamily: 'inherit' },
+};
diff --git a/frontend/src/components/DelegationForm.tsx b/frontend/src/components/DelegationForm.tsx
new file mode 100644
index 0000000..e5e35a5
--- /dev/null
+++ b/frontend/src/components/DelegationForm.tsx
@@ -0,0 +1,124 @@
+import { useState } from 'react';
+import { api } from '../api';
+import { useApi } from '../hooks/useApi';
+import type { DelegationContract } from '../types';
+
+export default function DelegationForm() {
+  const { data: delegations, refresh } = useApi(() => api.getDelegations(), []);
+  const [owner, setOwner] = useState('');
+  const [delegate, setDelegate] = useState('');
+  const [autonomy, setAutonomy] = useState(2);
+  const [metric, setMetric] = useState('');
+  const [suggestion, setSuggestion] = useState<any>(null);
+  const [searchText, setSearchText] = useState('');
+
+  async function create() {
+    if (!owner || !delegate) return;
+    try {
+      await api.createDelegation({ owner, delegate, autonomyLevel: autonomy, metric: metric || undefined });
+      setOwner(''); setDelegate(''); setMetric('');
+      refresh();
+    } catch (err: any) {
+      alert(err.message);
+    }
+  }
+
+  async function suggest() {
+    if (!searchText) return;
+    const result = await api.suggestDelegate(searchText);
+    setSuggestion(result);
+    if (result.suggestedDelegate) setDelegate(result.suggestedDelegate);
+  }
+
+  async function revoke(id: string) {
+    await api.revokeDelegation(id);
+    refresh();
+  }
+
+  return (
+    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
+      {/* Create Form */}
+      <div style={styles.card}>
+        <h3 style={styles.cardTitle}>Create Delegation</h3>
+
+        {/* Keyword routing preview */}
+        <div style={{ marginBottom: 16 }}>
+          <div style={{ display: 'flex', gap: 6 }}>
+            <input style={styles.input} placeholder="Describe the task for auto-routing..." value={searchText} onChange={e => setSearchText(e.target.value)} />
+            <button style={styles.suggestBtn} onClick={suggest}>Suggest</button>
+          </div>
+          {suggestion && (
+            <div style={{ marginTop: 8, padding: 10, background: '#12121f', borderRadius: 6, fontSize: 12, border: '1px solid #252540' }}>
+              <div>Suggested: <strong style={{ color: '#8b5cf6' }}>{suggestion.suggestedDelegate || 'None'}</strong></div>
+              <div style={{ color: '#7a7f96', fontSize: 11 }}>{suggestion.reasoning}</div>
+              {suggestion.hierarchyPath && <div style={{ color: '#7a7f96', fontSize: 11 }}>Path: {suggestion.hierarchyPath}</div>}
+            </div>
+          )}
+        </div>
+
+        <div style={styles.formGrid}>
+          <label style={styles.label}>
+            Owner
+            <input style={styles.input} value={owner} onChange={e => setOwner(e.target.value)} placeholder="e.g. ceo, cfo" />
+          </label>
+          <label style={styles.label}>
+            Delegate
+            <input style={styles.input} value={delegate} onChange={e => setDelegate(e.target.value)} placeholder="e.g. frank, max" />
+          </label>
+          <label style={styles.label}>
+            Autonomy Level: {autonomy}
+            <input type="range" min={1} max={5} value={autonomy} onChange={e => setAutonomy(Number(e.target.value))} style={{ width: '100%' }} />
+            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: '#7a7f96' }}>
+              <span>1 Supervised</span><span>5 Autonomous</span>
+            </div>
+          </label>
+          <label style={styles.label}>
+            Success Metric
+            <input style={styles.input} value={metric} onChange={e => setMetric(e.target.value)} placeholder="KPI or completion criteria" />
+          </label>
+        </div>
+
+        <button style={styles.createBtn} onClick={create}>Create Delegation Contract</button>
+      </div>
+
+      {/* Active Contracts */}
+      <div style={styles.card}>
+        <h3 style={styles.cardTitle}>Active Contracts</h3>
+        <div style={styles.list}>
+          {(!delegations || delegations.length === 0) && <div style={styles.empty}>No delegations.</div>}
+          {delegations?.map((d: DelegationContract) => (
+            <div key={d.id} style={styles.contract}>
+              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+                <span style={{ fontWeight: 600, fontSize: 13 }}>{d.owner} → {d.delegate}</span>
+                <span style={{ ...styles.statusBadge, background: d.status === 'active' ? '#22c55e22' : '#7a7f9622', color: d.status === 'active' ? '#22c55e' : '#7a7f96' }}>{d.status}</span>
+              </div>
+              <div style={{ fontSize: 11, color: '#7a7f96', marginTop: 4 }}>
+                Autonomy: {'★'.repeat(d.autonomy_level)}{'☆'.repeat(5 - d.autonomy_level)}
+                {d.hierarchy_path && ` | ${d.hierarchy_path}`}
+              </div>
+              {d.metric && <div style={{ fontSize: 11, color: '#b0b3c0', marginTop: 2 }}>Metric: {d.metric}</div>}
+              {d.status === 'active' && (
+                <button style={styles.revokeBtn} onClick={() => revoke(d.id)}>Revoke</button>
+              )}
+            </div>
+          ))}
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
+  cardTitle: { margin: '0 0 16px', fontSize: 15, fontWeight: 700 },
+  formGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 16 },
+  label: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 11, fontWeight: 600, color: '#7a7f96' },
+  input: { padding: '8px 10px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit', flex: 1 },
+  suggestBtn: { padding: '8px 14px', borderRadius: 6, border: 'none', background: '#6366f1', color: '#fff', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' },
+  createBtn: { padding: '10px 20px', borderRadius: 8, border: 'none', background: 'linear-gradient(135deg, #8b5cf6, #6366f1)', color: '#fff', fontWeight: 700, fontSize: 13, cursor: 'pointer', width: '100%', fontFamily: 'inherit' },
+  list: { display: 'flex', flexDirection: 'column', gap: 8 },
+  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 20 },
+  contract: { background: '#12121f', borderRadius: 8, padding: 12, border: '1px solid #252540' },
+  statusBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
+  revokeBtn: { marginTop: 8, padding: '4px 12px', borderRadius: 4, border: '1px solid #ef4444', background: '#ef444422', color: '#ef4444', fontSize: 11, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' },
+};
diff --git a/frontend/src/components/EscalationQueue.tsx b/frontend/src/components/EscalationQueue.tsx
new file mode 100644
index 0000000..df64ae9
--- /dev/null
+++ b/frontend/src/components/EscalationQueue.tsx
@@ -0,0 +1,53 @@
+import { api } from '../api';
+import { useApi } from '../hooks/useApi';
+
+export default function EscalationQueue() {
+  const { data: escalations, refresh } = useApi(() => api.getActiveEscalations(), []);
+
+  async function triggerCheck() {
+    await api.triggerEscalationCheck();
+    refresh();
+  }
+
+  return (
+    <div style={styles.card}>
+      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
+        <h3 style={styles.cardTitle}>Escalation Queue</h3>
+        <button style={styles.checkBtn} onClick={triggerCheck}>Run Check</button>
+      </div>
+
+      <div style={styles.list}>
+        {(!escalations || escalations.length === 0) && (
+          <div style={styles.empty}>No active escalations. Decisions past the threshold will appear here.</div>
+        )}
+        {escalations?.map((e: any) => (
+          <div key={e.id} style={styles.item}>
+            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+              <span style={{ fontWeight: 600, fontSize: 13 }}>{e.topic || 'Unknown topic'}</span>
+              <span style={styles.timeBadge}>{e.hours_elapsed}h elapsed</span>
+            </div>
+            <div style={{ fontSize: 11, color: '#7a7f96', marginTop: 4 }}>
+              Owner: {e.owner} | Type: {e.escalation_type} | Action: {e.action_taken}
+            </div>
+            {e.decision_status && (
+              <span style={{ ...styles.statusBadge, marginTop: 6, display: 'inline-block' }}>
+                Decision: {e.decision_status}
+              </span>
+            )}
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
+  cardTitle: { margin: 0, fontSize: 15, fontWeight: 700 },
+  checkBtn: { padding: '6px 14px', borderRadius: 6, border: 'none', background: '#ef444433', color: '#ef4444', fontWeight: 600, fontSize: 11, cursor: 'pointer', fontFamily: 'inherit' },
+  list: { display: 'flex', flexDirection: 'column', gap: 8 },
+  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 30 },
+  item: { background: '#12121f', borderRadius: 8, padding: 12, border: '1px solid #ef444433' },
+  timeBadge: { padding: '2px 10px', borderRadius: 4, fontSize: 11, fontWeight: 700, background: '#ef444422', color: '#ef4444' },
+  statusBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600, background: '#f59e0b22', color: '#f59e0b' },
+};
diff --git a/frontend/src/components/Initiatives.tsx b/frontend/src/components/Initiatives.tsx
new file mode 100644
index 0000000..e021820
--- /dev/null
+++ b/frontend/src/components/Initiatives.tsx
@@ -0,0 +1,114 @@
+import { useState } from 'react';
+import { api } from '../api';
+import { useApi } from '../hooks/useApi';
+import type { Initiative, RiskLevel } from '../types';
+
+const RISK_COLORS: Record<string, string> = { low: '#22c55e', medium: '#f59e0b', high: '#ef4444', critical: '#dc2626' };
+const STATUS_COLORS: Record<string, string> = { proposed: '#3b82f6', active: '#22c55e', paused: '#f59e0b', completed: '#7a7f96', cancelled: '#ef4444' };
+
+export default function Initiatives() {
+  const { data: initiatives, refresh } = useApi(() => api.getInitiatives(), []);
+  const [title, setTitle] = useState('');
+  const [driver, setDriver] = useState('');
+  const [kpi, setKpi] = useState('');
+  const [risk, setRisk] = useState<RiskLevel>('low');
+  const [showForm, setShowForm] = useState(false);
+
+  async function create() {
+    if (!title.trim()) return;
+    await api.createInitiative({ title, driverAgent: driver || 'unassigned', kpi, riskLevel: risk });
+    setTitle(''); setDriver(''); setKpi('');
+    setShowForm(false);
+    refresh();
+  }
+
+  async function updateStatus(id: string, status: string) {
+    await api.updateInitiative(id, { status });
+    refresh();
+  }
+
+  async function updateProgress(id: string, progress: number) {
+    await api.updateInitiative(id, { progress });
+    refresh();
+  }
+
+  return (
+    <div style={styles.wrapper}>
+      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
+        <h3 style={{ margin: 0, fontSize: 18, fontWeight: 700 }}>Initiative Registry</h3>
+        <button style={styles.addBtn} onClick={() => setShowForm(!showForm)}>
+          {showForm ? 'Cancel' : '+ New Initiative'}
+        </button>
+      </div>
+
+      {showForm && (
+        <div style={styles.form}>
+          <input style={styles.input} placeholder="Initiative title" value={title} onChange={e => setTitle(e.target.value)} />
+          <input style={styles.input} placeholder="Driver agent" value={driver} onChange={e => setDriver(e.target.value)} />
+          <input style={styles.input} placeholder="KPI / Success metric" value={kpi} onChange={e => setKpi(e.target.value)} />
+          <select style={styles.select} value={risk} onChange={e => setRisk(e.target.value as RiskLevel)}>
+            <option value="low">Low Risk</option><option value="medium">Medium</option><option value="high">High</option><option value="critical">Critical</option>
+          </select>
+          <button style={styles.createBtn} onClick={create}>Create</button>
+        </div>
+      )}
+
+      <div style={styles.grid}>
+        {(!initiatives || initiatives.length === 0) && <div style={styles.empty}>No initiatives. Create one to start tracking.</div>}
+        {initiatives?.map((init: Initiative) => (
+          <div key={init.id} style={styles.card}>
+            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
+              <span style={{ ...styles.riskBadge, background: RISK_COLORS[init.risk_level] + '22', color: RISK_COLORS[init.risk_level] }}>
+                {init.risk_level}
+              </span>
+              <span style={{ ...styles.statusBadge, background: STATUS_COLORS[init.status] + '22', color: STATUS_COLORS[init.status] }}>
+                {init.status}
+              </span>
+            </div>
+            <h4 style={{ margin: '0 0 4px', fontSize: 14, fontWeight: 700 }}>{init.title}</h4>
+            <div style={{ fontSize: 11, color: '#7a7f96', marginBottom: 8 }}>
+              Driver: {init.driver_agent} {init.kpi && `| KPI: ${init.kpi}`}
+            </div>
+
+            {/* Progress bar */}
+            <div style={styles.progressTrack}>
+              <div style={{ ...styles.progressFill, width: `${init.progress}%` }} />
+            </div>
+            <div style={{ fontSize: 10, color: '#7a7f96', marginTop: 4, display: 'flex', justifyContent: 'space-between' }}>
+              <span>{init.progress}%</span>
+              <input type="range" min={0} max={100} value={init.progress} style={{ width: 100 }}
+                onChange={e => updateProgress(init.id, Number(e.target.value))} />
+            </div>
+
+            {/* Status actions */}
+            <div style={{ display: 'flex', gap: 4, marginTop: 8, flexWrap: 'wrap' }}>
+              {['proposed', 'active', 'paused', 'completed'].map(s => (
+                <button key={s} onClick={() => updateStatus(init.id, s)}
+                  style={{ ...styles.miniBtn, opacity: init.status === s ? 1 : 0.5 }}>
+                  {s}
+                </button>
+              ))}
+            </div>
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  wrapper: {},
+  addBtn: { padding: '8px 16px', borderRadius: 8, border: 'none', background: '#8b5cf6', color: '#fff', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' },
+  form: { display: 'flex', gap: 8, marginBottom: 16, flexWrap: 'wrap' },
+  input: { padding: '8px 10px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit', flex: 1, minWidth: 150 },
+  select: { padding: '8px', borderRadius: 6, border: '1px solid #252540', background: '#0a0a12', color: '#e8eaf0', fontSize: 12, fontFamily: 'inherit' },
+  createBtn: { padding: '8px 16px', borderRadius: 6, border: 'none', background: '#22c55e', color: '#fff', fontWeight: 600, fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' },
+  grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 16 },
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 16, border: '1px solid #252540' },
+  riskBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 700, textTransform: 'uppercase' as const },
+  statusBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
+  progressTrack: { height: 6, background: '#252540', borderRadius: 3, overflow: 'hidden' },
+  progressFill: { height: '100%', background: 'linear-gradient(90deg, #8b5cf6, #22c55e)', borderRadius: 3, transition: 'width 0.3s' },
+  miniBtn: { padding: '3px 8px', borderRadius: 4, border: '1px solid #252540', background: 'transparent', color: '#7a7f96', fontSize: 10, cursor: 'pointer', fontFamily: 'inherit', textTransform: 'capitalize' as const },
+  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 40, gridColumn: '1 / -1' },
+};
diff --git a/frontend/src/components/Ledger.tsx b/frontend/src/components/Ledger.tsx
new file mode 100644
index 0000000..f67a584
--- /dev/null
+++ b/frontend/src/components/Ledger.tsx
@@ -0,0 +1,79 @@
+import { useState } from 'react';
+import { api } from '../api';
+import { useApi } from '../hooks/useApi';
+import type { Decision } from '../types';
+
+const STATUS_COLORS: Record<string, string> = {
+  pending: '#f59e0b', approved: '#22c55e', rejected: '#ef4444',
+  modified: '#3b82f6', deferred: '#7a7f96', auto_approved: '#06b6d4',
+};
+
+export default function Ledger() {
+  const [filter, setFilter] = useState('');
+  const { data: decisions, refresh } = useApi(() => api.getDecisions(), []);
+
+  const filtered = (decisions || []).filter((d: Decision) =>
+    !filter || d.status === filter
+  );
+
+  return (
+    <div style={styles.card}>
+      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
+        <h3 style={styles.cardTitle}>Decision Ledger</h3>
+        <div style={{ display: 'flex', gap: 4 }}>
+          <button style={{ ...styles.filterBtn, ...(filter === '' ? styles.activeFilter : {}) }} onClick={() => setFilter('')}>All</button>
+          {Object.keys(STATUS_COLORS).map(s => (
+            <button key={s} style={{ ...styles.filterBtn, ...(filter === s ? styles.activeFilter : {}) }} onClick={() => setFilter(s)}>
+              {s.replace('_', ' ')}
+            </button>
+          ))}
+        </div>
+      </div>
+
+      <div style={styles.table}>
+        <div style={styles.tableHeader}>
+          <span style={{ width: 140 }}>Date</span>
+          <span style={{ flex: 1 }}>Topic</span>
+          <span style={{ width: 30 }}>Type</span>
+          <span style={{ width: 40 }}>Score</span>
+          <span style={{ width: 90 }}>Status</span>
+          <span style={{ width: 80 }}>Owner</span>
+        </div>
+        {filtered.length === 0 && <div style={styles.empty}>No decisions found.</div>}
+        {filtered.map((d: Decision) => (
+          <div key={d.id} style={styles.row}>
+            <span style={{ width: 140, fontSize: 10, color: '#7a7f96' }}>
+              {new Date(d.created_at).toLocaleString('en-US', { timeZone: 'America/Los_Angeles', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
+            </span>
+            <span style={{ flex: 1, fontSize: 12, fontWeight: 500 }}>{d.topic}</span>
+            <span style={{ width: 30, fontSize: 11, fontWeight: 700, color: '#8b5cf6' }}>{d.decision_type}</span>
+            <span style={{ width: 40, fontSize: 11, fontWeight: 700 }}>{d.score ?? '—'}</span>
+            <span style={{ width: 90 }}>
+              <span style={{ ...styles.statusBadge, background: (STATUS_COLORS[d.status] || '#7a7f96') + '22', color: STATUS_COLORS[d.status] }}>
+                {d.status.replace('_', ' ')}
+              </span>
+            </span>
+            <span style={{ width: 80, fontSize: 11, color: '#7a7f96' }}>{d.owner}</span>
+          </div>
+        ))}
+      </div>
+
+      <div style={{ marginTop: 8, fontSize: 11, color: '#7a7f96' }}>
+        {filtered.length} decision(s) {filter && `(${filter})`}
+        <button style={{ ...styles.filterBtn, marginLeft: 8 }} onClick={refresh}>Refresh</button>
+      </div>
+    </div>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
+  cardTitle: { margin: 0, fontSize: 15, fontWeight: 700 },
+  filterBtn: { padding: '3px 8px', borderRadius: 4, border: '1px solid #252540', background: 'transparent', color: '#7a7f96', fontSize: 10, cursor: 'pointer', fontFamily: 'inherit', textTransform: 'capitalize' as const },
+  activeFilter: { background: '#8b5cf633', borderColor: '#8b5cf6', color: '#e8eaf0' },
+  table: { display: 'flex', flexDirection: 'column', gap: 2 },
+  tableHeader: { display: 'flex', gap: 8, padding: '8px 10px', fontSize: 10, fontWeight: 700, color: '#7a7f96', textTransform: 'uppercase' as const, borderBottom: '1px solid #252540' },
+  row: { display: 'flex', gap: 8, padding: '8px 10px', alignItems: 'center', borderBottom: '1px solid #1a1a2e', background: '#12121f', borderRadius: 4 },
+  statusBadge: { padding: '2px 8px', borderRadius: 4, fontSize: 10, fontWeight: 600 },
+  empty: { color: '#7a7f96', fontSize: 12, padding: 20, textAlign: 'center' as const },
+};
diff --git a/frontend/src/components/TabNav.tsx b/frontend/src/components/TabNav.tsx
new file mode 100644
index 0000000..aeaacbb
--- /dev/null
+++ b/frontend/src/components/TabNav.tsx
@@ -0,0 +1,34 @@
+const ICONS = ['🏛️', '⚖️', '🤝', '🚀', '🚨'];
+
+export default function TabNav({ tabs, active, onChange }: {
+  tabs: string[];
+  active: number;
+  onChange: (i: number) => void;
+}) {
+  return (
+    <nav style={styles.nav}>
+      {tabs.map((tab, i) => (
+        <button
+          key={tab}
+          onClick={() => onChange(i)}
+          style={{
+            ...styles.tab,
+            ...(active === i ? styles.activeTab : {}),
+          }}
+        >
+          <span>{ICONS[i]}</span> {tab}
+        </button>
+      ))}
+    </nav>
+  );
+}
+
+const styles: Record<string, React.CSSProperties> = {
+  nav: { display: 'flex', gap: 2, padding: '0 24px', background: '#12121f', borderBottom: '1px solid #252540' },
+  tab: {
+    padding: '12px 20px', border: 'none', background: 'transparent', color: '#7a7f96',
+    fontSize: 13, fontWeight: 600, cursor: 'pointer', borderBottom: '2px solid transparent',
+    transition: 'all 0.2s', display: 'flex', gap: 6, alignItems: 'center', fontFamily: 'inherit',
+  },
+  activeTab: { color: '#e8eaf0', borderBottomColor: '#8b5cf6', background: '#1a1a2e' },
+};
diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts
new file mode 100644
index 0000000..5f846f4
--- /dev/null
+++ b/frontend/src/hooks/useApi.ts
@@ -0,0 +1,26 @@
+import { useState, useEffect, useCallback } from 'react';
+
+export function useApi<T>(fetcher: () => Promise<T>, deps: any[] = []) {
+  const [data, setData] = useState<T | null>(null);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  const refresh = useCallback(async () => {
+    setLoading(true);
+    setError(null);
+    try {
+      const result = await fetcher();
+      setData(result);
+    } catch (err: any) {
+      setError(err.message);
+    } finally {
+      setLoading(false);
+    }
+  }, deps);
+
+  useEffect(() => {
+    refresh();
+  }, [refresh]);
+
+  return { data, loading, error, refresh, setData };
+}
diff --git a/frontend/src/hooks/useWebSocket.ts b/frontend/src/hooks/useWebSocket.ts
new file mode 100644
index 0000000..dcce50d
--- /dev/null
+++ b/frontend/src/hooks/useWebSocket.ts
@@ -0,0 +1,48 @@
+import { useState, useEffect, useRef } from 'react';
+import type { WSEvent } from '../types';
+
+export function useWebSocket() {
+  const [events, setEvents] = useState<WSEvent[]>([]);
+  const [connected, setConnected] = useState(false);
+  const wsRef = useRef<WebSocket | null>(null);
+
+  useEffect(() => {
+    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+    const wsUrl = `${protocol}//${window.location.host}/ws`;
+
+    function connect() {
+      const ws = new WebSocket(wsUrl);
+      wsRef.current = ws;
+
+      ws.onopen = () => {
+        setConnected(true);
+      };
+
+      ws.onmessage = (e) => {
+        try {
+          const event: WSEvent = JSON.parse(e.data);
+          setEvents(prev => [...prev.slice(-99), event]);
+        } catch {}
+      };
+
+      ws.onclose = () => {
+        setConnected(false);
+        setTimeout(connect, 3000);
+      };
+
+      ws.onerror = () => {
+        ws.close();
+      };
+    }
+
+    connect();
+
+    return () => {
+      if (wsRef.current) {
+        wsRef.current.close();
+      }
+    };
+  }, []);
+
+  return { events, connected };
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 0000000..9707d82
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>
+);
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
new file mode 100644
index 0000000..8e4b72a
--- /dev/null
+++ b/frontend/src/types.ts
@@ -0,0 +1,92 @@
+export type DecisionType = 'A' | 'B' | 'C';
+export type DecisionStatus = 'pending' | 'approved' | 'rejected' | 'modified' | 'deferred' | 'auto_approved';
+export type HumanDecision = 'approve' | 'reject' | 'modify' | 'defer';
+export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
+export type DelegationStatus = 'active' | 'completed' | 'revoked' | 'expired';
+export type InitiativeStatus = 'proposed' | 'active' | 'paused' | 'completed' | 'cancelled';
+export type MeetingStatus = 'scheduled' | 'in_progress' | 'completed' | 'halted' | 'crashed';
+
+export interface Decision {
+  id: string;
+  topic: string;
+  decision_type: DecisionType;
+  recommendation: string | null;
+  human_decision: HumanDecision | null;
+  owner: string;
+  autonomy_level: number;
+  deadline: string | null;
+  status: DecisionStatus;
+  source: string | null;
+  score: number | null;
+  reasoning: string | null;
+  notes: string | null;
+  created_at: string;
+  resolved_at: string | null;
+}
+
+export interface DelegationContract {
+  id: string;
+  decision_id: string | null;
+  owner: string;
+  delegate: string;
+  autonomy_level: number;
+  metric: string | null;
+  checkpoint_date: string | null;
+  status: DelegationStatus;
+  routing_keywords: string[] | null;
+  hierarchy_path: string | null;
+  created_at: string;
+}
+
+export interface Initiative {
+  id: string;
+  title: string;
+  description: string | null;
+  driver_agent: string;
+  kpi: string | null;
+  risk_level: RiskLevel;
+  capital_allocated: number;
+  status: InitiativeStatus;
+  progress: number;
+  created_at: string;
+}
+
+export interface Meeting {
+  id: string;
+  meeting_type: string;
+  phase: string;
+  started_at: string | null;
+  ended_at: string | null;
+  status: MeetingStatus;
+  attendees: string[];
+  summary: string | null;
+  messages?: MeetingMessage[];
+  created_at: string;
+}
+
+export interface MeetingMessage {
+  id: number;
+  meeting_id: string;
+  agent_id: string;
+  agent_name: string;
+  message: string;
+  message_type: string;
+  created_at: string;
+}
+
+export interface WSEvent {
+  type: string;
+  payload: any;
+  timestamp: string;
+}
+
+export interface GovernanceStatus {
+  meeting: { active: boolean; activeMeetingId: string | null };
+  decisions: Record<string, number>;
+  delegations: Record<string, number>;
+  initiatives: Record<string, number>;
+  meetings: Record<string, number>;
+  config: Record<string, any>;
+  websocket: { connections: number };
+  uptime: number;
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..92f93be
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react-jsx",
+    "strict": true
+  },
+  "include": ["src"]
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 0000000..360e36d
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,18 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+  plugins: [react()],
+  server: {
+    port: 4030,
+    host: '0.0.0.0',
+    proxy: {
+      '/api': 'http://localhost:4020',
+      '/ws': { target: 'ws://localhost:4020', ws: true }
+    }
+  },
+  preview: {
+    port: 4030,
+    host: '0.0.0.0'
+  }
+});
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..bf9c8fa
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1957 @@
+{
+  "name": "dw-boardroom-governance",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "dw-boardroom-governance",
+      "version": "1.0.0",
+      "dependencies": {
+        "cors": "^2.8.5",
+        "dotenv": "^16.3.1",
+        "express": "^4.18.2",
+        "pg": "^8.18.0",
+        "uuid": "^9.0.0",
+        "ws": "^8.16.0"
+      },
+      "devDependencies": {
+        "@types/cors": "^2.8.17",
+        "@types/express": "^4.17.21",
+        "@types/node": "^20.10.0",
+        "@types/pg": "^8.10.9",
+        "@types/uuid": "^9.0.7",
+        "@types/ws": "^8.5.10",
+        "ts-node": "^10.9.2",
+        "ts-node-dev": "^2.0.0",
+        "typescript": "^5.3.3"
+      }
+    },
+    "node_modules/@cspotcode/source-map-support": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/trace-mapping": "0.3.9"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.0.3",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
+    "node_modules/@tsconfig/node10": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
+      "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@tsconfig/node12": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@tsconfig/node14": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@tsconfig/node16": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+      "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/body-parser": {
+      "version": "1.19.6",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+      "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/connect": {
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/cors": {
+      "version": "2.8.19",
+      "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+      "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/express": {
+      "version": "4.17.25",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
+      "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^4.17.33",
+        "@types/qs": "*",
+        "@types/serve-static": "^1"
+      }
+    },
+    "node_modules/@types/express-serve-static-core": {
+      "version": "4.19.8",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz",
+      "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*",
+        "@types/send": "*"
+      }
+    },
+    "node_modules/@types/http-errors": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+      "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/mime": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+      "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "20.19.33",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
+      "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.21.0"
+      }
+    },
+    "node_modules/@types/pg": {
+      "version": "8.16.0",
+      "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz",
+      "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "pg-protocol": "*",
+        "pg-types": "^2.2.0"
+      }
+    },
+    "node_modules/@types/qs": {
+      "version": "6.14.0",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+      "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/range-parser": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/send": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+      "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/serve-static": {
+      "version": "1.15.10",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
+      "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/http-errors": "*",
+        "@types/node": "*",
+        "@types/send": "<1"
+      }
+    },
+    "node_modules/@types/serve-static/node_modules/@types/send": {
+      "version": "0.17.6",
+      "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
+      "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/mime": "^1",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/strip-json-comments": {
+      "version": "0.0.30",
+      "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz",
+      "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/uuid": {
+      "version": "9.0.8",
+      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz",
+      "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/ws": {
+      "version": "8.18.1",
+      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+      "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.16.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+      "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-walk": {
+      "version": "8.3.5",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
+      "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.11.0"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+      "license": "MIT"
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.4",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+      "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "content-type": "~1.0.5",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "~1.2.0",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.4.24",
+        "on-finished": "~2.4.1",
+        "qs": "~6.14.0",
+        "raw-body": "~2.5.3",
+        "type-is": "~1.6.18",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.12",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+      "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+      "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+      "license": "MIT"
+    },
+    "node_modules/cors": {
+      "version": "2.8.6",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+      "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+      "license": "MIT",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/diff": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
+      "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
+    "node_modules/dotenv": {
+      "version": "16.6.1",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+      "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/dynamic-dedupe": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
+      "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "xtend": "^4.0.0"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.22.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+      "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "~1.20.3",
+        "content-disposition": "~0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "~0.7.1",
+        "cookie-signature": "~1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.3.1",
+        "fresh": "~0.5.2",
+        "http-errors": "~2.0.0",
+        "merge-descriptors": "1.0.3",
+        "methods": "~1.1.2",
+        "on-finished": "~2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "~0.1.12",
+        "proxy-addr": "~2.0.7",
+        "qs": "~6.14.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "~0.19.0",
+        "serve-static": "~1.16.2",
+        "setprototypeof": "1.2.0",
+        "statuses": "~2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+      "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "~2.0.2",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+      "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~2.0.0",
+        "inherits": "~2.0.4",
+        "setprototypeof": "~1.2.0",
+        "statuses": "~2.0.2",
+        "toidentifier": "~1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.16.1",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/mkdirp": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+      "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "mkdirp": "bin/cmd.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+      "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+      "license": "MIT"
+    },
+    "node_modules/pg": {
+      "version": "8.18.0",
+      "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
+      "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
+      "license": "MIT",
+      "dependencies": {
+        "pg-connection-string": "^2.11.0",
+        "pg-pool": "^3.11.0",
+        "pg-protocol": "^1.11.0",
+        "pg-types": "2.2.0",
+        "pgpass": "1.0.5"
+      },
+      "engines": {
+        "node": ">= 16.0.0"
+      },
+      "optionalDependencies": {
+        "pg-cloudflare": "^1.3.0"
+      },
+      "peerDependencies": {
+        "pg-native": ">=3.0.1"
+      },
+      "peerDependenciesMeta": {
+        "pg-native": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pg-cloudflare": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
+      "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/pg-connection-string": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz",
+      "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==",
+      "license": "MIT"
+    },
+    "node_modules/pg-int8": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+      "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/pg-pool": {
+      "version": "3.11.0",
+      "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz",
+      "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==",
+      "license": "MIT",
+      "peerDependencies": {
+        "pg": ">=8.0"
+      }
+    },
+    "node_modules/pg-protocol": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz",
+      "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==",
+      "license": "MIT"
+    },
+    "node_modules/pg-types": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+      "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+      "license": "MIT",
+      "dependencies": {
+        "pg-int8": "1.0.1",
+        "postgres-array": "~2.0.0",
+        "postgres-bytea": "~1.0.0",
+        "postgres-date": "~1.0.4",
+        "postgres-interval": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/pgpass": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+      "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+      "license": "MIT",
+      "dependencies": {
+        "split2": "^4.1.0"
+      }
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postgres-array": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+      "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postgres-bytea": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
+      "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-date": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+      "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-interval": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+      "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+      "license": "MIT",
+      "dependencies": {
+        "xtend": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.14.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+      "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+      "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "~3.1.2",
+        "http-errors": "~2.0.1",
+        "iconv-lite": "~0.4.24",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/resolve": {
+      "version": "1.22.11",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+      "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-core-module": "^2.16.1",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+      "deprecated": "Rimraf versions prior to v4 are no longer supported",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/send": {
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+      "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "~0.5.2",
+        "http-errors": "~2.0.1",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "~2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "~2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/serve-static": {
+      "version": "1.16.3",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+      "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "~0.19.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3",
+        "side-channel-list": "^1.0.0",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+      "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-support": {
+      "version": "0.5.21",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
+    "node_modules/split2": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">= 10.x"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+      "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/tree-kill": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+      "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "tree-kill": "cli.js"
+      }
+    },
+    "node_modules/ts-node": {
+      "version": "10.9.2",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+      "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@cspotcode/source-map-support": "^0.8.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.1",
+        "yn": "3.1.1"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js",
+        "ts-node-cwd": "dist/bin-cwd.js",
+        "ts-node-esm": "dist/bin-esm.js",
+        "ts-node-script": "dist/bin-script.js",
+        "ts-node-transpile-only": "dist/bin-transpile.js",
+        "ts-script": "dist/bin-script-deprecated.js"
+      },
+      "peerDependencies": {
+        "@swc/core": ">=1.2.50",
+        "@swc/wasm": ">=1.2.50",
+        "@types/node": "*",
+        "typescript": ">=2.7"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "@swc/wasm": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/ts-node-dev": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz",
+      "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chokidar": "^3.5.1",
+        "dynamic-dedupe": "^0.3.0",
+        "minimist": "^1.2.6",
+        "mkdirp": "^1.0.4",
+        "resolve": "^1.0.0",
+        "rimraf": "^2.6.1",
+        "source-map-support": "^0.5.12",
+        "tree-kill": "^1.2.2",
+        "ts-node": "^10.4.0",
+        "tsconfig": "^7.0.0"
+      },
+      "bin": {
+        "ts-node-dev": "lib/bin.js",
+        "tsnd": "lib/bin.js"
+      },
+      "engines": {
+        "node": ">=0.8.0"
+      },
+      "peerDependencies": {
+        "node-notifier": "*",
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "node-notifier": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/tsconfig": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
+      "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/strip-bom": "^3.0.0",
+        "@types/strip-json-comments": "0.0.30",
+        "strip-bom": "^3.0.0",
+        "strip-json-comments": "^2.0.0"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
+    "node_modules/v8-compile-cache-lib": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/ws": {
+      "version": "8.19.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+      "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4"
+      }
+    },
+    "node_modules/yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..234a806
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+  "name": "dw-boardroom-governance",
+  "version": "1.0.0",
+  "description": "DW Boardroom Governance Engine — Isolated decision management, delegation, and meeting orchestration",
+  "main": "dist/api/server.js",
+  "scripts": {
+    "dev": "ts-node-dev --respawn --transpile-only src/api/server.ts",
+    "build": "tsc",
+    "start": "node dist/api/server.js",
+    "migrate": "ts-node src/db/migrate.ts"
+  },
+  "dependencies": {
+    "express": "^4.18.2",
+    "pg": "^8.18.0",
+    "cors": "^2.8.5",
+    "uuid": "^9.0.0",
+    "ws": "^8.16.0",
+    "dotenv": "^16.3.1"
+  },
+  "devDependencies": {
+    "typescript": "^5.3.3",
+    "ts-node": "^10.9.2",
+    "ts-node-dev": "^2.0.0",
+    "@types/express": "^4.17.21",
+    "@types/pg": "^8.10.9",
+    "@types/cors": "^2.8.17",
+    "@types/uuid": "^9.0.7",
+    "@types/ws": "^8.5.10",
+    "@types/node": "^20.10.0"
+  }
+}
diff --git a/src/agents/escalation.ts b/src/agents/escalation.ts
new file mode 100644
index 0000000..6f37d81
--- /dev/null
+++ b/src/agents/escalation.ts
@@ -0,0 +1,129 @@
+/**
+ * Escalation Agent — 12-hour threshold monitor
+ * Ported from /root/DW-Agents/boardroom-agent/lib/escalation.js
+ *
+ * Checks every 5 minutes for pending decisions past threshold.
+ * Logs escalations to gov_escalation_log.
+ */
+
+import { query, execute } from '../db/db';
+import { broadcast } from '../ws/broadcast';
+import { Decision } from '../types';
+
+const CHECK_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
+const DEFAULT_THRESHOLD_HOURS = 12;
+
+let escalationTimer: NodeJS.Timeout | null = null;
+
+export async function getThresholdHours(): Promise<number> {
+  const row = await query<{ value: any }>(
+    "SELECT value FROM gov_config WHERE key = 'escalation_threshold_hours'"
+  );
+  return row[0] ? Number(row[0].value) : DEFAULT_THRESHOLD_HOURS;
+}
+
+export async function checkEscalations(): Promise<{
+  escalated: number;
+  checked: number;
+}> {
+  const thresholdHours = await getThresholdHours();
+
+  // Find pending decisions older than threshold
+  const overdue = await query<Decision>(
+    `SELECT * FROM gov_decision_log
+     WHERE status = 'pending'
+     AND created_at < NOW() - INTERVAL '1 hour' * $1
+     AND id NOT IN (
+       SELECT decision_id FROM gov_escalation_log
+       WHERE decision_id IS NOT NULL
+       AND created_at > NOW() - INTERVAL '2 hours'
+     )
+     ORDER BY created_at ASC
+     LIMIT 10`,
+    [thresholdHours]
+  );
+
+  let escalated = 0;
+
+  for (const decision of overdue) {
+    const hoursElapsed = (Date.now() - new Date(decision.created_at).getTime()) / (1000 * 60 * 60);
+
+    await query(
+      `INSERT INTO gov_escalation_log (decision_id, escalation_type, hours_elapsed, action_taken, escalated_to)
+       VALUES ($1, $2, $3, $4, $5)`,
+      [
+        decision.id,
+        `${thresholdHours}hr`,
+        Math.round(hoursElapsed * 10) / 10,
+        'auto_escalated',
+        'human_operator',
+      ]
+    );
+
+    // Upgrade decision type to C (Controlled = requires human)
+    if (decision.decision_type !== 'C') {
+      await execute(
+        "UPDATE gov_decision_log SET decision_type = 'C', reasoning = COALESCE(reasoning, '') || ' [Auto-escalated after ' || $1 || 'h]' WHERE id = $2",
+        [Math.round(hoursElapsed), decision.id]
+      );
+    }
+
+    broadcast('escalation', {
+      decisionId: decision.id,
+      topic: decision.topic,
+      hoursElapsed: Math.round(hoursElapsed * 10) / 10,
+    });
+
+    escalated++;
+    console.log(`[Escalation] Decision #${decision.id} escalated after ${Math.round(hoursElapsed)}h`);
+  }
+
+  return { escalated, checked: overdue.length };
+}
+
+export async function getActiveEscalations(): Promise<any[]> {
+  return query(
+    `SELECT e.*, d.topic, d.owner, d.status as decision_status, d.decision_type
+     FROM gov_escalation_log e
+     LEFT JOIN gov_decision_log d ON e.decision_id = d.id
+     WHERE d.status = 'pending'
+     ORDER BY e.created_at DESC
+     LIMIT 20`
+  );
+}
+
+export async function getEscalationLog(limit: number = 50): Promise<any[]> {
+  return query(
+    `SELECT e.*, d.topic, d.owner
+     FROM gov_escalation_log e
+     LEFT JOIN gov_decision_log d ON e.decision_id = d.id
+     ORDER BY e.created_at DESC
+     LIMIT $1`,
+    [limit]
+  );
+}
+
+export function startEscalationMonitor(): void {
+  if (escalationTimer) return;
+
+  escalationTimer = setInterval(async () => {
+    try {
+      const result = await checkEscalations();
+      if (result.escalated > 0) {
+        console.log(`[Escalation] Cycle: ${result.escalated} escalated of ${result.checked} checked`);
+      }
+    } catch (err: any) {
+      console.error('[Escalation] Check error:', err.message);
+    }
+  }, CHECK_INTERVAL_MS);
+
+  console.log(`[Escalation] Monitor started (check every ${CHECK_INTERVAL_MS / 60000}min)`);
+}
+
+export function stopEscalationMonitor(): void {
+  if (escalationTimer) {
+    clearInterval(escalationTimer);
+    escalationTimer = null;
+    console.log('[Escalation] Monitor stopped');
+  }
+}
diff --git a/src/agents/president.ts b/src/agents/president.ts
new file mode 100644
index 0000000..76cc89b
--- /dev/null
+++ b/src/agents/president.ts
@@ -0,0 +1,57 @@
+/**
+ * President Agent — Meeting chair
+ * Validates agenda, enforces 5-item cap, opens/closes meetings
+ */
+
+import { AgendaItem } from '../types';
+
+const MAX_AGENDA_ITEMS = 5;
+
+export interface AgendaValidation {
+  valid: boolean;
+  errors: string[];
+  summary: string;
+}
+
+export function validateAgenda(items: AgendaItem[]): AgendaValidation {
+  const errors: string[] = [];
+
+  if (!items || items.length === 0) {
+    errors.push('Agenda is empty — at least one item required.');
+  }
+
+  if (items && items.length > MAX_AGENDA_ITEMS) {
+    errors.push(`Agenda exceeds cap of ${MAX_AGENDA_ITEMS} items (received ${items.length}). Trim or defer lower-priority items.`);
+  }
+
+  if (items) {
+    items.forEach((item, i) => {
+      if (!item.topic || item.topic.trim().length < 3) {
+        errors.push(`Item #${i + 1}: Topic too vague or missing.`);
+      }
+      if (!item.recommendation) {
+        errors.push(`Item #${i + 1}: Missing recommendation — every item needs a proposed action.`);
+      }
+      if (!item.decisionType || !['A', 'B', 'C'].includes(item.decisionType)) {
+        errors.push(`Item #${i + 1}: Invalid decision type (must be A, B, or C).`);
+      }
+    });
+  }
+
+  return {
+    valid: errors.length === 0,
+    errors,
+    summary: errors.length === 0
+      ? `Agenda approved: ${items.length} item(s), structurally valid.`
+      : `Agenda rejected: ${errors.length} issue(s) found.`
+  };
+}
+
+export function generateOpeningStatement(meetingType: string, itemCount: number): string {
+  const time = new Date().toLocaleTimeString('en-US', { timeZone: 'America/Los_Angeles', hour: '2-digit', minute: '2-digit' });
+  return `This ${meetingType} meeting is called to order at ${time} PT. We have ${itemCount} agenda item(s) to review.`;
+}
+
+export function generateClosingStatement(meetingType: string, decisionsCount: number): string {
+  return `The ${meetingType} meeting is adjourned. ${decisionsCount} decision(s) recorded in the governance ledger.`;
+}
diff --git a/src/agents/vp.ts b/src/agents/vp.ts
new file mode 100644
index 0000000..d2174a1
--- /dev/null
+++ b/src/agents/vp.ts
@@ -0,0 +1,47 @@
+/**
+ * VP Agent — Delegation coordinator
+ * Uses keyword routing + exec hierarchy to suggest delegates
+ */
+
+import { routeByKeywords, getExecForAgent, buildHierarchyPath } from '../engine/delegationEngine';
+import { EXEC_DELEGATES } from '../types';
+
+export interface DelegationSuggestion {
+  suggestedDelegate: string | null;
+  exec: string | null;
+  hierarchyPath: string;
+  confidence: 'high' | 'medium' | 'low';
+  reasoning: string;
+}
+
+export function suggestDelegate(taskDescription: string): DelegationSuggestion {
+  const agent = routeByKeywords(taskDescription);
+
+  if (agent) {
+    const exec = getExecForAgent(agent);
+    return {
+      suggestedDelegate: agent,
+      exec,
+      hierarchyPath: buildHierarchyPath(agent),
+      confidence: 'high',
+      reasoning: `Keyword match routed to ${agent} under ${exec || 'unknown exec'}`,
+    };
+  }
+
+  // No keyword match — suggest based on context clues
+  return {
+    suggestedDelegate: null,
+    exec: null,
+    hierarchyPath: '',
+    confidence: 'low',
+    reasoning: 'No keyword match found. Manual delegation recommended.',
+  };
+}
+
+export function getExecTeam(exec: string): string[] {
+  return EXEC_DELEGATES[exec] || [];
+}
+
+export function getAllExecs(): string[] {
+  return Object.keys(EXEC_DELEGATES);
+}
diff --git a/src/api/middleware/auth.ts b/src/api/middleware/auth.ts
new file mode 100644
index 0000000..bd1fb37
--- /dev/null
+++ b/src/api/middleware/auth.ts
@@ -0,0 +1,22 @@
+import { Request, Response, NextFunction } from 'express';
+
+export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
+  const auth = req.headers.authorization;
+
+  if (!auth || !auth.startsWith('Basic ')) {
+    res.setHeader('WWW-Authenticate', 'Basic realm="DW Governance"');
+    res.status(401).json({ error: 'Authentication required' });
+    return;
+  }
+
+  const decoded = Buffer.from(auth.split(' ')[1], 'base64').toString();
+  const [user, pass] = decoded.split(':');
+
+  if (user === process.env.AUTH_USER && pass === process.env.AUTH_PASS) {
+    next();
+    return;
+  }
+
+  res.setHeader('WWW-Authenticate', 'Basic realm="DW Governance"');
+  res.status(401).json({ error: 'Invalid credentials' });
+}
diff --git a/src/api/routes/decisions.ts b/src/api/routes/decisions.ts
new file mode 100644
index 0000000..06df2a8
--- /dev/null
+++ b/src/api/routes/decisions.ts
@@ -0,0 +1,89 @@
+import { Router, Request, Response } from 'express';
+import * as decisionEngine from '../../engine/decisionEngine';
+
+const router = Router();
+
+// GET /api/decisions — list decisions
+router.get('/', async (req: Request, res: Response) => {
+  try {
+    const decisions = await decisionEngine.listDecisions({
+      status: req.query.status as string,
+      owner: req.query.owner as string,
+      limit: req.query.limit ? parseInt(req.query.limit as string) : undefined,
+    });
+    res.json(decisions);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// GET /api/decisions/stats — counts by status
+router.get('/stats', async (_req: Request, res: Response) => {
+  try {
+    const stats = await decisionEngine.getDecisionStats();
+    res.json(stats);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// GET /api/decisions/:id — single decision
+router.get('/:id', async (req: Request, res: Response) => {
+  try {
+    const decision = await decisionEngine.getDecision(req.params.id);
+    if (!decision) return res.status(404).json({ error: 'Decision not found' });
+    res.json(decision);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/decisions — create decision
+router.post('/', async (req: Request, res: Response) => {
+  try {
+    const decision = await decisionEngine.createDecision({
+      topic: req.body.topic,
+      decisionType: req.body.decisionType || 'B',
+      recommendation: req.body.recommendation,
+      owner: req.body.owner || 'system',
+      autonomyLevel: req.body.autonomyLevel,
+      deadline: req.body.deadline,
+      source: req.body.source,
+    });
+    res.json(decision);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/decisions/:id/approve
+router.post('/:id/approve', async (req: Request, res: Response) => {
+  try {
+    const decision = await decisionEngine.resolveDecision(req.params.id, 'approve', req.body.notes);
+    res.json(decision);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/decisions/:id/reject
+router.post('/:id/reject', async (req: Request, res: Response) => {
+  try {
+    const decision = await decisionEngine.resolveDecision(req.params.id, 'reject', req.body.notes);
+    res.json(decision);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/decisions/:id/defer
+router.post('/:id/defer', async (req: Request, res: Response) => {
+  try {
+    const decision = await decisionEngine.resolveDecision(req.params.id, 'defer', req.body.notes);
+    res.json(decision);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/routes/delegations.ts b/src/api/routes/delegations.ts
new file mode 100644
index 0000000..7957241
--- /dev/null
+++ b/src/api/routes/delegations.ts
@@ -0,0 +1,77 @@
+import { Router, Request, Response } from 'express';
+import * as delegationEngine from '../../engine/delegationEngine';
+import { suggestDelegate } from '../../agents/vp';
+
+const router = Router();
+
+// GET /api/delegations — list delegations
+router.get('/', async (req: Request, res: Response) => {
+  try {
+    const status = req.query.status as string;
+    const delegations = await delegationEngine.listDelegations(status);
+    res.json(delegations);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/delegations — create delegation
+router.post('/', async (req: Request, res: Response) => {
+  try {
+    const contract = await delegationEngine.createDelegation({
+      decisionId: req.body.decisionId,
+      owner: req.body.owner,
+      delegate: req.body.delegate,
+      autonomyLevel: req.body.autonomyLevel,
+      metric: req.body.metric,
+      checkpointDate: req.body.checkpointDate,
+      routingKeywords: req.body.routingKeywords,
+    });
+    res.json(contract);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/delegations/suggest — get routing suggestion
+router.post('/suggest', async (req: Request, res: Response) => {
+  try {
+    const suggestion = suggestDelegate(req.body.description || '');
+    res.json(suggestion);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// GET /api/delegations/:id — detail
+router.get('/:id', async (req: Request, res: Response) => {
+  try {
+    const contract = await delegationEngine.getDelegation(req.params.id);
+    if (!contract) return res.status(404).json({ error: 'Delegation not found' });
+    res.json(contract);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/delegations/:id/complete
+router.post('/:id/complete', async (req: Request, res: Response) => {
+  try {
+    const contract = await delegationEngine.completeDelegation(req.params.id);
+    res.json(contract);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/delegations/:id/revoke
+router.post('/:id/revoke', async (req: Request, res: Response) => {
+  try {
+    const contract = await delegationEngine.revokeDelegation(req.params.id);
+    res.json(contract);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/routes/escalations.ts b/src/api/routes/escalations.ts
new file mode 100644
index 0000000..0d908ac
--- /dev/null
+++ b/src/api/routes/escalations.ts
@@ -0,0 +1,36 @@
+import { Router, Request, Response } from 'express';
+import { getEscalationLog, getActiveEscalations, checkEscalations } from '../../agents/escalation';
+
+const router = Router();
+
+// GET /api/escalations — full log
+router.get('/', async (_req: Request, res: Response) => {
+  try {
+    const log = await getEscalationLog();
+    res.json(log);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// GET /api/escalations/active — active only
+router.get('/active', async (_req: Request, res: Response) => {
+  try {
+    const active = await getActiveEscalations();
+    res.json(active);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/escalations/check — manual trigger
+router.post('/check', async (_req: Request, res: Response) => {
+  try {
+    const result = await checkEscalations();
+    res.json(result);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/routes/governance.ts b/src/api/routes/governance.ts
new file mode 100644
index 0000000..aa396a5
--- /dev/null
+++ b/src/api/routes/governance.ts
@@ -0,0 +1,80 @@
+import { Router, Request, Response } from 'express';
+import { query, execute } from '../../db/db';
+import { isRunning, getActiveMeetingId } from '../../engine/meetingEngine';
+import { getConnectionCount } from '../../ws/broadcast';
+
+const router = Router();
+
+// GET /api/governance/config — get all governance settings
+router.get('/config', async (_req: Request, res: Response) => {
+  try {
+    const rows = await query('SELECT key, value FROM gov_config ORDER BY key');
+    const config: Record<string, any> = {};
+    rows.forEach((r: any) => { config[r.key] = r.value; });
+    res.json(config);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/governance/config — update config
+router.post('/config', async (req: Request, res: Response) => {
+  try {
+    const updates = req.body;
+    for (const [key, value] of Object.entries(updates)) {
+      await execute(
+        `INSERT INTO gov_config (key, value, updated_at) VALUES ($1, $2, NOW())
+         ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()`,
+        [key, JSON.stringify(value)]
+      );
+    }
+    const rows = await query('SELECT key, value FROM gov_config ORDER BY key');
+    const config: Record<string, any> = {};
+    rows.forEach((r: any) => { config[r.key] = r.value; });
+    res.json(config);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// GET /api/governance/status — full system status
+router.get('/status', async (_req: Request, res: Response) => {
+  try {
+    const [decisions, delegations, initiatives, meetings, config] = await Promise.all([
+      query("SELECT status, COUNT(*)::int as count FROM gov_decision_log GROUP BY status"),
+      query("SELECT status, COUNT(*)::int as count FROM gov_delegation_contract GROUP BY status"),
+      query("SELECT status, COUNT(*)::int as count FROM gov_initiative_registry GROUP BY status"),
+      query("SELECT status, COUNT(*)::int as count FROM gov_meetings GROUP BY status"),
+      query("SELECT key, value FROM gov_config"),
+    ]);
+
+    const toMap = (rows: any[]) => {
+      const m: Record<string, number> = {};
+      rows.forEach(r => { m[r.status] = r.count; });
+      return m;
+    };
+
+    const configMap: Record<string, any> = {};
+    config.forEach((r: any) => { configMap[r.key] = r.value; });
+
+    res.json({
+      meeting: {
+        active: isRunning(),
+        activeMeetingId: getActiveMeetingId(),
+      },
+      decisions: toMap(decisions),
+      delegations: toMap(delegations),
+      initiatives: toMap(initiatives),
+      meetings: toMap(meetings),
+      config: configMap,
+      websocket: {
+        connections: getConnectionCount(),
+      },
+      uptime: process.uptime(),
+    });
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/routes/initiatives.ts b/src/api/routes/initiatives.ts
new file mode 100644
index 0000000..52f11d0
--- /dev/null
+++ b/src/api/routes/initiatives.ts
@@ -0,0 +1,109 @@
+import { Router, Request, Response } from 'express';
+import { query, queryOne, execute } from '../../db/db';
+import { Initiative } from '../../types';
+import { v4 as uuidv4 } from 'uuid';
+
+const router = Router();
+
+// GET /api/initiatives
+router.get('/', async (_req: Request, res: Response) => {
+  try {
+    const initiatives = await query<Initiative>(
+      'SELECT * FROM gov_initiative_registry ORDER BY created_at DESC LIMIT 50'
+    );
+    res.json(initiatives);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/initiatives
+router.post('/', async (req: Request, res: Response) => {
+  try {
+    const id = uuidv4();
+    await query(
+      `INSERT INTO gov_initiative_registry
+       (id, title, description, driver_agent, kpi, risk_level, capital_allocated, status)
+       VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`,
+      [
+        id,
+        req.body.title,
+        req.body.description || null,
+        req.body.driverAgent || 'unassigned',
+        req.body.kpi || null,
+        req.body.riskLevel || 'low',
+        req.body.capitalAllocated || 0,
+        req.body.status || 'proposed',
+      ]
+    );
+    const initiative = await queryOne<Initiative>(
+      'SELECT * FROM gov_initiative_registry WHERE id = $1', [id]
+    );
+    res.json(initiative);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// GET /api/initiatives/:id
+router.get('/:id', async (req: Request, res: Response) => {
+  try {
+    const initiative = await queryOne<Initiative>(
+      'SELECT * FROM gov_initiative_registry WHERE id = $1', [req.params.id]
+    );
+    if (!initiative) return res.status(404).json({ error: 'Initiative not found' });
+    res.json(initiative);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// PATCH /api/initiatives/:id
+router.patch('/:id', async (req: Request, res: Response) => {
+  try {
+    const fields: string[] = [];
+    const values: any[] = [];
+    let idx = 1;
+
+    const allowedFields = ['title', 'description', 'driver_agent', 'kpi', 'risk_level', 'capital_allocated', 'status', 'progress'];
+    for (const field of allowedFields) {
+      const camelKey = field.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
+      if (req.body[camelKey] !== undefined || req.body[field] !== undefined) {
+        fields.push(`${field} = $${idx++}`);
+        values.push(req.body[camelKey] ?? req.body[field]);
+      }
+    }
+
+    if (fields.length === 0) return res.status(400).json({ error: 'No fields to update' });
+
+    fields.push(`updated_at = NOW()`);
+    values.push(req.params.id);
+
+    await execute(
+      `UPDATE gov_initiative_registry SET ${fields.join(', ')} WHERE id = $${idx}`,
+      values
+    );
+
+    const initiative = await queryOne<Initiative>(
+      'SELECT * FROM gov_initiative_registry WHERE id = $1', [req.params.id]
+    );
+    res.json(initiative);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// DELETE /api/initiatives/:id
+router.delete('/:id', async (req: Request, res: Response) => {
+  try {
+    await execute(
+      "UPDATE gov_initiative_registry SET status = 'cancelled', updated_at = NOW() WHERE id = $1",
+      [req.params.id]
+    );
+    res.json({ status: 'cancelled' });
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/routes/meetings.ts b/src/api/routes/meetings.ts
new file mode 100644
index 0000000..a98529c
--- /dev/null
+++ b/src/api/routes/meetings.ts
@@ -0,0 +1,109 @@
+import { Router, Request, Response } from 'express';
+import * as meetingEngine from '../../engine/meetingEngine';
+import { validateAgenda, generateOpeningStatement } from '../../agents/president';
+
+const router = Router();
+
+// GET /api/meetings — list all meetings
+router.get('/', async (_req: Request, res: Response) => {
+  try {
+    const meetings = await meetingEngine.listMeetings();
+    res.json(meetings);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// GET /api/meetings/active — get active meeting
+router.get('/active', async (_req: Request, res: Response) => {
+  try {
+    const id = meetingEngine.getActiveMeetingId();
+    if (!id) return res.json({ active: false });
+    const meeting = await meetingEngine.getMeeting(id);
+    const messages = await meetingEngine.getMessages(id);
+    res.json({ active: true, meeting, messages });
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// GET /api/meetings/:id — meeting detail
+router.get('/:id', async (req: Request, res: Response) => {
+  try {
+    const meeting = await meetingEngine.getMeeting(req.params.id);
+    if (!meeting) return res.status(404).json({ error: 'Meeting not found' });
+    const messages = await meetingEngine.getMessages(req.params.id);
+    res.json({ ...meeting, messages });
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+// POST /api/meetings/:type — trigger meeting
+router.post('/:type', async (req: Request, res: Response) => {
+  try {
+    const meetingType = req.params.type;
+    const agendaItems = req.body.agendaItems || [];
+
+    // Validate agenda if items provided
+    if (agendaItems.length > 0) {
+      const validation = validateAgenda(agendaItems);
+      if (!validation.valid) {
+        return res.status(400).json({ error: validation.summary, details: validation.errors });
+      }
+    }
+
+    const meeting = await meetingEngine.startMeeting(meetingType, agendaItems);
+
+    // Add opening statement
+    const opening = generateOpeningStatement(meetingType, agendaItems.length);
+    await meetingEngine.addMessage(meeting.id, 'president', 'President', opening, 'opening');
+
+    res.json(meeting);
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/meetings/:id/halt — halt meeting
+router.post('/:id/halt', async (req: Request, res: Response) => {
+  try {
+    await meetingEngine.haltMeeting(req.params.id);
+    res.json({ status: 'halted' });
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/meetings/:id/advance — advance to next phase
+router.post('/:id/advance', async (req: Request, res: Response) => {
+  try {
+    const phase = await meetingEngine.advancePhase(req.params.id);
+    res.json({ phase, completed: phase === null });
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// POST /api/meetings/:id/message — add message
+router.post('/:id/message', async (req: Request, res: Response) => {
+  try {
+    const { agentId, agentName, message, messageType } = req.body;
+    await meetingEngine.addMessage(req.params.id, agentId, agentName, message, messageType);
+    res.json({ status: 'sent' });
+  } catch (err: any) {
+    res.status(400).json({ error: err.message });
+  }
+});
+
+// GET /api/meetings/:id/messages — get messages
+router.get('/:id/messages', async (req: Request, res: Response) => {
+  try {
+    const messages = await meetingEngine.getMessages(req.params.id);
+    res.json(messages);
+  } catch (err: any) {
+    res.status(500).json({ error: err.message });
+  }
+});
+
+export default router;
diff --git a/src/api/server.ts b/src/api/server.ts
new file mode 100644
index 0000000..508b4e5
--- /dev/null
+++ b/src/api/server.ts
@@ -0,0 +1,80 @@
+/**
+ * DW Boardroom Governance — API Server
+ * Port 4020 | Isolated governance engine
+ */
+
+import express from 'express';
+import cors from 'cors';
+import http from 'http';
+import dotenv from 'dotenv';
+import { authMiddleware } from './middleware/auth';
+import { initWebSocket } from '../ws/broadcast';
+import { startEscalationMonitor } from '../agents/escalation';
+
+// Route imports
+import meetingRoutes from './routes/meetings';
+import decisionRoutes from './routes/decisions';
+import delegationRoutes from './routes/delegations';
+import initiativeRoutes from './routes/initiatives';
+import escalationRoutes from './routes/escalations';
+import governanceRoutes from './routes/governance';
+
+dotenv.config();
+
+// Crash handlers
+process.on('unhandledRejection', (reason: any) => {
+  console.error('[Governance CRASH] Unhandled Rejection:', reason?.stack || reason);
+});
+process.on('uncaughtException', (err) => {
+  console.error('[Governance CRASH] Uncaught Exception:', err?.stack || err);
+});
+
+const app = express();
+const server = http.createServer(app);
+const PORT = parseInt(process.env.PORT || '4020');
+
+// Health check — no auth
+app.get('/health', (_req, res) => {
+  res.json({
+    status: 'ok',
+    agent: 'DW Boardroom Governance',
+    port: PORT,
+    uptime: Math.round(process.uptime()),
+  });
+});
+
+// CORS
+app.use(cors({
+  origin: [
+    process.env.FRONTEND_URL || 'http://45.61.58.125:4030',
+    'http://localhost:4030',
+    'http://127.0.0.1:4030',
+    'http://45.61.58.125:4060',
+  ],
+  credentials: true,
+}));
+
+// Auth — after health, before routes
+app.use(authMiddleware);
+app.use(express.json({ limit: '1mb' }));
+
+// Routes
+app.use('/api/meetings', meetingRoutes);
+app.use('/api/decisions', decisionRoutes);
+app.use('/api/delegations', delegationRoutes);
+app.use('/api/initiatives', initiativeRoutes);
+app.use('/api/escalations', escalationRoutes);
+app.use('/api/governance', governanceRoutes);
+
+// WebSocket
+initWebSocket(server);
+
+// Start escalation monitor
+startEscalationMonitor();
+
+// Launch
+server.listen(PORT, '0.0.0.0', () => {
+  console.log(`[Governance] DW Boardroom Governance running on port ${PORT}`);
+  console.log(`[Governance] Health: http://45.61.58.125:${PORT}/health`);
+  console.log(`[Governance] API: http://45.61.58.125:${PORT}/api/governance/status`);
+});
diff --git a/src/db/db.ts b/src/db/db.ts
new file mode 100644
index 0000000..8909928
--- /dev/null
+++ b/src/db/db.ts
@@ -0,0 +1,30 @@
+import { Pool, QueryResult } from 'pg';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+export const pool = new Pool({
+  connectionString: process.env.DATABASE_URL,
+  max: 10,
+  idleTimeoutMillis: 30000,
+  connectionTimeoutMillis: 5000
+});
+
+pool.on('error', (err) => {
+  console.error('[Governance DB] Unexpected pool error:', err.message);
+});
+
+export async function query<T extends Record<string, any> = Record<string, any>>(text: string, params?: any[]): Promise<T[]> {
+  const result = await pool.query(text, params);
+  return result.rows as T[];
+}
+
+export async function queryOne<T extends Record<string, any> = Record<string, any>>(text: string, params?: any[]): Promise<T | null> {
+  const rows = await query<T>(text, params);
+  return rows[0] || null;
+}
+
+export async function execute(text: string, params?: any[]): Promise<number> {
+  const result = await pool.query(text, params);
+  return result.rowCount || 0;
+}
diff --git a/src/db/migrate.ts b/src/db/migrate.ts
new file mode 100644
index 0000000..c3b8d6c
--- /dev/null
+++ b/src/db/migrate.ts
@@ -0,0 +1,37 @@
+import fs from 'fs';
+import path from 'path';
+import { pool } from './db';
+
+async function migrate() {
+  console.log('[Governance] Running database migration...');
+
+  const schemaPath = path.join(__dirname, 'schema.sql');
+
+  // When running via ts-node, __dirname points to src/db; when compiled, dist/db
+  // Check both locations
+  let sql: string;
+  if (fs.existsSync(schemaPath)) {
+    sql = fs.readFileSync(schemaPath, 'utf-8');
+  } else {
+    const altPath = path.join(__dirname, '..', '..', 'src', 'db', 'schema.sql');
+    sql = fs.readFileSync(altPath, 'utf-8');
+  }
+
+  try {
+    await pool.query(sql);
+    console.log('[Governance] Migration complete — all gov_* tables ready');
+
+    // Verify tables exist
+    const result = await pool.query(
+      "SELECT tablename FROM pg_tables WHERE schemaname='public' AND tablename LIKE 'gov_%' ORDER BY tablename"
+    );
+    console.log('[Governance] Tables:', result.rows.map(r => r.tablename).join(', '));
+  } catch (err: any) {
+    console.error('[Governance] Migration failed:', err.message);
+    process.exit(1);
+  } finally {
+    await pool.end();
+  }
+}
+
+migrate();
diff --git a/src/db/schema.sql b/src/db/schema.sql
new file mode 100644
index 0000000..f0b7f5f
--- /dev/null
+++ b/src/db/schema.sql
@@ -0,0 +1,117 @@
+-- DW Boardroom Governance Schema
+-- All tables prefixed with gov_ to isolate from existing dw_unified tables
+-- Idempotent: safe to run multiple times
+
+-- Governance Decision Log
+CREATE TABLE IF NOT EXISTS gov_decision_log (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  topic TEXT NOT NULL,
+  decision_type TEXT NOT NULL CHECK (decision_type IN ('A', 'B', 'C')),
+  recommendation TEXT,
+  human_decision TEXT CHECK (human_decision IN ('approve', 'reject', 'modify', 'defer')),
+  owner TEXT NOT NULL,
+  autonomy_level INTEGER NOT NULL DEFAULT 1 CHECK (autonomy_level BETWEEN 1 AND 5),
+  deadline TIMESTAMPTZ,
+  status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected', 'modified', 'deferred', 'auto_approved')),
+  source TEXT,
+  score INTEGER CHECK (score BETWEEN 0 AND 10),
+  reasoning TEXT,
+  notes TEXT,
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+  resolved_at TIMESTAMPTZ
+);
+CREATE INDEX IF NOT EXISTS idx_gov_decision_status ON gov_decision_log(status);
+CREATE INDEX IF NOT EXISTS idx_gov_decision_owner ON gov_decision_log(owner);
+CREATE INDEX IF NOT EXISTS idx_gov_decision_created ON gov_decision_log(created_at DESC);
+
+-- Delegation Contracts
+CREATE TABLE IF NOT EXISTS gov_delegation_contract (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  decision_id UUID REFERENCES gov_decision_log(id) ON DELETE SET NULL,
+  owner TEXT NOT NULL,
+  delegate TEXT NOT NULL,
+  autonomy_level INTEGER NOT NULL DEFAULT 1 CHECK (autonomy_level BETWEEN 1 AND 5),
+  metric TEXT,
+  checkpoint_date TIMESTAMPTZ,
+  status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'completed', 'revoked', 'expired')),
+  routing_keywords TEXT[],
+  hierarchy_path TEXT,
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+  completed_at TIMESTAMPTZ
+);
+CREATE INDEX IF NOT EXISTS idx_gov_deleg_owner ON gov_delegation_contract(owner);
+CREATE INDEX IF NOT EXISTS idx_gov_deleg_delegate ON gov_delegation_contract(delegate);
+CREATE INDEX IF NOT EXISTS idx_gov_deleg_status ON gov_delegation_contract(status);
+
+-- Initiative Registry
+CREATE TABLE IF NOT EXISTS gov_initiative_registry (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  title TEXT NOT NULL,
+  description TEXT,
+  driver_agent TEXT NOT NULL,
+  kpi TEXT,
+  risk_level TEXT NOT NULL DEFAULT 'low' CHECK (risk_level IN ('low', 'medium', 'high', 'critical')),
+  capital_allocated NUMERIC(12,2) DEFAULT 0,
+  status TEXT NOT NULL DEFAULT 'proposed' CHECK (status IN ('proposed', 'active', 'paused', 'completed', 'cancelled')),
+  progress INTEGER DEFAULT 0 CHECK (progress BETWEEN 0 AND 100),
+  created_at TIMESTAMPTZ DEFAULT NOW(),
+  updated_at TIMESTAMPTZ DEFAULT NOW()
+);
+CREATE INDEX IF NOT EXISTS idx_gov_init_status ON gov_initiative_registry(status);
+CREATE INDEX IF NOT EXISTS idx_gov_init_driver ON gov_initiative_registry(driver_agent);
+
+-- Meeting Log
+CREATE TABLE IF NOT EXISTS gov_meetings (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  meeting_type TEXT NOT NULL,
+  phase TEXT DEFAULT 'scheduled',
+  started_at TIMESTAMPTZ,
+  ended_at TIMESTAMPTZ,
+  status TEXT NOT NULL DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'in_progress', 'completed', 'halted', 'crashed')),
+  attendees JSONB DEFAULT '[]',
+  summary TEXT,
+  action_items JSONB DEFAULT '[]',
+  created_at TIMESTAMPTZ DEFAULT NOW()
+);
+CREATE INDEX IF NOT EXISTS idx_gov_meeting_status ON gov_meetings(status);
+CREATE INDEX IF NOT EXISTS idx_gov_meeting_created ON gov_meetings(created_at DESC);
+
+-- Meeting Messages (live feed)
+CREATE TABLE IF NOT EXISTS gov_meeting_messages (
+  id SERIAL PRIMARY KEY,
+  meeting_id UUID REFERENCES gov_meetings(id) ON DELETE CASCADE,
+  agent_id TEXT NOT NULL,
+  agent_name TEXT NOT NULL,
+  message TEXT NOT NULL,
+  message_type TEXT DEFAULT 'update',
+  created_at TIMESTAMPTZ DEFAULT NOW()
+);
+CREATE INDEX IF NOT EXISTS idx_gov_msg_meeting ON gov_meeting_messages(meeting_id, created_at);
+
+-- Escalation Log
+CREATE TABLE IF NOT EXISTS gov_escalation_log (
+  id SERIAL PRIMARY KEY,
+  decision_id UUID REFERENCES gov_decision_log(id) ON DELETE SET NULL,
+  escalation_type TEXT NOT NULL DEFAULT '12hr',
+  hours_elapsed REAL,
+  action_taken TEXT,
+  escalated_to TEXT,
+  created_at TIMESTAMPTZ DEFAULT NOW()
+);
+CREATE INDEX IF NOT EXISTS idx_gov_escalation_created ON gov_escalation_log(created_at DESC);
+
+-- Governance Config (key-value store)
+CREATE TABLE IF NOT EXISTS gov_config (
+  key TEXT PRIMARY KEY,
+  value JSONB NOT NULL,
+  updated_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- Seed defaults
+INSERT INTO gov_config (key, value) VALUES
+  ('governance_mode', '"supervised"'),
+  ('autonomy_default', '1'),
+  ('escalation_threshold_hours', '12'),
+  ('max_agenda_items', '5'),
+  ('engines', '{"meetings": true, "decisions": true, "delegations": true, "escalations": true}')
+ON CONFLICT (key) DO NOTHING;
diff --git a/src/engine/decisionEngine.ts b/src/engine/decisionEngine.ts
new file mode 100644
index 0000000..2bb44b7
--- /dev/null
+++ b/src/engine/decisionEngine.ts
@@ -0,0 +1,217 @@
+/**
+ * Decision Engine — Scoring, dedup, approval logic
+ * Ported from:
+ *   - /root/DW-Agents/boardroom-agent/routes/api-approvals.js (dedup + decision logging)
+ *   - /root/DW-Agents/boardroom-agent/lib/task-reaper.js (0-10 scoring)
+ *
+ * Scoring:
+ *   0-2: VETO (auto-cancel as vague/duplicate/phantom)
+ *   3-6: RESOLVE (auto-approve if autonomy_level >= 4)
+ *   7-10: ESCALATE (always requires human decision)
+ */
+
+import { query, queryOne, execute } from '../db/db';
+import { broadcast } from '../ws/broadcast';
+import { Decision, DecisionType, DecisionStatus, HumanDecision, SCORE_THRESHOLDS } from '../types';
+import { v4 as uuidv4 } from 'uuid';
+
+// ─── Deduplication (ported from api-approvals.js normalizeTitle) ──
+
+function normalizeTitle(title: string): string {
+  return (title || '').toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, ' ').trim();
+}
+
+async function isDuplicate(topic: string): Promise<{ duplicate: boolean; reason?: string }> {
+  const key = normalizeTitle(topic);
+  if (!key || key.length < 3) return { duplicate: false };
+
+  // Check for rejected decisions in last 30 days
+  const rejected = await queryOne<Decision>(
+    `SELECT id, topic FROM gov_decision_log
+     WHERE status = 'rejected'
+     AND LOWER(REPLACE(topic, '-', '')) LIKE $1
+     AND created_at > NOW() - INTERVAL '30 days'
+     LIMIT 1`,
+    ['%' + key.substring(0, 20) + '%']
+  );
+  if (rejected) {
+    return { duplicate: true, reason: `Previously rejected: "${rejected.topic}" (#${rejected.id})` };
+  }
+
+  // Check for duplicate pending decisions
+  const pending = await queryOne<Decision>(
+    `SELECT id, topic FROM gov_decision_log
+     WHERE status = 'pending'
+     AND LOWER(REPLACE(topic, '-', '')) LIKE $1
+     LIMIT 1`,
+    ['%' + key.substring(0, 20) + '%']
+  );
+  if (pending) {
+    return { duplicate: true, reason: `Already pending: "${pending.topic}" (#${pending.id})` };
+  }
+
+  return { duplicate: false };
+}
+
+// ─── Scoring (ported from task-reaper.js) ──
+
+function scoreDecision(decision: Partial<Decision>): { score: number; reasoning: string } {
+  let score = 5; // Start neutral
+  const reasons: string[] = [];
+
+  // Topic quality
+  const topicLen = (decision.topic || '').trim().length;
+  if (topicLen < 10) {
+    score -= 3;
+    reasons.push('Topic too vague');
+  } else if (topicLen > 20) {
+    score += 1;
+    reasons.push('Well-described topic');
+  }
+
+  // Has recommendation
+  if (decision.recommendation && decision.recommendation.length > 10) {
+    score += 2;
+    reasons.push('Includes recommendation');
+  } else {
+    score -= 1;
+    reasons.push('Missing recommendation');
+  }
+
+  // Decision type weighting
+  if (decision.decision_type === 'C') {
+    score += 1; // Controlled = high importance
+    reasons.push('Controlled decision (human-required)');
+  } else if (decision.decision_type === 'A') {
+    score -= 1; // Autonomous = lower importance
+    reasons.push('Autonomous decision');
+  }
+
+  // Has deadline
+  if (decision.deadline) {
+    score += 1;
+    reasons.push('Has deadline');
+  }
+
+  // Clamp to 0-10
+  score = Math.max(0, Math.min(10, score));
+
+  return { score, reasoning: reasons.join('; ') };
+}
+
+// ─── CRUD ─────────────────────────────────────────
+
+export async function createDecision(data: {
+  topic: string;
+  decisionType: DecisionType;
+  recommendation?: string;
+  owner: string;
+  autonomyLevel?: number;
+  deadline?: string;
+  source?: string;
+}): Promise<Decision> {
+  // Dedup check
+  const dupeCheck = await isDuplicate(data.topic);
+  if (dupeCheck.duplicate) {
+    throw new Error(`Duplicate: ${dupeCheck.reason}`);
+  }
+
+  const id = uuidv4();
+  const autonomyLevel = data.autonomyLevel || 1;
+
+  // Score it
+  const { score, reasoning } = scoreDecision({
+    topic: data.topic,
+    recommendation: data.recommendation,
+    decision_type: data.decisionType,
+    deadline: data.deadline,
+  });
+
+  // Auto-approve logic
+  let status: DecisionStatus = 'pending';
+  if (score <= SCORE_THRESHOLDS.VETO_CEILING) {
+    status = 'rejected'; // Auto-veto: too vague
+  } else if (data.decisionType === 'A' && autonomyLevel >= 4 && score <= SCORE_THRESHOLDS.RESOLVE_CEILING) {
+    status = 'auto_approved'; // Autonomous + high autonomy + moderate score
+  }
+
+  await query(
+    `INSERT INTO gov_decision_log
+     (id, topic, decision_type, recommendation, owner, autonomy_level, deadline, status, source, score, reasoning)
+     VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)`,
+    [id, data.topic, data.decisionType, data.recommendation || null, data.owner,
+     autonomyLevel, data.deadline || null, status, data.source || null, score, reasoning]
+  );
+
+  const decision = (await queryOne<Decision>('SELECT * FROM gov_decision_log WHERE id = $1', [id]))!;
+
+  broadcast('new_decision', decision);
+  console.log(`[Decision] Created #${id} — score=${score} status=${status}`);
+
+  return decision;
+}
+
+export async function resolveDecision(
+  id: string,
+  humanDecision: HumanDecision,
+  notes?: string
+): Promise<Decision> {
+  const statusMap: Record<HumanDecision, DecisionStatus> = {
+    approve: 'approved',
+    reject: 'rejected',
+    modify: 'modified',
+    defer: 'deferred',
+  };
+
+  await execute(
+    `UPDATE gov_decision_log
+     SET human_decision = $1, status = $2, notes = COALESCE($3, notes), resolved_at = NOW()
+     WHERE id = $4`,
+    [humanDecision, statusMap[humanDecision], notes || null, id]
+  );
+
+  const decision = await queryOne<Decision>('SELECT * FROM gov_decision_log WHERE id = $1', [id]);
+  if (!decision) throw new Error('Decision not found');
+
+  broadcast('decision_resolved', decision);
+  console.log(`[Decision] Resolved #${id} → ${humanDecision}`);
+
+  return decision;
+}
+
+export async function listDecisions(filters?: {
+  status?: string;
+  owner?: string;
+  limit?: number;
+}): Promise<Decision[]> {
+  let sql = 'SELECT * FROM gov_decision_log WHERE 1=1';
+  const params: any[] = [];
+  let idx = 1;
+
+  if (filters?.status) {
+    sql += ` AND status = $${idx++}`;
+    params.push(filters.status);
+  }
+  if (filters?.owner) {
+    sql += ` AND owner = $${idx++}`;
+    params.push(filters.owner);
+  }
+
+  sql += ` ORDER BY created_at DESC LIMIT $${idx}`;
+  params.push(filters?.limit || 50);
+
+  return query<Decision>(sql, params);
+}
+
+export async function getDecision(id: string): Promise<Decision | null> {
+  return queryOne<Decision>('SELECT * FROM gov_decision_log WHERE id = $1', [id]);
+}
+
+export async function getDecisionStats(): Promise<Record<string, number>> {
+  const rows = await query<{ status: string; count: string }>(
+    'SELECT status, COUNT(*) as count FROM gov_decision_log GROUP BY status'
+  );
+  const stats: Record<string, number> = {};
+  rows.forEach(r => { stats[r.status] = parseInt(r.count); });
+  return stats;
+}
diff --git a/src/engine/delegationEngine.ts b/src/engine/delegationEngine.ts
new file mode 100644
index 0000000..517770e
--- /dev/null
+++ b/src/engine/delegationEngine.ts
@@ -0,0 +1,148 @@
+/**
+ * Delegation Engine — Keyword-based task routing + contract management
+ * Ported from /root/DW-Agents/boardroom-agent/lib/delegation-engine.js
+ *
+ * Features:
+ *   - TASK_KEYWORDS map for intelligent routing
+ *   - EXEC_DELEGATES hierarchy for fallback
+ *   - Contract lifecycle (create, complete, revoke, expire)
+ */
+
+import { query, queryOne, execute } from '../db/db';
+import { broadcast } from '../ws/broadcast';
+import { DelegationContract, TASK_KEYWORDS, EXEC_DELEGATES } from '../types';
+import { v4 as uuidv4 } from 'uuid';
+
+// ─── Keyword Routing ────────────────────────────
+
+export function routeByKeywords(description: string): string | null {
+  const lower = description.toLowerCase();
+
+  // Check multi-word keywords first (more specific)
+  const multiWordKeys = Object.keys(TASK_KEYWORDS).filter(k => k.includes(' '));
+  for (const keyword of multiWordKeys) {
+    if (lower.includes(keyword)) {
+      return TASK_KEYWORDS[keyword];
+    }
+  }
+
+  // Then single-word keywords
+  const words = lower.split(/\s+/);
+  for (const word of words) {
+    if (TASK_KEYWORDS[word]) {
+      return TASK_KEYWORDS[word];
+    }
+  }
+
+  return null;
+}
+
+export function getExecForAgent(agentId: string): string | null {
+  for (const [exec, agents] of Object.entries(EXEC_DELEGATES)) {
+    if (agents.includes(agentId)) return exec;
+  }
+  return null;
+}
+
+export function buildHierarchyPath(agentId: string): string {
+  const exec = getExecForAgent(agentId);
+  if (!exec) return agentId;
+  return `${exec} > ${agentId}`;
+}
+
+// ─── Contract CRUD ──────────────────────────────
+
+export async function createDelegation(data: {
+  decisionId?: string;
+  owner: string;
+  delegate: string;
+  autonomyLevel?: number;
+  metric?: string;
+  checkpointDate?: string;
+  routingKeywords?: string[];
+}): Promise<DelegationContract> {
+  const id = uuidv4();
+  const hierarchyPath = buildHierarchyPath(data.delegate);
+
+  await query(
+    `INSERT INTO gov_delegation_contract
+     (id, decision_id, owner, delegate, autonomy_level, metric, checkpoint_date, routing_keywords, hierarchy_path)
+     VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`,
+    [
+      id,
+      data.decisionId || null,
+      data.owner,
+      data.delegate,
+      data.autonomyLevel || 1,
+      data.metric || null,
+      data.checkpointDate || null,
+      data.routingKeywords || null,
+      hierarchyPath,
+    ]
+  );
+
+  const contract = (await queryOne<DelegationContract>(
+    'SELECT * FROM gov_delegation_contract WHERE id = $1', [id]
+  ))!;
+
+  broadcast('delegation_created', contract);
+  console.log(`[Delegation] Created: ${data.owner} → ${data.delegate} (${hierarchyPath})`);
+
+  return contract;
+}
+
+export async function completeDelegation(id: string): Promise<DelegationContract> {
+  await execute(
+    "UPDATE gov_delegation_contract SET status = 'completed', completed_at = NOW() WHERE id = $1",
+    [id]
+  );
+  const contract = await queryOne<DelegationContract>(
+    'SELECT * FROM gov_delegation_contract WHERE id = $1', [id]
+  );
+  if (!contract) throw new Error('Delegation not found');
+  return contract;
+}
+
+export async function revokeDelegation(id: string): Promise<DelegationContract> {
+  await execute(
+    "UPDATE gov_delegation_contract SET status = 'revoked', completed_at = NOW() WHERE id = $1",
+    [id]
+  );
+  const contract = await queryOne<DelegationContract>(
+    'SELECT * FROM gov_delegation_contract WHERE id = $1', [id]
+  );
+  if (!contract) throw new Error('Delegation not found');
+  return contract;
+}
+
+export async function listDelegations(status?: string): Promise<DelegationContract[]> {
+  if (status) {
+    return query<DelegationContract>(
+      'SELECT * FROM gov_delegation_contract WHERE status = $1 ORDER BY created_at DESC',
+      [status]
+    );
+  }
+  return query<DelegationContract>(
+    'SELECT * FROM gov_delegation_contract ORDER BY created_at DESC LIMIT 50'
+  );
+}
+
+export async function getDelegation(id: string): Promise<DelegationContract | null> {
+  return queryOne<DelegationContract>(
+    'SELECT * FROM gov_delegation_contract WHERE id = $1', [id]
+  );
+}
+
+export async function checkExpiredContracts(): Promise<number> {
+  const result = await execute(
+    `UPDATE gov_delegation_contract
+     SET status = 'expired', completed_at = NOW()
+     WHERE status = 'active'
+     AND checkpoint_date IS NOT NULL
+     AND checkpoint_date < NOW()`
+  );
+  if (result > 0) {
+    console.log(`[Delegation] Expired ${result} contract(s)`);
+  }
+  return result;
+}
diff --git a/src/engine/meetingEngine.ts b/src/engine/meetingEngine.ts
new file mode 100644
index 0000000..8ba1d9b
--- /dev/null
+++ b/src/engine/meetingEngine.ts
@@ -0,0 +1,204 @@
+/**
+ * Meeting Engine — Phase orchestrator
+ * Ported from /root/DW-Agents/boardroom-agent/lib/meeting-engine.js
+ *
+ * 8-phase structure: Caucus → Call to Order → Old Business → Current Business
+ *   → Breakout → New Business → Minutes → Adjournment
+ *
+ * Features:
+ *   - Mutex lock (one meeting at a time)
+ *   - Phase timeouts (10min adhoc, 20min scheduled)
+ *   - WebSocket broadcast on phase transitions
+ */
+
+import { query, queryOne, execute } from '../db/db';
+import { broadcast } from '../ws/broadcast';
+import { Meeting, MeetingPhase, MEETING_PHASES, MeetingStatus, AgendaItem } from '../types';
+import { v4 as uuidv4 } from 'uuid';
+
+// Mutex: only one meeting at a time
+let activeMeetingId: string | null = null;
+let phaseTimer: NodeJS.Timeout | null = null;
+
+const PHASE_DURATION_MS: Record<string, number> = {
+  caucus: 60_000,
+  call_to_order: 30_000,
+  old_business: 120_000,
+  current_business: 120_000,
+  breakout: 90_000,
+  new_business: 120_000,
+  minutes: 60_000,
+  adjournment: 30_000,
+};
+
+const MAX_MEETING_DURATION_MS: Record<string, number> = {
+  emergency: 10 * 60_000,
+  adhoc: 10 * 60_000,
+  strategic: 20 * 60_000,
+  initiative: 15 * 60_000,
+  ops: 15 * 60_000,
+  standup: 10 * 60_000,
+};
+
+export function isRunning(): boolean {
+  return activeMeetingId !== null;
+}
+
+export function getActiveMeetingId(): string | null {
+  return activeMeetingId;
+}
+
+export async function startMeeting(meetingType: string, agendaItems?: AgendaItem[]): Promise<Meeting> {
+  if (activeMeetingId) {
+    throw new Error('Meeting already in progress. Halt current meeting first.');
+  }
+
+  const id = uuidv4();
+  const now = new Date().toISOString();
+
+  await query(
+    `INSERT INTO gov_meetings (id, meeting_type, phase, started_at, status, action_items)
+     VALUES ($1, $2, $3, $4, $5, $6)`,
+    [id, meetingType, 'caucus', now, 'in_progress', JSON.stringify(agendaItems || [])]
+  );
+
+  activeMeetingId = id;
+
+  // Add opening message
+  await addMessage(id, 'system', 'Governance', `${meetingType} meeting called to order.`, 'system');
+
+  broadcast('meeting_started', { meetingId: id, meetingType, phase: 'caucus' });
+  console.log(`[Meeting] Started ${meetingType} meeting: ${id}`);
+
+  // Start auto-advance timer
+  schedulePhaseAdvance(id);
+
+  return (await queryOne<Meeting>('SELECT * FROM gov_meetings WHERE id = $1', [id]))!;
+}
+
+export async function advancePhase(meetingId: string): Promise<MeetingPhase | null> {
+  const meeting = await queryOne<Meeting>('SELECT * FROM gov_meetings WHERE id = $1', [meetingId]);
+  if (!meeting || meeting.status !== 'in_progress') return null;
+
+  const currentIdx = MEETING_PHASES.indexOf(meeting.phase as MeetingPhase);
+  if (currentIdx === -1 || currentIdx >= MEETING_PHASES.length - 1) {
+    // Last phase — complete the meeting
+    await completeMeeting(meetingId);
+    return null;
+  }
+
+  const nextPhase = MEETING_PHASES[currentIdx + 1];
+
+  await execute(
+    'UPDATE gov_meetings SET phase = $1 WHERE id = $2',
+    [nextPhase, meetingId]
+  );
+
+  await addMessage(meetingId, 'system', 'Governance', `Phase: ${formatPhase(nextPhase)}`, 'phase_change');
+  broadcast('meeting_phase', { meetingId, phase: nextPhase, phaseIndex: currentIdx + 1 });
+
+  console.log(`[Meeting] ${meetingId} → ${nextPhase}`);
+
+  // Schedule next advance
+  schedulePhaseAdvance(meetingId);
+
+  return nextPhase;
+}
+
+export async function haltMeeting(meetingId: string): Promise<void> {
+  if (phaseTimer) {
+    clearTimeout(phaseTimer);
+    phaseTimer = null;
+  }
+
+  await execute(
+    "UPDATE gov_meetings SET status = 'halted', ended_at = NOW() WHERE id = $1",
+    [meetingId]
+  );
+
+  await addMessage(meetingId, 'system', 'Governance', 'Meeting halted by operator.', 'system');
+  broadcast('meeting_halted', { meetingId });
+
+  if (activeMeetingId === meetingId) {
+    activeMeetingId = null;
+  }
+
+  console.log(`[Meeting] Halted: ${meetingId}`);
+}
+
+async function completeMeeting(meetingId: string): Promise<void> {
+  if (phaseTimer) {
+    clearTimeout(phaseTimer);
+    phaseTimer = null;
+  }
+
+  await execute(
+    "UPDATE gov_meetings SET status = 'completed', phase = 'adjournment', ended_at = NOW() WHERE id = $1",
+    [meetingId]
+  );
+
+  await addMessage(meetingId, 'system', 'Governance', 'Meeting adjourned.', 'system');
+  broadcast('meeting_completed', { meetingId });
+
+  if (activeMeetingId === meetingId) {
+    activeMeetingId = null;
+  }
+
+  console.log(`[Meeting] Completed: ${meetingId}`);
+}
+
+export async function addMessage(
+  meetingId: string,
+  agentId: string,
+  agentName: string,
+  message: string,
+  messageType: string = 'update'
+): Promise<void> {
+  await query(
+    `INSERT INTO gov_meeting_messages (meeting_id, agent_id, agent_name, message, message_type)
+     VALUES ($1, $2, $3, $4, $5)`,
+    [meetingId, agentId, agentName, message, messageType]
+  );
+
+  broadcast('meeting_message', { meetingId, agentId, agentName, message, messageType });
+}
+
+function schedulePhaseAdvance(meetingId: string): void {
+  if (phaseTimer) clearTimeout(phaseTimer);
+
+  // Auto-advance after phase duration
+  phaseTimer = setTimeout(async () => {
+    try {
+      const meeting = await queryOne<Meeting>('SELECT * FROM gov_meetings WHERE id = $1', [meetingId]);
+      if (meeting && meeting.status === 'in_progress') {
+        await advancePhase(meetingId);
+      }
+    } catch (err: any) {
+      console.error('[Meeting] Auto-advance error:', err.message);
+    }
+  }, PHASE_DURATION_MS['current_business'] || 120_000);
+}
+
+function formatPhase(phase: string): string {
+  return phase.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+}
+
+// ─── Queries ─────────────────────────────────────
+
+export async function listMeetings(limit: number = 20): Promise<Meeting[]> {
+  return query<Meeting>(
+    'SELECT * FROM gov_meetings ORDER BY created_at DESC LIMIT $1',
+    [limit]
+  );
+}
+
+export async function getMeeting(id: string): Promise<Meeting | null> {
+  return queryOne<Meeting>('SELECT * FROM gov_meetings WHERE id = $1', [id]);
+}
+
+export async function getMessages(meetingId: string): Promise<any[]> {
+  return query(
+    'SELECT * FROM gov_meeting_messages WHERE meeting_id = $1 ORDER BY created_at ASC',
+    [meetingId]
+  );
+}
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..efb8eb2
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,205 @@
+// ═══════════════════════════════════════════════
+// DW Boardroom Governance — Core Types
+// ═══════════════════════════════════════════════
+
+// Decision classification: A=Autonomous, B=Advisory, C=Controlled
+export type DecisionType = 'A' | 'B' | 'C';
+export type DecisionStatus = 'pending' | 'approved' | 'rejected' | 'modified' | 'deferred' | 'auto_approved';
+export type HumanDecision = 'approve' | 'reject' | 'modify' | 'defer';
+export type GovernanceMode = 'supervised' | 'autonomous';
+export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
+export type DelegationStatus = 'active' | 'completed' | 'revoked' | 'expired';
+export type InitiativeStatus = 'proposed' | 'active' | 'paused' | 'completed' | 'cancelled';
+export type MeetingStatus = 'scheduled' | 'in_progress' | 'completed' | 'halted' | 'crashed';
+
+// Meeting phases — ported from boardroom meeting-engine.js
+export const MEETING_PHASES = [
+  'caucus', 'call_to_order', 'old_business', 'current_business',
+  'breakout', 'new_business', 'minutes', 'adjournment'
+] as const;
+export type MeetingPhase = typeof MEETING_PHASES[number];
+
+// ─── Core Models ────────────────────────────────
+
+export interface Decision {
+  id: string;
+  topic: string;
+  decision_type: DecisionType;
+  recommendation: string | null;
+  human_decision: HumanDecision | null;
+  owner: string;
+  autonomy_level: number;
+  deadline: string | null;
+  status: DecisionStatus;
+  source: string | null;
+  score: number | null;
+  reasoning: string | null;
+  notes: string | null;
+  created_at: string;
+  resolved_at: string | null;
+}
+
+export interface DelegationContract {
+  id: string;
+  decision_id: string | null;
+  owner: string;
+  delegate: string;
+  autonomy_level: number;
+  metric: string | null;
+  checkpoint_date: string | null;
+  status: DelegationStatus;
+  routing_keywords: string[] | null;
+  hierarchy_path: string | null;
+  created_at: string;
+  completed_at: string | null;
+}
+
+export interface Initiative {
+  id: string;
+  title: string;
+  description: string | null;
+  driver_agent: string;
+  kpi: string | null;
+  risk_level: RiskLevel;
+  capital_allocated: number;
+  status: InitiativeStatus;
+  progress: number;
+  created_at: string;
+  updated_at: string;
+}
+
+export interface Meeting {
+  id: string;
+  meeting_type: string;
+  phase: string;
+  started_at: string | null;
+  ended_at: string | null;
+  status: MeetingStatus;
+  attendees: string[];
+  summary: string | null;
+  action_items: any[];
+  created_at: string;
+}
+
+export interface MeetingMessage {
+  id: number;
+  meeting_id: string;
+  agent_id: string;
+  agent_name: string;
+  message: string;
+  message_type: string;
+  created_at: string;
+}
+
+export interface EscalationEntry {
+  id: number;
+  decision_id: string | null;
+  escalation_type: string;
+  hours_elapsed: number | null;
+  action_taken: string | null;
+  escalated_to: string | null;
+  created_at: string;
+}
+
+export interface GovernanceConfig {
+  key: string;
+  value: any;
+  updated_at: string;
+}
+
+// ─── Agenda ─────────────────────────────────────
+
+export interface AgendaItem {
+  driver: string;
+  topic: string;
+  recommendation: string;
+  decisionType: DecisionType;
+  decisionRequired: boolean;
+}
+
+export interface MeetingRequest {
+  meetingType: string;
+  agendaItems?: AgendaItem[];
+}
+
+// ─── WebSocket Events ───────────────────────────
+
+export type WSEventType =
+  | 'connected'
+  | 'meeting_phase'
+  | 'meeting_started'
+  | 'meeting_halted'
+  | 'meeting_completed'
+  | 'meeting_message'
+  | 'new_decision'
+  | 'decision_resolved'
+  | 'escalation'
+  | 'delegation_created'
+  | 'config_changed';
+
+export interface WSEvent {
+  type: WSEventType;
+  payload: any;
+  timestamp: string;
+}
+
+// ─── Task Scoring (ported from task-reaper.js) ──
+
+export const SCORE_THRESHOLDS = {
+  VETO_CEILING: 2,      // 0-2 = auto-cancel
+  RESOLVE_CEILING: 6,   // 3-6 = auto-approve if autonomy >= 4
+  // 7-10 = always requires human decision
+} as const;
+
+// ─── Agent Hierarchy (ported from delegation-engine.js) ──
+
+export const EXEC_DELEGATES: Record<string, string[]> = {
+  ceo: ['frank', 'connie', 'chet', 'george', 'ken'],
+  cfo: ['grant', 'nash', 'hugh', 'annie', 'shane'],
+  coo: ['max', 'hawk', 'clay', 'rex', 'steve', 'abby'],
+  cto: ['vince', 'link', 'nate', 'ross'],
+  vpops: [
+    'atlas', 'bob', 'sage', 'faye', 'drew', 'chase', 'blake', 'dean', 'clark', 'clyde',
+    'ralph', 'sasha', 'knox', 'cleo', 'elise', 'artie',
+    'kira', 'maya', 'pj', 'ines', 'beau', 'tate',
+    'brock', 'york', 'graham', 'jade', 'will', 'scout'
+  ],
+};
+
+// Keyword → agent routing (ported from delegation-engine.js TASK_KEYWORDS)
+export const TASK_KEYWORDS: Record<string, string> = {
+  legal: 'frank', compliance: 'frank', trademark: 'frank', settlement: 'frank',
+  competitor: 'connie', competitive: 'connie', market: 'connie',
+  slack: 'chet', communications: 'chet', message: 'chet', notify: 'chet',
+  email: 'george', gmail: 'george', inbox: 'george',
+  trading: 'ken', trade: 'ken', investment: 'ken',
+  finance: 'grant', accounting: 'grant', ledger: 'grant', revenue: 'grant',
+  expense: 'nash', cost: 'nash', spending: 'nash',
+  purchasing: 'hugh', procurement: 'hugh', vendor: 'hugh', purchase: 'hugh',
+  pricing: 'annie', arbitrage: 'annie', price: 'annie', margin: 'annie',
+  shopify: 'shane', store: 'shane', product: 'shane', order: 'shane',
+  monitor: 'hawk', alert: 'hawk', cpu: 'hawk', memory: 'hawk',
+  schedule: 'clay', cron: 'clay', recurring: 'clay',
+  timeout: 'rex', process: 'rex', zombie: 'rex', frozen: 'rex',
+  server: 'steve', infrastructure: 'steve', nginx: 'steve',
+  uptime: 'vince', website: 'vince', ssl: 'vince',
+  central: 'link', coordination: 'link', sync: 'link',
+  agenda: 'abby',
+  directory: 'ross', agent: 'ross',
+  notes: 'nate', minutes: 'nate',
+  blog: 'bob', content: 'bob', seo: 'bob', article: 'bob',
+  trend: 'sage', forecast: 'sage', research: 'sage',
+  room: 'faye', staging: 'faye', scene: 'faye', furniture: 'faye',
+  sample: 'drew', digital: 'drew',
+  image: 'chase', ghost: 'chase',
+  collection: 'dean', collections: 'dean',
+  sku: 'clyde', audit: 'clyde',
+  pipeline: 'blake', scheduler: 'blake',
+  update: 'clark', updates: 'clark',
+  'ralph lauren': 'ralph', schumacher: 'sasha', kravet: 'knox',
+  'cole & son': 'cleo', 'cole son': 'cleo', elitis: 'elise', arte: 'artie',
+  koroseal: 'kira', 'maya romanoff': 'maya', 'phillip jeffries': 'pj',
+  innovations: 'ines', bespoke: 'beau', thibaut: 'tate',
+  brewster: 'brock', york: 'york', 'graham & brown': 'graham', 'graham brown': 'graham',
+  'glass bead': 'jade', catalog: 'atlas', scrape: 'scout', crawl: 'scout',
+};
diff --git a/src/ws/broadcast.ts b/src/ws/broadcast.ts
new file mode 100644
index 0000000..e3dec82
--- /dev/null
+++ b/src/ws/broadcast.ts
@@ -0,0 +1,53 @@
+import { WebSocketServer, WebSocket } from 'ws';
+import { Server } from 'http';
+import { WSEvent, WSEventType } from '../types';
+
+let wss: WebSocketServer | null = null;
+
+export function initWebSocket(server: Server): void {
+  wss = new WebSocketServer({ server, path: '/ws' });
+
+  wss.on('connection', (ws) => {
+    console.log('[Governance WS] Client connected');
+    ws.send(JSON.stringify({
+      type: 'connected',
+      payload: { message: 'DW Governance WebSocket connected' },
+      timestamp: new Date().toISOString()
+    }));
+  });
+
+  console.log('[Governance WS] WebSocket server ready on /ws');
+}
+
+export function broadcast(type: WSEventType, payload: any): void {
+  if (!wss) return;
+
+  const event: WSEvent = {
+    type,
+    payload,
+    timestamp: new Date().toISOString()
+  };
+
+  const data = JSON.stringify(event);
+  let sent = 0;
+
+  wss.clients.forEach((client) => {
+    if (client.readyState === WebSocket.OPEN) {
+      client.send(data);
+      sent++;
+    }
+  });
+
+  if (sent > 0) {
+    console.log(`[Governance WS] Broadcast ${type} to ${sent} client(s)`);
+  }
+}
+
+export function getConnectionCount(): number {
+  if (!wss) return 0;
+  let count = 0;
+  wss.clients.forEach((client) => {
+    if (client.readyState === WebSocket.OPEN) count++;
+  });
+  return count;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..ec58f17
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,18 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "module": "commonjs",
+    "lib": ["ES2020"],
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "resolveJsonModule": true,
+    "declaration": true,
+    "sourceMap": true,
+    "forceConsistentCasingInFileNames": true
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules", "dist", "frontend"]
+}

(oldest)  ·  back to Dw Boardroom Governance  ·  Fix meeting phase auto-advance to use per-phase durations in a6dac82 →