Skip to content

Refuse fixture capacities above the schema's ceilings at load - #900

Merged
jeremy merged 2 commits into
mainfrom
eventfeed/loader-ceilings
Sep 16, 2026
Merged

jeremy merged 2 commits into
mainfrom
eventfeed/loader-ceilings

Conversation

@jeremy

@jeremy jeremy commented Sep 16, 2026

Copy link
Copy Markdown
Member

Follow-up to #778. Addresses the post-merge finding #778 (comment): conformance/event-feed/schema.json caps liveBufferCapacity and dedupeCapacity at 1,000,000 (a resource ceiling — a driver may allocate the capacity eagerly), but the Go loader's validateConfig ranged only the duration fields. A fixture with dedupeCapacity: 2147483647 passed the loader and reached newDedupe, whose index map is sized by the capacity, so running the Go tier-2 tests directly (without make event-feed-fixtures-check in front) could OOM the test process instead of rejecting the fixture.

What changed

go/pkg/basecamp/eventfeed/scenario_fixture_test.go

  • The two capacities now share the durations' three-state decode (optionalMs renamed to optionalInt, since it now carries capacities and delay-envelope bounds as well): absent means default, JSON null is refused, a value is ranged.
  • New maxScenarioCapacity (1,000,000) and checkScenarioCapacity; validateConfig ranges both capacities to [1, 1000000] at load, naming the field and the ceiling in the refusal. An explicit 0, a negative and null are now refused as the schema does, where before they were silently read as "use the default".

go/pkg/basecamp/eventfeed/scenario_harness_test.go

  • Passes a capacity to the connector when it is set, rather than when it is positive.

go/pkg/basecamp/eventfeed/scenario_selftest_test.go

  • TestScenarioCapacitiesAreRangedAtLoad: for each capacity, the ceiling itself loads and an omitted key stays absent; one past the ceiling, 2147483647, the largest int64, 1e7, 0, -1, null and a quoted number are refused by parseScenario alone — before any connector exists to allocate — with the field and the ceiling in the message.
  • TestScenarioConfigBoundsMatchTheSchema: reads schema.json back and compares every bound the loader enforces (the five config ms fields, the two capacities, advance.ms, fireTimer.assertDelayMs.{min,max} — minima and maxima) to the loader's constants. The loader's copy of the numbers is now cross-checked against the contract rather than being a second unverified declaration; a mutant ceiling of 2,000,000 fails this test on both capacity fields.

go/pkg/basecamp/eventfeed/scenario_conformance_test.go

  • scenarioSchemaPath beside scenarioFixtureGlob.

Commands run

From go/:

  • go build ./... — exit 0
  • go vet ./... — exit 0
  • go test -race -count=1 ./pkg/basecamp/eventfeed/... — exit 0

From the repo root:

  • make go-lint — 0 issues, exit 0
  • make event-feed-fixtures-check — exit 0 (all fixtures validate, both pin probes verified)

Summary by cubic

Rejects fixture capacities above the schema's 1,000,000 ceiling at load. The loader previously ranged only the config duration fields, so a fixture with dedupeCapacity: 2147483647 passed validation and could OOM the Go tier-2 test process when run without the JSON-schema gate in front.

  • The two capacities now share the durations' three-state decode: absent means default, JSON null is refused, and a value is ranged.
  • Explicit zero, negative, and null are refused at load with the field and ceiling named, instead of silently read as "use the default."
  • The harness passes a capacity to the connector when set, rather than when positive.
  • A new self-test reads schema.json back and cross-checks every loader-enforced bound (durations, capacities, advance.ms, assertDelayMs edges) against the loader's constants, so they cannot drift apart silently.

Written for commit 5cd9532. Summary will update on new commits.

Review in cubic

conformance/event-feed/schema.json caps liveBufferCapacity and
dedupeCapacity at 1,000,000 — a resource ceiling, since a driver may
allocate the capacity eagerly — but the Go loader's validateConfig ranged
only the duration fields. A fixture naming dedupeCapacity: 2147483647
passed the loader and reached newDedupe, whose index map is sized by the
capacity, so running the Go tier-2 tests without the JSON-schema target in
front could take the process down in an allocation instead of rejecting
the fixture.

The capacities now share the durations' three-state decode (absent, null,
value) and are ranged to [1, 1000000] at load, refused with the field and
the ceiling named; an explicit zero, a negative and a null are refused as
the schema does rather than silently read as the default. A self-test
pins the ceiling itself loading and everything past it refused through
parseScenario alone, before any connector exists, and a second reads
schema.json back and compares every bound the loader enforces —
the ms maximum, the capacity maximum, their floors — to the constants the
range checks use, so the loader's copy cannot drift from the contract.
Copilot AI balanced review requested due to automatic review settings September 16, 2026 08:46
@github-actions github-actions Bot added the go label Sep 16, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-16T09:29:45.403271Z 5cd9532 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The schema-drift test omits the enforced minimum for advance.ms.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds load-time capacity validation to prevent oversized event-feed fixtures from causing excessive allocations.

Changes:

  • Preserves absent/null/value states for capacities.
  • Enforces schema capacity limits before connector construction.
  • Adds capacity and schema-drift tests.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File summaries
