Add code resolver, sync conflict handling, and user isolation

Introduce a CodeResolverService to classify user login codes, complete with detailed resolution logic and usability checks. Extend the sync system to handle conflicts via last-write-wins arbitration, with detailed conflict tracking for review. Update file permissions and runtime isolation in Docker to enhance security.
This commit is contained in:
2026-09-12 15:40:42 +02:00
parent 858c43a6aa
commit 5fd2d47a64
19 changed files with 950 additions and 131 deletions
@@ -0,0 +1,31 @@
-- AlterTable
ALTER TABLE "SyncLogEntry" ADD COLUMN "occurredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- CreateTable
CREATE TABLE "SyncRecordVersion" (
"id" TEXT NOT NULL,
"model" TEXT NOT NULL,
"recordId" TEXT NOT NULL,
"lastWriteAt" TIMESTAMP(3) NOT NULL,
"lastWriteOrigin" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SyncRecordVersion_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SyncConflict" (
"id" TEXT NOT NULL,
"model" TEXT NOT NULL,
"recordId" TEXT NOT NULL,
"winningOrigin" TEXT NOT NULL,
"losingOrigin" TEXT NOT NULL,
"winningPayload" JSONB NOT NULL,
"losingPayload" JSONB NOT NULL,
"detectedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "SyncConflict_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "SyncRecordVersion_model_recordId_key" ON "SyncRecordVersion"("model", "recordId");
+44 -8
View File
@@ -348,15 +348,19 @@ enum SyncOperation {
/// Append-only log of local mutations, replicated to the peer server (local
/// <-> cloud). `originId` is the SERVER_ID that made the change, so applying
/// an incoming entry never gets re-captured/re-pushed back (no echo loops).
/// `occurredAt` is the wall-clock moment of the mutation itself (set at
/// capture time), distinct from `createdAt` which is just row-insert time -
/// conflict resolution compares `occurredAt`, never sync/network timing.
model SyncLogEntry {
id String @id @default(cuid())
sequence Int @default(autoincrement())
model String
recordId String
operation SyncOperation
payload Json
originId String
createdAt DateTime @default(now())
id String @id @default(cuid())
sequence Int @default(autoincrement())
model String
recordId String
operation SyncOperation
payload Json
originId String
occurredAt DateTime @default(now())
createdAt DateTime @default(now())
}
/// Per-peer replication progress, kept on the side that initiates sync
@@ -368,3 +372,35 @@ model SyncCursor {
lastPushedSequence Int @default(0)
lastPulledSequence Int @default(0)
}
/// Last-write-wins register, one row per replicated record. Tracks the
/// wall-clock time and origin of whichever mutation - local or remote - is
/// currently considered authoritative for that record, so a concurrent edit
/// on both servers resolves deterministically by actual edit time instead
/// of by sync/network arrival order.
model SyncRecordVersion {
id String @id @default(cuid())
model String
recordId String
lastWriteAt DateTime
lastWriteOrigin String
updatedAt DateTime @updatedAt
@@unique([model, recordId])
}
/// Audit trail of detected conflicts: two servers wrote the same record
/// within the replication window. Resolution (last-write-wins by
/// occurredAt) still happens automatically and immediately - nothing here
/// blocks live sync - but Leitungsteam can review afterwards whether a
/// discarded edit needs to be manually reapplied.
model SyncConflict {
id String @id @default(cuid())
model String
recordId String
winningOrigin String
losingOrigin String
winningPayload Json
losingPayload Json
detectedAt DateTime @default(now())
}