Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 35 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,39 @@ jobs:
--timeout 240
--json

release_rehearsal:
name: release wheel rehearsal
runs-on: ubuntu-latest
env:
SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- name: Check out the exact rehearsal source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ env.SOURCE_SHA }}
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: "3.12"
- name: Build a pre-merge rehearsal pair
# No --release-pr: these bytes are explicitly ineligible for publication.
run: |
python -m pip install build twine
python scripts/release_candidate.py build --source "$GITHUB_WORKSPACE" \
--source-sha "$SOURCE_SHA" --dist "$RUNNER_TEMP/rehearsal-dist"
- name: Rehearse the exact installed wheel offline
run: |
python scripts/rehearse_v150.py --dist "$RUNNER_TEMP/rehearsal-dist" \
--source-sha "$SOURCE_SHA" --work-dir "$RUNNER_TEMP/rehearsal"
- name: Upload sanitized pre-merge evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: release-rehearsal-evidence
path: |
${{ runner.temp }}/rehearsal-dist/candidate.json
${{ runner.temp }}/rehearsal/rehearsal.json
if-no-files-found: error

graph_containment:
# The local-graph boundary, against the real kernel mechanism rather than a
# stand-in for one. The unit suite skips these when a host offers no
Expand Down Expand Up @@ -212,10 +245,11 @@ jobs:
package:
name: package
runs-on: ubuntu-latest
needs: [package_matrix, board_qualification]
needs: [package_matrix, board_qualification, release_rehearsal]
if: always()
steps:
- name: Check matrix result
run: |
test "${{ needs.package_matrix.result }}" = "success"
test "${{ needs.board_qualification.result }}" = "success"
test "${{ needs.release_rehearsal.result }}" = "success"
76 changes: 76 additions & 0 deletions .github/workflows/release-candidate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Code Mower Immutable Candidate

on:
workflow_dispatch:
inputs:
expected_sha:
description: Exact merge SHA of the release preparation PR; never a branch or tag.
required: true
type: string
release_pr:
description: Merged release preparation PR number.
required: true
type: string

permissions:
contents: read
pull-requests: read

concurrency:
group: release-candidate-${{ inputs.expected_sha }}
cancel-in-progress: false

jobs:
candidate:
runs-on: ubuntu-latest
steps:
- name: Validate candidate selection
env:
SOURCE_SHA: ${{ inputs.expected_sha }}
RELEASE_PR: ${{ inputs.release_pr }}
run: |
set -euo pipefail
[[ "$GITHUB_REF" == refs/heads/main ]] || exit 1
[[ "$SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]] || exit 1
[[ "$RELEASE_PR" =~ ^[1-9][0-9]*$ ]] || exit 1
[[ "$GITHUB_SHA" == "$SOURCE_SHA" ]] || exit 1
[[ "$GITHUB_RUN_ATTEMPT" == 1 ]] || exit 1

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ inputs.expected_sha }}
fetch-depth: 0

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: "3.12"

- name: Install build tools
run: python -m pip install build twine

- name: Build the immutable artifact pair once
env:
GH_TOKEN: ${{ github.token }}
SOURCE_SHA: ${{ inputs.expected_sha }}
RELEASE_PR: ${{ inputs.release_pr }}
run: |
set -euo pipefail
git merge-base --is-ancestor "$SOURCE_SHA" origin/main
python scripts/release_candidate.py build --source "$GITHUB_WORKSPACE" \
--dist "$RUNNER_TEMP/candidate" --source-sha "$SOURCE_SHA" --release-pr "$RELEASE_PR"

- name: Rehearse the exact wheel in disposable environments
env:
SOURCE_SHA: ${{ inputs.expected_sha }}
run: |
python scripts/rehearse_v150.py --dist "$RUNNER_TEMP/candidate" \
--source-sha "$SOURCE_SHA" --work-dir "$RUNNER_TEMP/rehearsal"
cp "$RUNNER_TEMP/rehearsal/rehearsal.json" "$RUNNER_TEMP/candidate/rehearsal.json"

