laskoviymishka commented on code in PR #9:
URL: 
https://github.com/apache/iceberg-verification/pull/9#discussion_r4036951278


##########
dev/check-license:
##########
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+acquire_rat_jar () {
+
+  
URL="https://repo.maven.apache.org/maven2/org/apache/rat/apache-rat/${RAT_VERSION}/apache-rat-${RAT_VERSION}.jar";
+
+  JAR="$rat_jar"
+
+  # Download rat launch jar if it hasn't been downloaded yet
+  if [ ! -f "$JAR" ]; then
+    # Download
+    printf "Attempting to fetch rat\n"
+    JAR_DL="${JAR}.part"
+    if [ $(command -v curl) ]; then
+      curl -L --silent "${URL}" > "$JAR_DL" && mv "$JAR_DL" "$JAR"

Review Comment:
   We download the RAT jar and only check that it unzips — no checksum or 
signature. A compromised mirror or MITM could swap in a jar that then executes 
in CI. I'd verify a pinned SHA-256 (or the `.sha512`/PGP that Maven Central 
publishes) before running it. Not blocking, but cheap hardening for something 
we execute.



##########
.github/workflows/license_check.yml:
##########
@@ -0,0 +1,32 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: "Run License Check"
+on: pull_request

Review Comment:
   This one triggers only on `pull_request`, but validate-fixtures also runs on 
push to `main` — so a direct push to main skips the license check. I'd add 
`push: branches: [main]` here to match. While we're in here, validate-fixtures 
has a concurrency group that cancels superseded runs and this one doesn't; 
worth copying over.



##########
dev/check-license:
##########
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+acquire_rat_jar () {
+
+  
URL="https://repo.maven.apache.org/maven2/org/apache/rat/apache-rat/${RAT_VERSION}/apache-rat-${RAT_VERSION}.jar";
+
+  JAR="$rat_jar"
+
+  # Download rat launch jar if it hasn't been downloaded yet
+  if [ ! -f "$JAR" ]; then
+    # Download
+    printf "Attempting to fetch rat\n"
+    JAR_DL="${JAR}.part"
+    if [ $(command -v curl) ]; then
+      curl -L --silent "${URL}" > "$JAR_DL" && mv "$JAR_DL" "$JAR"
+    elif [ $(command -v wget) ]; then
+      wget --quiet ${URL} -O "$JAR_DL" && mv "$JAR_DL" "$JAR"
+    else
+      printf "You do not have curl or wget installed, please install rat 
manually.\n"
+      exit -1

Review Comment:
   `exit -1` isn't portable — exit codes are 0–255, so this lands on 255 on 
bash but isn't guaranteed and lint tools flag it. I'd use `exit 1` in both 
spots.



##########
dev/schema/cases.base.schema.json:
##########
@@ -0,0 +1,46 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema";,
+  "$id": "https://iceberg.apache.org/verification/cases.base.schema.json";,
+  "title": "Conformance cases (base)",
+  "description": "Structure shared by every table-spec surface's cases.json.",
+  "type": "object",
+  "required": ["cases"],
+  "properties": {
+    "cases": {
+      "type": "array",
+      "items": { "$ref": "#/$defs/case" }
+    }
+  },
+  "$defs": {
+    "case": {
+      "type": "object",

Review Comment:
   I'd set `"additionalProperties": false` on the case object. Right now a typo 
like `"spec-ref"` or `"decode"` validates clean — the case just silently lacks 
the field, and a mistyped `decoded` could make a fixture assert the wrong 
thing. For a schema whose whole job is catching malformed fixtures, I'd want 
the strict form here (and on the top-level object).



##########
table-spec/types/geospatial/cases.json:
##########
@@ -0,0 +1,83 @@
+{
+  "cases": [
+    {
+      "id": "geometry-crs84",
+      "valid": true,
+      "input": "geometry(OGC:CRS84)",
+      "decoded": {"type": "geometry", "crs": "OGC:CRS84"},
+      "clause": "geometry(C) with explicit CRS; the canonical serialized form 
is unquoted \"geometry(<C>)\"",
+      "spec_ref": "format/spec.md#appendix-c-json-serialization"
+    },
+    {
+      "id": "geometry-srid",
+      "valid": true,
+      "input": "geometry(srid:4326)",
+      "decoded": {"type": "geometry", "crs": "srid:4326"},
+      "clause": "geometry(C) example from Appendix C is the unquoted 
\"geometry(srid:4326)\"",
+      "spec_ref": "format/spec.md#appendix-c-json-serialization"
+    },
+    {
+      "id": "geometry-default-crs",
+      "valid": true,
+      "input": "geometry",
+      "decoded": {"type": "geometry", "crs": "OGC:CRS84"},
+      "clause": "geometry(C): if C is not specified, C is OGC:CRS84",
+      "spec_ref": "format/spec.md#primitive-types"
+    },
+    {
+      "id": "geography-crs84-spherical",
+      "valid": true,
+      "input": "geography(OGC:CRS84, spherical)",
+      "decoded": {"type": "geography", "crs": "OGC:CRS84", "algorithm": 
"spherical"},
+      "clause": "geography(C, A); the canonical serialized form is unquoted 
\"geography(<C>, <A>)\"",
+      "spec_ref": "format/spec.md#appendix-c-json-serialization"
+    },
+    {
+      "id": "geography-default",
+      "valid": true,
+      "input": "geography",
+      "decoded": {"type": "geography", "crs": "OGC:CRS84", "algorithm": 
"spherical"},
+      "clause": "geography(C, A): if not specified, C is OGC:CRS84 and A is 
spherical",

Review Comment:
   There's no case for `geography(OGC:CRS84)` — CRS given, algorithm omitted. 
The prose says both default when unspecified, but whether the algorithm can be 
omitted while the CRS is explicit reads as genuinely ambiguous to me.
   
   I'd add one case pinning it either way (`valid: true` decoding with 
`algorithm: spherical`, or `valid: false` if the algorithm is required once CRS 
is explicit), with a clause citing the sentence that resolves it. How do you 
read the spec here?



##########
table-spec/types/primitive/cases.json:
##########
@@ -0,0 +1,33 @@
+{
+  "cases": [
+    { "id": "boolean", "valid": true, "input": "boolean", "decoded": { "type": 
"boolean" }, "canonical": "boolean", "clause": "Primitive Types: boolean; 
Appendix C canonical string", "spec_ref": 
"format/spec.md#appendix-c-json-serialization" },
+    { "id": "int", "valid": true, "input": "int", "decoded": { "type": "int" 
}, "canonical": "int", "clause": "Primitive Types: int; Appendix C canonical 
string", "spec_ref": "format/spec.md#appendix-c-json-serialization" },
+    { "id": "long", "valid": true, "input": "long", "decoded": { "type": 
"long" }, "canonical": "long", "clause": "Primitive Types: long; Appendix C 
canonical string", "spec_ref": "format/spec.md#appendix-c-json-serialization" },
+    { "id": "float", "valid": true, "input": "float", "decoded": { "type": 
"float" }, "canonical": "float", "clause": "Primitive Types: float; Appendix C 
canonical string", "spec_ref": "format/spec.md#appendix-c-json-serialization" },
+    { "id": "double", "valid": true, "input": "double", "decoded": { "type": 
"double" }, "canonical": "double", "clause": "Primitive Types: double; Appendix 
C canonical string", "spec_ref": "format/spec.md#appendix-c-json-serialization" 
},
+    { "id": "date", "valid": true, "input": "date", "decoded": { "type": 
"date" }, "canonical": "date", "clause": "Primitive Types: date; Appendix C 
canonical string", "spec_ref": "format/spec.md#appendix-c-json-serialization" },
+    { "id": "time", "valid": true, "input": "time", "decoded": { "type": 
"time" }, "canonical": "time", "clause": "Primitive Types: time; Appendix C 
canonical string", "spec_ref": "format/spec.md#appendix-c-json-serialization" },
+    { "id": "timestamp", "valid": true, "input": "timestamp", "decoded": { 
"type": "timestamp" }, "canonical": "timestamp", "clause": "Primitive Types: 
timestamp; Appendix C canonical string", "spec_ref": 
"format/spec.md#appendix-c-json-serialization" },
+    { "id": "timestamptz", "valid": true, "input": "timestamptz", "decoded": { 
"type": "timestamptz" }, "canonical": "timestamptz", "clause": "Primitive 
Types: timestamptz; Appendix C canonical string", "spec_ref": 
"format/spec.md#appendix-c-json-serialization" },
+    { "id": "timestamp_ns", "valid": true, "input": "timestamp_ns", "decoded": 
{ "type": "timestamp_ns" }, "canonical": "timestamp_ns", "clause": "Primitive 
Types: timestamp_ns added in v3; Appendix C canonical string", "spec_ref": 
"format/spec.md#primitive-types" },
+    { "id": "timestamptz_ns", "valid": true, "input": "timestamptz_ns", 
"decoded": { "type": "timestamptz_ns" }, "canonical": "timestamptz_ns", 
"clause": "Primitive Types: timestamptz_ns added in v3; Appendix C canonical 
string", "spec_ref": "format/spec.md#primitive-types" },
+    { "id": "string", "valid": true, "input": "string", "decoded": { "type": 
"string" }, "canonical": "string", "clause": "Primitive Types: string; Appendix 
C canonical string", "spec_ref": "format/spec.md#appendix-c-json-serialization" 
},
+    { "id": "uuid", "valid": true, "input": "uuid", "decoded": { "type": 
"uuid" }, "canonical": "uuid", "clause": "Primitive Types: uuid; Appendix C 
canonical string", "spec_ref": "format/spec.md#appendix-c-json-serialization" },
+    { "id": "binary", "valid": true, "input": "binary", "decoded": { "type": 
"binary" }, "canonical": "binary", "clause": "Primitive Types: binary; Appendix 
C canonical string", "spec_ref": "format/spec.md#appendix-c-json-serialization" 
},
+    { "id": "unknown", "valid": true, "input": "unknown", "decoded": { "type": 
"unknown" }, "canonical": "unknown", "clause": "Primitive Types: unknown added 
in v3; Appendix C canonical string", "spec_ref": 
"format/spec.md#primitive-types" },
+    { "id": "fixed-1", "valid": true, "input": "fixed[1]", "decoded": { 
"type": "fixed", "length": 1 }, "canonical": "fixed[1]", "clause": "Appendix C: 
fixed canonical string is fixed[<L>]", "spec_ref": 
"format/spec.md#appendix-c-json-serialization" },
+    { "id": "fixed-16", "valid": true, "input": "fixed[16]", "decoded": { 
"type": "fixed", "length": 16 }, "canonical": "fixed[16]", "clause": "Appendix 
C: fixed canonical string is fixed[<L>]", "spec_ref": 
"format/spec.md#appendix-c-json-serialization" },
+    { "id": "decimal-9-2", "valid": true, "input": "decimal(9,2)", "decoded": 
{ "type": "decimal", "precision": 9, "scale": 2 }, "clause": "Appendix C: both 
decimal(9,2) and decimal(9, 2) are canonical, so no byte-exact form is pinned", 
"spec_ref": "format/spec.md#appendix-c-json-serialization" },
+    { "id": "decimal-9-2-spaced", "valid": true, "input": "decimal(9, 2)", 
"decoded": { "type": "decimal", "precision": 9, "scale": 2 }, "clause": 
"Appendix C: the spaced decimal(9, 2) form parses to the same decimal", 
"spec_ref": "format/spec.md#appendix-c-json-serialization" },

Review Comment:
   moomindani's consumer-side run reframed this one for me: `decimal(9, 2)` is 
accepted by every implementation they checked, so as written this case is inert 
— it doesn't separate a strict parser from a lenient one. The input that does 
is the broader form apache/iceberg#16798 actually added, "whitespace around 
parameters and separators": PyIceberg rejects `decimal( 9 , 2 )` / `decimal(9 
,2)` while Java accepts them, and both are conformant.
   
   That's the case I'd want on this surface (the sungwy/iceberg-testing 
prototype carried it, noting "a reader stricter than that diverges here"). But 
because both parsers are conformant on it, it can't be a plain `valid: true` — 
a MUST-accept would fail PyIceberg for behavior the spec only recommends. So 
I'd swap in the divergent `decimal( 9 , 2 )` input and give it a normative 
tier: an optional `normative_level` on the base schema (default `"must"`) with 
this case marked as the SHOULD, so runners treat it as advisory rather than a 
hard fail.
   
   I'd leave `canonical` off the decimal cases as you have it — agreed the 
pinned-spelling question is spec text, not something to settle in a fixture. 
wdyt on the normative tier?



##########
.github/workflows/validate-fixtures.yml:
##########
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: "Validate Fixtures"
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+
+concurrency:
+  group: ${{ github.repository }}-${{ github.head_ref || github.sha }}-${{ 
github.workflow }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+permissions:
+  contents: read
+
+jobs:
+  validate:
+    name: Validate fixture cases
+    runs-on: ubuntu-24.04
+    steps:
+    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+      with:
+        persist-credentials: false
+    - name: Install Python
+      uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # 
v6.3.0
+      with:
+        python-version: '3.12'
+    - name: Install validator deps
+      run: python3 -m pip install -r dev/requirements.txt
+    - name: Validate cases.json
+      run: python3 dev/validate-fixtures.py

Review Comment:
   CI only ever runs the validator against the real fixtures, which are all 
well-formed, so nothing proves the validator actually rejects a bad fixture. If 
schema enforcement broke — a logic bug, or the jsonschema bump changing the 
error path — CI stays green and the gate is silently worthless.
   
   I'd add a step that feeds a deliberately malformed cases.json (say, `valid: 
true` with no `decoded`, or a types case missing `clause`) and asserts a 
nonzero exit. Since the validator is the gate for every future contribution, 
I'd want that guard in this PR.



##########
dev/check-license:
##########
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+acquire_rat_jar () {
+
+  
URL="https://repo.maven.apache.org/maven2/org/apache/rat/apache-rat/${RAT_VERSION}/apache-rat-${RAT_VERSION}.jar";
+
+  JAR="$rat_jar"
+
+  # Download rat launch jar if it hasn't been downloaded yet
+  if [ ! -f "$JAR" ]; then
+    # Download
+    printf "Attempting to fetch rat\n"
+    JAR_DL="${JAR}.part"
+    if [ $(command -v curl) ]; then
+      curl -L --silent "${URL}" > "$JAR_DL" && mv "$JAR_DL" "$JAR"
+    elif [ $(command -v wget) ]; then
+      wget --quiet ${URL} -O "$JAR_DL" && mv "$JAR_DL" "$JAR"
+    else
+      printf "You do not have curl or wget installed, please install rat 
manually.\n"
+      exit -1
+    fi
+  fi
+
+  unzip -tq "$JAR" &> /dev/null
+  if [ $? -ne 0 ]; then
+    # We failed to download
+    rm "$JAR"
+    printf "Our attempt to download rat locally to ${JAR} failed. Please 
install rat manually.\n"
+    exit -1
+  fi
+}
+
+# Go to the project root directory
+FWDIR="$(cd "`dirname "$0"`"/..; pwd)"
+cd "$FWDIR"
+
+if test -x "$JAVA_HOME/bin/java"; then
+    declare java_cmd="$JAVA_HOME/bin/java"
+else
+    declare java_cmd=java
+fi
+
+export RAT_VERSION=0.17
+export rat_jar="$FWDIR"/lib/apache-rat-${RAT_VERSION}.jar

Review Comment:
   The closing quote ends right after `$FWDIR`, so the `/lib/...` suffix is 
unquoted and a repo path with a space (common on macOS under `~/Documents/My 
Projects/`) splits into two words and fails. I'd quote the whole path: 
`"$FWDIR/lib/apache-rat-${RAT_VERSION}.jar"`, and likewise `mkdir -p 
"$FWDIR/lib"` and the `--input-exclude-file "$FWDIR/dev/.rat-excludes"` below.



##########
table-spec/types/geospatial/cases.json:
##########
@@ -0,0 +1,83 @@
+{
+  "cases": [
+    {
+      "id": "geometry-crs84",
+      "valid": true,
+      "input": "geometry(OGC:CRS84)",
+      "decoded": {"type": "geometry", "crs": "OGC:CRS84"},
+      "clause": "geometry(C) with explicit CRS; the canonical serialized form 
is unquoted \"geometry(<C>)\"",

Review Comment:
   The clause here says the canonical form is the unquoted 
`geometry(OGC:CRS84)`, but without a `canonical` field the write direction 
never gets tested — an implementation that emits `geometry('OGC:CRS84')` or 
`GEOMETRY(OGC:CRS84)` would pass. Since the clause already pins the spelling, 
I'd add `canonical` to each explicit case: `"canonical": "geometry(OGC:CRS84)"` 
here, `"geography(OGC:CRS84, spherical)"` on the geography ones, and so on. 
Same applies to `geometry-srid` and the four geography algorithm cases below.
   
   moomindani's consumer-side run makes this concrete: PyIceberg's regex 
requires a quoted CRS, so it rejects `geometry(srid:4326)` and writes 
`geometry('srid:4326')`, while Java reads the quotes into the CRS — a silent 
bidirectional divergence this fixture catches once `canonical` pins the write 
side (they filed apache/iceberg-python#3530).



##########
dev/validate-fixtures.py:
##########
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Validate the conformance type fixtures against the JSON Schemas in 
dev/schema/.
+
+Each cases.json is validated against the base structural schema and the types 
schema
+(which requires clause and spec_ref); case ids must be globally unique.
+"""
+
+import glob
+import json
+import os
+import sys
+
+import jsonschema
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+SCHEMA_DIR = os.path.join(HERE, "schema")
+
+
+def _load_schema(name):
+    with open(os.path.join(SCHEMA_DIR, name), encoding="utf-8") as fh:
+        return json.load(fh)
+
+
+def _rel(path):
+    return path.replace(os.sep, "/")
+
+
+def _is_types_surface(path):
+    p = _rel(path)
+    return "/table-spec/types/" in p or p.startswith("table-spec/types/")
+
+
+def main():
+    root = sys.argv[1] if len(sys.argv) > 1 else "."
+    base_validator = 
jsonschema.Draft202012Validator(_load_schema("cases.base.schema.json"))
+    types_validator = 
jsonschema.Draft202012Validator(_load_schema("cases.types.schema.json"))
+
+    files = sorted(glob.glob(f"{root}/table-spec/**/cases.json", 
recursive=True))
+    if not files:
+        print("no cases.json files found", file=sys.stderr)
+        return 1
+
+    errors = []
+    seen_ids = {}

Review Comment:
   `seen_ids` is global across every `cases.json`, so IDs are unique repo-wide. 
That's fine today, but the moment a schema or partition-spec surface lands, 
contributors will reach for `int`, `string`, `timestamp` — names already taken 
here — and hit a collision that forces artificial mangling.
   
   I'd scope uniqueness per surface directory (key on `(surface_dir, id)`); if 
global is intentional, I'd at least document it where contributors will look. 
wdyt?



##########
table-spec/types/geospatial/cases.json:
##########
@@ -0,0 +1,83 @@
+{
+  "cases": [
+    {
+      "id": "geometry-crs84",
+      "valid": true,
+      "input": "geometry(OGC:CRS84)",
+      "decoded": {"type": "geometry", "crs": "OGC:CRS84"},
+      "clause": "geometry(C) with explicit CRS; the canonical serialized form 
is unquoted \"geometry(<C>)\"",
+      "spec_ref": "format/spec.md#appendix-c-json-serialization"
+    },
+    {
+      "id": "geometry-srid",
+      "valid": true,
+      "input": "geometry(srid:4326)",
+      "decoded": {"type": "geometry", "crs": "srid:4326"},
+      "clause": "geometry(C) example from Appendix C is the unquoted 
\"geometry(srid:4326)\"",
+      "spec_ref": "format/spec.md#appendix-c-json-serialization"
+    },
+    {
+      "id": "geometry-default-crs",
+      "valid": true,
+      "input": "geometry",
+      "decoded": {"type": "geometry", "crs": "OGC:CRS84"},

Review Comment:
   For the default cases it's less clear-cut, so I'll pose it as a question. 
When the CRS is the default `OGC:CRS84`, should re-serializing emit `geometry` 
(eliding the default, matching the input) or `geometry(OGC:CRS84)` (always 
explicit)? Both are plausible and implementations will fork here.
   
   Whichever way Appendix C pins it, I'd add a `canonical` so the choice is 
nailed down — this is the most common geometry value, so an untested write 
direction here is where interop actually breaks. Same question for 
`geography-default`. wdyt?



##########
table-spec/types/variant/cases.json:
##########
@@ -0,0 +1,5 @@
+{
+  "cases": [
+    { "id": "variant", "valid": true, "input": "variant", "decoded": { "type": 
"variant" }, "canonical": "variant", "clause": "Semi-structured Types: variant 
added in v3; Appendix C canonical string", "spec_ref": 
"format/spec.md#appendix-c-json-serialization" }

Review Comment:
   variant only has the happy path — I'd add at least one `valid: false` case 
so the rejection direction is covered, e.g. `variant(x)` (variant takes no 
parameters). Without it, an implementation that accepts any string starting 
with `variant` passes.



##########
dev/requirements.txt:
##########
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Dependencies for the dev/ tooling (fixture validation).
+jsonschema>=4.18

Review Comment:
   I'd pin an upper bound here — `jsonschema>=4.18,<5`. This validator is the 
conformance gate every future contribution runs through, so an unbounded range 
means a 5.0 that changes how `iter_errors` surfaces nested errors could 
silently drop or duplicate violations with no code change on our side. Two of 
the passes flagged this.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to