diff --git a/.ai/domain/README.md b/.ai/domain/README.md index 40f3fc4..4288839 100644 --- a/.ai/domain/README.md +++ b/.ai/domain/README.md @@ -3,5 +3,10 @@ | File | Purpose | |---|---| | [../domain.md](../domain.md) | Tech ↔ 用户名 | -| [domain-map.md](domain-map.md) | Bounded contexts(冻结) | +| [domain-map.md](domain-map.md) | Bounded contexts(P1 冻结 + Ops Wave 0 Draft) | | [erd.md](erd.md) | P1 表结构草案(冻结) | +| [boundary-rules.md](boundary-rules.md) | BC owns / allowed / forbidden(**P0** · Wave 0) | +| [glossary.yaml](glossary.yaml) | 工程/API 唯一词(与 lexicon 双轨) | +| [entity-catalog.md](entity-catalog.md) | 概念级实体目录(无 migration) | + +Ops Contract-First 归档:`docs/WAVE0/`。 diff --git a/.ai/domain/boundary-rules.md b/.ai/domain/boundary-rules.md new file mode 100644 index 0000000..d36a31e --- /dev/null +++ b/.ai/domain/boundary-rules.md @@ -0,0 +1,239 @@ +# Bounded Context Boundary Rules + +> Wave 0 · **P0 资产** · AI / Engineer 只读;变更须 Architect + Human +> 运行时权威副本:`.ai/domain/boundary-rules.md` + +Status: `Draft`(Human Review 后 Frozen) + +--- + +## 如何读 + +- **owns**:该 BC **唯一写权威**(修改入口)。 +- **does_not_own**:明示禁止「顺手改」。 +- **allowed**:允许的依赖(多为读或经门面)。 +- **forbidden**:禁止的依赖(含跨 BC 写支付/会员等)。 + +违反本文件 = Reviewer **BLOCK**(架构回归)。 + +--- + +## Admin_Auth_Audit + +```yaml +Admin_Auth_Audit: + owns: + - AdminAccount + - AdminSession + - AuditLog + does_not_own: + - UserStatus + - MembershipPlan + - Entitlement + allowed: + - Admin_RBAC.read + forbidden: + - Payment.write + - C端业务聚合根的隐式写入 +``` + +## Admin_RBAC + +```yaml +Admin_RBAC: + owns: + - AdminRole + - AdminPermission + does_not_own: + - User + - Profile + - Membership + allowed: + - Admin_Auth_Audit.write_audit + forbidden: + - Payment + - Membership.write + - DeviceAuth.user_token_issue +``` + +## Identity_Profile + +```yaml +Identity_Profile: + owns: + - User + - Profile + - DeviceIdentity + - UserStatus + - BehaviorSnapshot + - PsychologicalTagSet + does_not_own: + - MembershipPlan + - Entitlement + - Order + - Payment + - RedemptionCode + allowed: + - Analytics_OpsB.read + forbidden: + - Payment + - Membership.write +``` + +## Membership_Orders + +```yaml +Membership_Orders: + owns: + - MembershipPlan + - Entitlement + - Membership + - Order + - DeepAccess + - RedemptionCode + - Payment # mock 今;真支付适配器后置,仍归本 BC + does_not_own: + - UserStatus + - AdminRole + - FilterRule + allowed: + - Identity_Profile.read + forbidden: + - Ask_Ops.write_prompt + - Content_Safety.rule_mutate_via_order +``` + +## Analytics_OpsB + +```yaml +Analytics_OpsB: + owns: + - AnalyticsSession + - AnalyticsEvent + does_not_own: + - User + - Membership + allowed: + - Identity_Profile.read_ids_only + forbidden: + - Payment + - 采集 PII 正文(生日/问答全文等) +``` + +## Ops_Content + +```yaml +Ops_Content: + owns: + - HomeTool + - ScalePublishState + does_not_own: + - Scale 题目正文编辑器(未开 ECR 前禁止扩张) + allowed: + - Explore_Reports.publish_gate + forbidden: + - Community + - Payment +``` + +## Account_Risk + +```yaml +Account_Risk: + owns: + - RiskFlag + - BanRecord + does_not_own: + - MembershipPlan + allowed: + - Identity_Profile.UserStatus.transition # 经明确应用服务 + - Admin_Auth_Audit.write_audit + forbidden: + - Payment.refund_silent +``` + +## Content_Safety + +```yaml +Content_Safety: + owns: + - FilterRule + - ModerationCase + - BlockPolicy + - CrisisEvent + - CrisisPolicy + does_not_own: + - Membership + - Payment + - AdminRole + allowed: + - Identity_Profile.read + - Ask_Ops.read_session_meta + forbidden: + - Membership.write + - Payment +``` + +## Ask_Ops + +```yaml +Ask_Ops: + owns: + - AskSessionView + - QualityFeedback + - HandoffCase + - SystemPrompt + - KnowledgeSource + - KnowledgeChunk + - ToolDefinition + does_not_own: + - Payment + - MembershipPlan + - UserStatus + allowed: + - Identity_Profile.read + - Content_Safety.evaluate + forbidden: + - Payment + - Membership.write + - Admin_RBAC.write +``` + +## Ops_CMS_NoUGC + +```yaml +Ops_CMS_NoUGC: + owns: + - Banner + - FeedSlot + - ScheduledPublication + does_not_own: + - UGC Post + - CommentGraph + - TrustScore + allowed: + - Admin_RBAC.check + forbidden: + - Community + - 用户生成内容入库为主路径 +``` + +## Community + +```yaml +Community: + status: Forbidden + owns: [] + note: feature-map UGC 广场 [No];开启须 L3 ADR + 新 Capability +``` + +--- + +## 反模式(禁止) + +```text +AskService → load User → update Membership → charge Payment +UserService.updateMembership() +Content_Safety 直接改 Entitlement +Ops_CMS 引入「用户帖子」表却声称 NoUGC +``` diff --git a/.ai/domain/domain-map.md b/.ai/domain/domain-map.md index debada8..018b897 100644 --- a/.ai/domain/domain-map.md +++ b/.ai/domain/domain-map.md @@ -23,6 +23,26 @@ --- +## Ops Platform Bounded Contexts(Wave 0 Draft) + +> 详细 owns/forbidden:[`boundary-rules.md`](boundary-rules.md) · 词表:[`glossary.yaml`](glossary.yaml) · 实体:[`entity-catalog.md`](entity-catalog.md) +> 归档:`docs/WAVE0/domain/` · Capability:`docs/WAVE0/capability/ops-capability-map.md` +> **不修改**上方 P1 不变量;Ops BC 实现须独立 ECR。 + +| Context | Capability | Status | +|---|---|---| +| Admin_Auth_Audit | AdminGovernance | Shipped Ops-A | +| Admin_RBAC | AdminGovernance | Draft → ECR-013A | +| Analytics_OpsB | GrowthInsights / UserIntelligence(read) | Shipped Ops-B | +| Ops_Content | ExploreConfig (partial) | Shipped Ops-C | +| Account_Risk | AccountLifecycle | Spec via ECR-013B(Identity_Profile owns UserStatus) | +| Content_Safety | ContentSafety · CrisisCare | ContentSafety **ECR-019**;CrisisCare **ECR-022**(CrisisPolicy 只读) | +| Ask_Ops | AskOperations · AICoreConfig | AskOperations **ECR-017/020**;AICoreConfig **ECR-021**(SystemPrompt 只读) | +| Ops_CMS_NoUGC | OpsCMS | Draft | +| Community | — | **Forbidden** | + +--- + ## Context diagram ``` diff --git a/.ai/domain/entity-catalog.md b/.ai/domain/entity-catalog.md new file mode 100644 index 0000000..bc78d1a --- /dev/null +++ b/.ai/domain/entity-catalog.md @@ -0,0 +1,43 @@ +# Entity Catalog(Wave 0 · 概念级) + +> 仅登记名称、归属 BC、Capability、生命周期备注。**不建表、不写 migration。** +> 与 [`glossary.yaml`](glossary.yaml) · [`boundary-rules.md`](boundary-rules.md) 一致。 + +Status: `Draft` + +| Entity | BC | Capability | Notes | +|--------|----|------------|-------| +| AdminAccount | Admin_Auth_Audit | AdminGovernance | 已存在 Ops-A | +| AdminSession | Admin_Auth_Audit | AdminGovernance | 已存在 | +| AuditLog | Admin_Auth_Audit | AdminGovernance | 已存在;只追加 | +| AdminRole | Admin_RBAC | AdminGovernance | **ECR-013A** | +| AdminPermission | Admin_RBAC | AdminGovernance | **ECR-013A** | +| User | Identity_Profile | AccountLifecycle | 已存在 | +| Profile | Identity_Profile | UserIntelligence | 已存在 | +| UserStatus | Identity_Profile | AccountLifecycle | **ECR-013B** 状态机 | +| AccountStateTransition | Identity_Profile | AccountLifecycle | **ECR-013B** | +| BehaviorSnapshot | Identity_Profile | UserIntelligence | **ECR-016** 读模型(analytics 聚合) | +| PsychologicalTagSet | Identity_Profile | UserIntelligence | **ECR-016** 由报告 type 派生 | +| MembershipPlan | Membership_Orders | CommerceEntitlement | **ECR-014 Closed** | +| Entitlement | Membership_Orders | CommerceEntitlement | **ECR-018** 读模型(Membership∪DeepAccess) | +| RedemptionCode | Membership_Orders | CommerceEntitlement | **ECR-015 Closed** | +| Membership | Membership_Orders | CommerceEntitlement | 已存在 | +| Order | Membership_Orders | CommerceEntitlement | 已存在 | +| DeepAccess | Membership_Orders | CommerceEntitlement | 已存在 | +| Payment | Membership_Orders | CommerceEntitlement | mock;真支付最后 | +| HomeTool | Ops_Content | ExploreConfig | 已存在 Ops-C | +| ScalePublishState | Ops_Content | ExploreConfig | 已存在 | +| AnalyticsSession / Event | Analytics_OpsB | GrowthInsights | 已存在 Ops-B | +| FilterRule | Content_Safety | ContentSafety | **ECR-019** 只读 + evaluate | +| ModerationCase | Content_Safety | ContentSafety | 后置 | +| CrisisEvent / CrisisPolicy | Content_Safety | CrisisCare | **ECR-022** CrisisPolicy 只读;CrisisEvent 后置 | +| SystemPrompt / Knowledge* / ToolDefinition | Ask_Ops | AICoreConfig | **ECR-021** SystemPrompt 只读;Knowledge/Tools 后置 | +| AskSessionView / QualityFeedback | Ask_Ops | AskOperations | **ECR-017** AskSessionView;**ECR-020** QualityFeedback | +| Banner / FeedSlot | Ops_CMS_NoUGC | OpsCMS | 后置 | +| UGC* | Community | — | **Forbidden** | + +## 状态机(ECR-013B · 契约已开) + +`UserStatus`: 见 `.ai/product/feature-spec/ops-account-lifecycle.md`。 +`deleted` soft-delete **不在 013B**(另开)。 +实现轮前:**禁止** migration / DeviceAuth 改动。 diff --git a/.ai/domain/glossary.yaml b/.ai/domain/glossary.yaml new file mode 100644 index 0000000..252bb85 --- /dev/null +++ b/.ai/domain/glossary.yaml @@ -0,0 +1,116 @@ +# domain glossary — engineering / API unique terms + +# Wave 0 Draft. Runtime copy: .ai/domain/glossary.yaml +# User-facing copy stays in .ai/product/lexicon.md +# Conflict: Architect + Human resolve; do not invent synonyms in code. + +version: "0.1.0" +status: draft + +terms: + AdminRole: + zh: 管理员角色 + bc: Admin_RBAC + forbidden: [admin_group, privilege_group] + + AdminPermission: + zh: 管理员权限点 + bc: Admin_RBAC + forbidden: [acl_flag, authz_bit] + + AuditLog: + zh: 操作审计日志 + bc: Admin_Auth_Audit + forbidden: [admin_history] + + UserStatus: + zh: 用户账户状态 + bc: Identity_Profile + values: [active, disabled, banned, suspended] + forbidden: [user_flag, account_level] + + AccountStateTransition: + zh: 账户状态迁移 + bc: Identity_Profile + forbidden: [status_patch_log] + + MembershipPlan: + zh: 会员套餐 + bc: Membership_Orders + forbidden: [package, vip_level, 订阅档, sku_vip] + + Entitlement: + zh: 权益 + bc: Membership_Orders + forbidden: [privilege_pack, benefit_bundle] + + RedemptionCode: + zh: 兑换码 + bc: Membership_Orders + forbidden: [gift_card, activation_key_alias] + + DeepAccess: + zh: 深度版 + bc: Membership_Orders + forbidden: [Unlock, unlock_report] + + BehaviorSnapshot: + zh: 行为快照 + bc: Identity_Profile + forbidden: [user_track_dump] + + PsychologicalTagSet: + zh: 心理标签集 + bc: Identity_Profile + forbidden: [psy_labels_raw] + + FilterRule: + zh: 过滤规则 + bc: Content_Safety + forbidden: [badword_only] + + ModerationCase: + zh: 审核工单 + bc: Content_Safety + forbidden: [review_ticket_ugc] + + CrisisEvent: + zh: 危机事件 + bc: Content_Safety + forbidden: [suicide_flag_public] + + CrisisPolicy: + zh: 危机策略 + bc: Content_Safety + forbidden: [kill_switch_generic] + + SystemPrompt: + zh: 系统提示词 + bc: Ask_Ops + forbidden: [god_prompt] + + KnowledgeSource: + zh: 知识源 + bc: Ask_Ops + forbidden: [rag_file] + + KnowledgeChunk: + zh: 知识块 + bc: Ask_Ops + forbidden: [embedding_row_ui] + + ToolDefinition: + zh: 工具定义 + bc: Ask_Ops + forbidden: [function_call_config_loose] + + Banner: + zh: 运营横幅 + bc: Ops_CMS_NoUGC + forbidden: [story_feed_ugc] + + Community: + zh: (禁止能力) + bc: Community + status: forbidden + forbidden: [ugc_plaza, 社区广场] diff --git a/.ai/product/feature-map.md b/.ai/product/feature-map.md index e0706bb..e562962 100644 --- a/.ai/product/feature-map.md +++ b/.ai/product/feature-map.md @@ -227,16 +227,18 @@ UI **不出现「塔罗」**。禁止神谕吉凶、恐吓话术。 ## 7. 运营后台(内部 · 非 C 端 Tab) -详见 Spec:[feature-spec/ops-admin.md](feature-spec/ops-admin.md) · ECR-006 +详见 Spec:[feature-spec/ops-admin.md](feature-spec/ops-admin.md) · ECR-006 +**演进规范(Wave 0):** [`docs/WAVE0/`](../../docs/WAVE0/) · Capability Map · boundary-rules · Contract-First(禁止 Feature 堆砌进实现) | 分期 | 内容 | |---|---| | Phase A `[Ops]` | 登录 · 用户/订单查询 · 会员授予 · 审计 · `apps/admin-h5`(ECR-006 Closed) | -| Phase B `[Ops]` | 行为分析:自有埋点 + 管理端「数据」看板(**ECR-007 Implemented** · Spec `ops-analytics.md`) | -| Phase C `[Ops]` | 内容:首页宫格 CRUD · 测评上下架(**ECR-008 Implemented** · Spec `ops-content.md`) | +| Phase B `[Ops]` | 行为分析:自有埋点 + 管理端「数据」看板(**ECR-007** · Spec `ops-analytics.md`) | +| Phase C `[Ops]` | 内容:首页宫格 CRUD · 测评上下架(**ECR-008** · Spec `ops-content.md`) | | Phase D `[Ops]` | 商业加深:订单筛选 · 展示价 · 退款只读(**ECR-009** · Spec `ops-commerce.md`) | | Phase E `[Ops]` | 系统:RBAC · 封禁 · 推送占位(**ECR-010** · Spec `ops-system.md`) | -| Phase F+ | 封禁细策略 · 真推送通道 · 更细权限矩阵(各开独立 ECR) | +| Phase D+ Contract | **Contract-First** 扩展目录:**ECR-013A…040**(RBAC/兑换码/问答运营/安全/危机/CMS/ExploreConfig 等)。见 `docs/WAVE0/` | +| Phase F+ | 封禁细策略 · 真推送通道 · 更细权限矩阵;**UGC 仍 `[No]`**;真支付最后 | 不计入 P1 Complete;不进入五 Tab。 @@ -258,11 +260,11 @@ UI **不出现「塔罗」**。禁止神谕吉凶、恐吓话术。 | `/scales/:slug` | 探索测试作答 | | | `/reports` | 成长报告列表 | | | `/reports/:id` | 成长报告详情 | | -| `/star` | 星座 | P2 | -| `/synastry` | 合盘 | P2 | -| `/synastry/invite/:token` | 合盘邀请 | P2 | -| `/rhythm` | 身心节律 | P2 | -| `/cards` | 意象卡片 | P2 | +| `/star` | 星座 | P2 Complete | +| `/synastry` | 合盘 | P2 Complete | +| `/synastry/invite/:token` | 合盘邀请 | P2 Complete | +| `/rhythm` | 身心节律 | P2 Complete | +| `/cards` | 意象卡片 | P2 Complete | 兼容重定向:`/decode` → `/portrait`(仅技术兼容,UI 不出现 decode 文案)。 diff --git a/.ai/product/feature-spec/README.md b/.ai/product/feature-spec/README.md index 54da7c9..7a5f2be 100644 --- a/.ai/product/feature-spec/README.md +++ b/.ai/product/feature-spec/README.md @@ -21,11 +21,38 @@ | [life-rhythm.md](life-rhythm.md) | 身心节律 | §2.7 | `/rhythm` | P2 Complete | | [image-card.md](image-card.md) | 意象卡片 | §2.8 | `/cards` | P2 Complete | | [ops-admin.md](ops-admin.md) | 运营后台 | §7 | `admin-h5` `/` `/users/:id` … | Ops-A | -| [ops-analytics.md](ops-analytics.md) | 运营行为分析(埋点+数据看板) | §7 | `admin-h5` `/analytics` · H5 track | Ops-B · ECR-007 Implemented | -| [ops-content.md](ops-content.md) | 运营内容(宫格+测评上下架) | §7 | `admin-h5` `/content` · `GET /home/tools` | Ops-C · ECR-008 Implemented | +| [ops-analytics.md](ops-analytics.md) | 运营行为分析(埋点+数据看板) | §7 | `admin-h5` `/analytics` · H5 track | Ops-B · ECR-007 | +| [ops-content.md](ops-content.md) | 运营内容(宫格+测评上下架) | §7 | `admin-h5` `/content` · `GET /home/tools` | Ops-C · ECR-008 | | [ops-commerce.md](ops-commerce.md) | 运营商业加深(订单筛选+展示价+退款只读) | §7 | `admin-h5` `/orders` `/pricing` | Ops-D · ECR-009 | | [ops-system.md](ops-system.md) | 运营系统(RBAC+封禁+推送占位) | §7 | `admin-h5` `/push` `/admins` | Ops-E · ECR-010 | | [input-compliance.md](input-compliance.md) | 用户文字输入合规 | 横切 · 安全 | 各写接口 · code 40060 | P2 · ECR-012 | +| [ops-rbac.md](ops-rbac.md) | 运营 RBAC | §7 | `admin-h5` `/me` permissions · `/admin/roles*` | Ops · **ECR-013A** | +| [ops-redemption-code.md](ops-redemption-code.md) | 兑换码 RedemptionCode | §7 | `admin-h5` `/codes` · `POST /membership/redeem` | Ops · **ECR-015(local)** | +| [ops-user-intelligence.md](ops-user-intelligence.md) | 用户洞察 UserIntelligence | §7 | `admin-h5` 用户详情「洞察」 | Ops · **ECR-016(local)** | +| [ops-ask-operations.md](ops-ask-operations.md) | 问答运营 AskOperations | §7 | `admin-h5` `/ask` | Ops · **ECR-017** | +| [ops-entitlement.md](ops-entitlement.md) | 用户权益 Entitlement | §7 | `admin-h5` 用户详情「权益」 | Ops · **ECR-018** | +| [ops-content-safety.md](ops-content-safety.md) | 内容安全 ContentSafety | §7 | `admin-h5` `/safety` | Ops · **ECR-019** | +| [ops-quality-feedback.md](ops-quality-feedback.md) | 问答质量反馈 QualityFeedback | §7 | `admin-h5` `/ask` | Ops · **ECR-020** | +| [ops-ai-core-config.md](ops-ai-core-config.md) | AI 核心配置 AICoreConfig | §7 | `admin-h5` `/ai` | Ops · **ECR-021** | +| [ops-crisis-care.md](ops-crisis-care.md) | 危机关怀 CrisisCare | §7 | `admin-h5` `/crisis` | Ops · **ECR-022** | +| [ops-knowledge-source.md](ops-knowledge-source.md) | AI 知识源 KnowledgeSource | §7 | `admin-h5` `/ai` | Ops · **ECR-023** | +| [ops-banner.md](ops-banner.md) | OpsCMS Banner | §7 | `admin-h5` `/cms` | Ops · **ECR-024** | +| [ops-feed-slot.md](ops-feed-slot.md) | OpsCMS FeedSlot | §7 | `admin-h5` `/cms` | Ops · **ECR-025** | +| [ops-scheduled-publication.md](ops-scheduled-publication.md) | OpsCMS ScheduledPublication | §7 | `/admin/cms/publications*` | Ops · **ECR-026** | +| [ops-knowledge-chunk.md](ops-knowledge-chunk.md) | AICoreConfig KnowledgeChunk | §7 | `/admin/ai/knowledge-chunks*` | Ops · **ECR-027** | +| [ops-tool-definition.md](ops-tool-definition.md) | AICoreConfig ToolDefinition | §7 | `/admin/ai/tools*` | Ops · **ECR-028** | +| [ops-block-policy.md](ops-block-policy.md) | ContentSafety BlockPolicy | §7 | `/admin/content-safety/block-policies*` | Ops · **ECR-029** | +| [ops-moderation-case.md](ops-moderation-case.md) | ContentSafety ModerationCase | §7 | `/admin/content-safety/cases*` | Ops · **ECR-030** | +| [ops-crisis-event.md](ops-crisis-event.md) | CrisisCare CrisisEvent | §7 | `/admin/crisis/events*` | Ops · **ECR-031** | +| [ops-intervention-outcome.md](ops-intervention-outcome.md) | CrisisCare InterventionOutcome | §7 | `/admin/crisis/interventions*` | Ops · **ECR-032** | +| [ops-handoff-case.md](ops-handoff-case.md) | AskOperations HandoffCase | §7 | `/admin/ask/handoffs*` | Ops · **ECR-033** | +| [ops-privacy-request.md](ops-privacy-request.md) | AdminGovernance PrivacyRequest | §7 | `/admin/privacy/requests*` | Ops · **ECR-034** | +| [ops-star-config.md](ops-star-config.md) | ExploreConfig StarConfig | §7 | `/admin/explore/star-configs*` | Ops · **ECR-035** | +| [ops-rhythm-config.md](ops-rhythm-config.md) | ExploreConfig RhythmConfig | §7 | `/admin/explore/rhythm-configs*` | Ops · **ECR-036** | +| [ops-image-card-deck.md](ops-image-card-deck.md) | ExploreConfig ImageCardDeck | §7 | `/admin/explore/image-card-decks*` | Ops · **ECR-037** | +| [ops-report-template.md](ops-report-template.md) | GrowthInsights ReportTemplate | §7 | `/admin/growth/report-templates*` | Ops · **ECR-038** | +| [ops-funnel-definition.md](ops-funnel-definition.md) | GrowthInsights FunnelDefinition | §7 | `/admin/analytics/funnel-definitions*` | Ops · **ECR-039** | +| [ops-scale-definition.md](ops-scale-definition.md) | ExploreConfig ScaleDefinition | §7 | `/admin/explore/scales*` | Ops · **ECR-040** | 新功能:复制 `_TEMPLATE.md` → 填满 → 在本表登记 → 再编码。 diff --git a/.ai/product/feature-spec/ops-account-lifecycle.md b/.ai/product/feature-spec/ops-account-lifecycle.md new file mode 100644 index 0000000..5e4c692 --- /dev/null +++ b/.ai/product/feature-spec/ops-account-lifecycle.md @@ -0,0 +1,156 @@ +# Feature Spec: 账户生命周期 / UserStatus(Ops · ECR-013B) + +> Status: `Active`(**Closed** · Feature Complete)· Map: `§7 运营后台` · Phase: `Ops-D` +> Parent: **WAVE0-FROZEN** (`27f27a1`) · Predecessor: **ECR-013A Closed** +> ESS: `docs/ECR/ECR-013B-account-lifecycle.md`(**Closed**) +> Capability: `AccountLifecycle` · BC: `Identity_Profile` +> 模板:`docs/WAVE0/contracts/OPS-CONTRACT-TEMPLATE.md` + +--- + +## ESS 门禁 + +1. Change Level = **L2** → 已 Approved + Coding auth + Implemented + **Closed** +2. ~~Contract Definition only~~ — 已完成 +3. 实现证据:`docs/TEST_REPORT/ECR-013B.md` · `docs/CODE_REVIEW/ECR-013B.md` + +--- + +## L0 Capability + +| 字段 | 内容 | +|------|------| +| Capability ID | `AccountLifecycle` | +| Purpose | 以可审计状态机管理 C 端用户账户启停与封禁,阻断违规会话 | +| Why now | `users.status` 字段已存在但无运营迁移与强制拒绝;013A 权限面就绪后可挂状态写权限 | +| Non-goals | soft-delete / `deleted`;UGC 社区封禁;真支付;Admin 账号启停(已有 admin.status);推送通知 | + +--- + +## L1 Bounded Context + +| 字段 | 内容 | +|------|------| +| Primary BC | `Identity_Profile` | +| owns | `User` · `UserStatus` · `AccountStateTransition` | +| does_not_own | `AdminRole` · `Membership` · `Payment` · `BanRecord`(Account_Risk 后置加深) | +| allowed | `Admin_Auth_Audit.write_audit`(经 admin 调用) | +| forbidden | `Payment` · `Membership.write` · soft-delete User | + +权威:`.ai/domain/boundary-rules.md` · `Account_Risk` 仅允许经明确服务触发 `UserStatus.transition`。 + +--- + +## 1. 功能定义 + +| 字段 | 内容 | +|------|------| +| Name | Account Lifecycle / UserStatus | +| Purpose | 运营可迁移用户状态;非 active 会话在 C 端被拒绝 | +| Business Goal | 风险处置最小闭环,不引入社区/UGC | + +| In | Out | +|---|---| +| 状态机 `active/disabled/banned/suspended` | soft-delete / GDPR 擦除 | +| Admin 迁移 API + AuditLog + Transition 记录 | 站内推送 / 短信 | +| DeviceAuth / Bearer 对非 active 拒绝 | AdminAccount.status(013A 外) | +| 权限码 `admin.users.status.write` | 行级数据 ACL | + +--- + +## L2 Domain + +| Entity | 不变式 / 状态机 | +|--------|----------------| +| `UserStatus` | 取值冻结:`active` · `disabled` · `banned` · `suspended`;非法值拒写 | +| `User.status` | 与 `UserStatus` 同值;默认 `active`(已有列) | +| `AccountStateTransition` | 只追加;记录 from→to · admin_id · reason · created_at | + +### 合法迁移 + +```text +active → disabled | banned | suspended +disabled → active | banned +suspended → active | banned | disabled +banned → active | disabled +``` + +其它边 → **400**。同状态写 → **400**(幂等拒绝,避免空审计噪音)。 + +### C 端效应 + +| Status | DeviceAuth / 已登录 Bearer | +|--------|------------------------------| +| `active` | 放行 | +| `disabled` / `banned` / `suspended` | **401**(或 403 统一码,实现轮定一)+ 不可发新 session | + +--- + +## L3 API Contract(意图 · 实现轮同步 OpenAPI) + +前缀:`/api/v1/admin` · AdminAuth · 信封 `{code,message,data}` + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| POST | `/users/:id/status` | `admin.users.status.write` | 迁移 UserStatus;写 Transition + AuditLog | +| GET | `/users/:id/status-transitions` | `admin.users.read` | 最近迁移列表(limit) | + +既有 `GET /users` · `GET /users/:id` 已暴露 `status` — 保持;实现轮确认枚举文档化。 + +`contract_diff`:`docs/CONTRACT_DIFF/ECR-013B.yaml` + +RBAC catalog **additive**:`admin.users.status.write` → 种子写入 `super_admin`(migration)。 + +--- + +## L4 Acceptance Criteria + +### Functional + +| ID | Given | When | Then | +|----|-------|------|------| +| AC-F-01 | 用户 `active` | POST status=`banned` + reason | 200;GET user.status=`banned` | +| AC-F-02 | 用户 `banned` | POST status=`active` | 200;可再次 DeviceAuth | +| AC-F-03 | 非法边(如 `active`→`active`) | POST | **400** | +| AC-F-04 | GET status-transitions | — | 含最近 from/to/admin/reason | + +### Security + +| ID | Given | When | Then | +|----|-------|------|------| +| AC-S-01 | Admin 无 `admin.users.status.write` | POST status | **403** + deny audit | +| AC-S-02 | 用户 `banned` | C 端带原 Bearer 访问受保护 API | **401/403** | +| AC-S-03 | 仅 DeviceAuth 无 Admin | POST `/admin/users/:id/status` | **401** | +| AC-S-04 | 无 Admin session | GET transitions | **401** | + +### Performance + +| ID | Given | When | Then | +|----|-------|------|------| +| AC-P-01 | transitions ≤1000 行/用户 | GET transitions limit=50 | 本机 P95 **< 500ms** | + +### Observability + +| ID | Given | When | Then | +|----|-------|------|------| +| AC-O-01 | 迁移成功 | — | AuditLog action=`users.status.transition` | +| AC-O-02 | 迁移成功 | — | `account_state_transitions` 有对应行 | + +--- + +## Forbidden(本切片) + +- soft-delete / `deleted` 状态 +- 真支付 · UGC · Prompt · 兑换码 · Crisis 深化 +- 改 Admin RBAC 模型(仅 **additive** 一枚 permission) +- 自动开下一 ECR + +--- + +## Implementation Notes(实现轮才执行) + +| 项 | 内容 | +|----|------| +| Migration | YES(transitions 表;permission 种子;必要时 CHECK/注释枚举) | +| Packages | service account/lifecycle · DeviceAuth/session 拒绝 · admin handler · admin-h5 用户详情最小 CTA | +| Depends | ECR-013A Closed(permission middleware) | diff --git a/.ai/product/feature-spec/ops-admin.md b/.ai/product/feature-spec/ops-admin.md index 60059ba..ccff6ca 100644 --- a/.ai/product/feature-spec/ops-admin.md +++ b/.ai/product/feature-spec/ops-admin.md @@ -208,7 +208,19 @@ Phase A 可先 `console`/本地;不挡验收。 | Phase | 内容 | |---|---| | **A(本 ECR)** | 登录 · 用户 · 订单 · 授予会员 · 审计 · admin-h5 壳 | -| **B(ECR-007)** | 自有埋点 · 管理端「数据」看板 — Spec `ops-analytics.md` | -| **C(ECR-008)** | 首页宫格 CRUD · 测评上下架 — Spec `ops-content.md`(Approved) | +| **B(ECR-007 Closed)** | 自有埋点 · 管理端「数据」看板 — Spec `ops-analytics.md` | +| **C(ECR-008 Closed)** | 首页宫格 CRUD · 测评上下架 — Spec `ops-content.md` | +| **E(ECR-013A Closed)** | Admin RBAC — Spec `ops-rbac.md` · Parent WAVE0-FROZEN | +| **F(ECR-013B Closed)** | AccountLifecycle — Spec `ops-account-lifecycle.md` | +| **G(ECR-014 Closed)** | MembershipPlan — Spec `ops-membership-plan.md` | +| **H(ECR-015 Closed)** | RedemptionCode — Spec `ops-redemption-code.md` | +| **I(ECR-016 Closed)** | UserIntelligence — Spec `ops-user-intelligence.md` | +| **J(ECR-017 Closed)** | AskOperations — Spec `ops-ask-operations.md` | +| **K(ECR-018 Closed)** | Entitlement — Spec `ops-entitlement.md` | +| **L(ECR-019 Closed)** | ContentSafety FilterRule — Spec `ops-content-safety.md` | +| **M(ECR-020 Closed)** | QualityFeedback — Spec `ops-quality-feedback.md` | +| **N(ECR-021 Closed)** | AICoreConfig SystemPrompt — Spec `ops-ai-core-config.md` | +| **O(ECR-022 Closed)** | CrisisCare CrisisPolicy — Spec `ops-crisis-care.md` | +| 后置 | Knowledge·Tools 写面 / CrisisEvent / 真支付(Loop 续跑) | | D | 订单筛选 · 展示价 · 退款只读(另开 ECR) | -| E | RBAC · 封禁 · 推送占位(另开 ECR) | +| 后置 | 封禁加深(Account_Risk)· 推送占位 | diff --git a/.ai/product/feature-spec/ops-ai-core-config.md b/.ai/product/feature-spec/ops-ai-core-config.md new file mode 100644 index 0000000..773cc93 --- /dev/null +++ b/.ai/product/feature-spec/ops-ai-core-config.md @@ -0,0 +1,41 @@ +# Feature Spec: AICoreConfig · SystemPrompt(Ops · ECR-021) + +> Status: `Active`(Loop continuous · **ECR-021 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-020 Closed +> Capability: `AICoreConfig` · BC: `Ask_Ops` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +Prompt 在线编辑/发布 · KnowledgeSource/Chunk · ToolDefinition · 切换运行时引擎 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `SystemPrompt` | code 唯一;body 为模板文本;active/system;本切片只读 | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/ai/system-prompts` | `admin.ai_config.read` | 列表 | +| GET | `/admin/ai/system-prompts/:id` | 同上 | 详情(含 body) | + +## Migration + +`000022`:`system_prompts` + 种子 `ask_default` + 授予权限 + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含 ask_default | +| AC-F-02 | get 返回 body 非空 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无 ai_config.read → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-021.yaml` diff --git a/.ai/product/feature-spec/ops-analytics.md b/.ai/product/feature-spec/ops-analytics.md index 513a915..13ce154 100644 --- a/.ai/product/feature-spec/ops-analytics.md +++ b/.ai/product/feature-spec/ops-analytics.md @@ -1,7 +1,7 @@ # Feature Spec: 运营行为分析(Ops-B) > Status: `Active` · Map: `§7 运营后台` · Phase: `Ops-B` -> ESS:`docs/ECR/ECR-007-ops-analytics.md`(Approved · L2)· BD-2026-007 Approved +> ESS:`docs/ECR/ECR-007-ops-analytics.md`(**Closed** · L2)· BD-2026-007 Approved > 关联:[ops-admin.md](ops-admin.md) · [analytics.md](analytics.md) · [cece-frontend-re/complete-design/40-ops-commerce.md](cece-frontend-re/complete-design/40-ops-commerce.md) --- diff --git a/.ai/product/feature-spec/ops-ask-operations.md b/.ai/product/feature-spec/ops-ask-operations.md new file mode 100644 index 0000000..d8841a0 --- /dev/null +++ b/.ai/product/feature-spec/ops-ask-operations.md @@ -0,0 +1,41 @@ +# Feature Spec: AskOperations(Ops · ECR-017) + +> Status: `Active`(Loop continuous · **ECR-017 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-016 Closed +> Capability: `AskOperations` · BC: `Ask_Ops` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +改写/删除消息正文 · SystemPrompt/Knowledge 配置 · QualityFeedback 写回 · AICoreConfig · 真支付 · UGC + +## L2 读模型 + +| 概念 | 语义 | +|------|------| +| `AskSessionView` | 线程元数据 + message_count(列表) | +| 会话详情 | 只读消息序列(role/content/created_at) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/ask/threads` | `admin.ask.read` | 列表(可选 `user_id`) | +| GET | `/admin/ask/threads/:id` | `admin.ask.read` | 详情含消息 | + +## Migration + +`000019`:为 `super_admin` 授予 `admin.ask.read`(无新业务表) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | 有消息线程 → list 含该 thread · message_count≥1 | +| AC-F-02 | detail 返回 user+assistant 消息 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无 ask.read → 403 | +| AC-P-01 | list < 500ms 本机 | +| AC-O-01 | N/A 只读(无写审计强制) | + +contract_diff: `docs/CONTRACT_DIFF/ECR-017.yaml` diff --git a/.ai/product/feature-spec/ops-banner.md b/.ai/product/feature-spec/ops-banner.md new file mode 100644 index 0000000..d03e306 --- /dev/null +++ b/.ai/product/feature-spec/ops-banner.md @@ -0,0 +1,41 @@ +# Feature Spec: OpsCMS · Banner(Ops · ECR-024) + +> Status: `Active`(Loop continuous · **ECR-024 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-023 Closed +> Capability: `OpsCMS` · BC: `Ops_CMS_NoUGC` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +Banner 写发布 · FeedSlot · ScheduledPublication · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `Banner` | code 唯一;placement ∈ {home,explore,ask};active/system;本切片只读 | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/cms/banners` | `admin.cms.read` | 只读 | +| GET | `/admin/cms/banners/{id}` | `admin.cms.read` | 只读 | + +## Migration + +`000025`:表 + 种子(若有) + 授予 admin.cms.read + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-024.yaml` diff --git a/.ai/product/feature-spec/ops-block-policy.md b/.ai/product/feature-spec/ops-block-policy.md new file mode 100644 index 0000000..e2cb4aa --- /dev/null +++ b/.ai/product/feature-spec/ops-block-policy.md @@ -0,0 +1,41 @@ +# Feature Spec: ContentSafety · BlockPolicy(Ops · ECR-029) + +> Status: `Active`(Loop continuous · **ECR-029 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-028 Closed +> Capability: `ContentSafety` · BC: `Content_Safety` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +策略写发布 · 用户侧硬拦截上线 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `BlockPolicy` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/content-safety/block-policies` | `admin.content_safety.read` | 只读 | +| GET | `/admin/content-safety/block-policies/{id}` | `admin.content_safety.read` | 只读 | + +## Migration + +`000030`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-029.yaml` diff --git a/.ai/product/feature-spec/ops-content-safety.md b/.ai/product/feature-spec/ops-content-safety.md new file mode 100644 index 0000000..fd376c0 --- /dev/null +++ b/.ai/product/feature-spec/ops-content-safety.md @@ -0,0 +1,42 @@ +# Feature Spec: ContentSafety · FilterRule(Ops · ECR-019) + +> Status: `Active`(Loop continuous · **ECR-019 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-018 Closed +> Capability: `ContentSafety` · BC: `Content_Safety` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +ModerationCase 写回 · CrisisPolicy 配置 · 真 NLP/厂商审核 · UGC · 真支付 · 用户侧硬拦截上线(本切片仅运营只读 + 试匹配) + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `FilterRule` | code 唯一;category ∈ {crisis,abuse,spam,pii};action ∈ {flag,block,escalate};system 种子不可本切片删除 | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/content-safety/filter-rules` | `admin.content_safety.read` | 列表 | +| GET | `/admin/content-safety/filter-rules/:id` | 同上 | 详情 | +| POST | `/admin/content-safety/evaluate` | 同上 | 试匹配(不写工单) | + +## Migration + +`000020`:`filter_rules` + 种子规则 + 授予 `admin.content_safety.read` + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含 system 种子 ≥1 | +| AC-F-02 | evaluate 命中种子 pattern → matches 非空 | +| AC-F-03 | get 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无 content_safety.read → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | evaluate 不写 Audit(只读试匹配) | + +contract_diff: `docs/CONTRACT_DIFF/ECR-019.yaml` diff --git a/.ai/product/feature-spec/ops-content.md b/.ai/product/feature-spec/ops-content.md index 3e4e4da..0558671 100644 --- a/.ai/product/feature-spec/ops-content.md +++ b/.ai/product/feature-spec/ops-content.md @@ -1,7 +1,7 @@ # Feature Spec: 运营内容配置(Ops-C) > Status: `Active` · Map: `§7 运营后台` · Phase: `Ops-C` -> ESS:`docs/ECR/ECR-008-ops-content.md`(Approved · L2)· BD-2026-008 Approved +> ESS:`docs/ECR/ECR-008-ops-content.md`(**Closed** · L2)· BD-2026-008 Approved > 关联:[ops-admin.md](ops-admin.md) · [ops-analytics.md](ops-analytics.md) · [home.md](home.md) · [explore-test.md](explore-test.md) --- diff --git a/.ai/product/feature-spec/ops-crisis-care.md b/.ai/product/feature-spec/ops-crisis-care.md new file mode 100644 index 0000000..0ba759f --- /dev/null +++ b/.ai/product/feature-spec/ops-crisis-care.md @@ -0,0 +1,42 @@ +# Feature Spec: CrisisCare · CrisisPolicy(Ops · ECR-022) + +> Status: `Active`(Loop continuous · **ECR-022 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-021 Closed +> Capability: `CrisisCare` · BC: `Content_Safety` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +CrisisEvent 入库工单 · InterventionOutcome · 策略在线编辑 · 医疗诊断结论 · UGC · 真支付 · 用户侧硬熔断上线 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `CrisisPolicy` | code 唯一;severity ∈ {high,critical};action ∈ {escalate,block,show_helpline};helpline_text 可选;本切片只读 | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/crisis/policies` | `admin.crisis.read` | 列表 | +| GET | `/admin/crisis/policies/:id` | 同上 | 详情 | +| POST | `/admin/crisis/evaluate` | 同上 | 试匹配(不写 CrisisEvent) | + +## Migration + +`000023`:`crisis_policies` + 种子 + 授予权限 + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含 system 种子 ≥1 | +| AC-F-02 | evaluate 命中 pattern → matches 非空 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无 crisis.read → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | evaluate 不写 Audit/Event | + +contract_diff: `docs/CONTRACT_DIFF/ECR-022.yaml` diff --git a/.ai/product/feature-spec/ops-crisis-event.md b/.ai/product/feature-spec/ops-crisis-event.md new file mode 100644 index 0000000..173b2df --- /dev/null +++ b/.ai/product/feature-spec/ops-crisis-event.md @@ -0,0 +1,41 @@ +# Feature Spec: CrisisCare · CrisisEvent(Ops · ECR-031) + +> Status: `Active`(Loop continuous · **ECR-031 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-030 Closed +> Capability: `CrisisCare` · BC: `Content_Safety` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +事件写入工单流 · 医疗诊断 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `CrisisEvent` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/crisis/events` | `admin.crisis.read` | 只读 | +| GET | `/admin/crisis/events/{id}` | `admin.crisis.read` | 只读 | + +## Migration + +`000032`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-031.yaml` diff --git a/.ai/product/feature-spec/ops-entitlement.md b/.ai/product/feature-spec/ops-entitlement.md new file mode 100644 index 0000000..1940e5f --- /dev/null +++ b/.ai/product/feature-spec/ops-entitlement.md @@ -0,0 +1,36 @@ +# Feature Spec: Entitlement(Ops · ECR-018) + +> Status: `Active`(Loop continuous · **ECR-018 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-017 Closed +> Capability: `CommerceEntitlement` · BC: `Membership_Orders` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +真支付 · 改订单/退款 · 新建权益矩阵表 · UGC · 改报告正文 · ask_pack SKU + +## L2 读模型(无 migration) + +| 概念 | 来源 | +|------|------| +| `Entitlement` | Membership + DeepAccess 列表 + ask_paid_quota 聚合 | +| flags | `report_detail_via_membership` · `deep_access_count` | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/users/:id/entitlements` | `admin.users.read` | 用户权益只读视图 | + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | 授予会员后 flags.report_detail_via_membership=true | +| AC-F-02 | 有 deep_access 时 deep_accesses 非空或 count≥1 | +| AC-F-03 | 无会员无深度 → membership.active=false 且 count=0 仍 200 | +| AC-S-01 | 无 Admin → 401 | +| AC-P-01 | GET < 500ms 本机 | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-018.yaml` diff --git a/.ai/product/feature-spec/ops-feed-slot.md b/.ai/product/feature-spec/ops-feed-slot.md new file mode 100644 index 0000000..1a061f6 --- /dev/null +++ b/.ai/product/feature-spec/ops-feed-slot.md @@ -0,0 +1,41 @@ +# Feature Spec: OpsCMS · FeedSlot(Ops · ECR-025) + +> Status: `Active`(Loop continuous · **ECR-025 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-024 Closed +> Capability: `OpsCMS` · BC: `Ops_CMS_NoUGC` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +FeedSlot 写发布 · ScheduledPublication · UGC · 真支付 · Banner 写 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `FeedSlot` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/cms/feed-slots` | `admin.cms.read` | 只读 | +| GET | `/admin/cms/feed-slots/{id}` | `admin.cms.read` | 只读 | + +## Migration + +`000026`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-025.yaml` diff --git a/.ai/product/feature-spec/ops-funnel-definition.md b/.ai/product/feature-spec/ops-funnel-definition.md new file mode 100644 index 0000000..5ffe301 --- /dev/null +++ b/.ai/product/feature-spec/ops-funnel-definition.md @@ -0,0 +1,41 @@ +# Feature Spec: GrowthInsights · FunnelDefinition(Ops · ECR-039) + +> Status: `Active`(Loop continuous · **ECR-039 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-038 Closed +> Capability: `GrowthInsights` · BC: `Analytics_OpsB` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +漏斗写配置 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `FunnelDefinition` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/analytics/funnel-definitions` | `admin.analytics.read` | 只读 | +| GET | `/admin/analytics/funnel-definitions/{id}` | `admin.analytics.read` | 只读 | + +## Migration + +`000040`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-039.yaml` diff --git a/.ai/product/feature-spec/ops-handoff-case.md b/.ai/product/feature-spec/ops-handoff-case.md new file mode 100644 index 0000000..d479ae2 --- /dev/null +++ b/.ai/product/feature-spec/ops-handoff-case.md @@ -0,0 +1,41 @@ +# Feature Spec: AskOperations · HandoffCase(Ops · ECR-033) + +> Status: `Active`(Loop continuous · **ECR-033 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-032 Closed +> Capability: `AskOperations` · BC: `Ask_Ops` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +转人工写流 · 顾问执业 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `HandoffCase` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/ask/handoffs` | `admin.ask.read` | 只读 | +| GET | `/admin/ask/handoffs/{id}` | `admin.ask.read` | 只读 | + +## Migration + +`000034`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-033.yaml` diff --git a/.ai/product/feature-spec/ops-image-card-deck.md b/.ai/product/feature-spec/ops-image-card-deck.md new file mode 100644 index 0000000..70b897a --- /dev/null +++ b/.ai/product/feature-spec/ops-image-card-deck.md @@ -0,0 +1,41 @@ +# Feature Spec: ExploreConfig · ImageCardDeck(Ops · ECR-037) + +> Status: `Active`(Loop continuous · **ECR-037 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-036 Closed +> Capability: `ExploreConfig` · BC: `Explore_Reports` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +牌组写发布 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `ImageCardDeck` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/explore/image-card-decks` | `admin.explore.read` | 只读 | +| GET | `/admin/explore/image-card-decks/{id}` | `admin.explore.read` | 只读 | + +## Migration + +`000038`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-037.yaml` diff --git a/.ai/product/feature-spec/ops-intervention-outcome.md b/.ai/product/feature-spec/ops-intervention-outcome.md new file mode 100644 index 0000000..98c3b75 --- /dev/null +++ b/.ai/product/feature-spec/ops-intervention-outcome.md @@ -0,0 +1,41 @@ +# Feature Spec: CrisisCare · InterventionOutcome(Ops · ECR-032) + +> Status: `Active`(Loop continuous · **ECR-032 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-031 Closed +> Capability: `CrisisCare` · BC: `Content_Safety` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +干预写回 · 医疗诊断 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `InterventionOutcome` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/crisis/interventions` | `admin.crisis.read` | 只读 | +| GET | `/admin/crisis/interventions/{id}` | `admin.crisis.read` | 只读 | + +## Migration + +`000033`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-032.yaml` diff --git a/.ai/product/feature-spec/ops-knowledge-chunk.md b/.ai/product/feature-spec/ops-knowledge-chunk.md new file mode 100644 index 0000000..16f3521 --- /dev/null +++ b/.ai/product/feature-spec/ops-knowledge-chunk.md @@ -0,0 +1,41 @@ +# Feature Spec: AICoreConfig · KnowledgeChunk(Ops · ECR-027) + +> Status: `Active`(Loop continuous · **ECR-027 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-026 Closed +> Capability: `AICoreConfig` · BC: `Ask_Ops` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +Embedding · 上传切块 · 运行时 RAG 接线 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `KnowledgeChunk` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/ai/knowledge-chunks` | `admin.ai_config.read` | 只读 | +| GET | `/admin/ai/knowledge-chunks/{id}` | `admin.ai_config.read` | 只读 | + +## Migration + +`000028`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-027.yaml` diff --git a/.ai/product/feature-spec/ops-knowledge-source.md b/.ai/product/feature-spec/ops-knowledge-source.md new file mode 100644 index 0000000..c0adb74 --- /dev/null +++ b/.ai/product/feature-spec/ops-knowledge-source.md @@ -0,0 +1,41 @@ +# Feature Spec: AICoreConfig · KnowledgeSource(Ops · ECR-023) + +> Status: `Active`(Loop continuous · **ECR-023 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-022 Closed +> Capability: `AICoreConfig` · BC: `Ask_Ops` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +KnowledgeChunk / Embedding · 源文件上传 · 在线编辑发布 · ToolDefinition · 运行时 RAG 接线 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `KnowledgeSource` | code 唯一;source_kind ∈ {faq,policy,guide};active/system;本切片只读目录,不含 Chunk | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/ai/knowledge-sources` | `admin.ai_config.read` | 列表 | +| GET | `/admin/ai/knowledge-sources/:id` | 同上 | 详情 | + +## Migration + +`000024`:`knowledge_sources` + 种子 `ask_grounding`(权限已由 000022 授予) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含 ask_grounding | +| AC-F-02 | get 返回 source_kind 合法 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无 ai_config.read → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-023.yaml` diff --git a/.ai/product/feature-spec/ops-membership-plan.md b/.ai/product/feature-spec/ops-membership-plan.md new file mode 100644 index 0000000..9183618 --- /dev/null +++ b/.ai/product/feature-spec/ops-membership-plan.md @@ -0,0 +1,77 @@ +# Feature Spec: 会员套餐 MembershipPlan(Ops · ECR-014) + +> Status: `Active`(Loop continuous · Approved · coding)· Map: `§7` · Phase: `Ops-D` +> Parent: **WAVE0-FROZEN** · Predecessor: **ECR-013B Closed** +> ESS: `docs/ECR/ECR-014-membership-plan.md` +> Capability: `CommerceEntitlement` · BC: `Membership_Orders` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +--- + +## ESS 门禁 + +1. L2 · Loop continuous:契约齐 → 自动 Approve + coding +2. **不含** 真支付 · 兑换码 · Entitlement 矩阵 · ask_pack 价目表 + +--- + +## L0 Capability + +| 字段 | 内容 | +|------|------| +| Capability ID | `CommerceEntitlement` | +| Purpose | 运营可配置成长会员套餐时长与标价(mock 履约仍用既有支付) | +| Why now | planDays/金额硬编码;013B 完成后进入 Commerce 配置面最小切片 | +| Non-goals | 真支付网关 · RedemptionCode · Entitlement 细权 · ask_pack | + +--- + +## L1 Bounded Context + +| Primary BC | `Membership_Orders` | +| owns | `MembershipPlan` | +| does_not_own | `UserStatus` · `Payment` 适配器 · `AdminRole` | +| allowed | `Admin_Auth_Audit.write_audit` | +| forbidden | 真支付 · UGC | + +--- + +## L2 Domain + +| Entity | 不变式 | +|--------|--------| +| `MembershipPlan` | `code` ∈ {month,quarter,year} 本切片冻结;`duration_days`>0;`amount_cents`≥0;`active` 布尔 | + +--- + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/membership-plans` | `admin.membership.plans.read` | 列表 | +| GET | `/admin/membership-plans/:code` | `admin.membership.plans.read` | 详情 | +| PUT | `/admin/membership-plans/:code` | `admin.membership.plans.write` | 更新 title/days/amount/active + AuditLog | + +履约:`GrantMembership` / membership `CreateOrder` 读表(缺行回退旧硬编码)。 + +`contract_diff`:`docs/CONTRACT_DIFF/ECR-014.yaml` + +--- + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | GET plans 含 month/quarter/year | +| AC-F-02 | PUT month days/amount → GET 一致 | +| AC-F-03 | GrantMembership 使用表内 duration_days | +| AC-S-01 | 无 write 权限 PUT → 403 | +| AC-S-02 | 无 Admin → 401 | +| AC-P-01 | GET list P95 < 500ms 本机 | +| AC-O-01 | PUT 成功 → AuditLog `membership.plans.update` | + +--- + +## Implementation Notes + +Migration `membership_plans` + RBAC additive permissions · admin-h5 最小列表编辑页 diff --git a/.ai/product/feature-spec/ops-moderation-case.md b/.ai/product/feature-spec/ops-moderation-case.md new file mode 100644 index 0000000..bade415 --- /dev/null +++ b/.ai/product/feature-spec/ops-moderation-case.md @@ -0,0 +1,41 @@ +# Feature Spec: ContentSafety · ModerationCase(Ops · ECR-030) + +> Status: `Active`(Loop continuous · **ECR-030 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-029 Closed +> Capability: `ContentSafety` · BC: `Content_Safety` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +审核写回 · 真 NLP 厂商 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `ModerationCase` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/content-safety/cases` | `admin.content_safety.read` | 只读 | +| GET | `/admin/content-safety/cases/{id}` | `admin.content_safety.read` | 只读 | + +## Migration + +`000031`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-030.yaml` diff --git a/.ai/product/feature-spec/ops-privacy-request.md b/.ai/product/feature-spec/ops-privacy-request.md new file mode 100644 index 0000000..5634935 --- /dev/null +++ b/.ai/product/feature-spec/ops-privacy-request.md @@ -0,0 +1,41 @@ +# Feature Spec: AdminGovernance · PrivacyRequest(Ops · ECR-034) + +> Status: `Active`(Loop continuous · **ECR-034 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-033 Closed +> Capability: `AdminGovernance` · BC: `Admin_Auth_Audit` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +隐私请求履约写 · soft-delete · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `PrivacyRequest` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/privacy/requests` | `admin.privacy.read` | 只读 | +| GET | `/admin/privacy/requests/{id}` | `admin.privacy.read` | 只读 | + +## Migration + +`000035`:表 + 种子(若有) + 授予 admin.privacy.read + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-034.yaml` diff --git a/.ai/product/feature-spec/ops-quality-feedback.md b/.ai/product/feature-spec/ops-quality-feedback.md new file mode 100644 index 0000000..ab2b192 --- /dev/null +++ b/.ai/product/feature-spec/ops-quality-feedback.md @@ -0,0 +1,42 @@ +# Feature Spec: QualityFeedback(Ops · ECR-020) + +> Status: `Active`(Loop continuous · **ECR-020 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-019 Closed +> Capability: `AskOperations` · BC: `Ask_Ops` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +改写消息正文 · SystemPrompt/Knowledge · HandoffCase 全量 · AICoreConfig · UGC 广场 · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `QualityFeedback` | 绑定 thread(可选 message);rating 1–5;tag ∈ {helpful,off_topic,unsafe,other};source ∈ {admin,user} | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/ask/feedback` | `admin.ask.read` | 列表 | +| POST | `/admin/ask/threads/:id/feedback` | `admin.ask.feedback.write` | 运营提交 | +| POST | `/ask/threads/:id/feedback` | DeviceAuth 用户 | C 端评分 | + +## Migration + +`000021`:`ask_quality_feedback` + 授予 `admin.ask.feedback.write` + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | admin POST feedback → 200 · list 可见 | +| AC-F-02 | user POST feedback → 200 | +| AC-F-03 | 无效 rating → 400 | +| AC-S-01 | 无 Admin → admin list 401 | +| AC-S-02 | 无 feedback.write → admin POST 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | admin 提交写 AuditLog `ask.feedback.create` | + +contract_diff: `docs/CONTRACT_DIFF/ECR-020.yaml` diff --git a/.ai/product/feature-spec/ops-rbac.md b/.ai/product/feature-spec/ops-rbac.md new file mode 100644 index 0000000..649a3c7 --- /dev/null +++ b/.ai/product/feature-spec/ops-rbac.md @@ -0,0 +1,151 @@ +# Feature Spec: 运营后台 RBAC(Ops · ECR-013A) + +> Status: `Active`(**Closed** · Feature Complete)· Map: `§7 运营后台` · Phase: `Ops-D` +> Parent: **WAVE0-FROZEN** (`27f27a1`) +> ESS: `docs/ECR/ECR-013A-admin-rbac.md`(**Closed**) +> Capability: `AdminGovernance` · BC: `Admin_RBAC` +> 模板:`docs/WAVE0/contracts/OPS-CONTRACT-TEMPLATE.md` + +--- + +## ESS 门禁 + +1. Change Level = **L2** → 已 Approved + Coding auth + Implemented + **Closed** +2. ~~Contract Definition only~~ — 已完成 +3. 实现证据:`docs/TEST_REPORT/ECR-013A.md` · `docs/CODE_REVIEW/ECR-013A.md` + +--- + +## L0 Capability + +| 字段 | 内容 | +|------|------| +| Capability ID | `AdminGovernance` | +| Purpose | 以最小权限原则约束管理员可执行的运营动作 | +| Why now | Ops-A 全员等价权限;进入 M1+ 前必须有权限面,否则越权不可治理 | +| Non-goals | 用户账户启停(ECR-013B);兑换码;CMS;Ask Prompt;真支付;UGC | + +--- + +## L1 Bounded Context + +| 字段 | 内容 | +|------|------| +| Primary BC | `Admin_RBAC` | +| owns | `AdminRole` · `AdminPermission` | +| does_not_own | `User` · `Profile` · `UserStatus` · `Membership` · `Payment` | +| allowed | `Admin_Auth_Audit.write_audit` | +| forbidden | `Payment` · `Membership.write` · `DeviceAuth.user_token_issue` | + +权威:`.ai/domain/boundary-rules.md` + +--- + +## 1. 功能定义 + +| 字段 | 内容 | +|------|------| +| Name | Admin RBAC | +| Purpose | 角色与权限点绑定;API 级强制鉴权;变更可审计 | +| Business Goal | 支撑多运营角色,而不扩大 C 端攻击面 | + +| In | Out | +|---|---| +| Role CRUD(最小:list/get/update permissions) | UserStatus / 封禁(013B) | +| `GET /admin/me` 返回 permissions | 细粒度数据行级 ACL | +| 既有 admin 写操作挂 permission 检查 | 新业务模块权限爆炸式新增(另 ECR) | + +--- + +## L2 Domain + +| Entity | 不变式 | +|--------|--------| +| `AdminRole` | `name` 唯一;系统角色 `super_admin` 不可删除 | +| `AdminPermission` | 稳定字符串码(见下表);只增不改语义 | +| `AdminAccount.role_id` | 每个账号恰好一个角色(本切片);无角色视为拒绝写操作 | + +### Permission catalog(本切片冻结) + +| Code | 覆盖既有能力 | +|------|----------------| +| `admin.users.read` | GET users / users/:id / stats | +| `admin.users.membership.grant` | POST membership/grant | +| `admin.users.ask_quota.grant` | POST ask-quota/grant | +| `admin.orders.read` | GET orders | +| `admin.audit.read` | GET audit-logs | +| `admin.analytics.read` | GET analytics/*(Ops-B) | +| `admin.content.write` | home tools / scales publish(Ops-C) | +| `admin.roles.read` | GET roles | +| `admin.roles.write` | 变更角色权限 | + +`super_admin` 种子拥有上表全部。 + +--- + +## L3 API Contract(意图 · 实现轮同步 OpenAPI) + +前缀:`/api/v1/admin` · AdminAuth · 信封 `{code,message,data}` + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/roles` | `admin.roles.read` | 角色列表 | +| GET | `/roles/:id` | `admin.roles.read` | 角色 + permissions[] | +| PUT | `/roles/:id/permissions` | `admin.roles.write` | 全量替换权限集;写 AuditLog | +| GET | `/me` | (已登录) | **扩展**返回 `role` + `permissions[]` | + +既有写接口:在实现轮挂上表 permission;缺权 → **403** + AuditLog(尝试记录)。 + +`contract_diff`:`docs/CONTRACT_DIFF/ECR-013A.yaml` + +--- + +## L4 Acceptance Criteria + +### Functional + +| ID | Given | When | Then | +|----|-------|------|------| +| AC-F-01 | super_admin 已登录 | GET `/roles` | 200,含 `super_admin` | +| AC-F-02 | 角色存在 | PUT permissions 合法集合 | 200;再次 GET 一致 | +| AC-F-03 | GET `/me` | — | data 含 `permissions` 数组 | + +### Security + +| ID | Given | When | Then | +|----|-------|------|------| +| AC-S-01 | Admin 无 `admin.roles.write` | PUT `/roles/:id/permissions` | **403**;AuditLog 有拒绝或尝试记录 | +| AC-S-02 | Admin 无 `admin.users.membership.grant` | POST membership/grant | **403** | +| AC-S-03 | 仅 DeviceAuth | 访问 `/admin/roles` | **401** | +| AC-S-04 | 删除 `super_admin` 角色 | 任意 API | **拒绝**(4xx) | + +### Performance + +| ID | Given | When | Then | +|----|-------|------|------| +| AC-P-01 | 角色数 N≤100 | GET `/roles` | 本机 integration 环境下 P95 **< 500ms** | + +### Observability + +| ID | Given | When | Then | +|----|-------|------|------| +| AC-O-01 | PUT permissions 成功 | — | AuditLog action 含 `roles.permissions.update` | +| AC-O-02 | AC-S-01 触发 | — | AuditLog 可追溯 admin_id + path | + +--- + +## Forbidden(本切片) + +- UserStatus / AccountLifecycle(**ECR-013B**) +- 真支付 · UGC · Prompt/RAG · 兑换码 · CMS Banner 新表 +- 改 DeviceAuth / C 端五 Tab + +--- + +## Implementation Notes(实现轮才执行) + +| 项 | 内容 | +|----|------| +| Migration | YES(roles / permissions 关联;accounts.role_id;种子 super_admin) | +| Packages | `service/admin` · repo · middleware permission check · admin-h5 只读展示权限(最小) | +| Parent | WAVE0-FROZEN | diff --git a/.ai/product/feature-spec/ops-redemption-code.md b/.ai/product/feature-spec/ops-redemption-code.md new file mode 100644 index 0000000..be5f44d --- /dev/null +++ b/.ai/product/feature-spec/ops-redemption-code.md @@ -0,0 +1,43 @@ +# Feature Spec: 兑换码 RedemptionCode(Ops · ECR-015) + +> Status: `Active`(Loop continuous · Approved · coding) +> Parent: WAVE0-FROZEN · Predecessor: ECR-014 Closed +> Capability: `CommerceEntitlement` · BC: `Membership_Orders` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +真支付 · Entitlement 细权 · ask_pack 兑换 · UGC + +## L2 + +| Entity | 不变式 | +|--------|--------| +| `RedemptionBatch` | label · plan_code∈membership_plans · quantity 1..100 | +| `RedemptionCode` | code 唯一;status unused→redeemed\|disabled;redeemed 不可再兑 | + +## L3 API + +| Method | Path | Auth | 语义 | +|--------|------|------|------| +| POST | `/admin/redemption-batches` | `admin.membership.codes.write` | 批量生成 | +| GET | `/admin/redemption-batches` | `admin.membership.codes.read` | 批次列表 | +| GET | `/admin/redemption-batches/:id/codes` | `admin.membership.codes.read` | 码列表 | +| POST | `/admin/redemption-codes/:id/disable` | `admin.membership.codes.write` | 作废 unused | +| POST | `/membership/redeem` | DeviceAuth+已注册 | 兑码→延长会员 | + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | POST batch quantity=3 → 3 unused codes | +| AC-F-02 | C端 redeem → membership active;码=redeemed | +| AC-F-03 | 再兑同一码 → 400 | +| AC-F-04 | disable unused → status=disabled;兑 → 400 | +| AC-S-01 | 无 write → POST batch 403 | +| AC-S-02 | 未登录兑码 → 401 | +| AC-P-01 | GET batches < 500ms | +| AC-O-01 | 生成 AuditLog `redemption.batch.create` | +| AC-O-02 | 兑换可追溯 redeemed_by | + +contract_diff: `docs/CONTRACT_DIFF/ECR-015.yaml` diff --git a/.ai/product/feature-spec/ops-report-template.md b/.ai/product/feature-spec/ops-report-template.md new file mode 100644 index 0000000..cb0eb86 --- /dev/null +++ b/.ai/product/feature-spec/ops-report-template.md @@ -0,0 +1,41 @@ +# Feature Spec: GrowthInsights · ReportTemplate(Ops · ECR-038) + +> Status: `Active`(Loop continuous · **ECR-038 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-037 Closed +> Capability: `GrowthInsights` · BC: `Explore_Reports` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +模板写发布 · 广告投放 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `ReportTemplate` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/growth/report-templates` | `admin.growth.read` | 只读 | +| GET | `/admin/growth/report-templates/{id}` | `admin.growth.read` | 只读 | + +## Migration + +`000039`:表 + 种子(若有) + 授予 admin.growth.read + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-038.yaml` diff --git a/.ai/product/feature-spec/ops-rhythm-config.md b/.ai/product/feature-spec/ops-rhythm-config.md new file mode 100644 index 0000000..5b700c0 --- /dev/null +++ b/.ai/product/feature-spec/ops-rhythm-config.md @@ -0,0 +1,41 @@ +# Feature Spec: ExploreConfig · RhythmConfig(Ops · ECR-036) + +> Status: `Active`(Loop continuous · **ECR-036 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-035 Closed +> Capability: `ExploreConfig` · BC: `Explore_Reports` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +配置写发布 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `RhythmConfig` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/explore/rhythm-configs` | `admin.explore.read` | 只读 | +| GET | `/admin/explore/rhythm-configs/{id}` | `admin.explore.read` | 只读 | + +## Migration + +`000037`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-036.yaml` diff --git a/.ai/product/feature-spec/ops-scale-definition.md b/.ai/product/feature-spec/ops-scale-definition.md new file mode 100644 index 0000000..d053372 --- /dev/null +++ b/.ai/product/feature-spec/ops-scale-definition.md @@ -0,0 +1,41 @@ +# Feature Spec: ExploreConfig · ScaleDefinition(Ops · ECR-040) + +> Status: `Active`(Loop continuous · **ECR-040 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-039 Closed +> Capability: `ExploreConfig` · BC: `Explore_Reports` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +PATCH status(已有 content.write)· 题干编辑 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `ScaleDefinition` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/explore/scales` | `admin.explore.read` | 只读 | +| GET | `/admin/explore/scales/{id}` | `admin.explore.read` | 只读 | + +## Migration + +Migration NO:复用 `scales` 表只读投影 + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-040.yaml` diff --git a/.ai/product/feature-spec/ops-scheduled-publication.md b/.ai/product/feature-spec/ops-scheduled-publication.md new file mode 100644 index 0000000..508b1bd --- /dev/null +++ b/.ai/product/feature-spec/ops-scheduled-publication.md @@ -0,0 +1,41 @@ +# Feature Spec: OpsCMS · ScheduledPublication(Ops · ECR-026) + +> Status: `Active`(Loop continuous · **ECR-026 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-025 Closed +> Capability: `OpsCMS` · BC: `Ops_CMS_NoUGC` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +定时发布写操作 · UGC · 真支付 · Banner/FeedSlot 写 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `ScheduledPublication` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/cms/publications` | `admin.cms.read` | 只读 | +| GET | `/admin/cms/publications/{id}` | `admin.cms.read` | 只读 | + +## Migration + +`000027`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-026.yaml` diff --git a/.ai/product/feature-spec/ops-star-config.md b/.ai/product/feature-spec/ops-star-config.md new file mode 100644 index 0000000..6badb5a --- /dev/null +++ b/.ai/product/feature-spec/ops-star-config.md @@ -0,0 +1,41 @@ +# Feature Spec: ExploreConfig · StarConfig(Ops · ECR-035) + +> Status: `Active`(Loop continuous · **ECR-035 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-034 Closed +> Capability: `ExploreConfig` · BC: `Explore_Reports` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +配置写发布 · 引擎改分层 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `StarConfig` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/explore/star-configs` | `admin.explore.read` | 只读 | +| GET | `/admin/explore/star-configs/{id}` | `admin.explore.read` | 只读 | + +## Migration + +`000036`:表 + 种子(若有) + 授予 admin.explore.read + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-035.yaml` diff --git a/.ai/product/feature-spec/ops-tool-definition.md b/.ai/product/feature-spec/ops-tool-definition.md new file mode 100644 index 0000000..bdabaea --- /dev/null +++ b/.ai/product/feature-spec/ops-tool-definition.md @@ -0,0 +1,41 @@ +# Feature Spec: AICoreConfig · ToolDefinition(Ops · ECR-028) + +> Status: `Active`(Loop continuous · **ECR-028 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-027 Closed +> Capability: `AICoreConfig` · BC: `Ask_Ops` +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +工具在线编辑 · 运行时绑定 · UGC · 真支付 + +## L2 Domain + +| 概念 | 语义 | +|------|------| +| `ToolDefinition` | 本切片只读目录;code 唯一(若适用) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/ai/tools` | `admin.ai_config.read` | 只读 | +| GET | `/admin/ai/tools/{id}` | `admin.ai_config.read` | 只读 | + +## Migration + +`000029`:表 + 种子(若有)(权限复用) + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | list 含种子或空列表合法 | +| AC-F-02 | 已知 id get 200 | +| AC-F-03 | 未知 id → 404 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无权限 → 403 | +| AC-P-01 | list < 500ms | +| AC-O-01 | N/A 只读 | + +contract_diff: `docs/CONTRACT_DIFF/ECR-028.yaml` diff --git a/.ai/product/feature-spec/ops-user-intelligence.md b/.ai/product/feature-spec/ops-user-intelligence.md new file mode 100644 index 0000000..bbea799 --- /dev/null +++ b/.ai/product/feature-spec/ops-user-intelligence.md @@ -0,0 +1,39 @@ +# Feature Spec: 用户洞察 UserIntelligence(Ops · ECR-016) + +> Status: `Active`(Loop continuous · **ECR-016 Closed**) +> Parent: WAVE0-FROZEN · Predecessor: ECR-015 Closed +> Capability: `UserIntelligence` · BC: `Identity_Profile`(读)+ `Analytics_OpsB`(读) +> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md` + +## Non-goals + +写标签 · 改报告正文 · Ask 全文运营编辑 · UGC · 真支付 · 新表(本切片纯读模型聚合) + +## L2 读模型(无 migration) + +| 概念 | 来源 | +|------|------| +| `UserProfileView` | profiles 摘要(已有 detail) | +| `InsightReport` | growth_reports 类型计数 + 最近列表 | +| `PsychologicalTagSet` | 由报告 type 派生的稳定标签(非 NLP) | +| `BehaviorSnapshot` | analytics_events 最近页面/事件(若有) | + +## L3 API + +| Method | Path | 权限 | 语义 | +|--------|------|------|------| +| GET | `/admin/users/:id/insight` | `admin.users.read` | 聚合洞察只读 | + +## L4 AC + +| ID | Then | +|----|------| +| AC-F-01 | 有报告用户 → insight.reports_by_type 非空或 reports 列表 | +| AC-F-02 | tags 含报告类型映射标签 | +| AC-F-03 | 无埋点时 behavior.events=[] 仍 200 | +| AC-S-01 | 无 Admin → 401 | +| AC-S-02 | 无 users.read → 403 | +| AC-P-01 | GET insight < 500ms 本机 | +| AC-O-01 | N/A 只读(无写审计) | + +contract_diff: `docs/CONTRACT_DIFF/ECR-016.yaml` diff --git a/.ai/product/feature-spec/star-profile.md b/.ai/product/feature-spec/star-profile.md index 898dca6..bd5fae1 100644 --- a/.ai/product/feature-spec/star-profile.md +++ b/.ai/product/feature-spec/star-profile.md @@ -169,7 +169,7 @@ **Given** Self 有生日 **When** 生成星座报告 -**Then** 展示轮盘、太阳/月亮/上升、相位速览、fortune 日运与行运;无「占卜」「算命」恐吓 +**Then** 展示轮盘、太阳/月亮/上升、相位速览、outlook 日/周/月/年/一生与行运;无「占卜」「算命」恐吓 **Given** 两份档案 **When** POST synastry @@ -214,3 +214,5 @@ | Packages | `internal/star` · `ephemeris` · `natal` · `fortune` · `synastry` | | 星历 | Swiss Ephemeris(CGO,默认 Moshier);热带 + Whole Sign | | 实现 | `POST /reports/star` · `POST /reports/synastry` · nearby/invites · H5 `/star` · `/synastry` · invite | +| Gaps | ECR-012 收口:报告页复用 StarFortunePanel;分享 type=star;fortuneBundle 仅读 outlook 且剥离 lucky;e2e mock 含 outlook | +| ESS | `docs/ECR/ECR-012-star-alignment.md`(**Approved** · L2)· BD-2026-012 Approved | diff --git a/apps/admin-h5/e2e/admin-ops-smoke.spec.ts b/apps/admin-h5/e2e/admin-ops-smoke.spec.ts index cd86159..55cfc68 100644 --- a/apps/admin-h5/e2e/admin-ops-smoke.spec.ts +++ b/apps/admin-h5/e2e/admin-ops-smoke.spec.ts @@ -21,7 +21,22 @@ test('admin login users grant and audit with mocked API', async ({ page }) => { await ok({ token: 'adm_e2e_token', expires_at: new Date(Date.now() + 3600_000).toISOString(), - admin: { id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', username: 'admin' }, + admin: { + id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', + username: 'admin', + role: 'super_admin', + permissions: [ + 'admin.users.read', + 'admin.users.membership.grant', + 'admin.users.ask_quota.grant', + 'admin.orders.read', + 'admin.audit.read', + 'admin.analytics.read', + 'admin.content.write', + 'admin.roles.read', + 'admin.roles.write', + ], + }, }) return } @@ -55,7 +70,22 @@ test('admin login users grant and audit with mocked API', async ({ page }) => { return } if (url.endsWith('/me') || url.includes('/me?')) { - await ok({ id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', username: 'admin' }) + await ok({ + id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', + username: 'admin', + role: 'super_admin', + permissions: [ + 'admin.users.read', + 'admin.users.membership.grant', + 'admin.users.ask_quota.grant', + 'admin.orders.read', + 'admin.audit.read', + 'admin.analytics.read', + 'admin.content.write', + 'admin.roles.read', + 'admin.roles.write', + ], + }) return } if (url.includes(`/users/${userId}/ask-quota/grant`) && method === 'POST') { diff --git a/apps/admin-h5/src/api/client.ts b/apps/admin-h5/src/api/client.ts index 873afbf..9ce106e 100644 --- a/apps/admin-h5/src/api/client.ts +++ b/apps/admin-h5/src/api/client.ts @@ -31,7 +31,17 @@ async function request(method: string, path: string, body?: unknown): Promise headers, body: body === undefined ? undefined : JSON.stringify(body), }) - const env = (await res.json()) as ApiEnvelope + const text = await res.text() + let env: ApiEnvelope + try { + env = JSON.parse(text) as ApiEnvelope + } catch { + throw new Error( + res.status === 404 + ? `接口不存在或后端未更新(${path})` + : `响应不是 JSON(HTTP ${res.status})`, + ) + } if (!res.ok || env.code !== 0) { throw new Error(env.message || `HTTP ${res.status}`) } @@ -96,7 +106,12 @@ export type UserDetail = { export type AdminRole = 'super' | 'ops' -export type AdminMe = { id: string; username: string; role: AdminRole } +export type AdminMe = { + id: string + username: string + role?: string + permissions?: string[] +} export type PushJob = { id: string @@ -125,12 +140,63 @@ export const adminApi = { }), logout: () => request<{ ok: boolean }>('POST', '/auth/logout'), me: () => request('GET', '/me'), + roles: () => + request<{ items: Array<{ id: string; name: string; system: boolean }> }>('GET', '/roles'), + role: (id: string) => + request<{ id: string; name: string; system: boolean; permissions: string[] }>('GET', `/roles/${id}`), stats: () => request('GET', '/stats'), users: (q = '') => request<{ items: UserListItem[] }>('GET', `/users?q=${encodeURIComponent(q)}`), user: (id: string) => request('GET', `/users/${id}`), banUser: (id: string) => request<{ ok: boolean }>('POST', `/users/${id}/ban`), unbanUser: (id: string) => request<{ ok: boolean }>('POST', `/users/${id}/unban`), + setUserStatus: (id: string, status: string, reason: string) => + request('POST', `/users/${id}/status`, { status, reason }), + statusTransitions: (id: string) => + request<{ + items: Array<{ + id: string + from_status: string + to_status: string + admin_id: string + reason: string + created_at: string + }> + }>('GET', `/users/${id}/status-transitions`), + userInsight: (id: string) => + request<{ + user_id: string + profiles_count: number + reports_by_type: Array<{ type: string; count: number }> + recent_reports: Array<{ id: string; type: string; created_at: string }> + tags: Array<{ code: string; label: string }> + behavior: { + events: Array<{ name: string; page_path?: string; received_at: string }> + ask_thread_count: number + } + }>('GET', `/users/${id}/insight`), + userEntitlements: (id: string) => + request<{ + user_id: string + membership: { + plan?: string + status: string + expires_at?: string + ask_quota_left?: number + active: boolean + } + ask_paid_quota_left: number + flags: { + report_detail_via_membership: boolean + deep_access_count: number + } + deep_accesses: Array<{ + id: string + report_id: string + report_type?: string + created_at: string + }> + }>('GET', `/users/${id}/entitlements`), grant: (id: string, plan: string) => request<{ ok: boolean }>('POST', `/users/${id}/membership/grant`, { plan }), grantAskQuota: (id: string, delta: number) => @@ -145,6 +211,323 @@ export const adminApi = { request('POST', '/push-jobs', body), patchPushJob: (id: string, body: { title?: string; body?: string; status?: 'draft' | 'cancelled' }) => request('PATCH', `/push-jobs/${id}`, body), + membershipPlans: () => + request<{ + items: Array<{ + code: string + title: string + duration_days: number + amount_cents: number + active: boolean + updated_at: string + }> + }>('GET', '/membership-plans'), + updateMembershipPlan: ( + code: string, + body: { title: string; duration_days: number; amount_cents: number; active: boolean }, + ) => + request<{ + code: string + title: string + duration_days: number + amount_cents: number + active: boolean + }>('PUT', `/membership-plans/${code}`, body), + createRedemptionBatch: (body: { label: string; plan_code: string; quantity: number }) => + request<{ + batch: { id: string; label: string; plan_code: string; quantity: number } + codes: Array<{ id: string; code: string; status: string }> + }>('POST', '/redemption-batches', body), + redemptionBatches: () => + request<{ items: Array<{ id: string; label: string; plan_code: string; quantity: number; created_at: string }> }>( + 'GET', + '/redemption-batches', + ), + redemptionCodes: (batchId: string) => + request<{ items: Array<{ id: string; code: string; status: string; plan_code: string }> }>( + 'GET', + `/redemption-batches/${batchId}/codes`, + ), + disableRedemptionCode: (id: string) => request<{ ok: boolean }>('POST', `/redemption-codes/${id}/disable`), + askThreads: (userId = '') => { + const q = userId ? `?user_id=${encodeURIComponent(userId)}` : '' + return request<{ + items: Array<{ + id: string + user_id: string + profile_id: string + scene?: string + message_count: number + created_at: string + updated_at: string + }> + }>('GET', `/ask/threads${q}`) + }, + askThread: (id: string) => + request<{ + id: string + user_id: string + profile_id: string + scene?: string + message_count: number + created_at: string + updated_at: string + messages: Array<{ id: string; role: string; content: string; created_at: string }> + }>('GET', `/ask/threads/${id}`), + filterRules: () => + request<{ + items: Array<{ + id: string + code: string + title: string + category: string + pattern: string + action: string + active: boolean + system: boolean + updated_at: string + }> + }>('GET', '/content-safety/filter-rules'), + filterRule: (id: string) => + request<{ + id: string + code: string + title: string + category: string + pattern: string + action: string + active: boolean + system: boolean + updated_at: string + }>('GET', `/content-safety/filter-rules/${id}`), + evaluateContent: (text: string) => + request<{ matches: Array<{ code: string; title: string; category: string; action: string }> }>( + 'POST', + '/content-safety/evaluate', + { text }, + ), + askFeedback: () => + request<{ + items: Array<{ + id: string + thread_id: string + message_id?: string + source: string + rating: number + tag?: string + note?: string + created_at: string + }> + }>('GET', '/ask/feedback'), + createAskFeedback: (threadId: string, body: { rating: number; tag?: string; note?: string; message_id?: string }) => + request<{ + id: string + thread_id: string + rating: number + source: string + }>('POST', `/ask/threads/${threadId}/feedback`, body), + systemPrompts: () => + request<{ + items: Array<{ + id: string + code: string + title: string + scene?: string + body: string + version: number + active: boolean + system: boolean + updated_at: string + }> + }>('GET', '/ai/system-prompts'), + systemPrompt: (id: string) => + request<{ + id: string + code: string + title: string + scene?: string + body: string + version: number + active: boolean + system: boolean + updated_at: string + }>('GET', `/ai/system-prompts/${id}`), + knowledgeSources: () => + request<{ + items: Array<{ + id: string + code: string + title: string + description?: string + source_kind: string + version: number + active: boolean + system: boolean + updated_at: string + }> + }>('GET', '/ai/knowledge-sources'), + knowledgeSource: (id: string) => + request<{ + id: string + code: string + title: string + description?: string + source_kind: string + version: number + active: boolean + system: boolean + updated_at: string + }>('GET', `/ai/knowledge-sources/${id}`), + crisisPolicies: () => + request<{ + items: Array<{ + id: string + code: string + title: string + severity: string + pattern: string + action: string + helpline_text?: string + active: boolean + system: boolean + updated_at: string + }> + }>('GET', '/crisis/policies'), + crisisPolicy: (id: string) => + request<{ + id: string + code: string + title: string + severity: string + pattern: string + action: string + helpline_text?: string + active: boolean + system: boolean + updated_at: string + }>('GET', `/crisis/policies/${id}`), + evaluateCrisis: (text: string) => + request<{ + matches: Array<{ + code: string + title: string + severity: string + action: string + helpline_text?: string + }> + }>('POST', '/crisis/evaluate', { text }), + banners: () => + request<{ + items: Array<{ + id: string + code: string + title: string + placement: string + image_url?: string + link_path?: string + sort_order: number + active: boolean + system: boolean + updated_at: string + }> + }>('GET', '/cms/banners'), + banner: (id: string) => + request<{ + id: string + code: string + title: string + placement: string + image_url?: string + link_path?: string + sort_order: number + active: boolean + system: boolean + updated_at: string + }>('GET', `/cms/banners/${id}`), + feedSlots: () => + request<{ + items: Array<{ + id: string + code: string + title: string + slot_key: string + placement: string + active: boolean + system: boolean + updated_at: string + }> + }>('GET', '/cms/feed-slots'), + feedSlot: (id: string) => + request<{ + id: string + code: string + title: string + slot_key: string + placement: string + active: boolean + system: boolean + updated_at: string + }>('GET', `/cms/feed-slots/${id}`), + publications: () => + request<{ items: Array> }>('GET', '/cms/publications'), + publication: (id: string) => + request>('GET', `/cms/publications/${id}`), + knowledgeChunks: () => + request<{ items: Array> }>('GET', '/ai/knowledge-chunks'), + knowledgeChunk: (id: string) => + request>('GET', `/ai/knowledge-chunks/${id}`), + tools: () => + request<{ items: Array> }>('GET', '/ai/tools'), + tool: (id: string) => + request>('GET', `/ai/tools/${id}`), + blockPolicies: () => + request<{ items: Array> }>('GET', '/content-safety/block-policies'), + blockPolicie: (id: string) => + request>('GET', `/content-safety/block-policies/${id}`), + cases: () => + request<{ items: Array> }>('GET', '/content-safety/cases'), + case: (id: string) => + request>('GET', `/content-safety/cases/${id}`), + events: () => + request<{ items: Array> }>('GET', '/crisis/events'), + event: (id: string) => + request>('GET', `/crisis/events/${id}`), + interventions: () => + request<{ items: Array> }>('GET', '/crisis/interventions'), + intervention: (id: string) => + request>('GET', `/crisis/interventions/${id}`), + handoffs: () => + request<{ items: Array> }>('GET', '/ask/handoffs'), + handoff: (id: string) => + request>('GET', `/ask/handoffs/${id}`), + requests: () => + request<{ items: Array> }>('GET', '/privacy/requests'), + privacyRequest: (id: string) => + request>('GET', `/privacy/requests/${id}`), + starConfigs: () => + request<{ items: Array> }>('GET', '/explore/star-configs'), + starConfig: (id: string) => + request>('GET', `/explore/star-configs/${id}`), + rhythmConfigs: () => + request<{ items: Array> }>('GET', '/explore/rhythm-configs'), + rhythmConfig: (id: string) => + request>('GET', `/explore/rhythm-configs/${id}`), + imageCardDecks: () => + request<{ items: Array> }>('GET', '/explore/image-card-decks'), + imageCardDeck: (id: string) => + request>('GET', `/explore/image-card-decks/${id}`), + reportTemplates: () => + request<{ items: Array> }>('GET', '/growth/report-templates'), + reportTemplate: (id: string) => + request>('GET', `/growth/report-templates/${id}`), + funnelDefinitions: () => + request<{ items: Array> }>('GET', '/analytics/funnel-definitions'), + funnelDefinition: (id: string) => + request>('GET', `/analytics/funnel-definitions/${id}`), + exploreScales: () => + request<{ items: Array> }>('GET', '/explore/scales'), + exploreScale: (id: string) => + request>('GET', `/explore/scales/${id}`), orders: (params?: { status?: string kind?: string diff --git a/apps/admin-h5/src/components/UserEntitlementPanel.vue b/apps/admin-h5/src/components/UserEntitlementPanel.vue new file mode 100644 index 0000000..51dc716 --- /dev/null +++ b/apps/admin-h5/src/components/UserEntitlementPanel.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/apps/admin-h5/src/layouts/AdminShell.vue b/apps/admin-h5/src/layouts/AdminShell.vue index c25a3d1..904ef71 100644 --- a/apps/admin-h5/src/layouts/AdminShell.vue +++ b/apps/admin-h5/src/layouts/AdminShell.vue @@ -28,6 +28,14 @@ async function onLogout() { 数据 内容 用户 + 套餐 + 兑换码 + 问答 + 安全 + AI + 危机 + CMS + 目录仓 订单 定价 推送 @@ -35,7 +43,8 @@ async function onLogout() { 审计
- {{ auth.username || '管理员' }} · {{ auth.role }} + {{ auth.username || '管理员' }} + {{ auth.role }} · {{ auth.permissions.length }} 权
@@ -75,6 +84,7 @@ nav a.router-link-active { box-shadow: 0 4px 12px rgba(229, 77, 66, 0.08); } .foot { margin-top: auto; display: flex; flex-direction: column; gap: 0.5rem; } +.role { font-size: 0.75rem; color: var(--muted); } .main { padding: 1.5rem 1.75rem; } @media (max-width: 800px) { .shell { grid-template-columns: 1fr; } diff --git a/apps/admin-h5/src/pages/AIConfigPage.vue b/apps/admin-h5/src/pages/AIConfigPage.vue new file mode 100644 index 0000000..8974696 --- /dev/null +++ b/apps/admin-h5/src/pages/AIConfigPage.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/apps/admin-h5/src/pages/AskPage.vue b/apps/admin-h5/src/pages/AskPage.vue new file mode 100644 index 0000000..333d58c --- /dev/null +++ b/apps/admin-h5/src/pages/AskPage.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/apps/admin-h5/src/pages/CMSPage.vue b/apps/admin-h5/src/pages/CMSPage.vue new file mode 100644 index 0000000..de8a86e --- /dev/null +++ b/apps/admin-h5/src/pages/CMSPage.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/apps/admin-h5/src/pages/CatalogHubPage.vue b/apps/admin-h5/src/pages/CatalogHubPage.vue new file mode 100644 index 0000000..a8ff7b3 --- /dev/null +++ b/apps/admin-h5/src/pages/CatalogHubPage.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/apps/admin-h5/src/pages/CrisisPage.vue b/apps/admin-h5/src/pages/CrisisPage.vue new file mode 100644 index 0000000..d5d47d1 --- /dev/null +++ b/apps/admin-h5/src/pages/CrisisPage.vue @@ -0,0 +1,103 @@ + + +