Skip to content

Commit 482797b

Browse files
BayyinahEnterpriseClaude (cowork)
andcommitted
release: v0.11.2 (at-Tawbah / G11.0.1 Python verifier corrective)
Phase G11.0.1 (at-Tawbah) closes the genuine prospective audit gaps the v0.11.0 + v0.11.1 ship did not absorb (per Round 28 empirical state audit), plus closes Round 28 finding F18 (flagship: backfill_github_releases.py --dry-run silently no-op'd). Round 28 state audit found that C-1, H-5, H-6, and M-7 were already backported to the unified Python verifier in v0.11.0 via the G11.1 commits ee656ad + c6b219a. The genuine v0.11.2 work is: - T01: extend SAFETY_INVARIANTS.md Invariant 6 step 6 to document CASM-V-032 / CASM-V-035 / CASM-V-036 explicitly. - T03: H-4 nested-generic recursion in Python signature canonicalizer (the Python equivalent of the G11.1 amended_4 T03 rules 6 + 7). The pre-v0.11.2 implementation fell through to ast.unparse(node.slice) for multi-arg generic parameters, producing tuple-stringification artifacts; this release recurses element-wise on Tuple slices and flattens inner unions across the Subscript-Union boundary. - T06: M-5 corrective applied to step5 docstring (references Manifest.from_dict as canonical enforcement site); M-6 corrective applied to SECURITY.md N2 disclosure wording (now names --expected-identity and CASM-V-035 explicitly). - T08 (flagship F18): scripts/backfill_github_releases.py rewritten with argparse; --dry-run / --apply mutually- exclusive required; default no-flag exits 2 with usage. F19 incidental closure: version filter excludes v0.11.1+ workflow-managed releases via _WORKFLOW_MANAGED_FLOOR. Local verification (sandbox): - pytest --collect-only -q -> 589 tests collected (target: 589, +13 from v0.11.1's 576: 8 in test_python_signature_h4.py, 5 in test_backfill_dry_run.py) - pytest -q -> 543 passed, 46 skipped - ruff check + ruff format --check + mypy clean - furqan-lint version -> 'furqan-lint 0.11.2' - Em-dash check (extended scope) clean - release_sweep.py: clean (README pins synchronized to v0.11.2) After v0.11.2 the Python verifier's signature canonicalization matches the Rust verifier's amended_4 T03 rules 6 + 7. The remaining amended_4 defenses (C-1 identity policy, H-5 trusted_root threading, H-6 checker_set_hash Form A, M-7 typed identity-extraction errors) were already shared between the two pipelines from v0.11.0; v0.11.2 acknowledges that and pins the audit register state. Co-authored-by: Claude (cowork) <claude+cowork@anthropic.com>
1 parent 3e7ca21 commit 482797b

14 files changed

Lines changed: 500 additions & 60 deletions

CHANGELOG.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,121 @@ introduced this convention.
1919

2020
---
2121

