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
110 changes: 50 additions & 60 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,6 @@ jobs:
- name: Test the real-repository hot-path benchmark harness
run: python3 scripts/test-bench-hot-paths.py

- name: Test the unused tool-context matching rules
run: python3 scripts/test-measure-unused-tool-context.py

# Configure the output base and restore caches before the first Bazel
# invocation so this job does not start two independent Bazel servers.
- uses: ./.github/actions/setup-bazel
Expand All @@ -190,6 +187,15 @@ jobs:
&& ((github.actor_id == '25274700' && vars.TS_BAZEL_CLIENT_ID)
|| (github.actor_id == '158243242' && vars.TS_BAZEL_BOT_CLIENT_ID)) || '' }}

- name: Test repository policies with Bazel workers
run: >-
bazel test --config=ci --test_output=errors
//scripts:unused_tool_context_measure_test
//scripts:sqlite_plan_stability_check_test
//scripts:ambient_tokio_check_test
//scripts:rusqlite_storage_boundary_test
//scripts:packaged_lsp_bridge_test

- name: Test the ranked-tail trim replay against the Rust rule cases
run: bazel run //scripts:test-measure-ranked-tail-trim.py

Expand All @@ -206,7 +212,6 @@ jobs:
# once here instead of once per release matrix target.
- name: Test release packagers
run: |
python3 scripts/test-check-packaged-lsp-bridge.py
python3 scripts/test-build-mcpb.py
python3 scripts/test-package-release-archive.py
python3 scripts/test-plan-release-recovery.py
Expand Down Expand Up @@ -241,22 +246,16 @@ jobs:

# Every SQLite connection plans with SQLITE_DBCONFIG_ENABLE_QPSG, so a
# bound partial-index predicate or LIKE pattern silently scans.
- name: Test and check SQL plans stay index-backed under QPSG
run: |
python3 scripts/test-check-sqlite-plan-stability.py
python3 scripts/check-sqlite-plan-stability.py
- name: Check SQL plans stay index-backed under QPSG
run: python3 scripts/check-sqlite-plan-stability.py

# `daemon run` drives its futures on native workers, which have no
# ambient Tokio runtime; production code names its Tokio handle.
- name: Test and check production code has no ambient Tokio call
run: |
python3 scripts/test-check-ambient-tokio.py
python3 scripts/check-ambient-tokio.py
- name: Check production code has no ambient Tokio call
run: python3 scripts/check-ambient-tokio.py

- name: Test and check rusqlite stays inside storage owners
run: |
python3 scripts/test-check-rusqlite-storage-boundary.py
python3 scripts/check-rusqlite-storage-boundary.py
- name: Check rusqlite stays inside storage owners
run: python3 scripts/check-rusqlite-storage-boundary.py

# The macOS and Windows lanes run the same Bazel workspace the Linux
# authority does, on demand: they exercise target-specific behavior the
Expand Down Expand Up @@ -761,68 +760,59 @@ jobs:
# Do not serialize them behind repository-wide Rust formatting and gates.
dashboard:
name: Dashboard
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: ./.github/actions/setup-bazel
id: bazel
with:
cache-key: ci-dashboard
tailscale-client-id: >-
${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
&& ((github.actor_id == '25274700' && vars.TS_BAZEL_CLIENT_ID)
|| (github.actor_id == '158243242' && vars.TS_BAZEL_BOT_CLIENT_ID)) || '' }}
Comment on lines +774 to +778

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Hosted dashboard cache never warms

On hosted-only runs, dashboard restores the ci-dashboard action cache but never saves its new Bazel outputs. No other job saves that cache key, so later runs repeatedly miss these outputs.

Learn more

The shared setup action restores the job's named Bazel disk cache when the Tailscale client ID is empty. It does not save that cache after the build; other Bazel jobs explicitly use actions/cache/save with steps.bazel.outputs.disk-cache-path and disk-cache-key. This job introduces a distinct ci-dashboard key but lacks a matching save step, so master cannot populate the cache restored by later hosted-only jobs.

Example: A hosted master dashboard build populates its local action cache and completes. A later fork PR restores ci-dashboard, finds no saved master entry, and repeats those actions.

Recommended fix: Add a master-only, always-on save step to dashboard, following the other Bazel jobs and using the bazel step's output path and key. Keep it conditional on the key being nonempty so fleet runs do not attempt a disk-cache save.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.


- name: Check frontend types, tests, and artifacts
run: >-
bazel test --config=ci --test_output=errors
//dashboard:typecheck //dashboard:unit_test
//dashboard:bundle_sha256 //dashboard:bundle_determinism_test
//sdks/typescript:typecheck //sdks/typescript:unit_test
//plugin/chatgpt-extension:typecheck
//plugin/chatgpt-extension:adapter_test

- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "26"

# Committed embedded bytes are generated with the pnpm package layout.
# Keep this comparison in that same layout; Bazel's package paths change
# bundler module IDs even when the source and dependency versions match.
- uses: ./.github/actions/setup-pnpm

- name: Typecheck dashboard consumers
working-directory: dashboard
run: pnpm run typecheck

- name: Typecheck TypeScript SDK
working-directory: sdks/typescript
run: pnpm run typecheck

- name: Run TypeScript SDK unit tests
working-directory: sdks/typescript
run: pnpm test

- name: Build dashboard assets
working-directory: dashboard
run: pnpm run build

- name: Run dashboard unit tests
working-directory: dashboard
run: pnpm test

# The dashboard is one rsbuild app emitting `app-dist/` (the legacy
# shell/holographic/lcm/graph/savings bundle split no longer exists), so
# the bundle validator is the artifact authority and the determinism
# check hashes whatever the build actually produced.
- name: Verify embedded dist artifacts
working-directory: dashboard
run: |
set -euo pipefail
python3 ../scripts/check-dashboard-bundle.py app-dist
(cd app-dist && find . -type f | sort | xargs sha256sum) > /tmp/dashboard-dist.sha
pnpm run build
(cd app-dist && find . -type f | sort | xargs sha256sum) | diff /tmp/dashboard-dist.sha -

- name: Build TypeScript SDK for the ChatGPT extension
- name: Build TypeScript SDK for the embedded artifact check
working-directory: sdks/typescript
run: pnpm run build

- name: Typecheck ChatGPT extension
working-directory: plugin/chatgpt-extension
run: pnpm run typecheck

# extension.test.ts needs a tracedecay binary, which this job does not
# build; the adapter tests run against a fake `serve`.
- name: Run ChatGPT extension adapter tests
working-directory: plugin/chatgpt-extension
run: pnpm exec vitest run test/adapter.test.ts

- name: Verify ChatGPT extension embedded artifacts
working-directory: plugin/chatgpt-extension
run: pnpm run check:embedded

- name: Upload failed frontend test diagnostics
if: failure()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: frontend-bazel-test-logs
path: |
bazel-testlogs/dashboard/**/test.log
bazel-testlogs/sdks/typescript/**/test.log
bazel-testlogs/plugin/chatgpt-extension/**/test.log

hermes-integration:
name: Hermes integration (stock)
needs: [scope-gate, debug-cli]
Expand Down
3 changes: 3 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ npm.npm_translate_lock(
"//dashboard:package.json",
],
pnpm_lock = "//:pnpm-lock.yaml",
# React Router imports React types from the shared npm store, outside
# dashboard/node_modules. Make that peer declaration resolvable there.
public_hoist_packages = {"@types/react": [""]},
verify_node_modules_ignored = "//:.bazelignore",
)
use_repo(npm, "npm")
20 changes: 20 additions & 0 deletions dashboard/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
load("@aspect_rules_js//js:defs.bzl", "js_test")
load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("@npm//dashboard:@rsbuild/core/package_json.bzl", rsbuild_bin = "bin")
load("@npm//dashboard:vitest/package_json.bzl", vitest_bin = "bin")
load("@npm//dashboard:typescript/package_json.bzl", tsc_bin = "bin")

package(default_visibility = ["//visibility:public"])

Expand Down Expand Up @@ -37,6 +39,24 @@ vitest_bin.vitest_test(
tags = ["cpu:4"],
)

tsc_bin.tsc_test(
name = "typecheck",
include_types = True,
include_transitive_types = True,
data = [":dashboard_sources", ":node_modules", "//:node_modules/@types/react"] + glob(["codegen/**", "e2e/**", "stories/**"]),
args = ["--noEmit"],
chdir = package_name(),
)

js_test(
name = "bundle_determinism_test",
entry_point = "scripts/check-bundle-determinism.mjs",
data = [":dashboard_sources", ":node_modules"],
chdir = package_name(),
exec_properties = {"cpu_count": "4"},
tags = ["cpu:4"],
)

