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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ declares; a release with no section fails the gate.

## Unreleased

## 0.3.2

- The contract and the boundary move to 0.3.2, which carries security fixes.
Under `sayfirst instrument run`, an act a person approved now runs once;
asked again, it waits under a new approval.
- The pages say that an allow produced by an approval carries no grant.

## 0.3.1

- **`instrument verify` says how a run ended when the program raised, too.** The harness said
Expand Down
25 changes: 13 additions & 12 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ $ 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.1 with the first command from wheels built from the two release trees,
given `--no-index` (the walk ran before 0.3.1 reached the index; « From
0.3.2 with the first command from wheels built from the two release trees,
given `--no-index` (the walk ran before 0.3.2 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;
Expand All @@ -29,11 +29,12 @@ hashes will differ; the shapes will not.
and macOS name it that; inside an activated virtual environment `python`
works too), and [`uv`](https://docs.astral.sh/uv/). The walk used
uv 0.12.
- A home directory that only you can write. The daemon refuses a directory a
group can write and that is not sticky: below the socket as
`socket_directory_unprotected`, and above the policy as
`policy_unavailable_at_start … exposed: group_write`; either way it says so
instead of starting.
- A home directory that only you can write. The daemon refuses to start if
the socket's directory, or any directory on the way to it, can be written
by a group or by others and is not sticky (`socket_directory_unprotected`),
and likewise for the policy's directory and those above it
(`policy_unavailable_at_start … exposed: group_write`); either way it says
so instead of starting.

## 1. Install

Expand All @@ -42,11 +43,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.1
+ sayfirst-cli==0.3.1
+ sayfirst-contract==0.3.1
+ sayfirst-control-plane==0.3.1
+ sayfirstd==0.3.1
+ 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
Installed 1 executable from `sayfirst-control-plane`: sayfirst-daemon
Installed 1 executable from `sayfirstd`: sayfirstd
Installed 1 executable: sayfirst
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ anything that matters: *may I do this, with these arguments, as this account?*
person can read, over a Unix socket that tells it who is asking from the
kernel's own credentials. No URL, no token, nothing to leak (article 6).
- **The program never decides.** It asks, and it does only what was allowed:
the boundary (`sayfirst-boundary`) holds the grant for exactly one execution.
the boundary (`sayfirst-boundary`) holds the grant a policy allow serves, for the run;
an allow that a person's approval produced carries no grant — it runs the act
once, and the same act asked again waits under a new approval.
- **A person is in the loop by construction, not by dashboard.** A suspension
is a wait. `sayfirst approvals approve` ends it once, the deadline comes from
the policy, and a rejection stands until that deadline, or until the daemon
Expand Down Expand Up @@ -93,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.1:
Everything the three commands above need is on the Python index at 0.3.2:
`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 @@ -226,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.1 ./scripts/gate.sh
$ SAYFIRST_CONTRACT_SOURCE=../sayfirst-control-plane SAYFIRST_CONTRACT_REF=v0.3.2 ./scripts/gate.sh
```

The workflow does the same and carries no credential of any kind: a fork can
Expand All @@ -253,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.1 is the current one.
**0.2.0 is the first public release**, 2026-09-17. 0.3.2 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
4 changes: 3 additions & 1 deletion docs/PACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,9 @@ is stepped over when any of them does not.
are not the same order. **Which is why a verifying run holds no grant.**
Under `instrument run` the boundary keeps what it was granted (article 10),
and an identical effect repeated while that grant lives is answered by it
with nothing asked and nothing recorded. Under `verify` the boundary in front
with nothing asked and nothing recorded. An allow that a person's approval
produced carries no grant: it runs the act once, and the same act asked again
waits under a new approval. Under `verify` the boundary in front
of the program asks for every effect, so every effect has a record of its
own: a program that spawns the same command twice is two decisions there,
and one there would read the second spawn as ungoverned.
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.1"
version = "0.3.2"
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.1", "sayfirst-boundary==0.3.1"]
dependencies = ["sayfirst-contract==0.3.2", "sayfirst-boundary==0.3.2"]

# 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.1",
"sayfirst-contract[stub]==0.3.2",
]

[build-system]
Expand Down
2 changes: 1 addition & 1 deletion src/sayfirst_cli/instrument/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def run(

`hold_grants=False` asks for every effect and holds no grant. The verifier
runs this way: its proof is one recorded decision for each effect it saw,
and a grant hit — an identical effect answered by an earlier allow, which is
and a grant hit — an identical effect answered by an earlier allow the policy gave, which is
what `run` does (article 10) — records nothing, so a repeated effect would
read as ungoverned.
"""
Expand Down
Loading