M1: 底座 —— @contracts 首版 + event / ledger / org / llm

@contracts 首版(§8.1 九个文件):
- id 品牌 ID / terminal 终态五名与归侧四标签 / envelope 信封六字段与四类事件
- card 工单两模式 + 对话卡族(形状对齐 2026-07-26 原型定稿)
- contract7 契约七件 / ledger 三级寻址与写三分 / process-package 流程包 DSL 与触发器三类
- manifest 活动包清单六区(含 CMMN·DMN·步序声明的资产形状)/ org / api 路由契约

模块(L1):
- event   信封存储、归属解析三级短路、投递五诺、监听注册表、三级消费裁定
- ledger  schema 自生长(最大化合并/最宽类型/只增不破坏)、任务视图叠加、写三分、冲突检查
- org     岗位授权(客体三类×动作三种)、停用前置检查、会话、种子播种
- llm     四元绑定、ModelGateway 三模式(live/record/replay)—— 两缝之一

app 装配:Hono 服务 + 鉴权中间件 + 治理台/工作台路由;drizzle 迁移;AES-256-GCM 凭证密封

ADR-007 工具链与门禁 · ADR-008 投递队列 · ADR-009 凭证加密
(ADR 编号自 007 续:编码规范 §14 已用到 006,README 写「从 005 续」是笔误)