22+
## [0.11.2] - 2026-05-09
23+
24+
### Substrate corrective (at-Tawbah / G11.0.1)
25+
26+
Phase G11.0.1 closes the two genuine prospective audit
27+
gaps the v0.11.0 + v0.11.1 ship did not absorb (per Round 28
28+
empirical state audit), plus closes Round 28 finding F18
29+
(flagship: backfill_github_releases.py --dry-run flag silently
30+
no-op'd). The other amended_4 prospective findings (C-1, H-5,
31+
H-6, M-7) were already backported to the unified Python
32+
verifier substrate via the G11.1 commits ee656ad +
33+
c6b219a; this release acknowledges that and pins the
34+
remaining two gaps explicitly.
35+
36+
#### Closures
37+
38+
- H-4 HIGH (Python signature canonicalization): the Python
39+
`_canonical_type_string` fell through to
40+
`ast.unparse(node.slice)` for multi-argument generic
41+
parameters, producing tuple-stringification artifacts like
42+
`Dict[(str, int)]` instead of `Dict[str, int]`. v0.11.2
43+
recurses element-wise on the Tuple slice and flattens
44+
inner unions across the Subscript-Union boundary so
45+
`Union[Optional[T], List[U]]` canonicalizes consistently.
46+
Cross-language symmetry with the Rust verifier's amended_4
47+
T03 rules 6 + 7 is now restored. (T03)
48+
- F18 HIGH (Round 28 flagship): scripts/backfill_github_releases.py
49+
now requires an explicit `--dry-run` (preview only) or
50+
`--apply` (real `gh release create` calls) flag. The default
51+
no-flag invocation exits 2 with argparse usage rather than
52+
silently mutating live GitHub state. The structural-honesty
53+
thesis applied to the project's own scripts: the documented
54+
surface claim ("--dry-run is preview mode") now matches the
55+
substrate behavior. (T08)
56+
57+
#### Closures from amended_4 prospective defenses (no-op acknowledgement)
58+
59+
The following corrections were already applied in v0.11.0
60+
via the G11.1 commits ee656ad + c6b219a; this entry pins the
61+
audit register state explicitly:
62+
63+
- C-1 CRITICAL (verifier UnsafeNoOp default): closed in v0.11.0
64+
by `verification.step6_verify_sigstore`'s refuse-without-
65+
policy default raising CASM-V-035; cross-applied to both
66+
Python and Rust pipelines via the unified verifier.
67+
- H-5 HIGH (trusted_root threading): closed in v0.11.0 by
68+
`Verifier(_inner=trusted_root)` lower-level constructor.
69+
- H-6 HIGH (checker_set_hash placeholder): closed in v0.11.0
70+
by `compute_checker_set_hash()` Form A substantive hash
71+
over pinned source-file tuple.
72+
- M-7 MEDIUM (string-sentinel identity extraction): closed
73+
in v0.11.0 by `_extract_identity` raising CASM-V-036
74+
typed errors.
75+
76+
#### New corrective work this release
77+
78+
- M-5 MEDIUM: `step5_canonicalize_manifest` docstring now
79+
references `Manifest.from_dict` as the canonical
80+
enforcement site; redundant in-step enforcement prose
81+
removed. (T06)
82+
- M-6 MEDIUM: SECURITY.md Newman 2022 N2 disclosure wording
83+
now names `--expected-identity` and CASM-V-035 explicitly,
84+
closing the surface-substrate gap. (T06)
85+
- F19 LOW (incidental): backfill version-filter excludes
86+
v0.11.1+ workflow-managed releases via the new
87+
`_WORKFLOW_MANAGED_FLOOR = (0, 11, 1)` constant in
88+
`scripts/backfill_github_releases.py`. (T08 side closure)
89+
90+
#### Substrate canonical-authority extension
91+
92+
- SAFETY_INVARIANTS.md Section "Invariant 6" step 6 now
93+
documents CASM-V-032 (identity policy mismatch),
94+
CASM-V-035 (no identity policy supplied), and CASM-V-036
95+
(identity extraction failure) explicitly per amended_4
96+
T05 specification. (T01)
97+
98+
#### Deferrals
99+
100+
- F4 ONNX empty-graph: Phase G11.3 scope.
101+
- F5 Release-vs-tag QUESTION: QUESTIONS.md.
102+
- F10 SBOM: Phase G11.4 or v1.5.
103+
- F12 furqan PyPI dep: declined Shape C.
104+
- F17 framework section 4 amendment: framework-side.
105+
- F20 sandbox-CI parity: separate release-checklist amendment.
106+
- F21 PyPI CDN cache transient: separate runbook amendment.
107+
108+
### Tests
109+
110+
Test count: 576 (v0.11.1 ship state) -> 589 (v0.11.2).
111+
Net delta: +13 (8 in test_python_signature_h4.py covering
112+
the H-4 fixtures + inner-difference detection + tuple-value
113+
preservation; 5 in test_backfill_dry_run.py covering F18
114+
no-flag exit / both-flags rejection / no-subprocess-call /
115+
expected-version enumeration / F19 workflow-managed-floor).
116+
117+
### Cross-language symmetry restored
118+
119+
After v0.11.2, the Python verifier's signature canonicalization
120+
matches the Rust verifier's amended_4 T03 rules 6 + 7
121+
(nested-generic element-wise recursion). The other amended_4
122+
defenses (C-1 identity policy, H-5 trusted_root threading,
123+
H-6 checker_set_hash, M-7 typed identity-extraction errors)
124+
were already shared between the two pipelines from v0.11.0.
125+
126+
### Round-28 closure ledger
127+
128+
| Disposition | Count | Findings |
129+
|-------------|-------|----------|
130+
| Genuine v0.11.2 closure | 4 | H-4, M-5, M-6, F18 |
131+
| Acknowledgement (closed in v0.11.0) | 4 | C-1, H-5, H-6, M-7 |
132+
| Side closure | 1 | F19 |
133+
| Deferral | 7 | F4, F5, F10, F12, F17, F20, F21 |
134+
135+
---
136+
22137
## [0.11.1] - 2026-05-08
23138

