Harden archive bot for production; exempt container egress in edge firewall

- archive_sync_state keyed by room per migration 029 (drop synthetic id cursor)
- putStaging buffers the stream and sends content-length (oCIS rejects chunked)
- mimeClass passthrough from sniffed payload
- edge-firewall: RETURN docker-bridge ingress before the CF-range catch-all so
  container egress (Matrix/R2/platform) survives the 80/443 source filter
- files scaffold: keep the ocdav data-gateway hop on-loopback
This commit is contained in:
glass beadian 2026-10-11 16:12:43 +00:00
parent 23bd4dd5ad
commit 2cb77ae6b4
9 changed files with 211 additions and 38 deletions

View file

@ -6,7 +6,7 @@ import { classifyEvent, type AttachmentWork, type MatrixEvent, type RoomEncrypti
import { PermanentArchiveError } from "./inspector.js";
import { claimKey, replyTxnId } from "./keys.js";
import { MatrixHttpError, type MatrixPort } from "./matrix.js";
import type { ArchiveStore } from "./store.js";
import type { ArchiveStore, RoomCursorTarget } from "./store.js";
export interface BatchResult {
/** True when the sync cursor was durably advanced past this batch. */
@ -61,6 +61,7 @@ export class ArchiveProcessor {
/** Committed claims whose single reply has not been delivered yet (key → safe name). */
private readonly unsentReplies = new Map<string, string>();
private readonly byRoom: Map<string, RoomMapping>;
private readonly cursorRooms: RoomCursorTarget[];
private readonly workspaceIds: string[];
constructor(
@ -72,11 +73,12 @@ export class ArchiveProcessor {
private readonly now: () => Date = () => new Date(),
) {
this.byRoom = new Map(mappings.map((m) => [m.roomId, m]));
this.cursorRooms = mappings.map((m) => ({ roomId: m.roomId, workspaceId: m.workspaceId }));
this.workspaceIds = [...new Set(mappings.map((m) => m.workspaceId))];
}
async init(): Promise<void> {
this.cursor = await this.store.loadCursor(this.workspaceIds);
this.cursor = await this.store.loadCursor(this.cursorRooms);
}
async health(): Promise<HealthSnapshot> {
@ -115,7 +117,7 @@ export class ArchiveProcessor {
}
if (retryAt !== null) return { advanced: false, retryAt };
await this.store.saveCursor(this.workspaceIds, sync.nextBatch);
await this.store.saveCursor(this.cursorRooms, sync.nextBatch);
this.cursor = sync.nextBatch;
return { advanced: true, retryAt: null };
}