门禁五道全绿;96 个测试通过,含对真实 PG 的 ledger 集成测试

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
JCAI Main Agent
2026-07-27 00:59:14 +08:00
co-authored by Claude Opus 5
parent 7cd664af3a
commit 14d4fa046a
120 changed files with 15236 additions and 78 deletions
@@ -0,0 +1,236 @@
/**
* ledger 用例 —— 编排 domain,依赖 port.ts,不 import 任何 adapter(编码规范 §5.3)。
*
* 写三分的不变量在此落实(OS 09):
* systemWrite → 正式版 + **即时**数据事件
* draftWrite → 草稿层,**零**事件
* releaseWrite → 冲突检查 → 原子合并 → **此刻**抛事件
*/
import type { EnvelopeDraft, LedgerPage, LedgerQuery, ObjectDeclaration, TaskId } from '@contracts';
import { eventIdSchema } from '@contracts';
import type {
DraftWrite,
LedgerService,
ObjectAddress,
ReleaseWriteResult,
SchemaApplyResult,
SystemWrite,
} from '../../contract.ts';
import { DraftConflictError, ObjectNotMaterializedError } from '../../contract.ts';
import type { Clock, DraftStore, EventPublisher, IdGenerator, LedgerStore, ObjectRef } from '../../port.ts';
import { mergeDeclarations, planIsNonDestructive, planSchemaChanges } from '../domain/schema-merge.ts';
import { detectConflicts, keyOf, overlay, pickKey } from '../domain/task-view.ts';
export type LedgerServiceDeps = {
readonly store: LedgerStore;
readonly drafts: DraftStore;
readonly events: EventPublisher;
readonly clock: Clock;
readonly ids: IdGenerator;
};
function groupDeclarations(declarations: readonly ObjectDeclaration[]): Map<string, ObjectDeclaration[]> {
const groups = new Map<string, ObjectDeclaration[]>();
for (const d of declarations) {
const key = `${d.domain}.${d.object}`;
const bucket = groups.get(key);
if (bucket === undefined) groups.set(key, [d]);
else bucket.push(d);
}
return groups;
}
export function createLedgerService(deps: LedgerServiceDeps): LedgerService {
function dataEvent(
ref: ObjectRef,
key: Record<string, string | number>,
changed: readonly string[],
operation: 'insert' | 'update' | 'delete',
taskId: TaskId | undefined,
cause: { kind: 'ledger-write'; ref: string },
): EnvelopeDraft {
return {
id: eventIdSchema.parse(deps.ids.next()),
type: 'data',
correlation: {
// 未绑任务的系统写不指定消费目标 —— 三级消费由 `event` 判定(OS 6.3)
...(taskId === undefined ? {} : { target: { kind: 'task' as const, id: taskId } }),
cause,
keys: Object.fromEntries(Object.entries(key).map(([k, v]) => [k, String(v)])),
},
payload: {
data: {
domain: ref.domain,
object: ref.object,
primaryKey: key,
changed: [...changed],
operation,
},
},
occurredAt: deps.clock.now().toISOString(),
source: { kind: 'engine', detail: 'ledger' },
};
}
async function requireSchema(address: ObjectAddress) {
const described = await deps.store.describe(address);
if (described === undefined) throw new ObjectNotMaterializedError(`${address.domain}.${address.object}`);
return described;
}
return {
async applyDeclarations(declarations) {
const results: SchemaApplyResult[] = [];
for (const [, group] of groupDeclarations(declarations)) {
const first = group[0]!;
const ref: ObjectRef = { domain: first.domain, object: first.object };
const merge = mergeDeclarations(group);
if (merge.merged === undefined || merge.conflicts.length > 0) {
results.push({ merged: merge.merged, notices: merge.notices, conflicts: merge.conflicts, applied: false });
continue;
}
const current = await deps.store.describe(ref);
const plan = planSchemaChanges(merge.merged, current);
const conflicts = [...merge.conflicts, ...plan.conflicts];
if (conflicts.length > 0 || !planIsNonDestructive(plan)) {
results.push({ merged: merge.merged, notices: [...merge.notices, ...plan.notices], conflicts, applied: false });
continue;
}
if (plan.changes.length > 0) await deps.store.applyChanges(ref, plan.changes);
results.push({
merged: merge.merged,
notices: [...merge.notices, ...plan.notices],
conflicts: [],
applied: true,
});
}
return results;
},
async describe(address) {
return deps.store.describe(address);
},
async listObjects() {
return deps.store.listObjects();
},
/** ① 系统写:世界已发生的事实,免门,即时抛事件 */
async systemWrite(write: SystemWrite) {
await requireSchema(write.address);
await deps.store.upsert(write.address, write.key, write.values);
await deps.events.publish(
dataEvent(
write.address,
write.key,
Object.keys(write.values),
'update',
write.taskId,
{ kind: 'ledger-write', ref: `${write.cause.kind}:${write.cause.ref}` },
),
);
},
/** ② 草稿写:读己之写,正式版不可见,**不产生数据事件** */
async draftWrite(write: DraftWrite) {
const schema = await requireSchema(write.address);
const existing = await deps.store.selectByKeys(write.address, [write.key]);
const baseline: Record<string, unknown> = {};
const row = existing[0];
if (row !== undefined) {
for (const field of Object.keys(write.patch)) baseline[field] = row[field];
}
await deps.drafts.put(write.taskId, write.address, {
key: pickKey({ ...write.key }, schema.primaryKey.length > 0 ? schema.primaryKey : Object.keys(write.key)),
patch: write.patch,
tombstone: write.tombstone ?? false,
baseline,
});
},
/** ③ 放行写:冲突检查 → 原子合并 → 此刻抛事件 */
async releaseWrite(taskId): Promise<ReleaseWriteResult> {
const conflicts = await this.checkConflicts(taskId);
if (conflicts.length > 0) throw new DraftConflictError(conflicts);
const drafts = await deps.drafts.list(taskId);
if (drafts.length === 0) return { mergedRows: 0, emittedEvents: 0 };
const pending: EnvelopeDraft[] = [];
await deps.store.transaction(async (store, draftStore) => {
for (const draft of drafts) {
const ref: ObjectRef = { domain: draft.domain, object: draft.object };
if (draft.tombstone) {
await store.deleteRow(ref, draft.key);
pending.push(
dataEvent(ref, draft.key, [], 'delete', taskId, { kind: 'ledger-write', ref: `release:${taskId}` }),
);
continue;
}
const existing = await store.selectByKeys(ref, [draft.key]);
await store.upsert(ref, draft.key, draft.patch);
pending.push(
dataEvent(
ref,
draft.key,
Object.keys(draft.patch),
existing.length === 0 ? 'insert' : 'update',
taskId,
{ kind: 'ledger-write', ref: `release:${taskId}` },
),
);
}
await draftStore.clear(taskId);
});
for (const envelope of pending) await deps.events.publish(envelope);
return { mergedRows: drafts.length, emittedEvents: pending.length };
},
async query(query: LedgerQuery): Promise<LedgerPage> {
const { rows, total } = await deps.store.select(query);
return { rows, total };
},
async taskView(taskId, query): Promise<LedgerPage> {
const ref: ObjectRef = { domain: query.address.domain, object: query.address.object };
const schema = await requireSchema(ref);
const [official, drafts] = await Promise.all([
deps.store.select(query),
deps.drafts.listForObject(taskId, ref),
]);
const rows = overlay(official.rows, drafts, schema.primaryKey);
return { rows, total: rows.length };
},
async checkConflicts(taskId) {
const drafts = await deps.drafts.list(taskId);
if (drafts.length === 0) return [];
const byObject = new Map<string, Array<(typeof drafts)[number]>>();
for (const d of drafts) {
const k = `${d.domain}.${d.object}`;
const bucket = byObject.get(k);
if (bucket === undefined) byObject.set(k, [d]);
else bucket.push(d);
}
const findings = [];
for (const [, group] of byObject) {
const first = group[0]!;
const ref: ObjectRef = { domain: first.domain, object: first.object };
const schema = await requireSchema(ref);
const rows = await deps.store.selectByKeys(
ref,
group.map((g) => g.key),
);
const officialByKey = new Map(rows.map((r) => [keyOf(pickKey(r, schema.primaryKey)), r]));
findings.push(...detectConflicts(group, officialByKey));
}
return findings;
},
async discardDrafts(taskId) {
await deps.drafts.clear(taskId);
},
};
}