gcf-python 2.5.0__py3-none-any.whl → 2.5.2__py3-none-any.whl

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.
gcf/decode.py CHANGED
@@ -45,6 +45,8 @@ def decode(input_text: str) -> Payload:
45
45
  sym_by_id: dict[int, Symbol] = {}
46
46
  current_distance = 0
47
47
  in_edges = False
48
+ declared_edges = -1
49
+ edges_declared = False
48
50
 
49
51
  for line in lines[1:]:
50
52
  line = line.rstrip("\r")
@@ -58,13 +60,29 @@ def decode(input_text: str) -> Payload:
58
60
  # Group header.
59
61
  if line.startswith("## "):
60
62
  group = line[3:]
61
- # Strip bracket suffix: "edges [200]" -> "edges"
63
+ # Strip bracket suffix: "edges [200]" -> "edges", capturing the
64
+ # declared count so it can be enforced per Section 13.
65
+ declared_count = -1
62
66
  bracket_idx = group.find(" [")
63
67
  if bracket_idx >= 0:
68
+ bracket = group[bracket_idx + 2:]
64
69
  group = group[:bracket_idx]
70
+ end = bracket.find("]")
71
+ if end >= 0:
72
+ cnt_str = bracket[:end]
73
+ if cnt_str != "?": # "[?]" is a streaming deferred count (Section 8)
74
+ try:
75
+ declared_count = int(cnt_str)
76
+ except ValueError:
77
+ raise DecodeError(
78
+ f"count_mismatch: invalid section count {cnt_str!r}"
79
+ )
65
80
  if is_delta and group not in valid_delta_sections:
66
81
  raise DecodeError(f"malformed_delta: invalid delta section {group!r}")
67
82
  in_edges = group == "edges"
83
+ if in_edges and declared_count >= 0:
84
+ declared_edges = declared_count
85
+ edges_declared = True
68
86
  if not in_edges:
69
87
  if group == "targets":
70
88
  current_distance = 0
@@ -91,6 +109,13 @@ def decode(input_text: str) -> Payload:
91
109
  symbols.append(sym)
92
110
  sym_by_id[sym_id] = sym
93
111
 
112
+ # Section 13: a declared [N] section count MUST match the actual item count.
113
+ # The graph edges section is the graph profile's only [N]-bearing section.
114
+ if edges_declared and len(p.edges) != declared_edges:
115
+ raise DecodeError(
116
+ f"count_mismatch: declared {declared_edges} edges, got {len(p.edges)}"
117
+ )
118
+
94
119
  p.symbols = symbols
95
120
  return p
96
121
 
gcf/decode_generic.py CHANGED
@@ -91,7 +91,14 @@ def decode_generic(input_text: str) -> Any:
91
91
  return parse_scalar(first[1:])
92
92
 
93
93
  if first.startswith("## ["):
94
- arr, _ = _parse_array_from_header(content_lines, 0, 0, first[3:])
94
+ arr, consumed = _parse_array_from_header(content_lines, 0, 0, first[3:])
95
+ # A root array or keyed map spans the whole document, so any structural line
96
+ # past the consumed rows is a surplus item, not sibling content. The row loop
97
+ # stops at the declared count, so the count assert only catches the deficit
98
+ # case; surplus is caught here (SPEC Section 13: a mismatch, fewer OR more
99
+ # items than declared, is an error).
100
+ if consumed < len(content_lines):
101
+ raise ValueError("count_mismatch: declared count is fewer than the rows present")
95
102
  return arr
96
103
 
97
104
  result: dict[str, Any] = {}
gcf/generic_delta.py CHANGED
@@ -299,16 +299,23 @@ def decode_generic_full(text: str) -> tuple[GenericSet, str]:
299
299
  while i < len(lines):
300
300
  line = lines[i]
301
301
  if not line.startswith("## "):
302
- i += 1
303
- continue
302
+ # Only blank lines, comments, and the ##! summary trailer are valid
303
+ # outside a section; any other line is a surplus row past a declared
304
+ # section count (Section 13).
305
+ if line == "" or line.startswith("# ") or line.startswith("##! "):
306
+ i += 1
307
+ continue
308
+ raise ValueError(
309
+ f"count_mismatch: unexpected content after declared section rows: {line!r}"
310
+ )
304
311
  name, count, fields, key_field = _parse_section_header(line[3:])
305
312
  s.name, s.fields = name, fields
306
313
  if not s.key:
307
314
  s.key = key_field
308
315
  i += 1
309
- for _ in range(count):
310
- if i >= len(lines):
311
- raise ValueError("delta_invalid: fewer rows than declared count")
316
+ for j in range(count):
317
+ if i >= len(lines) or lines[i].startswith("## "):
318
+ raise ValueError(f"count_mismatch: declared {count} rows, got {j}")
312
319
  s.rows.append(_parse_row(lines[i], fields))
313
320
  i += 1
314
321
  return s, hdr.get("pack_root", "")
@@ -335,8 +342,15 @@ def decode_generic_delta(text: str) -> GenericDeltaPayload:
335
342
  while i < len(lines):
336
343
  line = lines[i]
337
344
  if not line.startswith("## "):
338
- i += 1
339
- continue
345
+ # Only blank lines, comments, and the ##! summary trailer are valid
346
+ # outside a section; any other line is a surplus row past a declared
347
+ # section count (Section 13).
348
+ if line == "" or line.startswith("# ") or line.startswith("##! "):
349
+ i += 1
350
+ continue
351
+ raise ValueError(
352
+ f"count_mismatch: unexpected content after declared section rows: {line!r}"
353
+ )
340
354
  name, count, fields, key_field = _parse_section_header(line[3:])
341
355
  if not d.key and key_field:
342
356
  d.key = key_field
@@ -345,9 +359,9 @@ def decode_generic_delta(text: str) -> GenericDeltaPayload:
345
359
  i += 1
346
360
  if name in ("added", "changed"):
347
361
  rows = []
348
- for _ in range(count):
349
- if i >= len(lines):
350
- raise ValueError(f"delta_invalid: fewer rows than declared count in ## {name}")
362
+ for j in range(count):
363
+ if i >= len(lines) or lines[i].startswith("## "):
364
+ raise ValueError(f"count_mismatch: declared {count} rows in ## {name}, got {j}")
351
365
  rows.append(_parse_row(lines[i], fields))
352
366
  i += 1
353
367
  if name == "added":
@@ -355,9 +369,9 @@ def decode_generic_delta(text: str) -> GenericDeltaPayload:
355
369
  else:
356
370
  d.changed = rows
357
371
  elif name == "removed":
358
- for _ in range(count):
359
- if i >= len(lines):
360
- raise ValueError("delta_invalid: fewer identities than declared count in ## removed")
372
+ for j in range(count):
373
+ if i >= len(lines) or lines[i].startswith("## "):
374
+ raise ValueError(f"count_mismatch: declared {count} identities in ## removed, got {j}")
361
375
  d.removed.append(parse_scalar(lines[i], True))
362
376
  i += 1
363
377
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gcf-python
3
- Version: 2.5.0
3
+ Version: 2.5.2
4
4
  Summary: The AI-native wire format for structured data. 50-92% fewer tokens than JSON, with multi-turn delta encoding for agent loops. 100% comprehension on every frontier model. Zero dependencies.
5
5
  Project-URL: Homepage, https://github.com/blackwell-systems/gcf-python
6
6
  Project-URL: Documentation, https://gcformat.com/
@@ -304,7 +304,7 @@ GCF wins 15/16 datasets on the expanded [token efficiency benchmark](https://git
304
304
 
305
305
  **Zero runtime dependencies. Permanently.** All six implementations depend only on their language's standard library. No transitive dependencies. No supply chain risk. This is a permanent commitment: GCF will never take on external runtime dependencies. MIT licensed. All implementations support both generic profile (`encodeGeneric`) and graph profile (`encode`). CLI included in all 6 languages.
306
306
 
307
- **Specification:** [SPEC v3.4.1 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 204 conformance fixtures, 43,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. All implementations at v2.4.0+ (Go v1.5.0). Cross-language 6x6 matrix verified.
307
+ **Specification:** [SPEC v3.5.0 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 264 conformance fixtures, 43,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. All implementations at v2.5.1+ (Go v1.6.1, Swift v2.6.0). Cross-language 6x6 matrix verified.
308
308
 
309
309
  ## Adopted by
310
310
 
@@ -2,12 +2,12 @@ gcf/__init__.py,sha256=dZkdcjbG-dcolg_7TBROjRq3opftDGU423ankMIOhyA,2616
2
2
  gcf/__main__.py,sha256=EpvBz1yc8H0D5OJ1zy2tYke-kRzvudKa4DEbfeW14ao,71
3
3
  gcf/cli.py,sha256=UEe1CAZn-rKGNIo_ap8-oez3ucl6DSRbsdv6RDnzygY,5256
4
4
  gcf/constants.py,sha256=cmZ8YJSOB0im_eyfN8v4UvrLpBC6Fuf4cfcKZGbutxY,638
5
- gcf/decode.py,sha256=nD8bXYhoeHQQ3LCeAJQOAgFuob-V_6us4mcBYtL_bBc,5978
6
- gcf/decode_generic.py,sha256=hbVO3cHzhwcFh08ACVvyAFFNk9rOrufFD8SCot_6W_g,29984
5
+ gcf/decode.py,sha256=TP58_7UBhfeI0o9zpNgtQvAnLQGhNt0RaUjku7Ke75A,7162
6
+ gcf/decode_generic.py,sha256=OTNhryeAFfE5izpP6dD0DtBzcC_Jht1-vLg51gjnZ28,30510
7
7
  gcf/delta.py,sha256=oviQ9WsDRYXPXE8bw6SFutmzMRIvu-U_Zzw-39Nd5Ic,8053
8
8
  gcf/encode.py,sha256=OYGDyF3oP2I8Y2PrPL_5yDVURqB0mghtqlOGyDEd-Fs,4081
9
9
  gcf/generic.py,sha256=1c53utF2GcD8lS_wks2opQw08hMz4KGL-7C8g7JB5bA,21577
10
- gcf/generic_delta.py,sha256=GU7CNumz3PQhC0Xs5pO30Nb3ACvSorHEQMu-4kazpFM,17838
10
+ gcf/generic_delta.py,sha256=5KSUT_QUeH7OILLWfu9JveaXRSrqRIi3c6E8WjlgC4Q,18785
11
11
  gcf/keyed_map.py,sha256=qCwhehXB1FQ2iYCPhcvXOqGwrVqdXhGYZGxv56CSUgc,3644
12
12
  gcf/packroot.py,sha256=0rZY7TEVcLzA8XhoIOc6_g9lbRCn-MUr54YTRVhzsO4,2019
13
13
  gcf/scalar.py,sha256=NMf-ZJzTOGk1JLvydqYPRxUH80u2Sr_bonwRHXTYd5U,9856
@@ -15,8 +15,8 @@ gcf/session.py,sha256=rPqR4xsHKpi_G37t1ODwkTUdHhVAGa3Uig8F60h34Ms,5335
15
15
  gcf/stream.py,sha256=3eyzmLMZPyEGC1MS9pujxio2x_t7rCL-eNuJ4DjYkaA,5938
16
16
  gcf/stream_generic.py,sha256=h2mJ-jI-c1H2L0uoQVhrvDc7uJEcOwS1MB51k4SCPNM,5775
17
17
  gcf/types.py,sha256=AWm-LQoSqLHAYtEjcAxWQZqJ4JXqNreLUKO2mJFgNMA,1465
18
- gcf_python-2.5.0.dist-info/METADATA,sha256=M_7Wxzj5QZ-lR4YbkrR9qHQdR4HjFaeaj5sa-rK_BAc,16068
19
- gcf_python-2.5.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
20
- gcf_python-2.5.0.dist-info/entry_points.txt,sha256=aFT6gqlkh8iGfM8cblE-LUMxHH08_v71IIoZtDdRIVA,37
21
- gcf_python-2.5.0.dist-info/licenses/LICENSE,sha256=2Fit9wnaIe--RMSAgyQqxC5hfZTyZqn4fIdBtp9qPDw,1072
22
- gcf_python-2.5.0.dist-info/RECORD,,
18
+ gcf_python-2.5.2.dist-info/METADATA,sha256=Cnwh-CZYYXbQqcQyp8rKTHq5U_MnFzXZ1O4muLdaTss,16082
19
+ gcf_python-2.5.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
20
+ gcf_python-2.5.2.dist-info/entry_points.txt,sha256=aFT6gqlkh8iGfM8cblE-LUMxHH08_v71IIoZtDdRIVA,37
21
+ gcf_python-2.5.2.dist-info/licenses/LICENSE,sha256=2Fit9wnaIe--RMSAgyQqxC5hfZTyZqn4fIdBtp9qPDw,1072
22
+ gcf_python-2.5.2.dist-info/RECORD,,