Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4b49bb6
chore: import the #129 SDK runtime evidence checkpoint onto main
rldyourmnd Aug 14, 2026
279dde7
fix(sdk-fixtures): make the root trust rule a function of the rule, n…
rldyourmnd Aug 14, 2026
7fd429e
fix(sdk-ci): record the callee, and stop a generic reusable asserting…
rldyourmnd Aug 14, 2026
ae630ed
fix(sdk-fixtures): validate the receipt as a typed record with one di…
rldyourmnd Aug 14, 2026
bf49286
feat(sdk-fixtures): resolve the Flutter pin against the official mani…
rldyourmnd Aug 14, 2026
01f252a
docs: describe the receipt the reusables actually emit, and name the …
rldyourmnd Aug 14, 2026
8babc4b
ci(fixtures): add the Android exact-build reproduction lane
rldyourmnd Aug 14, 2026
4895b1c
fix(sdk-ci): make the three SDK lanes able to start at all
rldyourmnd Aug 14, 2026
c5234cd
fix(sdk-fixtures): let the root trust rule run on a hosted runner, ex…
rldyourmnd Aug 14, 2026
eada1a6
fix(sdk-fixtures): make the root trust rule name its trust model
rldyourmnd Aug 14, 2026
e789f9d
feat(sdk-fixtures): commit the Android exact-build closure, generated…
rldyourmnd Aug 14, 2026
97d79a8
fix(sdk-fixtures): let the observer read the receipt, and record what…
rldyourmnd Aug 14, 2026
df94855
fix(sdk-fixtures): make the committed task graph reproducible, and na…
rldyourmnd Aug 14, 2026
ad2199e
docs(changelog): record the SDK runtime evidence work
rldyourmnd Aug 14, 2026
250809a
fix(fixtures): keep the vendored Android closure out of the repositor…
rldyourmnd Aug 14, 2026
060792c
fix(fixtures): make the estate and the ledger agree about what can run
rldyourmnd Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# actionlint configuration.
#
# One suppression, scoped to three files and three property names.
#
# `job.workflow_repository`, `job.workflow_sha` and `job.workflow_file_path` are
# real GitHub Actions contexts. They are the only way a called reusable workflow
# can name *itself*: inside a reusable, every `github.*` value describes the
# CALLER, so `github.workflow_ref` and `github.workflow_sha` identify the calling
# workflow, not the called one. The three SDK reusables record the callee triple
# in their runtime receipt so the provenance in that receipt is the workflow that
# actually ran.
#
# actionlint v1.7.12 -- the current release, and the version `actionlint.yml`
# pins -- models the `job` context as only
# `{check_run_id, container, services, status}` and so reports these three as
# undefined properties. Upstream has no issue or commit for them.
#
# Proven to exist rather than assumed: a probe reusable pinned at an absolute
# commit and called from a different commit reported
# `job.workflow_sha=39f043f016b382760a566f9a2a3189c47b8ef74b` (the callee) while
# `github.sha=f40c3f0a63e7d29ffe07864615ae7bcd5e16f2b8` (the caller), in run
# https://github.com/NDDev-it-com/ci-workflows/actions/runs/31779014883.
#
# The suppression names the three properties explicitly, so a typo such as
# `job.workflow_shaa` is still reported. `check_actionlint_contract.py`
# additionally proves this file is still load-bearing and still narrow: it runs
# actionlint without the config and fails if these errors have stopped
# appearing, which is how this file gets deleted once upstream catches up.
paths:
.github/workflows/{dart-flutter-ci,kotlin-android-ci,qt-ci}.yml:
ignore:
- '^property "workflow_(file_path|repository|sha)" is not defined in object type \{check_run_id: number; container: .+\}$'
86 changes: 85 additions & 1 deletion .github/workflows/dart-flutter-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ name: dart-flutter-ci

on:
workflow_call:
outputs:
evidence:
description: 'Redacted JSON receipt for the resolved SDK and successful default lanes.'
value: ${{ jobs.dart-flutter.outputs.evidence }}
inputs:
runner:
type: string
Expand Down Expand Up @@ -54,6 +58,8 @@ jobs:
timeout-minutes: ${{ inputs.timeout_minutes }}
permissions:
contents: read
outputs:
evidence: ${{ steps.evidence.outputs.evidence }}
defaults:
run:
# Explicit, and not redundant with the workflow-level default above:
Expand All @@ -70,6 +76,7 @@ jobs:
persist-credentials: false