- name: Retain candidate and sanitized evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: code-mower-candidate
path: ${{ runner.temp }}/candidate/*
if-no-files-found: error
retention-days: 90
47 changes: 43 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ on:
description: Exact 40-character release commit this dispatch must build.
required: true
type: string
candidate_run_id:
description: Successful immutable-candidate workflow run to publish without rebuilding.
required: false
type: string

permissions:
contents: read
Expand Down Expand Up @@ -72,6 +76,10 @@ jobs:
build-distributions:
needs: release-identity
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
pull-requests: read
steps:
- name: Check out the validated release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
Expand All @@ -83,11 +91,42 @@ jobs:
with:
python-version: "3.12"

- name: Build distributions
- name: Retrieve and verify the qualified candidate without rebuilding
env:
GH_TOKEN: ${{ github.token }}
CANDIDATE_RUN_ID: ${{ inputs.candidate_run_id || vars.CODE_MOWER_CANDIDATE_RUN_ID }}
SOURCE_SHA: ${{ needs.release-identity.outputs.resolved-sha }}
run: |
python -m pip install --upgrade pip
python -m pip install build
python -m build
set -euo pipefail
[[ "$CANDIDATE_RUN_ID" =~ ^[1-9][0-9]*$ ]]
gh api "repos/$GITHUB_REPOSITORY/actions/runs/$CANDIDATE_RUN_ID" > "$RUNNER_TEMP/candidate-run.json"
python - "$RUNNER_TEMP/candidate-run.json" <<'PY'
import json, os, sys
from pathlib import Path
run = json.loads(Path(sys.argv[1]).read_text())
assert run['path'] == '.github/workflows/release-candidate.yml'
assert run['event'] == 'workflow_dispatch' and run['head_branch'] == 'main'
assert run['status'] == 'completed' and run['conclusion'] == 'success'
assert run['repository']['full_name'] == 'codemower-ai/code-mower'
assert run['head_sha'] == os.environ['SOURCE_SHA']
assert run['run_attempt'] == 1
PY
gh run download "$CANDIDATE_RUN_ID" --repo "$GITHUB_REPOSITORY" \
--name code-mower-candidate --dir candidate
python scripts/release_candidate.py verify --dist candidate \
--source-sha "$SOURCE_SHA" --require-candidate
python - <<'PY'
import json, os, subprocess
from pathlib import Path
from scripts.release_candidate import verify_rehearsal
candidate = json.loads(Path('candidate/candidate.json').read_text())
pr = json.loads(subprocess.check_output(['gh', 'pr', 'view', str(candidate['release_pr']),
'--repo', os.environ['GITHUB_REPOSITORY'], '--json', 'state,mergeCommit']))
assert pr['state'] == 'MERGED' and pr['mergeCommit']['oid'] == os.environ['SOURCE_SHA']
verify_rehearsal(Path('candidate'), candidate)
PY
mkdir dist
cp candidate/*.whl candidate/*.tar.gz dist/

- name: Upload distributions
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
Expand Down
31 changes: 19 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,29 @@ later entries are regular releases.

## Unreleased

Accepted on `main` and not in any published package. The published `v1.4.2`
package on the package index contains the originally shipped optional Graphify
integration; the entries below are intended for the next appropriate release.
The lineage-contract and Coworker-citation entries that were previously listed
here shipped in the published `v1.4.1` artifact and are recorded under that
release below.
No additional changes recorded.

## 1.5.0 — release

Supervised private-workspace Slack preparation and supervisor v2, Graphify
compatibility and query-reader parity. See [release notes](docs/v150-release-notes.md)
and the [qualification record](docs/v150-qualification.md).

### Added

- Explicit `slack setup` hosted manifest and redacted `slack doctor` (#1024).
Default install remains Slack-free. Offline snapshots never prove live readiness.
Basic private-workspace interaction only; telemetry/Board links and rich UX
remain v1.5.1. Audit publication binds the current head reliably (#1025).

- Supervisor v2 owns checkpointed `clarify` answers and explicitly authorized
`fix` requests under the original claim, provider binding, live lease and
cumulative ACU cap (#1017). Private input resolution, fsynced pending intents,
exact-head independent review, bounded fix/review allowances and saved
duplicate outcomes prevent implicit recovery or ambiguous message replay.
The packaged v1 schema, fixtures and five-operation enum remain frozen.
This source contract is intended for v1.5; private bridge development may use
an exact reviewed source pin, but live canaries and deployment require the
final v1.5 package. No hosted canary or provider qualification is claimed.
Live canaries and deployment require the immutable v1.5.0 package and
separate authorization. No hosted canary or provider qualification is claimed.

- Release campaigns can authenticate their isolated Codex home on a headless
Linux host that has no OS keyring. `CODE_MOWER_CODEX_CAMPAIGN_AUTH_MODE=file`
Expand Down Expand Up @@ -70,8 +75,10 @@ release below.
either TestPyPI or PyPI can run. The same release-readiness check runs in CI
before tagging and rejects unfinished publication promises. Release jobs
resolve the selected tag to its commit, validate that checkout, and pass the
exact SHA to the distribution build. Manual dispatch additionally requires
that tag commit to match the supplied expected SHA (#1014).
exact SHA to candidate verification. Manual dispatch additionally requires
that tag commit to match the supplied expected SHA (#1014). The immutable
merge-SHA wheel/sdist pair is built and qualified before tagging, then reused
without rebuilding for publication (#1027).

- A doctor snapshot taken while a Board is still binding its port no longer
reports that no Board is running moments before `board list` lists it. Board
Expand Down Expand Up @@ -131,7 +138,7 @@ release below.
ambiguous relationship, stays `available` and usable, discloses
`provider_has_more`, `unresolved_entities` or `document_limit`, and does not
mark the generation incomplete. An answer whose only omission is
`unresolved_entities` is `partial` too. Intended for `v1.5.0` together with #1007.
`unresolved_entities` is `partial` too. Included in `v1.5.0` together with #1007.

## 1.4.2 — published

Expand Down
Loading
Loading