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
27 changes: 17 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ name: release
# that starts a publication is the operator's tag and nothing else: there is no
# `branches:` under `push` and no `pull_request:` here, and
# `tests/test_release_workflow_publishes_only_on_a_tag.py` fails if either
# arrives. A tag naming a version this distribution does not carry is refused
# by name in the build and publishes nothing.
# arrives. A tag naming a version the two distributions built here do not
# carry is refused by name in the build and publishes nothing.
#
# The dry run exists so the mechanics are proven BEFORE the marks rather than
# discovered after them: started by hand with publishing switched off, it runs
# the gate, builds the distribution and checks the artefact, and stops.
# the gate, builds the distributions and checks the artefacts, and stops.
#
# WHERE THIS WORKFLOW READS THE CONTRACT, AND WHY IT IS NOT WHERE `ci.yml`
# READS IT. The gate builds `sayfirst-contract` from a checkout of the control
Expand Down Expand Up @@ -72,7 +72,7 @@ permissions:

jobs:
build:
name: build and check the distribution
name: build and check the distributions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand All @@ -95,12 +95,12 @@ jobs:
echo "::error::a dispatch publishes nothing: re-run it with publish false to dry-run, and publish by tagging"
exit 1

- name: The tag names the version this distribution carries
- name: The tag names the version the distributions carry
if: github.event_name == 'push'
working-directory: sayfirst-cli
run: uv run --no-project --python 3.12 python scripts/release_version.py --expect "${GITHUB_REF_NAME#v}"

- name: The distribution carries a version
- name: The distributions carry one version
if: github.event_name == 'workflow_dispatch'
working-directory: sayfirst-cli
run: uv run --no-project --python 3.12 python scripts/release_version.py
Expand Down Expand Up @@ -160,8 +160,8 @@ jobs:
0)
echo "### gate: full green" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Format, lint, every test and the dependency-closure guard of articles 13" >> "$GITHUB_STEP_SUMMARY"
echo "and 14 ran against the contract this release pins." >> "$GITHUB_STEP_SUMMARY"
echo "Format, lint, every test, the dependency-closure guard of articles 13 and 14" >> "$GITHUB_STEP_SUMMARY"
echo "and the umbrella's install check ran against the contract this release pins." >> "$GITHUB_STEP_SUMMARY"
;;
75)
echo "::error::the gate came back reduced: a release is never cut from a reduced gate"
Expand All @@ -172,9 +172,16 @@ jobs:
;;
esac

- name: Build the distribution
# Two projects, one `dist/`: the client, and the umbrella that installs
# the product whole. The publish job uploads what is left here, so a
# project this step does not build is a distribution a tag does not
# publish; `tests/test_release_builds_every_distribution.py` holds the
# two lines against the projects the version reader reads.
- name: Build the distributions
working-directory: sayfirst-cli
run: uv build --out-dir dist
run: |
uv build --out-dir dist
uv build umbrella --out-dir dist
# The checker is pinned, as the control plane's is and to the same version:
# an unpinned checker is a check whose rules can change between two
# releases without either release changing.
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ declares; a release with no section fails the gate.

## Unreleased

## 0.3.3

- A second distribution is built from this repository: `sayfirst`, the product
installed whole. It installs no code of its own. It depends on `sayfirst-cli`,
`sayfirst-control-plane` and `sayfirstd` at its own version and names their
three commands, so one install — `uv tool install sayfirst` — yields
`sayfirst`, `sayfirst-daemon` and `sayfirstd`. `sayfirst-cli` is still the
client alone: installing it still brings the contract and the boundary and
never the daemon.
- The contract and the boundary move to 0.3.3, released with this client.
`sayfirst-cli` itself changes in its version and its pins only.

## 0.3.2

