From 600bf913dd53fd7a7cc26de5d58bfc08f9780c51 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 12:28:44 +0000 Subject: [PATCH 1/2] docs(approvals): the resubmit discriminator states what main measured, not an invariant main falsified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `resolveRecordedContinuation` argued its `action: 'resubmit'` discriminator was "exact and structural" on three clauses. The first two hold. The third — "a resubmit opens the next round as a NEW row, so at most one such action row exists per request" — was measured false: a resubmit whose own resume strands opens no next round, the row stays `returned`, and after `restoreConsumedSuspension` re-arms the pause a second `resubmit` by the same submitter lands a SECOND `action: 'resubmit'` row. Prose only, zero behaviour change. No door narrowed, no guard touched: the comment now records the doubling, states why the read is correct anyway (it is a presence check with `limit: 1`, deciding identically on one row or two), and points at the pin that measured it. The pin's header gains one line recording that the ruling landed and chose prose, so the doubling is accepted residue; none of its assertions move. Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW Co-authored-by: Claude --- .../plugin-approvals/src/approval-service.ts | 28 ++++++++++++++++--- .../src/stranded-resubmit-second-door.test.ts | 5 ++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/plugins/plugin-approvals/src/approval-service.ts b/packages/plugins/plugin-approvals/src/approval-service.ts index 0f4aacb530..162b1b825b 100644 --- a/packages/plugins/plugin-approvals/src/approval-service.ts +++ b/packages/plugins/plugin-approvals/src/approval-service.ts @@ -4874,11 +4874,31 @@ export class ApprovalService implements IApprovalService { // fallback (#4414) warns and evaluates every out-edge, so the flow // proceeds with `{decision:'revise'}` where `{resubmitted:true}` was owed. // - // The discriminator is exact and structural: `action: 'resubmit'` has - // exactly ONE writer in this file (`resubmit`), it is inserted before - // that resume, and a resubmit opens the next round as a NEW row — so at - // most one such action row exists per request, and its presence means + // The discriminator's first two clauses are exact and structural: + // `action: 'resubmit'` has exactly ONE writer in this file (`resubmit`), + // and it is inserted before that resume. Its presence therefore means // the last continuation this row issued was the resubmit. + // + // ⚠️ What does NOT hold is the third clause this comment used to claim — + // "a resubmit opens the next round as a NEW row, so at most one such + // action row exists per request". A resubmit whose own resume STRANDS + // opens no next round at all, so the row stays `returned`; once an + // operator re-arms the pause with `restoreConsumedSuspension`, a second + // `resubmit` by the same submitter passes every door guard and writes a + // SECOND `action: 'resubmit'` row. ⇒ MORE THAN ONE such row CAN exist + // for one request. Measured guard by guard, on that very row in that + // very state, in `stranded-resubmit-second-door.test.ts` (#17601 probe, + // PR #17613) — read it there rather than re-deriving it from here. + // + // The read below is correct anyway, for a reason that clause never + // needed: it is a PRESENCE check (`limit: 1`), so it decides identically + // on one row or two — the pin's MEASUREMENT C drives this resolver on + // the doubled row and it still answers `resubmit`. What the doubling + // costs is the audit trail's one-row-per-advancement shape, ⛔ not the + // edge picked here, and that cost is ACCEPTED RESIDUE under the #17601 + // ruling of 2026-09-11 (option B: scope this prose to what was measured, + // narrow no door). Requiring one row per advancement is a new card, ⛔ + // not a local fix here. const resubmitted = await this.engine.find('sys_approval_action', { where: { request_id: requestId, action: 'resubmit' }, limit: 1, context: SYSTEM_CTX, }); diff --git a/packages/plugins/plugin-approvals/src/stranded-resubmit-second-door.test.ts b/packages/plugins/plugin-approvals/src/stranded-resubmit-second-door.test.ts index fb3c789581..77ca46653c 100644 --- a/packages/plugins/plugin-approvals/src/stranded-resubmit-second-door.test.ts +++ b/packages/plugins/plugin-approvals/src/stranded-resubmit-second-door.test.ts @@ -13,6 +13,11 @@ * this behaviour, THIS PIN is the thing to update, and its update is the * repair's evidence. * + * ⭐ THAT RULING LANDED (#17601, 2026-09-11): option B — the discriminator's + * prose in `approval-service.ts` was scoped to this measurement and NO door was + * narrowed, so the doubling measured below is ACCEPTED RESIDUE and every + * assertion in this file stands exactly as it was. + * * ## The hypothesis, and what it tested * * `resolveRecordedContinuation` discriminates the two continuation issuers of a From babc3d49a3165ff7a75b4be20ba598b5e90be91a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 12:39:44 +0000 Subject: [PATCH 2/2] chore(changeset): patch @objectstack/plugin-approvals for the resubmit discriminator prose Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW Co-authored-by: Claude --- .../scope-resubmit-discriminator-invariant.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .changeset/scope-resubmit-discriminator-invariant.md diff --git a/.changeset/scope-resubmit-discriminator-invariant.md b/.changeset/scope-resubmit-discriminator-invariant.md new file mode 100644 index 0000000000..14866f7ff6 --- /dev/null +++ b/.changeset/scope-resubmit-discriminator-invariant.md @@ -0,0 +1,16 @@ +--- +'@objectstack/plugin-approvals': patch +--- + +Correct the `resolveRecordedContinuation` discriminator's stated invariant in +`approval-service.ts` to what was measured. The comment claimed the +`action: 'resubmit'` audit row was "at most one per request"; a `resubmit` whose +own resume strands opens no next round, so the row stays `returned` and a second +`resubmit` after `restoreConsumedSuspension` lands a second such row. The +comment now records that more than one row can exist, states why the read is +correct anyway (it is a presence check with `limit: 1`, deciding identically on +one row or two), and points at the pin that measured it. + +Prose only — no behaviour change, no door narrowed, no guard touched. The audit +trail's one-row-per-advancement shape is accepted residue; requiring one row per +advancement is a separate change.