diff --git a/CHANGELOG.md b/CHANGELOG.md index d8901bf..8603bcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/QUICKSTART.md b/QUICKSTART.md index 13f06cb..1ed6ffd 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -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; @@ -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 @@ -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 diff --git a/README.md b/README.md index 39c5cbc..5b74e60 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 @@ -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. diff --git a/docs/PACKS.md b/docs/PACKS.md index b0003c4..93cb83f 100644 --- a/docs/PACKS.md +++ b/docs/PACKS.md @@ -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. diff --git a/pyproject.toml b/pyproject.toml index c741b1a..7d0a2d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" @@ -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 @@ -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] diff --git a/src/sayfirst_cli/instrument/launch.py b/src/sayfirst_cli/instrument/launch.py index 1aa36a8..800f14f 100644 --- a/src/sayfirst_cli/instrument/launch.py +++ b/src/sayfirst_cli/instrument/launch.py @@ -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. """