sayfirst-cli 0.2.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/.github/workflows/ci.yml +132 -33
- sayfirst_cli-0.3.1/CHANGELOG.md +184 -0
- sayfirst_cli-0.3.1/PKG-INFO +286 -0
- sayfirst_cli-0.3.1/QUICKSTART.md +547 -0
- sayfirst_cli-0.3.1/README.md +265 -0
- sayfirst_cli-0.3.1/docs/PACKS.md +412 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/docs/PARTITION.md +10 -9
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/pyproject.toml +7 -7
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/scripts/gate.sh +19 -13
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/approvals.py +16 -9
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/ask.py +18 -9
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/evidence.py +80 -14
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/exit_codes.py +4 -4
- sayfirst_cli-0.3.1/src/sayfirst_cli/instrument/_bootstrap/sitecustomize.py +106 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/instrument/commands.py +155 -10
- sayfirst_cli-0.3.1/src/sayfirst_cli/instrument/designation.py +111 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/instrument/engine.py +27 -13
- sayfirst_cli-0.3.1/src/sayfirst_cli/instrument/follow.py +156 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/instrument/harness.py +824 -89
- sayfirst_cli-0.3.1/src/sayfirst_cli/instrument/interpreter.py +491 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/instrument/launch.py +173 -10
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/instrument/verify.py +110 -29
- sayfirst_cli-0.3.1/src/sayfirst_cli/packs/__init__.py +11 -0
- sayfirst_cli-0.3.1/src/sayfirst_cli/packs/http-client/interpose.py +233 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/packs/subprocess/interpose.py +12 -5
- sayfirst_cli-0.3.1/src/sayfirst_cli/packs/subprocess/pack.toml +39 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/packs_cmd.py +30 -38
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/pages.py +7 -1
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/reads.py +63 -7
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/render.py +5 -1
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/trace.py +10 -6
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/canned_daemon.py +15 -1
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/conftest.py +25 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/contract_absence.py +70 -14
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/documents.py +77 -12
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_approvals.py +32 -2
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_contract_absence.py +116 -2
- sayfirst_cli-0.3.1/tests/test_contract_words.py +40 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_decided_name.py +24 -1
- sayfirst_cli-0.3.1/tests/test_default_socket.py +211 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_engine.py +57 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_evidence_export_and_exports.py +34 -5
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_evidence_history_and_audit.py +96 -9
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_evidence_reads.py +3 -2
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_exit_codes.py +1 -1
- sayfirst_cli-0.3.1/tests/test_follow_children.py +404 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_gate_modes.py +133 -28
- sayfirst_cli-0.3.1/tests/test_http_client_pack.py +563 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_instrument_run.py +299 -4
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_instrument_verify.py +438 -13
- sayfirst_cli-0.3.1/tests/test_interpreter_target.py +652 -0
- sayfirst_cli-0.3.1/tests/test_pack_designation.py +144 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_packs_cmd.py +27 -2
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_packs_shipped.py +1 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_quickstart_names_what_exists.py +24 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_reads.py +4 -3
- sayfirst_cli-0.3.1/tests/test_review_bounds.py +111 -0
- sayfirst_cli-0.3.1/tests/test_verification_claims_what_it_established.py +541 -0
- sayfirst_cli-0.3.1/tests/test_verify_asks_every_effect.py +82 -0
- sayfirst_cli-0.3.1/tests/test_verify_pairs_an_inner_spawn.py +241 -0
- sayfirst_cli-0.2.0/CHANGELOG.md +0 -44
- sayfirst_cli-0.2.0/PKG-INFO +0 -297
- sayfirst_cli-0.2.0/QUICKSTART.md +0 -345
- sayfirst_cli-0.2.0/README.md +0 -276
- sayfirst_cli-0.2.0/docs/PACKS.md +0 -187
- sayfirst_cli-0.2.0/src/sayfirst_cli/packs/__init__.py +0 -10
- sayfirst_cli-0.2.0/src/sayfirst_cli/packs/http-client/interpose.py +0 -72
- sayfirst_cli-0.2.0/src/sayfirst_cli/packs/subprocess/pack.toml +0 -13
- sayfirst_cli-0.2.0/tests/test_http_client_pack.py +0 -294
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/.github/workflows/release.yml +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/.gitignore +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/LICENSE +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/NOTICE +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/SECURITY.md +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/TRADEMARKS.md +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/docs/EVIDENCE-SURFACE.md +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/docs/PROVENANCE.md +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/docs/publication-checklist.md +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/scripts/check_dependency_closure.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/scripts/check_developer_certificate_of_origin.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/scripts/release_version.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/__init__.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/explain.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/instrument/__init__.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/instrument/manifest.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/main.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/packs/database/NOTE.md +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/packs/database/interpose.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/packs/database/pack.toml +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/packs/http-client/NOTE.md +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/packs/http-client/pack.toml +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/src/sayfirst_cli/packs/subprocess/NOTE.md +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/governed_programs.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/pack_doubles.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/replies.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_ask_connection_loss.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_ask_end_to_end.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_boundary_runtime.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_changelog_names_the_version.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_database_pack.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_dependency_closure.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_developer_certificate_of_origin.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_engine_is_agnostic.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_governed_run_leaves_no_trace.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_manifest.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_offline_audit_vectors.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_pack_doubles.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_partition_boundary.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_pointers_survive_publication.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_public_vocabulary.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_publication_checklist_names_every_act.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_read_rendering.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_release_workflow_publishes_only_on_a_tag.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_running_the_module.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_shipped_wheel_notices.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_source_distribution_notices.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_spdx_identifiers.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_subprocess_pack.py +0 -0
- {sayfirst_cli-0.2.0 → sayfirst_cli-0.3.1}/tests/test_trace_and_explain.py +0 -0
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
# run on a machine has to be given. Apart from it, what a contributor runs
|
|
7
7
|
# locally is what decides a merge; a step that exists only here is a step nobody
|
|
8
8
|
# can reproduce, and `tests/test_gate_modes.py` fails if this file grows a
|
|
9
|
-
# second one. The rest of what is written below decides which of the gate
|
|
10
|
-
#
|
|
11
|
-
# rather than a check.
|
|
9
|
+
# second one. The rest of what is written below decides which leg of the gate
|
|
10
|
+
# this run is entitled to and renders which of its two green outcomes it
|
|
11
|
+
# reached, which is routing and rendering rather than a check.
|
|
12
12
|
#
|
|
13
13
|
# This workflow publishes nothing. `release.yml` is the only file here that can,
|
|
14
14
|
# and only on a tag; a publish step added here is a constitutional change, not a
|
|
@@ -33,17 +33,19 @@
|
|
|
33
33
|
# text: neither file spells the version, because a version spelled twice is a
|
|
34
34
|
# version that will eventually be spelled two ways.
|
|
35
35
|
#
|
|
36
|
-
# WHY A REDUCED LEG STILL
|
|
37
|
-
#
|
|
38
|
-
#
|
|
36
|
+
# WHY A REDUCED LEG STILL EXISTS. A machine that cannot reach that repository
|
|
37
|
+
# still exists — a fork with no network, a runner behind a proxy, a contributor
|
|
38
|
+
# offline — and what it gets is a contract that is simply
|
|
39
39
|
# absent. An absent contract is a fact about the world, not a crash. Article 2
|
|
40
40
|
# says an absence is "never rendered as a negative fact, a zero or a healthy
|
|
41
41
|
# state", so `scripts/gate.sh` has three outcomes and this workflow renders
|
|
42
42
|
# three and not two:
|
|
43
43
|
#
|
|
44
|
-
# gate (full)
|
|
44
|
+
# gate (full, python X.Y) the contract was built from the control
|
|
45
45
|
# plane's public repository, at the tag this
|
|
46
|
-
# client pins, and every check ran
|
|
46
|
+
# client pins, and every check ran — once per
|
|
47
|
+
# interpreter `requires-python` admits, since
|
|
48
|
+
# a release installs on any of them.
|
|
47
49
|
# `scripts/gate.sh` exits 0.
|
|
48
50
|
# gate (reduced — contract no checkout of the control plane was made,
|
|
49
51
|
# absent, some checks not run) so the contract is absent. Format, lint and
|
|
@@ -57,16 +59,36 @@
|
|
|
57
59
|
# gate came back reduced, and a reduced job
|
|
58
60
|
# whose gate came back full.
|
|
59
61
|
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
62
|
+
# EXACTLY ONE OF THE TWO LEGS RUNS, AND ONE JOB DECIDES WHICH. The first job
|
|
63
|
+
# below asks the world a single question — can this runner read the control
|
|
64
|
+
# plane's public repository at the tag this client pins — and the two legs carry
|
|
65
|
+
# the two answers as their conditions. It is asked of the remote, with no
|
|
66
|
+
# credential and no checkout, because it is the same question the gate will meet
|
|
67
|
+
# a minute later: a runner that cannot read the tag cannot build the contract,
|
|
68
|
+
# and a runner that can has no business running a leg whose entire claim is that
|
|
69
|
+
# the contract is missing.
|
|
70
|
+
#
|
|
71
|
+
# Running both instead is not a stricter gate, and this file tried it for
|
|
72
|
+
# exactly one run. The reduced leg went red on a runner that could read the tag
|
|
73
|
+
# perfectly well: its step requires the gate to come back reduced, the gate had
|
|
74
|
+
# every reason to come back full, and « the contract is absent » was false on
|
|
75
|
+
# that machine. A leg whose premise does not hold is a check of a machine that
|
|
76
|
+
# is not this one.
|
|
77
|
+
#
|
|
78
|
+
# WHAT THEN PROVES THE REDUCED PATH, since a run of this repository can reach
|
|
79
|
+
# the tag and will therefore always take the full leg. The full leg proves it:
|
|
80
|
+
# `tests/test_contract_absence.py` runs this whole suite again in a child
|
|
81
|
+
# interpreter with the contract's packages made unimportable, and requires that
|
|
82
|
+
# run to come back green with every check it could not run named and counted.
|
|
83
|
+
# That guard is why the reduced path cannot rot unobserved while the leg that
|
|
84
|
+
# renders it sits skipped — which is how the fifteen tests that ask for the
|
|
85
|
+
# contract only when they run came to be discovered by a public runner rather
|
|
86
|
+
# than by this repository.
|
|
87
|
+
#
|
|
88
|
+
# A reduced run proves nothing about the contract and nothing about the
|
|
89
|
+
# installed dependency closure of articles 13 and 14, and today nothing about
|
|
90
|
+
# this client against a daemon either — the job lists what it did not run, so
|
|
91
|
+
# that sentence is checkable against the run rather than trusted from here.
|
|
70
92
|
name: gate
|
|
71
93
|
|
|
72
94
|
on:
|
|
@@ -77,28 +99,38 @@ permissions:
|
|
|
77
99
|
contents: read
|
|
78
100
|
|
|
79
101
|
jobs:
|
|
80
|
-
|
|
81
|
-
|
|
102
|
+
# Asked once, before either leg, and answered about the world rather than
|
|
103
|
+
# about this repository: the tag is derived from the pins, the remote is asked
|
|
104
|
+
# whether it has that tag, and the answer is written out for the two legs to
|
|
105
|
+
# read. No credential, and no clone — `git ls-remote` reads refs and nothing
|
|
106
|
+
# else — so a fork gets a truthful answer about its own machine, which is the
|
|
107
|
+
# only kind of answer article 16 can be held to.
|
|
108
|
+
#
|
|
109
|
+
# Why the question is the tag and not the network: a runner that can reach
|
|
110
|
+
# github.com and finds no such tag is in exactly the position of one that can
|
|
111
|
+
# reach nothing at all — there is no source to build the contract from — and
|
|
112
|
+
# the gate's reduced outcome is what honestly describes both.
|
|
113
|
+
reachability:
|
|
114
|
+
name: is the contract reachable
|
|
82
115
|
runs-on: ubuntu-latest
|
|
116
|
+
outputs:
|
|
117
|
+
contract: ${{ steps.reachable.outputs.contract }}
|
|
118
|
+
tag: ${{ steps.contract.outputs.tag }}
|
|
83
119
|
steps:
|
|
84
120
|
- uses: actions/checkout@v5
|
|
85
121
|
with:
|
|
86
122
|
path: sayfirst-cli
|
|
87
123
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# Read out of the project file, never spelled here: `scripts/gate.sh`
|
|
93
|
-
# reads the same pin out of the same file, and `release.yml` derives the
|
|
94
|
-
# same tag with this same script — held as one text by
|
|
124
|
+
# Read out of the project file, never spelled here: the gate reads the
|
|
125
|
+
# same pin out of the same file, and `release.yml` derives the
|
|
126
|
+
# same tag with this same shell — held as one text by
|
|
95
127
|
# `tests/test_gate_modes.py`, because two readings of one rule is how two
|
|
96
128
|
# files stop agreeing about it. Shell rather than an interpreter, so that
|
|
97
|
-
#
|
|
129
|
+
# this job runs no program of this repository at all.
|
|
98
130
|
#
|
|
99
131
|
# THE ASSUMPTION THIS DERIVATION RESTS ON, stated rather than left to be
|
|
100
|
-
# discovered. One tag of the control plane is
|
|
101
|
-
# client pins TWO of the distributions that tag builds. The tag is
|
|
132
|
+
# discovered. One tag of the control plane is what the full leg reads, and
|
|
133
|
+
# this client pins TWO of the distributions that tag builds. The tag is
|
|
102
134
|
# derived from the `sayfirst-contract` pin because the control plane moves
|
|
103
135
|
# every distribution it publishes to one version together — its own rule,
|
|
104
136
|
# held by its own guard, and by nothing in this repository. So the step
|
|
@@ -126,10 +158,69 @@ jobs:
|
|
|
126
158
|
echo "tag=v${pinned}" >> "$GITHUB_OUTPUT"
|
|
127
159
|
echo "The contract is read at v${pinned}." >> "$GITHUB_STEP_SUMMARY"
|
|
128
160
|
|
|
161
|
+
# The two words this whole file routes on. `--exit-code` is what makes a
|
|
162
|
+
# tag that is not there a status rather than empty output silently read as
|
|
163
|
+
# success, and the tag is passed in as an environment variable rather than
|
|
164
|
+
# interpolated into the shell, which is this repository's rule for every
|
|
165
|
+
# value a runner computes.
|
|
166
|
+
- name: Whether that tag can be read with nothing but this repository
|
|
167
|
+
id: reachable
|
|
168
|
+
env:
|
|
169
|
+
tag: ${{ steps.contract.outputs.tag }}
|
|
170
|
+
run: |
|
|
171
|
+
if git ls-remote --exit-code --tags \
|
|
172
|
+
https://github.com/fredaime/sayfirst-control-plane.git "refs/tags/${tag}" \
|
|
173
|
+
>/dev/null 2>&1; then
|
|
174
|
+
echo "contract=reachable" >> "$GITHUB_OUTPUT"
|
|
175
|
+
{
|
|
176
|
+
echo "### the contract is reachable at ${tag}"
|
|
177
|
+
echo ""
|
|
178
|
+
echo "The full gate runs, and the reduced leg does not: on this runner the"
|
|
179
|
+
echo "contract is not absent, so a leg whose name says it is would be reporting"
|
|
180
|
+
echo "about a machine other than this one."
|
|
181
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
182
|
+
else
|
|
183
|
+
echo "contract=absent" >> "$GITHUB_OUTPUT"
|
|
184
|
+
{
|
|
185
|
+
echo "### the contract cannot be read at ${tag}"
|
|
186
|
+
echo ""
|
|
187
|
+
echo "Nothing here holds a credential, so this is a fact about the runner and"
|
|
188
|
+
echo "the tag, not about permission: either the tag is not published yet or"
|
|
189
|
+
echo "this machine cannot reach the repository that carries it. The reduced"
|
|
190
|
+
echo "leg runs instead of the full one and says, in its own name, what it did"
|
|
191
|
+
echo "not prove."
|
|
192
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
full:
|
|
196
|
+
name: gate (full, python ${{ matrix.python }})
|
|
197
|
+
needs: reachability
|
|
198
|
+
if: needs.reachability.outputs.contract == 'reachable'
|
|
199
|
+
runs-on: ubuntu-latest
|
|
200
|
+
# Every interpreter `requires-python` admits. Python 3.14 changed what
|
|
201
|
+
# `Path.exists` answers and gave `json` a `__main__`, and a gate that ran one
|
|
202
|
+
# interpreter found neither: a range this client installs on is a range
|
|
203
|
+
# this gate runs.
|
|
204
|
+
strategy:
|
|
205
|
+
fail-fast: false
|
|
206
|
+
matrix:
|
|
207
|
+
python: ["3.12", "3.13", "3.14"]
|
|
208
|
+
steps:
|
|
209
|
+
- uses: actions/checkout@v5
|
|
210
|
+
with:
|
|
211
|
+
path: sayfirst-cli
|
|
212
|
+
|
|
213
|
+
- uses: astral-sh/setup-uv@v5
|
|
214
|
+
with:
|
|
215
|
+
version: "0.12.5"
|
|
216
|
+
|
|
217
|
+
# The tag is the one the job above derived, read as its output rather than
|
|
218
|
+
# derived a second time here: this job runs because that job answered, and
|
|
219
|
+
# a second derivation could answer differently.
|
|
129
220
|
- uses: actions/checkout@v5
|
|
130
221
|
with:
|
|
131
222
|
repository: fredaime/sayfirst-control-plane
|
|
132
|
-
ref: ${{
|
|
223
|
+
ref: ${{ needs.reachability.outputs.tag }}
|
|
133
224
|
path: sayfirst-control-plane
|
|
134
225
|
fetch-depth: 0
|
|
135
226
|
|
|
@@ -137,13 +228,14 @@ jobs:
|
|
|
137
228
|
working-directory: sayfirst-cli
|
|
138
229
|
env:
|
|
139
230
|
SAYFIRST_CONTRACT_SOURCE: ${{ github.workspace }}/sayfirst-control-plane
|
|
140
|
-
SAYFIRST_CONTRACT_REF: ${{
|
|
231
|
+
SAYFIRST_CONTRACT_REF: ${{ needs.reachability.outputs.tag }}
|
|
232
|
+
SAYFIRST_PYTHON: ${{ matrix.python }}
|
|
141
233
|
run: |
|
|
142
234
|
status=0
|
|
143
235
|
./scripts/gate.sh || status=$?
|
|
144
236
|
case "$status" in
|
|
145
237
|
0)
|
|
146
|
-
echo "### gate: full green" >> "$GITHUB_STEP_SUMMARY"
|
|
238
|
+
echo "### gate: full green (python ${{ matrix.python }})" >> "$GITHUB_STEP_SUMMARY"
|
|
147
239
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
148
240
|
echo "Format, lint, every test and the dependency-closure guard of articles 13" >> "$GITHUB_STEP_SUMMARY"
|
|
149
241
|
echo "and 14 ran against a contract built from the control plane's public" >> "$GITHUB_STEP_SUMMARY"
|
|
@@ -158,8 +250,15 @@ jobs:
|
|
|
158
250
|
;;
|
|
159
251
|
esac
|
|
160
252
|
|
|
253
|
+
# The other answer. This leg runs when the tag could not be read, which is
|
|
254
|
+
# the only condition under which its name is true — and skipping it otherwise
|
|
255
|
+
# is not a gap: the full leg re-runs this whole suite with the contract's
|
|
256
|
+
# packages hidden, so the reduced path is proven on every run of this
|
|
257
|
+
# repository even though this job is where it is rendered.
|
|
161
258
|
reduced:
|
|
162
259
|
name: gate (reduced — contract absent, some checks not run)
|
|
260
|
+
needs: reachability
|
|
261
|
+
if: needs.reachability.outputs.contract == 'absent'
|
|
163
262
|
runs-on: ubuntu-latest
|
|
164
263
|
steps:
|
|
165
264
|
- uses: actions/checkout@v5
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
|
2
|
+
# Changelog
|
|
3
|
+
|
|
4
|
+
Every release of this client carries a section here, named for the version the project file
|
|
5
|
+
declares; a release with no section fails the gate.
|
|
6
|
+
|
|
7
|
+
## Unreleased
|
|
8
|
+
|
|
9
|
+
## 0.3.1
|
|
10
|
+
|
|
11
|
+
- **`instrument verify` says how a run ended when the program raised, too.** The harness said
|
|
12
|
+
its own ending only for a program that returned, so a program that ended on an exception before
|
|
13
|
+
a line of it was seen — a script that does not compile, a module whose bytecode another Python
|
|
14
|
+
wrote — left no ending behind, and the command answered `4` and « the verification did not run
|
|
15
|
+
to a conclusion »: « the control plane could not be asked », about a plane that had answered.
|
|
16
|
+
It now answers `7`, « the verifier never saw the program's own code start », naming the
|
|
17
|
+
exception the program ended on; the program's own traceback is still printed. For the same
|
|
18
|
+
reason, a program that caught the abort of an unreadable chain and then raised an exception of
|
|
19
|
+
its own now gets the chain's ending (`4`, the transport's own problem code and sentence) rather
|
|
20
|
+
than the generic one.
|
|
21
|
+
- The contract and the boundary move to 0.3.1, released with this client.
|
|
22
|
+
- A `python` the shell cannot find is refused as before (64), and the refusal now names
|
|
23
|
+
the `python3` on the same PATH.
|
|
24
|
+
- Every program the README and the quickstart run is spelled `python3 …`; the
|
|
25
|
+
quickstart starts the daemon again before section 7, shows a closed epoch verified,
|
|
26
|
+
and says how long a rejection stands.
|
|
27
|
+
|
|
28
|
+
## 0.3.0
|
|
29
|
+
|
|
30
|
+
- **`--socket` is optional for a per-user profile**, on every verb that opens a connection. Given
|
|
31
|
+
none, the client looks at the per-user default address — the contract's own rule, the one a
|
|
32
|
+
per-user daemon given no address binds by — and at nothing else: nothing is searched for, and
|
|
33
|
+
whoever answers is still verified to be the expected account's process before a byte is sent. A
|
|
34
|
+
`--socket` somebody typed is the address. A system profile is never given one and still names
|
|
35
|
+
it (`64` otherwise). **What changes for a caller:** an invocation with no `--socket` used to end
|
|
36
|
+
as a usage error (`2`); it now asks, and with nobody at the default address it answers « could
|
|
37
|
+
not ask » (`4`), naming the address it looked at. `evidence audit` with neither `--file` nor
|
|
38
|
+
`--socket` is an online audit at that address rather than a usage error. Needs the contract
|
|
39
|
+
distribution that publishes the rule, which is the one after 0.2.0.
|
|
40
|
+
- **`--pack` takes the name of a pack this distribution ships** (`--pack subprocess`), as well as
|
|
41
|
+
a directory. The spelling alone decides: a designation with a path separator in it, or `.` or
|
|
42
|
+
`..`, is a directory; a bare word is a shipped pack's name and is never read as a directory of
|
|
43
|
+
the working directory. **What changes for a caller:** a pack directory designated by a bare
|
|
44
|
+
relative word (`--pack own-pack`) is now refused (`64`) with the spelling that names it
|
|
45
|
+
(`--pack ./own-pack`). Absolute paths and paths with a separator are read exactly as before.
|
|
46
|
+
`packs check` takes the same two spellings. `docs/PACKS.md` says why this is a designation and
|
|
47
|
+
not a registry.
|
|
48
|
+
- **A target whose first word is `python`, `python3` or `python3.N` is run by that interpreter.**
|
|
49
|
+
The whole command is handed to it, the boundary is installed in that process, and the client,
|
|
50
|
+
the boundary and the contract are lent to it by name — three packages and their metadata, and
|
|
51
|
+
nothing else of this environment. A program in a project's own environment therefore keeps its
|
|
52
|
+
dependencies when this client is installed as a tool. The interpreter has to be Python 3.12 or
|
|
53
|
+
later, and interpreter options are not carried. Before this, such a target was refused (`64`) as
|
|
54
|
+
a script that does not exist; a target with no interpreter word runs as it always did.
|
|
55
|
+
- **An executable script hands over by its shebang.** An agent started as a console script
|
|
56
|
+
(`-- ./myagent`) rather than as `python …` is run by the interpreter its shebang names —
|
|
57
|
+
including `#!/usr/bin/env python3` — so a console-script agent in a project's environment keeps
|
|
58
|
+
its dependencies too. A shebang naming this same interpreter, and a non-executable file, are
|
|
59
|
+
unchanged. **What changes for a caller:** an executable whose shebang names another Python used
|
|
60
|
+
to run in this command's interpreter and fail to import the project's packages; it now runs in
|
|
61
|
+
the interpreter the shebang names.
|
|
62
|
+
- **A runner in the first position is refused** (`64`): `-- uv run app.py`, `-- poetry run app.py`
|
|
63
|
+
and their kind start an interpreter the boundary is not in, so handing the runner over would
|
|
64
|
+
govern nothing. The refusal names the two spellings that work — name the interpreter, or ask the
|
|
65
|
+
runner for it once. Before, `uv` was reported as a script that does not exist.
|
|
66
|
+
- **`instrument verify` matches a run's records by a correlation it stamps, not by their
|
|
67
|
+
connection.** A program that makes two effects of different kinds is two asks, and the shipped
|
|
68
|
+
boundary holds one connection per grant, so its records span two connections. They are still one
|
|
69
|
+
run's, told so by a per-run token the verifier stamps on every ask and the plane records on the
|
|
70
|
+
effect (`correlation_source: boundary_supplied`). **The defect this fixes:** such a run reported
|
|
71
|
+
the second effect `unjudged` and exited `7`; it now verifies clean. The token also tells a
|
|
72
|
+
concurrent same-account run's records apart, which the connection check could not, and it holds
|
|
73
|
+
the first record as well as the rest. Needs the boundary distribution that stamps it, the one
|
|
74
|
+
after 0.2.0.
|
|
75
|
+
- **The `unjudged:` line names the reason the run actually recorded**, one per distinct cause,
|
|
76
|
+
rather than always saying an effect named a start file. A count made of a damaged range, an
|
|
77
|
+
interrupted walk, another execution's record or an uninterposed path now reads as what it was.
|
|
78
|
+
- `docs/PACKS.md` records, with the evidence, why no convenience pack for `requests`, `httpx`,
|
|
79
|
+
`os.system` or a Postgres driver is shipped — a limit of the verifier, not of the classification.
|
|
80
|
+
The verifier confirms an effect from a distinct CPython audit event and shares no state with the
|
|
81
|
+
engine; `requests`/`httpx`/Postgres emit nothing distinct from `socket.connect`, and `os.system`'s
|
|
82
|
+
module is `os`, which the engine imports throughout, so a pack for it would make the engine-
|
|
83
|
+
agnosticism guard read every `import os` as a special case. Each could be governed by hand against
|
|
84
|
+
`sayfirst-boundary`; none can be shipped as a verifiable pack without weakening a guard.
|
|
85
|
+
- **`instrument run --follow-children`** installs the boundary in the Python children the program
|
|
86
|
+
spawns with its environment, before the child's code runs, so a program that starts workers or
|
|
87
|
+
tools of its own has their effects governed too — and a grandchild started the same way. It works
|
|
88
|
+
by a `sitecustomize` on the child's import path (`src/sayfirst_cli/instrument/follow.py`), needs
|
|
89
|
+
this client installed in the interpreter the children run, and fails a child **closed** once the
|
|
90
|
+
bootstrap runs: a child that cannot install the boundary raises before its program runs, and a
|
|
91
|
+
child whose daemon is unreachable fails at the ask exactly as the parent does. A child the
|
|
92
|
+
bootstrap never reaches is not followed and runs as it would without the flag: one started with
|
|
93
|
+
`-S`, `-I` or `-E`, or given a `PYTHONPATH` of its own. It is `run`'s flag, not `verify`'s: a spawned
|
|
94
|
+
child is a separate process the single-process proof cannot see (`harness.py` counts such a child
|
|
95
|
+
as coverage it could not judge), so following would govern what the proof misses.
|
|
96
|
+
- With no `--socket` and nothing at the default address, `instrument run` says which address it
|
|
97
|
+
looked at before the program starts. Nothing else about that run changes: a program that asks
|
|
98
|
+
nothing still runs, and an effect a pack names still fails closed, as the program's own
|
|
99
|
+
exception.
|
|
100
|
+
- The quickstart is three commands, and the page is a transcript of them.
|
|
101
|
+
- **`instrument run` ends with the outcome's own status when the program does not handle it.** A
|
|
102
|
+
boundary outcome the program lets escape — denied, suspended, refused, could not ask — used to
|
|
103
|
+
end the run as any uncaught exception does, with the interpreter's `1`, which is this client's
|
|
104
|
+
« deny » for all four. The traceback is still the program's; the status is now `1`, `5`, `3` or
|
|
105
|
+
`4`. A program that catches the outcome and exits on its own still ends with its own status.
|
|
106
|
+
- **`instrument verify` asks for every effect.** The boundary in front of a verified program holds
|
|
107
|
+
no grant, so an identical effect repeated while a grant lived — answered with nothing asked and
|
|
108
|
+
nothing recorded — is no longer reported `ungoverned` and stopped (`6`). `instrument run` still
|
|
109
|
+
holds its grants (article 10).
|
|
110
|
+
- **`instrument verify` counts one spawn once.** `subprocess.Popen` creates its process through
|
|
111
|
+
`os.posix_spawn` or, from Python 3.14, `_posixsubprocess.fork_exec`, both paths the subprocess
|
|
112
|
+
pack names as not interposed, so one governed spawn was reported with an unjudged effect and
|
|
113
|
+
`7`. A point may now name, among its `uninterposed_events`, the `inner_events` its own call
|
|
114
|
+
raises; such an event is paired with the call it judged when it is that thread's very next event
|
|
115
|
+
and carries the same argument vector, and is counted otherwise. The subprocess pack also names
|
|
116
|
+
`_posixsubprocess.fork_exec`, which is how `multiprocessing` starts a process by default on Linux
|
|
117
|
+
from 3.14, so such a start is counted instead of passing unseen.
|
|
118
|
+
- **`instrument verify` answers `3` for a chain read the plane refused**, as every other command
|
|
119
|
+
does, rather than `4`; and `7` rather than `4` when it never saw the program's own code start,
|
|
120
|
+
since the plane had been asked. `docs/PACKS.md` lists the statuses.
|
|
121
|
+
- **Every connection is bounded.** `ask`, every read and the verifier's chain walk wait at most
|
|
122
|
+
five seconds for an answer; a far end that accepts and never answers is « could not ask » (`4`)
|
|
123
|
+
instead of a command that hangs. A named `--socket` is made absolute once, so a program that
|
|
124
|
+
changes directory asks the daemon it was pointed at (with the contract distribution of this
|
|
125
|
+
release).
|
|
126
|
+
- **Smaller corrections.** A uid with no account name is spelled `user:<uid>`, as the daemon names
|
|
127
|
+
it, instead of ending `instrument run` in a traceback; an `ask` answer nested past the reads'
|
|
128
|
+
depth bound is an answer this client could not read (`4`), not a traceback; `evidence export`
|
|
129
|
+
that cannot write its bundle says « could not save » (`7`); on Python 3.14, an `--out` path this
|
|
130
|
+
client cannot look at is still refused (`64`) before anything is asked, and an entry
|
|
131
|
+
`evidence exports` cannot look at is still « could not check » (`7`); `approvals` names the
|
|
132
|
+
person who acted; `packs check` refuses a pack whose `uninterposed_events` or `inner_events` the
|
|
133
|
+
verifier would refuse.
|
|
134
|
+
- **Interpreter hand-over.** A script whose shebang names a Python that is not there is refused
|
|
135
|
+
(`64`) instead of being run by this client's own interpreter; an interpreter newer than the lent
|
|
136
|
+
packages declare (3.15 and later) is refused; `--follow-children` under a named interpreter that
|
|
137
|
+
does not have this client installed is refused, where every Python child used to die at
|
|
138
|
+
start-up; and under `PYTHONSAFEPATH` the head of the import path is left as the interpreter gave
|
|
139
|
+
it, where the launcher used to replace an entry of the person's own.
|
|
140
|
+
- The full gate runs on Python 3.12, 3.13 and 3.14, the interpreters `requires-python` admits.
|
|
141
|
+
|
|
142
|
+
- Every commit of a change is read for its sign-off on every pull request, and one that carries
|
|
143
|
+
none is refused by name. A range that cannot be read is refused too, rather than reported as
|
|
144
|
+
passing.
|
|
145
|
+
- The publication checklist carries every act the constitution names, in the order they are
|
|
146
|
+
performed, and a guard fails when one of them stops being a line of it. The security policy says
|
|
147
|
+
which of article 0's two paths this repository is published by: a fresh repository, never a
|
|
148
|
+
change of visibility.
|
|
149
|
+
- The gate reduces on a machine with no contract instead of failing there. A test that asks for a
|
|
150
|
+
command only when it runs — this client imports a verb when it dispatches it — is now stood
|
|
151
|
+
down by name and counted, the way a module that could not be imported already was, and a run
|
|
152
|
+
with the contract hidden is required to come back green rather than merely to collect.
|
|
153
|
+
- One of the two gate legs runs, decided by whether the control plane's public repository can be
|
|
154
|
+
read at the tag this client pins. The question is asked once, of the remote, with no credential;
|
|
155
|
+
a leg reporting that the contract is absent no longer runs on a machine where it is not.
|
|
156
|
+
|
|
157
|
+
## 0.2.0
|
|
158
|
+
|
|
159
|
+
- The evidence surface reads and verifies: `history` pages a scope, `audit` puts the served
|
|
160
|
+
verdict beside a local check and names a finding wherever the two disagree, `export` saves a
|
|
161
|
+
bundle a third party verifies offline with the contract distribution alone, and `exports`
|
|
162
|
+
checks a directory of saved bundles without opening a socket.
|
|
163
|
+
- `instrument run` puts the boundary in front of somebody else's program, reversibly and writing
|
|
164
|
+
nothing; `instrument verify` runs it again under the interpreter's own audit hook and proves,
|
|
165
|
+
from that and the scope's evidence chain alone, that every effect of a named kind was preceded
|
|
166
|
+
by a decision; `instrument apply` is reserved and refuses, saying so.
|
|
167
|
+
- `trace` reads back the record of one decision and follows it into the evidence that holds it:
|
|
168
|
+
the record itself, and where it sits in the chain, bounded to the pages the read walks. Like
|
|
169
|
+
every read from a daemon it names its scope explicitly, and it exits `0` on a read whatever the
|
|
170
|
+
decision it read said.
|
|
171
|
+
- `explain` renders the reason the control plane gave for a decision — the rule it applied and
|
|
172
|
+
the policy version it ran under — in the plane's own words, and composes none of its own. A
|
|
173
|
+
client explains and invokes control semantics; it never derives them, and a command that
|
|
174
|
+
reasoned here would be a second control plane with no evidence behind it.
|
|
175
|
+
- `packs list` prints the convenience packs this distribution ships, one line each with the path
|
|
176
|
+
`--pack` accepts, and `packs check` reads one the way the engine will.
|
|
177
|
+
- `approvals` shows a suspended ask and ends the wait, so the next ask runs the body once.
|
|
178
|
+
- A verdict the verifier could not reach is rendered as it was given rather than as a negative
|
|
179
|
+
fact about the chain.
|
|
180
|
+
- The release itself: this distribution moves to `0.2.0`, and installing it pins
|
|
181
|
+
`sayfirst-contract==0.2.0` and `sayfirst-boundary==0.2.0` — the contract this client speaks and
|
|
182
|
+
the runtime a governed program holds its grant in — at that version and no other. An
|
|
183
|
+
installation of this version therefore carries exactly those two, and upgrading it moves all
|
|
184
|
+
three together.
|