fix(events): close review gaps found in the follow-up sweep
- deletion flow emitted job/instance envelopes before the transaction that closed the job rows; subscribers refetching on the notification saw status 'running' with no further event. Emits now happen after commit, including the failure path - interrupted-deletion sweep now emits job envelopes with the job's real request_id (it previously stayed silent while the secure-execution sweep already emitted) - verified during the sweep: backup restore validation re-derives the expected schema via migrateDatabase, so migration 18 is automatically covered; no tables reference audit_events or operation_preparations, so the retention deletes cannot trip FK constraints
This commit is contained in:
@@ -328,14 +328,6 @@ export class DeleteInstanceOperation {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.instances.delete(input.instanceId, input.revision, { allowJobHistory: true });
|
await this.instances.delete(input.instanceId, input.revision, { allowJobHistory: true });
|
||||||
this.#emit({
|
|
||||||
kind: 'instance',
|
|
||||||
id: this.id(),
|
|
||||||
occurredAt: this.clock().toISOString(),
|
|
||||||
requestId: input.requestId,
|
|
||||||
instanceId: input.instanceId,
|
|
||||||
});
|
|
||||||
this.#emitJob(ids.job, input.requestId);
|
|
||||||
const finished = this.clock().toISOString();
|
const finished = this.clock().toISOString();
|
||||||
this.db.transaction(() => {
|
this.db.transaction(() => {
|
||||||
this.db
|
this.db
|
||||||
@@ -356,6 +348,16 @@ export class DeleteInstanceOperation {
|
|||||||
)
|
)
|
||||||
.run(finished, finished, ids.job);
|
.run(finished, finished, ids.job);
|
||||||
})();
|
})();
|
||||||
|
// Events only after the terminal rows are committed, so a subscriber that
|
||||||
|
// refetches on the notification sees the finished job, not 'running'.
|
||||||
|
this.#emit({
|
||||||
|
kind: 'instance',
|
||||||
|
id: this.id(),
|
||||||
|
occurredAt: this.clock().toISOString(),
|
||||||
|
requestId: input.requestId,
|
||||||
|
instanceId: input.instanceId,
|
||||||
|
});
|
||||||
|
this.#emitJob(ids.job, input.requestId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const finished = this.clock().toISOString();
|
const finished = this.clock().toISOString();
|
||||||
const state = error instanceof InstanceServiceError ? 'failed' : 'unknown-result';
|
const state = error instanceof InstanceServiceError ? 'failed' : 'unknown-result';
|
||||||
@@ -375,6 +377,7 @@ export class DeleteInstanceOperation {
|
|||||||
.prepare('UPDATE jobs SET status=?,finished_at=?,updated_at=? WHERE id=?')
|
.prepare('UPDATE jobs SET status=?,finished_at=?,updated_at=? WHERE id=?')
|
||||||
.run(state, finished, finished, ids.job);
|
.run(state, finished, finished, ids.job);
|
||||||
})();
|
})();
|
||||||
|
this.#emitJob(ids.job, input.requestId);
|
||||||
}
|
}
|
||||||
return this.job(ids.job);
|
return this.job(ids.job);
|
||||||
}
|
}
|
||||||
@@ -449,9 +452,10 @@ export class DeleteInstanceOperation {
|
|||||||
const finished = this.clock().toISOString();
|
const finished = this.clock().toISOString();
|
||||||
return this.db.transaction(() => {
|
return this.db.transaction(() => {
|
||||||
const jobs = this.db
|
const jobs = this.db
|
||||||
.prepare("SELECT id FROM jobs WHERE operation_id=? AND status='running'")
|
.prepare("SELECT id, request_id FROM jobs WHERE operation_id=? AND status='running'")
|
||||||
.all(OPERATION_ID) as Array<{ id: string }>;
|
.all(OPERATION_ID) as Array<{ id: string; request_id: string }>;
|
||||||
for (const { id } of jobs) {
|
for (const { id, request_id: requestId } of jobs) {
|
||||||
|
this.#emitJob(id, requestId);
|
||||||
this.db
|
this.db
|
||||||
.prepare(
|
.prepare(
|
||||||
"UPDATE job_items SET status='unknown-result',result_code='INTERRUPTED',finished_at=?,updated_at=? WHERE job_id=? AND status='running'",
|
"UPDATE job_items SET status='unknown-result',result_code='INTERRUPTED',finished_at=?,updated_at=? WHERE job_id=? AND status='running'",
|
||||||
|
|||||||
Reference in New Issue
Block a user