Skip to content

Commit 8f59cf7

Browse files
author
gHashTag
committed
fix(numeric): add catalog count-invariant CI gate (SSOT==fresh-regen) + errata + P3109 docs
Follow-up to #1064. Adds CI guards and docs without touching gen/ (L2 GENERATION: gen/ is derived, never hand-committed). The count-gate regenerates fresh against the SSOT and enforces SSOT == fresh codegen == 83. Paper-declared 84 -> non-fatal WARN pointing at the errata. - tools/check_catalog_count.py + .github/workflows/catalog-count-gate.yml - ERRATA_2026-06-14.md: arXiv:2606.09686 declares 84; canonical live = 83 - docs/: P3109 4-param cross-walk (arXiv:2606.04028), kappa FP8 E4M3, positioning Closes #1079
1 parent 15080b4 commit 8f59cf7

15 files changed

Lines changed: 1351 additions & 7 deletions
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Catalog Count Invariant (CI-01)
2+
3+
# Kills the count-drift bug class. Enforces SSOT == fresh regenerated codegen
4+
# on every PR touching the numeric catalog or its codegen path. The canonical
5+
# number is the count of `// CATALOG:` lines in the SSOT; codegen run fresh
6+
# against it must reproduce that number exactly (catches the parser-bug class
7+
# that silently dropped formula-bias rows). Paper-count divergence
8+
# (arXiv:2606.09686 says 84; SSOT canonical is 83) is surfaced as a WARN that
9+
# requires an erratum, not a silent edit.
10+
#
11+
# NOTE: per the repo constitution (L2 GENERATION), gen/ is DERIVED and is never
12+
# hand-committed in a PR. This gate therefore regenerates into a temp dir and
13+
# checks that against the SSOT; it does NOT diff committed gen/.
14+
15+
on:
16+
pull_request:
17+
branches: [master]
18+
paths:
19+
- "specs/numeric/formats_catalog.t27"
20+
- "tools/gen_formats_catalog.py"
21+
- "tools/check_catalog_count.py"
22+
- ".github/workflows/catalog-count-gate.yml"
23+
workflow_dispatch:
24+
25+
jobs:
26+
count-invariant:
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- uses: actions/setup-python@v5
32+
with:
33+
python-version: "3.12"
34+
35+
- name: Enforce SSOT == fresh regen (canonical count)
36+
run: |
37+
python3 tools/check_catalog_count.py
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# WP-18 conformance-corpus integrity gate (CI).
2+
#
3+
# Sibling to catalog-count-gate.yml: that gate locks the SSOT catalog COUNT (==83);
4+
# this gate locks the conformance CORPUS against silent drift and undisclosed error.
5+
# Runs on every push / PR touching the SSOT, the vector corpus, or the gate itself.
6+
#
7+
# stdlib-only Python, no third-party deps, no network. The self-test runs FIRST and
8+
# must prove the gate is still falsifiable (every check FAIL-reachable) before the gate
9+
# is trusted against the live corpus.
10+
name: conformance-integrity-gate
11+
12+
on:
13+
push:
14+
paths:
15+
- "specs/numeric/formats_catalog.t27"
16+
- "conformance/vectors/**"
17+
- "tools/wp18_conformance_gate.py"
18+
- "tools/wp18_selftest_gate.py"
19+
- ".github/workflows/conformance-integrity-gate.yml"
20+
pull_request:
21+
paths:
22+
- "specs/numeric/formats_catalog.t27"
23+
- "conformance/vectors/**"
24+
- "tools/wp18_conformance_gate.py"
25+
- "tools/wp18_selftest_gate.py"
26+
- ".github/workflows/conformance-integrity-gate.yml"
27+
workflow_dispatch: {}
28+
29+
permissions:
30+
contents: read
31+
32+
jobs:
33+
integrity-gate:
34+
runs-on: ubuntu-latest
35+
steps:
36+
- name: Checkout
37+
uses: actions/checkout@v4
38+
39+
- name: Set up Python
40+
uses: actions/setup-python@v5
41+
with:
42+
python-version: "3.11"
43+
44+
- name: Self-test (gate must be falsifiable)
45+
# If any planted mutant is NOT killed, the gate is not trustworthy -> fail here.
46+
working-directory: tools
47+
run: python3 wp18_selftest_gate.py
48+
49+
- name: Run integrity gate against live corpus
50+
run: |
51+
python3 tools/wp18_conformance_gate.py \
52+
--ssot specs/numeric/formats_catalog.t27 \
53+
--vectors conformance/vectors \
54+
--allowlist conformance/vectors/abs_error_allowlist.json \
55+
--json wp18_report.json
56+
57+
- name: Upload integrity report
58+
if: always()
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: wp18-conformance-report
62+
path: wp18_report.json
63+
if-no-files-found: warn

