已合并
[SESSION-03] Implement frontend-neutral canonical user-event ingestion #23
urandon创建于 8月10日
[SESSION-03] Implement frontend-neutral canonical user-event ingestion #23
已合并
urandon创建于 8月10日
urandon
urandon成员
8月10日

Outcome

Implements SESSION-03: authenticated frontends can create or switch canonical sessions and atomically ingest a canonical user event plus its first workload records without transport-specific IDs in the ingress contract.

What changed

  • Added the frontend-neutral CanonicalIngressStore and application service.
  • Gated initial binding/session creation and clean-context switching on active tenant membership and writable session participation.
  • Added revision-fenced clean-context transactions that retain all previous sessions.
  • Added HMAC-derived opaque binding, session, event, run, attempt, manifest, and outbox IDs.
  • Added tenant/session/event Object Storage envelopes and attachment manifests.
  • Added frontend_ingress_idempotency so delayed duplicates resolve to the original event/run after a binding switch.
  • Atomically commit the canonical event, session sequence, run, initial attempt, manifest, dispatch outbox, and both idempotency facts.
  • Added FrontendEventOrigin and kept the old Telegram dispatch fields only as a documented compatibility bridge for #36.
  • Added a deterministic non-Telegram synthetic adapter and public English documentation.

Verification

  • make ci
  • clean YDB Local migration applied twice
  • full make ydb-integration
  • focused concurrent YDB test with count=1
  • exact duplicate after binding switch returns the original session/event/run
  • failed object work does not enter the YDB commit
  • rejected YDB work leaves no partial event/run/attempt/outbox
  • cross-tenant binding access and stale switches fail
  • tagged YDB package compilation
  • git diff --check
  • public documentation contains no Cyrillic

The merged-main CI for prerequisite !22 is green: GitHub Actions run #67 on 2aa2c7f.

Closes #22.

likedislike
Pull Request已成功合入, 合并人@urandon
(感谢 urandon 的贡献)
urandonurandon成员
8月10日 关联了issue:[SESSION-03] Implement frontend-neutral canonical user-event ingestion,[TELEGRAM-01] Adapt Telegram bindings and ingress to canonical user events
urandonurandon成员
8月10日 修改了pull request 的描述
urandon
urandon成员8月10日进行代码检视2
internal/ydbstore/scheduler.go
@@ -227,3 +227,4 @@
227227 Limits: request.Limits,
228+ Origin: outbox.Origin,
228229 DeliveryChat: outbox.DeliveryChat,
229230 ReplyToMessageID: outbox.ReplyToMessageID,
urandon
urandon8月10日评论:

[P1] Origin-only jobs cannot complete. AdmitDispatch now creates a WorkerJob with only Origin, but worker completion still always constructs a TelegramDeliveryOutbox; PutTelegramDeliveryOutbox rejects zero ChatID/ReplyToMessageID. Any canonical frontend run will execute and then fail terminal commit/retry/dead-letter. Gate these dispatches until result projection exists, or add a canonical completion path.

likedislike
urandon
urandon成员
8月10日 评论:

Fixed in a85367b. Origin-only dispatch outboxes now remain pending with canonical_projection_pending until SESSION-04/#23 provides frontend-neutral result finalization and projection. The admission path creates neither a worker job nor a quota reservation, while legacy Telegram-targeted jobs remain admissible. The YDB integration test covers the gate and both zero-row assertions.

urandon
urandon成员8月10日进行代码检视2
internal/sessioningress/service.go
@@ -0,0 +162,4 @@
162+ Frontend: input.Actor.Frontend, ExternalConversationID: input.Actor.ExternalConversationID,
163+ ExternalEventID: input.ExternalEventID,
164+ }
165+ prefix := domain.SessionEventObjectPrefix(input.Actor.TenantID, state.Session.ID, eventID)
urandon
urandon8月10日评论:

[P1] Deduplication after /new is broken. The stable idempotency key is reused, but the origin and Object Storage prefix are rebuilt from the current session/binding revision. A delayed duplicate after a binding switch therefore writes new refs and fails the store's exact Payload/Origin checks with ErrEventIdempotencyConflict instead of returning the original result. Resolve the dedup record before writing objects, or reuse the original persisted refs and origin.

likedislike
urandon
urandon成员
8月10日 评论:

Fixed in a85367b. Ingress now performs an authorized idempotency lookup before resolving the current binding or writing immutable objects. A delayed duplicate after /new returns the original session/event/run and creates no new object. The transactional fallback also handles concurrent preflight misses: the first committed payload, origin, and timestamp remain canonical.

urandon
urandon成员8月10日进行代码检视2
internal/ydbstore/canonical_ingress.go
@@ -0,0 +191,4 @@
191+ SessionID: event.SessionID, EventID: event.ID, Sequence: event.Sequence,
192+ RunID: run.ID, Created: false,
193+ }
194+ return nil
urandon
urandon8月10日评论:

[P2] An idempotency hit bypasses authorization. This branch returns before authorizeTenantWriteTx and authorizeSessionWriteTx, so a revoked tenant member or removed session participant can replay an old request and still receive a successful result. The documented contract requires current membership and writable participation even when the dedup row exists; perform those checks before this early return, while keeping the delayed-duplicate binding-revision exception.

likedislike
urandon
urandon成员
8月10日 评论:

Fixed in a85367b. Both the preflight lookup and the transactional dedup-hit path now re-authorize current tenant membership and write participation in the original session before returning the prior result. The integration test verifies rejection after membership revocation and after removing the user from the original session.

urandonurandon成员
8月10日 推送  1 个提交:a85367bd-fix: preserve canonical ingress invariants
urandon
urandon成员8月10日进行代码检视2
internal/sessioningress/service.go
@@ -0,0 +156,4 @@
156+ input.Actor.ExternalConversationID, input.ExternalEventID,
157+ ))
158+ eventID := domain.SessionEventID(service.stableID("event", input.Actor.TenantID, bindingID, idempotencyKey))
159+ runID := domain.RunID(service.stableID("run", input.Actor.TenantID, bindingID, idempotencyKey))
urandon
urandon8月10日评论:

[P1] Concurrent retries can overwrite the canonical blob. Both requests derive the same event/run IDs and object prefix here, then each calls BlobStore.Put before the YDB dedup transaction. PutObject is unconditional (and the in-memory store overwrites too), so a loser with a different payload can replace message.json or an attachment after the winner commits; the stored BlobRef.SHA256 then no longer describes the bytes read from Object Storage. Use content-addressed/staging keys with conditional create, or verify the existing object digest before allowing the retry.

likedislike
urandon
urandon成员
8月10日 评论:

Fixed in e1ff03b. Every post-preflight ingestion attempt now writes its envelope and attachments under a cryptographically random immutable upload namespace inside the canonical session/event prefix. Concurrent attempts with the same stable event/run IDs therefore cannot share or overwrite an object key; the winning YDB transaction persists only its own BlobRefs, while loser uploads remain unreferenced for lifecycle cleanup. A regression test forces both attempts to miss preflight with different payloads and verifies that the canonical bytes and SHA-256 remain those of the winner.

urandonurandon成员
8月10日 推送  1 个提交:e1ff03be-fix: isolate concurrent ingress uploads
urandonurandon成员
8月10日 解决了最后一个问题
urandonurandon成员
8月10日 合入了pull request,合并节点 SHA:13e886f41776e7dc0790d8c5de33a33ab47a313a