- The contract and the boundary move to 0.3.2, which carries security fixes.
Expand Down
26 changes: 15 additions & 11 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ $ sayfirst instrument run --pack subprocess --scope local -- python3 my_agent.py
```

Everything on this page was run, in this order, before it was written down: the
commands are pasted from that run, and so are their answers. The run installed
0.3.2 with the first command from the Python index, on a PATH with `python3`
and no `python`, in a shell with neither repository on any path, under a home
directory made for it; its paths are written here the way they read under an
ordinary account (`~`, and `/run/user/1000` for the runtime directory). Your
identifiers, timestamps and hashes will differ; the shapes will not.
commands are pasted from a run, and so are their answers. The walk recorded for
this release installed 0.3.3 with the first command from wheels built from the
two release trees, given `--no-index` (it ran before 0.3.3 reached the index;
« From checkouts instead » below is that install), on a PATH with `python3` and
no `python`, in a shell with neither repository on any path, under a home
directory made for it. It answered in the same shapes as the walk whose answers
are printed here, which is an earlier release's: only the five versions of
section 1 were moved. Paths are written the way they read under an ordinary
account (`~`, and `/run/user/1000` for the runtime directory). Your
identifiers, timestamps, sizes and hashes will differ; the shapes will not.

## What you need

Expand All @@ -40,11 +44,11 @@ $ uv tool install sayfirst-cli --with-executables-from sayfirst-control-plane --
Resolved 5 packages in 3ms
Prepared 5 packages in 6ms
Installed 5 packages in 1ms
+ sayfirst-boundary==0.3.2
+ sayfirst-cli==0.3.2
+ sayfirst-contract==0.3.2
+ sayfirst-control-plane==0.3.2
+ sayfirstd==0.3.2
+ sayfirst-boundary==0.3.3
+ sayfirst-cli==0.3.3
+ sayfirst-contract==0.3.3
+ sayfirst-control-plane==0.3.3
+ sayfirstd==0.3.3
Installed 1 executable from `sayfirst-control-plane`: sayfirst-daemon
Installed 1 executable from `sayfirstd`: sayfirstd
Installed 1 executable: sayfirst
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ command on that page was run, in that order, before it was written down.

### What the index holds

Everything the three commands above need is on the Python index at 0.3.2:
Everything the three commands above need is on the Python index at 0.3.3:
`sayfirst-cli`, the `sayfirst-contract` it speaks and the `sayfirst-boundary` a
governed program holds its grants in, and the control plane's
`sayfirst-control-plane` and `sayfirstd`. The first command installs all five
Expand Down Expand Up @@ -228,7 +228,7 @@ contract is built from a checkout of the control plane's repository, at the tag
this client pins:

```console
$ SAYFIRST_CONTRACT_SOURCE=../sayfirst-control-plane SAYFIRST_CONTRACT_REF=v0.3.2 ./scripts/gate.sh
$ SAYFIRST_CONTRACT_SOURCE=../sayfirst-control-plane SAYFIRST_CONTRACT_REF=v0.3.3 ./scripts/gate.sh
```

The workflow does the same and carries no credential of any kind: a fork can
Expand All @@ -255,7 +255,7 @@ GitHub's private reporting, never a public issue — [`LICENSE`](LICENSE),

## Status

**0.2.0 is the first public release**, 2026-09-17. 0.3.2 is the current one.
**0.2.0 is the first public release**, 2026-09-17. 0.3.3 is the current one.
What is *not* here is named too, because a surface a reader assumes is an
overclaim: `connect`, `profile`, `whoami`, `integrate` and `version` are in
[`docs/PARTITION.md`](docs/PARTITION.md) and none of them exists here.
Expand Down
19 changes: 16 additions & 3 deletions docs/publication-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,28 @@ the hosting platform.
over exactly what will be pushed, before the push, so that every claim the guards hold is
checked rather than asserted. Let the private address in `SECURITY.md` leave in the same change.
- [ ] **Change the display name before the tag if wanted.** The author recorded in this
repository's one project file is `fredaime <frederic.aime@gmail.com>`, which is the identity
every commit is signed off under. A different display name for the index is a change to that
file's `authors` table, before the tag.
repository's two project files is `fredaime <frederic.aime@gmail.com>`, which is the identity
every commit is signed off under. A different display name for the index is a change to the
`authors` table of both, in one commit, before the tag:
`tests/test_umbrella_metadata_agrees_with_the_client.py` holds them equal.
- [ ] **Require a reviewer on the `pypi` deployment environment, and create the trusted-publishing
relationship** for this repository's release workflow on the index itself. The release workflow
names the environment and asks for `id-token: write` instead of a stored token; whether the
environment demands an approval, and whether the index trusts this workflow's identity, are
settings of the repository and of the index that no file here can read, and none of them claims
to.
- [ ] **Create a pending publisher for every project the index does not know yet.** A release
that adds a project uploads its files one at a time and stops at the first file of a project
the index does not know: what was sent before it stays, which may be nothing or part of a
project, and a file the index already holds cannot be sent again. A project that has never
been uploaded is trusted by nothing until a pending publisher names it. For `sayfirst`, first
released in 0.3.3: on the index, under the account's publishing settings, add a pending
publisher with the project name `sayfirst`, this public repository, the workflow
`release.yml` and the environment `pypi`. The index refused a second pending publisher for
one repository, workflow and environment on 2026-09-17, so a release that adds two projects
the index does not know is two releases, or one upload by hand. No file here can read whether
it was done, and a release rehearsed with publishing switched off cannot either, since it
uploads nothing.
- [ ] **Set the sign-off check as a required status** in the public repository's branch protection.
Contributions arrive under the Developer Certificate of Origin (article 15), and whether a
failing check blocks a merge is a branch-protection setting of the public repository which no
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
[project]
name = "sayfirst-cli"
version = "0.3.2"
version = "0.3.3"
description = "The product command-line interface of the sayfirst control plane"
readme = "README.md"
requires-python = ">=3.12,<3.15"
Expand All @@ -20,7 +20,7 @@ classifiers = [
# promise made here — it is measured after an install. `sayfirst-boundary` joins
# it because a governed program holds a grant in it (article 10); it depends on
# the contract only, and it is not the server (article 14).
dependencies = ["sayfirst-contract==0.3.2", "sayfirst-boundary==0.3.2"]
dependencies = ["sayfirst-contract==0.3.3", "sayfirst-boundary==0.3.3"]

# The public repository this distribution sends a reader to, created fresh in
# the act that published it (2026-09-17) under the names the operator chose for
Expand All @@ -41,7 +41,7 @@ sayfirst = "sayfirst_cli.main:run"
dev = [
"pytest==8.4.1",
"ruff==0.12.12",
"sayfirst-contract[stub]==0.3.2",
"sayfirst-contract[stub]==0.3.3",
]

[build-system]
Expand Down
Loading
Loading