Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .corp-harness/site.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
3 changes: 2 additions & 1 deletion .cursor/hooks/verify-on-stop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"


Expand Down
13 changes: 8 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
3 changes: 3 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -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
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Read the thesis: [`docs/future-of-the-firm.md`](docs/future-of-the-firm.md).
## Demo

<p align="center">
<img src="docs/assets/demo.gif" alt="CorpOS ops console — capital and trust, company day result, and Governor audit controls (synthetic demo data)" width="900" />
<img src="docs/assets/demo.gif" alt="CorpOS ops console — agents hand off a company day (support → finance → ops), trust unlock, and Governor (synthetic demo data)" width="900" />
</p>

### Screenshots
Expand All @@ -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.

---

Expand Down
16 changes: 16 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
6 changes: 6 additions & 0 deletions apps/console/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CorpOS Ops Console</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=IBM+Plex+Sans:wght@400;600&family=IBM+Plex+Serif:wght@500&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="app"></div>
Expand Down
140 changes: 132 additions & 8 deletions apps/console/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Firm | null>(null);
const [exceptions, setExceptions] = useState<Exception[]>([]);
const [day, setDay] = useState<Record<string, unknown> | null>(null);
const [contracts, setContracts] = useState<Contract[]>([]);
const [day, setDay] = useState<CompanyDay | null>(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 (
<div class="shell">
<header>
Expand All @@ -58,8 +122,8 @@ function App() {
</p>
</div>
<div class="actions">
<button type="button" onClick={() => void runDay()}>
Run company day
<button type="button" disabled={running} onClick={() => void runDay()}>
{running ? "Running…" : "Run company day"}
</button>
<button
type="button"
Expand Down Expand Up @@ -108,8 +172,64 @@ function App() {
)}
</section>
<section>
<h2>Contracts</h2>
{contracts.length === 0 && !day?.contractId ? (
<p class="muted">No open contracts</p>
) : (
<ul>
{contracts.map((c) => (
<li key={c.id}>
<strong>{c.title}</strong> {c.state}
</li>
))}
{day?.contractId && !contracts.some((c) => c.id === day.contractId) ? (
<li key={day.contractId}>
<strong>Company day</strong> {day.contractId} (demo run)
</li>
) : null}
</ul>
)}
</section>
<section
class="company-day"
data-company-day={dayStatus}
data-timeline-visible={String(visibleCount)}
>
<h2>Company day</h2>
{day ? <pre>{JSON.stringify(day, null, 2)}</pre> : <p class="muted">Not run yet</p>}
{!day ? (
<p class="muted">Not run yet — watch agents hand off work across the firm.</p>
) : (
<>
<div class="metrics" data-testid="day-metrics">
<span>
Handoffs <strong>{day.handoffs}</strong>
</span>
<span>
Autonomous <strong>{day.autonomousSettles}</strong>
</span>
<span>
Exceptions <strong>{day.exceptionSettles}</strong>
</span>
<span>
Trust <strong>{day.trustAfter}</strong>
</span>
<span>
Status <strong>{day.ok ? "ok" : "incomplete"}</strong>
</span>
</div>
<ol class="timeline" aria-live="polite">
{day.timeline.slice(0, visibleCount).map((evt) => (
<li key={evt.id} data-timeline-id={evt.id} data-timeline-kind={evt.kind}>
<div class="role">{evt.role}</div>
<div>
<p class="summary">{evt.summary}</p>
<span class="kind">{evt.kind.split("_").join(" ")}</span>
</div>
</li>
))}
</ol>
</>
)}
</section>
</main>
) : (
Expand All @@ -131,4 +251,8 @@ function App() {
);
}

render(<App />, document.getElementById("app")!);
const root = document.getElementById("app");
if (!root) {
throw new Error("CorpOS console root #app not found");
}
render(<App />, root);
Loading
Loading