Skip to main content

sayfirst contract

This distribution publishes generation 1 of the transport-neutral domain contract and its generated HTTP-over-Unix-socket binding. It has no runtime dependencies. The scriptable fake is available only through the stub extra.

Scenarios

Scenario Binds Article Expected
allow both 1 grant=present, outcome=allow, reason=policy_allows
deny both 1 outcome=deny, reason=policy_denies
grant_expired_by_lifetime client 10 grant=present, grant_use=expired, outcome=allow, reason=policy_allows
grant_hit_within_lifetime client 10 grant=present, grant_use=hit, outcome=allow, reason=policy_allows
grant_miss_after_policy_version_change both 10 after_policy_change={'grant': 'absent', 'outcome': 'deny', 'reason': 'policy_denies'}, grant=present, outcome=allow, reason=policy_allows
grant_void_on_arguments_change client 3 grant=present, grant_use=arguments_changed, outcome=allow, reason=policy_allows
grant_void_on_connection_loss client 10 grant=present, grant_use=connection_lost, outcome=allow, reason=policy_allows
missing_policy both 1 outcome=deny, reason=policy_absent
no_grant_on_deny both 10 grant=absent, outcome=deny, reason=policy_denies
no_grant_without_signal_channel both 10 grant=absent, outcome=allow, reason=policy_allows
policy_unavailable_is_could_not_ask both 1 grant=absent, problem=policy_unavailable, result=could_not_ask
review_approve both 12 outcome=suspend, reason=policy_requires_review, state=approved, after_resolution.outcome=allow, after_resolution.reason=approval_granted
review_expire both 12 outcome=suspend, reason=policy_requires_review, state=expired
review_reject both 12 outcome=suspend, reason=policy_requires_review, state=rejected, after_resolution.outcome=deny, after_resolution.reason=approval_rejected
strictest_rule_wins server 1 grant=absent, outcome=deny, reason=policy_denies
unknown_outcome client 13 problem=outcome_unknown, reported_outcome=unknown, result=could_not_ask
unreachable client 1 problem=unreachable, result=could_not_ask

Server conformance replay

Run each server-bound scenario — every row of the table above whose binds is both or server — against a daemon instance already arranged for that scenario:

sayfirst-conformance replay \
  --socket allow=/run/conformance/allow.sock \
  --socket deny=/run/conformance/deny.sock \
  --socket grant_miss_after_policy_version_change=/run/conformance/grant_miss_after_policy_version_change.sock \
  --socket missing_policy=/run/conformance/missing_policy.sock \
  --socket no_grant_on_deny=/run/conformance/no_grant_on_deny.sock \
  --socket no_grant_without_signal_channel=/run/conformance/no_grant_without_signal_channel.sock \
  --socket policy_unavailable_is_could_not_ask=/run/conformance/policy_unavailable_is_could_not_ask.sock \
  --socket review_approve=/run/conformance/review_approve.sock \
  --socket review_expire=/run/conformance/review_expire.sock \
  --socket review_reject=/run/conformance/review_reject.sock \
  --socket strictest_rule_wins=/run/conformance/strictest_rule_wins.sock

Name every server-bound scenario once: with --socket SCENARIO=PATH, or, for one the deployment cannot arrange a daemon for, with --expected-absent SCENARIO=REASON. A scenario named by neither has no daemon to replay against, and it fails. Client-only scenarios are reported as not applicable because their binds value does not include server. Every result line contains the scenario name, proven, failed, or not-applicable, and a reason, and the last line is the whole run's verdict: proven when every server-bound scenario is proven, failed when any one failed, and unknown when none failed and not every one was replayed — an expected absence, or a platform the client cannot verify a peer on — even when another scenario was proven. The exit status is that verdict's: 0 proven, 1 failed, 3 unknown. An invalid invocation replays nothing and exits 2.

Three arrangements are the deployment's to make, and the command reaches the one it can. The daemon for grant_miss_after_policy_version_change is asked the same question twice and must answer the second under a policy that denies it: name the deployment's own command with --change-policy-command CMD. The replayer runs it once, between the two asks, split without a shell, with SAYFIRST_CONFORMANCE_SCENARIO and SAYFIRST_CONFORMANCE_POLICY (the scripted policy as JSON, {"example.effect": "deny"}) in its environment; it writes the policy the daemon must answer under and exits 0 once the daemon has read it — for this daemon, as soon as the new file is completely written, because it reads its policy afresh for every decision. A non-zero exit, a command that cannot be run, or one that outlasts --change-policy-timeout (default 30 seconds) fails that scenario and says which. A socket for that scenario with no command is an invalid invocation (2): the run could only fail.

Such a command can be a few lines of sh. This one, installed as /usr/local/bin/conformance-deny-example-effect, writes a policy denying example.effect to the policy file that daemon's --config names, and returns once the new policy is in place — this daemon reads the policy file as it is when a question arrives, so the command need only return once the new file is completely written (another daemon may need the command to wait for its own reload):

#!/bin/sh
# SAYFIRST_CONFORMANCE_POLICY is {"example.effect": "deny"} for this scenario.
policy=/etc/sayfirst/conformance/grant-miss/policy.toml
cat > "$policy.tmp" <<'EOF'
format = 1
[revision]
reason = "conformance: example.effect denied"
[[rule]]
id = "changed"
capability = "example.effect"
scope = "local"
principals = ["user:conformance"]
outcome = "deny"
reason = "changed"
EOF
mv "$policy.tmp" "$policy"