File Description
scenario_fixture_test.go Adds capacity decoding and validation.
scenario_harness_test.go Passes explicitly configured capacities.
scenario_selftest_test.go Tests capacity limits and schema alignment.
scenario_conformance_test.go Defines the schema path for self-tests.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread go/pkg/basecamp/eventfeed/scenario_selftest_test.go
@jeremy
jeremy requested a balanced review from Copilot September 16, 2026 09:26
@jeremy

jeremy commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approved

Capacity validation matches the schema, occurs before allocation, and has comprehensive boundary and drift coverage.

Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 5cd953215b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@jeremy

jeremy commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

Review threads: 1 resolved (1 fixed, 0 declined). #900 (comment) — the schema drift test now asserts advance.ms's minimum as well as its maximum (5cd9532).

@jeremy
jeremy merged commit 12cbad5 into main Sep 16, 2026
51 checks passed
@jeremy
jeremy deleted the eventfeed/loader-ceilings branch September 16, 2026 18:46
jeremy added a commit that referenced this pull request Sep 16, 2026
* Bound both capacities where they are allocated, not only in fixtures

A fixture asking for a dedupe capacity of 2,147,483,647 could take the test
process with it, because the dedupe index sizes its map to the capacity the
moment the connector is constructed. #900 closed that at the fixture loader, so
no conformance scenario can ask for it any more.

The same request still arrives through the public options. WithDedupeCapacity
and WithLiveBufferCapacity were checked for positivity and nothing else, so any
consumer of the package can hand the constructor a number that is not a slow run
but an allocation the process cannot decline — a typo, or a value that meant
bytes rather than events. Both are now bounded by the ceiling the conformance
schema already declares, in the constructor that owns the allocation and beside
every other bound it enforces.

The tests assert the constructor's error rather than the behaviour past it: a
test that allocates to prove the allocation is the thing being prevented.

* Say where the capacity is actually allocated

The comment justifying MaxCapacity described an allocation that does not
happen. New builds the config, validates it, and returns a Connector; it
allocates neither structure. The dedupe index sizes its map to the capacity in
newDedupe, reached from newLoop when an Events iteration starts, and the live
buffer starts as a nil slice and grows to its capacity lazily.

The limit is still right and the failure that prompted it was real — the
allocation is eager, it just happens on the first iteration rather than at
construction. But a comment that justifies a limit with a mechanism that does
not exist is how the next person removes the limit for a good-sounding reason,
so both it and the test's version now say what holds: construction-time
validation refuses the value before it can reach the run loop. The test comment
also claimed this test goes through the fixture loader, when it calls New
directly.

Both option GoDocs documented only positivity, so a caller could not validate
an input without first eating a construction error. They name the ceiling now.

* Accept the ceiling as well as refuse past it

Every capacity case asserted a refusal, so a comparison that slipped from > to
>= would narrow the published bound with the whole suite still green: each
refusal case is satisfied by a ceiling one lower. Construction now has to
accept MaxCapacity exactly, from both options. Flipping the comparison fails
those two cases and nothing else.

The schema-bound test pinned the loader's own maxScenarioCapacity, a second
literal 1,000,000 that could drift from the exported one silently. It is
eventfeed.MaxCapacity by declaration now, so the test that compares the
schema's `maximum` against it pins the schema to the public contract too.

* Declare the capacity ceiling in §23, not only in Go

§23's public validation contract said these options must be positive and set no
upper bound; the JSON-schema `maximum` constrains conformance fixtures only. So
the Go reference was enforcing a limit the shared contract did not state, and
the pending ports could legitimately accept 2,147,483,647 and diverge from it
with nobody finding out until a conformance run or a bug report.

The ceiling is right — an unbounded capacity a driver may honor eagerly is a
footgun in every port — so it is stated where the ports will read it:
EVENT_FEED_MAX_CAPACITY, a shared API constraint every SDK's construction-time
validation applies, refused with the `usage` code like the positivity check
beside it. Appendix A carries the constant with the other EVENT_FEED_* rows.

The bound is written in three places, and they are tied rather than
independently maintained: the Go loader's constant is the exported one by
declaration, the existing schema-bounds test compares the schema's `maximum`
against that, and the schema's own descriptions name the constant.

It is tagged [static] rather than [conformance]: the schema's `maximum` means
no fixture can carry an over-ceiling capacity, so the refusal is verified by
build checks, not by the fixture suite.

* State what the ceiling's drift check actually covers

The sentence claimed the three statements of 1,000,000 cannot drift. Only two
of them are machine-tied: the tier-2 loader's bound is the exported constant by
declaration and the schema's `maximum` is compared against it, but SPEC's own
number and Appendix A's row are hand-maintained, as Appendix A says every
`EVENT_FEED_*` row is. That is the same defect this PR started with — a claim
resting on a mechanism that does not reach as far as the claim — so it says
what holds and names the editing obligation the rest is left to.

Gating SPEC's number instead would mean a fifth doc-constants line kind, in a
gate whose own header calls that set deliberately bounded, for a value that has
not moved; Appendix A's rule is to gate a row when it starts moving.

The schema descriptions carried a sentence fragment. Fixed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants