|
|
@@ -6,6 +6,183 @@ English | [中文](feedback.zh.md)
|
|
|
|
|
|
Source: [`packages/feedback/message-feedback/src/types.ts`](../../packages/feedback/message-feedback/src/types.ts)
|
|
|
|
|
|
+## Public types
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Opaque compare-and-set token for one exact feedback item revision. */
|
|
|
+type MessageFeedbackVersion = Branded<'MessageFeedbackVersion'>
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** The human's overall judgment of one assistant message. */
|
|
|
+type MessageFeedbackRating = 'positive' | 'negative'
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** One current feedback value and its opaque mutation token. */
|
|
|
+interface MessageFeedbackItem {
|
|
|
+ /** Stable identity of the assistant message inside the owning Session. */
|
|
|
+ readonly messageId: MessageId
|
|
|
+ /** Overall positive or negative judgment. */
|
|
|
+ readonly rating: MessageFeedbackRating
|
|
|
+ /** Optional explanation, preserved verbatim after validation. */
|
|
|
+ readonly note?: string
|
|
|
+ /** Equality-only token replaced by every material create or update. */
|
|
|
+ readonly version: MessageFeedbackVersion
|
|
|
+ /** Host-assigned creation time in Unix epoch milliseconds. */
|
|
|
+ readonly createdAt: number
|
|
|
+ /** Host-assigned time of the most recent material update. */
|
|
|
+ readonly updatedAt: number
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Read all message feedback belonging to one persisted Session lifecycle. */
|
|
|
+interface MessageFeedbackListRequest {
|
|
|
+ /** Persisted Session whose sidecar should be read. */
|
|
|
+ readonly sessionId: SessionId
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Current feedback values for one Session, in first-creation order. */
|
|
|
+interface MessageFeedbackListValue {
|
|
|
+ /** Fresh immutable item snapshots. */
|
|
|
+ readonly items: readonly MessageFeedbackItem[]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Create or replace feedback for one assistant message. */
|
|
|
+interface MessageFeedbackPutRequest {
|
|
|
+ /** Persisted Session that owns the target message. */
|
|
|
+ readonly sessionId: SessionId
|
|
|
+ /** Target assistant-message identity. */
|
|
|
+ readonly messageId: MessageId
|
|
|
+ /** Desired overall judgment. */
|
|
|
+ readonly rating: MessageFeedbackRating
|
|
|
+ /** Optional non-blank explanation. */
|
|
|
+ readonly note?: string
|
|
|
+ /** Observed item version, or `null` to require that no item exists. */
|
|
|
+ readonly ifVersion: MessageFeedbackVersion | null
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Delete feedback for one message after observing its current version. */
|
|
|
+interface MessageFeedbackDeleteRequest {
|
|
|
+ /** Persisted Session that owns the sidecar. */
|
|
|
+ readonly sessionId: SessionId
|
|
|
+ /** Message whose feedback should be absent after this operation. */
|
|
|
+ readonly messageId: MessageId
|
|
|
+ /** Observed item version; ignored when the item is already absent. */
|
|
|
+ readonly ifVersion: MessageFeedbackVersion
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Idempotent deletion acknowledgement. */
|
|
|
+interface MessageFeedbackDeleteValue {
|
|
|
+ /** Stable postcondition shared by the first deletion and every retry. */
|
|
|
+ readonly absent: true
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** No persisted Session header exists for the requested id. */
|
|
|
+interface MessageFeedbackSessionNotFound {
|
|
|
+ readonly code: 'session-not-found'
|
|
|
+ readonly sessionId: SessionId
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** The id does not name a derived, append-origin assistant message. */
|
|
|
+interface MessageFeedbackTargetNotFound {
|
|
|
+ readonly code: 'target-not-found'
|
|
|
+ readonly sessionId: SessionId
|
|
|
+ readonly messageId: MessageId
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** A material mutation did not match the addressed item's current version. */
|
|
|
+interface MessageFeedbackVersionConflict {
|
|
|
+ readonly code: 'version-conflict'
|
|
|
+ /** Authoritative current item, or `null` when it does not exist. */
|
|
|
+ readonly current: MessageFeedbackItem | null
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** A supplied note contains no non-whitespace character. */
|
|
|
+interface MessageFeedbackNoteBlank {
|
|
|
+ readonly code: 'note-blank'
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** A supplied note exceeds the configured UTF-8 byte limit. */
|
|
|
+interface MessageFeedbackNoteTooLarge {
|
|
|
+ readonly code: 'note-too-large'
|
|
|
+ readonly maxBytes: number
|
|
|
+ readonly actualBytes: number
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Failures shared by the public message-feedback operations. */
|
|
|
+type MessageFeedbackFailure =
|
|
|
+ | MessageFeedbackSessionNotFound
|
|
|
+ | MessageFeedbackTargetNotFound
|
|
|
+ | MessageFeedbackVersionConflict
|
|
|
+ | MessageFeedbackNoteBlank
|
|
|
+ | MessageFeedbackNoteTooLarge
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Successful public operation result. */
|
|
|
+interface MessageFeedbackSuccess<T> {
|
|
|
+ readonly ok: true
|
|
|
+ readonly value: T
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Rejected public operation result with a stable business failure. */
|
|
|
+interface MessageFeedbackRejected<E extends MessageFeedbackFailure> {
|
|
|
+ readonly ok: false
|
|
|
+ readonly error: E
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Result returned by the message-feedback `list` operation. */
|
|
|
+type MessageFeedbackListResult =
|
|
|
+ | MessageFeedbackSuccess<MessageFeedbackListValue>
|
|
|
+ | MessageFeedbackRejected<MessageFeedbackSessionNotFound>
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Result returned by the message-feedback `put` operation. */
|
|
|
+type MessageFeedbackPutResult =
|
|
|
+ | MessageFeedbackSuccess<MessageFeedbackItem>
|
|
|
+ | MessageFeedbackRejected<
|
|
|
+ | MessageFeedbackSessionNotFound
|
|
|
+ | MessageFeedbackTargetNotFound
|
|
|
+ | MessageFeedbackVersionConflict
|
|
|
+ | MessageFeedbackNoteBlank
|
|
|
+ | MessageFeedbackNoteTooLarge
|
|
|
+ >
|
|
|
+```
|
|
|
+
|
|
|
+```ts type-equiv
|
|
|
+/** Result returned by the message-feedback `delete` operation. */
|
|
|
+type MessageFeedbackDeleteResult =
|
|
|
+ | MessageFeedbackSuccess<MessageFeedbackDeleteValue>
|
|
|
+ | MessageFeedbackRejected<MessageFeedbackSessionNotFound | MessageFeedbackVersionConflict>
|
|
|
+```
|
|
|
+
|
|
|
## Data and concurrency
|
|
|
|
|
|
One Session sidecar row contains its header identity `{createdAt, cwd}` and feedback items keyed by `MessageId`. Each item carries a positive or negative rating, an optional note, Host-assigned `createdAt`/`updatedAt` timestamps, and its own opaque version. Versions are compared only for equality and only against the addressed message; callers do not order or synthesize them.
|
|
|
@@ -22,12 +199,15 @@ The stored `{createdAt, cwd}` identity must match the inspected header. A mismat
|
|
|
|
|
|
The service stores whole Session rows in the `message_feedback` storage domain through `ctx.storageDomain`. Before `put` commits a row that references a target message, a matching live target passes through the canonical `ctx.sessions.flush` checkpoint; both live and cold paths are then physically read from sequence zero through `SessionPersistence.readFrom`. The resulting observation is revalidated before the sidecar write, so the durable target log always precedes its sidecar commit. `maxNoteBytes` is required and bounds note text by UTF-8 bytes; the Web Host composition sets `8192`. The package publishes the Host `messageFeedback.list`, `messageFeedback.put`, and `messageFeedback.delete` unary Remote contract through `GatewayService` and `@Remote`; the generated Cordis surface below is the method-level authority.
|
|
|
|
|
|
+Plugin disposal closes mutation admission, drains accepted per-Session queue work, and then closes the storage domain.
|
|
|
+
|
|
|
## Boundaries and limitations
|
|
|
|
|
|
- The client Remote aggregate mount and UI consumer are separately owned and deferred.
|
|
|
- The mutation queue is process-local. Storage-domain has no cross-process conditional write, so multiple Host writers to one storage root have no compare-and-swap or lost-update guarantee.
|
|
|
- Session persistence has no durable deletion surface. The service does not treat `session/disposed` or `host/session-removed` as deletion and therefore performs no fake cascade; orphan sidecar rows may remain after out-of-band log removal.
|
|
|
- A request in the narrow interval after live detach but before the persistence catalog materializes the header can receive `session-not-found`; callers retry after retirement materialization.
|
|
|
+- Cold requests scan the complete Session snapshot catalog because persistence has no lookup-by-id metadata operation. One Session row also has no item-count or aggregate-byte cap; `maxNoteBytes` bounds only each note until a concrete consumer owns a row policy.
|
|
|
- Header identity detects a reused id only when `{createdAt, cwd}` differs; a cloned log retaining the same header identity is indistinguishable by this contract.
|
|
|
- The Host contract records no authenticated actor or audit identity and therefore assumes a trusted caller boundary.
|
|
|
|