Skip to content

Commit 6fdc6f8

Browse files
sametbasbugclaude
andcommitted
Make the staging rehearsal prove what it cleaned up, not what it remembered
The rehearsal kept two ledgers: everything it created, and a hand-maintained list of what to delete afterwards. The rejected post was missing from the second one. Its cleanup check then asked whether each record still read publicly, and a rejected record returns 404 to everyone by definition — so the leak was invisible to the very assertion meant to catch it. Every run left one row in staging and reported cleanup as passing. That is the shape of failure worth naming: not a missing test, but a test that could only ever agree with itself. It surfaced on the first nightly run after the credentials landed, which is roughly the argument for having put it on a schedule. Cleanup now walks the ledger that already knows what was created, so nothing has to be remembered twice, and it finishes by asking D1 whether any of those rows survived. Records the rehearsal deletes on purpose return 404 to the delete call, which stays tolerated; a row that is merely invisible does not. Verified against staging: the run is green and leaves nothing behind, and the new query returns the row this morning's run abandoned — so the check would have gone red on it. That row is now deleted through the audited endpoint rather than by hand in SQL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent c5087a9 commit 6fdc6f8

1 file changed

Lines changed: 25 additions & 13 deletions

File tree

‎scripts/orbit-slice4-staging-e2e.ts‎

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@ const disposableAgent = async (mode: 'direct_publish' | 'approval_required' = 'd
169169

170170
const session = await ownerSession(ownerId, sessionPepper, csrfPepper, now + 3);
171171
const createdRecordIds: string[] = [];
172-
const cleanupRecordIds: string[] = [];
173172

174173
const directResponse = await agentWrite(direct.token, '/v1/records', {
175174
bodyMarkdown: 'Staging Slice 4 doğrudan yayın provası.',
@@ -178,7 +177,6 @@ const directResponse = await agentWrite(direct.token, '/v1/records', {
178177
assert.equal(directResponse.status, 201);
179178
const directBody = await directResponse.json() as { record: { id: string; slug: string } };
180179
createdRecordIds.push(directBody.record.id);
181-
cleanupRecordIds.push(directBody.record.id);
182180
assert.equal((await fetch(`${ORIGIN}/v1/records/${directBody.record.id}`)).status, 200);
183181

184182
const replay = await agentWrite(direct.token, '/v1/records', {
@@ -195,7 +193,6 @@ const reply = await agentWrite(direct.token, `/v1/records/${directBody.record.id
195193
assert.equal(reply.status, 201);
196194
const replyBody = await reply.json() as { record: { id: string; parentId: string; rootId: string } };
197195
createdRecordIds.push(replyBody.record.id);
198-
cleanupRecordIds.push(replyBody.record.id);
199196
assert.equal(replyBody.record.parentId, directBody.record.id);
200197
assert.equal(replyBody.record.rootId, directBody.record.id);
201198

@@ -205,7 +202,6 @@ const pending = await agentWrite(approval.token, '/v1/records', {
205202
assert.equal(pending.status, 202);
206203
const pendingBody = await pending.json() as { record: { id: string } };
207204
createdRecordIds.push(pendingBody.record.id);
208-
cleanupRecordIds.push(pendingBody.record.id);
209205
assert.equal((await fetch(`${ORIGIN}/v1/records/${pendingBody.record.id}`)).status, 404);
210206
const queue = await ownerRequest(session, '/v1/approvals').then((response) => response.json()) as {
211207
reviews: Array<{ id: string; record: { id: string } }>;
@@ -249,7 +245,6 @@ const concurrentPost = await pair(() => agentWrite(concurrentDirect.token, '/v1/
249245
}, `slice4-${suffix}-concurrent-post`), 201);
250246
const concurrentPostId = concurrentPost.record!.id;
251247
createdRecordIds.push(concurrentPostId);
252-
cleanupRecordIds.push(concurrentPostId);
253248
await settleBurstWindow();
254249
const concurrentReply = await pair(() => agentWrite(
255250
concurrentDirect.token,
@@ -258,7 +253,6 @@ const concurrentReply = await pair(() => agentWrite(
258253
`slice4-${suffix}-concurrent-reply`,
259254
), 201);
260255
createdRecordIds.push(concurrentReply.record!.id);
261-
cleanupRecordIds.push(concurrentReply.record!.id);
262256
await pair(() => agentWrite(concurrentDirect.token, `/v1/records/${concurrentPostId}`, {
263257
bodyMarkdown: `Staging paralel idempotency revision ${suffix}.`,
264258
}, `slice4-${suffix}-concurrent-revision`, 'PATCH'), 200);
@@ -283,7 +277,6 @@ const slugBodies = await Promise.all(slugRace.map((response) => response.json())
283277
assert.notEqual(slugBodies[0].record.slug, slugBodies[1].record.slug);
284278
assert.ok(slugBodies.some((item) => item.record.slug.endsWith(item.record.id.replaceAll('-', '').slice(-12))));
285279
createdRecordIds.push(...slugBodies.map((item) => item.record.id));
286-
cleanupRecordIds.push(...slugBodies.map((item) => item.record.id));
287280

288281
/* Her bekleyen kayıt kendi ajanıyla üretiliyor. Üç kayıt tek ajandan
289282
gelirse saatlik gönderi kotasına takılıyor; testin konusu onay akışı,
@@ -310,7 +303,6 @@ const approveReview = await reviewFor(approveId);
310303
await pair(() => ownerRequest(session, `/v1/approvals/${approveReview}/approve`, 'POST', {
311304
note: 'parallel staging approval',
312305
}, `slice4-${suffix}-concurrent-approve`), 200);
313-
cleanupRecordIds.push(approveId);
314306

315307
const { id: rejectId } = await createPending(`Staging paralel ret ${suffix}.`, `slice4-${suffix}-reject-create`);
316308
const rejectReview = await reviewFor(rejectId);
@@ -363,12 +355,19 @@ assert.equal(Number(evidence.records), createdRecordIds.length);
363355
assert.ok(Number(evidence.audits) >= 15);
364356
assert.ok(Number(evidence.idempotency_rows) >= 15);
365357

366-
/* Kök gönderiyi silmek yanıt ağacını da siliyor — bu, ayrı bir testin
358+
/* Temizlik, provanın yarattığı HER kaydı gezer — ayrı bir "silinecekler"
359+
* listesi tutmuyoruz. Öyle bir liste vardı ve bir kaydı atlıyordu: reddedilen
360+
* gönderi. Reddedilen kayıt zaten herkese 404 döndüğü için aşağıdaki genel
361+
* okuma kontrolü de onu yakalayamıyordu; prova her koşuda staging'e bir satır
362+
* bırakıp yeşil yanıyordu. Neyin yaratıldığını zaten bilen tek bir defter,
363+
* hatırlanmaya dayanan ikinci bir defterden güvenli.
364+
*
365+
* Kök gönderiyi silmek yanıt ağacını da siliyor — bu, ayrı bir testin
367366
* doğruladığı kasıtlı davranış. Dolayısıyla listede önce kök, sonra onun
368367
* yanıtı geldiğinde ikincisi 404 döner ve bu bir arıza değil, istediğimiz
369-
* son durumun ta kendisi. Temizliğin ölçütü "her çağrı 200 döndü mü"
370-
* değil, "geride yayımlanmış kayıt kaldı mı". */
371-
for (const recordId of cleanupRecordIds) {
368+
* son durumun ta kendisi. Aynısı provanın kendi sildiği kayıtlar için de
369+
* geçerli. Ölçüt "her çağrı 200 döndü mü" değil, "geride ne kaldı". */
370+
for (const recordId of createdRecordIds) {
372371
const ownerDelete = await ownerRequest(session, `/v1/manage/records/${recordId}/delete`, 'POST', {
373372
reason: 'Slice 4 staging cleanup.',
374373
}, `slice4-${suffix}-delete-${recordId}`);
@@ -377,13 +376,26 @@ for (const recordId of cleanupRecordIds) {
377376
`Cleanup delete failed for ${recordId}: ${ownerDelete.status}`,
378377
);
379378
}
380-
for (const recordId of cleanupRecordIds) {
379+
for (const recordId of createdRecordIds) {
381380
assert.equal(
382381
(await fetch(`${ORIGIN}/v1/records/${recordId}`)).status,
383382
404,
384383
`Cleanup left ${recordId} publicly readable.`,
385384
);
386385
}
386+
/* Herkese 404 dönmek yetmez: taslak, bekleyen ve reddedilen kayıtlar zaten
387+
* 404 döner. Kalıcı kanıt için D1'e soruyoruz — silinmemiş tek bir satır
388+
* kalırsa prova kırmızı yanar, staging'de birikmez. */
389+
const survivors = execute(`
390+
SELECT id, lifecycle_state FROM records
391+
WHERE deleted_at IS NULL AND id IN (${createdRecordIds.map(quote).join(',')})
392+
`) as Array<{ id: string; lifecycle_state: string }>;
393+
assert.deepEqual(
394+
survivors,
395+
[],
396+
`Cleanup left ${survivors.length} record(s) alive in staging: `
397+
+ survivors.map((row) => `${row.id} (${row.lifecycle_state})`).join(', '),
398+
);
387399
execute(`
388400
UPDATE agent_credentials SET revoked_at = ${Date.now()}, revoked_reason = 'staging_test_cleanup'
389401
WHERE id IN (${seededAgents.map((agent) => quote(agent.credentialId)).join(',')});

0 commit comments

Comments
 (0)