Repository navigation
refactor(public-safety): decide the credential question at the two capability faces #5532
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Artifacts | |
| on: | |
| pull_request: | |
| paths: | |
| - ".github/workflows/release-artifacts.yml" | |
| - "examples/release-artifacts-smoke.py" | |
| - "loopx/**" | |
| - "package.json" | |
| - "package-lock.json" | |
| - "pyproject.toml" | |
| - "setup.py" | |
| - "MANIFEST.in" | |
| - "apps/presentation/dashboard/**" | |
| - "scripts/chat_bundle.py" | |
| - "scripts/verify_installed_chat.py" | |
| - "README.md" | |
| - "scripts/release_artifacts.py" | |
| - "tsconfig.control-plane.json" | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Existing GitHub Release tag to build without changing its product source" | |
| required: true | |
| type: string | |
| browser_fixture_commit: | |
| description: "Optional full merged commit supplying corrected browser fixtures" | |
| required: false | |
| type: string | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: release-artifacts-${{ github.event.release.tag_name || inputs.tag || github.ref }} | |
| # Superseded PR validation may stop; publication retains its serialized lane. | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| build: | |
| if: github.repository == 'loopx-project/loopx' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| attestations: write | |
| outputs: | |
| release-tag: ${{ steps.identity.outputs.release-tag }} | |
| steps: | |
| - name: Check out release source | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event.release.tag_name || inputs.tag || github.sha }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Set up the packaged TypeScript control-plane runtime | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| - name: Resolve and validate release identity | |
| id: identity | |
| env: | |
| EVENT_RELEASE_TAG: ${{ github.event.release.tag_name }} | |
| INPUT_RELEASE_TAG: ${{ inputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| release_tag="${EVENT_RELEASE_TAG:-${INPUT_RELEASE_TAG:-}}" | |
| if [[ -z "${release_tag}" ]]; then | |
| release_tag="$(python scripts/release_artifacts.py expected-tag)" | |
| fi | |
| python scripts/release_artifacts.py validate-tag "${release_tag}" | |
| source_date_epoch="$(git show -s --format=%ct HEAD)" | |
| echo "release-tag=${release_tag}" >> "${GITHUB_OUTPUT}" | |
| echo "RELEASE_TAG=${release_tag}" >> "${GITHUB_ENV}" | |
| echo "SOURCE_DATE_EPOCH=${source_date_epoch}" >> "${GITHUB_ENV}" | |
| echo "PYTHONHASHSEED=0" >> "${GITHUB_ENV}" | |
| - name: Build exact release frontend with previous delivery assets | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: python scripts/chat_bundle.py release-build --tag "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" | |
| - name: Install release build tools | |
| run: python -m pip install --disable-pip-version-check build==1.4.4 twine==6.2.0 | |
| - name: Build wheel and source distribution | |
| run: python -m build --sdist --wheel --outdir dist/packages | |
| - name: Normalize source distribution metadata | |
| run: >- | |
| python scripts/release_artifacts.py normalize-sdist | |
| --dist-dir dist/packages | |
| --source-date-epoch "${SOURCE_DATE_EPOCH}" | |
| - name: Validate package metadata | |
| run: python -m twine check dist/packages/* | |
| - name: Generate and verify checksums | |
| run: | | |
| set -euo pipefail | |
| python scripts/release_artifacts.py write-checksums \ | |
| --dist-dir dist/packages \ | |
| --output dist/SHA256SUMS | |
| python scripts/release_artifacts.py verify-checksums \ | |
| --dist-dir dist/packages \ | |
| --checksum-file dist/SHA256SUMS | |
| - name: Verify wheel in a clean environment | |
| run: | | |
| set -euo pipefail | |
| python -m venv "${RUNNER_TEMP}/loopx-wheel" | |
| "${RUNNER_TEMP}/loopx-wheel/bin/python" -m pip install \ | |
| --disable-pip-version-check \ | |
| --no-deps \ | |
| dist/packages/*.whl | |
| test "$("${RUNNER_TEMP}/loopx-wheel/bin/loopx" --version)" = \ | |
| "loopx ${RELEASE_TAG#v}" | |
| "${RUNNER_TEMP}/loopx-wheel/bin/python" - <<'PY' | |
| from loopx.control_plane.effect_program import SettlementIdentity | |
| from loopx.control_plane.effect_runtime import ( | |
| collect_effect_runtime_readiness, | |
| ) | |
| from loopx.control_plane.turn_driver.turn_journal_runtime import ( | |
| interpret_turn_journal_projection, | |
| write_turn_journal, | |
| ) | |
| from pathlib import Path | |
| from tempfile import TemporaryDirectory | |
| turn_key = "sha256:" + "a" * 64 | |
| journal_identity = SettlementIdentity( | |
| goal_id="fixture-goal", | |
| agent_id="fixture-agent", | |
| todo_id="fixture-todo", | |
| turn_instance_id=turn_key, | |
| ) | |
| journal = { | |
| "schema_version": "loopx_turn_journal_v0", | |
| "goal_id": "fixture-goal", | |
| "turn_key": turn_key, | |
| "status": "committed", | |
| "completed_phases": [ | |
| "host_execute", | |
| "typed_result", | |
| "validation", | |
| "durable_writeback", | |
| "quota_spend", | |
| "scheduler_apply", | |
| "scheduler_ack", | |
| ], | |
| "plan": { | |
| "turn_envelope": { | |
| "goal_id": "fixture-goal", | |
| "agent_id": "fixture-agent", | |
| "action": { | |
| "selected_todo": {"todo_id": "fixture-todo"}, | |
| }, | |
| }, | |
| "transaction": { | |
| "turn_key": turn_key, | |
| "settlement_plan": { | |
| "schema_version": "quota_settlement_plan_v1", | |
| "identity": journal_identity.as_dict(), | |
| }, | |
| }, | |
| }, | |
| } | |
| result = interpret_turn_journal_projection( | |
| journal, | |
| goal_id="fixture-goal", | |
| agent_id="fixture-agent", | |
| turn_key=turn_key, | |
| ) | |
| assert result["decision"] == "replay_legal", result | |
| assert result["effects"] == [], result | |
| identity = SettlementIdentity( | |
| goal_id="fixture-goal", | |
| agent_id="fixture-agent", | |
| todo_id="fixture-todo", | |
| turn_instance_id="fixture-turn", | |
| ) | |
| assert identity.effect_id == ( | |
| "fixture-goal:fixture-agent:fixture-todo:fixture-turn" | |
| ), identity | |
| readiness = collect_effect_runtime_readiness(deep=True) | |
| assert readiness["status"] == "ready", readiness | |
| assert readiness["semantic_probe"] == "passed", readiness | |
| with TemporaryDirectory() as temporary_directory: | |
| effect_id = journal_identity.effect_id | |
| writable_journal = { | |
| **journal, | |
| "status": "in_progress", | |
| "completed_phases": [], | |
| } | |
| committed = write_turn_journal( | |
| str(Path(temporary_directory) / "turn.json"), | |
| writable_journal, | |
| expected_effect_id=effect_id, | |
| ) | |
| assert committed["appended"] is True, committed | |
| assert committed["replayed"] is False, committed | |
| PY | |
| - name: Verify installed frontend and sdist rebuild | |
| run: | | |
| python scripts/verify_installed_chat.py --python "${RUNNER_TEMP}/loopx-wheel/bin/python" | |
| python -m pip wheel --no-deps dist/packages/*.tar.gz --wheel-dir "${RUNNER_TEMP}/sdist-wheel" | |
| "${RUNNER_TEMP}/loopx-wheel/bin/python" -m pip install --force-reinstall --no-deps "${RUNNER_TEMP}"/sdist-wheel/*.whl | |
| python scripts/verify_installed_chat.py --python "${RUNNER_TEMP}/loopx-wheel/bin/python" | |
| - name: Resolve browser fixture correction without rebuilding product bytes | |
| env: | |
| BROWSER_FIXTURE_COMMIT: ${{ inputs.browser_fixture_commit }} | |
| run: | | |
| python - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| import re | |
| import subprocess | |
| def git(*args): | |
| return subprocess.check_output(["git", *args], text=True).strip() | |
| product = git("rev-parse", "HEAD") | |
| fixture = os.environ.get("BROWSER_FIXTURE_COMMIT", "") or product | |
| if not re.fullmatch(r"[0-9a-f]{40}", fixture): | |
| raise SystemExit("browser fixture correction requires a full commit SHA") | |
| if git("rev-parse", fixture + "^{commit}") != fixture: | |
| raise SystemExit("browser fixture correction must name a commit") | |
| allowed = { | |
| "examples/personal-workspace-browser/fixture.mjs", | |
| "examples/personal-workspace-browser/typed-actions.mjs", | |
| "examples/personal-workspace-browser/goal-work-map.mjs", | |
| } | |
| changed = set(git("diff", "--name-only", product, fixture, "--", *sorted(allowed)).splitlines()) | |
| if fixture != product and not changed: | |
| raise SystemExit("browser fixture commit contains no correction in the fixture owners") | |
| if fixture != product: | |
| subprocess.run(["git", "merge-base", "--is-ancestor", fixture, "origin/main"], check=True) | |
| for path in sorted(changed): | |
| if git("ls-tree", fixture, "--", path).split()[0] != "100644": | |
| raise SystemExit("browser fixture correction must retain a regular source file") | |
| Path(path).write_bytes(subprocess.check_output(["git", "show", fixture + ":" + path])) | |
| Path("dist/RELEASE-SOURCES.json").write_text(json.dumps({ | |
| "product_tag": os.environ["RELEASE_TAG"], | |
| "product_commit": product, | |
| "browser_fixture_commit": fixture, | |
| "browser_fixture_paths": sorted(changed), | |
| "workflow_commit": os.environ["GITHUB_SHA"], | |
| "workflow_ref": os.environ["GITHUB_WORKFLOW_REF"], | |
| }, indent=2) + "\n") | |
| PY | |
| - name: Exercise the installed page in a real browser | |
| working-directory: apps/presentation/dashboard | |
| env: | |
| LOOPX_PYTHON_BIN: ${{ runner.temp }}/loopx-wheel/bin/python | |
| run: | | |
| ./node_modules/.bin/playwright install --with-deps chromium | |
| npm run smoke:personal-workspace-packaged | |
| - name: Verify wheel through the DSH managed-runtime install shape | |
| run: | | |
| set -euo pipefail | |
| target="${RUNNER_TEMP}/loopx-wheel-target" | |
| skills_dir="${RUNNER_TEMP}/loopx-wheel-target-skills" | |
| python -m pip install \ | |
| --disable-pip-version-check \ | |
| --no-deps \ | |
| --target "${target}" \ | |
| dist/packages/*.whl | |
| ( | |
| cd "${RUNNER_TEMP}" | |
| PYTHONPATH="${target}" python -m loopx.cli --format json \ | |
| workflow-skills --skills-dir "${skills_dir}" \ | |
| --host-surface deepseek-harness-native \ | |
| > "${RUNNER_TEMP}/loopx-wheel-target-inspect.json" | |
| ) | |
| python - "${RUNNER_TEMP}/loopx-wheel-target-inspect.json" <<'PY' | |
| import json | |
| from pathlib import Path | |
| import sys | |
| payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) | |
| assert payload["ok"] is True, payload | |
| assert payload["operation"] == "inspect", payload | |
| assert payload["host_surface"] == "deepseek-harness-native", payload | |
| assert payload["source"]["kind"] == "python_distribution", payload | |
| assert payload["install_required"] is True, payload | |
| PY | |
| export CODEX_HOME="${RUNNER_TEMP}/loopx-wheel-codex" | |
| skills_dir="${CODEX_HOME}/skills" | |
| "${RUNNER_TEMP}/loopx-wheel/bin/loopx" --format json \ | |
| workflow-skills --install --skills-dir "${skills_dir}" \ | |
| > "${RUNNER_TEMP}/loopx-workflow-skills-install.json" | |
| "${RUNNER_TEMP}/loopx-wheel/bin/python" - \ | |
| "${RUNNER_TEMP}/loopx-workflow-skills-install.json" <<'PY' | |
| import json | |
| from pathlib import Path | |
| import sys | |
| payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) | |
| assert payload["ok"] is True, payload | |
| assert payload["source"]["kind"] == "python_distribution", payload | |
| assert payload["after"]["ready"] is True, payload | |
| PY | |
| conflicting_bin="${RUNNER_TEMP}/loopx-conflicting-bin" | |
| mkdir -p "${conflicting_bin}" | |
| ln -s /usr/bin/false "${conflicting_bin}/loopx" | |
| PATH="${conflicting_bin}:${PATH}" \ | |
| "${RUNNER_TEMP}/loopx-wheel/bin/loopx" --format json \ | |
| doctor --deep > "${RUNNER_TEMP}/loopx-wheel-doctor.json" | |
| "${RUNNER_TEMP}/loopx-wheel/bin/python" - \ | |
| "${RUNNER_TEMP}/loopx-wheel-doctor.json" <<'PY' | |
| import json | |
| from pathlib import Path | |
| import sys | |
| payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) | |
| assert payload["ok"] is True, payload | |
| assert payload["package"]["install_kind"] == "python_distribution", payload | |
| assert payload["path"]["loopx"] != payload["path"]["current_invocation"] | |
| validation = payload["release_candidate"] | |
| checks = {item["id"]: item for item in validation["checks"]} | |
| assert validation["schema_version"] == "loopx_python_distribution_checks_v0" | |
| assert checks["command_package_same_distribution"]["ok"] is True, payload | |
| assert checks["representative_distribution_paths"]["ok"] is True, payload | |
| assert "scripts/loopx" not in validation["representative_cli"][ | |
| "package_paths" | |
| ]["required"] | |
| PY | |
| "${RUNNER_TEMP}/loopx-wheel/bin/loopx" --format json \ | |
| workflow-skills --uninstall --skills-dir "${skills_dir}" \ | |
| > "${RUNNER_TEMP}/loopx-workflow-skills-uninstall.json" | |
| "${RUNNER_TEMP}/loopx-wheel/bin/python" - \ | |
| "${RUNNER_TEMP}/loopx-workflow-skills-uninstall.json" \ | |
| "${RUNNER_TEMP}/loopx-workflow-skills-install.json" <<'PY' | |
| import json | |
| from pathlib import Path | |
| import sys | |
| payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) | |
| assert payload["ok"] is True, payload | |
| installed = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8")) | |
| expected = installed["after"]["required_skill_ids"] | |
| assert expected and installed["after"]["ready"] is True, installed | |
| assert sorted(payload["result"]["removed"]) == sorted(expected), payload | |
| PY | |
| - name: Exercise release contract smoke | |
| run: python examples/release-artifacts-smoke.py | |
| - name: Restore browser fixtures and bind verified package checksums | |
| run: | | |
| git restore -- examples/personal-workspace-browser/fixture.mjs examples/personal-workspace-browser/typed-actions.mjs examples/personal-workspace-browser/goal-work-map.mjs | |
| python scripts/release_artifacts.py verify-checksums --dist-dir dist/packages --checksum-file dist/SHA256SUMS | |
| python - <<'PY' | |
| import hashlib | |
| import json | |
| from pathlib import Path | |
| identity = Path("dist/RELEASE-SOURCES.json") | |
| record = json.loads(identity.read_text()) | |
| record["sha256"] = { | |
| path.name: hashlib.sha256(path.read_bytes()).hexdigest() | |
| for path in [*sorted(Path("dist/packages").iterdir()), Path("dist/SHA256SUMS")] | |
| } | |
| identity.write_text(json.dumps(record, indent=2) + "\n") | |
| PY | |
| - name: Attest release packages and checksum manifest | |
| if: github.event_name != 'pull_request' | |
| uses: actions/attest@v4 | |
| with: | |
| subject-path: | | |
| dist/packages/* | |
| dist/SHA256SUMS | |
| dist/RELEASE-SOURCES.json | |
| - name: Upload validated release bundle | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: loopx-${{ steps.identity.outputs.release-tag }} | |
| path: dist | |
| if-no-files-found: error | |
| retention-days: 30 | |
| upload-release: | |
| if: github.event_name != 'pull_request' | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Check out release source | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ needs.build.outputs.release-tag }} | |
| - name: Download validated release bundle | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: loopx-${{ needs.build.outputs.release-tag }} | |
| path: dist | |
| - name: Verify checksums before upload | |
| run: >- | |
| python scripts/release_artifacts.py verify-checksums | |
| --dist-dir dist/packages | |
| --checksum-file dist/SHA256SUMS | |
| - name: Upload immutable GitHub Release assets | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_TAG: ${{ needs.build.outputs.release-tag }} | |
| run: | | |
| set -euo pipefail | |
| gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null | |
| gh release upload "${RELEASE_TAG}" \ | |
| dist/packages/* \ | |
| dist/SHA256SUMS \ | |
| dist/RELEASE-SOURCES.json \ | |
| --repo "${GITHUB_REPOSITORY}" | |
| publish-pypi: | |
| if: >- | |
| github.event_name != 'pull_request' && | |
| vars.PYPI_PUBLISH_ENABLED == 'true' | |
| needs: | |
| - build | |
| - upload-release | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| id-token: write | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/project/loopx/ | |
| steps: | |
| - name: Download validated release bundle | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: loopx-${{ needs.build.outputs.release-tag }} | |
| path: dist | |
| - name: Publish distributions with PyPI Trusted Publishing | |
| uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 | |
| with: | |
| packages-dir: dist/packages | |
| - name: Verify the published package from PyPI | |
| env: | |
| RELEASE_TAG: ${{ needs.build.outputs.release-tag }} | |
| run: | | |
| set -euo pipefail | |
| version="${RELEASE_TAG#v}" | |
| verify_target="${RUNNER_TEMP}/loopx-pypi-target-${version}" | |
| skills_dir="${RUNNER_TEMP}/loopx-pypi-skills-${version}" | |
| for attempt in {1..12}; do | |
| if python -m pip install \ | |
| --disable-pip-version-check \ | |
| --index-url https://pypi.org/simple \ | |
| --no-cache-dir \ | |
| --no-deps \ | |
| --target "${verify_target}" \ | |
| "loopx==${version}"; then | |
| if ( | |
| cd "${RUNNER_TEMP}" | |
| test "$(PYTHONPATH="${verify_target}" python -m loopx.cli --version)" = \ | |
| "loopx ${version}" | |
| PYTHONPATH="${verify_target}" python -m loopx.cli --format json \ | |
| workflow-skills --install --skills-dir "${skills_dir}" \ | |
| --host-surface deepseek-harness-native \ | |
| > "${RUNNER_TEMP}/loopx-pypi-workflow-skills.json" | |
| ); then | |
| python - "${RUNNER_TEMP}/loopx-pypi-workflow-skills.json" <<'PY' | |
| import json | |
| from pathlib import Path | |
| import sys | |
| payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) | |
| assert payload["ok"] is True, payload | |
| assert payload["operation"] == "install", payload | |
| assert payload["host_surface"] == "deepseek-harness-native", payload | |
| assert payload["source"]["kind"] == "python_distribution", payload | |
| assert payload["after"]["ready"] is True, payload | |
| PY | |
| exit 0 | |
| fi | |
| fi | |
| if [[ "${attempt}" -eq 12 ]]; then | |
| break | |
| fi | |
| echo "PyPI has not exposed loopx ${version} yet; retrying in 10 seconds" | |
| sleep 10 | |
| done | |
| echo "loopx ${version} is still not installable from PyPI" >&2 | |
| exit 1 |