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 14fb04510..56e4c3171 100644 --- a/apps/fabro-web/app/components/run-summary-panel.test.tsx +++ b/apps/fabro-web/app/components/run-summary-panel.test.tsx @@ -52,10 +52,15 @@ function cellAfterLabel( function makeRun(overrides: Record = {}) { return { - id: "run_1", - created_by: null, - diff: null, - billing: null, + id: "run_1", + created_by: { + kind: "user", + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token", + }, + diff: null, + billing: null, ...overrides, } as any; } @@ -71,9 +76,8 @@ describe("RunSummaryPanelView", () => { } }); - test("shows unavailable copy for missing run fields after load", () => { + test("shows unavailable copy for missing optional run fields after load", () => { const tree = render({ run: makeRun() }); - expect(instanceText(cellAfterLabel(tree, "Created by"))).toBe(EMPTY_VALUE); expect(instanceText(cellAfterLabel(tree, "Changes"))).toBe(EMPTY_VALUE); expect(instanceText(cellAfterLabel(tree, "Cost"))).toBe(EMPTY_VALUE); }); @@ -226,7 +230,7 @@ describe("RunSummaryPanelView", () => { kind: "user", identity: { issuer: "github", subject: "1" }, login: "brynary", - auth_method: "oauth", + auth_method: "github", }, }), }); @@ -240,7 +244,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", }, }), diff --git a/apps/fabro-web/app/components/run-summary-panel.tsx b/apps/fabro-web/app/components/run-summary-panel.tsx index 20a08af5b..cde91b09a 100644 --- a/apps/fabro-web/app/components/run-summary-panel.tsx +++ b/apps/fabro-web/app/components/run-summary-panel.tsx @@ -116,7 +116,7 @@ export function RunSummaryPanelView({ artifactsCount, artifactsLoading, }: RunSummaryPanelViewProps) { - const created = run?.created_by ? principalDisplay(run.created_by) : null; + const created = run == null ? null : principalDisplay(run.created_by); const diff = run?.diff ?? null; const cost = formatUsdMicros(run?.billing?.total_usd_micros); const sandboxKind = sandboxLifecycleKind(run?.sandbox); 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..5fd17dad1 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 @@ -54,7 +54,7 @@ export function RunTableRow({ {show("created_by") && ( - {run.createdBy && (() => { + {(() => { const display = principalDisplay(run.createdBy); return ( diff --git a/apps/fabro-web/app/data/runs.test.ts b/apps/fabro-web/app/data/runs.test.ts index 98586b2c4..e8a461cf3 100644 --- a/apps/fabro-web/app/data/runs.test.ts +++ b/apps/fabro-web/app/data/runs.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { Run, RunStatus as ApiRunStatus } from "@qltysh/fabro-api-client"; +import type { Principal, Run, RunStatus as ApiRunStatus } from "@qltysh/fabro-api-client"; import { columnForStatus, columnStatusDisplay, @@ -9,6 +9,15 @@ import { runStatusDisplay, } from "./runs"; +function testPrincipal(): Principal { + return { + kind: "user", + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token", + }; +} + function makeRun(overrides: Partial = {}): Run { return { id: "01ABC", @@ -17,7 +26,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: testPrincipal(), 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/run-actions.test.ts b/apps/fabro-web/app/lib/run-actions.test.ts index ffb208aa7..d8745f19b 100644 --- a/apps/fabro-web/app/lib/run-actions.test.ts +++ b/apps/fabro-web/app/lib/run-actions.test.ts @@ -25,6 +25,13 @@ import { } from "./run-actions"; import { generatedAxios } from "./api-client"; +const TEST_PRINCIPAL = { + kind: "user", + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token", +}; + type StubResponseInit = { status: number; body?: unknown; @@ -47,7 +54,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/routes/automations-new.test.tsx b/apps/fabro-web/app/routes/automations-new.test.tsx index 47f471abf..9667247b8 100644 --- a/apps/fabro-web/app/routes/automations-new.test.tsx +++ b/apps/fabro-web/app/routes/automations-new.test.tsx @@ -101,6 +101,13 @@ mock.module("swr", () => ({ const { default: AutomationsNew } = await import("./automations-new"); mock.restore(); +const TEST_PRINCIPAL = { + kind: "user", + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token", +}; + function makeRun(overrides: Record = {}) { return { id: "run_1", @@ -120,7 +127,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/run-detail.test.ts b/apps/fabro-web/app/routes/run-detail.test.ts index 45f288bf5..1fc2a775f 100644 --- a/apps/fabro-web/app/routes/run-detail.test.ts +++ b/apps/fabro-web/app/routes/run-detail.test.ts @@ -20,6 +20,13 @@ let currentQuestions: any[] = []; let deleteRunApiResult: Promise | null = null; const mountedRenderers: TestRenderer.ReactTestRenderer[] = []; +const TEST_PRINCIPAL = { + kind: "user", + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token", +}; + const deleteRunApiMock = mock((_id: string) => deleteRunApiResult ?? Promise.resolve({}), ); @@ -221,7 +228,7 @@ function makeRunSummary({ workflow: { slug: "default", name: "Default", graph_name: null, node_count: 0, edge_count: 0 }, automation, repository: { name: "fabro", origin_url: null, provider: "unknown" }, - created_by: null, + created_by: TEST_PRINCIPAL, origin: { kind: "api" }, labels: {}, lifecycle: { diff --git a/apps/fabro-web/app/routes/run-files.render.test.tsx b/apps/fabro-web/app/routes/run-files.render.test.tsx index 457f4bd41..9b4cb1a08 100644 --- a/apps/fabro-web/app/routes/run-files.render.test.tsx +++ b/apps/fabro-web/app/routes/run-files.render.test.tsx @@ -6,6 +6,13 @@ import { toast as sonnerToast } from "sonner"; import { ToastProvider } from "../components/toast"; +const TEST_PRINCIPAL = { + kind: "user", + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token", +}; + let currentFilesPayload: any = null; let currentCommitsPayload: any = null; let currentRunStatus = "succeeded"; @@ -51,7 +58,7 @@ mock.module("../lib/queries", () => ({ workflow: { slug: "default", name: "Default", graph_name: null, node_count: 0, edge_count: 0 }, automation: null, repository: { name: "fabro", origin_url: null, provider: "unknown" }, - created_by: null, + created_by: TEST_PRINCIPAL, origin: { kind: "api" }, labels: {}, lifecycle: { diff --git a/apps/fabro-web/app/routes/runs.preferences.test.tsx b/apps/fabro-web/app/routes/runs.preferences.test.tsx index dd5120af8..21af44aef 100644 --- a/apps/fabro-web/app/routes/runs.preferences.test.tsx +++ b/apps/fabro-web/app/routes/runs.preferences.test.tsx @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; import TestRenderer, { act } from "react-test-renderer"; import { createMemoryRouter, RouterProvider } from "react-router"; -import type { PaginatedRunList, Run } from "@qltysh/fabro-api-client"; +import type { PaginatedRunList, Principal, Run } from "@qltysh/fabro-api-client"; import { ToastProvider } from "../components/toast"; import { CHILD_RUNS_LIST_PREFERENCES_STORAGE_KEY } from "../components/runs-list/preferences"; @@ -27,6 +27,15 @@ let previousElement: unknown; let hadElement = false; const mountedRenderers: TestRenderer.ReactTestRenderer[] = []; +function testPrincipal(): Principal { + return { + kind: "user", + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token", + }; +} + function run(id: string, repo = "qlty/fabro", workflow = "release"): Run { return { id, @@ -35,7 +44,7 @@ function run(id: string, repo = "qlty/fabro", workflow = "release"): Run { workflow: { slug: workflow, name: workflow, graph_name: null, node_count: 0, edge_count: 0 }, automation: null, repository: { name: repo, origin_url: null, provider: "github" }, - created_by: null, + created_by: testPrincipal(), origin: { kind: "api" }, labels: {}, lifecycle: { diff --git a/apps/fabro-web/app/routes/runs.test.tsx b/apps/fabro-web/app/routes/runs.test.tsx index 51483cff2..6f71938c9 100644 --- a/apps/fabro-web/app/routes/runs.test.tsx +++ b/apps/fabro-web/app/routes/runs.test.tsx @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { BoardColumn, Run } from "@qltysh/fabro-api-client"; +import type { BoardColumn, Principal, Run } from "@qltysh/fabro-api-client"; import { buildBoardColumns, @@ -12,6 +12,15 @@ import { } from "./runs"; import { summarizeBatchLifecycleAction } from "../components/runs-list/batch-lifecycle"; +function testPrincipal(): Principal { + return { + kind: "user", + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token", + }; +} + function boardRun(id: string, column: BoardColumn, questionText?: string): Run { const status = column === "blocked" @@ -34,7 +43,7 @@ function boardRun(id: string, column: BoardColumn, questionText?: string): Run { workflow: { slug: "test", name: "Test", graph_name: null, node_count: 0, edge_count: 0 }, automation: null, repository: { name: "repo", origin_url: null, provider: "unknown" }, - created_by: null, + created_by: testPrincipal(), origin: { kind: "api" }, labels: {}, lifecycle: { diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml index d075ce98b..bfb3e66a1 100644 --- a/docs/public/api-reference/fabro-api.yaml +++ b/docs/public/api-reference/fabro-api.yaml @@ -8992,6 +8992,8 @@ components: RunProvenance: type: object + required: + - subject properties: server: oneOf: @@ -9002,9 +9004,7 @@ components: - $ref: "#/components/schemas/RunClientProvenance" - type: "null" subject: - oneOf: - - $ref: "#/components/schemas/Principal" - - type: "null" + $ref: "#/components/schemas/Principal" Principal: oneOf: @@ -10239,6 +10239,7 @@ components: - run_id - settings - graph + - provenance properties: run_id: type: string @@ -10262,9 +10263,7 @@ components: additionalProperties: type: string provenance: - oneOf: - - $ref: "#/components/schemas/RunProvenance" - - type: "null" + $ref: "#/components/schemas/RunProvenance" manifest_blob: type: ["string", "null"] definition_blob: @@ -10576,9 +10575,7 @@ components: - $ref: "#/components/schemas/RepositoryRef" - type: "null" created_by: - oneOf: - - $ref: "#/components/schemas/Principal" - - type: "null" + $ref: "#/components/schemas/Principal" origin: $ref: "#/components/schemas/RunOrigin" labels: diff --git a/lib/crates/fabro-api/Cargo.toml b/lib/crates/fabro-api/Cargo.toml index 284a1956f..298dad17c 100644 --- a/lib/crates/fabro-api/Cargo.toml +++ b/lib/crates/fabro-api/Cargo.toml @@ -34,3 +34,6 @@ serde_json = "1" serde_yaml = "0.9" prettyplease = "0.2" syn = "2" + +[dev-dependencies] +fabro-types = { path = "../fabro-types", features = ["test-support"] } diff --git a/lib/crates/fabro-api/tests/principal_round_trip.rs b/lib/crates/fabro-api/tests/principal_round_trip.rs index 601e6372c..ac2b2c258 100644 --- a/lib/crates/fabro-api/tests/principal_round_trip.rs +++ b/lib/crates/fabro-api/tests/principal_round_trip.rs @@ -139,9 +139,9 @@ fn run_provenance_subject_round_trips_as_principal() { name: Some("fabro-cli".to_string()), version: Some("0.1.0".to_string()), }), - subject: Some(Principal::Worker { + subject: Principal::Worker { run_id: fixtures::RUN_1, - }), + }, }; let json = serde_json::to_value(&provenance).unwrap(); diff --git a/lib/crates/fabro-api/tests/run_event_round_trip.rs b/lib/crates/fabro-api/tests/run_event_round_trip.rs index dc2c9297f..c36c1e67f 100644 --- a/lib/crates/fabro-api/tests/run_event_round_trip.rs +++ b/lib/crates/fabro-api/tests/run_event_round_trip.rs @@ -1,7 +1,7 @@ use std::any::{TypeId, type_name}; use fabro_api::types::RunEvent as ApiRunEvent; -use fabro_types::{Graph, RunEvent, WorkflowSettings, fixtures}; +use fabro_types::{Graph, RunEvent, WorkflowSettings, fixtures, test_support}; use serde_json::{Value, json}; #[test] @@ -20,7 +20,8 @@ fn run_event_round_trips_run_created() { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), "run_dir": "/tmp/fabro/run-1", - "source_directory": "/tmp/fabro/run-1" + "source_directory": "/tmp/fabro/run-1", + "provenance": test_support::test_run_provenance() } }); @@ -39,7 +40,8 @@ fn run_event_round_trips_run_created_with_web_url() { "graph": Graph::new("test"), "run_dir": "/tmp/fabro/run-1", "source_directory": "/tmp/fabro/run-1", - "web_url": format!("http://localhost:3000/runs/{}", fixtures::RUN_1) + "web_url": format!("http://localhost:3000/runs/{}", fixtures::RUN_1), + "provenance": test_support::test_run_provenance() } }); diff --git a/lib/crates/fabro-api/tests/run_projection_round_trip.rs b/lib/crates/fabro-api/tests/run_projection_round_trip.rs index 9a2a0f310..4ab846a7f 100644 --- a/lib/crates/fabro-api/tests/run_projection_round_trip.rs +++ b/lib/crates/fabro-api/tests/run_projection_round_trip.rs @@ -137,7 +137,7 @@ fn run_spec_json() -> serde_json::Value { automation: None, source_directory: None, labels: std::collections::HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, diff --git a/lib/crates/fabro-api/tests/run_summary_round_trip.rs b/lib/crates/fabro-api/tests/run_summary_round_trip.rs index 798448941..e8ea9622f 100644 --- a/lib/crates/fabro-api/tests/run_summary_round_trip.rs +++ b/lib/crates/fabro-api/tests/run_summary_round_trip.rs @@ -12,7 +12,7 @@ use fabro_types::{ AskFabro, AskFabroUnavailableReason, AutomationRef, DiffSummary, PullRequestLink, RepositoryProvider, RepositoryRef, Run, RunApproval, RunApprovalState, RunBillingSummary, RunId, RunLifecycle, RunLinks, RunOrigin, RunRunnableSource, RunSize, RunTimestamps, RunTiming, - WorkflowRef, fixtures, + WorkflowRef, fixtures, test_support, }; use serde_json::json; @@ -88,7 +88,7 @@ fn run_summary_json_matches_openapi_shape() { origin_url: None, provider: RepositoryProvider::Unknown, }), - created_by: None, + created_by: test_support::test_principal(), origin: RunOrigin::default(), labels: HashMap::from([("team".to_string(), "core".to_string())]), lifecycle: RunLifecycle { @@ -161,7 +161,15 @@ fn run_summary_json_matches_openapi_shape() { "origin_url": null, "provider": "unknown" }, - "created_by": null, + "created_by": { + "kind": "user", + "identity": { + "issuer": "fabro:test", + "subject": "test-user" + }, + "login": "test", + "auth_method": "dev_token" + }, "origin": { "kind": "api" }, @@ -238,6 +246,15 @@ fn run_summary_deserializes_when_optional_fields_are_absent() { "name": null, "graph_name": "GraphName" }, + "created_by": { + "kind": "user", + "identity": { + "issuer": "fabro:test", + "subject": "test-user" + }, + "login": "test", + "auth_method": "dev_token" + }, "origin": { "kind": "api" }, @@ -275,6 +292,7 @@ fn run_summary_deserializes_when_optional_fields_are_absent() { assert_eq!(summary.workflow.edge_count, 0); assert_eq!(summary.goal, "ship it"); assert_eq!(summary.title, "ship it"); + assert_eq!(summary.created_by, test_support::test_principal()); assert_eq!(summary.labels, HashMap::new()); assert_eq!(summary.source_directory, None); assert_eq!( @@ -300,6 +318,50 @@ fn run_summary_deserializes_when_optional_fields_are_absent() { assert_eq!(summary.pull_request, None); } +#[test] +fn run_summary_requires_created_by() { + let created_at = Utc.with_ymd_and_hms(2026, 4, 20, 12, 0, 0).unwrap(); + let run_id = RunId::with_timestamp(created_at, 7); + + let result = serde_json::from_value::(json!({ + "id": run_id.to_string(), + "goal": "ship it", + "title": "ship it", + "workflow": { + "slug": null, + "name": null, + "graph_name": "GraphName" + }, + "origin": { + "kind": "api" + }, + "labels": {}, + "lifecycle": { + "status": { + "kind": "running" + }, + "archived": false + }, + "repository": { + "name": "fabro", + "origin_url": null, + "provider": "unknown" + }, + "models": [], + "timestamps": { + "created_at": "2026-04-20T12:00:00Z", + "started_at": null, + "last_event_at": null, + "completed_at": null + }, + "links": { + "web": null + } + })); + + assert!(result.is_err()); +} + #[test] fn run_summary_rejects_legacy_flat_json() { let created_at = Utc.with_ymd_and_hms(2026, 4, 20, 12, 0, 0).unwrap(); diff --git a/lib/crates/fabro-cli/Cargo.toml b/lib/crates/fabro-cli/Cargo.toml index 266f41244..44dcc0f37 100644 --- a/lib/crates/fabro-cli/Cargo.toml +++ b/lib/crates/fabro-cli/Cargo.toml @@ -119,6 +119,7 @@ assert_cmd = "2" fabro-acp = { path = "../fabro-acp", features = ["test-support"] } fabro-build-support = { path = "../build-support" } fabro-server = { path = "../fabro-server", features = ["test-support"] } +fabro-types = { path = "../fabro-types", features = ["test-support"] } insta = { workspace = true, features = ["filters"] } paste = "1" predicates = "3" diff --git a/lib/crates/fabro-cli/src/commands/run/attach.rs b/lib/crates/fabro-cli/src/commands/run/attach.rs index 77c5c113a..5e49adf42 100644 --- a/lib/crates/fabro-cli/src/commands/run/attach.rs +++ b/lib/crates/fabro-cli/src/commands/run/attach.rs @@ -841,7 +841,7 @@ mod tests { automation: None, source_directory: None, labels: std::collections::HashMap::default(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, diff --git a/lib/crates/fabro-cli/tests/it/cmd/inspect.rs b/lib/crates/fabro-cli/tests/it/cmd/inspect.rs index ec0d191e3..1934d8c2a 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/inspect.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/inspect.rs @@ -221,7 +221,18 @@ fn inspect_resolves_selector_via_server_endpoint() { "attrs": {} }, "workflow_slug": "remote-workflow", - "source_directory": "/srv/repo" + "source_directory": "/srv/repo", + "provenance": { + "subject": { + "kind": "user", + "identity": { + "issuer": "fabro:test", + "subject": "test-user" + }, + "login": "test", + "auth_method": "dev_token" + } + } }, "start_record": null, "conclusion": null, diff --git a/lib/crates/fabro-cli/tests/it/cmd/support.rs b/lib/crates/fabro-cli/tests/it/cmd/support.rs index 275ff9437..93ad4d6a9 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/support.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/support.rs @@ -177,6 +177,15 @@ pub(crate) fn remote_run_summary_json( "origin_url": null, "provider": "unknown" }, + "created_by": { + "kind": "user", + "identity": { + "issuer": "fabro:test", + "subject": "test-user" + }, + "login": "test", + "auth_method": "dev_token" + }, "origin": { "kind": "api" }, diff --git a/lib/crates/fabro-cli/tests/it/support/mod.rs b/lib/crates/fabro-cli/tests/it/support/mod.rs index 80b65e4aa..0e3122606 100644 --- a/lib/crates/fabro-cli/tests/it/support/mod.rs +++ b/lib/crates/fabro-cli/tests/it/support/mod.rs @@ -49,7 +49,7 @@ pub(crate) fn run_projection_json(run_id: &str, status: &serde_json::Value) -> s automation: None, source_directory: Some("/srv/repo".to_string()), labels: std::collections::HashMap::default(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, diff --git a/lib/crates/fabro-dump/src/lib.rs b/lib/crates/fabro-dump/src/lib.rs index 397d55a3f..b4da2215c 100644 --- a/lib/crates/fabro-dump/src/lib.rs +++ b/lib/crates/fabro-dump/src/lib.rs @@ -476,6 +476,7 @@ mod tests { Checkpoint, CheckpointRecord, Conclusion, RunDiff, RunSandbox, RunSandboxInstance, RunSandboxPlan, RunStatus, SandboxProviderKind, StageCompletion, StageModelUsage, StageOutcome, StartRecord, SuccessReason, WorkflowSettings, first_event_seq, fixtures, + test_support, }; use futures::executor; @@ -498,7 +499,7 @@ mod tests { push_outcome: fabro_types::PreRunPushOutcome::NotAttempted, }), labels: HashMap::from([("team".to_string(), "platform".to_string())]), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, diff --git a/lib/crates/fabro-server/src/demo/mod.rs b/lib/crates/fabro-server/src/demo/mod.rs index d189b2a21..05d2e345a 100644 --- a/lib/crates/fabro-server/src/demo/mod.rs +++ b/lib/crates/fabro-server/src/demo/mod.rs @@ -7,7 +7,7 @@ reason = "Demo fixture data favors literal fidelity over pedantic style lints." )] -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use axum::Json; use axum::extract::{Path, Query, State}; @@ -23,7 +23,9 @@ use fabro_api::types::{ RunFilesMeta, RunFilesMetaScope, RunFilesMetaSource, SandboxService, SandboxServiceListResponse, }; -use fabro_types::{SandboxServiceDiscoverySource, SandboxServiceListMeta}; +use fabro_types::{ + AuthMethod, IdpIdentity, Principal, SandboxServiceDiscoverySource, SandboxServiceListMeta, +}; use serde_json::json; use crate::error::ApiError; @@ -31,6 +33,14 @@ use crate::principal_middleware::RequiredUser; use crate::run_selector::{ResolveRunError, resolve_run_by_selector}; use crate::server::{AppState, EventListParams, PaginationParams, parse_stage_id_path}; +static DEMO_PRINCIPAL: LazyLock = LazyLock::new(|| { + Principal::user( + IdpIdentity::new("fabro:demo", "demo").expect("demo identity should be valid"), + "demo".to_string(), + AuthMethod::DevToken, + ) +}); + fn paginated_response( items: Vec, pagination: &PaginationParams, @@ -1096,7 +1106,7 @@ mod runs { RunSize, RunTimestamps, StageId, WorkflowRef, WorkflowSettings, }; - use super::ts; + use super::{DEMO_PRINCIPAL, ts}; use crate::server::run_stage_from_stage_id; fn labels(entries: &[(&str, &str)]) -> HashMap { @@ -1171,7 +1181,7 @@ mod runs { repo_origin_url, source_directory.as_deref(), )), - created_by: None, + created_by: DEMO_PRINCIPAL.clone(), origin: RunOrigin::default(), labels: labels(entries), lifecycle: RunLifecycle { diff --git a/lib/crates/fabro-server/src/run_files.rs b/lib/crates/fabro-server/src/run_files.rs index 1f358821b..e49567162 100644 --- a/lib/crates/fabro-server/src/run_files.rs +++ b/lib/crates/fabro-server/src/run_files.rs @@ -2389,7 +2389,7 @@ index 1111111..2222222 160000 automation: None, source_directory: None, labels: HashMap::default(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, diff --git a/lib/crates/fabro-server/src/run_manifest.rs b/lib/crates/fabro-server/src/run_manifest.rs index 4feb22133..729e5666f 100644 --- a/lib/crates/fabro-server/src/run_manifest.rs +++ b/lib/crates/fabro-server/src/run_manifest.rs @@ -27,7 +27,9 @@ use fabro_static::EnvVars; use fabro_types::settings::cli::OutputVerbosity; use fabro_types::settings::interp::InterpString; use fabro_types::settings::run::{EnvironmentProvider, RunGoal, RunNamespace}; -use fabro_types::{ManifestPath, RunId, SandboxProviderKind, ServerSettings, WorkflowSettings}; +use fabro_types::{ + ManifestPath, RunId, RunProvenance, SandboxProviderKind, ServerSettings, WorkflowSettings, +}; use fabro_util::check_report::{CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus}; use fabro_validate::Severity; use fabro_workflow::Error as WorkflowError; @@ -194,6 +196,7 @@ pub(crate) fn create_run_input( prepared: PreparedManifest, configured_providers: Vec, web_url: Option, + provenance: RunProvenance, ) -> CreateRunInput { CreateRunInput { workflow: WorkflowInput::Bundled(prepared.workflow_input), @@ -209,7 +212,7 @@ pub(crate) fn create_run_input( git: prepared.git, fork_source_ref: None, parent_id: prepared.parent_id, - provenance: None, + provenance, configured_providers, web_url, } diff --git a/lib/crates/fabro-server/src/server/handler/events.rs b/lib/crates/fabro-server/src/server/handler/events.rs index 180c04bdd..ff2fabcdc 100644 --- a/lib/crates/fabro-server/src/server/handler/events.rs +++ b/lib/crates/fabro-server/src/server/handler/events.rs @@ -570,7 +570,7 @@ mod stage_events_tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-server/src/server/handler/lifecycle.rs b/lib/crates/fabro-server/src/server/handler/lifecycle.rs index 06beb7c0b..5df7e72c5 100644 --- a/lib/crates/fabro-server/src/server/handler/lifecycle.rs +++ b/lib/crates/fabro-server/src/server/handler/lifecycle.rs @@ -894,7 +894,7 @@ async fn retry_run( let input = operations::RetryRunInput { source_run_id: id, new_run_id, - provenance: Some(run_provenance(&headers, &actor)), + provenance: run_provenance(&headers, &actor), web_url: state.run_web_url(&new_run_id), }; match Box::pin(operations::retry_run(&state.store, &input)).await { diff --git a/lib/crates/fabro-server/src/server/handler/pair.rs b/lib/crates/fabro-server/src/server/handler/pair.rs index 7b673fad8..73bf6f374 100644 --- a/lib/crates/fabro-server/src/server/handler/pair.rs +++ b/lib/crates/fabro-server/src/server/handler/pair.rs @@ -1024,7 +1024,7 @@ mod tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-server/src/server/handler/runs.rs b/lib/crates/fabro-server/src/server/handler/runs.rs index fca9fdc3c..01b0245d1 100644 --- a/lib/crates/fabro-server/src/server/handler/runs.rs +++ b/lib/crates/fabro-server/src/server/handler/runs.rs @@ -687,13 +687,14 @@ pub(crate) async fn create_run_from_manifest( .as_ref() .map(LlmClientResult::provider_ids) .unwrap_or_default(); + let provenance = run_provenance(&headers, &actor); let mut create_input = run_manifest::create_run_input( prepared.clone(), ready_provider_ids.clone(), web_url.clone(), + provenance, ); create_input.run_id = Some(run_id); - create_input.provenance = Some(run_provenance(&headers, &actor)); create_input.submitted_manifest_bytes = Some(submitted_manifest_bytes); create_input.automation = automation; @@ -864,7 +865,7 @@ pub(super) fn run_provenance(headers: &HeaderMap, subject: &Principal) -> RunPro version: FABRO_VERSION.to_string(), }), client: run_client_provenance(headers), - subject: Some(subject.clone()), + subject: subject.clone(), } } diff --git a/lib/crates/fabro-server/src/server/handler/sandbox.rs b/lib/crates/fabro-server/src/server/handler/sandbox.rs index 61d8b7ee9..bbf42a478 100644 --- a/lib/crates/fabro-server/src/server/handler/sandbox.rs +++ b/lib/crates/fabro-server/src/server/handler/sandbox.rs @@ -1339,6 +1339,7 @@ mod retrieve_sandbox_tests { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), "run_dir": "/tmp/test", + "provenance": fabro_types::test_support::test_run_provenance(), }, }), run_id, diff --git a/lib/crates/fabro-server/src/server/handler/sessions.rs b/lib/crates/fabro-server/src/server/handler/sessions.rs index bad48c6d4..cd8c07579 100644 --- a/lib/crates/fabro-server/src/server/handler/sessions.rs +++ b/lib/crates/fabro-server/src/server/handler/sessions.rs @@ -1700,7 +1700,7 @@ mod tests { automation: None, source_directory: None, labels: HashMap::default(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, diff --git a/lib/crates/fabro-server/src/server/tests.rs b/lib/crates/fabro-server/src/server/tests.rs index 363884738..418ffc098 100644 --- a/lib/crates/fabro-server/src/server/tests.rs +++ b/lib/crates/fabro-server/src/server/tests.rs @@ -4022,7 +4022,7 @@ async fn append_default_run_created(run_store: &fabro_store::RunDatabase, run_id workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, @@ -4076,7 +4076,7 @@ async fn create_slack_notification_run( workflow_slug: workflow_slug.map(str::to_string), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, @@ -5083,7 +5083,7 @@ async fn list_run_stages_distinguishes_visits() { workflow_slug: Some("test".to_string()), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, @@ -6140,7 +6140,7 @@ async fn create_completed_run_ready_for_pull_request( source_directory: Some("/tmp/project".to_string()), git: git.clone(), labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -9943,15 +9943,10 @@ async fn run_tool_worker_token_can_use_client_backend_routes_across_runs() { .unwrap() .expect("created run should be cached"); assert_eq!( - cached - .projection - .spec - .provenance - .as_ref() - .and_then(|provenance| provenance.subject.as_ref()), - Some(&Principal::Worker { + cached.projection.spec.provenance.subject, + Principal::Worker { run_id: parent_run_id, - }), + }, ); let response = app @@ -12310,7 +12305,7 @@ async fn create_preserved_local_sandbox_run(state: &Arc, run_id: RunId workflow_slug: Some("test".to_string()), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, @@ -13062,7 +13057,7 @@ async fn delete_run_retry_after_missing_provider_resource_removes_metadata() { workflow_slug: Some("test".to_string()), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-server/tests/it/api/run_files.rs b/lib/crates/fabro-server/tests/it/api/run_files.rs index a3bf7ad76..01d762a73 100644 --- a/lib/crates/fabro-server/tests/it/api/run_files.rs +++ b/lib/crates/fabro-server/tests/it/api/run_files.rs @@ -69,7 +69,7 @@ async fn append_completed_run_with_final_patch( workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-store/Cargo.toml b/lib/crates/fabro-store/Cargo.toml index 016b55d3d..c81bca853 100644 --- a/lib/crates/fabro-store/Cargo.toml +++ b/lib/crates/fabro-store/Cargo.toml @@ -32,6 +32,7 @@ futures.workspace = true uuid.workspace = true [dev-dependencies] +fabro-types = { path = "../fabro-types", features = ["test-support"] } tokio = { workspace = true, features = ["test-util", "macros"] } tempfile = "3" ulid.workspace = true diff --git a/lib/crates/fabro-store/src/run_state.rs b/lib/crates/fabro-store/src/run_state.rs index cd6fa0640..bf9ddb760 100644 --- a/lib/crates/fabro-store/src/run_state.rs +++ b/lib/crates/fabro-store/src/run_state.rs @@ -922,11 +922,7 @@ pub(crate) fn build_summary(state: &RunProjection, run_id: &RunId) -> Run { }) .map(|(_, record)| record.question.clone()); let models = run_models(state); - let created_by = state - .spec - .provenance - .as_ref() - .and_then(|provenance| provenance.subject.clone()); + let created_by = state.spec.provenance.subject.clone(); let source_directory = state.spec.source_directory.clone(); let repo_origin_url = state.spec.git.as_ref().map(|git| git.origin_url.clone()); let start_time = state.start.as_ref().map(|start| start.start_time); @@ -1276,7 +1272,7 @@ mod tests { StageContextWindowBreakdownItem, StageContextWindowCategory, StageContextWindowCountMethod, StageContextWindowProjection, StageContextWindowStaleness, StageContextWindowWarning, StageModelUsage, StageOutcome, StageState, SubAgentStatus, SuccessReason, WorkflowSettings, - first_event_seq, fixtures, + first_event_seq, fixtures, test_support, }; use serde_json::json; @@ -1358,7 +1354,7 @@ mod tests { automation: None, source_directory: None, labels: HashMap::new(), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, @@ -1439,7 +1435,7 @@ mod tests { } #[test] - fn legacy_run_created_projects_retried_from_none() { + fn run_created_without_retried_from_projects_none() { let event = test_raw_event( 1, "run.created", @@ -1447,7 +1443,8 @@ mod tests { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), "labels": {}, - "run_dir": "/tmp/run" + "run_dir": "/tmp/run", + "provenance": test_support::test_run_provenance() }), None, ); @@ -1475,7 +1472,8 @@ mod tests { "graph": Graph::new("test"), "automation": automation, "labels": {}, - "run_dir": "/tmp/run" + "run_dir": "/tmp/run", + "provenance": test_support::test_run_provenance() }), None, ); @@ -1498,7 +1496,8 @@ mod tests { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), "labels": {}, - "run_dir": "/tmp/run" + "run_dir": "/tmp/run", + "provenance": test_support::test_run_provenance() }), None, )]) @@ -1520,7 +1519,8 @@ mod tests { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), "labels": {}, - "run_dir": "/tmp/run" + "run_dir": "/tmp/run", + "provenance": test_support::test_run_provenance() }), None, )]) @@ -1597,7 +1597,8 @@ mod tests { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), "labels": {}, - "run_dir": "/tmp/run" + "run_dir": "/tmp/run", + "provenance": test_support::test_run_provenance() }), None, ), @@ -1822,7 +1823,7 @@ mod tests { "repo_origin_url": null, "base_branch": null, "labels": {}, - "provenance": null, + "provenance": test_support::test_run_provenance(), "manifest_blob": null, "definition_blob": null, "git": null, @@ -2851,7 +2852,7 @@ mod tests { source_directory: Some("/tmp/repo".to_string()), git: None, labels: HashMap::new(), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -2877,7 +2878,7 @@ mod tests { source_directory: Some("/tmp/repo".to_string()), git: None, labels: HashMap::new(), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -2916,7 +2917,8 @@ mod tests { "attrs": { "goal": { "String": "Goal title" } } }, "labels": {}, - "run_dir": "/tmp/run" + "run_dir": "/tmp/run", + "provenance": test_support::test_run_provenance() }), None, ); @@ -2930,7 +2932,7 @@ mod tests { } #[test] - fn legacy_run_created_without_title_infers_projection_title() { + fn run_created_without_title_infers_projection_title() { let event = test_raw_event( 1, "run.created", @@ -2943,7 +2945,8 @@ mod tests { "attrs": { "goal": { "String": "## Plan: Legacy title\n\nDetails" } } }, "labels": {}, - "run_dir": "/tmp/run" + "run_dir": "/tmp/run", + "provenance": test_support::test_run_provenance() }), None, ); @@ -2972,7 +2975,8 @@ mod tests { "attrs": { "goal": { "String": "Goal title" } } }, "labels": {}, - "run_dir": "/tmp/run" + "run_dir": "/tmp/run", + "provenance": test_support::test_run_provenance() }), None, ), @@ -3016,7 +3020,8 @@ mod tests { "labels": {}, "run_dir": "/tmp/run", "source_directory": "/tmp/run", - "manifest_blob": manifest_blob + "manifest_blob": manifest_blob, + "provenance": test_support::test_run_provenance() } })) .unwrap(), diff --git a/lib/crates/fabro-store/src/slate/mod.rs b/lib/crates/fabro-store/src/slate/mod.rs index c6406fd8d..27df802ff 100644 --- a/lib/crates/fabro-store/src/slate/mod.rs +++ b/lib/crates/fabro-store/src/slate/mod.rs @@ -472,7 +472,7 @@ mod tests { use chrono::{DateTime, Utc}; use fabro_types::{ AttrValue, FailureReason, Graph, RunControlAction, RunSpec, RunStatus, StageId, - SuccessReason, WorkflowSettings, + SuccessReason, WorkflowSettings, test_support, }; use futures::TryStreamExt; use object_store::memory::InMemory; @@ -542,7 +542,7 @@ mod tests { automation: None, source_directory: Some(format!("/tmp/{label}")), labels: std::collections::HashMap::from([("team".to_string(), "infra".to_string())]), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: Some(fabro_types::GitContext { @@ -601,6 +601,7 @@ mod tests { "run_dir": format!("/tmp/{label}"), "git": run_spec.git, "labels": run_spec.labels, + "provenance": run_spec.provenance, }), )) .await @@ -627,6 +628,7 @@ mod tests { "git": run_spec.git, "labels": run_spec.labels, "parent_id": parent_id, + "provenance": run_spec.provenance, }), )) .await @@ -1300,6 +1302,7 @@ mod tests { "run_dir": "/tmp/run-2", "git": run_spec["git"], "labels": run_spec["labels"], + "provenance": run_spec["provenance"], }, })) .unwrap(), diff --git a/lib/crates/fabro-store/src/slate/run_store.rs b/lib/crates/fabro-store/src/slate/run_store.rs index 1718cb662..94116fa94 100644 --- a/lib/crates/fabro-store/src/slate/run_store.rs +++ b/lib/crates/fabro-store/src/slate/run_store.rs @@ -667,7 +667,7 @@ mod tests { use std::sync::Arc; use std::time::Duration; - use fabro_types::{Graph, RunId, SessionId, StageId, WorkflowSettings}; + use fabro_types::{Graph, RunId, SessionId, StageId, WorkflowSettings, test_support}; use object_store::memory::InMemory; use serde_json::json; @@ -723,6 +723,7 @@ mod tests { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), "run_dir": "/tmp/test", + "provenance": test_support::test_run_provenance(), }, }), run_id, diff --git a/lib/crates/fabro-store/tests/serializable_projection.rs b/lib/crates/fabro-store/tests/serializable_projection.rs index 6584a36cd..ffc7fbb03 100644 --- a/lib/crates/fabro-store/tests/serializable_projection.rs +++ b/lib/crates/fabro-store/tests/serializable_projection.rs @@ -8,7 +8,7 @@ use fabro_types::{ BilledModelUsage, BilledTokenCounts, Checkpoint, CheckpointRecord, InterviewQuestionRecord, QuestionType, RunDiff, RunSandbox, RunSandboxInstance, RunSandboxPlan, RunSandboxRuntime, RunStatus, SandboxProviderKind, StageCompletion, StageModelUsage, StageOutcome, StartRecord, - WorkflowSettings, first_event_seq, fixtures, + WorkflowSettings, first_event_seq, fixtures, test_support, }; use serde_json::json; @@ -22,7 +22,7 @@ fn sample_run_spec() -> RunSpec { automation: None, source_directory: Some("/tmp/project".to_string()), labels: HashMap::from([("team".to_string(), "platform".to_string())]), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: Some(fabro_types::GitContext { diff --git a/lib/crates/fabro-tool/Cargo.toml b/lib/crates/fabro-tool/Cargo.toml index 08b6df7cf..c50ea6a38 100644 --- a/lib/crates/fabro-tool/Cargo.toml +++ b/lib/crates/fabro-tool/Cargo.toml @@ -29,4 +29,5 @@ tokio.workspace = true toml.workspace = true [dev-dependencies] +fabro-types = { path = "../fabro-types", features = ["test-support"] } tempfile = "3" diff --git a/lib/crates/fabro-tool/src/common.rs b/lib/crates/fabro-tool/src/common.rs index 9dd64599a..159ec426d 100644 --- a/lib/crates/fabro-tool/src/common.rs +++ b/lib/crates/fabro-tool/src/common.rs @@ -307,7 +307,9 @@ fn format_tool_error(err: &anyhow::Error) -> String { #[cfg(test)] mod tests { use chrono::{TimeZone, Utc}; - use fabro_types::{RunLifecycle, RunLinks, RunOrigin, RunStatus, RunTimestamps, WorkflowRef}; + use fabro_types::{ + RunLifecycle, RunLinks, RunOrigin, RunStatus, RunTimestamps, WorkflowRef, test_support, + }; use super::*; @@ -413,7 +415,7 @@ mod tests { }, automation: None, repository: None, - created_by: None, + created_by: test_support::test_principal(), origin: RunOrigin::default(), labels: HashMap::new(), lifecycle: RunLifecycle { diff --git a/lib/crates/fabro-tool/src/create.rs b/lib/crates/fabro-tool/src/create.rs index 8488f8f0b..57e41acd5 100644 --- a/lib/crates/fabro-tool/src/create.rs +++ b/lib/crates/fabro-tool/src/create.rs @@ -508,7 +508,7 @@ mod tests { use fabro_api::types; use fabro_types::{ EventEnvelope, Run, RunLifecycle, RunLinks, RunOrigin, RunProjection, RunStatus, - RunTimestamps, WorkflowRef, + RunTimestamps, WorkflowRef, test_support, }; use schemars::SchemaGenerator; use serde_json::json; @@ -902,7 +902,7 @@ mod tests { }, automation: None, repository: None, - created_by: None, + created_by: test_support::test_principal(), origin: RunOrigin::default(), labels: HashMap::new(), lifecycle: RunLifecycle { diff --git a/lib/crates/fabro-tool/src/interact.rs b/lib/crates/fabro-tool/src/interact.rs index 34023120d..503147ece 100644 --- a/lib/crates/fabro-tool/src/interact.rs +++ b/lib/crates/fabro-tool/src/interact.rs @@ -453,7 +453,7 @@ mod tests { use chrono::{TimeZone, Utc}; use fabro_types::{ EventEnvelope, FailureReason, Run, RunId, RunLifecycle, RunLinks, RunOrigin, RunProjection, - RunStatus, RunTimestamps, WorkflowRef, + RunStatus, RunTimestamps, WorkflowRef, test_support, }; use serde_json::json; @@ -690,7 +690,7 @@ mod tests { }, automation: None, repository: None, - created_by: None, + created_by: test_support::test_principal(), origin: RunOrigin::default(), labels: HashMap::new(), lifecycle: RunLifecycle { diff --git a/lib/crates/fabro-tool/src/search.rs b/lib/crates/fabro-tool/src/search.rs index 0df7e391a..e3d0162c6 100644 --- a/lib/crates/fabro-tool/src/search.rs +++ b/lib/crates/fabro-tool/src/search.rs @@ -293,7 +293,9 @@ mod tests { use std::collections::HashMap; use chrono::{TimeZone, Utc}; - use fabro_types::{RunLifecycle, RunLinks, RunOrigin, RunStatus, RunTimestamps, WorkflowRef}; + use fabro_types::{ + RunLifecycle, RunLinks, RunOrigin, RunStatus, RunTimestamps, WorkflowRef, test_support, + }; use super::*; @@ -444,7 +446,7 @@ mod tests { }, automation: None, repository: None, - created_by: None, + created_by: test_support::test_principal(), origin: RunOrigin::default(), labels: HashMap::from([("group".to_string(), group.to_string())]), lifecycle: RunLifecycle { diff --git a/lib/crates/fabro-types/src/lib.rs b/lib/crates/fabro-types/src/lib.rs index f1a8c8b39..cf2605429 100644 --- a/lib/crates/fabro-types/src/lib.rs +++ b/lib/crates/fabro-types/src/lib.rs @@ -44,6 +44,8 @@ pub mod start; pub mod status; pub mod steering; pub mod system_integrations; +#[cfg(any(test, feature = "test-support"))] +pub mod test_support; pub mod timing; pub mod todo; pub mod transcript; diff --git a/lib/crates/fabro-types/src/run.rs b/lib/crates/fabro-types/src/run.rs index 2b27d0a06..2f1671fe6 100644 --- a/lib/crates/fabro-types/src/run.rs +++ b/lib/crates/fabro-types/src/run.rs @@ -24,14 +24,13 @@ pub struct RunClientProvenance { pub version: Option, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RunProvenance { #[serde(default, skip_serializing_if = "Option::is_none")] pub server: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub client: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub subject: Option, + pub subject: Principal, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -93,8 +92,7 @@ pub struct RunSpec { pub source_directory: Option, #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub labels: HashMap, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub provenance: Option, + pub provenance: RunProvenance, #[serde(default, skip_serializing_if = "Option::is_none")] pub manifest_blob: Option, #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/lib/crates/fabro-types/src/run_event/mod.rs b/lib/crates/fabro-types/src/run_event/mod.rs index 52cbb83a9..a8998c96c 100644 --- a/lib/crates/fabro-types/src/run_event/mod.rs +++ b/lib/crates/fabro-types/src/run_event/mod.rs @@ -933,7 +933,7 @@ mod tests { use super::*; use crate::{ AuthMethod, Edge, Graph, IdpIdentity, Node, PendingReason, RunBlobId, WorkflowSettings, - fixtures, + fixtures, test_support, }; fn user_principal(login: &str) -> Principal { @@ -1017,7 +1017,8 @@ mod tests { "graph": graph, "labels": {}, "run_dir": "/tmp/run", - "source_directory": "/tmp/run" + "source_directory": "/tmp/run", + "provenance": test_support::test_run_provenance() } }); @@ -1038,7 +1039,8 @@ mod tests { "labels": {}, "run_dir": "/tmp/run", "source_directory": "/tmp/run", - "manifest_blob": RunBlobId::new(br#"{"version":1}"#).to_string() + "manifest_blob": RunBlobId::new(br#"{"version":1}"#).to_string(), + "provenance": test_support::test_run_provenance() } }); diff --git a/lib/crates/fabro-types/src/run_event/run.rs b/lib/crates/fabro-types/src/run_event/run.rs index e3023997a..06077171d 100644 --- a/lib/crates/fabro-types/src/run_event/run.rs +++ b/lib/crates/fabro-types/src/run_event/run.rs @@ -30,8 +30,7 @@ pub struct RunCreatedProps { pub automation: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub db_prefix: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub provenance: Option, + pub provenance: RunProvenance, #[serde(default, skip_serializing_if = "Option::is_none")] pub manifest_blob: Option, #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/lib/crates/fabro-types/src/run_projection.rs b/lib/crates/fabro-types/src/run_projection.rs index 4b52dcc96..40130ed9c 100644 --- a/lib/crates/fabro-types/src/run_projection.rs +++ b/lib/crates/fabro-types/src/run_projection.rs @@ -681,7 +681,7 @@ mod title_tests { use chrono::Utc; - use crate::{AttrValue, Graph, RunId, RunProjection, RunSpec, WorkflowSettings}; + use crate::{AttrValue, Graph, RunId, RunProjection, RunSpec, WorkflowSettings, test_support}; fn projection_with_goal(goal: Option<&str>) -> RunProjection { let mut graph = Graph::new("test"); @@ -700,7 +700,7 @@ mod title_tests { automation: None, source_directory: None, labels: HashMap::new(), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, @@ -752,7 +752,7 @@ mod iter_stages_tests { use serde_json::json; use super::RunProjection; - use crate::{Graph, RunId, RunSpec, StageProjection, WorkflowSettings}; + use crate::{Graph, RunId, RunSpec, StageProjection, WorkflowSettings, test_support}; fn seq(n: u32) -> NonZeroU32 { NonZeroU32::new(n).unwrap() @@ -770,7 +770,7 @@ mod iter_stages_tests { automation: None, source_directory: None, labels: HashMap::default(), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, diff --git a/lib/crates/fabro-types/src/run_summary.rs b/lib/crates/fabro-types/src/run_summary.rs index fd35dc2a4..3c4321052 100644 --- a/lib/crates/fabro-types/src/run_summary.rs +++ b/lib/crates/fabro-types/src/run_summary.rs @@ -52,8 +52,7 @@ pub struct Run { pub automation: Option, #[serde(default)] pub repository: Option, - #[serde(default)] - pub created_by: Option, + pub created_by: Principal, pub origin: RunOrigin, pub labels: HashMap, pub lifecycle: RunLifecycle, diff --git a/lib/crates/fabro-types/src/test_support.rs b/lib/crates/fabro-types/src/test_support.rs new file mode 100644 index 000000000..994813974 --- /dev/null +++ b/lib/crates/fabro-types/src/test_support.rs @@ -0,0 +1,19 @@ +use crate::{AuthMethod, IdpIdentity, Principal, RunProvenance}; + +#[must_use] +pub fn test_principal() -> Principal { + Principal::user( + IdpIdentity::new("fabro:test", "test-user").expect("test identity should be valid"), + "test".to_string(), + AuthMethod::DevToken, + ) +} + +#[must_use] +pub fn test_run_provenance() -> RunProvenance { + RunProvenance { + server: None, + client: None, + subject: test_principal(), + } +} diff --git a/lib/crates/fabro-types/tests/run_event_serde.rs b/lib/crates/fabro-types/tests/run_event_serde.rs index 49af80c50..41696ffe8 100644 --- a/lib/crates/fabro-types/tests/run_event_serde.rs +++ b/lib/crates/fabro-types/tests/run_event_serde.rs @@ -6,7 +6,7 @@ use fabro_types::run_event::run::{RunCreatedProps, RunParentLinkedProps, RunPare use fabro_types::run_event::{RunSessionTurnFailedCode, RunSessionTurnFailedProps}; use fabro_types::settings::InterpString; use fabro_types::settings::run::RunGoal; -use fabro_types::{AutomationRef, EventBody, TurnId, WorkflowSettings, fixtures}; +use fabro_types::{AutomationRef, EventBody, TurnId, WorkflowSettings, fixtures, test_support}; fn templated_settings() -> WorkflowSettings { let mut settings = WorkflowSettings::default(); @@ -32,7 +32,7 @@ fn run_created_props_round_trip_templated_settings() { trigger_id: Some("schedule_1".to_string()), }), db_prefix: Some("run_".to_string()), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, git: Some(GitContext { origin_url: "https://github.com/fabro-sh/fabro.git".to_string(), @@ -97,7 +97,7 @@ fn run_created_props_omits_web_url_when_absent() { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, @@ -128,21 +128,36 @@ fn run_created_props_omits_web_url_when_absent() { } #[test] -fn run_created_props_defaults_additive_fields_for_legacy_events() { +fn run_created_props_defaults_optional_additive_fields_when_absent() { let json = serde_json::json!({ "title": null, "settings": WorkflowSettings::default(), "graph": Graph::new("ship"), "labels": {}, - "run_dir": "/tmp/run" + "run_dir": "/tmp/run", + "provenance": test_support::test_run_provenance(), }); - let props: RunCreatedProps = - serde_json::from_value(json).expect("legacy props should deserialize"); + let props: RunCreatedProps = serde_json::from_value(json).expect("props should deserialize"); assert_eq!(props.retried_from, None); assert_eq!(props.automation, None); } +#[test] +fn run_created_props_requires_total_provenance() { + let mut missing = serde_json::json!({ + "title": null, + "settings": WorkflowSettings::default(), + "graph": Graph::new("ship"), + "labels": {}, + "run_dir": "/tmp/run", + }); + assert!(serde_json::from_value::(missing.clone()).is_err()); + + missing["provenance"] = serde_json::Value::Null; + assert!(serde_json::from_value::(missing).is_err()); +} + #[test] fn run_parent_events_round_trip_parent_ids() { let linked = EventBody::RunParentLinked(RunParentLinkedProps { diff --git a/lib/crates/fabro-types/tests/run_spec_methods.rs b/lib/crates/fabro-types/tests/run_spec_methods.rs index b05dca05e..6eb9e105c 100644 --- a/lib/crates/fabro-types/tests/run_spec_methods.rs +++ b/lib/crates/fabro-types/tests/run_spec_methods.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use fabro_types::graph::Graph; use fabro_types::run::{DirtyStatus, GitContext, PreRunPushOutcome, RunSpec}; use fabro_types::settings::{ProjectNamespace, WorkflowNamespace}; -use fabro_types::{WorkflowSettings, fixtures}; +use fabro_types::{WorkflowSettings, fixtures, test_support}; fn sample_run_spec() -> RunSpec { let settings = WorkflowSettings { @@ -27,7 +27,7 @@ fn sample_run_spec() -> RunSpec { automation: None, source_directory: Some("/Users/client/project".to_string()), labels: HashMap::from([("team".to_string(), "platform".to_string())]), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: Some(GitContext { diff --git a/lib/crates/fabro-types/tests/run_spec_serde.rs b/lib/crates/fabro-types/tests/run_spec_serde.rs index 89b09ccbd..0b84fd861 100644 --- a/lib/crates/fabro-types/tests/run_spec_serde.rs +++ b/lib/crates/fabro-types/tests/run_spec_serde.rs @@ -4,7 +4,7 @@ use fabro_types::graph::Graph; use fabro_types::run::{DirtyStatus, ForkSourceRef, GitContext, PreRunPushOutcome, RunSpec}; use fabro_types::settings::InterpString; use fabro_types::settings::run::RunGoal; -use fabro_types::{AutomationRef, WorkflowSettings, fixtures}; +use fabro_types::{AutomationRef, RunProvenance, WorkflowSettings, fixtures, test_support}; fn templated_settings() -> WorkflowSettings { let mut settings = WorkflowSettings::default(); @@ -27,7 +27,7 @@ fn run_spec_round_trips_templated_settings() { }), source_directory: Some("/Users/client/project".to_string()), labels: HashMap::from([("team".to_string(), "platform".to_string())]), - provenance: None, + provenance: test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: Some(GitContext { @@ -75,15 +75,47 @@ fn run_spec_round_trips_templated_settings() { } #[test] -fn run_spec_defaults_automation_for_legacy_specs() { +fn run_spec_defaults_automation_when_absent() { + let provenance = test_support::test_run_provenance(); let json = serde_json::json!({ "run_id": fixtures::RUN_1, "settings": WorkflowSettings::default(), "graph": Graph::new("ship"), - "labels": {} + "labels": {}, + "provenance": provenance, }); - let record: RunSpec = serde_json::from_value(json).expect("legacy spec should deserialize"); + let record: RunSpec = serde_json::from_value(json).expect("spec should deserialize"); assert_eq!(record.automation, None); } + +#[test] +fn run_spec_requires_total_provenance() { + let mut missing = serde_json::json!({ + "run_id": fixtures::RUN_1, + "settings": WorkflowSettings::default(), + "graph": Graph::new("ship"), + "labels": {}, + }); + assert!(serde_json::from_value::(missing.clone()).is_err()); + + missing["provenance"] = serde_json::Value::Null; + assert!(serde_json::from_value::(missing).is_err()); +} + +#[test] +fn run_provenance_requires_total_subject() { + let mut provenance = + serde_json::to_value(test_support::test_run_provenance()).expect("provenance serializes"); + provenance + .as_object_mut() + .expect("provenance is an object") + .remove("subject"); + assert!(serde_json::from_value::(provenance).is_err()); + + let null_subject = serde_json::json!({ + "subject": null, + }); + assert!(serde_json::from_value::(null_subject).is_err()); +} diff --git a/lib/crates/fabro-workflow/src/billing_rollup.rs b/lib/crates/fabro-workflow/src/billing_rollup.rs index d9e2867ab..73391c9bc 100644 --- a/lib/crates/fabro-workflow/src/billing_rollup.rs +++ b/lib/crates/fabro-workflow/src/billing_rollup.rs @@ -353,7 +353,7 @@ mod tests { automation: None, source_directory: None, labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, diff --git a/lib/crates/fabro-workflow/src/event/convert.rs b/lib/crates/fabro-workflow/src/event/convert.rs index 4c1f91228..5f0fc9959 100644 --- a/lib/crates/fabro-workflow/src/event/convert.rs +++ b/lib/crates/fabro-workflow/src/event/convert.rs @@ -2339,7 +2339,7 @@ mod tests { let provenance = RunProvenance { server: None, client: None, - subject: Some(user_principal("alice")), + subject: user_principal("alice"), }; let automation = AutomationRef { id: "nightly".to_string(), @@ -2348,25 +2348,25 @@ mod tests { }; let stored = to_run_event(&fixtures::RUN_1, &Event::RunCreated { - run_id: fixtures::RUN_1, - title: None, - settings: serde_json::to_value(WorkflowSettings::default()).unwrap(), - graph: serde_json::to_value(Graph::new("test")).unwrap(), - workflow_source: None, - workflow_config: None, - labels: BTreeMap::default(), - run_dir: "/tmp/run".to_string(), + run_id: fixtures::RUN_1, + title: None, + settings: serde_json::to_value(WorkflowSettings::default()).unwrap(), + graph: serde_json::to_value(Graph::new("test")).unwrap(), + workflow_source: None, + workflow_config: None, + labels: BTreeMap::default(), + run_dir: "/tmp/run".to_string(), source_directory: Some("/tmp/run".to_string()), - workflow_slug: None, - automation: Some(automation.clone()), - db_prefix: None, - provenance: Some(provenance), - manifest_blob: None, - git: None, - fork_source_ref: None, - retried_from: None, - parent_id: None, - web_url: None, + workflow_slug: None, + automation: Some(automation.clone()), + db_prefix: None, + provenance, + manifest_blob: None, + git: None, + fork_source_ref: None, + retried_from: None, + parent_id: None, + web_url: None, }); let actor = stored.actor.as_ref().expect("actor set"); assert_eq!(actor, &user_principal("alice")); diff --git a/lib/crates/fabro-workflow/src/event/events.rs b/lib/crates/fabro-workflow/src/event/events.rs index 886868804..0b5afb1c7 100644 --- a/lib/crates/fabro-workflow/src/event/events.rs +++ b/lib/crates/fabro-workflow/src/event/events.rs @@ -41,8 +41,7 @@ pub enum Event { automation: Option, #[serde(default, skip_serializing_if = "Option::is_none")] db_prefix: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - provenance: Option, + provenance: RunProvenance, #[serde(default, skip_serializing_if = "Option::is_none")] manifest_blob: Option, #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/lib/crates/fabro-workflow/src/event/sink.rs b/lib/crates/fabro-workflow/src/event/sink.rs index 7b65fc99c..4acc5265a 100644 --- a/lib/crates/fabro-workflow/src/event/sink.rs +++ b/lib/crates/fabro-workflow/src/event/sink.rs @@ -244,7 +244,7 @@ mod tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/event/stored_fields.rs b/lib/crates/fabro-workflow/src/event/stored_fields.rs index 94fba25ad..4ada17b67 100644 --- a/lib/crates/fabro-workflow/src/event/stored_fields.rs +++ b/lib/crates/fabro-workflow/src/event/stored_fields.rs @@ -57,7 +57,7 @@ pub(super) fn stored_event_fields(event: &Event, scope: Option<&StageScope>) -> fn stored_event_fields_for_variant(event: &Event) -> StoredEventFields { match event { Event::RunCreated { provenance, .. } => StoredEventFields { - actor: provenance.as_ref().and_then(|p| p.subject.clone()), + actor: Some(provenance.subject.clone()), ..StoredEventFields::default() }, Event::RunCancelRequested { actor } diff --git a/lib/crates/fabro-workflow/src/git.rs b/lib/crates/fabro-workflow/src/git.rs index 643d49b58..0f0613fe8 100644 --- a/lib/crates/fabro-workflow/src/git.rs +++ b/lib/crates/fabro-workflow/src/git.rs @@ -469,7 +469,7 @@ mod tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/handler/agent.rs b/lib/crates/fabro-workflow/src/handler/agent.rs index 3a120e8fc..3b048374b 100644 --- a/lib/crates/fabro-workflow/src/handler/agent.rs +++ b/lib/crates/fabro-workflow/src/handler/agent.rs @@ -484,7 +484,7 @@ mod tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/handler/command.rs b/lib/crates/fabro-workflow/src/handler/command.rs index f10ebafde..2e59a954b 100644 --- a/lib/crates/fabro-workflow/src/handler/command.rs +++ b/lib/crates/fabro-workflow/src/handler/command.rs @@ -256,7 +256,7 @@ mod tests { automation: None, source_directory: None, labels: std::collections::HashMap::default(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, @@ -357,7 +357,7 @@ mod tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/handler/llm/api.rs b/lib/crates/fabro-workflow/src/handler/llm/api.rs index 91753f4c9..8520a0c93 100644 --- a/lib/crates/fabro-workflow/src/handler/llm/api.rs +++ b/lib/crates/fabro-workflow/src/handler/llm/api.rs @@ -2133,7 +2133,7 @@ reasoning = false }, automation: None, repository: None, - created_by: None, + created_by: fabro_types::test_support::test_principal(), origin: RunOrigin::default(), labels: HashMap::new(), lifecycle: RunLifecycle { diff --git a/lib/crates/fabro-workflow/src/handler/parallel.rs b/lib/crates/fabro-workflow/src/handler/parallel.rs index b52478bac..6772db8fb 100644 --- a/lib/crates/fabro-workflow/src/handler/parallel.rs +++ b/lib/crates/fabro-workflow/src/handler/parallel.rs @@ -728,7 +728,7 @@ mod tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/handler/prompt.rs b/lib/crates/fabro-workflow/src/handler/prompt.rs index 1c2a82267..d88c9e875 100644 --- a/lib/crates/fabro-workflow/src/handler/prompt.rs +++ b/lib/crates/fabro-workflow/src/handler/prompt.rs @@ -283,7 +283,7 @@ mod tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/lifecycle/git.rs b/lib/crates/fabro-workflow/src/lifecycle/git.rs index ccd24632d..233044930 100644 --- a/lib/crates/fabro-workflow/src/lifecycle/git.rs +++ b/lib/crates/fabro-workflow/src/lifecycle/git.rs @@ -736,7 +736,7 @@ mod tests { workflow_slug: Some("metadata".to_string()), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/operations/archive.rs b/lib/crates/fabro-workflow/src/operations/archive.rs index db4b0ccf9..2e0112a61 100644 --- a/lib/crates/fabro-workflow/src/operations/archive.rs +++ b/lib/crates/fabro-workflow/src/operations/archive.rs @@ -226,7 +226,7 @@ mod tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/operations/create.rs b/lib/crates/fabro-workflow/src/operations/create.rs index 8bf16d115..f68c3545b 100644 --- a/lib/crates/fabro-workflow/src/operations/create.rs +++ b/lib/crates/fabro-workflow/src/operations/create.rs @@ -45,7 +45,7 @@ pub struct CreateRunInput { pub git: Option, pub fork_source_ref: Option, pub parent_id: Option, - pub provenance: Option, + pub provenance: RunProvenance, pub configured_providers: Vec, /// Public URL where this run can be viewed in the web UI, when the server /// has the web UI enabled. Recorded on the `run.created` event so attach @@ -72,7 +72,7 @@ struct PersistCreateOptions { automation: Option, git: Option, fork_source_ref: Option, - provenance: Option, + provenance: RunProvenance, configured_providers: Vec, catalog: Arc, } @@ -1115,7 +1115,7 @@ mod tests { git: None, fork_source_ref: None, parent_id: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), configured_providers: Vec::new(), web_url: None, }, @@ -1183,7 +1183,7 @@ mod tests { }), fork_source_ref: None, parent_id: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), configured_providers: Vec::new(), web_url: None, }, @@ -1295,7 +1295,7 @@ mod tests { git: None, fork_source_ref: None, parent_id: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), configured_providers: Vec::new(), web_url: None, }, @@ -1341,7 +1341,7 @@ mod tests { }), fork_source_ref: None, parent_id: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), configured_providers: Vec::new(), web_url: None, }, @@ -1414,7 +1414,7 @@ mod tests { git: None, fork_source_ref: None, parent_id: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), configured_providers: Vec::new(), web_url: None, }, @@ -1467,7 +1467,7 @@ mod tests { git: None, fork_source_ref: None, parent_id: None, - provenance: Some(fabro_types::RunProvenance { + provenance: fabro_types::RunProvenance { server: Some(fabro_types::RunServerProvenance { version: "0.9.0".to_string(), }), @@ -1476,12 +1476,12 @@ mod tests { name: Some("fabro-cli".to_string()), version: Some("0.9.0".to_string()), }), - subject: Some(fabro_types::Principal::user( + subject: fabro_types::Principal::user( fabro_types::IdpIdentity::new("https://github.com", "12345").unwrap(), "octocat".to_string(), fabro_types::AuthMethod::Github, - )), - }), + ), + }, configured_providers: Vec::new(), web_url: None, }, @@ -1494,7 +1494,7 @@ mod tests { let run_store = store.open_run_reader(&created.run_id).await.unwrap(); let state = run_store.state().await.unwrap(); let run = state.spec; - let provenance = run.provenance.expect("provenance should be projected"); + let provenance = run.provenance; assert_eq!(provenance.server.unwrap().version, "0.9.0"); assert_eq!( @@ -1502,7 +1502,7 @@ mod tests { Some("fabro-cli") ); assert_eq!( - provenance.subject.unwrap(), + provenance.subject, fabro_types::Principal::user( fabro_types::IdpIdentity::new("https://github.com", "12345").unwrap(), "octocat".to_string(), diff --git a/lib/crates/fabro-workflow/src/operations/fork.rs b/lib/crates/fabro-workflow/src/operations/fork.rs index 2bf7e144f..006f960fa 100644 --- a/lib/crates/fabro-workflow/src/operations/fork.rs +++ b/lib/crates/fabro-workflow/src/operations/fork.rs @@ -383,7 +383,7 @@ mod tests { workflow_slug: Some("fork-source".to_string()), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: Some(fabro_types::GitContext { origin_url: "https://github.com/example/repo.git".to_string(), diff --git a/lib/crates/fabro-workflow/src/operations/retry.rs b/lib/crates/fabro-workflow/src/operations/retry.rs index be7793d3e..6d3cbdc3c 100644 --- a/lib/crates/fabro-workflow/src/operations/retry.rs +++ b/lib/crates/fabro-workflow/src/operations/retry.rs @@ -12,7 +12,7 @@ use crate::event::{self, Event}; pub struct RetryRunInput { pub source_run_id: RunId, pub new_run_id: RunId, - pub provenance: Option, + pub provenance: RunProvenance, pub web_url: Option, } @@ -152,7 +152,7 @@ mod tests { version: "test".to_string(), }), client: None, - subject: Some(actor(login)), + subject: actor(login), } } @@ -191,7 +191,7 @@ mod tests { workflow_slug: Some("retry-source".to_string()), automation: None, db_prefix: None, - provenance: Some(provenance("source-user")), + provenance: provenance("source-user"), manifest_blob, git: Some(git_context()), fork_source_ref, @@ -368,7 +368,7 @@ mod tests { let outcome = retry_run(&store, &RetryRunInput { source_run_id, new_run_id: RunId::new(), - provenance: Some(provenance("retry-user")), + provenance: provenance("retry-user"), web_url: Some("http://localhost:3000/runs/retry".to_string()), }) .await @@ -402,14 +402,7 @@ mod tests { assert_eq!(retry_state.spec.manifest_blob, manifest_blob); assert_eq!(retry_state.spec.definition_blob, definition_blob); assert_eq!(retry_state.spec.fork_source_ref, Some(fork_source_ref)); - assert_eq!( - retry_state - .spec - .provenance - .as_ref() - .and_then(|provenance| provenance.subject.as_ref()), - Some(&actor("retry-user")) - ); + assert_eq!(retry_state.spec.provenance.subject, actor("retry-user")); assert_eq!( retry_state.web_url.as_deref(), Some("http://localhost:3000/runs/retry") @@ -463,7 +456,7 @@ mod tests { let outcome = retry_run(&store, &RetryRunInput { source_run_id, new_run_id: RunId::new(), - provenance: Some(provenance("retry-user")), + provenance: provenance("retry-user"), web_url: None, }) .await @@ -517,7 +510,7 @@ mod tests { let err = retry_run(&store, &RetryRunInput { source_run_id: run_id, new_run_id: RunId::new(), - provenance: None, + provenance: provenance("retry-user"), web_url: None, }) .await @@ -535,7 +528,7 @@ mod tests { let err = retry_run(&store, &RetryRunInput { source_run_id: fixtures::RUN_1, new_run_id: RunId::new(), - provenance: None, + provenance: provenance("retry-user"), web_url: None, }) .await diff --git a/lib/crates/fabro-workflow/src/operations/start.rs b/lib/crates/fabro-workflow/src/operations/start.rs index 8083350be..3ac123a9a 100644 --- a/lib/crates/fabro-workflow/src/operations/start.rs +++ b/lib/crates/fabro-workflow/src/operations/start.rs @@ -1438,7 +1438,7 @@ reasoning = false git: None, fork_source_ref: None, parent_id: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), configured_providers: Vec::new(), web_url: None, }, @@ -1860,7 +1860,7 @@ reasoning = false git: None, fork_source_ref: None, parent_id: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), configured_providers: Vec::new(), web_url: None, }, diff --git a/lib/crates/fabro-workflow/src/operations/timeline.rs b/lib/crates/fabro-workflow/src/operations/timeline.rs index 68e0ee959..bfe0da88d 100644 --- a/lib/crates/fabro-workflow/src/operations/timeline.rs +++ b/lib/crates/fabro-workflow/src/operations/timeline.rs @@ -248,7 +248,7 @@ mod tests { automation: None, source_directory: None, labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, diff --git a/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs b/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs index d05398556..0deb440e5 100644 --- a/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs +++ b/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs @@ -165,7 +165,7 @@ fn persisted_workflow(graph: Graph, source: String, run_dir: &Path, run_id: RunI push_outcome: fabro_types::PreRunPushOutcome::NotAttempted, }), labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -208,7 +208,7 @@ async fn seed_created_and_starting( workflow_slug: run_options.workflow_slug.clone(), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: run_options.pre_run_git.clone(), fork_source_ref: run_options.fork_source_ref.clone(), diff --git a/lib/crates/fabro-workflow/src/pipeline/finalize.rs b/lib/crates/fabro-workflow/src/pipeline/finalize.rs index c677c1007..3b6344422 100644 --- a/lib/crates/fabro-workflow/src/pipeline/finalize.rs +++ b/lib/crates/fabro-workflow/src/pipeline/finalize.rs @@ -739,7 +739,7 @@ mod tests { workflow_slug: Some("metadata".to_string()), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, @@ -856,7 +856,7 @@ mod tests { automation: None, source_directory: None, labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, diff --git a/lib/crates/fabro-workflow/src/pipeline/initialize.rs b/lib/crates/fabro-workflow/src/pipeline/initialize.rs index cc1b0777a..b2894e2b6 100644 --- a/lib/crates/fabro-workflow/src/pipeline/initialize.rs +++ b/lib/crates/fabro-workflow/src/pipeline/initialize.rs @@ -773,7 +773,7 @@ mod tests { push_outcome: fabro_types::PreRunPushOutcome::NotAttempted, }), labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/pipeline/persist.rs b/lib/crates/fabro-workflow/src/pipeline/persist.rs index 7a8c896e4..64ef56a2e 100644 --- a/lib/crates/fabro-workflow/src/pipeline/persist.rs +++ b/lib/crates/fabro-workflow/src/pipeline/persist.rs @@ -148,7 +148,7 @@ mod tests { ("env".to_string(), "test".to_string()), ("team".to_string(), "workflow".to_string()), ]), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/pipeline/pull_request.rs b/lib/crates/fabro-workflow/src/pipeline/pull_request.rs index c02376988..79433cb79 100644 --- a/lib/crates/fabro-workflow/src/pipeline/pull_request.rs +++ b/lib/crates/fabro-workflow/src/pipeline/pull_request.rs @@ -823,7 +823,7 @@ mod tests { automation: None, source_directory: None, labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, @@ -1148,7 +1148,7 @@ mod tests { push_outcome: fabro_types::PreRunPushOutcome::NotAttempted, }), labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -1219,7 +1219,7 @@ mod tests { push_outcome: fabro_types::PreRunPushOutcome::NotAttempted, }), labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -1575,7 +1575,7 @@ mod tests { source_directory: Some(tmp.path().display().to_string()), git: None, labels: std::collections::HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -1704,7 +1704,7 @@ mod tests { source_directory: Some("/tmp/project".to_string()), git: None, labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -1722,7 +1722,7 @@ mod tests { workflow_slug: run_spec.workflow_slug.clone(), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, @@ -1875,7 +1875,7 @@ mod tests { source_directory: None, git: None, labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -1893,7 +1893,7 @@ mod tests { workflow_slug: None, automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/run_lookup.rs b/lib/crates/fabro-workflow/src/run_lookup.rs index e7b0badfc..e62e17fbe 100644 --- a/lib/crates/fabro-workflow/src/run_lookup.rs +++ b/lib/crates/fabro-workflow/src/run_lookup.rs @@ -491,7 +491,7 @@ mod tests { push_outcome: fabro_types::PreRunPushOutcome::NotAttempted, }), labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/run_metadata.rs b/lib/crates/fabro-workflow/src/run_metadata.rs index b11c9667c..40c42a05f 100644 --- a/lib/crates/fabro-workflow/src/run_metadata.rs +++ b/lib/crates/fabro-workflow/src/run_metadata.rs @@ -639,7 +639,7 @@ mod tests { push_outcome: PreRunPushOutcome::NotAttempted, }), labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/runtime_store.rs b/lib/crates/fabro-workflow/src/runtime_store.rs index 0dafbfdad..da3cfb099 100644 --- a/lib/crates/fabro-workflow/src/runtime_store.rs +++ b/lib/crates/fabro-workflow/src/runtime_store.rs @@ -148,7 +148,7 @@ mod tests { source_directory: Some("/tmp/test".to_string()), git: None, labels: HashMap::new(), - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, definition_blob: None, fork_source_ref: None, @@ -170,7 +170,7 @@ mod tests { workflow_slug: Some("test".to_string()), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::test_support::test_run_provenance(), manifest_blob: None, git: None, fork_source_ref: None, diff --git a/lib/crates/fabro-workflow/src/test_support.rs b/lib/crates/fabro-workflow/src/test_support.rs index e72a0ee1b..1acf410a4 100644 --- a/lib/crates/fabro-workflow/src/test_support.rs +++ b/lib/crates/fabro-workflow/src/test_support.rs @@ -175,7 +175,13 @@ async fn initialized( workflow_slug: run_options.workflow_slug.clone(), automation: None, db_prefix: None, - provenance: None, + provenance: fabro_types::RunProvenance { + server: None, + client: None, + subject: fabro_types::Principal::System { + system_kind: fabro_types::SystemActorKind::Engine, + }, + }, manifest_blob: None, git: run_options.pre_run_git.clone(), fork_source_ref: run_options.fork_source_ref.clone(), diff --git a/lib/packages/fabro-api-client/src/models/run-provenance.ts b/lib/packages/fabro-api-client/src/models/run-provenance.ts index 7276857f8..59fa7a063 100644 --- a/lib/packages/fabro-api-client/src/models/run-provenance.ts +++ b/lib/packages/fabro-api-client/src/models/run-provenance.ts @@ -26,5 +26,5 @@ import type { RunServerProvenance } from './run-server-provenance'; export interface RunProvenance { 'server'?: RunServerProvenance | null; 'client'?: RunClientProvenance | null; - 'subject'?: Principal | null; + 'subject': Principal; } diff --git a/lib/packages/fabro-api-client/src/models/run-spec.ts b/lib/packages/fabro-api-client/src/models/run-spec.ts index f1e5adab6..6abad2884 100644 --- a/lib/packages/fabro-api-client/src/models/run-spec.ts +++ b/lib/packages/fabro-api-client/src/models/run-spec.ts @@ -41,7 +41,7 @@ export interface RunSpec { 'automation'?: AutomationRef | null; 'source_directory'?: string | null; 'labels'?: { [key: string]: string; }; - 'provenance'?: RunProvenance | null; + 'provenance': RunProvenance; 'manifest_blob'?: string | null; 'definition_blob'?: string | null; 'git'?: GitContext | null; diff --git a/lib/packages/fabro-api-client/src/models/run.ts b/lib/packages/fabro-api-client/src/models/run.ts index 1ed6c6c26..a4ade38a3 100644 --- a/lib/packages/fabro-api-client/src/models/run.ts +++ b/lib/packages/fabro-api-client/src/models/run.ts @@ -83,7 +83,7 @@ export interface Run { 'workflow': WorkflowRef; 'automation': AutomationRef | null; 'repository': RepositoryRef | null; - 'created_by': Principal | null; + 'created_by': Principal; 'origin': RunOrigin; 'labels': { [key: string]: string; }; 'lifecycle': RunLifecycle; diff --git a/lib/packages/fabro-api-client/tests/principal-exhaustive.ts b/lib/packages/fabro-api-client/tests/principal-exhaustive.ts index 5fff8b8f4..f274fcb0e 100644 --- a/lib/packages/fabro-api-client/tests/principal-exhaustive.ts +++ b/lib/packages/fabro-api-client/tests/principal-exhaustive.ts @@ -40,8 +40,8 @@ export function systemKind(principal: PrincipalSystem): string { } } -type Provenance = NonNullable; -type Subject = NonNullable; +type Provenance = RunSpec["provenance"]; +type Subject = Provenance["subject"]; type SubjectIsNotAny = AssertFalse>; type SubjectExtendsPrincipal = AssertExtends;