Skip to content

feat(knowledge): promote selected Chat thread messages - #2052

Merged
ootakazuhiko merged 6 commits into
mainfrom
feat/2015-thread-promote-to-knowledge
Aug 10, 2026
Merged

feat(knowledge): promote selected Chat thread messages#2052
ootakazuhiko merged 6 commits into
mainfrom
feat/2015-thread-promote-to-knowledge

Conversation

@ootakazuhiko

@ootakazuhiko ootakazuhiko commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

対象

Refs #2015
Parent: #2003 Workstream 07
Depends on merged PR #2050 / #2051

背景と目的

Knowledge share の Chat thread から、利用者が明示選択した direct reply だけを新規 KnowledgeSynthesis version 1 へ昇格する backend 境界を追加します。自動要約、thread 全文コピー、既存 synthesis への append は行いません。

実装概要

  • KnowledgeThreadPromotion、immutable selected-message snapshot、opaque request ledger、explicit synthesis group grant を expand-only migration で追加
  • KnowledgeSynthesisSource に typed sourceThreadPromotionId FKを追加し、既存 exactly-one 制約を維持
  • preview/commit API:
    • POST /chat-messages/{rootMessageId}/promote-to-knowledge/preview
    • POST /chat-messages/{rootMessageId}/promote-to-knowledge
  • ordered direct reply 1〜100件、任意のshare-card参照、personal/organization destination、利用者入力synthesisをexact binding
  • domain-separated HMAC preview token(TTL 10分)とopaque idempotency key hash
  • current Chat room read ACL と destination Knowledge ACL/group membership をcommit時に別々に再検査
  • mandatory auditをsynthesis/promotion mutationと同一Serializable transactionで確定
  • room access失効後はdestination ACLでsynthesis本文を保持しつつlive Chat provenance IDをredact
  • migration前organization synthesisのorganization-wide read互換を維持し、promotion-generated synthesisだけexplicit grantsを要求
  • 一般synthesis APIでは内部thread_promotion sourceを指定不可。promotion use caseだけがtyped sourceを作成

設計上の判断

  • ChatMessageTypeには値を追加せず、PR #2051のgeneric text fallback + KnowledgeShare relationを利用
  • promotionはKnowledgeConversationへ変換せず独立aggregateとする
  • selected reply本文・順序・hash・activity sequence・作成時刻をimmutable copyとして固定
  • same key + same payloadは既存結果を返し、same key + different payloadはsanitized 409
  • concurrent replayはSerializable transactionを最大3回だけ再試行
  • organization destinationはactorのcurrent organizationかつcurrent active group membershipの明示subset(最大20)だけ許可
  • promotion後のChat ACL失効はdestination synthesisを削除せず、provenance referenceのみredact

契約変更

Schema / migration

additive only:

  • KnowledgeThreadPromotionAuthorCategory
  • KnowledgeThreadPromotion
  • KnowledgeThreadPromotionMessage
  • KnowledgeThreadPromotionRequest
  • KnowledgeSynthesisGroupGrant
  • KnowledgeSynthesisSource.sourceThreadPromotionId
  • FK / CHECK / unique / index / immutable trigger / deferred aggregate completeness trigger

既存table/columnのdrop・rename・destructive type changeはありません。

API

上記preview/commit endpointをadditiveに追加。OpenAPI breaking diffは0件です。

Env

新規env keyなし。既存KNOWLEDGE_CURSOR_SIGNING_SECRETから用途分離keyを導出します。productionでは既存どおり32 UTF-8 bytes以上を必須とします。

後方互換性

直前main b720f575140e533c59070d33723146925faad489 の旧backendを新migration適用DBへ接続し、実promotion row存在下でtimeline/thread/search/unread/notification/health/readiness/writeを検証しました。Chat rootはmessageType=textかつgeneric fallbackのため未知enum値はありません。

Security / privacy

  • preview tokenに本文・raw message ID・room ID・request keyを平文保存しない
  • request ledgerにraw request key/bodyを保存しない
  • audit metadataはschema version、件数、scope、結果codeだけ
  • 未選択reply/thread全文/notification/search snippetをpromotion snapshotへ複製しない
  • unauthorized/missingは同一404
  • source Chat ACLとdestination Knowledge ACLを分離
  • provider key/URL/raw errorをresponse/logへ展開しない
  • secret scan: PASS