rsbuild_bin.rsbuild(
name = "app_dist",
srcs = [
Expand Down
35 changes: 35 additions & 0 deletions dashboard/scripts/check-bundle-determinism.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import { spawnSync } from "node:child_process";

const require = createRequire(import.meta.url);
const packageJson = require.resolve("@rsbuild/core/package.json");
const cli = path.resolve(path.dirname(packageJson), "bin/rsbuild.js");

// Independently rebuild twice: a cache hit cannot prove determinism.
const temporary = await mkdtemp(path.join(tmpdir(), "dashboard-determinism-"));
try {
const outputs = [];
const destination = path.join(temporary, "app-dist");
for (let attempt = 0; attempt < 2; attempt++) {
const result = spawnSync(process.execPath, [cli, "build"], {
env: { ...process.env, TRACEDECAY_DASHBOARD_DIST_PATH: destination },
stdio: "inherit",
});
if (result.error) throw result.error;
assert.equal(result.status, 0, "dashboard build failed");
const entries = await readdir(destination, { recursive: true, withFileTypes: true });
const files = entries.filter((entry) => entry.isFile()).map((entry) =>
path.relative(destination, path.join(entry.parentPath, entry.name))).sort();
assert.ok(files.length > 0, "dashboard build emitted no files");
outputs.push(await Promise.all(files.map(async (file) =>
[file, createHash("sha256").update(await readFile(path.join(destination, file))).digest("hex")])));
}
assert.deepEqual(outputs[0], outputs[1], "dashboard builds must be byte-identical");
} finally {
await rm(temporary, { recursive: true, force: true });
}
18 changes: 18 additions & 0 deletions plugin/chatgpt-extension/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
load("@npm//plugin/chatgpt-extension:typescript/package_json.bzl", tsc_bin = "bin")
load("@npm//plugin/chatgpt-extension:vitest/package_json.bzl", vitest_bin = "bin")
load("@npm//:defs.bzl", "npm_link_all_packages")

npm_link_all_packages(name = "node_modules")
Expand All @@ -6,3 +8,19 @@ filegroup(
srcs = glob(["**/*"]),
visibility = ["//visibility:public"],
)

tsc_bin.tsc_test(
name = "typecheck",
include_types = True,
include_transitive_types = True,
data = [":sources", ":node_modules", "//sdks/typescript:dist"],
args = ["--noEmit"],
chdir = package_name(),
)

vitest_bin.vitest_test(
name = "adapter_test",
data = [":sources", ":node_modules", "//sdks/typescript:dist"],
args = ["run", "test/adapter.test.ts"],
chdir = package_name(),
)
7 changes: 5 additions & 2 deletions plugin/chatgpt-extension/test/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { createExtensionServer } from "../src/server/main.js";
import { DaemonFailure, ServeSession } from "../src/server/serve-client.js";

const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
// MCP deliberately filters child environments; use the declared Node runtime,
// not Bazel's wrapper, which depends on launcher-only environment variables.
const NODE_BINARY = process.env.JS_BINARY__NODE_BINARY ?? process.execPath;
const FAKE_SERVE = path.join(PACKAGE_ROOT, "test", "fake-serve.mjs");
const ASSETS = { html: "<!doctype html><html></html>", iconSvg: "<svg xmlns='http://www.w3.org/2000/svg'/>" };

Expand All @@ -19,7 +22,7 @@ afterEach(async () => {
});

function fakeSession(): ServeSession {
const session = new ServeSession({ binary: FAKE_SERVE, projectRoot: null, env: process.env, cwd: PACKAGE_ROOT });
const session = new ServeSession({ binary: FAKE_SERVE, projectRoot: null, env: { ...process.env, PATH: `${path.dirname(NODE_BINARY)}${path.delimiter}${process.env.PATH ?? ""}` }, cwd: PACKAGE_ROOT });
cleanups.push(() => session.close());
return session;
}
Expand All @@ -41,7 +44,7 @@ describe("embedded server entry", () => {
await symlink(path.join(PACKAGE_ROOT, "embedded", "server.mjs"), link);

const client = new Client({ name: "symlink-test", version: "0.0.0" });
await client.connect(new StdioClientTransport({ command: process.execPath, args: [link], cwd: dir }));
await client.connect(new StdioClientTransport({ command: NODE_BINARY, args: [link], cwd: dir }));
cleanups.push(() => client.close());

expect(client.getServerVersion()?.name).toBe("tracedecay-chatgpt-extension");
Expand Down
10 changes: 10 additions & 0 deletions sdks/typescript/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_library")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("@npm//sdks/typescript:vitest/package_json.bzl", vitest_bin = "bin")
load("@npm//sdks/typescript:typescript/package_json.bzl", tsc_bin = "bin")
load("@npm//sdks/typescript:@rslib/core/package_json.bzl", rslib_bin = "bin")

npm_link_all_packages(name = "node_modules")
Expand Down Expand Up @@ -37,3 +38,12 @@ vitest_bin.vitest_test(
args = ["run", "--exclude", "test/installed-package.test.ts"],
chdir = package_name(),
)

tsc_bin.tsc_test(
name = "typecheck",
include_types = True,
include_transitive_types = True,
data = [":pkg", ":node_modules"] + glob(["test/**", "*.json", "*.ts"]),
args = ["-p", "tsconfig.json", "--noEmit"],
chdir = package_name(),
)
Loading