Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
mergedhumanPR #278Other

fix(track): report an application whose acknowledgement was fenced

Follow-up to #264–#277, from the same correctness audit of main at df0b5f1.

Author

JamesCarnley avatar

@JamesCarnley

github profile →
Status
Merged
Merged on
Sep 14
Branch
fix/track-fenced-application-record

Description

The pitch.

Follow-up to #264–#277, from the same correctness audit of `main` at `df0b5f1`.
It merges cleanly with every other open PR in that batch.

This one was found by chasing an intermittent CI failure instead of re-running
the job.

### Problem

A seller can apply a quote request and send its reply, then have its
acknowledgement refused as a stale fence — or die outright — before the town
records it. Either way the redelivery was acknowledged as a bare duplicate, so
the `processed` stage reported "processed acknowledgements carry no application
record" and the run came out `incomplete`.

That blames the subject for the town's own lease timing, although the work was
applied exactly once. In the `quote-crash-restart` profile the lease is 1.5 s,
and on a slow machine the restarted seller can lose it between applying the work
and acknowledging it.

### Change

- The journal row carries an `unreported` mark, written in the same statement as
  the application, before any acknowledgement is attempted. The mark therefore
  exists from the instant the work does, so a death that nothing could observe
  cannot lose it.
- Only an accepted acknowledgement clears it.
- A redelivery of still-unreported work is acknowledged as a duplicate that also
  carries the application actually performed.
- The inbox loop gained one optional accepted-only callback. Its refusal path
  does no bookkeeping at all.

### Older journals upgrade in place

The mark is a new column on the journal's `processed` table, and
`CREATE TABLE IF NOT EXISTS` does not alter a table that already exists. So a
journal file written by an older Town would have crashed the participant with
`no such column: unreported`.

Normal runs use a fresh state directory each time, but `test-agent --wait` and
`run --agent ROLE=external` print a `STATE_DIR` that an operator is told to
replace with their own writable directory, so one reused across an upgrade would
have hit this. A participant crashing that way is exactly the "Town fault that
looks like the subject's" failure this PR removes.

The journal now adds the column when it is missing. Existing rows read as
reported, which is correct: those applications were acknowledged under the older
code, or are gone with the run that made them, so the upgrade cannot manufacture
a second application record. The migration also protects the buyer, the A2A
bridge and the model harness, which all build journals even though only the
seller sets the mark.

### Compatibility

- The coordinator HTTP contract, the bundled profiles, the bundle format and the
  evaluator are all unchanged.
- Exactly-once application still holds. The journal still prevents a second
  apply, and a duplicate whose application already reached the record is
  acknowledged without an application record, so an ordinary duplicate delivery
  still shows exactly one.
- `record()`'s default leaves the buyer, the A2A bridge and the model harness
  unaffected.

### Verification

- **Regression tests** cover the fenced acknowledgement, a crash between applying
  and acknowledging, two consecutive fences, and the unchanged duplicate case.
  The crash-window test fails on an earlier design that only reacted to a
  refusal, and passes on this one.
- **Delay sweep** around the 1.5 s lease (1.0, 1.4, 1.6, 2.5 and 5.0 s): 3/3 each.
- **Crash-restart end to end:** 30/30.
- **All eight Track profiles** pass, and so does `examples/byoa_seller.py`.
- **`nandatown verify`:** 36/36 bundles.
- **Full suite:** 823 passed, twice.

### Known limitation

Duplicate delivery *with the applied acknowledgement fenced* ends `incomplete`
at `duplicate_recognized`. The single redelivery acknowledgement carries both
`applied` and `duplicate`, which satisfies the runner's quiescence heuristic, so
the run ends before the town re-offers the work.

- It needs a stall of more than 5 s, which no shipped profile can produce.
- It is not a regression: `main` fails the same case earlier, at `processed`.
  This change strictly

…

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/278/head:pr-278
git checkout pr-278