ERRATA_2026-06-14.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Erratum to arXiv:2606.09686 (Catalog Count Reconciliation)
2+
3+
**Paper:** "An 84-Format Numeric Catalog with Bit-Exact Conformance Vectors:
4+
A Vendor-Neutral Reference for FP8, BF16, MXFP4, and Microscaling Formats"
5+
(arXiv:2606.09686v1, 8 Jun 2026).
6+
7+
**Date of erratum:** 2026-06-14
8+
**Canonical source of truth:** `specs/numeric/formats_catalog.t27` in
9+
`github.com/gHashTag/t27` (verified live by fresh clone, HEAD `16042f4`).
10+
11+
---
12+
13+
## Summary
14+
15+
The paper title and Table 1 state the catalog contains **exactly 84 formats**
16+
in 13 clusters. The live single-source-of-truth (SSOT) catalog contains
17+
**83 formats** in 13 clusters. This erratum reconciles the divergence honestly,
18+
states the canonical number, and records the CI invariant now added to prevent
19+
recurrence. No conformance pack, SHA-256, or bit-exact vector is affected; the
20+
divergence is purely in the catalog row count, not in any shipped vector.
21+
22+
**Canonical count going forward: 83.** The number 84 in the paper is superseded.
23+
24+
## What diverged, and why (four numbers, one cause)
25+
26+
At the time the paper was finalized, four artifacts carried different counts:
27+
28+
| Source | Count | Status |
29+
|---|---|---|
30+
| arXiv:2606.09686 Table 1 / title | 84 | aspirational; written against an earlier SSOT snapshot |
31+
| SSOT `formats_catalog.t27` (HEAD `16042f4`) | **83** | **canonical, live** |
32+
| codegen fresh re-run (this date) | 83 | now matches SSOT (parser fix verified) |
33+
| committed `gen/numeric/*` (pre-fix) | 77 | stale; codegen had not been re-run after PR #1051 |
34+
35+
Root causes, now all resolved:
36+
37+
1. **Paper snapshot lag.** The paper's Table 1 was built against a catalog
38+
state that split the Microscaling cluster into 5 rows (MXFP6 as two rows
39+
E2M3 + E3M2, plus a standalone E8M0 scale) and listed 16 GoldenFloat rungs.
40+
The live SSOT collapses MXFP6 to a single `mxfp6` row (3 Microscaling rows:
41+
mxfp8, mxfp6, mxfp4) and has since expanded GoldenFloat to 22 rungs
42+
(PR #1051 added gf10/gf14/gf48/gf96/gf512/gf1024 and others).
43+
44+
2. **Stale committed codegen.** After PR #1051 expanded the SSOT, the generated
45+
artifacts in `gen/numeric/` were not regenerated, so they remained at the
46+
pre-expansion count of 77. (An earlier parser limitation that dropped the
47+
formula-biased rungs gf512/gf1024 -- whose bias is `2^194-1` / `2^390-1`,
48+
exceeding int64 -- has been fixed: `parse_bias` now carries these as a
49+
formula string with an int64-overflow sentinel, so codegen no longer drops
50+
them. Fresh codegen yields 83.)
51+
52+
## Canonical cluster breakdown (SSOT, 83 formats, 13 clusters)
53+
54+
| Cluster | Count |
55+
|---|---|
56+
| GoldenFloat | 22 |
57+
| HistoricalVendor | 10 |
58+
| PositUnumIII | 8 |
59+
| IntegerFixed | 8 |
60+
| MlLowPrecision | 7 |
61+
| Ieee754Binary | 5 |
62+
| Theoretical | 4 |
63+
| Lns | 4 |
64+
| CompressionTrick | 4 |
65+
| Microscaling | 3 |
66+
| Ieee754Decimal | 3 |
67+
| ExtendedFloat | 3 |
68+
| QuantTuned | 2 |
69+
| **TOTAL** | **83** |
70+
71+
All 83 IDs are unique (verified). No duplicate or malformed rows.
72+
73+
## Delta vs the paper's Table 1 (the -1 net)
74+
75+
- **GoldenFloat:** paper 16 -> SSOT 22 (+6: gf10, gf14, gf48, gf96, gf512,
76+
gf1024 added by PR #1051).
77+
- **Microscaling/OCP MX:** paper 5 -> SSOT 3 (-2: MXFP6 is one row, not split
78+
E2M3/E3M2; E8M0 block scale is represented in the conformance packs, not as a
79+
standalone catalog row).
80+
- Net effect against the paper's other clusters yields the canonical 83.
81+
82+
The paper's 84 is therefore an over-count of 1 relative to the current SSOT,
83+
arising from a different Microscaling row-split convention combined with a
84+
later GoldenFloat expansion. Neither choice is "wrong"; the SSOT convention is
85+
the one we can ship end-to-end with bit-exact provenance, so it is canonical.
86+
87+
## Corrective actions taken
88+
89+
1. Regenerated all 16 `gen/numeric/*` artifacts from the SSOT -> all now 83.
90+
2. Added `tools/check_catalog_count.py`: a CI invariant enforcing
91+
`SSOT count == regenerated gen count == committed gen count`, and surfacing
92+
any paper-count divergence as an explicit erratum reminder (not a silent
93+
edit).
94+
3. Added `.github/workflows/catalog-count-gate.yml` (CI-01): runs the invariant
95+
on every PR touching the catalog, codegen, or gen artifacts; also fails if
96+
committed gen artifacts are stale relative to a fresh codegen.
97+
98+
## Recommended paper-side action
99+
100+
Update arXiv:2606.09686 with a v2 or a published erratum changing all "84"
101+
references to "83", citing this reconciliation. Until v2 is posted, the
102+
abstract's "84" should be read as superseded by the canonical SSOT count of 83.
103+
The conformance packs, SHA-256 anchors, and the phi^2 + 1/phi^2 = 3 anchor
104+
vector are unaffected.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"fixture": "fp8_e5m2_pos_inf_special_roundtrip",
3+
"kind": "special_value_row",
4+
"expectation": "Check D2 MUST pass: +inf encodes and decodes to +inf; abs_error is a placeholder, not an arithmetic error; exempt from D and E.",
5+
"pack_id": "fp8_e5m2",
6+
"row": {
7+
"name": "pos_inf",
8+
"input_f64": "Inf",
9+
"input_f64_hex": "0x7FF0000000000000",
10+
"fp8_bits_hex": "0x7C",
11+
"fp8_bits_int": 124,
12+
"decoded_f64": "Inf",
13+
"decoded_f64_hex": "0x7FF0000000000000",
14+
"abs_error": 0.0,
15+
"category": "inf"
16+
},
17+
"severity": "none",
18+
"note": "Same-class round-trip inf->inf. The v1 gate wrongly flagged this as a nan-mismatch (gate handling gap); v2 D2 validates it correctly."
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"fixture": "gf16_phi_mislabeled_abs_error",
3+
"kind": "finite_value_row",
4+
"expectation": "Check D MUST flag: stored abs_error 0.0 but abs(decoded-input) is nonzero.",
5+
"pack_id": "gf16",
6+
"row": {
7+
"name": "phi",
8+
"input_f64": 1.618033988749895,
9+
"input_f64_hex": "0x3FF9E3779B97F4A8",
10+
"gf16_bits_hex": "0x3F3C",
11+
"gf16_bits_int": 16188,
12+
"decoded_f64": 1.6171875,
13+
"decoded_f64_hex": "0x3FF9E00000000000",
14+
"abs_error": 0.0,
15+
"category": "phi_anchor"
16+
},
17+
"rederived_abs_error": 0.0008464887498949025,
18+
"severity": "Risk",
19+
"note": "gf16 decode of phi rounds to 1.6171875; the 8.46e-04 error was stored as 0.0. Anchor identity phi^2+1/phi^2=3 still holds in f64; only the per-row abs_error label is wrong."
20+
}

conformance/vectors/INDEX_all_formats.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@
321321
"file": "gf16_conformance_v0.json",
322322
"kind": "bitexact",
323323
"source": "hand-curated (pre-existing)",
324-
"sha256": "7aea5b9e86ea71a54ae0c1601cea13e2d90d95fecaf2ae969eac1349cf7a2b42"
324+
"sha256": "d1c0eb5bd66247b3c5db9a00a95e29cf4359653aec56f2f9e6827f96898d1509"
325325
},
326326
{
327327
"id": "gf20",
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"schema": "wp18-abs-error-allowlist/v1",
3+
"note": "Explicit, auditable allow-list of bit-exact-pack rows that carry a NONZERO abs_error for an honest, documented reason. Every entry is a row where the round-trip is CORRECT but the input is not exactly representable (not_on_grid) or saturates (overflow_to_inf). The gate fails on any nonzero abs_error NOT listed here. Never add an entry to silence a real error; each must be a genuinely-disclosed case with a fixture.",
4+
"allow": [
5+
{
6+
"pack_id": "bfloat16",
7+
"row_name": "pos_0p1",
8+
"reason": "not_on_grid",
9+
"detail": "0.1 is not exactly representable in bf16; decoded_f64=0.10009765625, abs_error~9.77e-05 is the true nearest-representable error. Honest, expected."
10+
},
11+
{
12+
"pack_id": "gf16",
13+
"row_name": "overflow_to_inf",
14+
"reason": "overflow_to_inf",
15+
"detail": "input 1e40 exceeds the gf16 dynamic range and saturates to +inf; abs_error=inf is the honest record of an out-of-range input, not an encode/decode bug."
16+
},
17+
{
18+
"pack_id": "gf16",
19+
"row_name": "phi",
20+
"reason": "not_on_grid",
21+
"detail": "phi is not exactly representable on the gf16 grid; round-trip is correct, abs_error=0.000846489 is the true nearest-representable error (WP-18 F-1 correction)."
22+
},
23+
{
24+
"pack_id": "gf16",
25+
"row_name": "inv_phi",
26+
"reason": "not_on_grid",
27+
"detail": "inv_phi is not exactly representable on the gf16 grid; round-trip is correct, abs_error=0.000130074 is the true nearest-representable error (WP-18 F-1 correction)."
28+
},
29+
{
30+
"pack_id": "gf16",
31+
"row_name": "phi_squared",
32+
"reason": "not_on_grid",
33+
"detail": "phi_squared is not exactly representable on the gf16 grid; round-trip is correct, abs_error=0.000846489 is the true nearest-representable error (WP-18 F-1 correction)."
34+
},
35+
{
36+
"pack_id": "gf16",
37+
"row_name": "inv_phi_sq",
38+
"reason": "not_on_grid",
39+
"detail": "inv_phi_sq is not exactly representable on the gf16 grid; round-trip is correct, abs_error=0.000130074 is the true nearest-representable error (WP-18 F-1 correction)."
40+
},
41+
{
42+
"pack_id": "gf16",
43+
"row_name": "e",
44+
"reason": "not_on_grid",
45+
"detail": "e is not exactly representable on the gf16 grid; round-trip is correct, abs_error=0.000468172 is the true nearest-representable error (WP-18 F-1 correction)."
46+
},
47+
{
48+
"pack_id": "gf16",
49+
"row_name": "pi",
50+
"reason": "not_on_grid",
51+
"detail": "pi is not exactly representable on the gf16 grid; round-trip is correct, abs_error=0.000967654 is the true nearest-representable error (WP-18 F-1 correction)."
52+
}
53+
]
54+
}

