scpe-protocol 0.2.0__tar.gz → 0.2.2__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.
Files changed (57) hide show
  1. {scpe_protocol-0.2.0/scpe_protocol.egg-info → scpe_protocol-0.2.2}/PKG-INFO +64 -21
  2. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/README.md +313 -270
  3. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/pyproject.toml +56 -56
  4. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/producer.py +13 -4
  5. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/standalone/verify_envelope.py +42 -4
  6. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/__init__.py +16 -16
  7. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/cli.py +22 -2
  8. scpe_protocol-0.2.2/scpe/context.py +123 -0
  9. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/results.py +42 -5
  10. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/seal.py +41 -12
  11. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2/scpe_protocol.egg-info}/PKG-INFO +64 -21
  12. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/SOURCES.txt +3 -0
  13. scpe_protocol-0.2.2/tests/test_action_expressions.py +69 -0
  14. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_action_yaml.py +561 -508
  15. scpe_protocol-0.2.2/tests/test_context_binding.py +195 -0
  16. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_results_contract.py +1 -1
  17. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_seal.py +50 -0
  18. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/LICENSE +0 -0
  19. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/LICENSE-SPEC +0 -0
  20. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/__init__.py +0 -0
  21. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/disclosure.py +0 -0
  22. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/level1_lint.py +0 -0
  23. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/standalone/__init__.py +0 -0
  24. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/diffinfo.py +0 -0
  25. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/optin.py +0 -0
  26. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/testrun.py +0 -0
  27. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/verify.py +0 -0
  28. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/dependency_links.txt +0 -0
  29. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/entry_points.txt +0 -0
  30. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/requires.txt +0 -0
  31. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/top_level.txt +0 -0
  32. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/setup.cfg +0 -0
  33. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_diff_resolution.py +0 -0
  34. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_differential_verifiers.py +0 -0
  35. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_disclosure.py +0 -0
  36. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_e2e_local.py +0 -0
  37. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_empty_keys_anchor_parity.py +0 -0
  38. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_entry_points.py +0 -0
  39. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_gate_requires_forge_anchor.py +0 -0
  40. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_key_source_anchor.py +0 -0
  41. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_level1_lint.py +0 -0
  42. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_level2_implies_level1.py +0 -0
  43. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_no_duplicate_verifier.py +0 -0
  44. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_optin.py +0 -0
  45. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_package_is_stdlib_only.py +0 -0
  46. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_producer_cli.py +0 -0
  47. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_producer_roundtrip.py +0 -0
  48. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_producer_submit.py +0 -0
  49. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_profiles_demo.py +0 -0
  50. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_retired_envelope_format.py +0 -0
  51. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_seal_render.py +0 -0
  52. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_seal_results.py +0 -0
  53. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_spec_vectors.py +0 -0
  54. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_ssrf_fetch.py +0 -0
  55. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_stress_100prs.py +0 -0
  56. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_testrun.py +0 -0
  57. {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_verify_cli_parity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scpe-protocol
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: SCPE: an open protocol for verifiable provenance of any digital artifact — who signed a contribution or file, what they declared about AI use, and that nothing was tampered with, verified offline with no server and no new accounts. Ships the single-file reference verifier, its producer, and a CLI (`scpe`) that seals a pull request from it.
5
5
  Author: Augusto Bastos
6
6
  License-Expression: Apache-2.0
@@ -44,7 +44,11 @@ Dynamic: license-file
44
44
  <img alt="spec" src="https://img.shields.io/badge/spec-scpe%2F0.1-41c257?style=flat-square&labelColor=0b0b0c">
45
45
  <img alt="python" src="https://img.shields.io/badge/python-3.11%2B-41c257?style=flat-square&labelColor=0b0b0c">
46
46
  <img alt="license" src="https://img.shields.io/badge/code-Apache--2.0-41c257?style=flat-square&labelColor=0b0b0c">
47
- <img alt="status" src="https://img.shields.io/badge/v0.2-early-d29922?style=flat-square&labelColor=0b0b0c">
47
+ <img alt="status" src="https://img.shields.io/badge/v0.2.2-early-d29922?style=flat-square&labelColor=0b0b0c">
48
+ </p>
49
+
50
+ <p align="center">
51
+ <a href="https://augbastos.github.io/scpe/"><b>augbastos.github.io/scpe</b></a> — what it is, in one page
48
52
  </p>
49
53
 
50
54
  An open protocol for two questions a pull request cannot answer today: **who signed this
@@ -146,6 +150,16 @@ SLSA uses to sell levels. See [docs/LEVELS.md](docs/LEVELS.md).
146
150
  (target repo, base commit, a SHA-256 of the exact diff, an AI-use disclosure, and an
147
151
  optional attribution record) signed with the SSH key already on their GitHub profile
148
152
  (`ssh-keygen -Y sign -n scpe/0.1`). No new account.
153
+
154
+ > **The key has to be published as an authentication key.** `forge` verification reads
155
+ > `github.com/<login>.keys`, and GitHub serves only authentication keys there — a key added
156
+ > under *Signing keys* is real, is used by `git`, and **never appears at that URL**, so a
157
+ > contribution signed with it can only ever reach `key_source: bundled`. Add it with
158
+ > `gh ssh-key add <key>.pub --title scpe` (no `--type signing`). GitHub does publish signing
159
+ > keys, at `api.github.com/users/<login>/ssh_signing_keys`, but `<host>/<login>.keys` is the
160
+ > shape the fixed provider table is built on; reading the signing-key endpoint per provider is
161
+ > open, not decided. Filing the key in the obviously-correct place and silently losing forge
162
+ > verification is the first thing that went wrong for the protocol's own author.
149
163
  2. It travels inside a **normal pull request** — the diff in the branch, the ~1–2 KB signed
150
164
  attestation embedded in the PR body. Merging leaves the repo history clean.
151
165
  3. The **owner's side** re-derives everything itself, with no SCPE server involved: the diff's
@@ -188,22 +202,44 @@ anchors and what a verdict is worth under each.
188
202
  Add a workflow that verifies every PR and posts a seal. Set `require` to gate merges.
189
203
 
190
204
  ```yaml
191
- # .github/workflows/scpe.yml — see docs/workflows/scpe.yml for the fork-safe full version
192
- - uses: augbastos/scpe@v0.2
205
+ # .github/workflows/scpe.yml — the step. Copy BOTH files from docs/workflows/ for the
206
+ # fork-safe version: scpe.yml (verify) and its companion scpe-seal.yml (post the seal).
207
+ - uses: augbastos/scpe@v0.2.2
193
208
  with:
194
209
  level: "1" # 1 = disclosure lint · 2 = signed envelope required
195
210
  require: "true" # fail the check on anything not verifiable
196
211
  ```
197
212
 
198
- Pin the exact tag while the protocol is pre-1.0. `v0.2` is an **immutable alias** — it points at
199
- one commit and is never moved, so a fix arrives as a new tag you adopt by editing the pin, not as
200
- a silent change under the one you already wrote. It is also the first release in which the Action
201
- verifies the `scpe/0.1` envelope itself; `v0.1.x` verified a different, now-removed format, so
202
- upgrading from it is a behaviour change and not a patch [docs/MIGRATION.md](docs/MIGRATION.md)
203
- has the steps.
204
-
205
- The Action uses a fork-safe two-job split: the untrusted job (which runs contributor code) holds
206
- no secrets; only a trusted follow-up job posts the comment. Neither level installs anything in the
213
+ ### Version policy
214
+
215
+ Pin the exact tag while the protocol is pre-1.0.
216
+
217
+ - **Every tag is an immutable alias.** It points at one commit and is never moved. A fix
218
+ arrives as a *new* tag you adopt by editing the pin, never as a silent change under the one
219
+ you already wrote. Both `v0.2.1` and `v0.2.2` exist for that reason rather than because
220
+ `v0.2` was re-pointed.
221
+ - **The cost is that fixes do not reach you on their own** including security fixes. Watch
222
+ the releases, or let Dependabot's `github-actions` ecosystem open the bump as a pull
223
+ request you can read before merging.
224
+ - **A patch tag can change behaviour when the old behaviour was wrong.** `v0.2.2` refuses
225
+ contributions that `v0.2.1` accepted, because `v0.2.1` accepted some it should not have.
226
+ Read the [CHANGELOG](CHANGELOG.md) before moving a pin; "patch" here means the protocol
227
+ did not change, not that nothing did.
228
+ - **`spec_version` is a separate number from the tag.** The protocol is `scpe/0.1` across
229
+ every `v0.2.x` release. See [CHANGELOG.md](CHANGELOG.md) for the three axes and why
230
+ reading one as the other is the easiest mistake to make here.
231
+ - **The `v0.1.x` line is legacy, not merely old.** It verifies an envelope format that is not
232
+ in `spec/`, so upgrading from it is a behaviour change and not a patch —
233
+ [docs/MIGRATION.md](docs/MIGRATION.md) has the steps.
234
+
235
+ **Which tag to be on:** `v0.2.2`. `v0.2.1` and `v0.2` accept a valid envelope replayed from
236
+ another repository (see [SECURITY.md](SECURITY.md)); `v0.2.2` refuses it.
237
+
238
+ The Action uses a fork-safe two-job split, and the two jobs live in **two files**: the untrusted
239
+ job in `scpe.yml` (which runs contributor code) holds no secrets, and only the trusted follow-up
240
+ job in `scpe-seal.yml` posts the comment. Two files is a GitHub constraint, not a preference — a
241
+ workflow that names itself in its own `workflow_run` trigger fails to register at all. Neither
242
+ level installs anything in the
207
243
  runner — both run stdlib-only Python straight out of the Action's own checkout, so the bytes that
208
244
  decide a merge are the bytes of the tag you pinned, not whatever a package index serves that day.
209
245
  Check out with `fetch-depth: 0`: level 2 recomputes the diff as `git diff <base>...<head>`, and the
@@ -237,16 +273,20 @@ passing all eighteen does not by itself show that the `key_source` MUST is honou
237
273
  checked by inspection. Every vector ships its own `keys` file so the suite runs offline, and no
238
274
  vector reaches the `forge` anchor.
239
275
 
240
- **Cost** — measured on the Python reference (Ryzen 5 5600H, Python 3.14, `local` provider):
276
+ **Cost**
241
277
 
242
278
  | | Measured |
243
279
  |---|---|
244
280
  | PR-body attestation (manifest + sig, base64) | 1.1–1.5 KB |
245
281
  | Standalone envelope (3-file / 27-line PR, zipped) | ~1.5 KB |
246
- | Verify wall-time | ~210 ms cold CLI · ~39 ms in a warm process |
282
+ | Verify wall-time | ~200 ms cold process · ~31 ms warm |
247
283
 
248
284
  An `artifact` subject adds its payload size on top of ~800 B fixed overhead. Order-of-magnitude,
249
- single machine — not a formal benchmark suite.
285
+ single machine — not a formal benchmark suite. Re-run it yourself rather than taking the number:
286
+ `python tests/bench_verify.py <envelope.zip> --keys <login.keys>` prints the median of 15 runs
287
+ for both paths. The cold figure is a fresh interpreter per run; the warm one re-verifies
288
+ in-process, which is what a batch consumer sees after import cost is paid once. Both use the
289
+ `flag` anchor, so no network is in the measurement.
250
290
 
251
291
  ## Where it sits
252
292
 
@@ -258,13 +298,16 @@ single machine — not a formal benchmark suite.
258
298
  - **A different layer from build provenance.** Sigstore / SLSA / in-toto attest *artifacts and
259
299
  builds*; SCPE attests a *contribution*, at the pull-request boundary.
260
300
  - **Direct prior art:** `patatt` + `b4` ([kernel.org](https://github.com/mricon/patatt)) have run
261
- this exact pattern — self-sign a patch with a key the platform publishes, verify independently,
262
- no CA, no server — on the Linux kernel's mailing list for years. SCPE applies the same shape to
263
- the GitHub pull-request boundary.
301
+ this shapethe contributor self-signs a patch, the recipient verifies independently, no CA,
302
+ no server — on the Linux kernel's mailing list for years. They differ from SCPE in where the
303
+ key comes from: `patatt` keeps a keyring in the repository, so the project curates who may
304
+ sign; SCPE reads the keys the forge already publishes for the account, so there is nothing to
305
+ curate and nothing to depend on but the forge. Same shape, different trust root, applied to
306
+ the pull-request boundary instead of a mailing list.
264
307
 
265
308
  ## Status
266
309
 
267
- **v0.2 — early.** This is a specification plus a reference implementation (a single-file
310
+ **v0.2.2 — early.** This is a specification plus a reference implementation (a single-file
268
311
  verifier, a producer, and a maintainer-side Action). The full test suite — including a 100-PR
269
312
  stress proof and a local end-to-end — runs on every push; the CI badge above is its live
270
313
  result. Two more independent verifiers, in Go and Rust, reach the same verdict as the Python
@@ -278,7 +321,7 @@ accept. There is **no external adoption yet**. It is not a hosted service and ne
278
321
  **`v0.1.x` is legacy: its signed-envelope path verifies a format that is not in this spec.** The
279
322
  Action at those tags checked a second envelope format that shipped inside the package and was
280
323
  deleted in `0.2.0`. A workflow still pinned there does not fail — it keeps posting seals for a
281
- format nothing here produces, and once `0.2.0` reaches PyPI it breaks instead, because that
324
+ format nothing here produces and now that `0.2.x` is on PyPI it breaks outright instead, because that
282
325
  Action resolved its verifier from the package index at run time rather than from its own
283
326
  checkout. [CHANGELOG.md](CHANGELOG.md) has what moved; [docs/MIGRATION.md](docs/MIGRATION.md)
284
327
  has what to do about it.