Skip to content
Merged
151 changes: 151 additions & 0 deletions dsl/extdeps/github/workflow_runs.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
module extdeps.github.workflow_runs

import extdeps.github.github { GitHubUser, default_api_base, default_per_page }
import extdeps.github.errors { GitHubErrorShape }
import std.types { CommitSha, Timestamp, Url }
import std.credentials { EnvVar }
import extdeps.external_authority { ExternalAuthority }
import extdeps.uri { Uri, Https }
import extdeps.github.workflow_runs_contracts {
workflow_run_status_wire_contract,
workflow_run_conclusion_wire_contract
}

data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
uri: Uri {
scheme: Https
locator: "docs.github.com/en/rest/actions/workflow-runs"
}
}

type WorkflowRunStatus
= Queued
| InProgress
| Completed
| Waiting
| Requested
| Pending

type WorkflowRunConclusion
= Success
| Failure
| Cancelled
| Neutral
| Skipped
| TimedOut
| ActionRequired
| StartupFailure

data dissolve_on_conclusion_authority_consolidation: List<String> = [
"closure:unify_run_and_check_conclusions|WorkflowRunConclusion (workflow runs REST API) and CheckConclusion (checks REST API, extdeps.github.actions) share 7 of 8 variants: WorkflowRunConclusion adds StartupFailure, CheckConclusion adds Stale. These are genuinely different API shapes today; dissolve when a shared conclusion authority is grounded that subsumes both (GitHub API evolution or a typed union representation)|trigger:GitHub API unifies conclusion values across both APIs, or std gains a tagged-union type allowing a shared core with API-specific extensions"
]

type WorkflowRun {
id: Int
name: String?
head_sha: CommitSha
status: WorkflowRunStatus
conclusion: WorkflowRunConclusion?
workflow_id: Int
run_number: Int
html_url: Url
created_at: Timestamp
updated_at: Timestamp
}

type WorkflowRunList {
total_count: Int
workflow_runs: List<WorkflowRun>
}

fn parse_workflow_run_status(raw: String) -> WorkflowRunStatus? {
match raw {
"queued" => Present { value: Queued }
"in_progress" => Present { value: InProgress }
"completed" => Present { value: Completed }
"waiting" => Present { value: Waiting }
"requested" => Present { value: Requested }
"pending" => Present { value: Pending }
_ => none
}
}

fn workflow_run_status_wire_label(status: WorkflowRunStatus) -> String {
match status {
Queued => "queued"
InProgress => "in_progress"
Completed => "completed"
Waiting => "waiting"
Requested => "requested"
Pending => "pending"
}
}

fn parse_workflow_run_conclusion(raw: String) -> WorkflowRunConclusion? {
match raw {
"success" => Present { value: Success }
"failure" => Present { value: Failure }
"cancelled" => Present { value: Cancelled }
"neutral" => Present { value: Neutral }
"skipped" => Present { value: Skipped }
"timed_out" => Present { value: TimedOut }
"action_required" => Present { value: ActionRequired }
"startup_failure" => Present { value: StartupFailure }
_ => none
}
}

fn workflow_run_conclusion_wire_label(conclusion: WorkflowRunConclusion) -> String {
match conclusion {
Success => "success"
Failure => "failure"
Cancelled => "cancelled"
Neutral => "neutral"
Skipped => "skipped"
TimedOut => "timed_out"
ActionRequired => "action_required"
StartupFailure => "startup_failure"
}
}

data structural_coverage_gap_workflow_run_codec_hand_rolled: List<String> = [
"closure:consolidate_variant_string_mapping|parse_workflow_run_status/workflow_run_status_wire_label and parse_workflow_run_conclusion/workflow_run_conclusion_wire_label hand-roll the SnakeCase mapping already declared in the wire contracts. Dissolve when the codec-from-contract bridge derives decode and encode from CoproductWireContract rows directly."
]