- name: Set up Flutter
id: flutter
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
with:
channel: ${{ inputs.flutter_channel }}
Expand All @@ -94,6 +101,83 @@ jobs:
env:
TEST_COMMAND: ${{ inputs.test_command }}
FLUTTER_CHANNEL: ${{ inputs.flutter_channel }}
TEST_LOG: ${{ runner.temp }}/dart-flutter-tests.log
run: |
bash -euo pipefail -c "$TEST_COMMAND"
set -euo pipefail
bash -euo pipefail -c "$TEST_COMMAND" 2>&1 | tee "$TEST_LOG"
echo "Flutter ${FLUTTER_CHANNEL} format + analyze + tests passed." >> "$GITHUB_STEP_SUMMARY"

- name: Emit runtime evidence
id: evidence
env:
CACHE_KEY: ${{ steps.flutter.outputs.CACHE-KEY }}
CALLEE_PATH: ${{ job.workflow_file_path }}
CALLEE_REPOSITORY: ${{ job.workflow_repository }}
CALLEE_SHA: ${{ job.workflow_sha }}
FLUTTER_ARCH: ${{ steps.flutter.outputs.ARCHITECTURE }}
FLUTTER_CHANNEL: ${{ steps.flutter.outputs.CHANNEL }}
FLUTTER_VERSION: ${{ steps.flutter.outputs.VERSION }}
PUB_CACHE_KEY: ${{ steps.flutter.outputs.PUB-CACHE-KEY }}
PUB_GET_COMMAND: ${{ inputs.pub_get_command }}
TEST_COMMAND: ${{ inputs.test_command }}
TEST_LOG: ${{ runner.temp }}/dart-flutter-tests.log
run: |
python3 -I <<'PY'
import hashlib, json, os, pathlib, re, subprocess

version = json.loads(subprocess.check_output(
["flutter", "--version", "--machine"], text=True))

# Provenance of the workflow that is running, not of the caller's tree.
# `github.*` inside a called reusable is bound to the CALLER, so hashing
# `${github.workspace}/.github/workflows/dart-flutter-ci.yml` digests a
# file that belongs to the caller. `job.workflow_*` is bound to the
# callee and supplied by the runner, so the repository/sha/path triple
# names these exact bytes and no caller can forge it.
receipt = {
"callee_path": os.environ["CALLEE_PATH"],
"callee_repository": os.environ["CALLEE_REPOSITORY"],
"callee_sha": os.environ["CALLEE_SHA"],
"caller_repository": os.environ["GITHUB_REPOSITORY"],
"caller_sha": os.environ["GITHUB_SHA"],
"dart_version": version["dartSdkVersion"].split()[0],
"flutter_arch": os.environ["FLUTTER_ARCH"],
"flutter_channel": os.environ["FLUTTER_CHANNEL"],
"flutter_revision": version["frameworkRevision"],
"flutter_version": os.environ["FLUTTER_VERSION"],
"kind": "flutter",
"os": os.environ["RUNNER_OS"],
"runner_arch": os.environ["RUNNER_ARCH"],
"schema_version": 1,
}
sections = ["toolchain"]
if os.environ["CACHE_KEY"] and os.environ["PUB_CACHE_KEY"]:
sections.append("cache")
receipt["cache_key"] = os.environ["CACHE_KEY"]
receipt["pub_cache_key"] = os.environ["PUB_CACHE_KEY"]

# `pub_get_command` is documented "Empty to skip", so a caller that
# skips resolution has no reason to own a `pubspec.lock` -- reading one
# unconditionally turned a documented option into a crash.
lock = pathlib.Path("pubspec.lock")
if os.environ["PUB_GET_COMMAND"] and lock.is_file():
sections.append("resolve")
receipt["pub_get_command"] = os.environ["PUB_GET_COMMAND"]
receipt["pubspec_lock_sha256"] = hashlib.sha256(lock.read_bytes()).hexdigest()

log = pathlib.Path(os.environ["TEST_LOG"])
if os.environ["TEST_COMMAND"] and log.is_file():
counts = [int(value) for value in re.findall(
r"\+(\d+)", log.read_text(encoding="utf-8"))]
sections.append("test")
receipt["test_command"] = os.environ["TEST_COMMAND"]
receipt["test_count"] = max(counts, default=0)
receipt["test_log_sha256"] = hashlib.sha256(log.read_bytes()).hexdigest()

receipt["sections"] = sorted(sections)
encoded = json.dumps(receipt, sort_keys=True, separators=(",", ":"))
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
output.write(f"evidence={encoded}\n")
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as summary:
summary.write(f"\nRuntime evidence: `{encoded}`\n")
PY
Loading
Loading