検証

  • focused unit: 24/24 PASS
  • focused coverage: statements/lines 93.42%, branches 73.55%, functions 98.14%
  • backend full: 2161/2161 PASS
  • frontend full: 720/720 PASS
  • PostgreSQL 15 migration/schema/application integration: PASS
    • selected-only snapshot
    • deferred completeness / immutability / exactly-one provenance
    • idempotent/concurrent replay
    • explicit organization grant / revoked grant denial
    • Chat ACL loss provenance redaction
    • mandatory audit rollback
  • old-application compatibility: PASS
  • Prisma format/validate/generate: PASS
  • lint / format-check / typecheck / build: PASS
  • bounded-context dependency / coverage: PASS
  • OpenAPI export/snapshot: PASS
  • OpenAPI breaking diff: 0
  • make audit: backend/frontend 0 vulnerabilities
  • make ops-quality: PASS
  • docs index/image links: PASS
  • git diff --check: PASS
  • repository secret scan: PASS
  • RELEASE_E2E_SCOPE=core make release-readiness: PASS(core E2E 107/107)

未実施

  • production migration/cutover
  • Sakura VPS / live Quadlet/systemd
  • Google Drive / Sakura Object Storage credential
  • external LLM
  • PR Dのfrontend UI / real-backend promote E2E

リスク

  • 新しいmigrationはtrigger/constraintを含むため、PostgreSQL 15 integrationとold-app実row互換で固定しています。
  • source room ACL失効後はlive provenanceをredactします。immutable synthesis本文は明示promotionの結果としてdestination ACLで保持されます。
  • external-user-enabled roomはMVPでfail closedです。

Rollback

applicationを直前mainへ戻し、新endpoint/UIを無効化します。expand-only table/column、promotion snapshot、synthesis version、audit historyは保持し、migration down/dropやsource/message削除は行いません。ChatMessageTypeを変更していないため旧clientはgeneric text fallbackを継続表示できます。

Review remediation

exact-head review前の指摘を以下のとおり修正しました。

  • fresh previewごとのrandom promotion IDをcanonical bindingから除外し、same key + same exact logical payloadが既存promotionへ収束するよう修正
  • PostgreSQL integrationへfresh-preview replayを追加し、created=false/reused=trueを確認
  • promotion destination versionへcommit後にsecond sourceを追加する操作をDB triggerで拒否し、実PostgreSQL negative testを追加
  • current Chat contractで識別可能なsource categoryをuser-onlyとして固定。external-enabled roomはfail closed、system/tool message typeは対象外と文書化
  • posted share lookupへrevokedAt=nullを明示
  • Copilot指摘に従いorganizationGroupCount response上限をrequest/DB契約と同じ20へ統一
  • 同一previewを別request keyでcommitした場合をsanitized 409 promotion_conflictへ正規化し、逐次・並行ともpromotion/synthesis/request ledgerが1件へ収束するPostgreSQL回帰testを追加
  • source roomが後からexternal-user-enabledへ変更された場合もsynthesis本文はdestination ACLで保持しつつlive promotion provenance IDをredact
  • different-key race後にKnowledgeSynthesis/Version/Promotion/Requestを各1件と直接countし、orphan/duplicate不在を固定
  • source share revoke後もdestination synthesis本文を保持しつつlive promotion provenance IDをredactするread-path integrationを追加
  • Copilot指摘に従いnon-null share-card previewへtyped omittedCategoriesを追加し、includeSharedCard=trueのHTTP mapper回帰testを追加

Current exact head: 3f4b3cc5eeeaccfabf34c851201d9700e5a87426

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Knowledge Hub の「Chat thread から、利用者が明示選択した direct reply のみを新規 KnowledgeSynthesis v1 へ昇格する」バックエンド境界(schema / domain / routes / adapters / tests / docs)を追加し、既存の Knowledge share / Chat 互換を維持したまま promotion を additive に実装するPRです。