service github.WorkflowRuns {
config {
endpoint: default_api_base
auth: Bearer
auth_source: EnvVar { name: "GITHUB_TOKEN" }

rate_limit: { requests: 5000, per: hour, scope: core }

retry: { max_attempts: 3, backoff: exponential, retry_on: [429, 500, 502, 503, 504] }
}

operation ListForRef {
input {
auth_token: Secret
owner: String
repo: String
head_sha: CommitSha
per_page: Int = default_per_page
}
output {
runs: WorkflowRunList
}
readonly
transport rest {
method: GET,
path: "/repos/\{owner\}/\{repo\}/actions/runs",
query: { head_sha: head_sha, per_page: per_page }
}
response {
200 => WorkflowRunList
401 => GitHubErrorShape
403 => GitHubErrorShape
404 => GitHubErrorShape
500 => GitHubErrorShape
}
}
}
22 changes: 22 additions & 0 deletions dsl/extdeps/github/workflow_runs_contracts.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module extdeps.github.workflow_runs_contracts

import extdeps.external_authority { ExternalAuthority }
import extdeps.uri { Uri, Https }
import std.serialization { CoproductWireContract, StringVariant, SnakeCase }

data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
uri: Uri {
scheme: Https
locator: "docs.github.com/en/rest/actions/workflow-runs"
}
}

data workflow_run_status_wire_contract: CoproductWireContract = {
coproduct: "WorkflowRunStatus"
encoding: StringVariant { naming: SnakeCase }
}

data workflow_run_conclusion_wire_contract: CoproductWireContract = {
coproduct: "WorkflowRunConclusion"
encoding: StringVariant { naming: SnakeCase }
}
176 changes: 176 additions & 0 deletions dsl/test/claim/github_workflow_runs_witness_test.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
module test.claim.github_workflow_runs_witness

import extdeps.github.workflow_runs {
WorkflowRunStatus, WorkflowRunConclusion, WorkflowRun, WorkflowRunList,
Queued, InProgress, Completed, Waiting, Requested, Pending,
Success, Failure, Cancelled, Neutral, Skipped, TimedOut, ActionRequired, StartupFailure,
parse_workflow_run_status, workflow_run_status_wire_label,
parse_workflow_run_conclusion, workflow_run_conclusion_wire_label
}

data sample_run: WorkflowRun = WorkflowRun {
id: 12345678
name: "CI"
head_sha: "abc123def456abc123def456abc123def456abc123"
status: Completed
conclusion: Present { value: Success }
workflow_id: 987
run_number: 42
html_url: "https://github.com/owner/repo/actions/runs/12345678"
created_at: "2026-06-23T10:00:00Z"
updated_at: "2026-06-23T10:15:00Z"
}

data sample_run_list: WorkflowRunList = WorkflowRunList {
total_count: 1
workflow_runs: [sample_run]
}

test fn workflow_run_status_wire_label_queued() -> Bool {
workflow_run_status_wire_label(Queued) == "queued"
}

test fn workflow_run_status_wire_label_in_progress() -> Bool {
workflow_run_status_wire_label(InProgress) == "in_progress"
}

test fn workflow_run_status_wire_label_completed() -> Bool {
workflow_run_status_wire_label(Completed) == "completed"
}

test fn workflow_run_status_wire_label_waiting() -> Bool {
workflow_run_status_wire_label(Waiting) == "waiting"
}

test fn workflow_run_status_wire_label_requested() -> Bool {
workflow_run_status_wire_label(Requested) == "requested"
}

test fn workflow_run_status_wire_label_pending() -> Bool {
workflow_run_status_wire_label(Pending) == "pending"
}

test fn parse_workflow_run_status_queued() -> Bool {
match parse_workflow_run_status("queued") {
Present { value: Queued } => true
_ => false
}
}

test fn parse_workflow_run_status_in_progress() -> Bool {
match parse_workflow_run_status("in_progress") {
Present { value: InProgress } => true
_ => false
}
}

test fn parse_workflow_run_status_completed() -> Bool {
match parse_workflow_run_status("completed") {
Present { value: Completed } => true
_ => false
}
}

