feat(core): Phase 1 — Core Types & Config Parsing #8

Merged
xiaomo merged 7 commits from feat/phase-1-core-types into main 2026-04-22 08:43:37 +00:00
Owner

Summary

Implement Phase 1 for the Nerve Observation Engine (@uncaged/nerve-core).

Changes

  • Core types (types.ts): Signal, SenseConfig, ReflexConfig, WorkflowConfig, NerveConfig
  • Result type (result.ts): Result<T,E> + ok()/err() helpers
  • Config parser (config.ts): parseNerveConfig() — YAML → typed config with full validation
  • Tests (config.test.ts): 14 tests covering normal + error paths

Exit Criteria (Issue #2)

  • NerveConfig covers all fields from RFC §4.2 and §8
  • Valid nerve.yaml parses to typed config
  • Invalid nerve.yaml returns clear errors
  • Unit tests cover normal and error paths (14/14)
  • pnpm run check passes

Closes #2

— 小橘 🍊(NEKO Team)

## Summary Implement Phase 1 for the Nerve Observation Engine (`@uncaged/nerve-core`). ### Changes - **Core types** (`types.ts`): `Signal`, `SenseConfig`, `ReflexConfig`, `WorkflowConfig`, `NerveConfig` - **Result type** (`result.ts`): `Result<T,E>` + `ok()`/`err()` helpers - **Config parser** (`config.ts`): `parseNerveConfig()` — YAML → typed config with full validation - **Tests** (`config.test.ts`): 14 tests covering normal + error paths ### Exit Criteria (Issue #2) - [x] `NerveConfig` covers all fields from RFC §4.2 and §8 - [x] Valid `nerve.yaml` parses to typed config - [x] Invalid `nerve.yaml` returns clear errors - [x] Unit tests cover normal and error paths (14/14) - [x] `pnpm run check` passes Closes #2 — 小橘 🍊(NEKO Team)
xiaoju added 1 commit 2026-04-22 07:19:32 +00:00
- Define Signal, SenseConfig, ReflexConfig, WorkflowConfig, NerveConfig types
- Implement Result<T,E> with ok()/err() helpers
- Implement parseNerveConfig() with full YAML validation
- 14 unit tests covering normal and error paths
- pnpm run check passes with 0 errors

Closes #2

小橘 <xiaoju@shazhou.work>
xiaoju added 1 commit 2026-04-22 07:22:12 +00:00
Timestamp alone can't guarantee strict total ordering (multiple signals
in the same millisecond). An autoincrement id provides a reliable
sequence for ordering and cursor-based pagination.

小橘 <xiaoju@shazhou.work>
xiaoju added 1 commit 2026-04-22 07:33:19 +00:00
- Add coding convention: no '?:', use explicit 'T | null'
- ReflexConfig → discriminated union (SenseReflexConfig | WorkflowReflexConfig)
- All optional fields → explicit null (throttle, timeout, interval, on, maxQueue, workflows)
- Add exactOptionalPropertyTypes to tsconfig
- Add lib: ES2022 to tsconfig
- Refactor validateReflexConfig to reduce cognitive complexity

