From fffe671dd846643117e69050024adbcbfd6d2037 Mon Sep 17 00:00:00 2001 From: Fabro Date: Mon, 8 Jun 2026 20:33:22 +0000 Subject: [PATCH] Make run provenance and creator total --- .../app/components/run-summary-panel.test.tsx | 18 ++++++--- .../app/components/run-summary-panel.tsx | 2 +- .../components/runs-list/run-table-row.tsx | 2 +- apps/fabro-web/app/data/runs.test.ts | 10 ++++- apps/fabro-web/app/data/runs.ts | 2 +- apps/fabro-web/app/lib/run-actions.test.ts | 10 ++++- .../app/routes/automations-new.test.tsx | 10 ++++- apps/fabro-web/app/routes/run-detail.test.ts | 10 ++++- .../app/routes/run-files.render.test.tsx | 10 ++++- .../app/routes/runs.preferences.test.tsx | 10 ++++- apps/fabro-web/app/routes/runs.test.tsx | 10 ++++- docs/public/api-reference/fabro-api.yaml | 15 +++----- lib/crates/fabro-api/Cargo.toml | 3 ++ .../fabro-api/tests/principal_round_trip.rs | 4 +- .../fabro-api/tests/run_event_round_trip.rs | 3 ++ .../tests/run_projection_round_trip.rs | 2 +- .../fabro-api/tests/run_summary_round_trip.rs | 7 +++- lib/crates/fabro-cli/Cargo.toml | 1 + .../fabro-cli/src/commands/run/attach.rs | 2 +- lib/crates/fabro-cli/tests/it/cmd/inspect.rs | 13 ++++++- lib/crates/fabro-cli/tests/it/cmd/support.rs | 1 + lib/crates/fabro-cli/tests/it/support/mod.rs | 2 +- lib/crates/fabro-dump/src/lib.rs | 3 +- lib/crates/fabro-server/src/demo/mod.rs | 17 +++++++-- lib/crates/fabro-server/src/run_files.rs | 2 +- lib/crates/fabro-server/src/run_manifest.rs | 7 +++- .../fabro-server/src/server/handler/events.rs | 2 +- .../src/server/handler/lifecycle.rs | 2 +- .../fabro-server/src/server/handler/pair.rs | 2 +- .../fabro-server/src/server/handler/runs.rs | 5 ++- .../src/server/handler/sandbox.rs | 2 + .../src/server/handler/sessions.rs | 2 +- lib/crates/fabro-server/src/server/tests.rs | 23 +++++------ .../fabro-server/tests/it/api/run_files.rs | 2 +- lib/crates/fabro-store/Cargo.toml | 1 + lib/crates/fabro-store/src/run_state.rs | 32 +++++++++++----- lib/crates/fabro-store/src/slate/mod.rs | 6 ++- lib/crates/fabro-store/src/slate/run_store.rs | 2 + .../tests/serializable_projection.rs | 4 +- lib/crates/fabro-tool/Cargo.toml | 1 + lib/crates/fabro-tool/src/common.rs | 3 +- lib/crates/fabro-tool/src/create.rs | 3 +- lib/crates/fabro-tool/src/interact.rs | 3 +- lib/crates/fabro-tool/src/search.rs | 3 +- lib/crates/fabro-types/src/lib.rs | 2 + lib/crates/fabro-types/src/run.rs | 8 ++-- lib/crates/fabro-types/src/run_event/mod.rs | 5 ++- lib/crates/fabro-types/src/run_event/run.rs | 3 +- lib/crates/fabro-types/src/run_projection.rs | 6 ++- lib/crates/fabro-types/src/run_summary.rs | 3 +- lib/crates/fabro-types/src/test_support.rs | 19 ++++++++++ .../fabro-types/tests/run_event_serde.rs | 22 ++--------- .../fabro-types/tests/run_spec_methods.rs | 4 +- .../fabro-types/tests/run_spec_serde.rs | 11 +++--- .../fabro-workflow/src/billing_rollup.rs | 2 +- .../fabro-workflow/src/event/convert.rs | 38 +++++++++---------- lib/crates/fabro-workflow/src/event/events.rs | 3 +- lib/crates/fabro-workflow/src/event/sink.rs | 2 +- .../fabro-workflow/src/event/stored_fields.rs | 2 +- lib/crates/fabro-workflow/src/git.rs | 2 +- .../fabro-workflow/src/handler/agent.rs | 2 +- .../fabro-workflow/src/handler/command.rs | 4 +- .../fabro-workflow/src/handler/llm/api.rs | 2 +- .../fabro-workflow/src/handler/parallel.rs | 2 +- .../fabro-workflow/src/handler/prompt.rs | 2 +- .../fabro-workflow/src/lifecycle/git.rs | 2 +- .../fabro-workflow/src/operations/archive.rs | 2 +- .../fabro-workflow/src/operations/create.rs | 26 ++++++------- .../fabro-workflow/src/operations/fork.rs | 2 +- .../fabro-workflow/src/operations/retry.rs | 23 ++++------- .../fabro-workflow/src/operations/start.rs | 4 +- .../fabro-workflow/src/operations/timeline.rs | 2 +- .../src/pipeline/execute/tests.rs | 4 +- .../fabro-workflow/src/pipeline/finalize.rs | 4 +- .../fabro-workflow/src/pipeline/initialize.rs | 2 +- .../fabro-workflow/src/pipeline/persist.rs | 2 +- .../src/pipeline/pull_request.rs | 16 ++++---- lib/crates/fabro-workflow/src/run_lookup.rs | 2 +- lib/crates/fabro-workflow/src/run_metadata.rs | 2 +- .../fabro-workflow/src/runtime_store.rs | 4 +- lib/crates/fabro-workflow/src/test_support.rs | 15 +++++++- .../src/models/run-provenance.ts | 2 +- .../fabro-api-client/src/models/run-spec.ts | 2 +- .../fabro-api-client/src/models/run.ts | 2 +- 84 files changed, 333 insertions(+), 203 deletions(-) create mode 100644 lib/crates/fabro-types/src/test_support.rs 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..abc5d6e7c 100644 --- a/apps/fabro-web/app/components/run-summary-panel.test.tsx +++ b/apps/fabro-web/app/components/run-summary-panel.test.tsx @@ -34,6 +34,14 @@ function render(props: Partial = {}) { return tree!; } +const TEST_PRINCIPAL = { + kind: "user" as const, + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token" as const, + avatar_url: null, +}; + function cellAfterLabel( tree: TestRenderer.ReactTestRenderer, label: string, @@ -53,7 +61,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 +79,9 @@ 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, "Created by"))).toBe("Ttest"); expect(instanceText(cellAfterLabel(tree, "Changes"))).toBe(EMPTY_VALUE); expect(instanceText(cellAfterLabel(tree, "Cost"))).toBe(EMPTY_VALUE); }); @@ -226,7 +234,7 @@ describe("RunSummaryPanelView", () => { kind: "user", identity: { issuer: "github", subject: "1" }, login: "brynary", - auth_method: "oauth", + auth_method: "github", }, }), }); @@ -240,7 +248,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..177270630 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 ? principalDisplay(run.created_by) : null; 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..69f2dbe81 100644 --- a/apps/fabro-web/app/data/runs.test.ts +++ b/apps/fabro-web/app/data/runs.test.ts @@ -9,6 +9,14 @@ import { runStatusDisplay, } from "./runs"; +const TEST_PRINCIPAL = { + kind: "user" as const, + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token" as const, + avatar_url: null, +}; + function makeRun(overrides: Partial = {}): Run { return { id: "01ABC", @@ -17,7 +25,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/run-actions.test.ts b/apps/fabro-web/app/lib/run-actions.test.ts index ffb208aa7..c5cb1245c 100644 --- a/apps/fabro-web/app/lib/run-actions.test.ts +++ b/apps/fabro-web/app/lib/run-actions.test.ts @@ -39,6 +39,14 @@ type CapturedRequest = { const originalAdapter = generatedAxios.defaults.adapter; +const TEST_PRINCIPAL = { + kind: "user" as const, + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token" as const, + avatar_url: null, +}; + function makeRun(status: RunStatus, archived = false): Run { return { id: "run-1", @@ -47,7 +55,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..2a767fcea 100644 --- a/apps/fabro-web/app/routes/automations-new.test.tsx +++ b/apps/fabro-web/app/routes/automations-new.test.tsx @@ -101,6 +101,14 @@ mock.module("swr", () => ({ const { default: AutomationsNew } = await import("./automations-new"); mock.restore(); +const TEST_PRINCIPAL = { + kind: "user" as const, + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token" as const, + avatar_url: null, +}; + function makeRun(overrides: Record = {}) { return { id: "run_1", @@ -120,7 +128,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..cb9906196 100644 --- a/apps/fabro-web/app/routes/run-detail.test.ts +++ b/apps/fabro-web/app/routes/run-detail.test.ts @@ -20,6 +20,14 @@ let currentQuestions: any[] = []; let deleteRunApiResult: Promise | null = null; const mountedRenderers: TestRenderer.ReactTestRenderer[] = []; +const TEST_PRINCIPAL = { + kind: "user" as const, + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token" as const, + avatar_url: null, +}; + const deleteRunApiMock = mock((_id: string) => deleteRunApiResult ?? Promise.resolve({}), ); @@ -221,7 +229,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..b6d95b70c 100644 --- a/apps/fabro-web/app/routes/run-files.render.test.tsx +++ b/apps/fabro-web/app/routes/run-files.render.test.tsx @@ -18,6 +18,14 @@ const virtualizerCalls: any[] = []; const providerCalls: any[] = []; const mountedRenderers: TestRenderer.ReactTestRenderer[] = []; +const TEST_PRINCIPAL = { + kind: "user" as const, + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token" as const, + avatar_url: null, +}; + mock.module("@pierre/diffs/react", () => ({ MultiFileDiff: (props: any) => { multiFileDiffCalls.push(props); @@ -51,7 +59,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..e2be07d12 100644 --- a/apps/fabro-web/app/routes/runs.preferences.test.tsx +++ b/apps/fabro-web/app/routes/runs.preferences.test.tsx @@ -7,6 +7,14 @@ import { ToastProvider } from "../components/toast"; import { CHILD_RUNS_LIST_PREFERENCES_STORAGE_KEY } from "../components/runs-list/preferences"; import { setupReactTestEnv } from "../lib/test-utils"; +const TEST_PRINCIPAL = { + kind: "user" as const, + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token" as const, + avatar_url: null, +}; + class MemoryStorage { values = new Map(); @@ -35,7 +43,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: TEST_PRINCIPAL, 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..edc7e388f 100644 --- a/apps/fabro-web/app/routes/runs.test.tsx +++ b/apps/fabro-web/app/routes/runs.test.tsx @@ -12,6 +12,14 @@ import { } from "./runs"; import { summarizeBatchLifecycleAction } from "../components/runs-list/batch-lifecycle"; +const TEST_PRINCIPAL = { + kind: "user" as const, + identity: { issuer: "fabro:test", subject: "test-user" }, + login: "test", + auth_method: "dev_token" as const, + avatar_url: null, +}; + function boardRun(id: string, column: BoardColumn, questionText?: string): Run { const status = column === "blocked" @@ -34,7 +42,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: TEST_PRINCIPAL, 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..e7ab4cfe8 100644 --- a/lib/crates/fabro-api/Cargo.toml +++ b/lib/crates/fabro-api/Cargo.toml @@ -27,6 +27,9 @@ serde.workspace = true serde_json.workspace = true uuid = { workspace = true, features = ["serde"] } +[dev-dependencies] +fabro-types = { path = "../fabro-types", features = ["test-support"] } + [build-dependencies] openapiv3 = "2" progenitor = "0.13" 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..95ab7e558 100644 --- a/lib/crates/fabro-api/tests/run_event_round_trip.rs +++ b/lib/crates/fabro-api/tests/run_event_round_trip.rs @@ -1,6 +1,7 @@ use std::any::{TypeId, type_name}; use fabro_api::types::RunEvent as ApiRunEvent; +use fabro_types::test_support::test_run_provenance; use fabro_types::{Graph, RunEvent, WorkflowSettings, fixtures}; use serde_json::{Value, json}; @@ -19,6 +20,7 @@ fn run_event_round_trips_run_created() { "properties": { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), + "provenance": test_run_provenance(), "run_dir": "/tmp/fabro/run-1", "source_directory": "/tmp/fabro/run-1" } @@ -37,6 +39,7 @@ fn run_event_round_trips_run_created_with_web_url() { "properties": { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), + "provenance": test_run_provenance(), "run_dir": "/tmp/fabro/run-1", "source_directory": "/tmp/fabro/run-1", "web_url": format!("http://localhost:3000/runs/{}", fixtures::RUN_1) 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..24c97c52a 100644 --- a/lib/crates/fabro-api/tests/run_summary_round_trip.rs +++ b/lib/crates/fabro-api/tests/run_summary_round_trip.rs @@ -8,6 +8,7 @@ use fabro_api::types::{ RunRunnableSource as ApiRunRunnableSource, RunSize as ApiRunSize, }; use fabro_types::status::{RunStatus, SuccessReason}; +use fabro_types::test_support::test_principal; use fabro_types::{ AskFabro, AskFabroUnavailableReason, AutomationRef, DiffSummary, PullRequestLink, RepositoryProvider, RepositoryRef, Run, RunApproval, RunApprovalState, RunBillingSummary, @@ -88,7 +89,7 @@ fn run_summary_json_matches_openapi_shape() { origin_url: None, provider: RepositoryProvider::Unknown, }), - created_by: None, + created_by: test_principal(), origin: RunOrigin::default(), labels: HashMap::from([("team".to_string(), "core".to_string())]), lifecycle: RunLifecycle { @@ -161,7 +162,7 @@ fn run_summary_json_matches_openapi_shape() { "origin_url": null, "provider": "unknown" }, - "created_by": null, + "created_by": test_principal(), "origin": { "kind": "api" }, @@ -253,6 +254,7 @@ fn run_summary_deserializes_when_optional_fields_are_absent() { "origin_url": null, "provider": "unknown" }, + "created_by": test_principal(), "models": [], "timestamps": { "created_at": "2026-04-20T12:00:00Z", @@ -275,6 +277,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_principal()); assert_eq!(summary.labels, HashMap::new()); assert_eq!(summary.source_directory, None); assert_eq!( diff --git a/lib/crates/fabro-cli/Cargo.toml b/lib/crates/fabro-cli/Cargo.toml index 266f41244..4850b26fc 100644 --- a/lib/crates/fabro-cli/Cargo.toml +++ b/lib/crates/fabro-cli/Cargo.toml @@ -128,6 +128,7 @@ temp-env = "0.3" httpmock = "0.8" fabro-test = { workspace = true } fabro-macros = { path = "../fabro-macros" } +fabro-types = { path = "../fabro-types", features = ["clap", "test-support"] } hkdf.workspace = true reqwest = { workspace = true, features = ["cookies"] } tokio = { workspace = true, features = ["test-util", "macros"] } 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..4625cb81c 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/support.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/support.rs @@ -177,6 +177,7 @@ pub(crate) fn remote_run_summary_json( "origin_url": null, "provider": "unknown" }, + "created_by": fabro_types::test_support::test_principal(), "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..2825aa348 100644 --- a/lib/crates/fabro-server/src/demo/mod.rs +++ b/lib/crates/fabro-server/src/demo/mod.rs @@ -1081,7 +1081,7 @@ fn ts(s: &str) -> DateTime { mod runs { use std::collections::HashMap; - use std::sync::OnceLock; + use std::sync::{LazyLock, OnceLock}; use std::time::Duration; use fabro_api::types::*; @@ -1092,13 +1092,22 @@ mod runs { }; use fabro_types::settings::{InterpString, ProjectNamespace, WorkflowNamespace}; use fabro_types::{ - PendingReason, RepositoryRef, RunBillingSummary, RunId, RunLifecycle, RunLinks, RunOrigin, - RunSize, RunTimestamps, StageId, WorkflowRef, WorkflowSettings, + AuthMethod, IdpIdentity, PendingReason, Principal, RepositoryRef, RunBillingSummary, RunId, + RunLifecycle, RunLinks, RunOrigin, RunSize, RunTimestamps, StageId, WorkflowRef, + WorkflowSettings, }; use super::ts; use crate::server::run_stage_from_stage_id; + static DEMO_PRINCIPAL: LazyLock = LazyLock::new(|| { + Principal::user( + IdpIdentity::new("fabro:demo", "demo").expect("static demo identity should be valid"), + "demo".to_string(), + AuthMethod::DevToken, + ) + }); + fn labels(entries: &[(&str, &str)]) -> HashMap { entries .iter() @@ -1171,7 +1180,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..f5a09ab61 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; @@ -192,6 +194,7 @@ pub(crate) fn validate_prepared_manifest( pub(crate) fn create_run_input( prepared: PreparedManifest, + provenance: RunProvenance, configured_providers: Vec, web_url: Option, ) -> CreateRunInput { @@ -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..41fdc4070 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(), + provenance, ready_provider_ids.clone(), web_url.clone(), ); 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..d3e77d7da 100644 --- a/lib/crates/fabro-server/src/server/handler/sandbox.rs +++ b/lib/crates/fabro-server/src/server/handler/sandbox.rs @@ -1299,6 +1299,7 @@ FABRO_PROC_NET_TCP /proc/net/tcp6 mod retrieve_sandbox_tests { use axum::body::{Body, to_bytes}; use axum::http::{Request, StatusCode}; + use fabro_types::test_support::test_run_provenance; use fabro_types::{Graph, RunId, WorkflowSettings}; use serde_json::{Value, json}; use tower::ServiceExt; @@ -1339,6 +1340,7 @@ mod retrieve_sandbox_tests { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), "run_dir": "/tmp/test", + "provenance": 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..d525906bc 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, @@ -1644,6 +1640,7 @@ mod tests { properties: &serde_json::Value, node_id: Option<&str>, ) -> EventEnvelope { + let properties = run_created_properties(event, properties); EventEnvelope { seq, event: RunEvent::from_value(json!({ @@ -1665,6 +1662,7 @@ mod tests { properties: &serde_json::Value, node_id: Option<&str>, ) -> EventEnvelope { + let properties = run_created_properties(event, properties); EventEnvelope { seq, event: RunEvent::from_value(json!({ @@ -1679,6 +1677,19 @@ mod tests { } } + fn run_created_properties(event: &str, properties: &serde_json::Value) -> serde_json::Value { + let mut properties = properties.clone(); + if event == "run.created" && properties.get("provenance").is_none() { + if let Some(object) = properties.as_object_mut() { + object.insert( + "provenance".to_string(), + serde_json::to_value(test_support::test_run_provenance()).unwrap(), + ); + } + } + properties + } + #[test] fn live_run_timing_returns_none_before_run_starts() { let state = initialized_projection(); @@ -1822,7 +1833,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 +2862,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 +2888,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, @@ -3016,6 +3027,7 @@ mod tests { "labels": {}, "run_dir": "/tmp/run", "source_directory": "/tmp/run", + "provenance": test_support::test_run_provenance(), "manifest_blob": manifest_blob } })) diff --git a/lib/crates/fabro-store/src/slate/mod.rs b/lib/crates/fabro-store/src/slate/mod.rs index c6406fd8d..568f07fac 100644 --- a/lib/crates/fabro-store/src/slate/mod.rs +++ b/lib/crates/fabro-store/src/slate/mod.rs @@ -470,6 +470,7 @@ fn active_run_from( #[cfg(test)] mod tests { use chrono::{DateTime, Utc}; + use fabro_types::test_support::test_run_provenance; use fabro_types::{ AttrValue, FailureReason, Graph, RunControlAction, RunSpec, RunStatus, StageId, SuccessReason, WorkflowSettings, @@ -542,7 +543,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_run_provenance(), manifest_blob: None, definition_blob: None, git: Some(fabro_types::GitContext { @@ -601,6 +602,7 @@ mod tests { "run_dir": format!("/tmp/{label}"), "git": run_spec.git, "labels": run_spec.labels, + "provenance": run_spec.provenance, }), )) .await @@ -626,6 +628,7 @@ mod tests { "run_dir": format!("/tmp/{label}"), "git": run_spec.git, "labels": run_spec.labels, + "provenance": run_spec.provenance, "parent_id": parent_id, }), )) @@ -1300,6 +1303,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..013e8aba2 100644 --- a/lib/crates/fabro-store/src/slate/run_store.rs +++ b/lib/crates/fabro-store/src/slate/run_store.rs @@ -667,6 +667,7 @@ mod tests { use std::sync::Arc; use std::time::Duration; + use fabro_types::test_support::test_run_provenance; use fabro_types::{Graph, RunId, SessionId, StageId, WorkflowSettings}; use object_store::memory::InMemory; use serde_json::json; @@ -723,6 +724,7 @@ mod tests { "settings": WorkflowSettings::default(), "graph": Graph::new("test"), "run_dir": "/tmp/test", + "provenance": 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..1595dc28f 100644 --- a/lib/crates/fabro-tool/src/common.rs +++ b/lib/crates/fabro-tool/src/common.rs @@ -307,6 +307,7 @@ fn format_tool_error(err: &anyhow::Error) -> String { #[cfg(test)] mod tests { use chrono::{TimeZone, Utc}; + use fabro_types::test_support::test_principal; use fabro_types::{RunLifecycle, RunLinks, RunOrigin, RunStatus, RunTimestamps, WorkflowRef}; use super::*; @@ -413,7 +414,7 @@ mod tests { }, automation: None, repository: None, - created_by: None, + created_by: 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..8988d1a4e 100644 --- a/lib/crates/fabro-tool/src/create.rs +++ b/lib/crates/fabro-tool/src/create.rs @@ -506,6 +506,7 @@ mod tests { use async_trait::async_trait; use chrono::{TimeZone, Utc}; use fabro_api::types; + use fabro_types::test_support::test_principal; use fabro_types::{ EventEnvelope, Run, RunLifecycle, RunLinks, RunOrigin, RunProjection, RunStatus, RunTimestamps, WorkflowRef, @@ -902,7 +903,7 @@ mod tests { }, automation: None, repository: None, - created_by: None, + created_by: 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..9f7957d15 100644 --- a/lib/crates/fabro-tool/src/interact.rs +++ b/lib/crates/fabro-tool/src/interact.rs @@ -451,6 +451,7 @@ mod tests { use async_trait::async_trait; use chrono::{TimeZone, Utc}; + use fabro_types::test_support::test_principal; use fabro_types::{ EventEnvelope, FailureReason, Run, RunId, RunLifecycle, RunLinks, RunOrigin, RunProjection, RunStatus, RunTimestamps, WorkflowRef, @@ -690,7 +691,7 @@ mod tests { }, automation: None, repository: None, - created_by: None, + created_by: 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..379806d07 100644 --- a/lib/crates/fabro-tool/src/search.rs +++ b/lib/crates/fabro-tool/src/search.rs @@ -293,6 +293,7 @@ mod tests { use std::collections::HashMap; use chrono::{TimeZone, Utc}; + use fabro_types::test_support::test_principal; use fabro_types::{RunLifecycle, RunLinks, RunOrigin, RunStatus, RunTimestamps, WorkflowRef}; use super::*; @@ -444,7 +445,7 @@ mod tests { }, automation: None, repository: None, - created_by: None, + created_by: 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..a5acb6795 100644 --- a/lib/crates/fabro-types/src/run_event/mod.rs +++ b/lib/crates/fabro-types/src/run_event/mod.rs @@ -931,6 +931,7 @@ mod tests { use serde_json::json; use super::*; + use crate::test_support::test_run_provenance; use crate::{ AuthMethod, Edge, Graph, IdpIdentity, Node, PendingReason, RunBlobId, WorkflowSettings, fixtures, @@ -1017,7 +1018,8 @@ mod tests { "graph": graph, "labels": {}, "run_dir": "/tmp/run", - "source_directory": "/tmp/run" + "source_directory": "/tmp/run", + "provenance": test_run_provenance() } }); @@ -1038,6 +1040,7 @@ mod tests { "labels": {}, "run_dir": "/tmp/run", "source_directory": "/tmp/run", + "provenance": test_run_provenance(), "manifest_blob": RunBlobId::new(br#"{"version":1}"#).to_string() } }); 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..47dffdcf7 100644 --- a/lib/crates/fabro-types/src/run_projection.rs +++ b/lib/crates/fabro-types/src/run_projection.rs @@ -681,6 +681,7 @@ mod title_tests { use chrono::Utc; + use crate::test_support::test_run_provenance; use crate::{AttrValue, Graph, RunId, RunProjection, RunSpec, WorkflowSettings}; fn projection_with_goal(goal: Option<&str>) -> RunProjection { @@ -700,7 +701,7 @@ mod title_tests { automation: None, source_directory: None, labels: HashMap::new(), - provenance: None, + provenance: test_run_provenance(), manifest_blob: None, definition_blob: None, git: None, @@ -752,6 +753,7 @@ mod iter_stages_tests { use serde_json::json; use super::RunProjection; + use crate::test_support::test_run_provenance; use crate::{Graph, RunId, RunSpec, StageProjection, WorkflowSettings}; fn seq(n: u32) -> NonZeroU32 { @@ -770,7 +772,7 @@ mod iter_stages_tests { automation: None, source_directory: None, labels: HashMap::default(), - provenance: None, + provenance: 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..687bc57ff 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, @@ -127,22 +127,6 @@ fn run_created_props_omits_web_url_when_absent() { assert_eq!(round_trip.retried_from, None); } -#[test] -fn run_created_props_defaults_additive_fields_for_legacy_events() { - let json = serde_json::json!({ - "title": null, - "settings": WorkflowSettings::default(), - "graph": Graph::new("ship"), - "labels": {}, - "run_dir": "/tmp/run" - }); - - let props: RunCreatedProps = - serde_json::from_value(json).expect("legacy props should deserialize"); - assert_eq!(props.retried_from, None); - assert_eq!(props.automation, None); -} - #[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..437afad5f 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, 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,16 @@ fn run_spec_round_trips_templated_settings() { } #[test] -fn run_spec_defaults_automation_for_legacy_specs() { +fn run_spec_defaults_automation_when_field_absent() { let json = serde_json::json!({ "run_id": fixtures::RUN_1, "settings": WorkflowSettings::default(), "graph": Graph::new("ship"), - "labels": {} + "labels": {}, + "provenance": test_support::test_run_provenance() }); - let record: RunSpec = serde_json::from_value(json).expect("legacy spec should deserialize"); + let record: RunSpec = serde_json::from_value(json).expect("run spec should deserialize"); assert_eq!(record.automation, None); } 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..86dff030b 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..c620e3685 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: run_spec.provenance.clone(), 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: run_spec.provenance.clone(), 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..3996dc406 100644 --- a/lib/crates/fabro-workflow/src/test_support.rs +++ b/lib/crates/fabro-workflow/src/test_support.rs @@ -10,6 +10,7 @@ use fabro_graphviz::graph::Graph as GvGraph; use fabro_interview::AutoApproveInterviewer; use fabro_model::Catalog; use fabro_store::{ArtifactStore, Database, RunProjection}; +use fabro_types::{AuthMethod, IdpIdentity, Principal, RunProvenance}; use object_store::local::LocalFileSystem; use crate::artifact_upload::ArtifactSink; @@ -53,6 +54,18 @@ async fn execute_and_emit_terminal(initialized: InitializedState) -> Executed { executed } +fn test_run_provenance() -> RunProvenance { + RunProvenance { + server: None, + client: None, + subject: Principal::user( + IdpIdentity::new("fabro:test", "test-user").expect("test identity should be valid"), + "test".to_string(), + AuthMethod::DevToken, + ), + } +} + /// Construct a fully-populated `BilledModelUsage` for tests. Centralised so /// callers don't keep rebuilding the same JSON skeleton. #[must_use] @@ -175,7 +188,7 @@ async fn initialized( workflow_slug: run_options.workflow_slug.clone(), automation: None, db_prefix: None, - provenance: None, + provenance: 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/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;