diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f63324d41..5dcc0ce3c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -67,4 +67,4 @@ jobs: git remote set-url origin \ "https://x-access-token:${release_token}@github.com/${GITHUB_REPOSITORY}.git" unset release_token - cargo dev release --nightly + cargo --locked dev release --nightly diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91519569b..4410f71f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,7 +84,7 @@ jobs: - uses: taiki-e/install-action@773334c0e05d7e699e4d78234494308223f3a2cf # nextest - name: Refresh embedded SPA - run: cargo dev spa refresh + run: cargo --locked dev spa refresh - name: Test (x86_64-musl) # nextest still shells through cargo test for this target, so @@ -93,22 +93,22 @@ jobs: env: CC_x86_64_unknown_linux_musl: musl-gcc CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc - run: cargo nextest run --workspace --target ${{ matrix.target }} --release --status-level slow --profile ci + run: cargo nextest run --locked --workspace --target ${{ matrix.target }} --release --status-level slow --profile ci - name: Test # aarch64-musl test runs have not been validated on the compile # runner yet; shipping binary is exercised via Docker smoke tests. # Re-enable after verifying the workspace passes on this target. if: matrix.target != 'aarch64-unknown-linux-musl' && matrix.target != 'x86_64-unknown-linux-musl' - run: cargo nextest run --workspace --target ${{ matrix.target }} --release --status-level slow --profile ci + run: cargo nextest run --locked --workspace --target ${{ matrix.target }} --release --status-level slow --profile ci - name: Build (musl via cargo-zigbuild) if: matrix.musl - run: cargo zigbuild --target ${{ matrix.target }} --release -p fabro-cli + run: cargo zigbuild --locked --target ${{ matrix.target }} --release -p fabro-cli - name: Build if: ${{ !matrix.musl }} - run: cargo build --target ${{ matrix.target }} --release -p fabro-cli + run: cargo build --locked --target ${{ matrix.target }} --release -p fabro-cli - name: Package run: | diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index cc3582ecf..c20fe22de 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -76,7 +76,7 @@ jobs: run: | ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*"disabled"' \ lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml - - run: cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings + - run: cargo +nightly-2026-04-14 clippy --locked --workspace --all-targets -- -D warnings generated-docs: name: Generated Docs @@ -91,7 +91,7 @@ jobs: - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 with: cache-on-failure: true - - run: cargo dev docs check + - run: cargo --locked dev docs check test: name: Test (Linux) @@ -107,7 +107,7 @@ jobs: with: cache-on-failure: true - uses: taiki-e/install-action@773334c0e05d7e699e4d78234494308223f3a2cf # nextest - - run: cargo nextest run --workspace --status-level slow --profile ci + - run: cargo nextest run --locked --workspace --status-level slow --profile ci test-macos: name: Test (macOS) @@ -124,4 +124,4 @@ jobs: with: cache-on-failure: true - uses: taiki-e/install-action@773334c0e05d7e699e4d78234494308223f3a2cf # nextest - - run: cargo nextest run --workspace --status-level slow --profile ci + - run: cargo nextest run --locked --workspace --status-level slow --profile ci diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml index c240e3837..e2f63a047 100644 --- a/.github/workflows/typescript.yml +++ b/.github/workflows/typescript.yml @@ -73,5 +73,5 @@ jobs: - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - run: bun install --frozen-lockfile - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - - run: cargo dev build -- -p fabro-cli --release + - run: cargo --locked dev build -- --locked -p fabro-cli --release - run: wc -c < target/release/fabro diff --git a/Cargo.lock b/Cargo.lock index 19852f701..187bd3181 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2609,6 +2609,7 @@ dependencies = [ "clap", "dirs", "fabro-model", + "fabro-types", "fabro-util", "hex", "serde", diff --git a/apps/fabro-web/app/components/run-summary-panel.test.tsx b/apps/fabro-web/app/components/run-summary-panel.test.tsx index 52e79cf0f..018184802 100644 --- a/apps/fabro-web/app/components/run-summary-panel.test.tsx +++ b/apps/fabro-web/app/components/run-summary-panel.test.tsx @@ -6,6 +6,7 @@ import { RunSummaryPanelView, type RunSummaryPanelViewProps, } from "./run-summary-panel"; +import { TEST_PRINCIPAL } from "../lib/test-fixtures"; function instanceText(instance: TestRenderer.ReactTestInstance): string { const parts: string[] = []; @@ -53,7 +54,7 @@ function cellAfterLabel( function makeRun(overrides: Record = {}) { return { id: "run_1", - created_by: null, + created_by: TEST_PRINCIPAL, diff: null, billing: null, ...overrides, @@ -71,9 +72,9 @@ describe("RunSummaryPanelView", () => { } }); - test("shows unavailable copy for missing run fields after load", () => { + test("shows creator and unavailable copy for optional missing run fields after load", () => { const tree = render({ run: makeRun() }); - expect(instanceText(cellAfterLabel(tree, "Created by"))).toBe(EMPTY_VALUE); + expect(instanceText(cellAfterLabel(tree, "Created by"))).toBe("Ttest"); expect(instanceText(cellAfterLabel(tree, "Changes"))).toBe(EMPTY_VALUE); expect(instanceText(cellAfterLabel(tree, "Cost"))).toBe(EMPTY_VALUE); }); @@ -226,7 +227,7 @@ describe("RunSummaryPanelView", () => { kind: "user", identity: { issuer: "github", subject: "1" }, login: "brynary", - auth_method: "oauth", + auth_method: "github", }, }), }); @@ -240,7 +241,7 @@ describe("RunSummaryPanelView", () => { kind: "user", identity: { issuer: "github", subject: "1" }, login: "brynary", - auth_method: "oauth", + auth_method: "github", avatar_url: "https://example.com/brynary.png", }, }), @@ -252,7 +253,7 @@ describe("RunSummaryPanelView", () => { }); test("renders non-user actor with kind label", () => { - for (const kind of ["agent", "system", "slack", "webhook", "worker", "anonymous"]) { + for (const kind of ["agent", "system", "slack", "webhook", "worker"]) { const tree = render({ run: makeRun({ created_by: { kind } as any }) }); expect(instanceText(cellAfterLabel(tree, "Created by"))).toContain(kind); } diff --git a/apps/fabro-web/app/components/run-summary-panel.tsx b/apps/fabro-web/app/components/run-summary-panel.tsx index 20a08af5b..c3784a9ba 100644 --- a/apps/fabro-web/app/components/run-summary-panel.tsx +++ b/apps/fabro-web/app/components/run-summary-panel.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from "react"; import type { + Principal, Run, SandboxResources, SandboxState, @@ -50,6 +51,16 @@ function Cell({ label, children }: { label: string; children: ReactNode }) { ); } +function CreatedByValue({ actor }: { actor: Principal }) { + const created = principalDisplay(actor); + return ( +
+ {created.glyph} + {created.label} +
+ ); +} + export interface RunSummaryPanelViewProps { run: Run | null; runLoading: boolean; @@ -116,7 +127,6 @@ export function RunSummaryPanelView({ artifactsCount, artifactsLoading, }: RunSummaryPanelViewProps) { - const created = run?.created_by ? principalDisplay(run.created_by) : null; const diff = run?.diff ?? null; const cost = formatUsdMicros(run?.billing?.total_usd_micros); const sandboxKind = sandboxLifecycleKind(run?.sandbox); @@ -127,11 +137,8 @@ export function RunSummaryPanelView({ {runLoading ? ( - ) : created ? ( -
- {created.glyph} - {created.label} -
+ ) : run ? ( + ) : ( )} diff --git a/apps/fabro-web/app/components/runs-list/run-table-row.tsx b/apps/fabro-web/app/components/runs-list/run-table-row.tsx index 4fdb82777..e367e1e6f 100644 --- a/apps/fabro-web/app/components/runs-list/run-table-row.tsx +++ b/apps/fabro-web/app/components/runs-list/run-table-row.tsx @@ -35,6 +35,7 @@ export function RunTableRow({ }) { const lifecycleLabel = listLifecycleStatusLabel(run); const statusDisplay = columnStatusDisplay[run.status]; + const creator = principalDisplay(run.createdBy); const show = (col: ToggleableColumn) => !hiddenColumns.has(col); return ( @@ -54,14 +55,9 @@ export function RunTableRow({ {show("created_by") && ( - {run.createdBy && (() => { - const display = principalDisplay(run.createdBy); - return ( - - {display.glyph} - - ); - })()} + + {creator.glyph} + )} {show("repo") && ( diff --git a/apps/fabro-web/app/data/runs.test.ts b/apps/fabro-web/app/data/runs.test.ts index 98586b2c4..feb2ba568 100644 --- a/apps/fabro-web/app/data/runs.test.ts +++ b/apps/fabro-web/app/data/runs.test.ts @@ -8,6 +8,7 @@ import { mapRunToRunItem, runStatusDisplay, } from "./runs"; +import { TEST_PRINCIPAL } from "../lib/test-fixtures"; function makeRun(overrides: Partial = {}): Run { return { @@ -17,7 +18,7 @@ function makeRun(overrides: Partial = {}): Run { workflow: { slug: "fix_build", name: "Fix Build", graph_name: "FixBuild", node_count: 0, edge_count: 0 }, automation: null, repository: { name: "myrepo", origin_url: null, provider: "unknown" }, - created_by: null, + created_by: TEST_PRINCIPAL, origin: { kind: "api" }, labels: {}, lifecycle: { diff --git a/apps/fabro-web/app/data/runs.ts b/apps/fabro-web/app/data/runs.ts index 2b277b6d0..3bc9779de 100644 --- a/apps/fabro-web/app/data/runs.ts +++ b/apps/fabro-web/app/data/runs.ts @@ -41,7 +41,7 @@ export interface RunItem { sandboxWorkingDirectory?: string; sourceDirectory?: string; createdAt?: string; - createdBy?: Principal | null; + createdBy: Principal; lastEventAt?: string; size?: RunSize; } diff --git a/apps/fabro-web/app/lib/principal-display.tsx b/apps/fabro-web/app/lib/principal-display.tsx index fa9d4ec16..666f0f64c 100644 --- a/apps/fabro-web/app/lib/principal-display.tsx +++ b/apps/fabro-web/app/lib/principal-display.tsx @@ -4,7 +4,6 @@ import { ChatBubbleLeftEllipsisIcon, Cog6ToothIcon, CpuChipIcon, - QuestionMarkCircleIcon, ServerIcon, } from "@heroicons/react/20/solid"; import type { Principal } from "@qltysh/fabro-api-client"; @@ -57,10 +56,5 @@ export function principalDisplay(actor: Principal): PrincipalDisplay { return { glyph: principalIconGlyph(), label: "webhook" }; case "worker": return { glyph: principalIconGlyph(), label: "worker" }; - case "anonymous": - return { - glyph: principalIconGlyph(), - label: "anonymous", - }; } } diff --git a/apps/fabro-web/app/lib/run-actions.test.ts b/apps/fabro-web/app/lib/run-actions.test.ts index ffb208aa7..d88fdd3af 100644 --- a/apps/fabro-web/app/lib/run-actions.test.ts +++ b/apps/fabro-web/app/lib/run-actions.test.ts @@ -24,6 +24,7 @@ import { unarchiveRuns, } from "./run-actions"; import { generatedAxios } from "./api-client"; +import { TEST_PRINCIPAL } from "./test-fixtures"; type StubResponseInit = { status: number; @@ -47,7 +48,7 @@ function makeRun(status: RunStatus, archived = false): Run { workflow: { slug: "fix_build", name: "Fix Build", graph_name: null, node_count: 0, edge_count: 0 }, automation: null, repository: null, - created_by: null, + created_by: TEST_PRINCIPAL, origin: { kind: "api" }, labels: {}, lifecycle: { diff --git a/apps/fabro-web/app/lib/test-fixtures.ts b/apps/fabro-web/app/lib/test-fixtures.ts new file mode 100644 index 000000000..c625ebe7d --- /dev/null +++ b/apps/fabro-web/app/lib/test-fixtures.ts @@ -0,0 +1,8 @@ +import type { Principal } from "@qltysh/fabro-api-client"; + +export const TEST_PRINCIPAL: Principal = { + kind: "user", + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token", +}; diff --git a/apps/fabro-web/app/routes/automations-new.test.tsx b/apps/fabro-web/app/routes/automations-new.test.tsx index 47f471abf..32a88dccc 100644 --- a/apps/fabro-web/app/routes/automations-new.test.tsx +++ b/apps/fabro-web/app/routes/automations-new.test.tsx @@ -4,6 +4,7 @@ import TestRenderer, { act } from "react-test-renderer"; import { createMemoryRouter, RouterProvider } from "react-router"; import { ToastProvider } from "../components/toast"; +import { TEST_PRINCIPAL } from "../lib/test-fixtures"; import { setupReactTestEnv } from "../lib/test-utils"; let currentRun: any = null; @@ -120,7 +121,7 @@ function makeRun(overrides: Record = {}) { origin_url: "https://github.com/fallback/repo.git", provider: "github", }, - created_by: null, + created_by: TEST_PRINCIPAL, origin: { kind: "api" }, labels: {}, lifecycle: { diff --git a/apps/fabro-web/app/routes/automations.tsx b/apps/fabro-web/app/routes/automations.tsx index dd388a690..03725287c 100644 --- a/apps/fabro-web/app/routes/automations.tsx +++ b/apps/fabro-web/app/routes/automations.tsx @@ -115,7 +115,7 @@ function AutomationCard({ onDelete: () => void; }) { const Icon = automation.icon; - const runDisabled = busy || !automation.apiEnabled; + const runDisabled = busy || running || !automation.apiEnabled; return (
@@ -153,7 +153,7 @@ function AutomationCard({