Skip to content

Event feed conformance: three corrections to what tier 2 claims (3/3) - #778

Open
jeremy wants to merge 49 commits into
event-feed-go-connectorfrom
event-feed-conformance-driver
Open

Event feed conformance: three corrections to what tier 2 claims (3/3)#778
jeremy wants to merge 49 commits into
event-feed-go-connectorfrom
event-feed-conformance-driver

Conversation

@jeremy

@jeremy jeremy commented Aug 18, 2026

Copy link
Copy Markdown
Member

Third of the split. Stacked on #705, which is stacked on #777 — review after
those. Three corrections to the tier-2 conformance family, all of them about the
suite claiming more than it proves.

1. Row 15's kill claim is partial, and the sentinel obligation is withdrawn

The mutation table said fixture 30 kills follow-cross-origin-redirect partly
through "the harness obligation that the fixture's foreign origin is bound to a
sentinel listener whose any-request fails the scenario". Fixture 30's own
description said the same.

No implementation meets that obligation, and meeting it would prove nothing.
At tier 2 the poll lane is a seam: the driver receives the fixture's scripted
302 and hands the connector an already-formed redirect-refused verdict. The Go
driver says so itself — "the driver is the seam, so the foreign origin is
unreachable by construction"
. The connector never sees a Location header and
never decides whether to follow one, so a silent sentinel is a statement about
the driver, not the connector.

That makes the mutation unreachable at this tier — it lives below the seam.
What fixture 30 does kill is the half above it: mishandling the verdict by
retrying it, misclassifying it, or echoing more of the Location than its
origin. Real zero-egress proof is the Layer-1 adapter's own 302 test, tracked
for G1b.

The fixture's description is amended too. Fixtures are merged contract and are
not edited lightly, but leaving the identical false claim in the file the other
five SDKs read would make this a half-correction. Prose only — no assertion
changed, both pin probes still verify.

This also corrects #705's own kill-matrix, which inherited the claim.

2. An advance whose window would fire any timer is rejected, in every driver

The driver ran advance through the plain clock Advance, with a comment
noting that a fixture wanting a chained firing "would pass AdvanceSettling the
rendezvous". That is an opt-in, and the next fixture author is exactly who would
not take it.

Stress-testing the settle killed it, for reasons that are fundamental rather
than effort:

  • Waiting for a firing to be consumed deadlocks. The connector deliberately
    does not consume a staleness firing promptly — a window closing during a
    delivery is latched and observed later, which is what §23 requires. A clock
    that waited would hang against the behavior the spec mandates.
  • Waiting for the follow-on arming requires knowing one is coming. Nothing
    distinguishes "has not armed yet" from "will not arm".

So the reentrant clause is not settleable where the connector runs
concurrently, and the divergence is made unscriptable instead — by keying
the rule on firing, not arming. Arming happens on the connector's schedule,
so a driver could only detect it by waiting and then assuming nothing more was
coming — a heuristic wearing a MUST. What an advance would fire is decided from
the clock's own state before time moves: an advance whose window holds any
initially due timer now fails, naming fireTimer as the deterministic
alternative. This is stricter than an arming rule on purpose — a firing that
arms nothing is rejected too; a script that wants that firing writes
fireTimer and names the timer. No firing in the window means nothing can be
armed in response, so an accepted advance is provably inert. Unconditional — a
schema field would let an author take the divergence rather than avoid it. Go
implements the decision as a single critical section (AdvanceIfQuiet):
due-set read and time movement under one hold of the clock's locks.

The family README carries the constraint, because it binds all six SDKs and they
are split across single-threaded and concurrent test clocks.

Self-tested both ways: the reject case (an advance across the handshake
deadline, which is due inside the window) fails, and the control (a quiet
window) still passes — without which the guard would reject every advance,
including fixture 05's.

3. Fixture 31 — a straggler below the entry page's served id

Closes the hole where a dedupe ordering live ids against the highest poll-served
id passes the whole suite.

Fixture 20 already proves a post-snapshot straggler is delivered, but its entry
page serves no events, so a highest-served-id implementation passes it too:
with nothing served the mark is unset and every id clears it. Every other
straggler in the suite is buffered pre-cut.

31 serves id 99 on the entry page and pushes 41 afterwards, in the streaming
lane. 41 must be delivered — dedupe tracks actually-delivered ids and 41 was
never served by poll — while a re-push of 99 is still suppressed, which keeps
this a dedupe fixture rather than a no-dedupe one.

The kill was verified, and the first two attempts were wrong in instructive
ways.
A mutant in admitLive survived — that is the buffer-admission path, and
a streaming straggler never goes through it. A mutant in the shared deliver()
gate failed six fixtures, so it was broader than the hole and would not have
justified a new fixture. The mutant that matches the claim — the ordering applied
only to the streaming lane — passes all of 01–30 and is killed by 31 alone.

Verification

go build / go vet / go test -race -count=1 pass; 23/23 fixtures green;
5 driver self-test groups pass; make event-feed-fixtures-check clean with both
pin probes verified; make go-lint 0 issues.


Summary by cubic

Strengthens tier-2 event-feed conformance: timer advances and fixture numbers no longer depend on goroutine timing or JSON spelling, and the contract now matches what the tier can actually prove. Adds a streaming dedupe regression fixture and tightens the suite's tracking and documentation checks.

Conformance

  • An advance may be the first step; otherwise it must follow expectState and a non-empty expectTimers, reject any initially due timer, and use fireTimer for deterministic firings.
  • The loader judges every numeric literal by exact mathematical value at load, accepting integral decimal and exponent spellings while rejecting quoted, fractional, oversized, or resource-exhausting values.
  • Millisecond fields use int64 values limited to 10 virtual years, while duration and assertDelayMs fields distinguish absent, null, and value states; nulls, missing bounds, and unknown members are rejected.
  • Fixture 31 serves event 99 during the entry page, then delivers streaming event 41 below that id while suppressing a duplicate 99.
  • The Go reference now documents the cable-lane retained payload ceiling, plus one transient decode frame, for transports with bounded reads; it does not claim a bound for poll-page size.

Contracts and checks

  • Fixture 30 now documents tier-2 redirect refusal, no retry, and no further poll; connector-visible hostile continuations retain their zero-request checks, while the redirect adapter proof is a Layer-1 obligation tracked in Event feed: Layer-1 seam-adapter conformance is assigned but not yet built (302 redirect-refusal test) #819.
  • spec/tracking-issues.yml and its detector fail closed on unregistered or unsweepable canonical mentions, count drift, duplicate keys, malformed YAML, and multi-document files.
  • Fixture and schema checks, both pin probes, Go tests including -race, lint, and tracking self-tests pass.

Written for commit e23ade6. Summary will update on new commits.

Review in cubic

Copilot AI balanced review requested due to automatic review settings August 18, 2026 21:12
@jeremy jeremy added the conformance Conformance test suite label Aug 18, 2026
@github-actions github-actions Bot added the go label Aug 18, 2026

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.

Pull request overview

Tightens tier-2 event-feed conformance claims and closes a streaming deduplication coverage gap.

Changes:

  • Clarifies redirect coverage at the poll seam.
  • Rejects scheduling-dependent virtual-time advances.
  • Adds fixture 31 for lower-ID live stragglers.

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.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
go/pkg/basecamp/eventfeed/scenario_selftest_test.go Tests the new advance guard.
go/pkg/basecamp/eventfeed/scenario_conformance_test.go Implements guarded virtual-time advancement.
conformance/event-feed/README.md Revises tier-2 guarantees and fixture matrix.
conformance/event-feed/fixtures/31-post-snapshot-straggler-below-served-id.json Adds streaming dedupe coverage.
conformance/event-feed/fixtures/30-continuation-redirect-cross-origin.json Corrects the redirect fixture description.

💡 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_conformance_test.go Outdated
Comment thread conformance/event-feed/README.md
Copilot AI review requested due to automatic review settings August 18, 2026 21:30
@jeremy
jeremy force-pushed the event-feed-conformance-driver branch from 039aad6 to 4c8446c Compare August 18, 2026 21:30

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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (3)

go/pkg/basecamp/eventfeed/scenario_conformance_test.go:494

  • This observes only the net counts per timer name, not whether NewTimer ran. A timer can fire and be rearmed under the same name before settle samples it (the connector does same-name rearming, e.g. repair-poll at catchup.go:852), leaving the map equal to before and allowing the scheduling-dependent advance. Conversely, removal alone is labeled as an arm. Track a monotonic timer-arm/firing generation in feedtest.Clock and add a same-name-rearm self-test so this guard detects activity independently of the snapshot schedule.
	before := timerCounts(d.h.clock)
	armed := false
	d.h.clock.AdvanceSettling(millis(step.Ms), func() {
		if !maps.Equal(timerCounts(d.h.clock), before) {
			armed = true

conformance/event-feed/README.md:360

  • The sentinel obligation is still present verbatim in the shared schema (schema.json:906), and the fixture inventory at README line 239 still presents “zero foreign egress” without the seam qualification. As a result, the merged contract continues to make the exact tier-2 claim this PR says is withdrawn. Remove the sentinel requirement and qualify the inventory/schema text as driver construction rather than connector proof.
An earlier revision of this row claimed a harness obligation to "bind the
foreign origin to a sentinel listener whose any-request fails the scenario".
That is withdrawn. No implementation met it, and meeting it would prove
nothing: the foreign origin is unreachable **by construction of the harness**,

conformance/event-feed/README.md:131

  • The shared schema still defines advance as firing timers armed inside the window (schema.json:1208) and does not mention this mandatory rejection. Drivers generated from or validated against that contract are therefore still instructed to implement the behavior this paragraph makes unscriptable. Update the schema description alongside the README so the cross-language contract is consistent.
Every driver must therefore FAIL an `advance` during which the connector arms
anything, naming `fireTimer` as the deterministic alternative — it fires one
named timer without moving the clock, so no re-selection is involved. This is

@jeremy

jeremy commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

Round summary — 3 findings, 3 fixed, 0 open

Swept all three surfaces. Both review threads answered and resolved; all three suppressed comments from the round on head are addressed, since two of them carried findings no thread did.

# Source Finding Outcome
1 thread + suppressed scenario_conformance_test.go:494 the advance guard measured firings, not arming fixed ab41f581a
2 thread + suppressed README.md:360 the sentinel obligation was withdrawn in prose only fixed be5a1c7e0
3 suppressed README.md:131 schema.json's advance still instructs drivers to implement the unscriptable clause fixed be5a1c7e0

The one that mattered

Finding 1 was a test that could not fail, and the review's sharper half was in the suppressed block rather than the thread. Clock.advance removes a fired timer from the registry before any observer runs, so comparing Outstanding() snapshots reports an ordinary expiry as an arm — and, symmetrically, cannot see a same-name rearm at all, which is exactly what the connector does with repair-poll/poll-retry.

Proven by mutation rather than argued: with the connector's backoff arming deleted, the old self-test still passed (REAL_EXIT=0); with the fix in place the same mutation fails (REAL_EXIT=1). The test had been passing on the handshake deadline's own removal. The guard now reads feedtest.Clock.ArmCount(), the monotonic timer-creation count the clock already maintained for tie-breaking.

Findings 2 and 3 are one mechanism — a correction that reached the README but not schema.json, which the README names as the contract. I swept the family for the whole class rather than patching the two that were reported; it closes at exactly three sites, all fixed.

Not absorbed here: #789 (arrival-strict matching)

#789 is the same class as finding 1 — "the driver cannot fail the contract it enforces" — and it is now the third known instance. I am deliberately not fixing it in this PR, and the reason is mechanism, not appetite.

Finding 1's remedy is a per-step verdict read from an instrument the clock already had: additive, locally provable, no new concurrency. #789's remedy is a harness-wide state machine — an active-step tracked under h.mu and consulted by recorders running on other goroutines, with atomic handoff between adjacent steps. Its naive spelling has a failure direction worse than the bug it fixes: a set/clear leaves a window between adjacent steps matching no expectation, which turns a correct connector red on a scheduling accident. That needs its own design, its own per-class red-proof matrix, and a positive argument that no conforming connector can be reddened — none of which belongs inside a PR titled "three corrections to what tier 2 claims" without changing what this PR is.

So it should land as its own change on top of this stack. #705 made the same call for the same reason, which is why it recorded rather than patched.

One piece of design input I do owe #789, posted there: this PR's finding is evidence about the shape of the remedy. Outstanding() failed because a reconstructable snapshot collapses distinct histories onto one value. deliveredAt is the same kind of witness and collapses the same way — which is why it orders saves against deliveries and nothing else. Count the event, don't reconstruct it from state.

Stack / rebase

Rebased twice — event-feed-go-connector moved under me mid-session (3eaf48a39cf1d06b96). Now on the settled head, and GitHub's computed diff agrees with the local one at 460 additions / 8 files (it was 20,936 / 62 against the stale base, over Copilot's 20,000-line review limit).

cf1d06b96 landed in my file territory (scenario_conformance_test.go, scenario_selftest_test.go); its terminal-contract guards and this PR's advance guard are disjoint and the full suite is green on the merged result.

Gates

go test ./pkg/basecamp/eventfeed/... REAL_EXIT=0 · gofmt 0 · go vet 0 · golangci-lint 0 issues · make doc-constants-check REAL_EXIT=0 — all under LC_ALL=C. make event-feed-fixtures-check REAL_EXIT=0 under LC_ALL=C.UTF-8, stated explicitly because make conformance is broken under LC_ALL=C (#774).

No finding in this round argued from bc3 server behaviour. I re-checked anyway, since the pins moved today: between 8be5c67de5 and 6adb6050cd the only change on the event-feed contract surface is a 12-line authorization-scope addition to stream_tickets_controller.rb (read-scoped tokens may now mint). events_channel.rb, event/feed.rb, event/feed/position.rb and doc/api/sections/event_feed.md are untouched, so the poll envelope, the absent-next predicate and the disconnect reasons this PR pins have not moved. The README's verified at bc3 8be5c67de5 header is an as-of fact and correctly does not advance; PR-T's true-up remains the place that re-verifies.

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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (1)

go/pkg/basecamp/eventfeed/scenario_selftest_test.go:365

  • This control makes the new guard timing-dependent. After Advance delivers the due timer, the connector handles it on another goroutine; the driver only polls ArmCount until scenarioWatchdog expires (scenario_conformance_test.go:508-519). If that goroutine is delayed past the watchdog, the scenario passes and can arm a timer after advance returns—the exact cross-language divergence this rule is meant to eliminate. A timeout cannot prove that no arm is coming; either reject every advance that fires a timer (while still allowing truly quiet windows), or add an explicit synchronous completion protocol.
			{"expectConnect":{"url":"{{CABLE_URL:2}}"}}],
			"finally":{"state":"awaiting_welcome"}}`

Copilot AI review requested due to automatic review settings August 19, 2026 02:37
@jeremy
jeremy force-pushed the event-feed-conformance-driver branch from be5a1c7 to 81414d7 Compare August 19, 2026 02:38

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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (1)

conformance/event-feed/README.md:356

  • Adding row 16 leaves the matrix summary inaccurate in two ways: the heading still says “fifteen,” and the introductory sentence says every listed mutation is killed even though row 15 is now explicitly only partial/below-seam. Update the heading and introduction so this conformance document does not continue overstating the suite’s proof.
| 15 | `follow-cross-origin-redirect` (follows a 302 to a foreign Location) | 30 — **partially**, and the boundary is below the seam. See the note under this table. |
| 16 | `discard-live-id-at-or-below-served-id` (streaming lane orders live ids against the highest poll-served id) | 31 — and 31 alone: verified to pass all of 01–30, because every other straggler either arrives with nothing yet served (20) or is buffered pre-cut (01, 12, 19) |

Copilot AI review requested due to automatic review settings August 19, 2026 02:40
@jeremy

jeremy commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

Stopping on the advance guard — third round, one class

The suppressed comment on the latest review (scenario_selftest_test.go:365, against cfd9b3d4e) is correct, and it is the third round on this guard. Per AGENTS.md that is a signal about the instrument, not a queue, so I am naming the pattern and the real question rather than writing the third patch.

The three rounds

  1. 1de86dfe7 — the guard is introduced, comparing snapshots of Outstanding().
  2. This round — the snapshot is the wrong instrument. A firing removes its timer before any observer runs (so an expiry reads as an arm) and a same-name rearm leaves the set identical (so repair-poll rearms are invisible). Fixed in 28d03cc18 by counting timer creations.
  3. This round, suppressed — a timeout cannot prove that no arm is coming. If the connector's goroutine is starved past scenarioWatchdog, the guard passes and the arm lands afterwards.

Rounds 2 and 3 are not the same bug, but they are the same class: the driver cannot observe the quantity the rule names. Round 2 observed the wrong quantity; round 3 cannot observe the right one at all. A fourth selector on the same wait would be the treadmill.

The real question

The rule says: reject an advance during which the connector arms a timer. That predicate is decidable in a single-threaded driver and undecidable in a concurrent one — you can only ever conclude "nothing armed within N seconds".

Which means the current rule has the disease it was written to cure. A rule that TypeScript decides exactly and Go decides by waiting is itself a cross-language divergence: the same fixture can be admitted in one language and rejected in another. So the question is not "how long should the wait be" — it is "what predicate can every driver decide identically and synchronously?"

The candidate answer, and the measurement that makes it actionable

Copilot's first alternative is the sound one: key the rule on FIRING, not arming. Every driver decides "did this advance fire a timer" synchronously, inside its own advance, on its own thread, with no waiting and no observation of the connector at all.

It is sound as a conservative over-approximation because the reentrant divergence requires a firing to trigger it: no firing in the window ⇒ nothing can be armed in response ⇒ no divergence, provably, with no wait. It is strictly coarser than the true predicate, and the coarseness is the price of decidability.

Its cost is that it forbids a shape currently legal — an advance whose window expires a timer that is not replaced. I measured that cost against the merged suite and it is zero. Fixture 05 is the only advance in all 31 fixtures, and its window fires nothing:

PROBE advance 121000ms: outstanding before=[staleness repair-poll] after=[staleness repair-poll] armCount 6->6

Both timers sit beyond the window, exactly as the fixture intends. fireTimer already covers the "make this timer fire" need deterministically, so the coarser rule costs authors nothing they cannot express.

Why I am not making the change here

It rewrites the normative rule in README.md and schema.json for all six SDKs, forbidding a fixture shape that is legal today, and it inverts a self-test I added this round. That is a contract decision, not a driver fix, and it is the third round on one class — so it wants a decision rather than my patch.

What is shipping meanwhile, stated plainly

The guard as it stands is conservative in the safe direction. ArmCount is monotonic and rises only on a real NewTimer, so the guard cannot reject a well-formed script; its only defect is the missed detection round 3 names, under a starvation longer than the watchdog. It is strictly better than what it replaced, which was reporting expiries as arms and missing rearms entirely. So this PR is not shipping a wrong-direction failure — it is shipping a detector that is sound but not complete, with the completeness question written down here rather than left in a suppressed block.

Related: #789 is the same family — "the tier-2 driver cannot fail the contract it enforces" — and this is a further instance of its root shape, a predicate the driver cannot actually observe. If the firing-based rule is accepted, it should land with #789's work rather than as a fourth pass here.

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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (5)

conformance/event-feed/README.md:358

  • Calling this a “partial kill” contradicts the next sentence, which says the redirect-follow mutation lives below the seam and no tier-2 harness can reach it. Describe it as unreachable/not killed so the explanatory note agrees with the mutation matrix.
**Row 15 is the family's one partial kill, and the reason is structural.** In

conformance/event-feed/README.md:355

  • The table still says fixture 30 partially kills follow-cross-origin-redirect, but the note below and the PR description establish that this mutation is below the poll seam and cannot be reached at tier 2. In a mutation kill matrix, an unreachable mutant is not partially killed; fixture 30 instead covers separate verdict-handling failures above the seam. Mark this row as not killed at tier 2.

This issue also appears on line 358 of the same file.

| 15 | `follow-cross-origin-redirect` (follows a 302 to a foreign Location) | 30 — **partially**, and the boundary is below the seam. See the note under this table. |

conformance/event-feed/README.md:366

  • Fixture 30 cannot detect Location over-echoing. The driver reduces the scripted Location to CanonicalOrigin and supplies a generic safe cause (scenario_conformance_test.go:1065-1081), while this fixture asserts only the terminal reason. A connector that echoed all data it received would therefore still pass. Remove this kill claim; the separate hostile-cause unit test owns the redaction proof.
and no tier-2 harness can reach it. What fixture 30 does kill is the half above
the seam: a connector that mishandles the verdict — retrying it, classifying it
as anything but Terminal(`invalid_continuation`), or echoing more of the
`Location` than its origin — diverges on `finally` and fails.

conformance/event-feed/fixtures/30-continuation-redirect-cross-origin.json:3

  • The fixture description claims it kills Location over-echoing, but the driver strips the Location to its origin before constructing PollError and the fixture asserts only the terminal reason. Thus an implementation that unsafely preserves its entire seam input still passes this scenario. Remove the redaction claim or extend the contract and driver with an assertion that exposes a secret-bearing cause.
  "description": "A VALIDATED same-origin `next` answers 302 with a cross-origin Location: the poll seam suppresses automatic redirect-following, and the foreign Location is Terminal(invalid_continuation) with zero egress to the foreign origin. {{NEXT:1}} substitutes same-origin, so the pre-poll validation PASSES and the second poll seam call is made (contrast fixture 26, where no request reaches the URL at all) — the redirect answer is where the per-hop rule bites. The Location host is literal, never substituted, and never served. NOTE: at tier 2 the poll lane is a seam, so the driver forms the redirect-refused verdict and the connector never sees a Location header \u2014 this fixture kills the half above the seam (mishandling the verdict: retrying it, misclassifying it, or echoing more of the Location than its origin), not follow-cross-origin-redirect itself, which lives below the seam and is proven by the Layer-1 adapter's own 302 test. See the row-15 note in README.md.",

conformance/event-feed/schema.json:906

  • This contract description still overclaims redaction coverage. The tier-2 driver passes the connector only the canonical origin plus errors.New("poll refused a redirect"), and fixture 30 checks only invalid_continuation; no path/query text reaches the connector for the fixture to prove it is not echoed. Remove that item from the pinned behaviors.
          "description": "Redirect on a continuation (fixture 30): the seam suppresses automatic following; a cross-origin/downgraded Location is Terminal(invalid_continuation). At tier 2 the poll lane is a SEAM, so the driver forms the redirect-refused verdict and the connector never sees a Location header: this pins the half above the seam (mishandling the verdict — retrying it, misclassifying it, or echoing more of the Location than its origin), not redirect-following itself, which lives below the seam and is proven by the Layer-1 adapter's own 302 test. No sentinel-listener obligation is imposed on the harness: the foreign origin is unreachable by construction of the harness, so a silent sentinel would be a statement about the driver rather than about the connector. See the row-15 note in README.md.",

Copilot AI review requested due to automatic review settings August 19, 2026 02:44
@jeremy

jeremy commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

Round 3's second suppressed finding — fixed in 961143eee

The suppressed comment on README.md:356 is right, and it is the sharper kind of finding for this PR specifically: the correction landed and its own summary was left behind.

Adding row 16 left the heading reading fifteen against sixteen rows, and withdrawing row 15's whole-kill claim left the introduction still saying every mutation is shown red against at least one fixture before it counts. So a document whose entire job is to state precisely what tier 2 proves was overstating the suite in two places at once — including in the first line a reader sees.

Both now say so, with the exception stated at the top rather than left to be discovered in row 15:

One row is an exception, and it is the reason this heading is worth reading twice. Row 15's mutation lives BELOW the tier-2 seam, so this suite kills only the half above it […] A matrix that counted the partial row as a kill would be making exactly the class of claim this family exists to check.

I swept the family for the rest of the class before fixing, as with the schema corrections: these two lines are the only places the matrix's size or completeness is asserted anywhere under conformance/event-feed/.

The count is now checked mechanically rather than by eye — the sixteen rows are contiguous 1..16 and the heading agrees. Gates re-run green: make event-feed-fixtures-check REAL_EXIT=0 (LC_ALL=C.UTF-8, per #774), make doc-constants-check REAL_EXIT=0 (LC_ALL=C).

This one is a plain fix, not an escalation — it is a different mechanism from the advance-guard class in the comment above, which stays stopped pending a decision on the predicate.

jeremy and others added 29 commits September 12, 2026 17:28
…ss claim

Copilot's fourth round: four findings, all taken.

* DueWithin's doc claimed "exactly the set an Advance(d) would fire",
  and Advance fires more than that: it re-evaluates after each fire, so
  a timer armed reentrantly by a firing's recipient fires inside the
  window without ever appearing in DueWithin's answer —
  TestClock_AdvanceFiresATimerArmedByAFiringsRecipient demonstrates it.
  The doc now names what is actually read: the INITIAL due set, a floor
  when non-empty and exact when empty (no first firing means no
  reentrant arm), which is the asymmetry the advance guard stands on.
  AdvanceIfQuiet's return doc carried the same overclaim one step
  removed ("the due set") and now says "the initially due set" with the
  same reasoning.

* The registry's `site` and `file` were validated through to_s, which
  coerces [] and {} and 123 into non-empty text — a row with no
  human-readable location reached the issue check, and a non-String
  `file` could never match the sweep's [file, issue] pair: a promise
  registered into a shape nothing reads. Both now require a non-blank
  String, with the offending value named in the error. Red-proven: the
  two new self-test cases were run against the previous checker first
  and both failed with "expected FAILURE but checker passed"; all 25
  cases pass after the fix.

* §23's zero-egress paragraph led with "Zero egress to the foreign
  origin is a Layer-1 obligation, not tier-2 coverage" — a subject that
  swept all three hostile-URL cases while its justification described
  only the redirect. Three review rounds read it the same wrong way,
  which makes the sentence the defect regardless of the defense: the
  family README (placeholder rule, rows 26/27) affirmatively requires
  tier-2 zero-request coverage for connector-visible hostile targets,
  and tier 2 does own it — a connector that follows one hands the URL
  to the poll seam, which the driver observes and fails. The paragraph
  now splits at the seam: fixtures 26/27's structural zero-request
  coverage is tier 2's, and only the redirect Location — which the
  driver reduces to an origin before the connector sees anything — is
  the Layer-1 obligation, recorded as pending in the row-15 note. This
  narrows the earlier decline of the same finding: the operative
  artifacts were never in conflict, but a sentence that needs this much
  defending three rounds running earns the rewrite.

Gates: tracking-issues self-test 25/25; the live checker sweeps clean
and verifies all 22 entries (five issues open); doc-constants-check
green under LC_ALL=C; both eventfeed Go packages green; gofmt clean.
Copilot's fifth round: two findings, both taken.

* The by-holder ceiling missed the codec's copy. parseFrame unmarshals
  a message frame's payload into a json.RawMessage — a copy — while the
  original bytes are still in hand, so the state machine's in-hand term
  briefly costs two frame-sized allocations, not one, for a near-limit
  message. Verified at head: all four parseFrame call sites are
  state-machine side (catchup.go, loop.go dispatch), so the pump's term
  is untouched and the copy is not a fourth party — it is a WEIGHT on a
  holder the enumeration already counts, which is why the closure
  argument survives: each holder is now counted at its worst-case
  weight. The ceiling becomes
  (pump depth + 4 + EVENT_FEED_LIVE_BUFFER_CAPACITY) × MAX_FRAME_BYTES,
  the +4 being the deferral slot, the pump's in-hand frame, and the
  state machine's in-hand frame at its decode-time weight of two — the
  copy transient and bounded by one extra frame. The catchup.go mirror
  and both test comments carry the same figure.

* The gate's "no-op when the allowlist references no issues, which is
  today's state" rationale predated the seeded registry: every run now
  makes live lookups regardless of the allowlist. The script header,
  the Makefile's test-target comment, and the workflow step comment now
  say what is true — a green live run proves only that today's
  referenced issues are open, and the offline self-test is what covers
  the failure branches (closed issues, malformed rows, the fail-closed
  paths) a green run never exercises.

Gates: both eventfeed Go packages green, gofmt clean; tracking-issues
self-test 25/25; the live checker sweeps clean and verifies all 22
entries (five issues open); doc-constants-check green under LC_ALL=C.
… shapes

Copilot's sixth round. The ceiling finding reached its third increment
(+3, +4, now +5), which per AGENTS.md is the signal to reassess the
instrument rather than bump the number again — so the number is now
DERIVED instead of discovered.

* The whole decode chain was read end to end: a message frame exists in
  the state machine's hands as exactly THREE frame-scale
  representations — the wire bytes (still in scope at every
  decodeMessageEvent call site: catchup.go dispatch arms, loop.go),
  parseFrame's json.RawMessage payload copy, and the decoded Event's
  strings — each step's output allocated while its input is still in
  hand, and an adversarial near-limit payload can put frame-scale text
  in any of them. The state machine's weight IS that representation
  count: a fourth term requires CODING a fourth representation into the
  chain, not noticing one, which is what ends the increment series. The
  ceiling becomes (pump depth + 5 + capacity) × MAX_FRAME_BYTES, with
  the closure statement in SPEC and the catchup.go mirror, and both
  test comments at the same figure.

* Verified for the buffer term, and stated: a buffered Event retains
  only the chain's LAST representation — Go's decoder never aliases its
  input buffer, so the Event's strings are copies, and Event carries no
  raw-bytes field — so capacity × MAX stands and admission sheds the
  first two representations.

* A registry or allowlist file whose YAML top level is not a mapping
  ([] or a bare string) crashed the checker at .key? / Array#[String]
  with a backtrace — nonzero, but a backtrace is not a diagnosis. Both
  loads now validate the top-level type with the gate's own error.
  Red-proven: the two new self-test cases were run against the previous
  checker first; the registry case failed with the literal
  "checker:115: undefined method 'key?' for an instance of Array
  (NoMethodError)" and no diagnostic. All 27 cases pass after the fix.

Gates: tracking-issues self-test 27/27; the sweep half of the live
checker is clean on this tree, and the five issue lookups failed CLOSED
twice on GitHub's shared-quota 403 — the same 22 entries verified open
on the last quota-healthy run, and the next green run covers this tree;
doc-constants-check green under LC_ALL=C; both eventfeed Go packages
green; gofmt clean.
Copilot's seventh-round suppressed finding: YAML.safe_load raises
Psych::SyntaxError for malformed syntax, so a broken registry or
allowlist still exited through a Ruby backtrace naming psych/parser.rb
rather than the file to fix — fail-closed, but useless. Both loads now
rescue Psych's exception family (syntax errors, forbidden tags, and the
aliases safe_load refuses are all the same "this file cannot be
trusted" verdict) and fail with the gate's message naming the path.

Red-proven: the two new self-test cases were run against the previous
checker first and both failed through the literal Psych::SyntaxError
backtrace; all 29 cases pass after the fix. The self-test harness
gains raw-bytes overrides for both inputs, since YAML.dump can never
produce the malformed file the cases need.

The round's other two findings are handled outside this commit: the
G1b ownership claim needs an issue filed before it can be cited and
registered (reported to the coordinator with the prepared rewrite),
and the PR description correction is the coordinator's to apply.

Gates: tracking-issues self-test 29/29; live checker green (sweep
clean, all 22 entries verified, five issues open);
doc-constants-check green under LC_ALL=C.
The Layer-1 seam-adapter obligation — the 302 redirect-refusal test
that fixture 30's corrections assign below the poll seam — was cited
five times as "tracked for G1b", a program-milestone label defined
nowhere in this repository and linked to no issue: undiscoverable, and
invisible to the tracking registry. #819 now exists and owns it.

All five spans (README rows 15 and 30, the row-15 note, fixture 30's
description, schema.json's fixture-30 respond variant) now say
"tracked in #819" in the canonical grammar, and the three files carry
registry rows, so the discovery sweep enforces the citations and the
gate fails the day #819 closes without the adapters landing — which is
the forgettable-silently failure the review named.

Gates: the live checker verifies #819 open in all three rows and
reports all 6 referenced tracking issues open; tracking-issues
self-test 29/29; event-feed-fixtures-check green including both pin
probes (fixture 30 and schema.json changed); doc-constants-check green
under LC_ALL=C.
…itation

Copilot's eighth round: two findings, both taken.

* The sweep's line-leader class knew comments but not quotes: a
  canonical promise wrapped in a Markdown blockquote — "tracked\n>
  separately in #N" — read as absence, in the rendered prose where a
  promise reads most like one. The leader class gains `(?:>[ \t]*)+`,
  covering nested quotes. Red-proven: the blockquote corpus case was
  run against the previous detector first and failed with "expected
  FAILURE but checker passed"; all 30 cases pass after the widening.

* SPEC §23's zero-egress paragraph was the one changed prose site that
  assigned the below-seam obligation to Layer-1 without citing #819 —
  so after #819 closed, the other five sites' registry rows could be
  cleaned up while this paragraph stayed stale. It now ends with the
  canonical citation, and SPEC.md carries the registry row, so the
  sweep enforces it with the rest.

Gates: tracking-issues self-test 30/30; the live checker verifies all
26 entries and reports all 6 referenced tracking issues open;
doc-constants-check green under LC_ALL=C.
Copilot's ninth round, finding 1: the schema's ms fields carried no
maximum, so a schema-valid advance.ms of 9223372036855 — one past the
largest ms count whose int64-nanosecond product fits — multiplied
NEGATIVE in Go's millis(), and AdvanceIfQuiet, asked to advance by a
negative window, silently REWOUND virtual time. Drivers diverge on an
input the schema blesses.

Both sides now bound it. Every ms-carrying field (the five config
durations, advance.ms, both assertDelayMs bounds — the whole
duration-carrying class; liveBufferCapacity and dedupeCapacity are
counts, not durations) carries maximum: 315576000000. The bound is 10
virtual years, and it is a DOMAIN bound, stated in the schema and the
driver const: scripts age tickets by minutes to days (largest today
~11 days, 300× headroom), and the schema should say what a script can
MEAN rather than restate one language's integer layout — while sitting
~29× under the int64-nanosecond line so no conforming representation
can overflow. The Go driver enforces the range at load through one
helper (checkScenarioMs), so an out-of-range value is a fixture error
in every driver, not a representation accident in one.

Red-proven: three new load-rejection selftests (advance.ms, the
assertDelayMs envelope, config stalenessMs) were run against the
previous driver first and all failed — the overflow advance case
consumed its 5s watchdog, the accepted-rewind in action. Green after,
with event-feed-fixtures-check green (schema changed; all 31 fixtures
and both pin probes verify).
Copilot's ninth round, finding 2: the sweep's [file, issue] coverage
was a grant without a number — once a pair was registered, every FUTURE
canonical mention of that issue in that file was silently covered,
violating the registry's one-entry-per-claim contract (README carries
three #819 mentions on one row, python's todolists.py two #578). The
repository's instrument for exactly this is doc-constants' committed
marker counts, and the registry now uses it.

Every row commits `mentions:` — the number of canonical "tracked in #N"
mentions it stands for, 0 for a promise phrased outside the canonical
grammar — validated like `issue`/`site`/`file`, with duplicate
[file, issue] rows rejected (they would make the count ambiguous). The
sweep verifies the number in both directions: a second mention fails
until the count moves, and a deleted sentence fails until it moves
back. All 26 rows carry their true counts (13 canonical mentions
across the tree), computed with the sweep's own grammar.

Red-proven: four new self-test cases were run against the previous
checker first and all failed with "expected FAILURE but checker
passed" — the drifted count (2 swept, 1 registered), the deleted
mention (0 swept, 1 registered), the missing count, and the duplicate
pair — while the matching-count control passed both sides. All 35
cases pass after the fix.

Gates: tracking-issues self-test 35/35; the live checker sweeps clean
with counts verified and reports all 6 referenced tracking issues
open; doc-constants-check green under LC_ALL=C.
Copilot's tenth-round finding: maxScenarioMs (315,576,000,000) exceeds
MaxInt32 while the fixture structs and validator used platform-width
int, so GOARCH=386 fails to COMPILE at the comparison — and typing
only the constant would trade the compile error for a decode error,
since schema-valid values above MaxInt32 could not land in the int
structs. The ms values are now int64 end to end: the five config
durations, advance.Ms, the assertDelayMs envelope, checkScenarioMs,
and millis() itself, with the width rationale on the validator.

Red-proven with the 32-bit compile check: GOOS=linux GOARCH=386 go vet
against the previous code fails with "maxScenarioMs (untyped int
constant 315576000000) overflows int" at the cited line (darwin/386 is
not a valid pair, hence the linux GOOS); after the conversion — and
after typing the constant itself int64, which the first pass missed
because the untyped constant still defaulted to int inside
fmt.Errorf's variadic — the same vet exits 0.

Gates: GOOS=linux GOARCH=386 go vet clean; native suite green for both
eventfeed packages, plain and -race; gofmt clean.
…p keys

Copilot's eleventh round: three findings taken, one already fixed.

* The cable-lane retention ceiling was presented inside the shared
  six-SDK contract while its proof is Go-shaped — goroutines,
  json.RawMessage, a copying decoder — and Appendix F already records a
  lane where the per-item premise fails: TypeScript's default
  global-WebSocket transport cannot pre-bound a read, so an oversized
  message is allocated whole at receipt, before the max-frame check
  drops it. The formula is now scoped explicitly to the GO REFERENCE
  IMPLEMENTATION and transports with bounded reads: every SDK inherits
  the shape (bounded queue, blocking hand-off, single deferral slot,
  buffer as the only drop point) but re-derives its own weights, the TS
  divergence is cited where the ceiling is stated, and no universal
  cross-SDK byte ceiling is published.

* The registry header still said "one entry per sentence" while the
  validator rejects duplicate [file, issue] rows and commits one
  mentions count — instructions that would lead a maintainer to write
  rows the gate refuses. The header now describes the actual unit: one
  [file, issue] entry, sentences in the same file sharing the row and
  summing in its count, with bump/decrement as the maintenance verbs.

* YAML keeps only the LAST of duplicate mapping keys, so a second
  sdk_routes_known_defective: key REPLACED the first list — every issue
  reference in it gone, with nothing left malformed for shape
  validation to see. Both loads now walk Psych's parse tree (which
  still holds both keys) before safe_load flattens them, failing with
  the gate's diagnostic on any duplicated key at any depth. Red-proven:
  the two new self-test cases — a duplicated allowlist key hiding a
  defective route, a duplicated registry key hiding a promise — were
  run against the previous checker first and both passed it ("expected
  FAILURE but checker passed"); all 37 cases pass after the fix.

Already fixed, no change: the 32-bit int overflow finding was reviewed
against fed7b20, one commit before e42277e modeled ms values as
int64 end to end; GOOS=linux GOARCH=386 go vet is clean at this head.

Gates: tracking-issues self-test 37/37; live checker green (sweep and
counts verified, all 6 issues open); doc-constants-check green under
LC_ALL=C.
Codex's P2 plus three Copilot findings, all taken.

* The firing-keyed advance guard read the due set from a settled state,
  but nothing guaranteed the state HAD settled: an action's completion
  can precede the timer arms its transition causes — expectConnect
  returns when the dial is recorded, while the handshake deadline arms
  on the connector's goroutine after — so an advance right behind an
  action raced those arms, rejected on one schedule and accepted with
  time moved past a deadline about to arm on the other. The earlier
  rounds' conclusion stands (pending arms are undetectable in general);
  what is new is that the DRIVER controls the preceding expectation, so
  the rendezvous is AUTHORED rather than guessed: every advance must be
  the scenario's first step or immediately follow expectTimers, whose
  exact-set match is the settle (§23's per-state exact-set invariants
  are what make a match mean settled), enforced at fixture load and
  stated in the schema, README, and SPEC §23. The racing self-test
  script and fixture 05 gain their rendezvous steps (the fixture's
  expectState observed the state, not the timer set — the same race one
  door over).

  Proof, honestly: 60 rounds of the racing case never lost the race on
  this machine (the arm wins natively), so the live flake has no
  captured red; the deterministic red is the load rule — the
  unrendezvoused script, run against the previous driver, was rejected
  with the race-dependent "would fire [handshake-deadline]" verdict
  where the rule now names the missing rendezvous, and on the other
  schedule that same script would have advanced silently.

* Psych.parse("") returns false, not nil, so an empty registry or
  allowlist crashed the duplicate-key walk on false.root. Falsy parse
  results are empty documents; the shape validation speaks in its own
  words. Red-proven: the empty-registry case crashed the previous
  checker with the literal "undefined method 'root' for false
  (NoMethodError)"; all 39 cases pass after.

* The cable-lane ceiling counts FRAMES, and now says so against the one
  non-frame item: the read error that ends the pump — at most one per
  attempt, because the pump exits by sending it — rides the queue or
  the deferral slot with a SIZE the seam contract does not bound. The
  built-in transport's errors are bounded by construction (fixed
  shapes, renderings from configured text or placeholders, never server
  bytes); a custom transport's error is its author's to bound. Stated,
  not capped.

* The kill-matrix invariant still said every mutation is red-proven
  while row 15 sits in the table pending Layer-1. It now says fifteen
  of sixteen, names row 15 as the recorded exception pending the
  Layer-1 adapters #819 tracks, and points at the note that accounts
  for it — phrased outside the canonical grammar, so the README's
  committed mention count is unchanged.

Gates: both eventfeed packages green, plain and -race;
event-feed-fixtures-check green (fixture 05 and the schema changed;
all 31 fixtures and both pin probes verify); tracking-issues self-test
39/39; live checker green (all 6 issues open); doc-constants-check
green under LC_ALL=C; gofmt clean.
…ment

Codex's re-raised P2 and Copilot's multi-document finding, both taken.

* The premise of the re-raise is half wrong and half right, and the
  half matters. expectTimers is NOT an instantaneous comparison — the
  step dispatches to awaitTimers, which polls under the scenario
  watchdog until the outstanding set exactly matches (the snapshot
  compare at stepSatisfiedLocked is the lookahead probe, not the
  executor). What survives is the informationless-match class: a match
  can only mean settled when the authored set could not exist BEFORE
  the preceding transition was processed. Codex's own example is the
  decidable case — an empty set can never contain an arm of the
  preceding transition, so {"exact":{}} after a released failed mint
  matches before backoff arms, exactly as if the rendezvous were
  absent — and the load rule now rejects it: an advance's rendezvous
  set must be non-empty (a scenario with nothing yet armed advances as
  its first step). The limit no load rule can close is stated in the
  schema, README, and SPEC §23 instead of implied: the authored set
  must include at least one timer the preceding transition ARMS, and a
  transition that only rearms a timer of the same kind and count is
  invisible to set matching — such scripts use fireTimer. Red-proven:
  the empty-rendezvous script against the previous driver consumed its
  5s watchdog (empty match accepted, advance accepted) and failed only
  for want of the fragment the rule now emits.

* Psych.parse and safe_load read only the FIRST YAML document, so
  everything after a `---` separator — registry rows, allowlist
  entries — silently vanished from the live check. The gate now parses
  the STREAM and rejects a file holding more than one document with
  its own diagnostic. Red-proven: a two-document registry hiding a row
  and a two-document allowlist hiding a defective route both passed
  the previous checker ("expected FAILURE but checker passed"); all 41
  cases pass after the fix. The empty-file path moved with it: no
  documents at all is the empty-document case, and the shape
  validation still speaks.

One honest note: a single -race batch run failed once with its output
discarded by the batch's redirect; three subsequent captured runs
(-count=1 and -count=2) are clean with no data-race report, consistent
with the wall-clock-under-load flake class #794 deflaked — not
reproduced, and nothing in this commit touches connector concurrency.

Gates: tracking-issues self-test 41/41; live checker green (all 6
issues open); event-feed-fixtures-check green (schema changed); both
eventfeed packages green, plain and -race ×3; doc-constants-check
green under LC_ALL=C; gofmt clean.
…teps

Copilot's explicit-zero finding and Codex's third settle round, both
taken — and the second fix caught the first live instance of its own
bug class inside our own self-test.

* Explicit zero is not omission. The five config durations decoded into
  plain int64, so {"stalenessMs":0} was indistinguishable from absence:
  the range check skipped it and the driver silently used the default —
  accepting a value the schema rejects (minimum 1) and substituting
  another. The fields are pointers now; every SUPPLIED value is ranged,
  explicit zero included, and the unmodeled backoff pair is rejected at
  ANY supplied value (explicit zero used to slip that check too).
  Red-proven: four zero-boundary self-tests (the three ranged
  durations, one unmodeled) all failed against the previous driver.

* The settle instrument, round three. Codex's counter is real: the
  welcome transition stops handshake-deadline and arms
  confirmation-deadline in separate clock acquisitions, so an exact-set
  match ALONE can coincide with a transient mid-surgery set. The audit
  of every setState site found the codebase already holds
  "surgery before announcement" deliberately at streaming,
  awaiting-welcome, and awaiting-confirmation — with Backoff and
  Connecting announcing before a single tail arm. That shape is what
  makes the TWO-STEP rendezvous sound without touching the connector:
  expectState blocks until the transition announces (bounding the
  surgery), and any timer still unarmed at an announcement is exactly
  what the following exact-set match waits for. Every advance must now
  be first or follow expectState + expectTimers, enforced at load;
  both steps block under the watchdog, so wrong authorship fails
  loudly rather than diverging. The residue is stated, not implied: a
  transition that announces no state change or only rearms a same-kind
  same-count timer is invisible to both barriers — such scripts use
  fireTimer. Schema, README, and SPEC §23 carry the rule.

  The vindication: adding the state barrier to the racing self-test
  immediately failed it with "outstanding timers
  map[handshake-deadline:1 staleness:1], want exactly
  map[handshake-deadline:1]" — the old expectTimers-only script had
  been matching {handshake-deadline:1} in the pre-staleness TRANSIENT,
  the precise coincidence Codex described, live in our own suite. The
  script now authors the true settled set. Red-proven for the rule
  itself: the barrier-less script against the previous driver was
  rejected only by the race-dependent "would fire" verdict, never the
  rendezvous diagnostic; fixture 05 already conformed.

Gates: both eventfeed packages green, plain and -race;
event-feed-fixtures-check green; tracking-issues live checker green
(all 6 issues open); doc-constants-check green under LC_ALL=C; gofmt
clean.
… coded

Copilot's presence saga round two and Codex's ceiling round four, both
taken.

* *int64 preserved explicit zero but read explicit JSON null exactly as
  omission: {"stalenessMs":null} silently defaulted while the schema's
  "type": "integer" rejects it, and null slipped the unmodeled backoff
  rejection too. The five config durations are now a three-state
  optionalMs — absent, null, value — via a value-type UnmarshalJSON
  (encoding/json calls it for null where it short-circuits a pointer's,
  which is why the pointer could not see the difference). Everything
  SUPPLIED is judged: a value is ranged, null is refused with the
  schema's own reasoning, and the unmodeled pair rejects any supplied
  state including null. The driver keeps enforcing the schema's
  judgments portably; this is the same charter that put the range check
  in Go. Red-proven: five explicit-null self-tests (three ranged
  durations, both unmodeled) all passed the previous driver silently.

* The ceiling's count moved because the chain moved. Re-derived at this
  head, decodeMessageEvent no longer unmarshals the payload straight
  into a typed struct: the exact-spelling decode (the null-vs-absent
  frame-fields work) goes through a per-field map[string]json.RawMessage
  first, whose field copies sum to frame scale when one field dominates.
  A near-limit correlated message therefore exists as FOUR coexisting
  frame-scale representations — wire bytes in the dispatching caller's
  hands, parseFrame's envelope RawMessage copy, the per-field RawMessage
  map, and the decoded Event's strings — until the decode returns. The
  ceiling becomes (pump depth + 6 + capacity) × MAX_FRAME_BYTES, with
  the four-link enumeration IN the SPEC text so the count is checkable
  by reading, and the closure statement now records how the count last
  moved: not by a fifth review noticing a party, but by a representation
  being CODED into the chain — exactly the growth mode the rule names.
  The earlier "three" was correct for the chain as it stood when it was
  derived; the chain changed underneath it in a base merge, which is the
  strongest argument yet for keeping the enumeration next to the number.
  All four mirrored sites carry the figure.

Gates: both eventfeed packages green, plain and -race; tracking-issues
live checker green (all 6 issues open); doc-constants-check green under
LC_ALL=C; gofmt clean.
Codex's settle round four is declined with the mechanism examined and
the contract strengthened; Copilot's ghost-file finding is taken.

* Ghost rows. A mentions: 0 row guards a non-canonical promise by NAME
  alone, so a rename, delete, or typo made swept.fetch empty, 0 == 0
  passed, and the row silently guarded nothing — green until the issue
  closed and CI blocked on a phantom promise. Every registered path
  must now be in the sweep's view (present, text, not excluded), failing
  closed with the row named. Red-proven: the ghost-file row passed the
  previous checker. The check also flushed out four self-test cases
  registering rows for files absent from their corpora — including the
  deleted-mention case, which now stages its file present with the
  sentence gone, truer to what it tests. All 42 cases pass.

* The rearm rendezvous, examined rather than re-patched. Codex asks the
  barrier to require a NEWLY observed transition (a generation tied to
  the preceding action). The mechanism was worked through and it
  reintroduces the disease one level up: whether a state announcement
  lands before or after the PRECEDING expectation completes is itself a
  scheduling race — fixture 05's streaming announcement races its
  expectCheckpoint's completion — so generation-newness would make the
  suite's one legitimate advance flaky. Reachability was checked
  honestly: no committed fixture advances behind an announcement-free
  rearm. Fixture 05's serves are protocol frames whose receipt-side
  staleness resets happen-before the announcements its rendezvous
  observes, and its config pins staleness and repair-poll at ~11.5
  virtual days against a 121-second window — the schema's own
  stalenessMs guidance — so old and new deadlines alike sit far outside
  the advance. What changes is the contract's specificity: the residue
  paragraph in README and SPEC now names the concrete case (a served
  live frame's pump-side staleness rearm), the sanctioned mitigation
  (override stalenessMs large, as fixture 05 does), and fireTimer for a
  script that wants the firing itself — a stated limit with a worked
  example instead of an abstract sentence.

Gates: tracking-issues self-test 42/42; live checker green (all 26
registered files visible to the real sweep, all 6 issues open);
doc-constants-check green under LC_ALL=C.
…t was

Copilot's envelope presence gap and ceiling round five, both taken —
the second by reframing the instrument rather than writing a fifth
number.

* assertDelayMs had every presence gap the config durations just
  closed: the pointer read "assertDelayMs": null as omission, silently
  dropping the assertion the script wrote, and inside a non-null
  envelope an absent or null min/max decoded to int64(0) — in range,
  silently a different envelope than the one authored, though the
  schema requires both members. The envelope is now three-state
  (optionalEnvelope) with three-state members (optionalMs): null
  refused at both levels with the schema's reasoning, an absent member
  a fixture error, values ranged, min ≤ max preserved — and the inner
  decode re-establishes DisallowUnknownFields, which the outer strict
  decoder cannot reach through a custom unmarshaler. Red-proven: three
  self-tests (null envelope, missing min, null member) all passed the
  previous driver silently.

* The ceiling treadmill's fifth round named map bucket storage and
  copied keys — overhead proportional to MEMBER COUNT, which no
  per-representation count can bound: a frame of many tiny members
  inflates the maps past any chain-length figure. That is the signal
  the number was conflating two different questions, so the accounting
  now splits. RETAINED storage — what a consumer sizes against: frames
  and events held across blocking points, by holder — is
  (pump depth + 3 + capacity) × MAX_FRAME_BYTES, the enumeration that
  was stable before decode weights entered it. TRANSIENT decode-time
  allocation rides on top, per frame, in the state machine's hands
  alone: the representation chain plus decoder overhead proportional
  to member count, bounded by a small implementation-topology multiple
  that the SPEC deliberately does NOT publish — it moved when the
  exact-spelling decode was coded in, and pinning it would turn every
  decoder refactor into a spec change. What is contract: transients
  exist one frame at a time, between dequeue and decode return, so
  peak memory is the retained formula plus ONE frame's transient —
  never a per-slot or per-queue multiplier. All four mirrored sites
  carry the split.

Gates: both eventfeed packages green, plain and -race; tracking-issues
self-test 42/42; live checker green (all 6 issues open);
doc-constants-check green under LC_ALL=C; gofmt clean.
Copilot's number-model finding, taken red-first. draft 2020-12's
"integer" is any number whose MATHEMATICAL value is integral — 1000.0
and 1e3 are integer instances a schema-valid fixture may carry — while
json.Unmarshal into int64 judges the spelling, so those fixtures failed
only in the Go driver, against the loader's portable-enforcement
charter. This is the repo's float-spelled-int class again, the one
FlexInt absorbs on the rich-text lane.

Every ms field now parses through one helper: json.Number first as
int64, else as float64 with an integrality check — decidable without
precision loss, since every in-range value sits far below float64's
2^53 exact-integer ceiling (maxScenarioMs ≈ 3.16e11), which the helper
states. A non-integral number is refused in the schema's terms ("is
not an integer: the schema's type is integer — a number whose
mathematical value is integral"), never in encoding/json's; a number
beyond 2^53 is refused as beyond any modeled value. optionalMs (the
five config durations and both envelope members) and advance.ms (a new
scenarioMs type, so the field-level unmarshaler leaves decodeStrict's
unknown-key strictness intact) share the helper.

Red-proven: 1000.0 and 1e3 failed to load against the previous driver,
and 1000.5 was refused with encoding/json's message rather than the
schema's — five cases across the focused spelling test and the
rejection table, all red first, all green after, with the accepted
spellings asserted to decode to exactly 1000.

Gates: both eventfeed packages green, plain and -race; doc-constants
green under LC_ALL=C; gofmt clean.
…nvert

The Lint failure and Copilot's + Codex's three threads (two of them the
same defect), folded into one commit.

* Lint: golangci-lint's unconvert flagged the int64() around a field
  that is already int64 in the new spelling test. Dropped; the local
  run reports 0 issues.

* Integrality is a fact about the TEXT, and the float64 detour erased
  it: Float64 rounds 1000.00000000000001 to exactly 1000 — and
  315575999999.99999 to exactly the maximum, which then PASSED the
  range check too — before any Trunc could look. The 2^53 argument
  guaranteed exact representation for integers, not for arbitrary
  decimals; both threads are right. The literal json.Number preserves
  is now judged exactly with big.Rat: integral iff the rational's
  denominator is one, ranged via the numerator's IsInt64, no float
  anywhere. An exponent is read as text first and refused for its
  magnitude outside ±40, so an exponent bomb (1e999999999) is never
  materialized into a billion-digit rational.

* json.Number's own Unmarshal accepts quoted numeric strings, so
  {"ms":"1000"} decoded and passed every later check while the
  schema's type refuses string instances. The first non-space byte
  decides: a leading quote is refused as a string in the schema's
  terms before any number parsing.

Red-proven: five new cases against the previous parser — the
rounding-boundary fraction and the near-maximum fraction both ACCEPTED
(the latter as the maximum itself), both quoted spellings ACCEPTED,
and the exponent bomb refused with the wrong words — alongside the
exact-maximum float spelling accepted both sides as the control. All
green after, with accepted spellings asserted to decode to the
literal's integral value.

Gates: both eventfeed packages green, plain and -race; golangci-lint
0 issues locally; doc-constants-check green under LC_ALL=C; gofmt
clean.
Both bots caught the same edge in the exponent guard: a significand can
offset any exponent, so 1e44-digits x e-41 is exactly 1000 and the
lexical +/-40 cap rejected schema-valid integers. Two exact string
judgments replace it: the most significant nonzero digit's decimal place
caps the value (above place 13 nothing fits the range), and a least
significant nonzero digit below the units place is non-integral outright
(decimal digits do not carry) -- which refuses 1e-999999999 without a
billion-digit denominator. A 100000-character literal cap comes first so
no multi-megabyte number is walked into a rational.

The test table (authored before the session limit took the subagent that
started this round) covers both bots' offset spellings, the fraction-led
spelling of one, both bombs, and the parse-bomb literal; run against the
pre-fix parser the offset cases fail to load, which is the red proof.
Copilot round: the peak equality read as a process-memory bound while
the section itself allows an unbounded transport-authored error item and
counts no runtime metadata -- it now says peak FRAME-PAYLOAD retention
and names both exclusions. And the 100000-character literal cap was a
Go-only constraint the schema never declared (1 followed by 100k zeros
times e-100000 is mathematically 1): the schema's top-level description
now sanctions the bound for every driver as a resource limit on
spellings, not a value constraint, and the driver's comment points at
the sanction.
Copilot: the platform's max integer as an exponent wrapped
intLen - firstNZ + exp negative and bypassed the magnitude judgments.
The exponent is bounded at +/-200000 right after parsing -- the literal
cap already bounds the significand at 100000 digits, so no in-range
value needs more to spell -- and both integer-extreme spellings are
pinned. The negative-bomb case's expected message moves to the new
guard's, which fires first.
Codex: a zero significand skipped the magnitude guard (firstNZ stays
-1), handing the raw exponent onward. Zero now returns immediately --
integral however spelled, judged by the range check like any other zero
-- pinned by the 0e199999 case landing in the range refusal, never a
parse error.
Codex: 0e200001 is nine characters with value zero, and the exponent cap
ran first. The significand scan now precedes exponent parsing entirely
-- an exponent multiplies a significand, and a zero one needs no
arithmetic -- pinned by the beyond-cap zero landing in the range
refusal.
Settle rounds five and six, both declined as mechanism — with one
sentence of the contract corrected, because Codex's new observation is
true about the WORDING even though it changes nothing about the design.

Round five re-litigates the stated residue verbatim: a serve in
streaming whose pump-side staleness rearm races a following advance is
the contract paragraph's own named example, with its two sanctioned
outcomes (override stalenessMs large, as the suite's one advance does
at ~11.5 virtual days against a 121-second window; or fireTimer for
the firing the script actually wants). Reachability: exactly one
advance exists across all committed fixtures and it follows the
mitigation. Prior threads 3849496601, 3849540097, and 3849863896
carry the instrument history, including why the timer-generation
barrier was examined and declined — whether an announcement lands
before or after the preceding expectation completes is itself a
scheduling race, so generation-newness makes the one legitimate
advance flaky.

Round six shows the "fails loudly" sentence overclaimed by a schedule:
a pair naming the PRE-action state can pass on the schedule where the
action has not yet been processed, so wrong authorship is at worst
FLAKY — red whenever the transition lands first — never stably green.
That is a fact about the wording, not the design: the settled
guarantee always belonged to correctly authored pairs, and both
documents now say exactly that instead of implying every wrong script
fails every run. A loader-side transition table could reject
pre-action pairs statically, but it would be a second model of the
connector for the loader to drift against — a new instrument, not a
cheap strengthening — and no committed script authors a pre-action
pair.

Gates: doc-constants-check green under LC_ALL=C; tracking-issues live
checker green; no code changed, so the Go suites stand at this head's
green.
Fixture 30's description and the README's row-15 note both named
TestRedirectRefusalExposesOnlyTheLocationOrigin as the redaction proof's
owner; the test that actually feeds the secret-bearing cause and walks
the whole rendering and unwrap chain is
TestRedirectRefusalRendersNoServerValue. The described obligation was
met all along — only the name pointed at a ghost.
The exponent parse moves from Atoi to a fixed 64-bit ParseInt: int is 32
bits on some targets, where 1e9223372036854775807 failed as unreadable
before reaching the magnitude judgment and the diagnostic changed by
platform. A 64-bit overflow can only mean the exponent is past the
±200000 bound, so range errors take the bound's own verdict — pinned by
beyond-int64 cases that fail against the Atoi version on every width.

The schema's advance description and the rendezvous loader's comment
both still claimed wrong authorship "fails loudly rather than
diverging"; both now carry the README/SPEC qualification — a pre-action
pair can pass on the schedule where the action is not yet processed, so
wrong authorship is at worst flaky, never stably green.
…-numeric claim holds for every integer field

The schema's top-level description promises that drivers judge each numeric
literal by its exact mathematical value, so 1000.0 and 1e3 are integer
instances a schema-valid fixture may carry. The Go loader honored that only
for the ms-converted fields, which went through parseIntegralMs; every other
schema integer — a status, an id, a capacity, a close code — decoded straight
into int/int64, where encoding/json refuses 200.0 or 1e2 outright. So a
fixture the schema accepts was non-portable by this driver alone, and each
new integer field would have needed its own number type to opt in.

Replace the per-field parser with one walk over the whole document before
any typed decode: decode with UseNumber, judge every literal as text (the
100000-character cap, the exponent bound, the digit-place integrality and
magnitude judgments, then big.Rat for the exact value), rewrite each integral
literal to its plain integer spelling, and hand the typed script the result.
The walk's magnitude cap is int64, the widest integer any schema field
decodes into; the ms fields' 10-year range stays a value check after decode,
since its floor differs by field. optionalMs and scenarioMs keep only what
the walk cannot decide for them: the three JSON states and the refusal of a
quoted number as the string instance it is. A refusal names the member path.

The existing ms-spelling table keeps every case; its out-of-range spellings
are now named by the walk's bound rather than by "ms value". A new table
loads a status, an id and a capacity spelled 200.0 / 1e2 / 1.0 (and 2000e-1,
0.1e3) equal to their integer-spelled twin, and refuses one non-integral
literal in each position naming the member.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e23ade6c0a

ℹ️ 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".

"$id": "https://basecamp.com/schemas/event-feed-scenario.json",
"title": "Event Feed Connector Tier-2 Scenario",
"description": "One strictly-ordered interleaved script driving the SPEC §23 connector over its five seams: HTTP exchanges (mint/poll), cable frames, time directives, and observations, in one `steps` array. Strict-matched actions (mint, connect, poll, outbound frame, client close, checkpoint save) must each match their expect step under the per-action-class rules in this family's README ('Strictness semantics, per action class': saves and outbound frames are arrival-strict; mint/poll seam calls are parked and matched in order); observation directives (delivered, buffered, timers, state, signals, handler invocations) are rendezvous assertions evaluated cumulatively under a small wall-clock watchdog while virtual time is frozen. All counts (mintCount, connectCount) count SEAM CALLS — one fully-governed generated call each, with SPEC §7 retries inside — never wire attempts. Time flows only through the injected Clock seam; the virtual-advance algorithm and the ownership-cut/dequeued-frames semantics are normative prose in this family's README and SPEC §23. Validated by `make event-feed-fixtures-check`.",
"description": "One strictly-ordered interleaved script driving the SPEC §23 connector over its five seams. NUMERIC LITERALS: schema keywords judge mathematical value (1e3 and 1000.0 are integer instances) and drivers judge integrality and range on the literal's exact value — but a driver MAY refuse any numeric literal longer than 100000 characters regardless of value: a sanctioned resource bound on pathological spellings, not a value constraint. Continuing: HTTP exchanges (mint/poll), cable frames, time directives, and observations, in one `steps` array. Strict-matched actions (mint, connect, poll, outbound frame, client close, checkpoint save) must each match their expect step under the per-action-class rules in this family's README ('Strictness semantics, per action class': saves and outbound frames are arrival-strict; mint/poll seam calls are parked and matched in order); observation directives (delivered, buffered, timers, state, signals, handler invocations) are rendezvous assertions evaluated cumulatively under a small wall-clock watchdog while virtual time is frozen. All counts (mintCount, connectCount) count SEAM CALLS — one fully-governed generated call each, with SPEC §7 retries inside — never wire attempts. Time flows only through the injected Clock seam; the virtual-advance algorithm and the ownership-cut/dequeued-frames semantics are normative prose in this family's README and SPEC §23. Validated by `make event-feed-fixtures-check`.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Encode the integer ceiling in the schema

The new numeric-literal contract still permits integers outside the Go driver's range: for example, finally.mintCount: 9223372036854775808 satisfies this schema (the field has only minimum: 0), but the new global normalization rejects it at num.IsInt64(). Similar unbounded fields include capacities and counts, so a schema-valid merged fixture can be accepted by an arbitrary-precision driver while Go refuses it. Add explicit, portable maxima to bounded integer fields (including a safe capacity limit), or avoid applying the int64 ceiling globally.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conformance Conformance test suite github-actions Pull requests that update GitHub Actions go python Pull requests that update the Python SDK ruby Pull requests that update the Ruby SDK spec Changes to the Smithy spec or OpenAPI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants