Skip to content

Commit 4e6dc23

Browse files
fix(pm/fleet-write): re-list a lagging issue list before an issue_create read-back says unfound (#21278)
Fixes #21181 Clause-②: no ## What changed The `issue_create` leg of `readBackStroke` in `scripts/pm/fleet-write/dispatch.mjs` no longer judges `unfound` on its first list. If the repository issue list answers and no issue created since the dispatch carries the title sent, the read-back re-reads that same list on a bounded schedule before it may say `unfound`: - The schedule is `ISSUE_CREATE_RELIST_DELAYS_MS` = 3 s, 7 s, 15 s: 25 s in all. The self-test holds the sum to at most 30 s. - It prints one line per re-list, and one line naming the issue and the re-list that found it. Every re-list is a read: - ⛔ The create is never re-sent. The self-test asserts exactly one write per stroke, the dispatch, both on a lagging list and on a miss. - A re-list that cannot be read ends the wait as `unread`, as on the first pass. - An issue the last re-list still lacks is `unfound`, exit 6: UNCONFIRMED, read the board. Exit 6 means what it meant before. The reason now also says how often and over how long the list was read (`listed 4 times over 25000 ms`). The `pr_create` and `comment` read-backs are unchanged; the self-test pins `pr_create` as still read once. ## Why: the issue list lags a create (measured) The table gives each issue's age at the moment its relay run completed, for the five filings in the shift the card reports. The read-back takes its first list within one 5 s run poll of that completion. | issue | relay run | issue `created_at` | run completed (`updated_at`) | age at completion | read-back | |---|---|---|---|---|---| | #21096 | 36824566714 | 06:24:25Z | 06:24:28Z | 3 s | unfound | | #21114 | 36830404632 (matched by time window) | 07:27:26Z | 07:27:31Z | 5 s | IDENTICAL | | #21127 | 36835496924 | 08:18:56Z | 08:19:00Z | 4 s | unfound | | #21135 | 36840884334 (matched by time window) | 09:09:47Z | 09:09:52Z | 5 s | IDENTICAL | | #21181 | 36873699883 | 14:06:25Z | 14:06:28Z | 3 s | unfound | Age separates the outcomes exactly: misses at 3–4 s, hits at 5 s, n = 5. That points at list lag, not at the query. `since` filters on `updated_at`, which a brand-new issue always meets, and `sort=created&direction=desc` puts it first. The first re-list, 3 s after the first list, already puts every measured miss past the measured hit age. The 7 s and 15 s re-lists cover a list that lags harder under load. The `found on re-list K, N ms after the first list` line is the reading to retune the constants from. ## Why not the run's own report of the number (route A): probe readings Triage's first direction was to resolve the created issue from the relay run's report of the number it created. I probed that from this seat container against run 36873699883 (job 110407621005), on 2026-10-02: | read | answer | |---|---| | `GET /repos/objectstack-ai/objectstack/actions/runs/{id}` and `.../runs/{id}/jobs` | 200 | | `GET .../actions/jobs/{job}/logs` | 302 to `productionresultssa11.blob.core.windows.net` | | following that redirect | `CONNECT tunnel failed, response 403` (curl exit 56, http 000) | | `GET .../actions/runs/{id}/logs` | 302 (the same blob storage) | | `GET .../check-runs/{job}`, `output.summary` | null: the step summary is not exposed there | | `GET .../check-runs/{job}/annotations` | 200: three rows, two action-input deprecation warnings and one runner-image notice | The executor prints `#number url` only to the job log and the step summary, and a seat container can read neither, so route A does not exist as the relay stands. A readable channel does exist: the annotations endpoint answers 200. Using it would mean changing what the relay emits, for example a notice annotation printed by `execute.mjs`. That would be a new report channel: it goes live only once merged, and it costs two more reads per filing. It is named here and not built. Route B, the bounded re-list, lives entirely inside the read-back. It works as soon as a seat's checkout carries it, and its worst case is bounded. `execute.mjs` and `.github/workflows/fleet-write.yml` are untouched. ## Verification (at a5c52b9) - **Self-test:** `node scripts/pm/fleet-write/dispatch.mjs --self-test` gives `✓ fleet-write/dispatch self-test: 159 cases pass across 16 batteries`. Base `2791138c` gives 146 cases across 15 batteries. The battery floor moves 15 to 16, and the new battery is pinned at 13 cases. It covers: - the schedule's shape and its 30 s bound; - ⭐ the lagging list (the issue absent until 8 s after the dispatch) reading back IDENTICAL on that issue, exit 0, found on the second re-list at the declared cumulative waits `[0, 3000, 10000]`; - one line per re-list; - one write; - the control: a list that never carries the title is still `unfound`, exit 6, after reads at exactly `[0, 3000, 10000, 25000]`, with one write; - a stale same-title issue never taken; - an unreadable re-list reading `unread` (HTTP 503 named); - a list that already carries the issue read once; - `pr_create` still read once. - **Ablation:** this was run on the committed fix. `node scripts/ablation-replace.mjs` replaced `const relists = isPr ? [] : ISSUE_CREATE_RELIST_DELAYS_MS;` with `const relists = isPr ? [] : [];`. The anchor count went 1 to 0 and the blob `ccea8e06b6c6` to `0abc31df06c2`. - Result: red, 7 of 159. - The lag fixture returned the card's defect shape exactly: `["unverified","unverified","unfound","objectstack-ai/objectstack (new issue)",6]`. - The control, the one-write cases, the read-once case and `pr_create` stayed green. - The file was restored to blob `ccea8e06b6c6`, equal to HEAD, and `git diff HEAD` is empty. - **Gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived the same 36 families the dispatch named. All 36 exited 0, and `--ran` reports `✓ dispatch-gates --ran: 36 derived famil(ies) accounted for — 36 run, 0 NOT-MEASURED (a DERIVED zero …)`. That includes `pnpm check:pm-dispatch-gates`, run detached (`dispatch-gates self-test: 1976 cases pass`), and the fleet-write `dispatch` / `execute` / `validate` self-tests. - **Lint, scoped:** - `eslint --no-inline-config --format json scripts/pm/fleet-write/dispatch.mjs` returned 1 file, 0 errors, 0 warnings. - The file is in the population: it is not ignored, and the resolved config applies `no-restricted-imports` and `comment-swallow/no-code-inside-block-comment`. - Type-aware linting is not enabled (`parserOptions.project` and `projectService` are both null), so this diff cannot move any untouched file's verdict. The full `pnpm lint` stays CI's. - **Changeset:** `skip-changeset`, because `scripts/pm/**` publishes nothing. ## Acceptance notes - The `pr_create` read-back (the pulls list by head) and the `comment` read-back (the comments list since the dispatch) are also list-based, so in principle they can lag the same way. No false negative has been measured on either, so they are unchanged here. This is noted only, not filed. Carrier: none. - Five readings tuned the schedule. A shift's `found on re-list K` lines are the evidence for moving it. A miss that still reads `unfound` after 25 s, while the issue exists, is the evidence for widening it. --- _Generated by [Claude Code](https://claude.ai/code/session_01FNKm1SmPpuJASnbjxWGtsJ)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 748b240 commit 4e6dc23

1 file changed

Lines changed: 124 additions & 11 deletions

File tree

‎scripts/pm/fleet-write/dispatch.mjs‎

Lines changed: 124 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,30 @@
159159
* and over. `4` means what it means in every other fleet tool — the write
160160
* HAPPENED and the board disagrees: go READ it.
161161
*
162+
* ## The `issue_create` re-list — the repository issue list lags a create
163+
*
164+
* A new issue is found by LISTING the repository's issues, and that list lags
165+
* a create by seconds: measured over five relay filings in one shift, the three
166+
* whose issue was created 3–4 s before its run completed read back `unfound`
167+
* while the issue stood on the board with the title sent, byte for byte, and
168+
* the two created 5 s before read back IDENTICAL. (Not the query: `since` filters
169+
* on `updated_at`, which a brand-new issue always meets, and `sort=created`
170+
* puts it first.) So when an `issue_create`'s list ANSWERED and no issue
171+
* created since the dispatch carries the title sent, the read-back re-reads
172+
* that same list on the bounded schedule `ISSUE_CREATE_RELIST_DELAYS_MS` names,
173+
* one printed line per re-list and one when a re-list finds it, before it may
174+
* say `unfound`. A list that could not be read ends the wait as `unread`, as
175+
* on the first pass. Every re-list is a READ: ⛔ the create is never re-sent,
176+
* and an issue the last re-list still lacks is `unfound` — exit 6,
177+
* UNCONFIRMED, read the board — exactly what it meant before.
178+
*
179+
* Why a re-list and not the run's own report of the number it created: the
180+
* executor prints `#number url` to the job log and the step summary, and a
181+
* seat container reads neither — the job-log endpoint answers 302 to blob
182+
* storage that the egress proxy refuses (CONNECT 403), and the job's check run
183+
* carries a null `output.summary`. Making the run report it somewhere a seat
184+
* CAN read is a change to what the relay emits, not to this read-back.
185+
*
162186
* How the callers read the new outcomes — one exit vocabulary, no caller edited:
163187
* - `failure` + `notStored` keeps the state every caller already reads as "the
164188
* platform did not keep it whole — go READ, never fall back": post-stamped's
@@ -472,6 +496,19 @@ export function fallbackText(result, tool = 'fleet-write') {
472496
/** The clock slack a created object's timestamp may carry before the dispatch — post-stamped's relay read-back allows the same minute. */
473497
export const READ_BACK_SLACK_MS = 60_000;
474498

499+
/**
500+
* The `issue_create` read-back's bounded re-list (header): the wait before each
501+
* re-read of the list, in order, taken only while the list answers and the
502+
* title sent is not on it. Why these values: the measured misses were issues
503+
* 3–4 s old when their run completed, the measured hits 5 s old, and the first
504+
* list is taken within one 5 s poll of that completion — so the first re-list,
505+
* 3 s later, already puts every measured miss past the measured hit age, and
506+
* 7 s then 15 s cover a list that lags harder under load. 25 s in all, under
507+
* 30 s: a genuine miss pays the window once and still reads `unfound`. ⛔ Never
508+
* unbounded and ⛔ never a re-send of the create; the self-test holds the sum.
509+
*/
510+
export const ISSUE_CREATE_RELIST_DELAYS_MS = Object.freeze([3_000, 7_000, 15_000]);
511+
475512
/** Every op the table lets carry a `body` — derived from `ops.mjs`, never listed, so a new body op is read back the day it lands. */
476513
export const BODY_OPS = Object.freeze(OP_NAMES.filter((op) => [...OPS[op].required, ...OPS[op].optional].includes('body')));
477514

@@ -488,7 +525,7 @@ export const BODY_OPS = Object.freeze(OP_NAMES.filter((op) => [...OPS[op].requir
488525
export const READ_BACK_LOCATORS = Object.freeze({
489526
issue_patch: Object.freeze({ found: 'address', where: 'GET /repos/{repo}/issues/{issue}' }),
490527
comment_edit: Object.freeze({ found: 'address', where: 'GET /repos/{repo}/issues/comments/{comment_id}' }),
491-
issue_create: Object.freeze({ found: 'key', where: 'the newest issue created since the dispatch whose title is the title sent' }),
528+
issue_create: Object.freeze({ found: 'key', where: 'the newest issue created since the dispatch whose title is the title sent, the list re-read on ISSUE_CREATE_RELIST_DELAYS_MS before unfound' }),
492529
pr_create: Object.freeze({ found: 'key', where: 'the newest pull request on the head sent, created since the dispatch' }),
493530
comment: Object.freeze({ found: 'content', where: 'the newest comment on the issue, created since the dispatch, whose stored body holds the bytes sent' }),
494531
});
@@ -655,13 +692,16 @@ async function listAll(api, path, t, maxPages = 10) {
655692
* sent. Returns `{ state, rows }` — `state` from `strokeReadBackState`. Never
656693
* throws on a status: a read that fails makes its row `unverified`, with the
657694
* call and status in `why`. `taken` keeps two actions of one stroke from being
658-
* judged against the same created object.
695+
* judged against the same created object. `sleep` and `log` serve the
696+
* `issue_create` re-list alone (`ISSUE_CREATE_RELIST_DELAYS_MS`).
659697
*/
660698
export async function readBackStroke(payload, { dispatchedAt }, deps = {}) {
661699
const targets = readBackTargets(payload);
662700
if (!targets.length) return { state: 'none', rows: [] };
663701
const api = deps.api ?? DEFAULT_API;
664702
const t = { fetch: deps.fetch, token: deps.token };
703+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
704+
const log = deps.log ?? ((line) => console.error(line));
665705
const judge = deps.judge ?? (await loadReadBackJudge());
666706
const since = dispatchedAt - READ_BACK_SLACK_MS;
667707
const sinceIso = encodeURIComponent(new Date(since).toISOString());
@@ -685,14 +725,29 @@ export async function readBackStroke(payload, { dispatchedAt }, deps = {}) {
685725
const path = isPr
686726
? `/repos/${repo}/pulls?state=all&head=${encodeURIComponent(head)}&sort=created&direction=desc`
687727
: `/repos/${repo}/issues?state=all&sort=created&direction=desc&since=${sinceIso}`;
688-
const listed = await listAll(api, path, t, isPr ? 1 : 3);
689-
const hit = listed.rows.find((o) => created(o) && !taken.has(o.id) && (isPr ? true : !o.pull_request && String(o.title ?? '').trim() === String(target.title ?? '').trim()));
728+
const where = isPr ? `${repo} head ${head}` : `${repo} (new issue)`;
729+
const find = (listedRows) => listedRows.find((o) => created(o) && !taken.has(o.id) && (isPr ? true : !o.pull_request && String(o.title ?? '').trim() === String(target.title ?? '').trim()));
730+
let listed = await listAll(api, path, t, isPr ? 1 : 3);
731+
let hit = find(listed.rows);
732+
// `issue_create` alone: the list lags a create (header), so a list that ANSWERED without the title sent is re-read
733+
// on the bounded schedule before `unfound`. Reads only — ⛔ the create is never re-sent.
734+
const relists = isPr ? [] : ISSUE_CREATE_RELIST_DELAYS_MS;
735+
let waited = 0;
736+
for (let i = 0; i < relists.length && listed.ok && !hit; i++) {
737+
log(`fleet-write: read-back action ${target.action} ${target.op} ${where}: no issue created since the dispatch carries the title sent yet — re-list ${i + 1}/${relists.length} in ${relists[i]} ms (the issue list lags a create; ⛔ the create is not re-sent).`);
738+
await sleep(relists[i]);
739+
waited += relists[i];
740+
listed = await listAll(api, path, t, 3);
741+
hit = find(listed.rows);
742+
if (hit) log(`fleet-write: read-back action ${target.action} ${target.op}: ${repo}#${hit.number} found on re-list ${i + 1}, ${waited} ms after the first list.`);
743+
}
690744
if (!listed.ok && !hit) {
691-
rows.push(unread(target, isPr ? `${repo} head ${head}` : `${repo} (new issue)`, listed.failed));
745+
rows.push(unread(target, where, listed.failed));
692746
continue;
693747
}
694748
if (!hit) {
695-
rows.push({ action: target.action, op: target.op, where: isPr ? `${repo} head ${head}` : `${repo} (new issue)`, verdict: 'unverified', cls: 'unfound', why: isPr ? `no pull request on ${head} was created since the dispatch` : `no issue created on ${repo} since the dispatch carries the title sent` });
749+
const tried = relists.length ? ` (listed ${relists.length + 1} times over ${waited} ms)` : '';
750+
rows.push({ action: target.action, op: target.op, where, verdict: 'unverified', cls: 'unfound', why: isPr ? `no pull request on ${head} was created since the dispatch` : `no issue created on ${repo} since the dispatch carries the title sent${tried}` });
696751
continue;
697752
}
698753
taken.add(hit.id);
@@ -788,7 +843,7 @@ export async function sendFleetWrite(payload, deps = {}) {
788843
if (!ok) return { ...done, state: 'failure', ok: false, detail: `conclusion ${run.conclusion}` };
789844

790845
// ── read it back — the run's success is the executor's, not the write's ─────
791-
const readBack = await readBackStroke(payload, { dispatchedAt }, { api, fetch: deps.fetch, token: deps.token, judge: deps.judge });
846+
const readBack = await readBackStroke(payload, { dispatchedAt }, { api, fetch: deps.fetch, token: deps.token, judge: deps.judge, sleep, log });
792847
for (const row of readBack.rows) log(readBackLine(row));
793848
if (readBack.state === 'not-stored') {
794849
const first = readBack.rows.find((r) => r.verdict === 'not-stored');
@@ -870,10 +925,11 @@ const SELF_TEST_BATTERIES = Object.freeze({
870925
"the verdict: post-stamped's own classifier, imported — declared normalisations land, a split, lost or truncated byte is NOT STORED at its first differing byte, an unreadable body is unverified, the PR-create footer forgiven on pr_create alone": 13,
871926
"the round trip: the card's 41,699 bytes with a multi-byte character across every 16 KiB boundary of every stream, byte for byte through pack, the wire, the runner's env text, the validator and the executor; a per-chunk decode is NOT STORED": 8,
872927
'the read-back end to end: after a success run each body at its locator — a corrupted read-back exits 4 through the CLI, an unreadable or unfound one 6, a body-less stroke reads nothing, never a retry': 17,
928+
'the issue_create re-list: a list that lags the create reads back IDENTICAL on a bounded re-list; a real miss is still unfound (exit 6) after exactly the declared window; an unreadable re-list is unread; the create is never re-sent': 13,
873929
'the wiring: the POST is paced and on the roster, the reads are not, the token never reaches the log': 5,
874930
'the CLI: a dry run sends nothing, usage, the exit ladder, the session derived from the container, a route read behind a dead proxy refuses': 11,
875931
});
876-
const SELF_TEST_BATTERY_FLOOR = 15;
932+
const SELF_TEST_BATTERY_FLOOR = 16;
877933
const UNATTRIBUTED_BATTERY = '(unattributed)';
878934

879935
const batteryCases = new Map();
@@ -1119,7 +1175,8 @@ export async function selfTest() {
11191175
*/
11201176
/**
11211177
* The BOARD the read-back reads: `store` maps a read call (`GET <path>`) to
1122-
* `{ status, json }`, or to a function of the parsed query. The default
1178+
* `{ status, json }`, or to a function of the parsed query and the elapsed
1179+
* ms since the dispatch — so a list that lags a create is time too. The default
11231180
* holds the one comment the default stroke writes, created just after the
11241181
* dispatch and stored as sent — so a stroke that succeeds reads back clean.
11251182
*/
@@ -1146,7 +1203,7 @@ export async function selfTest() {
11461203
return r ? { status: 200, headers, json: async () => r } : { status: 404, headers, json: async () => ({ message: 'Not Found' }) };
11471204
}
11481205
if (call in store) {
1149-
const s = typeof store[call] === 'function' ? store[call](u.searchParams) : store[call];
1206+
const s = typeof store[call] === 'function' ? store[call](u.searchParams, clock.elapsed()) : store[call];
11501207
// A list answers its rows on page 1 and nothing after, as the platform's pagination does.
11511208
const page = Number(u.searchParams.get('page') ?? '1');
11521209
const json = Array.isArray(s.json) && page > 1 ? [] : s.json;
@@ -1427,6 +1484,61 @@ export async function selfTest() {
14271484
t('…and the same stroke read back intact exits 0, its row landed', [cliGood.code, cliGood.json?.read_back?.[0]?.verdict], [EXIT_OK, 'landed']);
14281485
}
14291486

1487+
// ── the issue_create re-list ────────────────────────────────────────────
1488+
battery('the issue_create re-list: a list that lags the create reads back IDENTICAL on a bounded re-list; a real miss is still unfound (exit 6) after exactly the declared window; an unreadable re-list is unread; the create is never re-sent');
1489+
{
1490+
const REPO = 'objectstack-ai/objectstack';
1491+
const ISSUES = `GET /repos/${REPO}/issues`;
1492+
const PULLS = `GET /repos/${REPO}/pulls`;
1493+
const OK_RUN = { runs: () => [RUN('completed', 'success')] };
1494+
const B = 'Body with a multi-byte tail: no… 全部\n';
1495+
const strokeOf = (action) => packRequest({ repo: REPO, session: SESSION, actions: [action], requestId: 'fw-test-1' }).payload;
1496+
const stroke = strokeOf({ op: 'issue_create', title: 'Card', body: B });
1497+
const CARD = { id: 8, number: 20998, title: 'Card', created_at: at(3_000), body: B };
1498+
const OTHER = { id: 9, number: 20999, title: 'Other', created_at: at(2_000), body: 'x' };
1499+
const WINDOW = ISSUE_CREATE_RELIST_DELAYS_MS.reduce((a, b) => a + b, 0);
1500+
const SCHEDULE = ISSUE_CREATE_RELIST_DELAYS_MS.reduce((acc, ms) => [...acc, acc[acc.length - 1] + ms], [0]);
1501+
const writes = (r) => r.seen.filter((s) => !s.call.startsWith('GET ')).map((s) => s.call);
1502+
/** The issue list as a function of time: CARD is on it from `visibleFromMs` on. Each first-page read is recorded at its elapsed ms. */
1503+
const lagging = (visibleFromMs, rows, reads) => ({
1504+
[ISSUES]: (q, ms) => {
1505+
if ((q.get('page') ?? '1') === '1') reads.push(ms);
1506+
return { status: 200, json: ms >= visibleFromMs ? [CARD, ...rows] : rows };
1507+
},
1508+
});
1509+
t('the schedule is frozen and non-empty, every wait a positive integer that never shrinks, the whole window at most 30 s', [Object.isFrozen(ISSUE_CREATE_RELIST_DELAYS_MS), ISSUE_CREATE_RELIST_DELAYS_MS.length > 0, ISSUE_CREATE_RELIST_DELAYS_MS.every((ms, i, a) => Number.isInteger(ms) && ms > 0 && (i === 0 || ms >= a[i - 1])), WINDOW <= 30_000], [true, true, true, true]);
1510+
const lagReads = [];
1511+
const lag = await drive({ ...OK_RUN, store: lagging(8_000, [OTHER], lagReads) }, { stroke });
1512+
t('⭐ the list lags the create — the new issue absent from it until 8 s after the dispatch — and the read-back is IDENTICAL on THAT issue, not unfound: success, exit 0', [lag.state, lag.readBack?.rows?.[0]?.verdict, lag.readBack?.rows?.[0]?.cls, lag.readBack?.rows?.[0]?.where, exitForResult(lag)], ['success', 'landed', 'identical', `${REPO}#20998`, EXIT_OK]);
1513+
t('…found on the SECOND re-list: the list read at the declared cumulative waits, in order, and no further', lagReads, SCHEDULE.slice(0, 3));
1514+
t('…one printed line per re-list, and one naming the issue and the re-list that found it', [lag.logs.filter((l) => l.includes('re-list') && l.includes('the create is not re-sent')).length, lag.logs.some((l) => l.includes(`${REPO}#20998 found on re-list 2`))], [2, true]);
1515+
t('⛔ the create is never re-sent: ONE write across the whole stroke — the dispatch — however many re-lists', writes(lag), [DISPATCH]);
1516+
const missReads = [];
1517+
const miss = await drive({ ...OK_RUN, store: lagging(Infinity, [OTHER], missReads) }, { stroke });
1518+
t('the control — a list that never carries the title sent — is still unfound: unverified, exit 6, ⛔ never success, ⛔ never not-stored', [miss.state, miss.readBack?.rows?.[0]?.cls, miss.notStored ?? false, exitForResult(miss)], ['unverified', 'unfound', false, EXIT_UNCONFIRMED]);
1519+
t('…after exactly the declared window: one list read per step of the schedule, at its cumulative waits, and no more', missReads, SCHEDULE);
1520+
t('…its reason names how often and over how long it listed, and the CLI sentence still says do not re-run blind, exit 6', [miss.readBack?.rows?.[0]?.why?.includes(`listed ${SCHEDULE.length} times over ${WINDOW} ms`), unverifiedText(miss).includes('do not re-run blind'), unverifiedText(miss).includes(`Exit ${EXIT_UNCONFIRMED}`)], [true, true, true]);
1521+
t('⛔ …and still ONE write: a miss re-sends nothing', writes(miss), [DISPATCH]);
1522+
const STALE = { id: 7, number: 20001, title: 'Card', created_at: at(-120_000), body: B };
1523+
const stale = await drive({ ...OK_RUN, store: lagging(8_000, [STALE], []) }, { stroke });
1524+
t('an issue carrying the same title but created BEFORE the dispatch is never taken while the new one lags — the re-list waits for the new one', [stale.state, stale.readBack?.rows?.[0]?.where], ['success', `${REPO}#20998`]);
1525+
const flakyReads = [];
1526+
const flaky = await drive({ ...OK_RUN, store: { [ISSUES]: (q, ms) => {
1527+
if ((q.get('page') ?? '1') === '1') flakyReads.push(ms);
1528+
return ms === 0 ? { status: 200, json: [OTHER] } : { status: 503, json: { message: 'down' } };
1529+
} } }, { stroke });
1530+
t('a re-list that cannot be read ends the wait as unread — the call and its status named, ⛔ not unfound, no further re-list', [flaky.state, flaky.readBack?.rows?.[0]?.cls, flaky.readBack?.rows?.[0]?.why?.includes('HTTP 503') ?? false, flakyReads.length, exitForResult(flaky)], ['unverified', 'unread', true, 2, EXIT_UNCONFIRMED]);
1531+
const promptReads = [];
1532+
const prompt = await drive({ ...OK_RUN, store: lagging(0, [OTHER], promptReads) }, { stroke });
1533+
t('a list that already carries it is read ONCE — no wait, no re-list line', [prompt.state, promptReads.length, prompt.logs.some((l) => l.includes('re-list'))], ['success', 1, false]);
1534+
let pulls = 0;
1535+
const noPr = await drive({ ...OK_RUN, store: { [PULLS]: () => {
1536+
pulls += 1;
1537+
return { status: 200, json: [] };
1538+
} } }, { stroke: strokeOf({ op: 'pr_create', title: 'T', head: 'claude/issue-1-x', base: 'main', body: B }) });
1539+
t("the re-list is issue_create's alone: a pull its head does not find is read ONCE and unfound, as before", [noPr.state, noPr.readBack?.rows?.[0]?.cls, pulls], ['unverified', 'unfound', 1]);
1540+
}
1541+
14301542
// ── the wiring ──────────────────────────────────────────────────────────
14311543
battery('the wiring: the POST is paced and on the roster, the reads are not, the token never reaches the log');
14321544
{
@@ -1507,7 +1619,8 @@ export async function selfTest() {
15071619
console.log(
15081620
`✓ fleet-write/dispatch self-test: ${cases.length} cases pass across ${declared.length} batteries — the transport selector that never guesses, ` +
15091621
'the session on the envelope, one paced dispatch per stroke, a run found by its request id and waited to its conclusion, both ceilings answered UNCONFIRMED and never retried, ' +
1510-
'and every body read back after a success run — the card\'s 41,699 bytes byte-exact across every 16 KiB boundary, a corrupted read-back NOT STORED (exit 4), an unfound one UNCONFIRMED (6).',
1622+
'and every body read back after a success run — the card\'s 41,699 bytes byte-exact across every 16 KiB boundary, a corrupted read-back NOT STORED (exit 4), an unfound one UNCONFIRMED (6) ' +
1623+
'only after a lagging issue list was re-read on its bounded schedule, the create never re-sent.',
15111624
);
15121625
selfTestReachedVerdict = true;
15131626
return 0;

0 commit comments

Comments
 (0)