Skip to content

docs: settle the v0.5 contract's API surface and interchange follow-ups (#278) - #392

Merged
iceboundrock merged 6 commits into
mainfrom
autoforge/278
Nov 10, 2026
Merged

iceboundrock merged 6 commits into
mainfrom
autoforge/278

Conversation

@iceboundrock

Copy link
Copy Markdown
Contributor

Why

The PR #250 assessment left seventeen API-surface and Arrow-interchange questions open. None blocked that merge, but each must be settled before the M item that implements it, or before the release. This PR settles every row in the contract text and names the §24 row that tests it. It changes no code: each rule is implemented by its M item, and the §24 rows stay specifications until then.

Owner rows

These apply the choices recorded in Deliverable Q on 2026-10-09:

  • AC3: len(t) raises TypeError that points to count() (§3.2; __len__ is NoReturn in §22; S3). list(t), sorted(t) and pandas.DataFrame(t) call __len__ as a length hint, so an executing len ran the plan twice. Python treats a TypeError from the hint as no hint, so each now executes once. This was checked with CPython 3.13 and pandas 3.0.5.
  • U2: no bound of n rows on tail(n), and distinct stays out of the MUST-stream list (§21.1, R3). The R3 measurements from test: pin the engine layout per test, add layout fixtures and the R3 memory harness (#274) #282 are recorded.
  • OD-E: the general path is the reference, and a fast path ships only if it passes parity. One that fails is removed or narrowed (§21.2; P5, F1).
  • AC17 was applied by Q already; nothing changes here.

Other rows

Row Resolution Tests
AC6 Every integer argument is SupportsIndex, read once with operator.index; bool and non-index values raise LTSeqTypeError (§2.3, §22 and every signature) S2, W2, D10
AC7 For terminals and eager calls, the call and execution stages raise at one moment. They are told apart by the call's own checks against the demand rule (§20.2) E2
AC8 New §2.4: CPython 3.13+, pyarrow 25.0.1+, optional pandas 3.0.5+ and NumPy 2.4.4+; a floor is a version CI tests —
AC9 ltseq.typing.Literal becomes LiteralValue, and its value evaluates with or without NumPy S1, S2
AC10 is_in refuses a str, bytes or bytearray argument D7
AC11 select and derive carry sort keys by one rule: bare references carry, any other expression truncates (§7.3) B2, B3, O1
AC12 t.concat() is defined exactly; the SQL UNION idiom is concat(...).distinct(keep="any") (§13, §23.16) U2
AC13 Idioms for top_k and skew (§14.2) A2
AC16 ColumnNotFoundError is kept for r["x"], with the reason (§8.1) E1
X8 Each scan reads its source when it opens it; collect() is the snapshot (§5.1) Z1
Run-end, list views Normalized to the value type and to list/large_list (§6.2) T2
Importer exceptions The code and message prefix are normative; the consumer's class is informative (§16.4) X3, P4
N20 float32 text keeps NumPy's shortest digits in Python's repr layout, with the exponent form outside 1e-4 to 1e16 (§16.5) X4

The reasoning, probes and open items are in the new review record R (review/history/follow-up-closure.md). Q gets a pointer, and the README, inventory and impact map are updated.

Review focus

  • AC3 turns Q's recorded preference into a rule. Please confirm it at review.
  • AC6 covers every integer parameter, not only the five the issue named, so head and show cannot disagree.
  • AC11 changes derive(x=lambda r: r.x), which now keeps the key instead of truncating it.
  • AC8: pyproject.toml is unchanged. It already declares the two required floors. The pandas and NumPy floors would need extras, a packaging change left for the release gate.

Validation

  • pyright 1.1.414 (standard mode), run on the two §22 stubs extracted from the docs, reports no errors before or after. The NumPy-integer calls (head(np.int64(3)), r.x.shift(np.int64(1)), ntile(np.int64(4)), ...) went from 16 errors to 0.
  • The float32 text-form reference was checked on the finite values among 300,000 random bit patterns with NumPy 2.4.4. Every form read back to the same value and had NumPy's shortest digits.
  • Every [§N] link in the contract and review modules resolves to a heading. git diff --check is clean.
  • No tests were run, since no code changed.

Still open

  • How read_parquet treats a file with no leaf columns whose footer records rows is unprobed; it is needed before M22.
  • The baseline defects for tables without columns stay with M22.

Closes #278

…ps (#278)

Each row of #278 is now settled in the contract text, and each names the
§24 row that tests it. Nothing is implemented; the M item in each row
implements it.

Owner rows apply the choices recorded in Deliverable Q:
- AC3: len(t) raises TypeError pointing to count(), so list(t),
  sorted(t) and pandas.DataFrame(t) no longer run a hidden second
  execution through the length hint (S3). This turns Q's stated
  preference into a rule, so the owner should confirm it.
- U2: no bound on tail(n), and distinct stays out of the MUST-stream
  list, with the R3 measurements recorded (R3).
- OD-E: the general path is the reference, and a fast path ships only
  if it passes parity (P5, F1).

Other rows:
- AC6: every integer argument is SupportsIndex, read with
  operator.index; bool raises LTSeqTypeError (§2.3, §22).
- AC7: the call and execution stages of an eager call are told apart by
  the demand rule, not by timing (E2).
- AC8: supported versions in a new §2.4.
- AC9: ltseq.typing.Literal is renamed LiteralValue and evaluates at
  runtime (S1).
- AC10: is_in refuses a str or bytes argument (D7).
- AC11: select and derive carry sort keys by one rule (B2, B3, O1).
- AC12: t.concat() is defined exactly, and the UNION idiom is
  distinct(keep="any") (U2).
- AC13: idioms for top_k and skew (A2).
- AC16: ColumnNotFoundError is kept for r["x"], with the reason (E1).
- X8: each scan reads its source when it opens it (Z1).
- Run-end encoding and list views are normalized (T2, #239).
- Importer exception classes are informative; the code and message
  prefix are normative (X3, P4).
- N20: the float32 text form and where its exponent form starts (X4).

Review record R holds the reasoning, the probes and what is still open.

Refs #278
@iceboundrock

Copy link
Copy Markdown
Contributor Author

AI Code Review — Round 1

Reviewed HEAD: df77544429ce041b97141afcb447fde7de9e4f05 against base main (merge base 269431ecd19d7ff910c73ec9bb65e03db0b0df27)

Findings

R1-F1 [blocked] docs/proposals/v0.5/contract/schema-and-table-operations.md:122 — The alias-key example contradicts the propagation rules

Required resolution:

Make the carrying rule, examples and acceptance cases mutually consistent. For input keyed by x, the new same-name preference leaves derive(y=r.x) keyed by x; §7.5 then makes drop("x") truncate that key to None. It therefore cannot equal select(y=r.x), which is keyed by y, as §7.3 and B3 require. Either remove that cross-call equivalence requirement or explicitly specify the additional alias tracking and drop behavior needed to support it. Also reconcile §9.2's unconditional replacement-truncation statement and §11.2's replacement wording with the new self-reference and swap cases.

R1-F2 [non-blocked] docs/proposals/v0.5/contract/streaming-and-output.md:111 — The float32 reference and notation threshold disagree

Required resolution:

Define the notation threshold consistently with the selected reference and add the counterexample to X4. With NumPy 2.4.4, x=np.float32(1e-4) has the exact value 0.0000999999974737875163555145263671875, below 10⁻⁴, but the prescribed reference returns "0.0001" rather than exponent notation. Specify that the threshold applies to the chosen shortest decimal representation, or adjust the reference if the original binary value should decide. Update the corresponding N20 explanation.

R1-F3 [non-blocked] docs/proposals/v0.5/contract/acceptance.md:128 — The top-k test oracle does not implement LTSeq's ordering

Required resolution:

Specify a Python reference that implements §9.4's total order and NULL handling, rather than unqualified heapq.nlargest, and include NaN and NULL cases. For k=1 and values [1.0, NaN, 2.0], heapq.nlargest returns [2.0], while the documented descending window puts NaN first. Plain heapq also raises TypeError when comparing None with numbers. The oracle must accept the documented idiom's results for these supported inputs.

R1-F4 [non-blocked] docs/proposals/v0.5/review/history/follow-up-closure.md:40 — AC8 lacks the required acceptance-test mapping

Required resolution:

Add a §24 acceptance case or criterion for the supported-version floors and reference it in AC8's resolution instead of the dash. Cover agreement with package metadata and minimum-version CI coverage for required and optional dependencies. The issue requires every resolved row to name its §24 coverage; §2.4's prose and today's lockfile pins do not supply that missing acceptance criterion. This can remain specification work without implementing the release packaging changes here.

Spec

Most rows receive explicit contract rules and targeted acceptance cases. Completion is prevented by contradictory AC11 propagation requirements, inconsistent N20 boundary wording, an invalid AC13 oracle, and missing AC8 acceptance mapping. Runtime implementation appropriately remains outside this documentation-only PR.

Standards

The changes generally follow the modular contract structure and explain their reasoning and limitations. Repository guidance was read. No source, test or documentation files were edited during review.

Assessment

Another fix round is needed before this contract can serve as an unambiguous implementation target. The primary problem is AC11: its stated rules and acceptance example require different sort_keys for the same pipeline. No runtime security changes are introduced.

Observations

The supplied PR context matches the bound HEAD and base main. AC3 follows the recorded owner preference; the review record explicitly retains final owner confirmation. Static stub checks do not validate the future runtime implementation of optional-NumPy alias evaluation.

Verification

Reviewed the full bound diff and related contract sections, issue discussion, PR description, repository guidance, workflow and dependency metadata. All four supplied CI checks were pending; no controller validation was configured. git diff --check passed. A section-reference check validated 1,007 references with no errors. Pyright 1.1.414 checked extracted canonical stubs and positive call probes: zero errors, one expected stub-only module-source warning. CPython 3.13 and pandas 3.0.5 probes confirmed single iteration with a TypeError length hint. NumPy 2.4.4 probes confirmed the notation-boundary counterexample; 298,843 finite values from 300,000 random float32 bit patterns round-tripped with the reference's decimal digits. Python heapq probes confirmed the NaN/NULL oracle mismatch. The runtime suite was not built or run for this documentation-only diff. HEAD remains unchanged and the worktree is clean.

Summary

Four findings: contradictory alias-key propagation, a float32 notation-boundary inconsistency, an incompatible top-k oracle, and missing AC8 acceptance coverage. Another fix round is required.

Needs another fix round: YES

R1-F1 (AC11): the rule for carrying keys records one name per key and
applies within one call, so derive(y=r.x) keeps x's key as x and a later
drop("x") truncates it; derive(y=r.x).drop("x") no longer claims to equal
select(y=r.x). B3 tests what the rule gives, and derive(y=r.x) equals
select(*t.columns, y=r.x). Section 9.2's truncation paragraph, section
11.2 and the derive bullet now say that a replaced key survives while a
result column holds it, as in derive(x=r.x) and the swap; G3 adds the
NestedTable swap.

R1-F2 (N20): the float32 notation bounds apply to the shortest decimal
the reference writes, not the binary value, so float32(1e-4), just below
1e-4, is written 0.0001. X4 adds it, 1e16 and their neighbors.

R1-F3 (AC13): section 14.2 gives a Python reference for the top_k idiom
that follows section 9.4's order (NaN first descending, NULL last, ties
in table order), and A2 uses it instead of heapq.nlargest, with the NaN
and NULL cases.

R1-F4 (AC8): section 2.4 declares the optional floors as extras, and a new
S6 row checks the wheel metadata against the floors and requires a CI job
that installs each floor exactly; AC8 names S6.

Refs #278
@iceboundrock

Copy link
Copy Markdown
Contributor Author

AI Code Review — Round 2

Reviewed HEAD: cf6df4a6653b843433a6a7ce9e9a83932c420f1f against base main (merge base 269431ecd19d7ff910c73ec9bb65e03db0b0df27)

Findings

R2-F1 [blocked] docs/proposals/v0.5/contract/public-surface.md:48-58 — The prescribed literal alias still fails at runtime

Required resolution:

Make the normative alias definition and the runtime-evaluation requirement compatible. The displayed type LiteralValue references numpy even though that name is imported only under TYPE_CHECKING, so evaluating LiteralValue.__value__ raises NameError whether NumPy is installed or not. Section 22.2 currently requires the aliases to be defined exactly as this section writes them, so an implementation following the stubs cannot pass S1. Specify a workable runtime definition that preserves the static annotation and handles NumPy being absent.

R2-F2 [non-blocked] docs/proposals/v0.5/contract/acceptance.md:62 — The protocol acceptance test requires unequal Python objects to be equal

Required resolution:

State an appropriate comparison for each protocol result while retaining the single-execution check. tuple(t) is a tuple, not the list returned by to_dicts(), and sorted(t, key=...) can reorder its rows; a pandas DataFrame is not equal to a list of row dictionaries either. Compare converted contents and, for sorting, against the correspondingly sorted reference.

R2-F3 [non-blocked] docs/proposals/v0.5/contract/acceptance.md:128 — The skew acceptance criterion exceeds the numerical guarantees of its idiom

Required resolution:

Reconcile A2 with the numerical behavior of the documented skew expression and describe its limitations or provide a more robust idiom. For finite values [1e-200, -1e-200], the squared and cubed float intermediates underflow to zero, so the expression yields NaN although the population skewness is zero. A2 currently requires equality to population skewness without restricting inputs or acknowledging intermediate rounding. Add a boundary case that reflects the chosen contract.

Spec

The owner choices and the requested follow-up rows are generally documented, but the literal alias remains incompatible with its required runtime behavior, and two acceptance criteria do not accurately specify achievable results.

Standards

Repository guidance and the non-code artifact rules were reviewed. The modular documentation structure is otherwise consistent with the surrounding contract.

Assessment

The prior review’s four reported contradictions are resolved in the bound revision. The remaining issues affect the contract as an implementation and acceptance-test target; this documentation-only change introduces no runtime security changes.

Observations

The supplied PR context identifies the bound HEAD and base main. AC3 applies the recorded owner preference, with final owner confirmation still identified as open. Runtime implementation remains outside this PR’s scope.

Verification

Read the full bound diff, issue and PR context, previous review, checks, repository guidance and related contract sections. git diff --check passed and the worktree remained clean. All four CI checks were pending; no controller validation was configured. A Python probe confirmed that a tuple of rows is not equal to its list and that sorting can change row order. NumPy was unavailable in the system Python, so no NumPy probe was run. The runtime suite was not run for this documentation-only change.

Summary

Three findings require another fix round: the literal alias cannot satisfy its runtime requirement as prescribed, and the protocol and skew acceptance cases need corrected or qualified comparisons.

Needs another fix round: YES

iceboundrock and others added 3 commits November 10, 2026 04:32
R2-F1: section 2.3's single type statement named numpy, which only an
if TYPE_CHECKING block imports, so reading LiteralValue.__value__ raised
NameError with or without NumPy, and section 22.2 required the alias to
be defined exactly that way. The type checker now reads a TYPE_CHECKING
branch with the NumPy scalars. The runtime reads an else branch, whose
lazy value imports NumPy when it is first read and falls back to the
private _Scalar union when the import fails. Pyright 1.1.414 accepts the
definition, and CPython 3.13 to 3.15 evaluate it with and without a
NumPy module. Section 22.2 names both branches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R2-F2: S3 required list(t), tuple(t), sorted(t, key=...) and
pandas.DataFrame(t) to equal to_dicts(), a list, which a tuple, a
reordered list and a DataFrame never do. Each result is now compared
with the matching conversion of rows = t.to_dicts(): the list itself,
tuple(rows), sorted(rows, key=f) with the same key, and a DataFrame
built from rows, checked with pandas.testing.assert_frame_equal. The
single-execution count stays. The source is a CSV file without NaN,
because two NaN objects compare unequal inside rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R2-F3: the moment ratio of the deviations underflowed for
[1e-200, -1e-200], where d ** 2 and d ** 3 round to zero and give
0.0 / 0.0, and overflowed for [1e200, -1e200]. A2 nevertheless required
the population skewness for every input. Section 14.2's idiom now
divides each deviation by the group's largest, so every ratio lies in
[-1, 1] and neither power overflows or underflows to zero. The section
states the idiom's limits: x - mean cancels when the spread is tiny next
to the mean, and a deviation beyond the float64 range gives NaN. A2
checks a 1e-12 tolerance against exact central moments for integer
groups, 0.0 for the +-1e-200, 1e200 and 1e308 pairs, identical bits
under power-of-two scaling, and NaN for constant, one-value and
overflowing groups. A simulation of the idiom that rounds each step as
section 14.2 specifies stayed within 4.4e-16 of the exact value over
3,000 random integer groups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@iceboundrock

Copy link
Copy Markdown
Contributor Author

AI Code Review — Round 3

Reviewed HEAD: e44f39b3ba261633e761d73a9f626a9c0df08560 against base main (merge base 269431ecd19d7ff910c73ec9bb65e03db0b0df27)

Findings

R3-F1 [non-blocked] docs/proposals/v0.5/contract/aggregation.md:78 — The top-k string idiom does not reproduce the old output

Required resolution:

Correct the claim that the displayed expression produces the old string, or provide an idiom that does so and cover its boundary cases. The existing top_k implementation casts values to float64 before formatting and returns an empty string for an all-NULL group. The proposed expression casts the original values directly to string and string_agg returns NULL for an all-NULL group; integer 1 is consequently formatted differently, and all-NULL groups differ. If exact reproduction is not intended, state these migration differences explicitly.

R3-F2 [non-blocked] docs/proposals/v0.5/contract/aggregation.md:79 — The skew scaling explanation incorrectly rules out underflow

Required resolution:

Qualify the claim that neither normalized power underflows to zero. For the finite group [1e200, -1e200, 1e-200, -1e-200], normalization itself rounds the small deviations to signed zero, so their squares and cubes are zero. Explain that scaling protects the largest deviation and the overall second-moment denominator, not every individual term, and describe the resulting precision limitation.

Spec

The issue rows are generally mapped to explicit contract rules and acceptance cases, but the new aggregate migration guidance contains the two inaccuracies above. Runtime implementation remains outside this documentation change.

Standards

The modular contract and review record follow repository structure. Repository guidance was inspected; the review did not change files.

Assessment

The three round-2 fixes address their reported issues. The remaining defects affect the accuracy of migration guidance and numerical explanations rather than runtime code.

Observations

The supplied PR context matches the bound HEAD and base main. AC3 applies the owner's recorded preference; the review record calls out final owner confirmation.

Verification

Read the issue, PR and prior reviews, checks, repository guidance, full bound diff and related contract and implementation sections. All four supplied checks were queued; controller validation was not configured. git diff --check passed and the worktree remained clean. A Python probe confirmed underflow in the normalized-deviation example. Inspected the existing top_k implementation and aggregate contract for formatting and NULL behavior. The runtime suite was not run: pytest is not installed, and this change modifies documentation only.

Summary

Two documentation findings require a fix round: the top-k string replacement does not reproduce the old output, and the skew explanation overstates its protection against underflow.

Needs another fix round: YES

…ow limits (#278)

R3-F1: the top_k idiom's joined string does not reproduce v0.4's.
State the three differences (text form, ranking order, all-NULL
group), how to restore "" for an all-NULL group, and add the joined
string's cases to acceptance row A2 and the AC13 record row.

R3-F2: scaling the deviations protects the largest deviation and the
second-moment denominator, not every term. Show the group whose small
deviations round to signed zero, bound the effect of underflow on the
result, and add that group to row A2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@iceboundrock

Copy link
Copy Markdown
Contributor Author

AI Code Review — Round 4

Reviewed HEAD: 28372f34c1317cd9adb69b7a44f431f7abe44aee against base main (merge base 269431ecd19d7ff910c73ec9bb65e03db0b0df27)

Findings

None.

Spec

The issue’s rows are addressed in the contract and mapped to acceptance cases. Runtime implementation remains outside this documentation-only change.

Standards

The changes follow the repository’s modular contract and review-record structure. Repository guidance was reviewed.

Assessment

The revised contract provides implementation and acceptance guidance for the requested follow-ups. No actionable correctness or safety defect was identified in this round.

Observations

R3-F1 is addressed by explicitly documenting the top-k text, ordering and all-NULL differences. R3-F2 is addressed by qualifying the scaling discussion and describing the loss of small terms. AC3 applies the owner’s recorded preference; the review record identifies final owner confirmation as open.

Verification

Reviewed the bound diff, related contract and implementation sections, issue and PR context, prior reviews, repository guidance and supplied checks. All four CI checks succeeded; no controller validation was configured. git diff --check passed and the worktree remained clean. The runtime suite was not run locally for this documentation-only change; NumPy is not installed in the available system Python.

Summary

No findings require another fix round. Both round-3 findings are addressed.

Needs another fix round: NO

@iceboundrock
iceboundrock merged commit 86cf9ef into main Nov 10, 2026
4 checks passed
@iceboundrock
iceboundrock deleted the autoforge/278 branch November 10, 2026 18:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v0.5 contract follow-ups: API surface and interchange (AC3, AC6–AC17, U2, OD-E, X8, run-end encoding, N20)

1 participant