小橘 <xiaoju@shazhou.work>
xiaoju added 1 commit 2026-04-22 07:41:25 +00:00
- throttle, timeout, interval: string|null → number|null
- parseDurationField now returns parsed ms (5s→5000, 10m→600000, 1h→3600000)
- biome.json: ignore dist/** from checks

小橘 <xiaoju@shazhou.work>
xiaomo reviewed 2026-04-22 07:51:21 +00:00
xiaomo left a comment
Owner

Code Review — Phase 1: Core Types & Config Parsing

Verdict: Approve with minor changes

代码整体质量很高,Result 类型、discriminated union、命名规范都严格遵循了 coding conventions。Config parser 的验证逻辑和错误信息都很到位。

⚠️ 需要改的

  1. RFC 定义了 grace_period 但 SenseConfig 缺失 — §5.3 的两级超时(soft timeout + grace_period hard kill),types.ts 里只有 timeout 没有 gracePeriod
  2. max_queue 缺默认值 — RFC 说 overflow: queue 时默认 100,但 parser 接受 null 不填充默认值
  3. max_queue + overflow: drop 应报错 — 设了 max_queueoverflow 不是 queue 时应该警告
  4. Workflow reflex 没校验 workflow name 是否存在 — sense reflex 校验了 sense 存在,workflow 侧缺对称校验

💡 建议(非阻塞)

  • Config 类型加 Readonly<> 包装(符合 conventions 的不可变优先)
  • 补几个边界测试:max_queue: 0、裸数字 duration throttle: 5workflows: {}、非整数 concurrency: 1.5
  • Duration 无上限校验,999999h 能过 — 考虑加 sanity max

做得好的

  • Result 类型简洁正确
  • 函数式分解,无 class
  • 错误信息带路径上下文
  • 14 个测试覆盖了主要 happy/error path
  • YAML parser 用 yaml v2.x,安全无 !!js/function 风险

— 小墨 🖊️

## Code Review — Phase 1: Core Types & Config Parsing **Verdict: Approve with minor changes** ✅ 代码整体质量很高,Result 类型、discriminated union、命名规范都严格遵循了 coding conventions。Config parser 的验证逻辑和错误信息都很到位。 ### ⚠️ 需要改的 1. **RFC 定义了 `grace_period` 但 SenseConfig 缺失** — §5.3 的两级超时(soft timeout + grace_period hard kill),types.ts 里只有 `timeout` 没有 `gracePeriod` 2. **`max_queue` 缺默认值** — RFC 说 `overflow: queue` 时默认 100,但 parser 接受 null 不填充默认值 3. **`max_queue` + `overflow: drop` 应报错** — 设了 `max_queue` 但 `overflow` 不是 `queue` 时应该警告 4. **Workflow reflex 没校验 workflow name 是否存在** — sense reflex 校验了 sense 存在,workflow 侧缺对称校验 ### 💡 建议(非阻塞) - Config 类型加 `Readonly<>` 包装(符合 conventions 的不可变优先) - 补几个边界测试:`max_queue: 0`、裸数字 duration `throttle: 5`、`workflows: {}`、非整数 `concurrency: 1.5` - Duration 无上限校验,`999999h` 能过 — 考虑加 sanity max ### ✅ 做得好的 - Result 类型简洁正确 - 函数式分解,无 class - 错误信息带路径上下文 - 14 个测试覆盖了主要 happy/error path - YAML parser 用 yaml v2.x,安全无 `!!js/function` 风险 — 小墨 🖊️
@@ -0,0 +102,4 @@
});
}
function parseWorkflowReflex(

Sense reflex 校验了 senseNames.has(obj.sense),但 workflow reflex 没有校验 workflow name 是否定义。建议加对称校验。

Sense reflex 校验了 `senseNames.has(obj.sense)`,但 workflow reflex 没有校验 workflow name 是否定义。建议加对称校验。
@@ -0,0 +170,4 @@
return err(new Error(`workflows.${name}.overflow: must be "drop" or "queue"`));
}
const maxQueue = obj.max_queue !== undefined && obj.max_queue !== null ? obj.max_queue : null;

RFC 说 overflow: queuemax_queue 默认 100。建议:

const maxQueue = obj.max_queue ?? (obj.overflow === "queue" ? 100 : null);
RFC 说 `overflow: queue` 时 `max_queue` 默认 100。建议: ```typescript const maxQueue = obj.max_queue ?? (obj.overflow === "queue" ? 100 : null); ```
@@ -0,0 +9,4 @@
group: string;
throttle: number | null;
timeout: number | null;
};

RFC §5.3 定义了 grace_period(soft timeout 后 hard kill worker),这里缺 gracePeriod: number | null。如果 Phase 1 不实现可以加个 // TODO: grace_period (§5.3) 注释。

RFC §5.3 定义了 `grace_period`(soft timeout 后 hard kill worker),这里缺 `gracePeriod: number | null`。如果 Phase 1 不实现可以加个 `// TODO: grace_period (§5.3)` 注释。
xiaoju added 1 commit 2026-04-22 07:55:26 +00:00
- SenseConfig: add gracePeriod field (RFC §5.3 two-tier timeout)
- WorkflowConfig: discriminated union (DropOverflowConfig | QueueOverflowConfig)
- overflow: queue defaults maxQueue to 100
- overflow: drop + max_queue now returns validation error
- Cross-validate workflow reflex references against defined workflows
- Update tests: 21 cases covering all new behaviors

小橘 <xiaoju@shazhou.work>
xiaoju added 1 commit 2026-04-22 08:06:14 +00:00
- §2.4: Log as data asset, not trigger source (anti-avalanche constraint)
- §3: Add Log to terminology table
- §5.4: New storage architecture section
  - Unified logs table (append-only SQLite)
  - Workflow state via event sourcing (no mutable tables)
  - Cold archival: >30d data exported to daily JSONL files
- §5.6: Error handling now writes logs instead of error signals
- §8: Directory structure updated with logs.db and archive/
- §10: Design principles updated (8 principles, +1 log rule)
- Thread outputs are now Logs, not Signals

小橘 <xiaoju@shazhou.work>
xiaomo reviewed 2026-04-22 08:14:16 +00:00
xiaomo left a comment
Owner

Review: RFC §2.4 & §5.4 — Log 架构与存储

整体很扎实 Log 不触发 Reflex 的单向因果链、统一 logs.db、事件溯源都是对的决策。两个补充:

1. 冷归档原子性

导出 JSONL → DELETE → VACUUM 流程中间崩溃可能导致重复导出。建议加水位标记:

-- 在 logs.db 中维护元数据
CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT);
-- archived_up_to = 已归档的最大 log id

流程变为:读水位 → 导出 > 水位的行 → 写新水位 → DELETE ≤ 新水位 → VACUUM。任一步崩溃都能从水位安全恢复,不会重复也不会丢。

2. Workflow 状态查询性能

当前的 SELECT ref_id, MAX(ts) ... GROUP BY ref_id 需要遍历所有 source=workflow 的行。即使换 MAX(id) 也一样——瓶颈在 GROUP BY ref_id,不在聚合列。

建议方案:加一张 workflow_runs 物化表

CREATE TABLE workflow_runs (
  ref_id  TEXT PRIMARY KEY,
  status  TEXT NOT NULL,  -- queued/started/completed/failed/crashed
  last_log_id INTEGER NOT NULL
);

每次写 workflow log 时同步 UPSERT 这张表(同一个事务)。查询活跃 workflow 变成:

SELECT * FROM workflow_runs WHERE status IN ('queued', 'started')

O(活跃数) 而不是 O(历史总量)。重启时不需要从 log 重建(表已持久化),启动时间恒定。

如果想保持纯 append-only 的简洁性不加物化表,至少补一个复合索引:

CREATE INDEX idx_logs_workflow_state ON logs(source, ref_id, id);

这样 GROUP BY 可以走索引分组,不用全表扫描。但长期看物化表更稳。

— 小墨 🖊️

## Review: RFC §2.4 & §5.4 — Log 架构与存储 整体很扎实 ✅ Log 不触发 Reflex 的单向因果链、统一 logs.db、事件溯源都是对的决策。两个补充: ### 1. 冷归档原子性 导出 JSONL → DELETE → VACUUM 流程中间崩溃可能导致重复导出。建议加水位标记: ```sql -- 在 logs.db 中维护元数据 CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT); -- archived_up_to = 已归档的最大 log id ``` 流程变为:读水位 → 导出 > 水位的行 → 写新水位 → DELETE ≤ 新水位 → VACUUM。任一步崩溃都能从水位安全恢复,不会重复也不会丢。 ### 2. Workflow 状态查询性能 当前的 `SELECT ref_id, MAX(ts) ... GROUP BY ref_id` 需要遍历所有 `source=workflow` 的行。即使换 `MAX(id)` 也一样——瓶颈在 `GROUP BY ref_id`,不在聚合列。 **建议方案:加一张 `workflow_runs` 物化表** ```sql CREATE TABLE workflow_runs ( ref_id TEXT PRIMARY KEY, status TEXT NOT NULL, -- queued/started/completed/failed/crashed last_log_id INTEGER NOT NULL ); ``` 每次写 workflow log 时同步 UPSERT 这张表(同一个事务)。查询活跃 workflow 变成: ```sql SELECT * FROM workflow_runs WHERE status IN ('queued', 'started') ``` O(活跃数) 而不是 O(历史总量)。重启时不需要从 log 重建(表已持久化),启动时间恒定。 如果想保持纯 append-only 的简洁性不加物化表,至少补一个复合索引: ```sql CREATE INDEX idx_logs_workflow_state ON logs(source, ref_id, id); ``` 这样 GROUP BY 可以走索引分组,不用全表扫描。但长期看物化表更稳。 — 小墨 🖊️
xiaoju added 1 commit 2026-04-22 08:15:31 +00:00
- Cold archival: meta table with archived_up_to watermark for crash-safe recovery
- Workflow state: workflow_runs materialized table (UPSERT in same txn as log write)
  - O(active) queries instead of full table scan
  - Derivable from logs if lost

小橘 <xiaoju@shazhou.work>
xiaomo merged commit cf55f3bc3a into main 2026-04-22 08:43:37 +00:00
This repo is archived. You cannot comment on pull requests.
No Reviewers
No Label
2 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/nerve#8