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,303 @@
/**
* 台账存储 adapter —— node-postgres 直写(编码规范 §1 / ADR-002)。
*
* 运行时 DDL/DML 由声明生成,ORM 静态 schema 不适用;`ledger` schema 的**唯一写者**就是本文件。
* 标识符全部来自 @contracts 的 identifier 正则(小写字母开头、字母数字下划线),
* 落库前再过一道 `quoteIdent` —— 拼接 SQL 的地方只有这里。
*/
import type { Pool, PoolClient } from 'pg';
import type { LedgerKey, LedgerQuery, LedgerRow } from '@contracts';
import { LEDGER_SCHEMA } from '@shared/db.ts';
import type { DraftStore, LedgerStore, ObjectRef } from '../../port.ts';
import type { MaterializedObject, MergedField } from '../domain/schema-merge.ts';
import type { DraftEntry } from '../domain/task-view.ts';
import { keyOf } from '../domain/task-view.ts';
const IDENT = /^[a-z][a-z0-9_]*$/;
function quoteIdent(name: string): string {
if (!IDENT.test(name)) throw new Error(`非法标识符:${name}`);
return `"${name}"`;
}
/** 物理表名:<域>__<对象>;域与对象都过 IDENT,拼接安全 */
export function tableNameOf(ref: ObjectRef): string {
if (!IDENT.test(ref.domain) || !IDENT.test(ref.object)) {
throw new Error(`非法台账地址:${ref.domain}.${ref.object}`);
}
return `${ref.domain}__${ref.object}`;
}
const PG_TYPE: Readonly<Record<MergedField['type'], string>> = {
text: 'text',
number: 'double precision',
integer: 'bigint',
boolean: 'boolean',
date: 'date',
timestamp: 'timestamptz',
json: 'jsonb',
};
const FROM_PG: Readonly<Record<string, MergedField['type']>> = {
text: 'text',
'character varying': 'text',
'double precision': 'number',
numeric: 'number',
bigint: 'integer',
integer: 'integer',
boolean: 'boolean',
date: 'date',
'timestamp with time zone': 'timestamp',
'timestamp without time zone': 'timestamp',
jsonb: 'json',
json: 'json',
};
type Queryable = Pick<Pool | PoolClient, 'query'>;
export function createLedgerStore(pool: Pool): LedgerStore {
return buildStore(pool, pool);
}
function buildStore(runner: Queryable, pool: Pool): LedgerStore {
const store: LedgerStore = {
async describe(ref) {
const table = tableNameOf(ref);
const cols = await runner.query<{ column_name: string; data_type: string }>(
`select column_name, data_type from information_schema.columns
where table_schema = $1 and table_name = $2 order by ordinal_position`,
[LEDGER_SCHEMA, table],
);
if (cols.rows.length === 0) return undefined;
const pk = await runner.query<{ attname: string }>(
`select a.attname from pg_index i
join pg_attribute a on a.attrelid = i.indrelid and a.attnum = any(i.indkey)
where i.indrelid = $1::regclass and i.indisprimary`,
[`${LEDGER_SCHEMA}.${quoteIdent(table)}`],
);
const materialized: MaterializedObject = {
domain: ref.domain,
object: ref.object,
columns: cols.rows.map((c) => ({ name: c.column_name, type: FROM_PG[c.data_type] ?? 'text' })),
primaryKey: pk.rows.map((p) => p.attname),
};
return materialized;
},
async listObjects() {
const res = await runner.query<{ table_name: string }>(
`select table_name from information_schema.tables
where table_schema = $1 and table_name not like '\\_\\_%' order by table_name`,
[LEDGER_SCHEMA],
);
return res.rows.flatMap((r) => {
const idx = r.table_name.indexOf('__');
if (idx <= 0) return [];
return [{ domain: r.table_name.slice(0, idx), object: r.table_name.slice(idx + 2) }];
});
},
async applyChanges(ref, changes) {
const table = `${LEDGER_SCHEMA}.${quoteIdent(tableNameOf(ref))}`;
for (const change of changes) {
switch (change.kind) {
case 'create-table': {
const cols = change.object.fields.map((f) => `${quoteIdent(f.name)} ${PG_TYPE[f.type]}`);
const pk =
change.object.primaryKey.length > 0
? `, primary key (${change.object.primaryKey.map(quoteIdent).join(', ')})`
: '';
await runner.query(`create table if not exists ${table} (${cols.join(', ')}${pk})`);
break;
}
case 'add-column':
await runner.query(
`alter table ${table} add column if not exists ${quoteIdent(change.field.name)} ${PG_TYPE[change.field.type]}`,
);
break;
case 'widen-column':
await runner.query(
`alter table ${table} alter column ${quoteIdent(change.field.name)} type ${PG_TYPE[change.field.type]} ` +
`using ${quoteIdent(change.field.name)}::${PG_TYPE[change.field.type]}`,
);
break;
}
}
},
async select(query: LedgerQuery) {
const table = `${LEDGER_SCHEMA}.${quoteIdent(tableNameOf(query.address))}`;
const params: unknown[] = [];
const clauses = Object.entries(query.where).map(([field, value]) => {
params.push(value);
return `${quoteIdent(field)} = $${params.length}`;
});
const where = clauses.length > 0 ? ` where ${clauses.join(' and ')}` : '';
const order =
query.orderBy.length > 0
? ` order by ${query.orderBy.map((o) => `${quoteIdent(o.field)} ${o.dir === 'desc' ? 'desc' : 'asc'}`).join(', ')}`
: '';
const countRes = await runner.query<{ n: string }>(`select count(*)::text as n from ${table}${where}`, params);
params.push(query.limit, query.offset);
const rows = await runner.query<LedgerRow>(
`select * from ${table}${where}${order} limit $${params.length - 1} offset $${params.length}`,
params,
);
return { rows: rows.rows, total: Number(countRes.rows[0]?.n ?? '0') };
},
async selectByKeys(ref, keys) {
if (keys.length === 0) return [];
const table = `${LEDGER_SCHEMA}.${quoteIdent(tableNameOf(ref))}`;
const out: LedgerRow[] = [];
for (const key of keys) {
const params: unknown[] = [];
const clauses = Object.entries(key).map(([field, value]) => {
params.push(value);
return `${quoteIdent(field)} = $${params.length}`;
});
if (clauses.length === 0) continue;
const res = await runner.query<LedgerRow>(
`select * from ${table} where ${clauses.join(' and ')} limit 1`,
params,
);
out.push(...res.rows);
}
return out;
},
async upsert(ref, key, values) {
const table = `${LEDGER_SCHEMA}.${quoteIdent(tableNameOf(ref))}`;
const merged: LedgerRow = { ...key, ...values };
const fields = Object.keys(merged);
if (fields.length === 0) return;
const params = fields.map((f) => merged[f]);
const placeholders = fields.map((_, i) => `$${i + 1}`);
const keyFields = Object.keys(key);
const updatable = fields.filter((f) => !keyFields.includes(f));
const onConflict =
keyFields.length === 0
? ''
: updatable.length === 0
? ` on conflict (${keyFields.map(quoteIdent).join(', ')}) do nothing`
: ` on conflict (${keyFields.map(quoteIdent).join(', ')}) do update set ` +
updatable.map((f) => `${quoteIdent(f)} = excluded.${quoteIdent(f)}`).join(', ');
await runner.query(
`insert into ${table} (${fields.map(quoteIdent).join(', ')}) values (${placeholders.join(', ')})${onConflict}`,
params,
);
},
async deleteRow(ref, key) {
const table = `${LEDGER_SCHEMA}.${quoteIdent(tableNameOf(ref))}`;
const params: unknown[] = [];
const clauses = Object.entries(key).map(([field, value]) => {
params.push(value);
return `${quoteIdent(field)} = $${params.length}`;
});
if (clauses.length === 0) return;
await runner.query(`delete from ${table} where ${clauses.join(' and ')}`, params);
},
async transaction(fn) {
const client = await pool.connect();
try {
await client.query('begin');
const scoped = buildStore(client, pool);
const result = await fn(scoped, createDraftStore(client));
await client.query('commit');
return result;
} catch (error) {
await client.query('rollback');
throw error;
} finally {
client.release();
}
},
};
return store;
}
/* ── 任务级草稿层:ledger.__draft ─────────────────────────────────────── */
export const DRAFT_TABLE = `${LEDGER_SCHEMA}.__draft`;
export const DRAFT_DDL = `
create table if not exists ${DRAFT_TABLE} (
task_id text not null,
domain text not null,
object text not null,
key_hash text not null,
key jsonb not null,
patch jsonb not null,
baseline jsonb not null,
tombstone boolean not null default false,
updated_at timestamptz not null default now(),
primary key (task_id, domain, object, key_hash)
);`;
type DraftRow = {
task_id: string;
domain: string;
object: string;
key: Record<string, string | number>;
patch: LedgerRow;
baseline: LedgerRow;
tombstone: boolean;
};
function toEntry(row: DraftRow): DraftEntry & ObjectRef {
return {
domain: row.domain,
object: row.object,
key: row.key,
patch: row.patch,
baseline: row.baseline,
tombstone: row.tombstone,
};
}
export function createDraftStore(runner: Queryable): DraftStore {
return {
async list(taskId) {
const res = await runner.query<DraftRow>(
`select * from ${DRAFT_TABLE} where task_id = $1 order by updated_at`,
[taskId],
);
return res.rows.map(toEntry);
},
async listForObject(taskId, ref) {
const res = await runner.query<DraftRow>(
`select * from ${DRAFT_TABLE} where task_id = $1 and domain = $2 and object = $3 order by updated_at`,
[taskId, ref.domain, ref.object],
);
return res.rows.map(toEntry);
},
async put(taskId, ref, entry) {
await runner.query(
`insert into ${DRAFT_TABLE} (task_id, domain, object, key_hash, key, patch, baseline, tombstone)
values ($1,$2,$3,$4,$5,$6,$7,$8)
on conflict (task_id, domain, object, key_hash) do update
set patch = ${DRAFT_TABLE}.patch || excluded.patch,
tombstone = excluded.tombstone,
updated_at = now()`,
[
taskId,
ref.domain,
ref.object,
keyOf(entry.key as LedgerKey),
JSON.stringify(entry.key),
JSON.stringify(entry.patch),
JSON.stringify(entry.baseline),
entry.tombstone,
],
);
},
async clear(taskId) {
await runner.query(`delete from ${DRAFT_TABLE} where task_id = $1`, [taskId]);
},
};
}