test fn parse_workflow_run_status_invalid_returns_none() -> Bool {
match parse_workflow_run_status("unknown_status") {
Absent => true
_ => false
}
}

test fn parse_workflow_run_status_case_sensitive() -> Bool {
match parse_workflow_run_status("Queued") {
Absent => true
_ => false
}
}

test fn workflow_run_conclusion_wire_label_success() -> Bool {
workflow_run_conclusion_wire_label(Success) == "success"
}

test fn workflow_run_conclusion_wire_label_failure() -> Bool {
workflow_run_conclusion_wire_label(Failure) == "failure"
}

test fn workflow_run_conclusion_wire_label_startup_failure() -> Bool {
workflow_run_conclusion_wire_label(StartupFailure) == "startup_failure"
}

test fn workflow_run_conclusion_wire_label_action_required() -> Bool {
workflow_run_conclusion_wire_label(ActionRequired) == "action_required"
}

test fn workflow_run_conclusion_wire_label_timed_out() -> Bool {
workflow_run_conclusion_wire_label(TimedOut) == "timed_out"
}

test fn parse_workflow_run_conclusion_success() -> Bool {
match parse_workflow_run_conclusion("success") {
Present { value: Success } => true
_ => false
}
}

test fn parse_workflow_run_conclusion_startup_failure() -> Bool {
match parse_workflow_run_conclusion("startup_failure") {
Present { value: StartupFailure } => true
_ => false
}
}

test fn parse_workflow_run_conclusion_neutral() -> Bool {
match parse_workflow_run_conclusion("neutral") {
Present { value: Neutral } => true
_ => false
}
}

test fn parse_workflow_run_conclusion_invalid_returns_none() -> Bool {
match parse_workflow_run_conclusion("stale") {
Absent => true
_ => false
}
}

test fn parse_workflow_run_conclusion_case_sensitive() -> Bool {
match parse_workflow_run_conclusion("Success") {
Absent => true
_ => false
}
}

test fn workflow_run_status_roundtrip_queued() -> Bool {
(parse_workflow_run_status(workflow_run_status_wire_label(Queued))) == Present { value: Queued }
}

test fn workflow_run_status_roundtrip_in_progress() -> Bool {
(parse_workflow_run_status(workflow_run_status_wire_label(InProgress))) == Present { value: InProgress }
}

test fn workflow_run_status_roundtrip_completed() -> Bool {
(parse_workflow_run_status(workflow_run_status_wire_label(Completed))) == Present { value: Completed }
}

test fn workflow_run_conclusion_roundtrip_all() -> Bool {
let r_success = (parse_workflow_run_conclusion(workflow_run_conclusion_wire_label(Success))) == Present { value: Success }
let r_failure = (parse_workflow_run_conclusion(workflow_run_conclusion_wire_label(Failure))) == Present { value: Failure }
let r_cancelled = (parse_workflow_run_conclusion(workflow_run_conclusion_wire_label(Cancelled))) == Present { value: Cancelled }
let r_neutral = (parse_workflow_run_conclusion(workflow_run_conclusion_wire_label(Neutral))) == Present { value: Neutral }
let r_skipped = (parse_workflow_run_conclusion(workflow_run_conclusion_wire_label(Skipped))) == Present { value: Skipped }
let r_timed_out = (parse_workflow_run_conclusion(workflow_run_conclusion_wire_label(TimedOut))) == Present { value: TimedOut }
let r_action_required = (parse_workflow_run_conclusion(workflow_run_conclusion_wire_label(ActionRequired))) == Present { value: ActionRequired }
let r_startup_failure = (parse_workflow_run_conclusion(workflow_run_conclusion_wire_label(StartupFailure))) == Present { value: StartupFailure }
r_success && r_failure && r_cancelled && r_neutral && r_skipped && r_timed_out && r_action_required && r_startup_failure
}

test fn sample_run_has_success_conclusion() -> Bool {
match sample_run.conclusion {
Present { value: Success } => true
_ => false
}
}

test fn sample_run_list_has_one_run() -> Bool {
sample_run_list.total_count == 1
}