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

- CorpOS ops console — capital and trust, company day result, and Governor audit controls (synthetic demo data) + CorpOS ops console — agents hand off a company day (support → finance → ops), trust unlock, and Governor (synthetic demo data)

### 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() {

-