✨ NetYamlForge 自举演进中枢 · 多智能体闭环架构

🤖 Agent Studio 演进工作台

专为 NetYamlForge 演进任务设计的高效查阅与指挥中枢。驱动 Claude(架构师)、Antigravity(开发者)与 OpenCode(测试者)三阶段闭环推演与物理落盘。

📋
全部演进任务
8
⚡
进行中 / 推演中
2
✅
已成功交付
6
📜
阶段执行流水
26
流水线交付达成率 75%
协同智能体阵列: 🤖 NetYamlForge Kernel: 4 次 🤖 antigravity: 3 次 🤖 claude: 4 次 🤖 opencode: 15 次

+ 新建演进任务 (Fast Task Launchpad)

Stage 1: 架构师
Stage 2: 开发者
Stage 3: 测试者

🚀 演进任务全景看板 8 项

点击下方各阶段按钮即可直接检视架构设计 (RFC)、开发交付产物与测试报告

#1

实现 API 访问频率限制 (Rate Limiting)

🕒 创建于: 2026-09-20 06:02:23
✅ 已交付 (Completed)
需求规格:为 NetYamlForge API 与认证端点实现基于 IP 与用户的滑动窗口频率限制中间件,防止暴力破解与 DoS 攻击。
协同流水线编排进度 三阶段闭环体系
🏛️
Stage 1: 架构设计 (RFC)
Role: Architect · opencode
✔ 已完成
💻
Stage 2: 开发实现规范
Role: Developer · opencode
✔ 已完成
🧪
Stage 3: 测试验收结论
Role: Tester · opencode
✔ 已完成
底层详情
#2

创建轻量级待办应用 (Todo Application)

🕒 创建于: 2026-09-24 21:17:10
✅ 已交付 (Completed)
需求规格:基于 NetYamlForge 引擎构建轻量级 Todo 待办事项管理模块,包含任务项实体(Title、Description、IsCompleted、DueDate、Priority)、YAML 声明式页面/菜单配置及自定义状态切换 Hook。
协同流水线编排进度 三阶段闭环体系
🏛️
Stage 1: 架构设计 (RFC)
Role: Architect · opencode
✔ 已完成
💻
Stage 2: 开发实现规范
Role: Developer · opencode
✔ 已完成
🧪
Stage 3: 测试验收结论
Role: Tester · opencode
✔ 已完成
🌐 体验 待办工作台
底层详情
#3

待办应用业务主页定制与管理后台解耦 (HomePage & Standard CRUD Decoupling)

🕒 创建于: 2026-09-25 00:53:40
✅ 已交付 (Completed)
需求规格:为 Todo 应用引入「独立业务主页 + 通用 CRUD 管理后台」解耦模式:1. 启用 pages 特性,创建 pages/HomePage.yaml,使用 stat_cards 与 table 组件构建高层业务工作台;2. 配置应用根路由及各角色登录入口 (landingPageByRole) 默认重定向至 /todo-app/Page/HomePage;3. 保留 /todo-app/DynamicEntity/Index?entity=todo 为专属数据运维后台,并在侧边栏导航中对业务前台与运维管理进行清晰分区与角色隔离。
协同流水线编排进度 三阶段闭环体系
🏛️
Stage 1: 架构设计 (RFC)
Role: Architect · opencode
✔ 已完成
💻
Stage 2: 开发实现规范
Role: Developer · opencode
✔ 已完成
🧪
Stage 3: 测试验收结论
Role: Tester · opencode
✔ 已完成
🌐 体验 待办工作台
底层详情
#4

创建独立博客与内容发布应用 (Blog Application)

🕒 创建于: 2026-09-25 06:56:06
✅ 已交付 (Completed)
需求规格:基于 NetYamlForge 引擎构建独立博客应用 (blog-app),包含文章管理(blog_post)、分类管理(blog_category)数据模型与 YAML 声明式实体配置;采用前台业务门户与管理后台解耦模式,创建 pages/HomePage.yaml 博客主页(聚合精选文章、分类概览与阅读统计),配置应用根路由与角色默认跳转,并将标准 CRUD 归入专属运维管理分区;实现文章发布状态切换与浏览量维护等轻量级 C# 业务 Hook。流水线协同阶段分别由不同的 AI CLI(Claude、Antigravity、OpenCode)各司其职推演并物理落地上线。
协同流水线编排进度 三阶段闭环体系
🏛️
Stage 1: 架构设计 (RFC)
Role: Architect · claude
✔ 已完成
💻
Stage 2: 开发实现规范
Role: Developer · antigravity
✔ 已完成
🧪
Stage 3: 测试验收结论
Role: Tester · opencode
✔ 已完成
🌐 体验 博客主页
底层详情
#5

待办与博客体验升级:用户友好主页与文章一览及详情页 (Todo & Blog Portal Revamp)

🕒 创建于: 2026-09-25 16:20:00
✅ 已交付 (Completed)
需求规格:重构 Todo 与 Blog 应用主界面,提供现代、直观、用户友好的交互体验。Todo 工作台集成指标卡片、行内创建、状态切换及多维筛选;Blog 应用构建标准博客门户的一览主页(卡片流、分类标签、实时过滤)及独立文章详情页(PostDetail:正文排版、元数据横幅、相关阅读推荐)。架构师角色严格设定为 Claude。
协同流水线编排进度 三阶段闭环体系
🏛️
Stage 1: 架构设计 (RFC)
Role: Architect · claude
✔ 已完成
💻
Stage 2: 开发实现规范
Role: Developer · antigravity
✔ 已完成
🧪
Stage 3: 测试验收结论
Role: Tester · opencode
✔ 已完成
🌐 体验 待办工作台
底层详情
#6

待办与博客门户体验深度重构:用户友好工作台与标准博客系统 (Todo & Blog Portals Revamp with Claude as Architect)

🕒 创建于: 2026-09-25 16:25:00
✅ 已交付 (Completed)
需求规格:重塑 Todo 工作台与 Blog 独立应用的门户体验。Todo 工作台集成完成率进度条、优先级色彩胶囊、到期日预警及行内快捷添加;Blog 应用构建标准博客门户(头条焦点头条、分类实时过滤、热门侧边栏)与沉浸式单独文章阅读页(文章元数据横幅、Markdown 正则格式化解析、互动点赞/分享及相关文章推荐)。架构师指定并严格设定为 Claude。
协同流水线编排进度 三阶段闭环体系
🏛️
Stage 1: 架构设计 (RFC)
Role: Architect · claude
✔ 已完成
💻
Stage 2: 开发实现规范
Role: Developer · antigravity
✔ 已完成
🧪
Stage 3: 测试验收结论
Role: Tester · opencode
✔ 已完成
🌐 体验 待办工作台
底层详情
#7

创建企业级表单审批流应用 (Enterprise Approval Flow)

🕒 创建于: 2026-10-25 14:00:00
📝 草稿就绪 (Draft)
需求规格:支持请假、差旅报销与设备采购的多级审批流,包含状态流转与历史流转审计
协同流水线编排进度 三阶段闭环体系
🏛️
Stage 1: 架构设计 (RFC)
Role: Architect · claude
✔ 已完成
💻
Stage 2: 开发实现规范
Role: Developer · antigravity
✔ 已完成
🧪
Stage 3: 测试验收结论
Role: Tester · opencode
✔ 已完成
底层详情
#8

创建企业经营分析与决策看板应用 (Enterprise Analytics Dashboard)

🕒 创建于: 2026-10-25 14:20:00
📝 草稿就绪 (Draft)
需求规格:全景监控经营 KPI、商业营收、多维渠道贡献与基础设施稳定性
协同流水线编排进度 三阶段闭环体系
🏛️
Stage 1: 架构设计 (RFC)
Role: Architect · antigravity
✔ 已完成
💻
Stage 2: 开发实现规范
Role: Developer · opencode
✔ 已完成
🧪
Stage 3: 测试验收结论
Role: Tester · claude
✔ 已完成
底层详情

📜 实时多智能体协同流水记录 近 30 条

跨角色(Claude / Antigravity / OpenCode)的实时执行轨迹与状态验证

查阅全量日志表格
FAILED Architect Architect @ claude 关联任务: #1 实现 API 访问频率限制 (Rate Limiting)
2026-09-20 23:17:57
claude: 90秒でタイムアウト | opencode: 90秒でタイムアウト | antigravity: 90秒でタイムアウト | opencode: 90秒でタイムアウト | antigravity: 90秒でタイムアウト | claude: 90秒でタイムアウト
SUCCESS Architect Architect @ opencode 关联任务: #1 实现 API 访问频率限制 (Rate Limiting)
2026-09-21 03:51:46
Now let me read the existing rate limiting middleware and related files to understand the current implementation details:
SUCCESS Developer Developer @ opencode 关联任务: #1 实现 API 访问频率限制 (Rate Limiting)
2026-09-21 03:56:46
Let me read the key files in detail. Let me also check the project config model and the extensions for service registration: Now I have a full picture. Let me implement the rate limiting feature. The existing `DynamicRateLimitingMiddleware` only handles per-entity limits. I need to create: 1. **Global rate limiting** for API endpoints (`/api/*`) 2. **Brute force protection** for auth endpoints (`/Account/Login`, `/Account/Register`) 3. Configuration models and registration
SUCCESS Tester Tester @ opencode 关联任务: #1 实现 API 访问频率限制 (Rate Limiting)
2026-09-21 04:01:46
Now let me read the key implementation files in detail: Now let me read existing test patterns for middleware testing:
SUCCESS Architect Architect @ opencode 关联任务: #1 实现 API 访问频率限制 (Rate Limiting)
2026-09-24 13:12:33
## RFC: IP+User 滑动窗口 Rate Limiting(Global / Auth / Entity 三层) ### 1. 现状(已存在的基础) | 文件 | 现状 | |---|---| | `NetYamlForge/Services/Api/GlobalRateLimitingMiddleware.cs` | 已有,滑动窗口,IP/User 二选一分区,覆盖 `/api/*`、`/mcp`、AuthPaths | | `NetYamlForge/Services/Api/DynamicRateLimitingMiddleware.cs` | 已有,按 YAML entity `RateLimiting` 配置分区(IP/User/Tenant) | | `NetYamlForge/Services/Api/FailedLoginTracker.cs` | 已有,IP 与用户名双 key 爆破锁定,被 `AccountController`/`TenantAccountController` 调用 | | `Models/Config/RateLimitingOptions.cs` | `RateLimiting:{Enabled,Api,Auth,BruteForce}` | | `Models/Entity/SecurityDefinition.cs:89` | `RateLimitingDefinition`(YAML entity 级) | | `appsettings.json:69` | `RateLimiting` 节 | | `Extensions/ServiceCollectionExtensions.cs:478` | `AddApiServices` 已注册 options 与 `IFailedLoginTracker` | | `Program.cs:402-403` | 已接线两个中间件 | | `NetYamlForge.Tests/Security/RateLimitingAndBruteForceSecurityTests.cs` | 5 个测试覆盖 tracker/global/auth | 结论:**不是从零实现,而是修复覆盖缺口 + 强化分区语义 + 统一契约 + 防内存泄漏**。 ### 2. 缺口分析(本次任务必须解决) 1. **覆盖缺口(安全漏洞)**:`IsApiEndpoint` 只匹配 `/api/` 与 `/mcp`。下列端点全部逃逸全局限流: - `/chinook/api/qr/*`、`/{project}/api/vector-search`、`{project}/api/embedding-stats`、`photo-file`(`AhpQrController`、`VectorSearchController`、`PhotoFileController`) - `/{project}/entities/{entity}/...`(`DynamicEntityController`)仅靠 Dynamic 中间件,且部分 entity 未开 `rateLimiting` - `/api/hotreload`、`/api/connectionpool`(`Controllers/Api/`) 2. **反向代理下 IP 失真**:项目已自行处理 `X-Forwarded-Host/Prefix/Direct-Domain`(`Program.cs:290-318`)但**从不读 `X-Forwarded-For`**。Caddy 场景下 `RemoteIpAddress` 恒为代理 IP,导致全站共享一个 IP 桶(误杀)或限流失效。 3. **“基于 IP 与用户”未落地**:`LimitBy` 是单值;API 应同时按 IP+User 分区。且 `GlobalRateLimitingMiddleware` 在 `UseAuthorization` **之前**执行,ApiToken 认证用户在此刻 `httpContext.User` 尚未填充(handler 在授权阶段才跑),`LimitBy=User` 实际退化成了 `"Anonymous"`。 4. **DoS 语义缺陷**:API 规则 `QueueLimit=10` 会把请求挂起排队而非拒绝,DoS 下连接被占满。 5. **429 契约不一致**:Global 返回完整 `X-RateLimit-*` + `Retry-After`,Dynamic 硬编码 `retryAfter=60`,且响应 JSON 结构不同。 6. **静态 limiter 字典永不回收**:`GlobalRateLimitingMiddleware.cs:18` 与 `DynamicRateLimitingMiddleware.cs:16` 的静态 `ConcurrentDictionary` 每个新 IP 永久驻留 → 内存泄漏。 ### 3. 目标架构 中间件管线(保持现有顺序,仅新增/替换实现): ``` UseRouting → ProjectMiddleware → UseAuthentication → UseRequestLocalization → ProjectScopeMiddleware / TenantResolverMiddleware → GlobalRateLimitingMiddleware (前置 429 优先于 401/403,防探测) → DynamicRateLimitingMiddleware (entity 级,继续留在此处) → UseAuthorization ``` 复合 key 语义(任务核心:IP **AND** User): - 认证端点 Auth 桶:`IP|匿名 或 IP|UserName`(用户名取自表单 `UserName`,读 body 需 `Request.EnableBuffering` + `ReadFormAsync`,仅对 POST 做一次) - API 桶:API 请求若携带 token(`Authorization`/`X-Api-Token`/`X-Api-Key`/`?api_token=`)则预执行 `context.AuthenticateAsync("ApiToken")` 融合 principal,再取 `IP|UserName`;否则 `IP|anonymous` - Entity 桶(Dynamic):保持 `entity|limitBy|value`,但 value 统一走 `IClientIpResolver` ### 4. 配置契约(`appsettings.json`,向后兼容) ```jsonc "RateLimiting": { "Enabled": true, "ClientIpHeader": "X-Forwarded-For", // 新增:空则不信任头 "TrustedProxies": [], // 新增:仅信任这些网段填入的头 "CacheSweepSeconds": 600, // 新增:limiter 字典回收周期 "LimiterIdleTtlSeconds": 3600, // 新增:单 key 最长驻留 "Api": { "PermitLimit": 100, "WindowSeconds": 60, "QueueLimit": 0, "LimitBy": "IP,User" }, "Auth": { "PermitLimit": 10, "WindowSeconds": 60, "QueueLimit": 0, "LimitBy": "IP,User" }, "BruteForce": { "FailedAttemptThreshold": 5, "LockoutSeconds": 300, "UsernameFormKey": "UserName" } // 新增:中间件级用户名爆破检查 } ``` - `LimitBy` 扩展为逗号分隔列表,元素 ∈ {`IP`,`User`};空串默认 `IP`。`"IP,User"` = 复合分区。 - 默认 `QueueLimit` 改为 `0`(立即 429 拒绝,不排队)。 ### 5. 类型契约(文件路径 + 签名) **新增 `NetYamlForge/Services/Api/ClientIpResolver.cs`** ```csharp public interface IClientIpResolver { string GetClientIp(HttpContext context); } public class ClientIpResolver : IClientIpResolver { // 读 ClientIpHeader(默认 X-Forwarded-For),取最左一个非本机地址; // TrustedProxies 非空时跳过属于该网段的 hop;无头/不可信则回退 RemoteIpAddress。 // 相同 IP 的连续 key 在 process 内缓存(ConcurrentDictionary<string,string> 做(header,ip)快速路径)不一定需要。 } ``` **新增 `NetYamlForge/Services/Api/RateLimiterRegistry.cs`** ```csharp public interface IRateLimiterRegistry { RateLimiter GetOrCreate(string key, Func<RateLimiter> factory); long RegisteredCount { get; } } public class RateLimiterRegistry : IRateLimiterRegistry, IHostedService { // SemaphoreSlim 保护的 ConcurrentDictionary<string,(RateLimiter,LimiterAccess)>; // 每次 GetOrCreate/Acquire 刷新 LastSeen;后台每 CacheSweepSeconds 扫描, // 剔除 LastSeen 超过 LimiterIdleTtlSeconds 的 key(IDisposable.DisposeAsync())。 } ``` **新增 `NetYamlForge/Services/Api/StandardRateLimitResponses.cs`** ```csharp public interface IStandardRateLimitResponses { Task WriteAsync(HttpContext ctx, string group, int retryAfterSeconds, int? permitLimit = null, int? windowSeconds = null); // 统一输出: 429 + Retry-After + X-RateLimit-Limit/Remaining/Window + X-RateLimit-Group // + JSON { error, message="Rate limit exceeded. Please retry after {retryAfter}s.", // retryAfter, group } (舍 Dynamic 现在硬编码 60 与缺标头的问题) } ``` **新增 `NetYamlForge/Services/Api/RateLimitPartitioners.cs`** ```csharp public static class RateLimitPartitioners { // "IP,User" → "{group}|{ip}|{user}",元素按出现顺序拼接;重名时只取一次。 public static string BuildCompositeKey(string group, string limitByCsv, string ip, string? user); public static string ResolveUser(HttpContext ctx, string? formUsername); // 已认证 → ctx.User.Identity!.Name;否则 formUsername ?? "anonymous" } ``` **修改 `NetYamlForge/Services/Api/GlobalRateLimitingMiddleware.cs`** ```csharp public class GlobalRateLimitingMiddleware { // 依赖注入改为: IOptions<RateLimitingOptions>, IFailedLoginTracker, // IClientIpResolver, IRateLimiterRegistry, IStandardRateLimitResponses public async Task InvokeAsync(HttpContext httpContext) { /* 见步骤 S4 */ } // 私有: RegexApiPath() 新判定、PreAuthenticateApiTokenAsync()、IsAuthEndpoint() } ``` **修改 `NetYamlForge/Services/Api/DynamicRateLimitingMiddleware.cs`** ```csharp // 去掉静态字典,改用 IRateLimiterRegistry;Retry-After 取 rule.WindowSeconds; // 429 走 IStandardRateLimitResponses。 ``` **修改 `Models/Config/RateLimitingOptions.cs`** — `ClientIpHeader`、`TrustedProxies`(List<string>)、`CacheSweepSeconds`、`LimiterIdleTtlSeconds`、`BruteForceProtection.UsernameFormKey`,`RateLimitRule.LimitBy` 注释改为逗号分隔。 **修改 `Extensions/ServiceCollectionExtensions.cs:478 AddApiServices`** — 追加: ```csharp services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); // 若未注册 services.AddSingleton<IClientIpResolver, ClientIpResolver>(); services.AddSingleton<IRateLimiterRegistry, RateLimiterRegistry>(); services.AddHostedService(sp => (RateLimiterRegistry)sp.GetRequiredService<IRateLimiterRegistry>()); services.AddSingleton<IStandardRateLimitResponses, StandardRateLimitResponses>(); ``` ### 6. 分步落地指引(Developer 执行顺序) - **S1 模型扩充**:改 `RateLimitingOptions.cs` 与 `BruteForceProtection`(加 `UsernameFormKey`),更新 `appsettings.json` 对应字段(`QueueLimit:0`、`LimitBy:"IP,User"`)。 - **S2 新组件**:照 §5 新建 `ClientIpResolver.cs`、`RateLimiterRegistry.cs`、`StandardRateLimitResponses.cs`、`RateLimitPartitioners.cs`。 - **S3 DI**:在 `AddApiServices` 注册 S2 四个组件;HostedService 用 `services.AddHostedService` 由容器自动启动清扫。 - **S4 Global 中间件重构**: - `IsApiEndpoint` 改为:`/api/`、`/mcp`、含 `/api/` 段、含 `/entities/` 段、`/api/{project}/{entity}`; - 入口先 `PreAuthenticateApiTokenAsync`(存在任意 token 来源且未认证时 `AuthenticateAsync("ApiToken")`,成功后 `context.User = principal`); - Auth 分支:POST 时一次性读表单取 `UsernameFormKey`,`IsLockedOut(ip) || IsLockedOut(username)` 拦截; - 两个桶统一走 `IClientIpResolver.GetClientIp` + `RateLimitPartitioners.BuildCompositeKey("API", cfg.LimitBy, ip, user)`; - 拒绝路径统一调用 `IStandardRateLimitResponses`;`QueueLimit=0` 时移除排队取值逻辑。 - **S5 Dynamic 中间件**:换 `IRateLimiterRegistry`,`Retry-After = rule.WindowSeconds`,借 `IStandardRateLimitResponses`。 - **S6 测试**:在 `NetYamlForge.Tests/Security/RateLimitingAndBruteForceSecurityTests.cs` 追加: - `ClientIpResolver`:x-forwarded-for 三级取最左可信、空头回退 `RemoteIpAddress`、不可信网段丢弃; - Global 覆盖 `/{project}/api/...` 与 `/entities/...` 被限流; - `LimitBy="IP,User"` 复合桶:同 IP 不同用户互不耗尽; - `QueueLimit=0` 并发打压立即 429(不挂起); - Registry 清扫:插入假 key 手动触发 Sweep 后 `RegisteredCount` 归零; - 更新既有 2 个 Global 测试的构造参数(新增依赖注入项)。 - **S7 验证**: - `dotnet build /home/ubuntu/ws/NetYamlForge/NetYamlForge.slnx` - `dotnet test /home/ubuntu/ws/NetYamlForge/NetYamlForge.Tests`(xunit + Moq,已有 5 个相关用例须全绿) - 手工:`curl -s -o /dev/null -w '%{http_code}' -X POST` 连续打到 `/Account/Login` 超 10 次应得 `429` 且 `Retry-After` 出现;模拟 `X-Forwarded-For: 1.2.3.4` 验不同 IP 不互斥。 ### 7. 非目标 / 后续 - 分布式跨实例限流(Redis/Outbox)——`IRateLimiterRegistry` 的抽象已留替换点,本次内存实现。 - 对 GET 静态资源(wwwroot)限流——`UseStaticFiles` 先于中间件已短路,保持现状。 ### 8. 风险与回滚 - 表单 `ReadFormAsync` 会消费 body → 必须 `Request.EnableBuffering()` 而后 `Rewind()`,否则后续模型绑定读空表单。 - AuthPaths 含不存在的 `/TenantAccount/CustomerRegister`(实际端点是 `/Account/CustomerRegister`)——顺手可从列表剔除,避免误匹配。 - 全部改动向后兼容配置(旧 `LimitBy:"IP"` 仍有效),回滚只需还原 `Global/Dynamic` 中间件与 DI 注册。 需要 Developer 确认的关键决策点(无需现在回复,写入与 Developer 的交接备注): 1. `QueueLimit` 强制归零的破坏性变更(原值 10); 2. API 桶的 `PreAuthenticateApiTokenAsync` 会增加一次 handler 调用成本(仅对携带 token 的请求,可接受)。
SUCCESS Developer Developer @ opencode 关联任务: #1 实现 API 访问频率限制 (Rate Limiting)
2026-09-24 13:17:34
I'll start by exploring the current state of the relevant files to understand the existing implementation. Now let me look at the configuration, DI registration, Program.cs wiring, and existing tests. Now let me look at the existing tests and the SecurityDefinition model. Let me examine the API token authentication scheme, the controllers to cover, and the remaining Program.cs. Let me check how `AccountController` uses the tracker and check the roles/tenant resolver setup. I now have a full picture. Let me set up a todo list and start implementing. Starting with S1 — the model expansion: Now update `appsettings.json`: S2 — creating the new components. First, checking for existing `InternalsVisibleTo` conventions and test project setup: InternalsVisibleTo exists. Now let me create all four new components: S3 — DI registration in `AddApiServices`: Now S4 — refactoring the GlobalRateLimitingMiddleware. First let me check the analyzer constraints to avoid build failures: Now rewriting the Global middleware: S5 — rewriting the Dynamic middleware: I need to update `RateLimitPartitioners` to support the Tenant element used by the Dynamic middleware: Now simplifying the Dynamic middleware to use the Tenant-aware overload:
SUCCESS Tester Tester @ opencode 关联任务: #1 实现 API 访问频率限制 (Rate Limiting)
2026-09-24 13:22:34
I'll start by exploring the current state of the codebase to understand what the Developer implemented. The Developer's implementation is in place. Let me read all the new and modified files. Now let me read the modified middleware files, options model, DI registration, and appsettings. The RFC mentioned an existing test file with 5 tests, but it doesn't exist. Let me check the test project structure and find related security tests. The test file exists. Let me read it fully. Now let me check the DI registration and how the middleware is wired in Program.cs. Let me look at the failed login tracker, the entity metadata provider, and the SecurityDefinition model. Now let me build the solution to check for compile errors, especially the existing tests that need constructor updates. Build has 5 compile errors in the Developer's implementation. Let me investigate the .NET 10 API surface for `RateLimitLease.RetryAfter` and `IPAddress.IsIPv6LinkLocal`. Let me write a quick reflection check to see the actual `RateLimitLease` API surface. `RateLimitLease` in .NET 10 has no `RetryAfter` property. Let me find which type exposes it and how to get retry-after info. Found the real issues. In .NET 10, `RateLimitLease` no longer has a `RetryAfter` property (must use `TryGetMetadata(MetadataName.RetryAfter, out TimeSpan)`), and `IPAddress.IsIPv6LinkLocal` is now an instance property. Let me check the pre-change middleware to compare. The prior implementation used `retryAfter = rule.WindowSeconds`, but the Developer introduced `lease.RetryAfter` which doesn't exist in .NET 10. These are compile-blocking bugs in the implementation. Let me set up my testing plan. Now let me fix the three compile-blocking bugs in the Developer's implementation. Main project now builds; only the two legacy Global tests need constructor updates. Let me check the test project references for TestServer availability. Let me look at the integration test factories and a sample integration test to understand patterns.
SUCCESS Architect Architect @ opencode 关联任务: #2 创建轻量级待办应用 (Todo Application)
2026-09-24 23:08:32
# 待办 Todo 应用 — 架构 RFC / 实现规范 ## 1. 背景与架构决策 基于 NetYamlForge 的既定模式,Todo 模块应作为**独立项目 (project)** 实现,而非全局实体。理由: - 与 `projects/agent-studio/`、`projects/userhome/` 的现有隔离模式一致; - 项目级 Hook(`projects/<name>/Hooks/*.cs`)会在启动时被 `ProjectHookLoader` 用 Roslyn 自动编译并注册(`IEntityHook` 与 `ICustomActionHandler` 均自动发现);全局 `config/entities.yml` + `Program.cs` 手动 `AddSingleton<IEntityHook,...>` 的方式仅适合平台级钩子。 - 路由自然获得命名空间:`/todo-app/DynamicEntity/Index?entity=todo`。 **关键机制(已核实源码):** - **路由/CRUD UI**:`DynamicEntityController` 按 `entities/*.yml` 元数据自动生成 Index/New/Edit/Delete,无需 Razor 页面。 - **建表**:不自动建表。表由 `ProjectDataMigrationRunner` 在启动时按 `projects/<name>/database/migrations/NNNN_*.sql`(命名正则 `^(\d{3,})_[A-Za-z0-9_\-]+\.sql$`)顺序执行,记录于 `_nyf_data_migrations`。 - **状态切换 Hook**:推荐用 `ICustomActionHandler`,在实体 YAML 的 `actions:` 区声明按钮,行级执行;`IEntityHook` 用于校验型业务规则(beforeCreate/beforeUpdate)。 - **菜单**:`project.yaml` 的 `layout.navigation`(`entities:` + `items:`)。 - **看板**:`dashboard.yml`,stats/chart 均支持 `filter`(WHERE 子句)。 - **测试**:`NetYamlForge.Tests/`(xUnit),有 `Hooks/`、`Integration/` 目录,工厂类 `NetYamlForgeWebApplicationFactory.cs`。 --- ## 2. 组件交互 ``` [Browser] ── GET /todo-app/DynamicEntity/Index?entity=todo ──┐ └─ POST toggle_complete ┘ ▼ DynamicEntityController (动态CRUD + actions 分发) │ │ entities/todo.yml ────────┘ │ 通过 ctx.RecordId 定位行 (元数据驱动UI/校验/列/表单) │ ▼ Hooks/TodoHooks.cs (启动时 Roslyn 编译注册) ├─ TodoValidationHook : IEntityHook (beforeCreate/beforeUpdate) └─ TodoToggleCompleteHandler : ICustomActionHandler (toggle_complete) │ ▼ database/todo-app.db (SQLite) ┌── 0001_create_todo.sql 由 ProjectDataMigrationRunner 启动建表 └── dashboard.yml / config/i18n.yml / project.yaml 驱动展示与导航 ``` --- ## 3. 文件清单(全部相对仓库根 `NetYamlForge/`) | 路径 | 用途 | |---|---| | `projects/todo-app/project.yaml` | 项目定义 + 菜单/导航 | | `projects/todo-app/entities/todo.yml` | Todo 实体(列/表单/过滤/动作/钩子) | | `projects/todo-app/database/migrations/0001_create_todo.sql` | 建表 SQL | | `projects/todo-app/Hooks/TodoHooks.cs` | 自定义校验 Hook + 状态切换 Handler | | `projects/todo-app/dashboard.yml` | 看板统计与图表 | | `projects/todo-app/config/i18n.yml` | 多语言键 | | `NetYamlForge.Tests/Hooks/TodoHooksTests.cs` | 契约单测 | | `NetYamlForge.Tests/Integration/TodoAppIntegrationTests.cs` | 端到端 CRUD + 动作测试 | --- ## 4. 契约签名 ### 4.1 实体 YAML — `projects/todo-app/entities/todo.yml` ```yaml entities: todo: table: todo key: id displayName: Todo softDelete: false paging: pageSize: 15 mode: numbered actions: toggle_complete: # ← 状态切换 Hook(按钮) label: 切换完成状态 labelKey: todo.actions.toggle_complete scope: row confirm: 确定要切换该待办事项的完成状态吗? confirmKey: todo.actions.toggle_complete_confirm handler: todo_toggle_complete # ICustomActionHandler.Name hooks: beforeCreate: todo_validate beforeUpdate: todo_validate columns: id: { type: int, identity: true, label: ID, sortable: true } title: { type: string, required: true, label: 标题, searchable: true, sortable: true } description: { type: textarea, label: 描述, searchable: true } is_completed: { type: boolean, label: 完成, sortable: true } due_date: { type: date, label: 截止日期, sortable: true } priority: { type: string, required: true, label: 优先级, searchable: true, sortable: true } created_at: { type: datetime, label: 创建时间, sortable: true } updated_at: { type: datetime, label: 更新时间, sortable: true } forms: title: type: string required: true label: 标题 editable: true description: type: textarea label: 描述 editable: true is_completed: type: boolean label: 完成 editable: true due_date: type: date label: 截止日期 editable: true priority: type: select label: 优先级 editable: true required: true options: [low, medium, high] # 默认值 medium 由 Hook 兜底 filters: priority: type: toggle-group label: 优先级 options: [low, medium, high] is_completed: type: bool-toggle label: 完成状态 due_date: type: date-range label: 截止日期 ``` ### 4.2 project.yaml(导航/菜单) `layout.navigation` 形如 agent-studio:`showDashboard: true`、`entities: [todo]`,外加 `items` 指向 `/todo-app/DynamicEntity/Index?entity=todo` 与 `/todo-app/Dashboard`。`features.api: true`,`database.type: sqlite`(相对路径 `database/todo-app.db`)。项目级 `dashboard.yml` 直接放项目根(`DashboardConfigProvider(string projectDir)` 读取)。 ### 4.3 建表 SQL (SQLite) 文件 `projects/todo-app/database/migrations/0001_create_todo.sql`: ```sql CREATE TABLE IF NOT EXISTS todo ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, is_completed INTEGER NOT NULL DEFAULT 0, due_date TEXT, priority TEXT NOT NULL DEFAULT 'medium', created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); ``` > 字段类型遵循 `SqlTypeMapper`(sqlite:`boolean→INTEGER`、`date/datetime→TEXT`)。仅支持 sqlite。若需支持 Postgres/MySQL,需为各 dialect 补迁移文件。 ### 4.4 C# Hook 契约 — `projects/todo-app/Hooks/TodoHooks.cs` 命名空间须可被 `HookAssemblyCompiler` 编译(参考 `NetYamlForge.Projects.AgentStudio.Hooks`)。**不得**在 `Program.cs` 注册——项目 Hook 自动加载。 ```csharp using System.Data; using Dapper; using NetYamlForge.Services.Hooks; namespace NetYamlForge.Projects.TodoApp.Hooks; public sealed class TodoValidationHook : IEntityHook { public string Name => "todo_validate"; public Task<HookResult> BeforeAsync(EntityHookContext ctx, IDbConnection db, IDbTransaction? tx) { // 规则(实现要点): // 1) title 非空(配合 YAML required 双重保障) // 2) priority ∈ {low, medium, high},否则 Abort(); 缺省则 Values["priority"]="medium" // 3) due_date 若早于今天则不强制拦截(可配置),此处仅做格式校验 // 4) updated_at 设为 db 当前时间;Create 时补 created_at return Task.FromResult(HookResult.Continue()); } public Task AfterAsync(EntityHookContext ctx, IDbConnection db, IDbTransaction? tx) => Task.CompletedTask; } public sealed class TodoToggleCompleteHandler : ICustomActionHandler { public string Name => "todo_toggle_complete"; public Task<ActionHandlerResult> ExecuteAsync(CustomActionContext ctx, IDbConnection db, IDbTransaction? tx) { // 实现要点: // - int.TryParse(ctx.RecordId) 否则 Failure("无效的待办 ID") // - SELECT id,is_completed,updated_at FROM todo WHERE id=@Id (tx) // - 不存在 → Failure("未找到该待办事项"); // - UPDATE todo SET is_completed = 1 - is_completed, updated_at=datetime('now') // WHERE id=@Id // - 组装结果消息携带新状态: ctx.Data["newState"] = ... return Task.FromResult(ActionHandlerResult.Success()); } } ``` > **注意**:`ExecuteAsync` 运行在 `SqliteWriteGate + 事务`内(见 agent-studio 注释),只做 SQLite 快操作即可,禁止在此同步长 I/O。 ### 4.5 dashboard.yml(stats 的 `filter` 为 WHERE) ```yaml stats: - { label: 全部待办, entity: todo, aggregate: count, filter: "", icon: 📝, color: badge-primary } - { label: 已完成, entity: todo, aggregate: count, filter: "is_completed = 1", icon: ✅, color: badge-success } - { label: 待处理, entity: todo, aggregate: count, filter: "is_completed = 0", icon: ⏳, color: badge-warning } charts: - title: 待办优先级分布 type: doughnut entity: todo valueAggregate: count groupExpression: priority orderBy: value orderDir: desc limit: 3 colors: [ "rgba(16,185,129,0.8)", "rgba(245,158,11,0.8)", "rgba(239,68,68,0.8)" ] ``` (`filter` 字段已由 `DashboardStatDefinition.Filter`/`DashboardChartDefinition.Filter` 支持。) --- ## 5. 给 Developer 的分步指南 1. **初始化目录结构与项目骨架** - `mkdir -p projects/todo-app/{entities,database/migrations,Hooks,config}`(`database/` 由运行期自动创建即可)。 - 写 `projects/todo-app/project.yaml`(`name: todo-app`、`displayName: Todo App`、`database.type: sqlite`、`features.{multiLanguage:true,userAuthentication:true,dashboard:true,api:true}`、`layout.navigation` 含 `entities:[todo]` + 两个 `items`)。 2. **写实体 YAML** — `entities/todo.yml`(见 4.1)。注意:`columns` 与 `forms` 的字段名使用 snake_case 与表列一致,类型用 entity-schema 枚举(`string/int/boolean/date/textarea/datetime/select`)。 3. **建表迁移** — `database/migrations/0001_create_todo.sql`(见 4.3)。可选补 `-- +down` 段。 4. **实现 Hook** — `Hooks/TodoHooks.cs`(见 4.4)实现 `TodoValidationHook : IEntityHook` 与 `TodoToggleCompleteHandler : ICustomActionHandler`。参照 `projects/agent-studio/Hooks/AgentStudioActionHandlers.cs` 的结构与 DI 用法(可注入 `ILogger<T>`)。 5. **看板 + i18n** — `dashboard.yml`(见 4.5);`config/i18n.yml` 为 `todo.actions.toggle_complete`、`todo.actions.toggle_complete_confirm` 及各 label 配 `en-US/zh-CN/ja-JP`(若希望纯单语可省略 labelKey 直接用 label)。 6. **单测** — `NetYamlForge.Tests/Hooks/TodoHooksTests.cs`:直接 new 两个类,手工构造 `EntityHookContext`/`CustomActionContext` + `SqliteConnection`,验证: - 合法 priority 的依赖值注入 / 非法 priority `Abort`; - 空 title `Abort`; - 不存在 id `Failure`;已存在 id 切换 `is_completed` 0→1→0 且 `updated_at` 被刷新。 7. **集成测试(可选)** — `NetYamlForge.Tests/Integration/TodoAppIntegrationTests.cs`,复用 `NetYamlForgeWebApplicationFactory` 后置 `Program`,对 `/todo-app/DynamicEntity/Index?entity=todo` 的 GET 列表、POST 新建、Post toggle 动作断言。 ## 6. 验证方式 - **启动验证**:`dotnet run --project NetYamlForge`;日志出现 `PROJECT_LOADED`/`todo-app` 注册与 `HOOK_LOAD` 计数;无 `EntityDbSchemaConsistencyValidator` 抛错。 - **功能验证**:`/todo-app` 落地页 → 菜单进入「待办列表」;新建/编辑/删除、行内「切换完成状态」按钮、优先级/完成态/截止日期过滤、Dashboard 统计。 - **Schema 校验**:控制台可用 `--scaffold-entities --project=todo-app`(幂等)或先 `dotnet run -- --init-project --project=todo-app --no-auto-scaffold --force` 生成骨架后覆盖(推荐手工写文件保持可控)。 - **回归**:`dotnet test NetYamlForge.Tests --filter "FullyQualifiedName~Todo"`。 ## 7. 边界与假设 - 默认仅 **SQLite**(框架默认 DB);跨 dialect 迁移不做(`SqlTypeMapper` 已为各方言映射类型,可后续补 SQL Server/Postgres 迁移)。 - `is_completed` 用 INTEGER 0/1;dashboard `filter: "is_completed = 1"` 在 sqlite 成立。 - 不进 `config/entities.yml` / `Program.cs` 注册——保持模块自包含,符合项目级 Hook 自动载入机制。
SUCCESS Developer Developer @ opencode 关联任务: #2 创建轻量级待办应用 (Todo Application)
2026-09-24 23:13:32
I'll start by exploring the repository structure and reference patterns before implementing. Now let me look at the hook interfaces and contexts. Now let me look at the test infrastructure and some existing tests. Let me look at the test infrastructure and existing integration test patterns. Let me examine the entity schema definition and how actions and hooks are wired. Let me look at the DynamicEntityController and its action dispatch routes. Interesting — there's an existing reference to `todo-app` in `ProjectSpecificInitializer.cs`. Let me check it. The project.yaml is validated against a JSON schema. Let me check the schema to ensure compliance. Let me read the schema files to ensure my YAML will validate. Let me check how filters are applied at runtime and look at an entity yaml that uses filters with options. Let me check the `ChinookDownloader` behavior and the `SqliteAuthSchemaInitializer` to understand startup side-effects on a new project DB. The startup migration runner may target the default connection. Let me verify how it actually behaves by checking run logs and existing config. Let me review the test project structure and csproj to model the tests correctly. ## Objective - Implement a self-contained Todo (待办) application module in NetYamlForge as an independent project `projects/todo-app/` (entity `todo`, YAML-driven CRUD UI, dashboard, i18n, validation + status-toggle hooks), following the `projects/agent-studio/` pattern, plus unit/integration tests. - Deliverable: no code changes made yet — only repo exploration completed; all files still need to be authored. ## Important Details - Repo root: `/home/ubuntu/ws/NetYamlForge`; app project: `NetYamlForge/`; existing projects: `NetYamlForge/projects/{agent-studio,userhome}`. Tests: `NetYamlForge.Tests/` (xUnit). - File plan (per RFC): `projects/todo-app/{project.yaml, dashboard.yml, entities/todo.yml, database/migrations/0001_create_todo.sql, Hooks/TodoHooks.cs, config/i18n.yml}`; tests: `NetYamlForge.Tests/Hooks/TodoHooksTests.cs`, `NetYamlForge.Tests/Integration/TodoAppIntegrationTests.cs`. - Hook contracts (namespace `NetYamlForge.Services.Hooks`): `IEntityHook { string Name; Task<HookResult> BeforeAsync(EntityHookContext, IDbConnection, IDbTransaction?); Task AfterAsync(...) }`; `HookResult.Continue()/Abort(msg)`; `CustomActionContext` has `RecordId (string?)`, `Inputs`, `Data`, `Project/Entity/Action`; `ActionHandlerResult.Success()/Failure(msg)`. - `EntityHookContext.Values` is a `Dictionary<string,object?>` — mutate it to inject defaults (e.g. `priority="medium"`); `ctx.Data` is free transfer area. - Project hooks auto-loaded at startup by `ProjectHookLoader` (Roslyn, `projects/<name>/Hooks/*.cs`); ICustomActionHandler + IEntityHook both auto-registered. Do NOT register in `Program.cs`/`config/entities.yml`. - Migration runner `ProjectDataMigrationRunner` executes `projects/<name>/database/migrations/NNNN_*.sql` (regex `^(\d{3,})_[A-Za-z0-9_\-]+\.sql$` → `0001_create_todo.sql` OK), tracked in `_nyf_data_migrations`; table creation must come from migration SQL, not auto-scaffold. - SQLite only. Migration SQL types follow `SqlTypeMapper`: `is_completed INTEGER NOT NULL DEFAULT 0`, `due_date TEXT`, `created_at/updated_at TEXT NOT NULL DEFAULT (datetime('now'))`. - `EntityDbSchemaConsistencyValidator.ValidateOrThrow` checks NOT NULL columns without defaults are present in YAML columns and required in forms if editable → `title TEXT NOT NULL` must be `required: true` in both `columns.title` and `forms.title`; `id` is identity; `is_completed/priority/created_at/updated_at` have DB defaults → safe. It skips when DB file/table missing. - YAML schema validation (`Schemas/`): `project-schema.json` requires `name`, `database.type`; agent-studio uses `features: {multiLanguage, userAuthentication, dashboard, pages, api}` which loads fine. `entity-schema.json`: column `type` enum does NOT include `select` — use `type: string` for `priority` column (agent-studio uses `string` for status); form `type: select` with `options: [low, medium, high]` IS valid per `FormDefinition` docstring. Filter `type` is free-form string (`toggle-group`, `bool-toggle`, `date-range` all rendered by `Views/Shared/_FilterControl.cshtml`; `dropdown/select` read `Options`/`OptionLabels`). - `EntityMetadataParser` uses CamelCaseNamingConvention + IgnoreUnmatchedProperties → `optionLabels` → `FilterDefinition.OptionLabels`, `labelKey`/`labelI18n` supported. - Dashboard models: `DashboardStatDefinition.Filter` = raw WHERE clause (e.g. `"is_completed = 1"`); `DashboardChartDefinition` supports `type: doughnut`, `groupExpression: priority`, `orderBy/orderDir/limit/colors`; project `dashboard.yml` read from project root via `DashboardConfigProvider(projectDir)`. - Custom actions run inside `SqliteWriteGate` + transaction (`EntityCrudExecutionService.ExecuteActionAsync`) — `ExecuteAsync` must be a fast SQLite op only, no long I/O. - Integration test infra: `ApiTestWebApplicationFactory`/`NetYamlForgeWebApplicationFactory` copy a project into a temp content root, exclude `.db/.db-wal/.db-shm/.env` + `jobs/Controllers/bin/obj`, and place an empty DB file to prevent `ChinookDownloader` network fetch. API auth uses `Bearer admin-api-token` (`ApiToken` scheme; system DB seeds `admin-api-token`). DynamicEntity routes: `~/{project}/DynamicEntity/...`; API routes: `api/{project}/{entity}` (GET list, POST create, `/meta` confirmed; action route not yet confirmed). - Legacy leftovers (pre-existing, harmless, don't modify): `Data/ProjectSpecificInitializer.cs` `InitializeTodoAppAsync` expects `projects/todo-app/database/init_seed.sql` + `Category` table (warns only if missing); `Data/Seeders/ProjectSpecificTestUserSeeder.cs` seeds `todo_admin1/todo_user1/todo_user2/todo_viewer1` into tenant DB `app_user` tables (auth tables exist via `SqliteAuthSchemaInitializer`). - Verification commands: `dotnet run --project NetYamlForge`; `dotnet test NetYamlForge.Tests --filter "FullyQualifiedName~Todo"`. ## Work State ### Completed - Verified repo layout, reference project (`agent-studio`) file structure, `project.yaml`, `dashboard.yml`, `Hooks/AgentStudioActionHandlers.cs` patterns. - Confirmed exact `IEntityHook`/`ICustomActionHandler`/`CustomActionContext`/`EntityHookContext`/`HookResult`/`ActionHandlerResult` signatures. - Confirmed migration runner regex, hook auto-loading mechanism, dashboard `filter` support, filter UI control types, entity/form/column YAML schema constraints, `EntityDbSchemaConsistencyValidator` rules. - Confirmed test factory patterns (`ApiTestWebApplicationFactory`, `NetYamlForgeWebApplicationFactory`, `TestAuthHandler`, `ApiEntityIntegrationTests` with `Bearer admin-api-token`). - Checked `Data/ProjectSpecificInitializer.cs` + `ProjectSpecificTestUserSeeder.cs`: existing legacy `todo-app` references will only warn/seed users, not break a migrations-based module. ### Active - Exploration only; no project files, hooks, or tests written yet. ### Blocked - None, but unverified: REST action dispatch route for API (`POST /api/{project}/{entity}/{id}/actions/{action}`) — confirm before writing API-based integration tests. ## Next Move 1. Create `projects/todo-app/` files per RFC: `project.yaml` (name `todo-app`, sqlite `database/todo-app.db`, features multiLanguage/userAuthentication/dashboard/api, `layout.navigation` with `entities: [todo]` + items), `entities/todo.yml` (snake_case columns/forms; `columns.priority: {type: string}`; `forms.priority: {type: select, options: [low, medium, high]}`; `actions.toggle_complete` scope `row` with `handler: todo_toggle_complete`; `hooks.beforeCreate/beforeUpdate: todo_validate`; filters toggle-group/bool-toggle/date-range), `database/migrations/0001_create_todo.sql`, `Hooks/TodoHooks.cs` (namespace `NetYamlForge.Projects.TodoApp.Hooks`; `TodoValidationHook : IEntityHook` Name `todo_validate`, `TodoToggleCompleteHandler : ICustomActionHandler` Name `todo_toggle_complete` with SQLite toggle `is_completed = 1 - is_completed` + `updated_at=datetime('now')`, row lookup by `int.TryParse(ctx.RecordId)`), `dashboard.yml` (3 stats with filter + doughnut chart by priority), `config/i18n.yml`. 2. Write `NetYamlForge.Tests/Hooks/TodoHooksTests.cs` (direct `new` + temp `SqliteConnection`; test priority default/abort, empty-title abort, toggle 0→1→0, missing id → Failure). 3. Write `NetYamlForge.Tests/Integration/TodoAppIntegrationTests.cs` (+ `TodoAppWebApplicationFactory` copying `NetYamlForge/projects/todo-app` into temp root with empty `.db`); use `Bearer admin-api-token` API CRUD + toggle action; verify action route first. 4. Build and run `dotnet test NetYamlForge.Tests --filter "FullyQualifiedName~Todo"`. ## Relevant Files - `NetYamlForge/projects/agent-studio/project.yaml`, `entities/agent_task.yml`, `dashboard.yml`, `Hooks/AgentStudioActionHandlers.cs`: reference patterns for todo-app files (incl. long-I/O warning comment in hooks). - `NetYamlForge/projects/userhome/project.yaml`, `config/i18n.yml`, `dashboard.yml`: minimal project/i18n reference (`translations:` key format). - `NetYamlForge/Services/Hooks/IEntityHook.cs`, `EntityHookContext.cs`, `ICustomActionHandler.cs`: hook contracts to implement. - `NetYamlForge/Services/Project/ProjectHookLoader.cs`, `ProjectDataMigrationRunner.cs`: auto-load + migration mechanics. - `NetYamlForge/Services/DynamicEntity/DynamicCrudFilterApplier.cs`, `Views/Shared/_FilterControl.cshtml`: filter types behavior. - `NetYamlForge/Models/DashboardConfig.cs`, `Services/Project/ProjectAwareDashboardConfigProvider.cs`: dashboard stat/chart `filter` field. - `NetYamlForge/Schemas/{project,entity,dashboard}-schema.json`, `NetYamlForge/Services/YamlSchemaValidator.cs`: YAML validation constraints. - `NetYamlForge/Models/Entity/{EntityDefinition,FormDefinition,ColumnDefinition,FilterDefinition,ActionDefinition}.cs`: entity model fields (Options/OptionLabels, hooks/actions). - `NetYamlForge.Tests/Integration/{ApiTestWebApplicationFactory,NetYamlForgeWebApplicationFactory,TestAuthHandler,ApiEntityIntegrationTests}.cs`: factories/auth patterns for integration tests. - `NetYamlForge/Data/ProjectSpecificInitializer.cs`, `NetYamlForge/Data/Seeders/ProjectSpecificTestUserSeeder.cs`: pre-existing legacy `todo-app` hooks reference — do not modify, verify init doesn't interfere. Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed. Now let me verify the remaining details: the entity schema JSON, the reference entity YAML, the API action route, and the test factory.
SUCCESS Tester Tester @ opencode 关联任务: #2 创建轻量级待办应用 (Todo Application)
2026-09-24 23:18:32
I'll start by verifying the current repository state to understand what exists and what needs testing. The `todo-app` module does not exist yet — only exploration was done by the developer. Let me examine the existing test infrastructure and reference patterns so I can author comprehensive tests (and the system-under-test they verify). Now let me read the hook contracts, reference entity/dashboard YAML, and test infrastructure. Let me look at the TestProjects fixtures, the API controller routes, and the DynamicEntity action dispatch. Found the API action route: `POST /api/{project}/{entity}/{id}/actions/{actionKey}`. Let me examine the API action service and the DynamicEntity row action routes. Now let me examine the hook loader, migration runner, dashboard config, and the legacy initializer. Critical detail found: at startup, migrations run against `Projects:{name}:Connection` or `DefaultConnection`. Let me check the appsettings and how project connections are resolved. I have a thorough understanding now. Let me check the security-demo entity YAML (used by existing API tests) and the form validation service to pin down API payload expectations. ## Objective - 完成 NetYamlForge 的轻量级 Todo 待办应用模块(`projects/todo-app/`:YAML 驱动 CRUD、dashboard、i18n、`IEntityHook` 校验 + `ICustomActionHandler` 状态切换),并交付/验证 xUnit 单测与集成测试。 - 当前模块实现文件尚未创建(开发阶段仅完成探索),Tester 需同时补齐模块文件(作为被测系统)与测试,再以 `dotnet test NetYamlForge.Tests --filter "FullyQualifiedName~Todo"` 验证并报告缺陷。 ## Important Details - 仓库根 `/home/ubuntu/ws/NetYamlForge`;应用项目 `NetYamlForge/`;测试 `NetYamlForge.Tests/`(net10.0、xUnit 2.9.2、Microsoft.AspNetCore.Mvc.Testing 10.0.3、Moq、引用 NetYamlForge.Tooling 与 Analyzers)。仓库存在与本任务无关的未提交改动(Showcase/限流相关),勿动。 - `projects/todo-app/` 不存在;现有项目仅 `projects/{agent-studio,userhome}`。测试夹具项目在 `NetYamlForge.Tests/TestProjects/security-demo/`。 - Hook 契约(`NetYamlForge.Services.Hooks`):`IEntityHook { string Name; Task<HookResult> BeforeAsync(EntityHookContext, IDbConnection, IDbTransaction?); Task AfterAsync(...) }`;`HookResult.Continue()/Abort(msg)`;`EntityHookContext.Values` 是 `Dictionary<string,object?>`,Hook 直接修改以注入默认值(如 `priority="medium"`、`is_completed=false`、`created_at/updated_at=datetime('now')`);`ctx.Data` 为自由传送区。 - `ICustomActionHandler { string Name; Task<ActionHandlerResult> ExecuteAsync(CustomActionContext, IDbConnection, IDbTransaction?) }`;`CustomActionContext` 含 `Project/Entity/Action/RecordId(string?)/Inputs/Files/MultipleFiles/UserName/Data`;`ActionHandlerResult.Success()/Failure(msg)`。 - Hook 自动加载:`ProjectHookLoader`(Roslyn)自动编译 `projects/<name>/Hooks/*.cs` 并注册 `IEntityHook` + `ICustomActionHandler`(`IProjectActionRegistry`)。不得在 `Program.cs`/`config/entities.yml` 注册。Hook 命名空间参照 `NetYamlForge.Projects.AgentStudio.Hooks`(本项目用 `NetYamlForge.Projects.TodoApp.Hooks`)。 - Action 执行位于 `EntityCrudExecutionService.ExecuteActionAsync` 的 `SqliteWriteGate` + 事务内(agent-studio 注释明确),仅允许快速 SQLite 操作。 - 路由:UI `/todo-app/DynamicEntity/Index?entity=todo`;API action 路由已确认 = `POST /api/{project}/{entity}/{id}/actions/{actionKey}`(`Controllers/ApiEntityController.cs` `InvokeAction`,行 231,body 为 JSON dict;handler 解析:`actionDef.Handler` 为空则用 `actionKey`)。API 列表/创建 `/api/{project}/{entity}`、元数据 `/meta`,鉴权 `Authorization: Bearer admin-api-token`(ApiToken 方案)。 - 迁移:`ProjectDataMigrationRunner` 正则 `^(\d{3,})_[A-Za-z0-9_\-]+\.sql$`(`0001_create_todo.sql` 合法),记录于 `_nyf_data_migrations`;另有 CLI `--migrate-data[--project=…]` / `--migrate-data-status` / `--migrate-data-rollback --version=…`。 - **关键框架事实**:启动迁移循环(`Program.cs` ~338-357)用 `Projects:{name}:Connection ?? DefaultConnection("Data Source=chinook.db")` 而非项目 `database.path`,故项目 DB 实际由 `DbInitializer→ProjectSpecificInitializer→AutoMigrateMissingColumnsAsync`(`TableDdlBuilder` 按 YAML 自动建表)创建;该自动建表**无 DEFAULT 约束**(`is_completed/priority/created_at/updated_at` 可 NULL),因此 `TodoValidationHook` 必须在 create 时注入 `is_completed`、`priority`、`created_at`、`updated_at`,update 时注入 `updated_at`。`EntityDbSchemaConsistencyValidator.ValidateOrThrow` 在 DB/表缺失时跳过;检查无默认 NOT NULL 列需出现在 YAML columns 且 editable form 需 `required: true`。 - YAML schema 约束:列 `type` 枚举无 `select`(`priority` 列用 `type: string`);表单 `type: select` + `options: [low,medium,high]` 合法;filter `type` 自由字符串(`toggle-group/bool-toggle/date-range` 由 `_FilterControl.cshtml` 渲染);`project-schema.json` 只要求 name+database,额外属性允许(agent-studio 的 features.dashboard/pages/api 可加载);dashboard 的 stat 需 entity+aggregate 且 label 或 labelKey,chart 需 type/entity/valueAggregate,`filter` 为原始 WHERE。 - `SqlTypeMapper`(sqlite):bool/int/long→INTEGER;decimal/double/number→NUMERIC;date/datetime/string 等→TEXT。`DynamicCrudRepository.InsertAsync` 仅插入 values 中出现且非 identity 的列。 - i18n 格式(userhome):`translations: { key: {en-US, zh-CN, ja-JP, ko-KR} }`。dashboard 项目根 `dashboard.yml` 由 `DashboardConfigProvider(projectDir)` 读取。 - 遗留代码(勿改,无害):`Data/ProjectSpecificInitializer.cs` 对 "todo-app" 调 `InitializeTodoAppAsync`(缺 `database/init_seed.sql`+`Category` 表仅告警)与 `ProjectSpecificTestUserSeeder`(向租户 DB 种 `todo_admin1/todo_user1/todo_user2/todo_viewer1`)。 - 测试基建:`ApiTestWebApplicationFactory`/`NetYamlForgeWebApplicationFactory` 把项目复制到临时 content root,排除 `.db/.db-wal/.db-shm/.env` 与 `jobs/Controllers/bin/obj` 目录,并放置空 DB 文件阻止 `ChinookDownloader` 联网;单测模式参照 `NetYamlForge.Tests/EntityCrudExecutionServiceTests.cs`(`new SqliteConnection("Data Source=:memory:")` + 直接 new + Test 注册表/DelegateHook),另有 `NetYamlForge.Tests/Hooks/DapperDictionaryDiagnosis.cs`。 - 参考实现:`projects/agent-studio/Hooks/AgentStudioActionHandlers.cs`(`RunPipelineHandler`,事务内快操作模式)、`project.yaml`、`entities/agent_task.yml`(hooks 用 string 形式 `beforeCreate: todo_validate`,`EntityHooksService.GetExpandedHookList` 支持)、`dashboard.yml`。 - 验证命令:`dotnet run --project NetYamlForge`;`dotnet test NetYamlForge.Tests --filter "FullyQualifiedName~Todo"`。 ## Work State ### Completed - 核实 `projects/todo-app/` 不存在,全部实现文件待创建(开发阶段仅完成探索,未产出代码)。 - 核实 Hook 契约签名、`EntityHookContext`/`CustomActionContext` 字段、`HookResult`/`ActionHandlerResult`。 - 确认 API action 路由 `POST /api/{project}/{entity}/{id}/actions/{actionKey}` 及 handler 解析、响应失败语义。 - 确认迁移正则、Hook 自动加载、dashboard `filter` 支持(agent-studio 示例)、filter UI 控件类型、YAML schema 约束(列无 select、表单 select+options 合法)。 - 读取并理解测试工厂(`ApiTestWebApplicationFactory`、`NetYamlForgeWebApplicationFactory`、`TestAuthHandler`、`ApiEntityIntegrationTests` 的 Bearer admin-api-token 用法)、agent-studio/userhome 参考 YAML、`EntityCrudExecutionService`、`DynamicCrudRepository.Write`、`FormDefinition/FilterDefinition/ColumnDefinition`、`EntityHooksService`。 - 发现启动迁移目标连接串为 `DefaultConnection("Data Source=chinook.db")` 而非项目 `database.path`,并确认项目表实际由 YAML 自动建表路径创建(无 DEFAULT)→ 明确 Hook 必须注入默认值与时间戳。 - 核实遗留 `todo-app` 相关代码(`ProjectSpecificInitializer`、`ProjectSpecificTestUserSeeder`)仅告警/种用户,不会破坏迁移式模块。 ### Active - 探索已全部完成;尚未写入任何文件(模块实现与测试均未创建)。已决定:由 Tester 一并补写模块文件(被测系统)+ 单测 + 集成测试,使 `dotnet test` 可执行验证。 ### Blocked - 实现缺失 → 测试引用 `NetYamlForge.Projects.TodoApp.Hooks` 前必须先创建模块文件,否则编译失败。 - 未验证项:`ApiEntityActionService` 成功响应的确切 DTO 字段;dashboard stat `filter: ""` 空串是否安全(倾向省略 filter 键);表单 `is_completed` 用 `type: bool` 还是 `boolean`(需查 `DynamicEntityFormValidationService` 的 bool 处理)。 ## Next Move 1. 编写模块文件(即被测系统):`projects/todo-app/project.yaml`(name: todo-app、sqlite `database/todo-app.db`、features multiLanguage/userAuthentication/dashboard/api、layout.navigation 含 `entities:[todo]` + items)、`entities/todo.yml`(snake_case;`columns.priority` 用 `type: string`;`forms.priority` 用 `type: select` + options;`actions.toggle_complete` scope row handler `todo_toggle_complete`;hooks `beforeCreate/beforeUpdate: todo_validate`;filters toggle-group/bool-toggle/date-range)、`database/migrations/0001_create_todo.sql`、`Hooks/TodoHooks.cs`(namespace `NetYamlForge.Projects.TodoApp.Hooks`;`TodoValidationHook` Name `todo_validate` 注入 title/priority/is_completed/created_at/updated_at 并校验;`TodoToggleCompleteHandler` Name `todo_toggle_complete` 用 `int.TryParse(ctx.RecordId)` + `UPDATE todo SET is_completed = 1 - is_completed, updated_at=datetime('now')`)、`dashboard.yml`(3 个 stats + priority 环形图,全部时段对第 1 个 stat 省略 filter 键)、`config/i18n.yml`(`todo.actions.toggle_complete` 等键,en-US/zh-CN/ja-JP)。 2. 编写 `NetYamlForge.Tests/Hooks/TodoHooksTests.cs`:直接 new + `SqliteConnection("Data Source=:memory:")`;覆盖 priority 默认注入/非法 Abort、空 title Abort、缺失 id → Failure、toggle 0→1→0 且 `updated_at` 刷新。 3. 编写 `NetYamlForge.Tests/Integration/TodoAppIntegrationTests.cs` + `TodoAppWebApplicationFactory`(复制 `NetYamlForge/projects/todo-app` 至临时根、建空 DB 防 ChinookDownloader、`Bearer admin-api-token`):GET 列表、POST 新建、`POST /api/todo-app/todo/{id}/actions/toggle_complete` 切换断言。 4. 构建并运行 `dotnet test NetYamlForge.Tests --filter "FullyQualifiedName~Todo"`,如有失败给出可复现步骤与缺陷报告。 ## Relevant Files - `NetYamlForge/projects/agent-studio/{project.yaml,entities/agent_task.yml,dashboard.yml,Hooks/AgentStudioActionHandlers.cs}`:todo-app 各文件的参考范式(含 SqliteWriteGate/事务长 I/O 警告)。 - `NetYamlForge/projects/userhome/{project.yaml,config/i18n.yml,dashboard.yml}`:最小项目与 i18n `translations:` 格式参考。 - `NetYamlForge/Services/Hooks/{IEntityHook.cs,EntityHookContext.cs,ICustomActionHandler.cs}`:待实现契约及上下文字段。 - `NetYamlForge/Services/Project/{ProjectHookLoader.cs,ProjectDataMigrationRunner.cs}`、`NetYamlForge/Program.cs`(~338-357 启动迁移连接串逻辑)、`NetYamlForge/Services/Entity/EntityCrudExecutionService.cs`:加载/迁移/action 事务机制。 - `NetYamlForge/Services/Entity/EntityDbSchemaConsistencyValidator.cs`、`NetYamlForge/Services/DynamicEntity/{TableDdlBuilder.cs,SqlTypeMapper.cs,EntityMetadataParser.cs,EntityHooksService.cs,DynamicCrudRepository.Write.cs}`:建表/类型映射/hooks 解析/插入列规则。 - `NetYamlForge/Controllers/ApiEntityController.cs`(InvokeAction 行 231)、`NetYamlForge/Services/Api/{ApiEntityActionService.cs,ApiEntityWriteService.cs,ApiEntityQueryService.cs}`:API action 路由与响应。 - `NetYamlForge/Schemas/{project,entity,dashboard}-schema.json`:YAML 校验约束。 - `NetYamlForge/Models/Entity/{FormDefinition,ColumnDefinition,FilterDefinition,ActionDefinition,EntityDefinition}.cs`:options/optionLabels/labelKey/hooks/actions 字段。 - `NetYamlForge.Tests/Integration/{ApiTestWebApplicationFactory,NetYamlForgeWebApplicationFactory,TestAuthHandler,ApiEntityIntegrationTests}.cs`、`NetYamlForge.Tests/EntityCrudExecutionServiceTests.cs`、`NetYamlForge.Tests/Hooks/DapperDictionaryDiagnosis.cs`:测试工厂/鉴权/单测模式。 - `NetYamlForge/Data/ProjectSpecificInitializer.cs`、`NetYamlForge/Data/Seeders/ProjectSpecificTestUserSeeder.cs`:遗留 todo-app 引用(勿改,验证不干扰)。 - 待创建:`projects/todo-app/{project.yaml,entities/todo.yml,database/migrations/0001_create_todo.sql,Hooks/TodoHooks.cs,dashboard.yml,config/i18n.yml}`、`NetYamlForge.Tests/Hooks/TodoHooksTests.cs`、`NetYamlForge.Tests/Integration/{TodoAppIntegrationTests.cs,TodoAppWebApplicationFactory.cs}`。 Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.