conformance/vectors/gf16_conformance_v0.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"gf16_bits_int": 16188,
8888
"decoded_f64": 1.6171875,
8989
"decoded_f64_hex": "0x3FF9E00000000000",
90-
"abs_error": 0.0,
90+
"abs_error": 0.0008464887498949025,
9191
"category": "phi_anchor"
9292
},
9393
{
@@ -98,7 +98,7 @@
9898
"gf16_bits_int": 15481,
9999
"decoded_f64": 0.6181640625,
100100
"decoded_f64_hex": "0x3FE3C80000000000",
101-
"abs_error": 0.0,
101+
"abs_error": 0.0001300737501052085,
102102
"category": "phi_anchor"
103103
},
104104
{
@@ -109,7 +109,7 @@
109109
"gf16_bits_int": 16542,
110110
"decoded_f64": 2.6171875,
111111
"decoded_f64_hex": "0x4004F00000000000",
112-
"abs_error": 0.0,
112+
"abs_error": 0.0008464887498949025,
113113
"category": "phi_anchor"
114114
},
115115
{
@@ -120,7 +120,7 @@
120120
"gf16_bits_int": 15118,
121121
"decoded_f64": 0.3818359375,
122122
"decoded_f64_hex": "0x3FD8700000000000",
123-
"abs_error": 0.0,
123+
"abs_error": 0.00013007375010509747,
124124
"category": "phi_anchor"
125125
},
126126
{
@@ -230,7 +230,7 @@
230230
"gf16_bits_int": 16568,
231231
"decoded_f64": 2.71875,
232232
"decoded_f64_hex": "0x4005C00000000000",
233-
"abs_error": 0.0,
233+
"abs_error": 0.0004681715409549092,
234234
"category": "normal"
235235
},
236236
{
@@ -241,7 +241,7 @@
241241
"gf16_bits_int": 16676,
242242
"decoded_f64": 3.140625,
243243
"decoded_f64_hex": "0x4009200000000000",
244-
"abs_error": 0.0,
244+
"abs_error": 0.000967653589793116,
245245
"category": "normal"
246246
}
247247
]

0 commit comments

Comments
 (0)