Changes:

  • KnowledgeThreadPromotion aggregate(selected-only immutable snapshot / request ledger / group grants / provenance FK)と expand-only migration を追加
  • preview/commit API(/chat-messages/:rootMessageId/promote-to-knowledge[/preview])と token/idempotency を実装
  • synthesis visibility / provenance へ thread promotion source を統合し、Chat ACL 失効後は provenance を redact する挙動を追加

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
scripts/test-knowledge-thread-promotion-schema-postgres.sh Promotion schema の PostgreSQL 統合検証スクリプト追加
scripts/test-knowledge-thread-promotion-old-app.sh old-app 互換検証(baseline SHA 固定)スクリプト追加
packages/backend/test/knowledgeThreadPromotionUseCases.test.js use case(preview/commit/idempotency/validation)単体テスト追加
packages/backend/test/knowledgeThreadPromotionToken.test.js preview token codec の単体テスト追加
packages/backend/test/knowledgeThreadPromotionSchema.test.js Prisma schema/migration/integration wrapper の契約テスト追加
packages/backend/test/knowledgeThreadPromotionRoutes.test.js Fastify routes の入出力 allowlist/validation テスト追加
packages/backend/test/knowledgeThreadPromotionAuditAdapter.test.js promotion audit writer のメタデータ境界テスト追加
packages/backend/test/knowledgeProvenanceUseCases.test.js 一般 synthesis API から thread_promotion source を指定不可にする回帰テスト追加
packages/backend/src/routes/knowledgeThreadPromotions.ts promotion preview/commit の新規 route と schema/mapper 追加
packages/backend/src/routes/knowledgeSyntheses.ts actor に chat claims を含め、thread promotion provenance の可視性評価を可能に
packages/backend/src/routes/knowledgeShares.ts knowledgeSharePublicCardSchema を export、chat actor 取得を共通化
packages/backend/src/routes/knowledgeRouteContext.ts chat claims を KnowledgeActor へ optional に注入できるよう拡張
packages/backend/src/routes/index.ts promotion routes の登録追加
packages/backend/src/application/knowledge/knowledgeThreadPromotionUseCases.ts promotion use cases(validation/token/idempotency)追加
packages/backend/src/application/knowledge/knowledgeThreadPromotionToken.ts domain-separated HMAC preview token codec 追加
packages/backend/src/application/knowledge/knowledgeThreadPromotionPorts.ts promotion port/DTO/limits 定義追加
packages/backend/src/application/knowledge/knowledgeSynthesisUseCases.ts synthesis source kind の入力許可リストを knowledgeSynthesisInputSourceKinds に更新
packages/backend/src/application/knowledge/knowledgeProvenancePorts.ts thread_promotion kind を内部用に追加、入力用 kind を分離
packages/backend/src/application/knowledge/knowledgeItemPorts.ts KnowledgeActor に optional chat claims を追加
packages/backend/src/adapters/knowledge/prismaKnowledgeThreadPromotionAuditAdapter.ts promotion audit の Prisma writer 追加
packages/backend/src/adapters/knowledge/prismaKnowledgeSynthesisVisibility.ts synthesis 可視性 where と thread promotion source の Chat ACL 検査を追加
packages/backend/src/adapters/knowledge/prismaKnowledgeProvenanceAdapter.ts provenance に thread promotion source を統合、visibility where を差し替え
packages/backend/scripts/knowledge-thread-promotion-schema-integration.mjs promotion schema の DB 統合検証追加
packages/backend/scripts/knowledge-thread-promotion-application-integration.mjs app レベル統合(ACL loss redaction / audit rollback 等)検証追加
packages/backend/scripts/knowledge-thread-promotion-old-app-compat.mjs old-app read/write 互換検証を追加
packages/backend/prisma/schema.prisma promotion/grants/provenance FK を Prisma schema に追加
packages/backend/prisma/migrations/20260810130000_add_knowledge_thread_promotion/migration.sql expand-only migration(constraints/triggers/index/audit check)追加
packages/backend/bounded-context-registry.cjs Knowledge routes に ThreadPromotions を追加
Makefile knowledge-thread-promotion-postgres / knowledge-thread-promotion-old-app ターゲット追加
docs/requirements/knowledge-hub.md thread promote の要件追記
docs/architecture/knowledge-hub-boundary.md promotion 境界の設計追記

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/backend/src/routes/knowledgeThreadPromotions.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/backend/src/routes/knowledgeThreadPromotions.ts:258

  • The preview response schema allows destination.organizationGroupCount up to 100, but the request contract/validation caps organizationGroupAccountIds at knowledgeThreadPromotionLimits.organizationGroupAccountIds (20). Keeping the response schema aligned helps prevent contract drift and ensures OpenAPI reflects real bounds.
        organizationGroupCount: {

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

packages/backend/test/knowledgeThreadPromotionUseCases.test.js:294

  • The preview-usecase test exercises includeSharedCard=true, but it doesn't assert the full shared-card shape required by the route mapper (knowledgeShareCardResponse expects omittedCategories). Adding a regression assertion here would have caught the missing omittedCategories field that can otherwise surface as a runtime 500 when mapping the preview response.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/backend/src/routes/knowledgeThreadPromotions.ts:21

  • knowledgeShares.js is imported twice; consolidating into a single import avoids duplicate-import patterns and keeps the import section easier to maintain.
import { knowledgeSharePublicCardSchema } from './knowledgeShares.js';
import { knowledgeShareCardResponse } from './knowledgeShares.js';

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated no new comments.

@ootakazuhiko
ootakazuhiko marked this pull request as ready for review August 10, 2026 09:12
@ootakazuhiko
ootakazuhiko merged commit ac51f1f into main Aug 10, 2026
23 checks passed
@ootakazuhiko
ootakazuhiko deleted the feat/2015-thread-promote-to-knowledge branch August 10, 2026 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants