Skip to content

Commit b562443

Browse files
committed
ci: publish native tagged release assets
1 parent a1cb70e commit b562443

8 files changed

Lines changed: 241 additions & 22 deletions

File tree

.github/workflows/release.yml

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,93 @@
11
name: Release
2+
23
on:
34
workflow_dispatch:
45
push:
56
tags: ["v*"]
67

8+
permissions:
9+
contents: read
10+
711
jobs:
8-
bundle:
12+
source-assets:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-python@v5
17+
with: {python-version: "3.11", cache: pip}
18+
- run: python -m pip install -e ".[dev]" build
19+
- run: ruff format --check .
20+
- run: ruff check .
21+
- run: python -m pytest tests/unit -q
22+
- run: python scripts/validate_schemas.py
23+
- run: python scripts/validate_skill.py .
24+
- run: python scripts/security_scan.py .
25+
- run: python -m build
26+
- run: python scripts/generate_supply_chain_docs.py
27+
- uses: actions/upload-artifact@v4
28+
with:
29+
name: release-source-assets
30+
path: |
31+
dist/multi_api_test_executor-*.whl
32+
dist/multi_api_test_executor-*.tar.gz
33+
build/release/sbom.cdx.json
34+
build/release/THIRD_PARTY_LICENSES.txt
35+
36+
native-bundles:
937
strategy:
1038
fail-fast: false
1139
matrix:
1240
include:
13-
- {os: windows-latest, artifact: windows-x64}
14-
- {os: ubuntu-latest, artifact: linux-x64}
15-
- {os: macos-15-intel, artifact: macos-x64}
16-
- {os: macos-14, artifact: macos-arm64}
41+
- {os: windows-latest, platform: windows-x64}
42+
- {os: ubuntu-latest, platform: linux-x64}
43+
- {os: macos-15-intel, platform: macos-x64}
1744
runs-on: ${{ matrix.os }}
1845
steps:
1946
- uses: actions/checkout@v4
2047
- uses: actions/setup-python@v5
2148
with: {python-version: "3.11", cache: pip}
2249
- run: python -m pip install -e ".[dev]" build pyinstaller
23-
- run: ruff check .
24-
- run: python -m pytest --cov --cov-branch -q
25-
- run: python scripts/validate_schemas.py
26-
- run: python scripts/validate_skill.py .
2750
- run: python -m build
28-
- run: python -m pip download --dest wheelhouse dist/*.whl
51+
- run: python -m pip download --dest wheelhouse .
2952
- run: python scripts/build_binary.py
3053
- run: python scripts/generate_supply_chain_docs.py
31-
- run: python scripts/build_skill_bundle.py --platform ${{ matrix.artifact }}
54+
- run: python scripts/build_skill_bundle.py --platform ${{ matrix.platform }}
55+
- run: python scripts/verify_checksums.py build/release/SHA256SUMS.txt build/release
56+
- if: runner.os == 'Windows'
57+
run: powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/ci_bundle_smoke.ps1 -BundleZip build/release/multi-api-test-executor-0.1.0-${{ matrix.platform }}.zip
58+
- if: runner.os != 'Windows'
59+
run: sh scripts/ci_bundle_smoke.sh build/release/multi-api-test-executor-0.1.0-${{ matrix.platform }}.zip
60+
- run: python scripts/package_release_asset.py --bundle build/release/multi-api-test-executor-0.1.0-${{ matrix.platform }}.zip --platform ${{ matrix.platform }} --tag ${{ github.ref_name }}
3261
- uses: actions/upload-artifact@v4
3362
with:
34-
name: multi-api-test-executor-${{ matrix.artifact }}
35-
path: build/release/*
63+
name: release-${{ matrix.platform }}
64+
path: build/release-assets/*
3665

3766
publish:
3867
if: startsWith(github.ref, 'refs/tags/')
39-
needs: bundle
68+
needs: [source-assets, native-bundles]
4069
runs-on: ubuntu-latest
41-
permissions: {contents: write}
70+
permissions:
71+
contents: write
4272
steps:
73+
- uses: actions/checkout@v4
4374
- uses: actions/download-artifact@v4
4475
with: {path: artifacts, merge-multiple: true}
45-
- run: sha256sum artifacts/* > artifacts/SHA256SUMS.txt
76+
- name: Write aggregate checksums
77+
run: |
78+
python - <<'PY'
79+
from hashlib import sha256
80+
from pathlib import Path
81+
root = Path("artifacts")
82+
files = sorted(path for path in root.iterdir() if path.is_file())
83+
(root / "SHA256SUMS.txt").write_text(
84+
"\n".join(f"{sha256(path.read_bytes()).hexdigest()} {path.name}" for path in files) + "\n",
85+
encoding="utf-8",
86+
)
87+
PY
4688
- uses: softprops/action-gh-release@v2
4789
with:
4890
files: artifacts/*
49-
generate_release_notes: true
91+
body_path: docs/release-notes-v0.1.0.md
92+
prerelease: ${{ contains(github.ref_name, '-') }}
93+
fail_on_unmatched_files: true

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# Changelog
22

3-
## 0.1.0 RC (unreleased)
3+
## 0.1.0 RC - 2026-07-21
44

55
- Added real read-only SQLite checks with PostgreSQL/MySQL adapters, limits, assertions, and redacted evidence.
66
- Added graceful cancellation, deterministic statuses, audited cleanup, allowlisted plugins, and `summary_for_ai.json`.
77
- Hardened local OpenAPI refs, Postman scripts, SSRF, response limits, HTML/Excel output, and installer checksums.
8+
- Verified Windows x64, Linux x64, and macOS x64 bundles plus Python 3.11–3.13 on all three operating systems in GitHub Actions.
9+
- Verified PostgreSQL 17 and MySQL 8.4 service integration, Unix SIGINT evidence, offline installation, checksum, SBOM, licenses, and repeated Mock E2E.
810

911
## 0.1.0 - 2026-07-21
1012

README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# multi-api-test-executor
22

3+
[![CI](https://github.com/Saitamasans/skill-multi-api-test-executor/actions/workflows/ci.yml/badge.svg)](https://github.com/Saitamasans/skill-multi-api-test-executor/actions/workflows/ci.yml)
4+
35
> 第十个 Skill|多接口自动化执行与证据回填
46
57
从接口文档出发,自动生成并真实执行多接口业务链路,完成动态参数传递、确定性断言、证据保存和结果回填。
@@ -17,6 +19,20 @@ HTTP 200 只证明传输层满足预期,不等于业务成功。业务结论
1719

1820
LLM 可以分析非结构化资料并提出候选规则;它不发送请求、不决定最终 passed/failed、不模拟证据。
1921

22+
## 真实支持矩阵
23+
24+
以下状态来自 GitHub Actions 原生 Runner 与真实数据库 service,不是仅解析 workflow 配置:
25+
26+
| 平台或依赖 | 状态 | 验证范围 |
27+
|---|---|---|
28+
| Windows x64 | 已真实验证 | Python 3.11–3.13、离线 Bundle、doctor、本地 Mock E2E |
29+
| Linux x64 | 已真实验证 | Python 3.11–3.13、SIGINT、离线 Bundle、doctor、本地 Mock E2E |
30+
| macOS x64 | 已真实验证 | Intel 原生 Runner、Python 3.11–3.13、SIGINT、离线 Bundle、doctor、本地 Mock E2E |
31+
| macOS arm64 | 未验证 | v0.1.0 不发布 arm64 产物 |
32+
| SQLite | 已真实验证 | 只读 post-check 与完整 E2E |
33+
| PostgreSQL 17 | 已真实验证 | GitHub service、参数绑定、只读事务与 SQL 门禁 |
34+
| MySQL 8.4 | 已真实验证 | GitHub service、参数绑定、只读事务与 SQL 门禁 |
35+
2036
## 支持资料与 L1-L4
2137

2238
| 等级 | 资料 | 可信能力 |
@@ -133,13 +149,15 @@ evidence/RUN_ID/
133149

134150
Runner 定位优先级:`MULTI_API_RUNNER_PATH`、PATH、Windows `%LOCALAPPDATA%\multi-api-test-executor\multi-api.cmd`、macOS/Linux `${XDG_DATA_HOME:-$HOME/.local/share}/multi-api-test-executor/multi-api`。Skill 0.1.x 兼容 Runner `>=0.1.0,<0.2.0`,不会自动升级或在 run 阶段安装。
135151

136-
当前机器为 Windows/Python 3.14:SQLite 和 Windows Runner 可本机验证;PostgreSQL/MySQL 与 Python 3.11–3.13 由 CI 定义覆盖;macOS/Linux 当前未做本机原生验证
152+
GitHub Actions 已在 Windows、Linux、macOS 的 Python 3.11–3.13 上真实运行;PostgreSQL 17、MySQL 8.4、Linux/macOS SIGINT 以及三个已发布平台的离线安装和 Mock E2E 均通过
137153

138-
- v0.1 聚焦 REST;可插拔加密、真实数据库驱动执行和 Ctrl+C 跨平台信号测试仍需增强。
154+
- v0.1 聚焦 REST,不支持 UI、gRPC 或 WebSocket 执行。
155+
- Windows 合作式取消已覆盖;GitHub Windows Runner 无法可靠完成原生双 Ctrl+C E2E,因此该项仍为已知限制。
156+
- Postman JavaScript 不执行;远程 OpenAPI `$ref` 默认关闭;插件未经 allowlist 配置不执行。
139157
- Markdown 是保守的 Method/Path 提取,不理解任意自然语言表格。
140158
- Excel 仅支持基础列映射和结果副本回填。
141159
- Apifox 仅确定性支持其 OpenAPI 导出格式。
142-
- macOS x64/arm64 发行依赖 GitHub 对应原生 Runner;工作流不会把未构建架构标成成功
160+
- macOS v0.1.0 仅发布已在 Intel 原生 Runner 验证的 x64 产物,不宣称 arm64 支持
143161

144162
## 性能基准
145163

README_EN.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# Multi-API Test Executor
22

3+
[![CI](https://github.com/Saitamasans/skill-multi-api-test-executor/actions/workflows/ci.yml/badge.svg)](https://github.com/Saitamasans/skill-multi-api-test-executor/actions/workflows/ci.yml)
4+
35
## V0.1 release-candidate capabilities
46

57
The runner supports real read-only SQLite post-checks, PostgreSQL/MySQL adapters, graceful cancellation with partial evidence, audited cleanup, an allowlisted plugin SDK, escaped RC HTML reports, and compact `summary_for_ai.json` output. Security controls include local-only OpenAPI refs, Postman script non-execution, SSRF policies, bounded evidence, Excel formula neutralization, and installer SHA-256 checks.
68

79
Exit codes are 0 passed, 1 test/assertion/cleanup failure, 2 input or preflight error, 3 infrastructure error, 4 cancellation, 5 partial/time-budget completion, and 6 internal error. Skill 0.1.x requires Runner `>=0.1.0,<0.2.0` and never installs at run time.
810

9-
Native verification here is Windows/Python 3.14. SQLite is local; PostgreSQL/MySQL and Python 3.11–3.13 are configured in CI. macOS/Linux were not run natively on this machine.
11+
GitHub Actions has natively verified Windows x64, Linux x64, and macOS x64 across Python 3.11–3.13. SQLite, PostgreSQL 17, MySQL 8.4, Unix SIGINT, offline installation, doctor, and the local Mock E2E all pass. macOS arm64 is unverified and is not published for v0.1.0; native Windows double Ctrl+C remains a known limitation.
1012

1113
An execution Skill that compiles API material into ATIR, sends real REST requests through a deterministic Runner, passes dynamic values across steps, evaluates sourced assertions, and writes redacted evidence plus JSON/HTML/JUnit reports.
1214

docs/installation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ Use the matching uninstall script. The scripts validate that default deletion st
1919
## Build limitations
2020

2121
Independent binaries use PyInstaller on native GitHub-hosted operating systems. macOS x64 and arm64 jobs are separate because cross-compiling those architectures is not treated as equivalent proof. If a native runner is unavailable, the corresponding job fails or remains absent; release notes must not claim that artifact.
22+
23+
Version 0.1.0 publishes only the architectures verified by native CI: Windows x64, Linux x64, and macOS x64. macOS arm64 is not published for this version.

docs/release-notes-v0.1.0.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Multi-API Test Executor v0.1.0
2+
3+
Multi-API Test Executor is an execution-oriented Codex Skill plus deterministic Runner. It compiles API documents and requirements into auditable REST workflows with dynamic value propagation, sourced assertions, read-only database checks, cleanup, and redacted evidence.
4+
5+
## Verified platforms
6+
7+
- Windows x64, Linux x64, and macOS x64 native bundles.
8+
- Python 3.11, 3.12, and 3.13 on Windows, Linux, and macOS.
9+
- SQLite, PostgreSQL 17, and MySQL 8.4 read-only database checks.
10+
11+
## Install once
12+
13+
This release does not provide an automatic Skill installation hook. Download the archive for the platform, verify it against `SHA256SUMS.txt`, extract it, and run the installer once:
14+
15+
- Windows: `powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\installers\install.ps1`
16+
- Linux/macOS: `sh installers/install.sh`
17+
18+
The installed Runner is reused by later Skill runs. Normal execution never invokes pip or updates dependencies.
19+
20+
## Trust and evidence
21+
22+
HTTP 200 is transport evidence, not automatic business success. Business outcomes require independent, sourced assertions. The Runner writes JSON, HTML, JUnit, manifest, cleanup, database, and cancellation evidence while recursively redacting credentials and sensitive headers.
23+
24+
Database post-checks permit only parameterized read-only queries. Production execution is blocked by default and high-risk operations require explicit approval.
25+
26+
## Known limits
27+
28+
- REST only; UI, gRPC, and WebSocket execution are not supported.
29+
- Postman JavaScript is not executed.
30+
- Remote OpenAPI `$ref` resolution is disabled by default.
31+
- Plugins require explicit allowlist configuration.
32+
- macOS arm64 is not included because v0.1.0 CI verified only the native x64 release runner.
33+
- Windows cooperative cancellation is covered; native double Ctrl+C remains a documented platform limitation.
34+
35+
## Integrity and RC plan
36+
37+
Verify every downloaded asset with `SHA256SUMS.txt`. The release also includes a CycloneDX SBOM and third-party license inventory. The RC is promoted to stable only after downloading the Windows asset from GitHub into a clean directory, verifying its checksum, installing offline, running doctor and the complete local Mock E2E twice, and confirming installation state remains unchanged.

scripts/package_release_asset.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import re
5+
import shutil
6+
import tarfile
7+
import tempfile
8+
import zipfile
9+
from pathlib import Path, PurePosixPath
10+
11+
VERSION = re.compile(r"^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?)$")
12+
13+
14+
def release_version(tag: str) -> str:
15+
match = VERSION.fullmatch(tag)
16+
if not match:
17+
raise ValueError(f"unsupported release tag: {tag}")
18+
return match.group(1)
19+
20+
21+
def _validate_zip_members(bundle: Path) -> None:
22+
with zipfile.ZipFile(bundle) as archive:
23+
for name in archive.namelist():
24+
member = PurePosixPath(name)
25+
if member.is_absolute() or ".." in member.parts:
26+
raise ValueError(f"unsafe bundle member: {name}")
27+
28+
29+
def package_release_asset(bundle: Path, platform: str, tag: str, output: Path) -> Path:
30+
bundle = bundle.resolve()
31+
if not bundle.is_file():
32+
raise FileNotFoundError(bundle)
33+
_validate_zip_members(bundle)
34+
version = release_version(tag)
35+
output.mkdir(parents=True, exist_ok=True)
36+
stem = f"multi-api-test-executor-{version}-{platform}"
37+
if platform.startswith("windows"):
38+
target = output / f"{stem}.zip"
39+
shutil.copy2(bundle, target)
40+
return target
41+
target = output / f"{stem}.tar.gz"
42+
with tempfile.TemporaryDirectory(prefix="multi-api-release-") as temporary:
43+
extracted = Path(temporary)
44+
with zipfile.ZipFile(bundle) as archive:
45+
archive.extractall(extracted)
46+
with tarfile.open(target, "w:gz") as archive:
47+
for path in sorted(extracted.rglob("*")):
48+
archive.add(path, arcname=path.relative_to(extracted).as_posix(), recursive=False)
49+
return target
50+
51+
52+
def main() -> None:
53+
parser = argparse.ArgumentParser()
54+
parser.add_argument("--bundle", type=Path, required=True)
55+
parser.add_argument("--platform", required=True)
56+
parser.add_argument("--tag", required=True)
57+
parser.add_argument("--output", type=Path, default=Path("build/release-assets"))
58+
args = parser.parse_args()
59+
print(package_release_asset(args.bundle, args.platform, args.tag, args.output))
60+
61+
62+
if __name__ == "__main__":
63+
main()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from __future__ import annotations
2+
3+
import tarfile
4+
import zipfile
5+
from pathlib import Path
6+
7+
ROOT = Path(__file__).parents[2]
8+
9+
10+
def load_packager() -> dict[str, object]:
11+
script = ROOT / "scripts/package_release_asset.py"
12+
namespace: dict[str, object] = {"__name__": "release_packager_test", "__file__": str(script)}
13+
exec(compile(script.read_text(encoding="utf-8"), str(script), "exec"), namespace)
14+
return namespace
15+
16+
17+
def test_release_packager_uses_tagged_platform_names_and_expected_formats(tmp_path: Path) -> None:
18+
namespace = load_packager()
19+
bundle = tmp_path / "bundle.zip"
20+
with zipfile.ZipFile(bundle, "w") as archive:
21+
archive.writestr("SKILL.md", "release fixture")
22+
archive.writestr("installers/install.sh", "#!/bin/sh\n")
23+
output = tmp_path / "assets"
24+
25+
windows = namespace["package_release_asset"](bundle, "windows-x64", "v0.1.0-rc.1", output)
26+
linux = namespace["package_release_asset"](bundle, "linux-x64", "v0.1.0-rc.1", output)
27+
28+
assert windows.name == "multi-api-test-executor-0.1.0-rc.1-windows-x64.zip"
29+
assert linux.name == "multi-api-test-executor-0.1.0-rc.1-linux-x64.tar.gz"
30+
with zipfile.ZipFile(windows) as archive:
31+
assert "SKILL.md" in archive.namelist()
32+
with tarfile.open(linux, "r:gz") as archive:
33+
assert "SKILL.md" in archive.getnames()
34+
35+
36+
def test_release_workflow_publishes_native_assets_and_supply_chain_files() -> None:
37+
workflow = (ROOT / ".github/workflows/release.yml").read_text(encoding="utf-8")
38+
for phrase in [
39+
"windows-x64",
40+
"linux-x64",
41+
"macos-x64",
42+
"scripts/package_release_asset.py",
43+
"multi_api_test_executor-*.whl",
44+
"multi_api_test_executor-*.tar.gz",
45+
"sbom.cdx.json",
46+
"THIRD_PARTY_LICENSES.txt",
47+
"SHA256SUMS.txt",
48+
"docs/release-notes-v0.1.0.md",
49+
"prerelease: ${{ contains(github.ref_name, '-') }}",
50+
]:
51+
assert phrase in workflow

0 commit comments

Comments
 (0)