24139
### Tooling (al-Mubin / G10.5)

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,20 @@ pip install "furqan-lint[rust,go,onnx-runtime,onnx-profile,gate11]" # all adapt
4141
### Install from a specific commit or tag
4242

4343
```bash
44-
pip install "git+https://github.com/BayyinahEnterprise/furqan-lint.git@v0.11.1"
44+
pip install "git+https://github.com/BayyinahEnterprise/furqan-lint.git@v0.11.2"
4545
```
4646

47-
Replace `v0.11.1` with any tag from the [release history](https://github.com/BayyinahEnterprise/furqan-lint/releases) or `main` for the development tip.
47+
Replace `v0.11.2` with any tag from the [release history](https://github.com/BayyinahEnterprise/furqan-lint/releases) or `main` for the development tip.
4848

4949
### Furqan dependency
5050

51-
furqan-lint requires `furqan>=0.11.0`, the Furqan programming-language tooling. As of 2026-05-03 the PyPI release of `furqan` is at v0.10.1; install v0.11.1 directly from GitHub:
51+
furqan-lint requires `furqan>=0.11.0`, the Furqan programming-language tooling. As of 2026-05-03 the PyPI release of `furqan` is at v0.10.1; install v0.11.2 directly from GitHub:
5252

5353
```bash
54-
pip install "git+https://github.com/BayyinahEnterprise/furqan-programming-language.git@v0.11.1"
54+
pip install "git+https://github.com/BayyinahEnterprise/furqan-programming-language.git@v0.11.2"
5555
```
5656

57-
This GitHub-pin step will not be necessary once `furqan` v0.11.1 is published to PyPI.
57+
This GitHub-pin step will not be necessary once `furqan` v0.11.2 is published to PyPI.
5858

5959
### Rust support (opt-in)
6060

@@ -485,7 +485,7 @@ jobs:
485485
runs-on: ubuntu-latest
486486
steps:
487487
- uses: actions/checkout@v4
488-
- uses: BayyinahEnterprise/furqan-lint@v0.11.1
488+
- uses: BayyinahEnterprise/furqan-lint@v0.11.2
489489
with:
490490
path: src/
491491
```
@@ -505,7 +505,7 @@ Python files:
505505
# .pre-commit-config.yaml
506506
repos:
507507
- repo: https://github.com/BayyinahEnterprise/furqan-lint
508-
rev: v0.11.1
508+
rev: v0.11.2
509509
hooks:
510510
- id: furqan-lint
511511
```
@@ -540,7 +540,7 @@ repos:
540540
- id: mypy
541541
542542
- repo: https://github.com/BayyinahEnterprise/furqan-lint
543-
rev: v0.11.1
543+
rev: v0.11.2
544544
hooks:
545545
- id: furqan-lint
546546
```
@@ -614,7 +614,7 @@ MARAD example.py
614614
Per-version closure ledgers are in
615615
[CHANGELOG.md](CHANGELOG.md). This README previously
616616
mirrored closures from v0.2.0 through v0.11.0; that mirror
617-
was retired in v0.11.1 (Phase G10.5 al-Mubin) because
617+
was retired in v0.11.2 (Phase G10.5 al-Mubin) because
618618
CHANGELOG.md is the canonical closure ledger and the
619619
README mirror was repeatedly drifting out of sync. The
620620
framework section 10.2 retirement procedure was followed:

SAFETY_INVARIANTS.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,13 @@ The nine steps:
282282
no cache exists)
283283
5. Re-canonicalize the manifest (RFC 8785; Invariant 3)
284284
6. Verify Sigstore signature against the canonical bytes
285-
(`CASM-V-030..034` by failure mode)
285+
(`CASM-V-030..034` by failure mode); enforce the
286+
configured Identity policy (`CASM-V-032` on identity
287+
mismatch; `CASM-V-035` if no `--expected-identity` and
288+
no explicit `--allow-any-identity` -- the default refuse-
289+
without-policy state); on failed identity extraction from
290+
the signing certificate raise `CASM-V-036` rather than
291+
returning a string sentinel
286292
7. Compare `module_root_hash` to the on-disk module
287293
(`CASM-V-040` on mismatch)
288294
8. Compare `public_surface.names` to the live extraction;

SECURITY.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,14 @@ residual disclosures apply to any consumer running
7676
not in the lint.
7777
2. **Typosquatting at the publish boundary.** Sigstore proves
7878
"identity X signed bytes Y at time T", not "identity X is
79-
the legitimate maintainer". Relying Parties must pin both
80-
the package name and the expected signing identity.
79+
the legitimate maintainer". Relying Parties MUST configure
80+
`--expected-identity <pattern>` for both the Python
81+
verifier (v0.11.2+) and the Rust verifier (v0.11.0+); the
82+
default `CASM-V-035` refuse-without-policy ensures
83+
verification fails closed when no policy is configured.
84+
Use of `--allow-any-identity` is the explicit opt-in to
85+
`UnsafeNoOp` policy; presence in CI logs is itself a
86+
Sulayman-Naml ADVISORY signal per Phase G11.A Strategy 7.
8187
3. **Rekor entry queryability and privacy.** The public Rekor
8288
log publishes manifest hashes and public-surface name lists
8389
unencrypted. Confidential codebases should sign to the

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "furqan-lint"
7-
version = "0.11.1"
7+
version = "0.11.2"
88
description = "Structural-honesty checks for Python, powered by Furqan"
99
readme = "README.md"
1010
requires-python = ">=3.10"

scripts/backfill_github_releases.py

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
#!/usr/bin/env python3
2-
"""Phase G10.5 (al-Mubin) T03: one-time backfill of GitHub Release
3-
objects for v0.8.2 through v0.11.0.
2+
"""Phase G10.5 (al-Mubin) T03 + Phase G11.0.1 (at-Tawbah) T08:
3+
one-time backfill of GitHub Release objects for v0.8.2 through v0.11.0.
44
55
Run manually after Phase G10.5 T01 + T02 land. After this script
66
runs against the upstream repository, finding F1 (12 historical
77
versions tagged on origin but absent from the GitHub Releases UI)
88
closes.
99
10+
Phase G11.0.1 (at-Tawbah) T08 / F18 closure: this script REQUIRES
11+
an explicit ``--dry-run`` (preview only, no API mutation) or
12+
``--apply`` (real ``gh release create`` calls) flag. The default
13+
no-flag invocation now exits 1 with usage text rather than silently
14+
mutating live GitHub state. The Round 28 audit observed the
15+
runbook's ``--dry-run`` invocation creating 12 Release objects
16+
because the pre-v0.11.2 script silently no-op'd ``--dry-run``;
17+
v0.11.2 closes that structural-honesty gap loudly.
18+
19+
Phase G11.0.1 T08 / F19 incidental closure: the version filter
20+
now excludes v0.11.1 and later because release.yml's T02 step
21+
(added in Phase G10.5 al-Mubin) creates Release objects on tag
22+
push. Backfilling them would race the workflow.
23+
1024
The script:
1125
1226
1. Reads CHANGELOG.md and enumerates every ``## [X.Y.Z]`` header.
@@ -37,16 +51,14 @@
3751

3852
from __future__ import annotations
3953

54+
import argparse
4055
import re
4156
import subprocess
4257
import sys
4358
from pathlib import Path
4459

45-
4660
_HISTORICAL_UNTAGGED_VERSIONS = frozenset({"0.2.0", "0.7.0"})
47-
_PRE_CHANGELOG_FORMAT_VERSIONS = frozenset(
48-
{"0.7.3", "0.8.0", "0.8.1"}
49-
)
61+
_PRE_CHANGELOG_FORMAT_VERSIONS = frozenset({"0.7.3", "0.8.0", "0.8.1"})
5062
# v0.1.0 already has a Release object on the upstream repo and
5163
# is excluded from this backfill so the script does not attempt
5264
# to recreate it.
@@ -58,6 +70,11 @@
5870
# release.yml + Trusted-Publishing era and are not eligible
5971
# for retroactive Release-object backfill via this tool.
6072
_BACKFILL_FLOOR = (0, 8, 2)
73+
# Phase G11.0.1 T08 / F19 incidental closure: workflow-managed
74+
# release boundary. v0.11.1 was the first release whose Release
75+
# object was created by release.yml T02 (Phase G10.5 al-Mubin
76+
# ship); backfilling at v0.11.1+ would race the workflow.
77+
_WORKFLOW_MANAGED_FLOOR = (0, 11, 1)
6178

6279

6380
def _version_tuple(v: str) -> tuple[int, ...]:
@@ -82,6 +99,14 @@ def _extract_versions_from_changelog(
8299
continue
83100
if _version_tuple(v)[:3] < _BACKFILL_FLOOR:
84101
continue
102+
# Phase G11.0.1 T08 / F19 incidental closure:
103+
# workflow-managed releases (v0.11.1+) are intentionally
104+
# excluded from backfill scope. The release.yml T02 step
105+
# (added in Phase G10.5 al-Mubin) creates GitHub Release
106+
# objects on tag push for these versions; backfilling
107+
# them here would race with the workflow.
108+
if _version_tuple(v)[:3] >= _WORKFLOW_MANAGED_FLOOR:
109+
continue
85110
versions.append(v)
86111
return versions
87112

@@ -103,16 +128,11 @@ def _extract_notes(version: str) -> str:
103128
notes = proc.stdout
104129
if not notes.strip():
105130
# Per S2 calibration: empty output (NOT skeletal) -> fallback.
106-
notes = (
107-
f"Patch release. See `git log v<previous>..v{version}` "
108-
f"for changes."
109-
)
131+
notes = f"Patch release. See `git log v<previous>..v{version}` " f"for changes."
110132
return notes
111133

112134

113-
def _create_release(
114-
tag: str, notes: str
115-
) -> tuple[bool, str, str | None]:
135+
def _create_release(tag: str, notes: str) -> tuple[bool, str, str | None]:
116136
"""Run ``gh release create``; return (created_ok, status, blocked_reason).
117137
118138
``status`` is one of ``created`` / ``already_exists`` /
@@ -141,9 +161,52 @@ def _create_release(
141161
return False, "blocked", result.stderr.strip()
142162

143163

144-
def main() -> int:
164+
def _build_arg_parser() -> argparse.ArgumentParser:
165+
"""Phase G11.0.1 T08 / F18 closure: argparse with required
166+
mutually-exclusive --dry-run / --apply group.
167+
168+
No-flag invocation exits 1 with usage rather than silently
169+
no-op'ing the documented --dry-run flag and mutating live
170+
GitHub state.
171+
"""
172+
p = argparse.ArgumentParser(
173+
prog="backfill_github_releases.py",
174+
description=(
175+
"One-time backfill of GitHub Release objects for "
176+
"historical tags. REQUIRES explicit --dry-run or --apply."
177+
),
178+
epilog=(
179+
"REQUIRED: --dry-run for preview OR --apply to execute. "
180+
"No silent-execute; the substrate's surface claim must "
181+
"match its behavior."
182+
),
183+
)
184+
mode = p.add_mutually_exclusive_group(required=True)
185+
mode.add_argument(
186+
"--dry-run",
187+
action="store_true",
188+
help="Preview the backfill scope without API mutation.",
189+
)
190+
mode.add_argument(
191+
"--apply",
192+
action="store_true",
193+
help="Execute the backfill (creates real GitHub Release objects).",
194+
)
195+
return p
196+
197+
198+
def main(argv: list[str] | None = None) -> int:
199+
parser = _build_arg_parser()
200+
args = parser.parse_args(argv)
145201
versions = _extract_versions_from_changelog()
146-
print(f"Backfilling {len(versions)} versions: {versions}")
202+
if args.dry_run:
203+
print(f"DRY-RUN: would backfill {len(versions)} versions:")
204+
for v in versions:
205+
print(f" v{v}")
206+
print("\nNo API calls executed. Pass --apply to execute.")
207+
return 0
208+
# args.apply is True (mutually exclusive with --dry-run guarantees this).
209+
print(f"APPLYING: backfilling {len(versions)} versions: {versions}")
147210
blocked: list[tuple[str, str]] = []
148211
created: list[str] = []
149212
skipped: list[str] = []
@@ -170,8 +233,7 @@ def main() -> int:
170233
)
171234
if blocked:
172235
print(
173-
"\nBLOCKED versions (record in "
174-
"docs/release-checklist.md Appendix A):",
236+
"\nBLOCKED versions (record in " "docs/release-checklist.md Appendix A):",
175237
file=sys.stderr,
176238
)
177239
for tag, reason in blocked:

0 commit comments

Comments
 (0)