feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,6 @@
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"Focused runtime/database/process checks"}
|
||||
{"file":".trellis/spec/backend/error-handling.md","reason":"Retry, dead-letter and cancellation error behavior"}
|
||||
{"file":".trellis/spec/backend/directory-structure.md","reason":"Job owner separation and no parallel queue implementation"}
|
||||
{"file": ".trellis/tasks/09-20-durable-jobs/research/implementation-map.md", "reason": "Verify corrected transaction, lease, cron and shutdown contracts"}
|
||||
{"file": ".trellis/spec/backend/compiler-contracts.md", "reason": "Verify language and native emitter contract parity"}
|
||||
{"file": ".trellis/spec/backend/database-guidelines.md", "reason": "Verify transaction and lease correctness"}
|
||||
@@ -0,0 +1,100 @@
|
||||
# Dever 持久化 Job 设计
|
||||
|
||||
## Source Contract
|
||||
|
||||
Job role 使用 `job.dever` 或平铺的 `job/*.dever`,继续遵守角色文件与目录互斥、单 topic 必须合并的规则。Job 只是持久任务入口,不是可调用的普通函数:
|
||||
|
||||
```dever
|
||||
database default
|
||||
|
||||
job publish_due(input: app.PublishInput) () retry(5) timeout(30000) {
|
||||
app.publish_due(input.article_id)
|
||||
}
|
||||
|
||||
job cleanup_sessions() () retry(3) timeout(30000) {
|
||||
app.cleanup_expired()
|
||||
}
|
||||
|
||||
schedule cleanup_sessions = "0 * * * *"
|
||||
```
|
||||
|
||||
Payload record 由同领域 App 声明为公开契约,Job 不新增第二套 DTO 可见性。Job 只能有零个输入,或一个 wire-safe record 输入;输出必须为空;`retry(n)` 表示包含首次执行在内最多执行 `n` 次。`timeout(ms)` 和 `retry` 都是必填、有硬上限的正整数。
|
||||
|
||||
共享 wire schema 增加 nominal ModelId 节点。编码仍使用既有 ModelId 数值表示,但 fingerprint 包含所属 Model identity,不能把两个 Model 的 id 降级为同一种 Int/Id。Job payload 仍拒绝 Secret、private Model data、Choice、handler 和不稳定类型。
|
||||
|
||||
App 通过 compiler-owned 静态 target 入队:
|
||||
|
||||
```dever
|
||||
id = dever.job.enqueue(job.publish_due, input, key)
|
||||
id = dever.job.enqueue_at(job.publish_due, input, key, run_at)
|
||||
id = dever.job.enqueue(job.cleanup_sessions, key)
|
||||
id = dever.job.enqueue_at(job.cleanup_sessions, key, run_at)
|
||||
```
|
||||
|
||||
Target 不是普通值,不能存储、转发或用于普通 call/run/group。enqueue 使用专用 HIR,不把 Job handler 加入同步调用/effect/递归图;Job handler 作为 worker 独立 root 生成并检查。
|
||||
|
||||
## Effect And Transaction Integration
|
||||
|
||||
Model 和 Job 都映射到一个统一 database binding。effect 系统记录有类型的 database owner,并通过唯一 accessor 解析为现有 `ConnectionSelector`;Model CRUD 仍保留 Model schema owner,不制造虚假 Model,也不增加第二套 transaction context。
|
||||
|
||||
enqueue 只传播队列连接的 database/suspension/failure effect,不同步传播 Job handler 的 Model、Port 或业务 failure。transaction App 的 enqueue 使用现有隐藏 `_database: Option<&Transaction>` 和 `Executor::new`,与同连接 Model 写入共享物理 transaction;无 ambient transaction 时开启并提交短 transaction。显式不同连接在语义检查拒绝,依赖 default/package fallback 的冲突在 settings-aware run/build bootstrap 拒绝。
|
||||
|
||||
## Private Schema And States
|
||||
|
||||
每个使用 Job 的逻辑 database 初始化 runtime-owned versioned schema,独立于 Model history、snapshot、seed 和 migration。至少包含:
|
||||
|
||||
- Job:id、target identity、schema fingerprint、payload、dedupe key、state、run_at、attempt、max attempts、timeout、claim token、lease deadline、安全错误分类/摘要和时间戳。
|
||||
- Schedule cursor:稳定 schedule identity、cron fingerprint、last processed UTC minute 和更新时间。
|
||||
|
||||
状态为 pending、running、succeeded、dead、blocked。业务 key 只在 pending/running 上保证 `(target, key)` 唯一,终结后可重用;schedule occurrence 由持久 cursor 保证永久单窗口物化一次,不能依赖 active-only 唯一索引。
|
||||
|
||||
未知 target、schema mismatch 和 corrupt payload 不执行 App,保留原记录并进入 blocked。历史记录的实际 timeout 也必须满足当前 SQLite lease 约束,否则以固定 `invalid_policy` 进入 blocked。attempt 耗尽进入 dead,并由独立有界清理处理,不占用领取可执行任务的槽。运行时不保存 Secret、完整 payload、backtrace 或未经控制的 `AppError::to_string()`;只保存固定类别和有界安全摘要。
|
||||
|
||||
## Claim, Lease And Retry
|
||||
|
||||
PostgreSQL 使用短 transaction、bounded batch 和 `FOR UPDATE SKIP LOCKED`;SQLite 使用短 `BEGIN IMMEDIATE` 与条件 update。只按当前空闲执行槽 claim,不能预占后在无界内存队列等待。
|
||||
|
||||
每次成功 claim 时原子增加 attempt,并生成独立随机 claim token。这样进程在 handler 返回前崩溃也会消耗一次 attempt。过期 running 在 attempt 未达上限时可重领,已达上限时转 dead。
|
||||
|
||||
renew、success、retry、blocked/dead 更新均使用 `id + claim token + running state + 未过期 lease` CAS。claim 在获得写锁后读取时钟并建立租约;renew/complete 先锁定 claim 行,再读取同一 Clock,不能用锁等待前的时间续租或确认已过期任务。CAS 失败表示已失去所有权,旧执行者不得覆盖新 owner 状态。worker 在 lease 周期内续租;handler timeout 或安全业务失败按 checked exponential backoff 重新 pending,且不超过配置上限。语义为 at-least-once,外部副作用仍由业务 idempotency 保证。
|
||||
|
||||
## UTC Schedule
|
||||
|
||||
Cron 固定为五字段 UTC:minute、hour、day-of-month、month、day-of-week。支持 `*`、单值、逗号列表、闭区间和步长;范围分别是 `0..59`、`0..23`、`1..31`、`1..12`、`0..6`,其中 Sunday 为 0。day-of-month 与 day-of-week 同时受限时采用 cron 的 OR 语义,否则匹配受限字段。
|
||||
|
||||
首次启用从当前 UTC minute 建 cursor,不回放历史。以后在同一 transaction 锁定 cursor、插入 occurrence、推进 cursor;每轮最多扫描并推进 256 个分钟槽,只对匹配 cron 的槽生成任务,剩余分钟后续继续补。slot key 包含稳定 schedule identity 和 UTC minute,与业务 key 分离。相同 schedule identity 的 cron fingerprint 不一致时启动失败关闭,禁止多版本互相改写 cursor。
|
||||
|
||||
## Runtime Topology And Shutdown
|
||||
|
||||
严格配置只来自 `config/setting.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"runtime": { "mode": "all", "shutdown_ms": 30000 },
|
||||
"job": {
|
||||
"workers": 4,
|
||||
"poll_ms": 250,
|
||||
"lease_ms": 60000,
|
||||
"retry_base_ms": 1000,
|
||||
"retry_max_ms": 300000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`runtime.mode` 只能为 api、worker、all。generated entry 在 listen、schema materialize 或 claim 前统一校验 source capabilities、HTTP/Job 配置、driver 和所有上限。api 模式中的 `dever.job.serve()`、worker 模式中的 `dever.api.serve()`安全 no-op;同一 binary 保留两套静态 dispatch roots,不按运行模式裁剪。
|
||||
|
||||
没有 worker capability 的纯 API 应用可省略 runtime,默认 api 和 30000 ms;有 worker capability 时必须显式配置。worker 连接池必须比 worker 槽至少多一个连接,供 claim/heartbeat 使用;不引入独立控制连接池。SQLite 只有一个写者,额外连接无法绕过业务写事务,因此启动前还必须验证 `lease_ms > Job timeout_ms`,让租约覆盖 handler 的最长执行时间;PostgreSQL 可继续使用较短租约并续租。
|
||||
|
||||
generated entry 拥有唯一 lifecycle:结构化等待应用 root 与 SIGTERM/Ctrl-C。收到信号后广播 shutdown;API 关闭 listener 并复用 HTTP connection drain,Job 停止 schedule/claim 后等待 active handlers。到 `runtime.shutdown_ms` 才取消剩余作用域;未确认 success 的 Job 保留/恢复 lease。应用 root 汇合后执行 database shutdown,再 flush log。Tokio signal 是既有依赖的 feature 扩展,不引入 signal/queue/cron 框架或 unsafe handler。
|
||||
|
||||
Job timeout 上限为 3,600,000 ms,lease 的有界范围为 100..7,200,000 ms。测试使用 lease 上限,保证所有合法 Job timeout 都能在隔离 SQLite 中执行,仍复用生产 claim/dispatch/finish 和策略检查。
|
||||
|
||||
## Application Testing
|
||||
|
||||
每个 test case 保持独立 native module、进程、临时目录、Port fake graph 和 SQLite connections。闭包同时包含:test 直接 enqueue 的 Job、Job handler 调用的 App/Port,以及 handler 的 Model connections;生产 Adapter 和部署 settings 不得回退进入 case。
|
||||
|
||||
测试专用 compiler-owned 操作提供 case-local clock advance 和 bounded due-job drain。它们复用生产 claim/dispatch/finish 状态机,不启动永久 `serve()`,不真实 sleep,也不允许测试直接调用 Job entry。无 database/Job effect 的纯测试仍不加载 settings。
|
||||
|
||||
## Explicit Non-Goals
|
||||
|
||||
不增加动态 target string、通用 DI/queue framework、外部 broker、非 UTC schedule、exactly-once 外部副作用、payload 自动迁移或 Job 管理 API/front。真实 PostgreSQL 并发、锁与取消仍需隔离 fixture 验收;feature check 不能替代运行证据。
|
||||
@@ -0,0 +1,5 @@
|
||||
{"file":".trellis/spec/backend/compiler-contracts.md","reason":"Source role, static target, effect and visibility contracts"}
|
||||
{"file":".trellis/spec/backend/database-guidelines.md","reason":"Transaction, connection, migration and cancellation invariants"}
|
||||
{"file":".trellis/spec/backend/toolchain-and-library.md","reason":"Structured concurrency, native runtime and shutdown"}
|
||||
{"file":".trellis/tasks/09-13-typed-orm/design.md","reason":"Existing SQLite/PostgreSQL transaction and pool architecture"}
|
||||
{"file": ".trellis/tasks/09-20-durable-jobs/research/implementation-map.md", "reason": "Verified source ownership and implementation risks"}
|
||||
@@ -0,0 +1,127 @@
|
||||
# Dever 持久化 Job 实施计划
|
||||
|
||||
## Start Gate
|
||||
|
||||
- [x] time-typed-codec 与 port-adapter 完成。
|
||||
- [x] 用户批准父任务规划并允许继续实现。
|
||||
- [x] 读取 structured concurrency、ORM transaction/settings、native profile、application test runner 和 HTTP drain owner。
|
||||
- [x] 完成源码 implementation map 与独立设计复核。
|
||||
- [x] 收紧 ModelId、connection effect、claim attempt、cron cursor、lifecycle 和测试 harness 契约。
|
||||
|
||||
## 1. Source And Static Contract
|
||||
|
||||
- [x] 增加 Job SourceRole、layout、contextual declaration、policy、schedule、formatter 和 Markdown parity。
|
||||
- [x] 增加 Program Job metadata、静态 target identity、App-owned payload schema 和 nominal ModelId wire codec。
|
||||
- [x] 增加 enqueue/enqueue_at 专用检查与 HIR;拒绝普通 call、handler/run/group 转发和非法 role/type/signature。
|
||||
- [x] 将 Job handler 作为独立生成 root,不把 enqueue 视为同步依赖或递归 edge。
|
||||
|
||||
Gate:新增 `durable_jobs` 源码/Markdown测试覆盖合法声明、路径、签名、可见性、payload、policy、cron和target正反例。
|
||||
|
||||
## 2. Connection Effect And Atomic Enqueue
|
||||
|
||||
- [x] 将 Model-only database effect 推广为统一的有类型 connection owner,并集中解析 ConnectionSelector。
|
||||
- [x] enqueue 只传播队列连接 effect,不传播 handler 业务 effect/failure。
|
||||
- [x] native emitter 复用 `_database`、Executor 和 wire codec,实现 standalone short transaction 与 ambient transaction enqueue。
|
||||
- [x] settings-aware bootstrap/test runner 使用统一 connection effect;拒绝解析后的跨连接 transaction。
|
||||
|
||||
Gate:enqueue-only transaction 可生成;Model write + enqueue 同时 commit/rollback;跨连接拒绝;同 transaction 不二次 checkout。
|
||||
|
||||
## 3. Private Store State Machine
|
||||
|
||||
- [x] 实现 SQLite/PostgreSQL runtime private schema、版本校验和初始化锁,不进入 Model history。
|
||||
- [x] 实现 active-key 原子幂等 enqueue、run-at、bounded row/value limits。
|
||||
- [x] 实现 bounded claim、claim-time attempt、随机 token、lease renew/expiry 与 owner CAS。
|
||||
- [x] 实现 success、retry/backoff、dead、blocked 及受控错误分类/摘要。
|
||||
|
||||
Gate:SQLite temp DB 覆盖并发去重、终结后 key 重用、到期、双 claimant、崩溃耗尽、过期重领、旧 token 拒绝和 schema/decode/target blocked;PostgreSQL 验证 feature/SQL 生成。
|
||||
|
||||
## 4. Schedule, Worker And Test Harness
|
||||
|
||||
- [x] 实现共享五字段 UTC cron parser、匹配与 bounded next-slot 搜索(每轮扫描最多 256 个分钟槽,不做无界寻找)。
|
||||
- [x] 实现持久 cursor、同 transaction occurrence materialize/advance、首次启用和每轮最多 256 个补偿窗口。
|
||||
- [x] 实现 bounded worker pool、timeout、heartbeat 和静态 payload dispatch。
|
||||
- [x] 扩展 application test closed graph、case-local clock 和 bounded due-job drain,复用生产状态机。
|
||||
|
||||
Gate:多 scheduler 同 slot、completed occurrence 不重建、cursor rollback/restart、cron fingerprint 冲突和补偿上限;不同 case fake/clock/database 不泄漏且不读取部署配置。
|
||||
|
||||
## 5. Mode And Lifecycle
|
||||
|
||||
- [x] 增加严格 runtime/job settings、source capability 预检和 runtime profile feature/cache identity。
|
||||
- [x] 实现 api/worker/all gating,保留同 binary 的 API/Job dispatch roots。
|
||||
- [x] 增加结构化 SIGTERM/Ctrl-C owner;API stop-accept/drain 与 Job stop-claim/drain 共享 deadline。
|
||||
- [x] generated entry 在应用汇合后统一 database shutdown、log flush,覆盖启动失败和超时取消。
|
||||
|
||||
Gate:缺失/非法配置在 listen/claim 前失败;三 mode 只启动应有服务;test-owned 进程验证信号后停止新工作、drain/超时取消和关闭顺序。
|
||||
|
||||
## 6. Documentation And Focused Verification
|
||||
|
||||
- [x] 更新 LANGUAGE、MARKDOWN-SYNTAX、compiler/database/toolchain 中的 application-testing 契约。
|
||||
- [x] 检查无环境变量入口、动态 target、第二套 codec/transaction/pool、脱管 task 或生产 Adapter 测试回退。
|
||||
- [x] 完成独立 Trellis check,修复 in-scope findings。
|
||||
- [x] 主代理最终复核并更新父任务 checklist。
|
||||
|
||||
```bash
|
||||
cargo check --locked --offline -p dever-core -p dever-runtime -p dever-cli
|
||||
cargo check --locked --offline -p dever-runtime --features sqlite,postgres
|
||||
cargo test --locked --offline -p dever-tests --test durable_jobs
|
||||
cargo test --locked --offline -p dever-tests --test source_architecture
|
||||
cargo test --locked --offline -p dever-tests --test error_effects
|
||||
cargo test --locked --offline -p dever-tests --test orm_query
|
||||
cargo test --locked --offline -p dever-tests --test sqlite_orm
|
||||
cargo test --locked --offline -p dever-tests --test application_config
|
||||
cargo test --locked --offline -p dever-tests --test application_testing
|
||||
cargo test --locked --offline -p dever-tests --test structured_concurrency
|
||||
cargo test --locked --offline -p dever-tests --test pooled_network graceful_shutdown
|
||||
git diff --check
|
||||
```
|
||||
|
||||
所有 Cargo 命令使用无增量、debug info 关闭和串行测试。默认不运行 workspace 全量测试、真实性能套件或持久服务。真实 PostgreSQL 仅在仓库隔离 `config/setting.json` fixture 明确存在且用户知情时运行;否则明确记录为未运行。
|
||||
|
||||
## 实施记录
|
||||
|
||||
- 源码、共享 wire/connection owner、生产 store/worker、native/test roots、mode/lifecycle 已接通;以上勾选表示实现完成,最终 Gate 证据记录在下方,不代替独立 Trellis check。
|
||||
- 启用既有 Tokio `signal` feature;经主代理明确授权解析依赖,Cargo.lock 仅新增 `errno 0.3.14` 与 `signal-hook-registry 1.4.8` 两个传递项。后续命令均 `--locked --offline`。
|
||||
- API-only 保留既有省略 runtime 配置的兼容行为(api、30000 ms);有 worker capability 的应用必须显式配置 runtime。worker pool 比执行槽至少多一个连接,以免 handler 事务耗尽 heartbeat/claim 容量。
|
||||
- enqueue 使用单条 active-key upsert + RETURNING,避免 PostgreSQL 上冲突 insert 后、查询 Id 前任务终结的竞态。
|
||||
- 新增测试发现 case 裁剪不可达 transaction 后被误报空事务;修复共同 effects validator,真实空事务仍有 clause 并继续被拒绝。
|
||||
- 数据库服务、真实 PostgreSQL、全量测试、性能/内存压测未运行;rustfmt/clippy 未安装,不安装。
|
||||
|
||||
### 最终实施验证
|
||||
|
||||
- `cargo check --locked --offline -p dever-core -p dever-runtime -p dever-cli`:通过。
|
||||
- 同一 check 加 `--features dever-runtime/sqlite,dever-runtime/postgres,dever-runtime/api,dever-core/reference`:通过。PostgreSQL 仅 feature 编译证据,未连接真实数据库,不把它算作锁、并发或 SQL 执行验收。
|
||||
- `durable_jobs`(sqlite):14/14。覆盖源码/Markdown、跨连接拒绝、nominal ModelId native 往返、同物理事务 commit/rollback、active-key 并发幂等、定时到期、claim-time attempt、旧 token CAS、崩溃耗尽、schema/corrupt/unknown 的 generated dispatch blocked、多 scheduler、完成窗口不重建、cursor 中途失败回滚/重启、256 窗口上限、case-local SQLite/clock/fake 与无数据库 empty drain。
|
||||
- 同一 `durable_jobs` 目标包含真实 native 二进制的 api/worker/all 三 mode:临时回环监听和临时 SQLite;api 不 claim、worker 不监听、all 同时工作,均 SIGTERM 正常退出。独立 runtime 子进程另覆盖 heartbeat、停止 claim、在途排空与 shutdown deadline 取消后关闭数据库/flush 日志。
|
||||
- `application_config`:3/3;`application_testing`:8/8;`source_architecture`:8/8。
|
||||
- `pooled_network graceful_shutdown`:1/1,验证已有 HTTP 停止 accept 并完成在途响应。
|
||||
- `time_codec wire_schemas_do_not_change_model_time_storage_identity`:1/1。该条旧断言随 nominal ModelId wire 合同更新;无 private/bounded 字段的 Model record 也可通过共享 codec,存储 snapshot 不变。该 fixture 已调整为当前 App 路径,未顺带改造其余旧测试。
|
||||
- 上述测试均串行;`CARGO_INCREMENTAL=0`,dev/test debug info 为 0,固定 stable toolchain PATH。`git diff --check` 通过;新 Job/lifecycle 源码检查未发现环境变量读取、动态 target、脱管 spawn 或 unsafe。
|
||||
- 独立 Trellis check 结果见下方;父任务 checklist 由主代理收口,任务仍保持 `in_progress`。
|
||||
|
||||
## 独立检查与修复
|
||||
|
||||
- 复现 SQLite handler 持写事务时短租约无法续租:业务已提交,但任务仍为 running。当前源码的启动预检要求 SQLite `lease_ms > timeout_ms`;PostgreSQL 继续支持短租约续租。领取在获得锁后建立 lease,续租/完成先锁 claim 行再读注入 Clock,避免使用锁等待前的旧时间。
|
||||
- worker 在调用业务前重新验证持久化记录的实际 timeout/max attempts;旧策略不满足当前 SQLite lease 或已越界时进入 `blocked/invalid_policy`,不执行 App。公共 timeout 上限仍为 3,600,000 ms,lease 上限为 7,200,000 ms;测试使用最大 lease,合法长任务不会因测试固定短租约被误拒绝。
|
||||
- 耗尽 attempts 的到期记录改为同一 claim 事务内独立有界清理;eligible 查询跳过耗尽记录,避免它占据执行槽并使 drain 误判队列为空。
|
||||
- 信号测试原来对同一 run_at 的任务假定按业务 key 执行,但实际稳定排序使用 run_at/UUID;同毫秒 UUID 带随机部分。改用不同到期时间表达本测试的执行次序,同时保留实际 heartbeat、持事务 drain 和超时取消检查。
|
||||
- 保持既有已审阅的 API-only 默认配置契约;有 worker capability 时 runtime 必填。统一 cron 文档为每轮扫描/推进最多 256 个分钟槽,只有匹配 cron 的槽生成任务。
|
||||
- 同步 LANGUAGE、design、compiler 目录 owner 图以及 database/toolchain 规范;未增加环境变量、第二套 pool/transaction/codec、动态 target 或脱管任务。
|
||||
|
||||
### 独立验证证据
|
||||
|
||||
- 最终 `cargo check --locked --offline -p dever-core -p dever-runtime -p dever-cli --features dever-runtime/sqlite,dever-runtime/postgres,dever-runtime/api,dever-core/reference`:通过。
|
||||
- 最终 `cargo test --locked --offline -p dever-tests --features sqlite --test durable_jobs -- --test-threads=1`:14/14,通过,122.86 秒。包含最大合法 timeout 的 case-local SQLite/fake、claim/renew/complete 锁等待时钟回归、exhausted 后正常任务 drain、旧持久策略 blocked、三 mode 和 SIGTERM 的 grace/timeout/持事务三场景。
|
||||
- 主代理最终复核再次运行 `sqlite_durable_state_machine`:1/1 通过;core/runtime/CLI 的 SQLite+PostgreSQL+API+reference feature check 再次通过。
|
||||
- Cargo 命令统一固定 stable PATH、`CARGO_INCREMENTAL=0`、dev/test debug info 为 0。`git diff --check` 和改动 Job 文件尾空白检查通过。
|
||||
- 一次中间 suite 为 13/14:信号 fixture 的同到期时间随机顺序断言失败;已按上述原因修复,最终完整定向 target 14/14。
|
||||
- rustfmt/clippy 二进制未安装,未安装或运行;全量测试、真实 PostgreSQL、性能/内存压测未运行。PostgreSQL 证据仅 feature 编译与代码审查,不宣称已完成真实锁/并发/SQL 执行验收。
|
||||
- 本次独立检查没有剩余已知实现阻塞;任务状态按调度要求保持 `in_progress`,交由主代理最终复核。
|
||||
|
||||
## 待复核风险
|
||||
|
||||
- 业务 commit 与 Job insert 必须共享同一 physical transaction,不能 after-commit 补写。
|
||||
- attempt 必须在 claim 时增加;finish/renew/retry 必须比较独立 claim token 和有效 lease。
|
||||
- cron materialization 必须由 cursor transaction 保证,不能复用 active-only key 或单进程内存 timer。
|
||||
- enqueue 不得引入 handler 同步 effect/递归;worker/test root 必须包含 handler 的真实 Model/Port/fake 闭包。
|
||||
- runtime mode 必须在副作用前统一预检;signal drain 不能调用立即 abort 的 Group::stop 伪装优雅退出。
|
||||
- payload/schema identity 是持久协议;未知版本必须 blocked/fail closed,不能 best-effort decode。
|
||||
@@ -0,0 +1,50 @@
|
||||
# Dever 持久化 Job
|
||||
|
||||
## Goal
|
||||
|
||||
为 Dever 提供数据库持久化、事务一致、可恢复且有界的后台任务和 UTC 定时任务,使 API 与 worker 可使用同一编译产物独立或组合部署,禁止用脱离结构化作用域的异步任务代替可靠 Job。
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `time-typed-codec`:DateTime、payload codec 和 schema fingerprint。
|
||||
- `port-adapter`:Job 调用的 App 可以通过 Port 执行外部副作用。
|
||||
- 复用既有 SQLite/PostgreSQL ORM transaction、pool、settings 和 shutdown。
|
||||
|
||||
## Requirements
|
||||
|
||||
- 新增 Job source role;允许 `job.dever` 或平铺 `job/*.dever`,角色目录遵守现有 file/directory exclusivity 和 no-single-topic 规则。
|
||||
- Job entry 使用明确 contextual declaration,零输入或一个 App-owned wire-safe record 输入、零输出,只能调用同领域 App,不能直接访问 Domain/Model/Port/Adapter/API;Job payload 支持保留 Model identity 的 ModelId。
|
||||
- Job entry 不可被普通 source call;App 只能通过 compiler-owned enqueue/enqueue_at target reference 创建持久化任务。
|
||||
- 每个 enqueue 必须提供业务 idempotency key;同 Job identity + key 的未终结任务不重复插入。
|
||||
- Job 声明绑定一个逻辑 database connection,并声明有界 timeout/max attempts;`retry(n)` 表示包含首次执行在内最多执行 n 次,不允许零/负/越界值。
|
||||
- 立即和 run-at Job 持久化 target identity、payload、schema fingerprint、key、run_at、attempt/state、lease 和错误摘要。
|
||||
- 周期 Job 使用编译期验证的五字段 UTC cron declaration且只能绑定零输入Job;持久 cursor、occurrence insert 和推进在同一 transaction,每个计划窗口生成确定性key,多进程及已完成任务不能重复创建同一occurrence。带payload的业务计划使用enqueue_at。
|
||||
- transaction App 内 enqueue复用隐藏 transaction context;Model effect与Job connection不同则编译失败。非 transaction enqueue使用自身短事务。
|
||||
- SQLite/PostgreSQL使用runtime private versioned schema;初始化、claim、lease renew/expiry、success、retry、dead-letter均有明确事务边界。
|
||||
- worker采用至少一次执行;attempt 在 claim 时原子增加,每次 claim 使用独立 fencing token;失败按有界指数退避重试,达到上限进入 dead-letter,不无限循环或静默丢弃。
|
||||
- payload schema不兼容、未知 Job target和解码失败进入dead-letter/blocked状态并记录脱敏原因,不best-effort执行。
|
||||
- `dever.job.serve()`显式启动worker。combined main使用现有Group并发启动API/Job;不增加隐式后台线程。
|
||||
- `config/setting.json`提供严格 `runtime.mode=api|worker|all` 和 job worker/lease/poll/shutdown上限;不接受环境变量或CLI覆盖。
|
||||
- SIGTERM/Ctrl-C停止API accept和Job claim,排空在途工作到deadline,再停止、释放lease/连接并执行数据库/log shutdown。
|
||||
- 应用测试的Job数据库由runner生成隔离SQLite;不读取部署setting或自动连接PostgreSQL。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] Job合法路径/签名可检查和格式化;ModelId payload 保持 nominal identity;普通调用、非法输出/类型、跨域或直接Model/Port访问被拒绝。
|
||||
- [x] 业务Model写入与Job入队在同一transaction一起commit/rollback;跨连接组合在编译期拒绝。
|
||||
- [x] 相同identity+key并发入队只保留一个可执行任务,不丢失已提交业务状态。
|
||||
- [x] 到期前不执行,UTC run-at到期执行;周期任务在多个worker下每个window最多创建一次。
|
||||
- [x] worker崩溃/取消会消耗 attempt,lease 到期可由另一 worker 恢复;活跃 lease 不被重复 claim,旧 fencing token 不能提交迟到结果。
|
||||
- [x] handler失败按策略重试,成功终结,达到attempt上限进入dead-letter;错误信息不含Secret/payload全文。
|
||||
- [x] schema mismatch、unknown target、corrupt payload不会执行App并保留可诊断记录。
|
||||
- [x] api/worker/all三种mode分别只启动应有服务;缺少相应配置在监听/claim前失败。
|
||||
- [x] SIGTERM/Ctrl-C定向runtime test验证停止新工作、排空或超时取消以及最终数据库/log关闭顺序。
|
||||
- [x] SQLite端到端通过;PostgreSQL feature编译通过,真实行为只在隔离 `postgres_test` 明确存在时运行。
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Exactly-once外部副作用、分布式事务和跨数据库原子提交。
|
||||
- Redis/Kafka/RabbitMQ/SQS和第三方队列。
|
||||
- 非UTC cron、DST和动态用户时区调度。
|
||||
- Job管理front;dead-letter运维界面后续由业务API/front实现。
|
||||
- 无界任务fan-out、优先级抢占和动态代码任务。
|
||||
@@ -0,0 +1,109 @@
|
||||
# Research: Durable Job implementation map
|
||||
|
||||
- Query: 核实持久化 Job 的源码接入点、最小复用架构、SQL/生命周期风险和定向验收。
|
||||
- Scope: mixed;以当前本地源码为准,外部资料仅核对 SQL/信号基础能力。
|
||||
- Date: 2026-09-20
|
||||
|
||||
## Findings
|
||||
|
||||
### 当前事实和实施前须解决的契约矛盾
|
||||
|
||||
1. 当前没有 Job source role、Job runtime、cron parser 或 SIGTERM/Ctrl-C owner。`rg signal|SIGTERM|ctrl_c|cron|Job crates library` 仅发现 `core/wire.rs` 的 `Policy::Job`。不可将设计中的这些能力当作已有实现。
|
||||
2. `wire::Policy::Job` 已存在,拒绝 Secret/private Model;现有 wire 同样拒绝 ModelId。设计示例 `PublishInput.article_id: content.article.model.id` 因而不成立。需要明确选择:扩展共享 wire owner 支持保持 nominal 身份的 ModelId,或修改示例使用当前 wire-safe DTO;不得只给 Job 私加类型白名单。考虑 Job 最常见用途正是实体异步操作,建议在共享 schema/codegen 增加 ModelId 节点,协议用字符串,fingerprint 保留所属 Model 身份,同时明确适用 policy。当前 `native/wire.rs:32` 的普通 `Id` 已映射 runtime `Id`,不可把 ModelId 在类型系统里降级成普通 Text/Id。
|
||||
3. `hir.rs:21`、`contracts/effects.rs:18` 的 database effects 是 Model ID 集合;`native.rs:436` 的 transaction 取第一个 Model 找连接。纯 Job enqueue 无 Model 时无法运行。必须推广为连接资源 effect,不能伪造 Model 或复制第二套 transaction context。
|
||||
4. 现有跨连接检查在 `model.rs:256` 的 `validate_database_settings`,依赖设置解析后连接名,而非仅 AST check。明确显式不同连接可在 check 拒绝;PackageRoot fallback 仍需要 settings-aware CLI 检查/启动校验。不要错误拒绝不同 selector 最终落在同一个 default 的合法情形。
|
||||
5. `retry(5)` 的含义必须固定为最多 5 次 attempt,还是首次之外 5 次重试。PRD 用 max attempts,建议前者并准确写明;不能只在失败后加 attempt,否则每次执行中崩溃会无限重领。
|
||||
6. 设计只规定 UTC cron,未规定字段语法、DOM/DOW 组合、停机补跑和首次部署起点。最小实现应固定五字段数字语法、范围/列表/步长和 DOM/DOW 规则;持久 checkpoint、每轮有界补齐。拒绝不能满足的日期组合或规定有界搜索失败,不能无穷找下个时间。
|
||||
|
||||
### Owner 文件与复用入口
|
||||
|
||||
| 文件/符号 | 当前职责与 Job 接入 |
|
||||
| --- | --- |
|
||||
| `crates/dever-core/src/source.rs:25` SourceRole/from_name/name | 当前 App/Domain/Model/Port/Adapter/Api;增加 Job,沿用 path-derived identity。 |
|
||||
| `check/layout.rs:14,56` RoleFiles | 已有 role file/directory 排他、非 API 平铺、目录至少两文件;增加枚举后复用,不另写 Job scanner。 |
|
||||
| `syntax.rs:169,184` FunctionClause/FunctionKind | 已有 ordinary/transaction、fails/bodyless;Job kind 和 bounded policy 作为声明元信息。schedule 用独立声明。 |
|
||||
| `parser.rs:210,707,761` declaration/function/function_contracts | contextual transaction 的 dispatch 可复用;增加 job/retry/timeout/schedule;勿让 retry/timeout 成普通函数调用。 |
|
||||
| `format.rs:120,258` declaration/function | 共享 AST formatter,输出 job 前缀和稳定 policy 顺序。 |
|
||||
| `markdown/contract/parse.rs:198,237,261` / `validate.rs:247` | function 元信息、非 function directive 标识已有 owner;schedule 需加入 directive 分支;Markdown policy 元信息应与 source 一致。 |
|
||||
| `check.rs:321,423,438` Program/test registration | 注册 Job metadata 和 target map;test case closed graph 与 fakes 已完成,必须沿用。 |
|
||||
| `check/symbols.rs:222` can_call / `:277` can_access_type | API 不可调用的现有模式适用于 Job;普通 function resolution 和 handler resolution 都必须拒绝 Job。Job 仅调用同域 App,DTO 类型访问另定规则。 |
|
||||
| `check/expressions.rs:521,620,655` static call/handler reference | 可复用 named-path 解析形态,但 enqueue target 不是 Handler 参数,不能转发。新增专用 checked enqueue path,参数先解 target 再按 payload schema 检查。 |
|
||||
| `hir.rs:164,507,524` ExpressionKind/static_call/CallArgument | 用 JobEnqueue {target,payload,key,run_at} 明确表达入队;遍历/变更遍历要覆盖三个值表达式。enqueue 不等价同步 call。 |
|
||||
| `contracts/dependencies.rs`, `contracts/effects.rs:61,505`, `contracts/error_effects.rs` | enqueue 引入本 Job 连接、database failures 和 suspension;不得传播 Job handler 的业务失败/网络 effect 到 enqueue 的 App。 |
|
||||
| `specialize.rs:77,120,178` database_effects/concrete/successors | 独立执行根收集 Job handlers;不要把 enqueue->handler 当普通递归 edge,App 入队自己未来工作的循环是正常队列行为。 |
|
||||
| `native.rs:292,418,434,569` DB setup/function/call | 统一隐藏 `_database: Option<&Transaction>`;普通 enqueue 继承,Job handler 执行从 None 开始。 |
|
||||
| `native/orm.rs` | 现有 Executor/参数/错误转换供 Job emitter 复用;不复制数据库错误到字符串再猜类别。 |
|
||||
| `native/api.rs`, `native/ports.rs` | 静态 dispatcher 和 startup initialization 可参考;Job 生成 concrete decoder + direct function invocation,不造 runtime Value 解释器。 |
|
||||
| `native/build.rs:120,309,349` | suite profile、runtime features、缓存身份;Job 无 Model 仍需 database+wire+已选驱动。feature/cache 与源码变化同步。 |
|
||||
| `crates/dever-runtime/src/database.rs:167,317,458` | Database/begin、Executor::new、database_for;所有 Job SQL 通过既有 Sql 双方言和 Value/Row 编解码。 |
|
||||
| `sqlite.rs:174,223,279` | BEGIN IMMEDIATE、transaction 方法、未确认完成 Drop 丢弃 pool object。可直接用于 claim 短事务。 |
|
||||
| `postgres.rs:236,302,383` | BEGIN、带 I/O deadline 的事务、Drop 丢弃连接;claim 使用 SKIP LOCKED。 |
|
||||
| `sqlite/migration.rs`, `postgres/migration.rs:73,109` | Model 自己的 schema/history owner;Job 私有 schema 不进入它们,但复用事务/advisory lock 纪律。 |
|
||||
| `config.rs:11,70,144,325,356` | 单一 Settings,严格 top-level/duplicate wire 验证,bootstrap/http lazy read。扩展 typed runtime/job config。 |
|
||||
| `task.rs:78,488`, `task/scoped.rs` | 单 runtime、Scope supervisor、Group;worker/heartbeat/signal 等所有活动必须有结构化 owner。 |
|
||||
| `api.rs:13`, `http/mod.rs:147,176`, `net.rs:47,232` | API 内创建 listener;HTTP 已响应 listener.close 并 graceful drain。新增 signal owner 触发此路径,不用取消 serve 代替优雅关闭。 |
|
||||
| `database.rs:496,502`, `postgres.rs:462,473` | prepare/shutdown 当前只负责 PostgreSQL;SQLite 没有显式 pool shutdown 路径。若 PRD 保证最终关闭 SQLite,需补 owner。 |
|
||||
| `core/wire.rs:10,55,64`, `native/wire.rs:8` | Job policy、稳定版本 schema identity、concrete codec;当前 fingerprint 是确定性描述串,不是固定长度加密 hash。持久字段可存完整 identity,不误称 SHA。 |
|
||||
| `cli/test_runner.rs:73,126` / `check.rs:466` | 每 case 新进程/临时目录,runner 生成 SQLite setting;无 DB case 无 setting。加入 Job connection、harness,仍不读部署配置。 |
|
||||
|
||||
### 建议最小架构和数据流
|
||||
|
||||
保留 Job body 为 Function/HIR,额外 Program.jobs 持有 identity/function/connection/payload schema/policy/schedule。compiler 专用 enqueue 表达式只保存静态 target ID 和普通参数;不将 target 暴露成值类型。Job entry 的 ordinary/handler 调用拒绝统一放符号访问 owner。
|
||||
|
||||
数据库 effect 推荐抽为统一 `DatabaseBinding`(selector + fallback root 的稳定值或 intern ID),Model 与 Job 都引用。Model 的 CRUD schema/index 仍保持 Model ID,仅 effects/transaction/startup/test 配置改用连接 binding。为最小改动也可用明确的资源枚举 Model/Job 并统一 `binding()` accessor,但不应让消费者每处重复 match。
|
||||
|
||||
源码 Job → checked metadata/payload Schema → enqueue HIR → 编码 Encoded JSON → 同一 Executor 写入 → commit → worker claim → 校验 target/schema → concrete decode → direct App 调用 → owner-CAS finish/retry。enqueue 的调用图不包含 handler 业务 effect;worker 启动根和 test harness 根要独立包含 handler/App/Port fake graph,避免漏链接及错误递归判定。
|
||||
|
||||
runtime 可由 `job.rs` 拥有 public compiler bridge,`job/store.rs` 拥有 DB 状态机和 SQL,`job/schedule.rs` 拥有 cron/slot,`job/worker.rs` 拥有有界执行;只有职责确实需要时拆这些文件,不添加通用 queue framework。compile-time cron 验证可调用纯 runtime cron parser,避免编译期/运行时双实现。chrono std-only 已存在,复用 UTC calendar;无须引入时区库。
|
||||
|
||||
### SQL/租约策略
|
||||
|
||||
- runtime private version 表 + job 表 + schedule checkpoint 表。SQLite BEGIN IMMEDIATE,PG 独立 runtime namespace advisory transaction lock;version 不认识时明确失败,不能 CREATE IF NOT EXISTS 后假装兼容。
|
||||
- enqueue 的 active-only 唯一索引覆盖 `(target,dedupe_key)` 且 predicate 只含待执行/执行中状态;参数化 INSERT ON CONFLICT DO NOTHING,再返回已有 active ID。不能用 SELECT-before-INSERT 作为唯一保障。业务失败 rollback 必须撤销同物理事务的入队。
|
||||
- PostgreSQL claim:短事务选取到期 pending 或过期 running,`FOR UPDATE SKIP LOCKED LIMIT ...`,写 owner、lease_until、attempt 后提交。SQLite 同过程已有 BEGIN IMMEDIATE 序列化。只 claim 有空闲执行容量的条目,不能先批量占 lease 再在无界内存队列等候。
|
||||
- attempts 建议在成功 claim 时递增;超时/失败/崩溃恢复都耗费次数。过期且 attempts 达上限的条目需进入 dead-letter,不能永远排除于查询而残留 running。
|
||||
- owner token 每次 claim 新生成,不能仅以进程 worker ID 作为 fence;旧执行在同 worker 中过期重领后仍可能迟到。finish/renew/retry 带 id + token + state,必要时判断 lease_until;影响行数为 0 即丢失租约,不可继续确认成功。复用现有 OS randomness/Uuid owner,不硬编码 ID。
|
||||
- renewal 不与 handler 持有同一 transaction;需预算 worker 数与 pool 容量,避免单连接长事务使 heartbeat 饿死。取消后复用当前丢弃未知连接的规则。若 handler 无法强制立即停止,最多只能保证至少一次,外部副作用依赖业务 idempotency。
|
||||
- PG runtime Int bind 是 i64,SQL 数值列和参数使用 BIGINT 语境;时间全部 epoch ms,勿调用 SQL now 导致测试时钟不一致。错误摘要写固定错误类别/有界安全信息,不保存 AppError Display(业务 message 可含 payload)。
|
||||
- cron occurrence 幂等不能只用 active-only job 索引:已经成功的 occurrence 可能被另一 scheduler 再建。锁定 checkpoint 行,插入 jobs 与推进 last slot 同事务;或者永久唯一 occurrence ledger。checkpoint 必须按规范化 cron identity,计划变更不能回放全部历史。每轮扫描/补跑有界。
|
||||
- key/payload/target/schema/error 等都有确定上限;wire 16 MiB 默认上限已经有界,但 worker 并发乘以该值仍需评估,不宣称低内存性能。
|
||||
|
||||
### Mode、启动与关闭
|
||||
|
||||
当前 RuntimeProfile 只有 sqlite/postgres;Tokio RuntimeConfig 只有线程/任务容量,均没有 api/worker/all。严格 runtime/job 文档必须进入 Settings 的真实解析,不能仅在 serve 里临时读 JSON。主程序调用 serve 的能力图用于校验 mode:worker-only 不要求 HTTP;API-only 不 claim;all 按 main 的 Group 并行拥有两个 server。启动前校验对应模式的缺失配置及所有启用 Job timeout/lease 关系,不能 API 已监听后才发现 Job 不可用。
|
||||
|
||||
API runtime::serve 需在创建 listener 后把关闭与 drain 接到同一 shutdown token。signal watcher 是 generated entry/运行时生命周期拥有的 future,不可 detached spawn;SIGINT/SIGTERM 一次广播,停止 accept/claim/schedule 后等待在途工作到统一 deadline。deadline 到达使用现有 Task/Group cancel-and-drain,再执行 database shutdown 和 log flush。`Group::stop` 当前直接 abort body,正常收到 signal 时先合作排空,超时才 stop。
|
||||
|
||||
Tokio 1.53.1 当前 features 不含 signal(根 Cargo.toml:20),Cargo.lock 未找到 signal-hook。需启用 Tokio signal feature 并确认 offline 所需依赖已缓存;不要用 unsafe libc handler(workspace 禁 unsafe)或 shell signal 轮询做替代。PG driver shutdown 当前存在;SQLite pool explicit close 和 startup failure 清理须单独覆盖。generated entry 当前仅 profile.postgres 时调用 shutdown,需按完成的生命周期设计调整。
|
||||
|
||||
### Application test 隔离
|
||||
|
||||
保持每 case 单独 native module、closed fake graph、进程和临时目录。test metadata 收集 enqueue 与 harness 执行的 Job 连接以及 handler 访问的 Model;不能只看 test 主函数的直接 Model effects。每 case SQLite default/explicit connections 由 runner 生成。Job-only case 也生成 setting;非数据库 case 仍无 setting。
|
||||
|
||||
设计还没有给 compiler-owned test harness 的源码调用协议,不能只实现 Rust store 单元测试便声称应用测试可用。建议最小 test-only drain-due/advance-clock 操作,返回有界观察结果或保证已处理 due work,测试通过 App 查业务状态;不开放 DB handle 或普通直接 Job entry 调用。假时钟作为 case-owned Clock 注入 store/scheduler,不改全局系统时间、不真实 sleep。
|
||||
|
||||
### 定向验收建议
|
||||
|
||||
1. 新 `durable_jobs` 源码/formatter/Markdown 定向 target:合法声明、错误 role/直接 call/handler reference、0/1 record 参数、Secret/private/非法 payload、policy 上下界、cron 非法、纯 Job transaction 与跨连接。
|
||||
2. runtime SQLite temp DB:业务写入+入队 commit/rollback;并发 active key 去重;终结后 key 重用;run_at;多 claimant;租约过期、旧 token finish/renew 拒绝、崩溃耗尽 attempts;有界退避/溢出;损坏/schema/unknown target 不执行。
|
||||
3. cron 两个独立 scheduler 同 slot、已完成 job 后再次调度、checkpoint rollback/restart、停机补跑上限、闰年/月底/DOM-DOW 规则。
|
||||
4. `application_testing` 精确 case:同二进制不同 case fake 完全隔离;Job-only SQLite setting;部署 PostgreSQL 配置未读取;受控时钟/harness 触达 App。
|
||||
5. config 三 mode 缺失/未知/重复字段、timeout/lease/poll/shutdown 约束。测试专有子进程 signal,明确 deadline;仅所属随机回环 listener(若批准)验证停止新请求和 HTTP drain;不向已有服务发送信号。
|
||||
6. `structured_concurrency`/`pooled_network graceful_shutdown` 受影响 case;SQLite one-connection cancellation 后可恢复;PG 仅 feature cargo check 与 SQL 生成断言,真实 DB 另行授权条件。
|
||||
|
||||
## External references
|
||||
|
||||
- PostgreSQL SELECT locking 官方说明:`SKIP LOCKED` 适合多消费者 queue 场景,但仅跳过 row lock,仍有表级锁:https://www.postgresql.org/docs/10/sql-select.html 。这是 SQL 策略依据,不是对本仓库 PostgreSQL 实测。
|
||||
- SQLite 官方 partial index 文档:https://www2.sqlite.org/partialindex.html ,UNIQUE 可只约束满足 predicate 的行。
|
||||
- Tokio signal 官方 Rustdoc:https://docs.rs/tokio/latest/tokio/signal/unix/fn.signal.html ,Unix signal API 要求 signal feature。仓库实际 pin 1.53.1,应以本地锁定版编译验证依赖。
|
||||
- 本地版本:Tokio 1.53.1、chrono 0.4.45 std-only;SQLite/Postgres pool 沿现有锁定依赖,不建议增加队列或 cron 框架。
|
||||
|
||||
## Related specs
|
||||
|
||||
已查 `.trellis/workflow.md`、任务 prd/design/implement/task.json,以及 backend `index.md`、`directory-structure.md`、`error-handling.md`、`quality-guidelines.md`、`compiler-contracts.md`、`database-guidelines.md`、`toolchain-and-library.md`、`logging-guidelines.md`。Job 新约束尤其会改变 compiler-contracts 的 Model-only effect 描述及 test runner 数据库触发条件。
|
||||
|
||||
## Caveats / Not Found
|
||||
|
||||
- 子 agent `task.py current --source` 返回 none;父 dispatch 明确提供本任务绝对路径,本研究仅写此 research/,不改 active state。
|
||||
- 未改源码、spec、任务状态;未运行 build、测试、服务、外部数据库或性能测量。
|
||||
- 当前研究证明复用边界和缺失点,不证明 Job 可执行。policy、cron、payload ModelId 和 test harness 上述未落实契约必须在实施/验收中明确,不能悄悄删需求。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "durable-jobs",
|
||||
"name": "durable-jobs",
|
||||
"title": "Dever 持久化 Job",
|
||||
"description": "实现事务内原子入队、延迟和周期任务、重试租约与 API/worker/all 运行模式",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P1",
|
||||
"creator": "Antigravity",
|
||||
"assignee": "Antigravity",
|
||||
"createdAt": "2026-09-20",
|
||||
"completedAt": "2026-09-20",
|
||||
"branch": "main",
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "09-20-production-application-language",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in new issue
Block a user