Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
41 changes: 32 additions & 9 deletions greenfield/docs/architecture/greenfield-rewrite/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
This matrix is the living phase status. Update it in the same change that materially advances or
closes a phase; dated entries below provide the evidence, not a second status source.

| Phase | Status | Current evidence and remaining gate |
| ----------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 — Evidence and qualification | Complete | All eight mandatory spikes pass on exact Bun revision `17d6843606d76620cb55d31424d7fb0aed51c367`: build, transport, cross-process SQLite/outbox, Drizzle/Bun SQLite, browser data, chat batching, shutdown, and capped resources. Source-derived parity and the OpenClaw source audit pass as additional evidence. |
| 1 — Foundation | Complete | The self-contained future root builds immutable browser/web/worker artifacts, protects project-local production state, installs exact Bun and systemd artifacts, migrates a database copy, atomically promotes the release/database pair, serves readiness/browser assets, writes project-local logs, and proves crash-safe rollback and shutdown in a disposable lifecycle. |
| 2 — Trust and transport | Complete for the stated server scope | Authentication, MFA, WebAuthn, automation credentials, audit, authenticated renewable SSE, one-shot native Gateway bootstrap verification, and the consolidated [threat model](../../security/greenfield-phase-two-threat-model.md) have executable evidence. Browser UI and production cutover remain later gates. |
| 3 — Core operator domains | Started | Task and agent-directory parity are implemented with durable history, realtime invalidation, and browser workflows. Monitoring ingestion plus report, incident, and notification server parity are implemented; report, incident, and global notification browser state are also complete. Dashboard-local durable schedules/jobs and real worker execution are implemented. The `/jobs` browser, OpenClaw cron, overview, and cache/metrics remain open. |
| 4 — Gateway and chat | Not started | The Phase 2 verifier is one-shot only. Persistent native Gateway lifecycle, current-protocol re-audit, sessions, chat journal/recovery, attachments, and frontend remain open. |
| 5 — Privileged and external domains | Not started | Worker-owned file/media, Docker, database, OpenClaw, GitHub, deployment, backup, and other privileged adapters remain open. |
| 6 — Parity, hardening, and cutover | Not started | Full UI parity, generated `/docs`, load/resource/restore evidence, cutover rehearsal, fresh production database, and legacy removal remain open. |
| Phase | Status | Current evidence and remaining gate |
| ----------------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 — Evidence and qualification | Complete | All eight mandatory spikes pass on exact Bun revision `17d6843606d76620cb55d31424d7fb0aed51c367`: build, transport, cross-process SQLite/outbox, Drizzle/Bun SQLite, browser data, chat batching, shutdown, and capped resources. Source-derived parity and the OpenClaw source audit pass as additional evidence. |
| 1 — Foundation | Complete | The self-contained future root builds immutable browser/web/worker artifacts, protects project-local production state, installs exact Bun and systemd artifacts, migrates a database copy, atomically promotes the release/database pair, serves readiness/browser assets, writes project-local logs, and proves crash-safe rollback and shutdown in a disposable lifecycle. |
| 2 — Trust and transport | Complete for the stated server scope | Authentication, MFA, WebAuthn, automation credentials, audit, authenticated renewable SSE, one-shot native Gateway bootstrap verification, and the consolidated [threat model](../../security/greenfield-phase-two-threat-model.md) have executable evidence. Browser UI and production cutover remain later gates. |
| 3 — Core operator domains | Started | Task and agent-directory parity are implemented with durable history, realtime invalidation, and browser workflows. Monitoring ingestion plus report, incident, and notification server parity are implemented; report, incident, and global notification browser state are also complete. Dashboard-local durable schedules/jobs, real worker execution, and their `/jobs` operator UI are implemented. OpenClaw cron, overview, and cache/metrics remain open. |
| 4 — Gateway and chat | Not started | The Phase 2 verifier is one-shot only. Persistent native Gateway lifecycle, current-protocol re-audit, sessions, chat journal/recovery, attachments, and frontend remain open. |
| 5 — Privileged and external domains | Not started | Worker-owned file/media, Docker, database, OpenClaw, GitHub, deployment, backup, and other privileged adapters remain open. |
| 6 — Parity, hardening, and cutover | Not started | Full UI parity, generated `/docs`, load/resource/restore evidence, cutover rehearsal, fresh production database, and legacy removal remain open. |

### 2026-08-03 — Phase 0 started

Expand Down Expand Up @@ -945,3 +945,26 @@ full-browser parity, production rehearsal, cutover, and legacy deletion remain o
Gateway, or host-mutation authority. The parity inventory marks the nine Dashboard jobs and
schedules operations implemented. The five `openClawCron.*` operations and `/jobs` browser
remain planned, along with overview and cache/metrics.

### 2026-08-08 — Phase 3 Dashboard-local jobs browser

- Authenticated `/jobs` now exposes the global queue summary, live worker inventory, versioned
claim pause/resume, filtered keyset-paginated run history, exact run detail, bounded event
history, and confirmed cancellation. Independent validated `runId` and `scheduleId` search
parameters preserve direct links outside the currently loaded pages and drop malformed values
without issuing detail requests.
- The code-owned schedule directory provides enabled-state filtering, exact detail, keyset run
history, a TanStack Form cadence editor for interval, daily, and five-field cron variants,
explicit disable-intent creation/replacement, re-enable, and caller-scoped idempotent manual
execution. A failed refresh cannot erase a validated mutation result or already cached detail.
- TanStack Query owns remote state and mutation repair; TanStack Table plus the shared virtualizer
bound maximum-sized run and schedule pages. A separate one-row queue snapshot refreshes live
worker freshness every 10 seconds without reloading accumulated history pages or emitting
heartbeat realtime spam. `jobs.runs` refreshes both queue/run and embedded schedule projections,
while `schedules.records` refreshes the schedule root. Both topics retain shared coalescing,
terminal resync, and 30-second fallback behavior.
- Route-level tests cover auth gating, navigation, independent deep links, malformed search,
list/detail isolation, overlapping-page deduplication, realtime refresh, pause, cancellation,
versioned schedule updates, and lost-response-safe manual execution. Legacy `/jobs` frontend
parity remains `planned` because the required OpenClaw cron half belongs to Phase 4; overview and
cache/metrics also remain open.
2 changes: 1 addition & 1 deletion greenfield/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"lint:fix": "oxlint . --fix --tsconfig tsconfig.bun.json --ignore-pattern 'src/browser/**' && oxlint src/browser --fix --tsconfig tsconfig.browser.json --no-error-on-unmatched-pattern",
"evidence:resources:sse": "bun src/test/integration/resources/runSseMemoryEvidence.ts",
"test": "bun run test:bun && bun run test:browser",
"test:browser": "bun scripts/runTestSuite.ts --preload ./src/browser/test/setup.ts src/browser",
"test:browser": "bun scripts/runBrowserTests.ts",
"test:bun": "bun scripts/runTestSuite.ts scripts src/app src/contracts src/server src/shared src/test src/worker",
"test:coverage": "bun scripts/runCoverage.ts",
"typecheck": "bun run typecheck:browser && bun run typecheck:bun",
Expand Down
115 changes: 115 additions & 0 deletions greenfield/scripts/runBrowserTests.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { describe, expect, test } from "bun:test";
import path from "node:path";

import {
browserTestPartitionForPath,
browserTestPartitionOwnsPath,
browserTestPartitions,
createBrowserTestArguments,
runBrowserTestShards,
} from "./runBrowserTests.ts";

const projectRoot = path.resolve(import.meta.dir, "..");

describe("browser test shard runner", () => {
test("builds exact, isolated, deterministic shard arguments", () => {
expect(createBrowserTestArguments("core")).toEqual([
"--preload",
"./src/browser/test/setup.ts",
"--max-concurrency=1",
"--bail=1",
"--path-ignore-patterns=src/browser/jobs/**",
"src/browser",
]);
expect(createBrowserTestArguments("jobs")).toEqual([
"--preload",
"./src/browser/test/setup.ts",
"--max-concurrency=1",
"--bail=1",
"--path-ignore-patterns=src/browser/jobs/ScheduleDetail{Form,State}.test.tsx",
"src/browser/jobs",
]);
expect(createBrowserTestArguments("schedule-detail-form")).toEqual([
"--preload",
"./src/browser/test/setup.ts",
"--max-concurrency=1",
"--bail=1",
"src/browser/jobs/ScheduleDetailForm.test.tsx",
]);
expect(createBrowserTestArguments("schedule-detail-state")).toEqual([
"--preload",
"./src/browser/test/setup.ts",
"--max-concurrency=1",
"--bail=1",
"src/browser/jobs/ScheduleDetailState.test.tsx",
]);
});

test("assigns every discovered browser test to exactly one shard", async () => {
const testPaths = await Array.fromAsync(
new Bun.Glob("src/browser/**/*.test.{ts,tsx}").scan({
cwd: projectRoot,
onlyFiles: true,
})
);

expect(testPaths.length).toBeGreaterThan(0);
for (const testPath of testPaths) {
const owners = browserTestPartitions.filter((partition) =>
browserTestPartitionOwnsPath(partition.name, testPath)
);
const owner = browserTestPartitionForPath(testPath);
expect(owners).toHaveLength(1);
expect(owner).toBe(owners[0]?.name);
if (testPath === "src/browser/jobs/ScheduleDetailForm.test.tsx") {
expect(owner).toBe("schedule-detail-form");
} else if (testPath === "src/browser/jobs/ScheduleDetailState.test.tsx") {
expect(owner).toBe("schedule-detail-state");
} else if (testPath.startsWith("src/browser/jobs/")) {
expect(owner).toBe("jobs");
} else {
expect(owner).toBe("core");
}
}
expect(browserTestPartitionForPath("src/server/example.test.ts")).toBeUndefined();
expect(
browserTestPartitionForPath("src/browser/jobs/example.ts")
).toBeUndefined();
});

test("runs fresh shards sequentially and stops on the first failure", async () => {
const calls: string[][] = [];
const exitCode = await runBrowserTestShards({
projectRoot: "/tmp/project",
runTests: (arguments_, receivedProjectRoot) => {
calls.push([...arguments_, receivedProjectRoot]);
return Promise.resolve(calls.length === 2 ? 17 : 0);
},
});

expect(exitCode).toBe(17);
expect(calls).toEqual([
[...createBrowserTestArguments("core"), "/tmp/project"],
[...createBrowserTestArguments("jobs"), "/tmp/project"],
]);
});

test("runs every later shard only after the previous shard passes", async () => {
const calls: string[][] = [];
const exitCode = await runBrowserTestShards({
projectRoot: "/tmp/project",
runTests: (arguments_, receivedProjectRoot) => {
calls.push([...arguments_, receivedProjectRoot]);
return Promise.resolve(0);
},
});

expect(exitCode).toBe(0);
expect(calls).toEqual([
[...createBrowserTestArguments("core"), "/tmp/project"],
[...createBrowserTestArguments("jobs"), "/tmp/project"],
[...createBrowserTestArguments("schedule-detail-form"), "/tmp/project"],
[...createBrowserTestArguments("schedule-detail-state"), "/tmp/project"],
]);
});
});
Loading
Loading