Skip to content

State ​

状态层(L0):把"发生了什么"变成可寻址、可版本化、仅追加、可复放的事实。


职责 ​

负责不负责
事件日志与物化状态业务状态机(fulfillment / escrow)
寻址、版本、复放对外 REST 写接口
actor 归属与因果链权限体系(只留默认策略)
订阅匹配与触发业务规则判断
旁挂读写替换既有执行路径

数据模型 ​

集合作用
state_events仅追加事件(真相源)
state_entries物化状态(投影)
state_subscriptions订阅
state_namespace_counters序列分配
state_domains域声明(治理单位:factClass / 权威 / 期间策略)

路径 ​

/tasks/{taskId}/deliveries/{deliveryId}
/tasks/{taskId}/deliveries/{deliveryId}/acceptance
/tasks/{taskId}/disputes/{disputeId}
/tasks/{taskId}/escrow
/tasks/{taskId}/agents/{agentId}/results/{eventId}
/tasks/{taskId}/nodes/{nodeId}

事件 ​

json
{
  "eventId": "evt_...",
  "path": "/tasks/t1/escrow",
  "op": "set",
  "value": { "status": "held", "amount": 100000 },
  "version": 3,
  "seq": 42,
  "actor": "buyer:u123",
  "causedByEventId": "evt_...",
  "triggerDepth": 1,
  "source": "agent_callback"
}

服务结构 ​

服务职责
StateService读写、寻址、版本、权限、派发
StateReplayServicereplay() / verify() / readVersion() / traceCausation()
StateSubscriptionService订阅注册、pattern 匹配、条件委派
StateTriggerDispatcher触发编排、防环、执行器注册
StateNodeWriteService节点旁挂写
StateNodeReadService节点旁挂读
StateConditionEvaluator白名单比较器(无 eval)

写入 ​

typescript
await state.write({
  path: '/tasks/t1/escrow',
  op: 'set',
  value: { status: 'held', amount: 100000 },
  actor: 'buyer:u123',
});

内部三步在同一事务:

① seq $inc
② 事件 insert
③ 物化 upsert

支持外部 session:

typescript
await state.write({ ..., session, onCommitted });

这样履约/托管可以与业务写入同生共死。


复放与校验 ​

typescript
// 重建
const state = await replay({ taskId: 't1' });

// 校验物化 == 重放
const ok = await verify({ namespace: 'tasks' });

// 读历史版本
const v2 = await readVersion('/tasks/t1/escrow', 2);

// 因果链
const trace = await traceCausation({ taskId: 't1' });

replay 与物化写入共用同一个 reducer,保证两条路径结果一致。


订阅与触发 ​

json
{
  "pattern": "/tasks/*/deliveries/**",
  "condition": { "field": "status", "op": "eq", "value": "delivered" },
  "action": { "type": "execute_agent", "agentId": "..." },
  "maxTriggerDepth": 5
}
执行器作用
execute_workflow启动工作流
execute_agent直接调 Agent,结果回写状态

防环依靠 causedByEventId + triggerDepth。


域治理与时间轴 ​

域声明 ​

平台按事实形态分四类存储,但共享同一套寻址与权限:

factClass存储例子
eventstate_events订单、期间、交付
ledgerledger_accounts + ledger_movements库存、配额
mastermaster_entities + master_aliases物料、供应商
computationcomputation_runs定价、MRP

每个域必须声明 factClass、authority、periodPolicy、writers。

  • 未声明的顶层域写入被拒(内置 tasks / agents 除外)
  • factClass 声明后不可更改——换类别等于换存储形态
  • 声明有 60s 缓存,写入热路径不查库

详见 四类事实形态与域模型。

双时间轴 ​

轴字段用途
业务发生occurredAt归属期间、余额计算
系统记录at审计「何时知道」、复放顺序

occurredAtSource 标明可信度:declared(调用方声明)/ source(外部系统)/ inferred(回退为记录时间)。 inferred 的事实不能进被重开过的期间。

期间门禁 ​

StateService.writeMany()
  → 域声明缓存
  → PeriodGuard.assertWritable()    ← 已关闭期间 → 409
  → prepareWrite()

只看 /periods/{id}/... 子路径——根路径永远放行,否则 close() 之后无法 reopen()。 绕过记账服务直接写状态层,同样被拒。


旁挂语义 ​

场景行为
节点声明 stateWrites写,失败只 warn
节点未声明零写入
节点声明 stateReads读,注入 input.stateReads.{as}
履约 / 托管强制写,失败即抛错

边界与限制 ​

限制说明
无对外端点条件/动作形状未稳定
事件不删除删除会破坏 replay
订阅条件有限无算术、时间窗、多事件聚合
无细粒度授权只有默认写权限策略
事件会增长尚无归档策略

测试 ​

bash
npx jest libs/modules/src/state
npx jest --config ./apps/hushwork/test/jest-e2e.json --runInBand \
  --testPathPatterns="state-layer"

源码位置 ​

libs/modules/src/state/
├── schemas/
├── services/
├── triggers/
├── state.module.ts
└── state-wiring.module.ts

相关页面 ​

Released under the MIT License.