diff --git a/.corp-harness/site.json b/.corp-harness/site.json
index 955662d..66d2cff 100644
--- a/.corp-harness/site.json
+++ b/.corp-harness/site.json
@@ -2,7 +2,7 @@
"schema": "corporate-site-site/v1",
"site_id": "corpos",
"corporate_program": "corpos-autonomous-company",
- "corporate_handoff_sha256": "c0bb90e70fdfe705192e435869c30ab401339ec6b47c0576be34d7f48bae1975",
+ "corporate_handoff_sha256": "0953f106918b8ffe1a61e30f3a69afbd0498bd44df695ff6b3c15c4b6915af68",
"verify_argv": ["./scripts/harness/verify.sh"],
"adversarial_argv": ["./scripts/harness/adversarial.sh"]
}
diff --git a/.cursor/hooks/verify-on-stop.py b/.cursor/hooks/verify-on-stop.py
index 85d3785..376c6a1 100755
--- a/.cursor/hooks/verify-on-stop.py
+++ b/.cursor/hooks/verify-on-stop.py
@@ -10,7 +10,8 @@
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _common import read_input, emit, log_event, resolve_workspace_root # noqa: E402
-TIMEOUT = 120
+# Full clean+ci verify can exceed 2m; default verify reuses node_modules.
+TIMEOUT = 300
VERIFY_SCRIPT = "scripts/verify.sh"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 10f596f..7f2418c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,9 +26,11 @@ jobs:
- name: Activate packageManager npm
run: |
corepack enable
- corepack prepare npm@10.9.2 --activate
+ corepack prepare npm@11.17.0 --activate
+ # setup-node's npm 10 stays on PATH; assert the pin via corepack npm.
+ test "$(corepack npm -v | cut -d. -f1)" -ge 11
- name: Site verify (build, typecheck, test, lint, format)
- run: ./scripts/harness/verify.sh
+ run: CORPOS_VERIFY_CLEAN=1 ./scripts/harness/verify.sh
adversarial:
runs-on: ubuntu-latest
@@ -43,8 +45,9 @@ jobs:
- name: Activate packageManager npm
run: |
corepack enable
- corepack prepare npm@10.9.2 --activate
- - run: npm ci
- - run: npm run build
+ corepack prepare npm@11.17.0 --activate
+ test "$(corepack npm -v | cut -d. -f1)" -ge 11
+ - run: corepack npm ci --include=dev
+ - run: corepack npm run build
- name: Site adversarial
run: ./scripts/harness/adversarial.sh
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..9f5f439
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,3 @@
+# Fail install if a dependency has install scripts not covered by package.json allowScripts.
+# Approvals are pinned in package.json "allowScripts" (esbuild + fsevents only).
+strict-allow-scripts=true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ac70088..3232e6a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,14 +21,16 @@ Please also follow the [Code of Conduct](CODE_OF_CONDUCT.md).
## Prerequisites
- **Node.js ≥ 22** (see [`.nvmrc`](.nvmrc); CI covers 22 and 24).
-- npm **10.9.2** via Corepack (`packageManager` in [`package.json`](package.json)).
+- npm **11.17.0** via Corepack (`packageManager` in [`package.json`](package.json)).
+ Required for enforceable `allowScripts` / `strict-allow-scripts`.
## Setup
```bash
-npm install
+npm ci --include=dev
npm run build
-./scripts/harness/verify.sh # typecheck · test · lint · format:check · stack guards
+./scripts/harness/verify.sh # build · typecheck · test · lint · format · stack guards
+# Force a clean reinstall (CI does this): CORPOS_VERIFY_CLEAN=1 ./scripts/harness/verify.sh
```
A contribution is not finished until `./scripts/harness/verify.sh` is green
diff --git a/Dockerfile b/Dockerfile
index 46ef1b8..714d9c3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,8 +5,8 @@ COPY packages ./packages
COPY apps ./apps
COPY scripts ./scripts
COPY tsconfig*.json vitest.config.ts eslint.config.mjs ./
-RUN corepack enable && corepack prepare npm@10.9.2 --activate \
- && npm ci && npm run build
+RUN corepack enable && corepack prepare npm@11.17.0 --activate \
+ && npm ci --include=dev && npm run build
ENV PORT=3000
EXPOSE 3000
CMD ["npm", "run", "start"]
diff --git a/README.md b/README.md
index 122fb6a..0e1cf52 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Read the thesis: [`docs/future-of-the-firm.md`](docs/future-of-the-firm.md).
## Demo
-
+
### Screenshots
@@ -33,7 +33,7 @@ Read the thesis: [`docs/future-of-the-firm.md`](docs/future-of-the-firm.md).
- **Local demo:** `npm install && npm run build && npm run dev` → [http://localhost:3000](http://localhost:3000)
- **Regenerate GIF/PNGs:** start the console, then `npm run screenshots` — see [`docs/assets/README.md`](docs/assets/README.md)
-Click **Run company day** for the multi-handoff demo (support → finance → ops), including an autonomous settle, an exception approval path, compensation, and trust unlock.
+Click **Run company day** to watch the agent activity timeline: support → finance → ops handoffs, an autonomous settle, an exception approval path, compensation, and trust unlock.
---
diff --git a/SECURITY.md b/SECURITY.md
index 02e6774..5313472 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -22,6 +22,22 @@ Treat it as a design artifact you can run locally.
When `CORPOS_MODE=shared`, `DASHBOARD_API_TOKEN` is required for approval and kill mutations.
+## Dependency / supply-chain hygiene
+
+- Install from the committed `package-lock.json` via `npm ci --include=dev`.
+- `packageManager` is **npm@11.17.0** (Corepack). npm 10 cannot enforce `allowScripts`.
+- Registry must remain `https://registry.npmjs.org/` — no project `.npmrc` registry override.
+- Install scripts are **opt-in and enforced**: `package.json` `allowScripts` pins only
+ `esbuild` / `fsevents` versions in the lockfile; `.npmrc` sets `strict-allow-scripts=true`.
+ `./scripts/harness/verify.sh` fails if `npm approve-scripts --allow-scripts-pending` reports gaps.
+- Verify does **not** wipe `node_modules` by default (avoids concurrent `ENOTEMPTY` races).
+ CI sets `CORPOS_VERIFY_CLEAN=1` for a full reinstall. Never fetch npm via `npx` inside verify.
+- `@hono/node-server` is pinned to `2.0.11` via root dependency + `overrides` (covers API
+ `serveStatic` and `@modelcontextprotocol/sdk`).
+- Review Dependabot PRs before merge; re-run `npm approve-scripts --allow-scripts-pending`
+ after lockfile changes that introduce new install scripts.
+- Do **not** set `dangerously-allow-all-scripts=true` in CI.
+
## Reporting
Use GitHub Security Advisories on the CorpOS repository.
diff --git a/apps/api/package.json b/apps/api/package.json
index 5d6807a..c2a4a1e 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -12,7 +12,7 @@
},
"dependencies": {
"@corpos/core": "0.2.0",
- "@hono/node-server": "^1.14.4",
+ "@hono/node-server": "^2.0.11",
"drizzle-orm": "^0.45.2",
"hono": "^4.12.32"
},
diff --git a/apps/console/index.html b/apps/console/index.html
index 347e35c..de6a587 100644
--- a/apps/console/index.html
+++ b/apps/console/index.html
@@ -4,6 +4,12 @@
CorpOS Ops Console
+
+
+
diff --git a/apps/console/src/main.tsx b/apps/console/src/main.tsx
index bdb5702..f612fbe 100644
--- a/apps/console/src/main.tsx
+++ b/apps/console/src/main.tsx
@@ -24,29 +24,93 @@ type Exception = {
agentId: string;
};
+type Contract = {
+ id: string;
+ title: string;
+ state: string;
+ assigneesJson?: string;
+};
+
+type TimelineEvent = {
+ id: string;
+ agentId: string;
+ role: string;
+ kind: string;
+ summary: string;
+};
+
+type CompanyDay = {
+ contractId: string;
+ handoffs: number;
+ autonomousSettles: number;
+ exceptionSettles: number;
+ compensated: number;
+ trustAfter: number;
+ slaExceptions: number;
+ auditHead: string;
+ ok: boolean;
+ timeline: TimelineEvent[];
+};
+
+const REVEAL_MS = 500;
+
function App() {
const [firm, setFirm] = useState(null);
const [exceptions, setExceptions] = useState([]);
- const [day, setDay] = useState | null>(null);
+ const [contracts, setContracts] = useState([]);
+ const [day, setDay] = useState(null);
+ const [visibleCount, setVisibleCount] = useState(0);
+ const [running, setRunning] = useState(false);
const [tab, setTab] = useState<"ops" | "governor">("ops");
const refresh = async () => {
const f = await fetch("/api/firm").then((r) => r.json());
const e = await fetch("/api/exceptions").then((r) => r.json());
+ const c = await fetch("/api/contracts").then((r) => r.json());
setFirm(f);
setExceptions(e);
+ setContracts(c);
};
useEffect(() => {
void refresh();
}, []);
+ useEffect(() => {
+ if (!day?.timeline.length) {
+ setVisibleCount(0);
+ return;
+ }
+ setVisibleCount(0);
+ let n = 0;
+ const id = window.setInterval(() => {
+ n += 1;
+ setVisibleCount(n);
+ if (n >= day.timeline.length) {
+ window.clearInterval(id);
+ }
+ }, REVEAL_MS);
+ return () => window.clearInterval(id);
+ }, [day]);
+
const runDay = async () => {
- const r = await fetch("/api/company-day", { method: "POST" }).then((res) => res.json());
- setDay(r);
- await refresh();
+ setRunning(true);
+ setDay(null);
+ setVisibleCount(0);
+ try {
+ const r = (await fetch("/api/company-day", { method: "POST" }).then((res) =>
+ res.json(),
+ )) as CompanyDay;
+ setDay(r);
+ await refresh();
+ } finally {
+ setRunning(false);
+ }
};
+ const timelineDone = Boolean(day && visibleCount >= day.timeline.length);
+ const dayStatus = !day ? "idle" : timelineDone ? "complete" : "running";
+
return (
@@ -58,8 +122,8 @@ function App() {
-