|
4 | 4 | * [#13546] `sys_http_delivery` rows carry the producer's organization. |
5 | 5 | * |
6 | 6 | * The consequence being pinned: the cross-organization wall on `redeliver()` |
7 | | - * (#10740) scopes by the row's `organization_id`, and the driver's tenant term |
8 | | - * is `(organization_id = :tenantId OR organization_id IS NULL)` — a deliberate |
9 | | - * global-row fail-open. A row enqueued without an organization therefore |
10 | | - * belongs to NO organization and is visible to EVERY one; before this repair |
11 | | - * the enqueue door never stamped the column, so 100% of rows were in that arm |
12 | | - * and the wall excluded nothing. |
| 7 | + * (#10740) scopes by the row's `organization_id`. The driver's tenant term was |
| 8 | + * then `(organization_id = :tenantId OR organization_id IS NULL)`, a global-row |
| 9 | + * fail-open, and the enqueue door never stamped the column, so 100% of rows |
| 10 | + * were in that arm and the wall excluded nothing. ADR-0131 D8 has since |
| 11 | + * retired the NULL arm: the term is `organization_id = :tenantId` alone, and |
| 12 | + * an organization-less row is reached by no tenant-scoped redeliver. |
13 | 13 | * |
14 | 14 | * ⭐ The control for this suite is the sibling notification outbox |
15 | 15 | * (`SqlOutbox.enqueue` writes `organization_id: input.organizationId ?? null`) |
@@ -166,16 +166,23 @@ describe('#13546 — MemoryHttpOutbox parity', () => { |
166 | 166 | expect(row.organizationId).toBe('org_pin_alpha'); |
167 | 167 | }); |
168 | 168 |
|
169 | | - it('an org-less row stays a GLOBAL row any tenant may replay (the driver fail-open arm, mirrored)', async () => { |
170 | | - // The over-denial control: hiding NULL rows from every tenant would be |
171 | | - // a different defect (platform rows invisible to everyone). The memory |
172 | | - // predicate must mirror `(organization_id = :tenantId OR organization_id |
173 | | - // IS NULL)`, not tighten it. |
| 169 | + it('an org-less row is INVISIBLE to every tenant-scoped redeliver — no NULL arm (ADR-0131 D8)', async () => { |
| 170 | + // The memory predicate mirrors the SQL driver's `organization_id = |
| 171 | + // :tenantId` with no `OR organization_id IS NULL` arm: NULL is not an |
| 172 | + // owner, so no tenant may replay a row of no organization. |
174 | 173 | const outbox = new MemoryHttpOutbox(); |
175 | 174 | const id = await deadRow(outbox); |
176 | 175 |
|
177 | | - const row = await outbox.redeliver(id, { tenantId: 'org_pin_beta' }); |
178 | | - expect(row.status).toBe('pending'); |
| 176 | + const attempt = outbox.redeliver(id, { tenantId: 'org_pin_beta' }); |
| 177 | + await expect(attempt).rejects.toBeInstanceOf(HttpRedeliverError); |
| 178 | + await expect(attempt).rejects.toMatchObject({ code: 'RESOURCE_NOT_FOUND' }); |
| 179 | + const [row] = await outbox.list({ status: 'dead' }); |
| 180 | + expect(row.id).toBe(id); |
| 181 | + expect(row.attempts).toBe(1); |
| 182 | + |
| 183 | + // Still reachable where nothing scopes: a tenant-less caller. |
| 184 | + const replayed = await outbox.redeliver(id, { tenantId: undefined }); |
| 185 | + expect(replayed.status).toBe('pending'); |
179 | 186 | }); |
180 | 187 |
|
181 | 188 | it('a tenant-less caller stays unscoped (the honest degraded shape RedeliverOptions rules)', async () => { |
|
0 commit comments