The path and the principal are this example's, not a convention: write to the policy path your daemon's configuration names, for the account the replay asks as.

The daemon for policy_unavailable_is_could_not_ask must be unable to read its policy when it is asked; since a daemon refuses to start on a policy it cannot read, the file is removed after the start. The daemons for the three that end a wait serve both approval operations — the one for review_expire suspending with a wait shorter than the replay's --deadline-wait-seconds, and rendering it expired as of the instant a read is taken, not only after a sweep it schedules itself.

A replay leaves its daemons as it found them only where the scenario says so: a wait it rejected stays rejected until its deadline. Replay against freshly started daemons; a second run against the same ones is a different run.

The request a scenario scripts is the request the replayer sends: no_grant_without_signal_channel asks for the answer that carries no event stream.

A complete run adds the policy-change command to the sockets above:

sayfirst-conformance replay \
  --socket allow=/run/conformance/allow.sock \
  --socket deny=/run/conformance/deny.sock \
  --socket grant_miss_after_policy_version_change=/run/conformance/grant_miss_after_policy_version_change.sock \
  --socket missing_policy=/run/conformance/missing_policy.sock \
  --socket no_grant_on_deny=/run/conformance/no_grant_on_deny.sock \
  --socket no_grant_without_signal_channel=/run/conformance/no_grant_without_signal_channel.sock \
  --socket policy_unavailable_is_could_not_ask=/run/conformance/policy_unavailable_is_could_not_ask.sock \
  --socket review_approve=/run/conformance/review_approve.sock \
  --socket review_expire=/run/conformance/review_expire.sock \
  --socket review_reject=/run/conformance/review_reject.sock \
  --socket strictest_rule_wins=/run/conformance/strictest_rule_wins.sock \
  --change-policy-command /usr/local/bin/conformance-deny-example-effect \
  --deadline-wait-seconds 5

A run that names every server-bound scenario and the policy-change command is complete, and proves a conforming daemon with exit 0.

sayfirstd conformance replay is the same replay under the operator surface: the same options, the same whole-run line and the same exit statuses. Its scenario lines carry the name, the verdict and the reason; those of sayfirst-conformance add whether the scenario binds the server and how many expected members its verdict rests on.

The default expects each per-user daemon to run as the invoking user. System daemon tests name its numeric account with --expected-uid. The client checks that identity before sending each request. review_expire waits 61 seconds by default; a test harness can inject its clock through SocketHarness instead.

The repository acceptance suite uses the same mapping convention under SAYFIRST_CONFORMANCE_SOCKET_DIR: it looks for <scenario>.sock there and uses SAYFIRST_CONFORMANCE_EXPECTED_UID when the daemon does not run as the test user. When the socket directory is not configured, each case is an expected absence whose reason states that observed configuration fact. When it is configured, the same inventory runs live and carries no absence claim.

Distribution boundary

The reusable replayer and its HTTP-over-Unix-socket client remain in this contract distribution. The client verifies the server's peer credential through the same adapters as the transport client, transport.peer, and maps failures to impostor, peer_credential_unavailable, unreachable, and answer_unreadable. A platform no adapter covers is not applicable to a replay, and on the path a boundary asks through, hold_decision, it is the could-not-ask peer_identity_unsupported. The socket client is published from binding.http_unix_socket.client; the replay harness is in the sibling replay module.

This repository publishes the sayfirst-conformance distribution, the sayfirst_conformance import package and the sayfirst-conformance script; it also publishes the operator surface that inspects the daemon, from packages/cli, as the distribution sayfirstd, the import package sayfirstd and the console script sayfirstd. Each of those names is claimed once, by one distribution, and tests/test_client_distribution_names.py holds that against the [project] tables. The surface re-exports this operation as sayfirstd conformance replay while depending on this contract and on no server distribution.

The distribution sayfirst-cli, the import package sayfirst_cli and the console script sayfirst belong to the product command-line interface and are not published from this repository; the operator settled that on 2026-09-05.

This distribution installs no console script of its own, so nothing here is bound to the name of a surface that forwards to it. The operator surface sayfirstd is the one that reaches this operation today; the product command-line interface may reach it through these same modules, and no release of it does so yet.

Metadata

Release files for sayfirst-contract 0.3.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sayfirst-contract 0.3.3
File Size Uploaded
sayfirst_contract-0.3.3.tar.gz 187.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sayfirst-contract 0.3.3
File Interpreter ABI Platform
sayfirst_contract-0.3.3-py3-none-any.whl Python 3 none any Details

Total release size: 334.6 kB

Release files / sayfirst_contract-0.3.3.tar.gz

Download URL sayfirst_contract-0.3.3.tar.gz
Size 187.0 kB
Tags Source
SHA-256 checksum
How to use checksums
b6a7ef3eaf3ca495dfdf6c96d2f8ffb69de83e3e4abf969279ce6be7e24bccbe
BLAKE2b-256 checksum
How to use checksums
ed3603fdd55841e5cda39677cc93ba0938c6653b957428b1e4a2f9bffff0cb5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / sayfirst_contract-0.3.3-py3-none-any.whl

Download URL sayfirst_contract-0.3.3-py3-none-any.whl
Size 147.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5fcfc1fbd2457347cfa3793c9a8abe77ff211877c29e570cbc6756b4e4ed10dc
BLAKE2b-256 checksum
How to use checksums
e6392a5ca0b7e4cf6b4f7b2ea7d885a2d1aca173fd957ca98c3faa738c952cc2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.3 This release

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page