Archived
人手改的那一手真正落得下去;界面不撒谎那几处补齐
手改是**兜 AI 的错**的那一手 —— 这一轮把它从「摆在那儿但落不了地」做成真的: 件上就地改 · 保存**按地址收**,不看当下那张卡:载荷从前借道工单的 `blocks[i]` 取列名, 于是熔断卡(没有 blocks)在屏上时,人补的那一格一个字都送不出去 —— 而他要补的恰恰是把这一单卡住的那个必填格。授权面改由活动清单的台账声明把关。 · **能增删行**:错不止「这一格填错了」,还有「这一行不该在」与「漏了一行」。 删挂记号不当场抹掉(看得见、可后悔),且**删要说出来**(`drops`)—— 「没回传就是删了」会把一次不完整的回传变成一次删表。 · **必填没填按不下提交**:与放行门 L1 同一个数同一套算法;闸上摆出差在哪一行哪一格。 · 只写真改了的那几行 —— 改一格却留「修订 4 行」的痕,那句话是假的。 判据与门 · 裁量判据带谓词时**谓词说了算**:「选填缺口已补齐」基座数得出来, 人填好的目标价它照样判「客户未提供」的毛病由此消掉。 · 验收卡判据**读时现算** —— 人在成果件上补一格,判据跟着变。 · 放行门认「**办完了没有**」不认「有人写过」:从前写个 `false` 就把门推开, 于是一单没打标就进了放行门。三个数全由基座现数。 · 拒回把「活没干完」说成「数或行对不上」的毛病:判据点名的表一行都没写时直说那一步没办。 **指向两扇打不开的门,比不指还糟。** 界面不撒谎 · 日志只有三色:负责人绿 · 智能体蓝 · 系统灰(归侧五种是度量的分法,不是给人看的)。 · 验收收口答两件事:落了什么(按**交付物**点名,一份成果的几张表并成一行)、 往哪儿去(转出哪一口、带过去哪些字段);一个口都没发就直说到此为止。 · 签完到收尾之间那一段有自己的话「字已签下,正在落地」—— 中间态本来就该说出来,只是要说对。 · 消息不再被内部事件刷屏:只有从通道进来的件才谈得上「无人认领」。 · 建客户公司与联系人**一次确认落两行**;联系人编码自增不问人;卡面摆表的人读名。 A1 三案例端到端全 released:全 A 类直通 12 拍 / 客户点名跳一条 10 拍 / 新客户缺必填 14 拍。 五道门禁全绿(971 passed)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsTDjoHovERXyy6Wmw1ALp
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 真活动包的安装 —— **经安装通道读文件,不经代码通道**(ADR-004 / 编码规范 §5.1)。
|
||||
*
|
||||
* 活动包在系统外开发:`activities/<pkg>/` 里写清单与资产,`pnpm activities:build`
|
||||
* 产出 `dist/{manifest.json,index.js}`。api 只读这两个文件 —— 与 activities 之间
|
||||
* 只有安装通道,没有 import(depcruise 的 `apps-not-into-activities` 在守)。
|
||||
*
|
||||
* 这就是「系统外开发、安装即用」的机器形态:装的时候过两审(静态审读清单、
|
||||
* 回放审重演历史),清单里的台账声明顺带把表物化出来,资产随包播种。
|
||||
*
|
||||
* 装不上不掀翻播种:如实报出来。活动包是可选件 —— 没构建产物就是没装,
|
||||
* 不是数据库坏了(而假装装上了才是真的坏)。
|
||||
*/
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { activityManifestSchema } from '@contracts';
|
||||
import type { Container } from './container.ts';
|
||||
import { MASTER_OBJECTS, MASTER_SAMPLE_ROWS } from './seed-mvp-data.ts';
|
||||
|
||||
/** 已开发完成、要随播种装上的真活动包(目录名 = 包目录,不是 activityId) */
|
||||
/*
|
||||
* 真活动包 —— 从 `activities/<pkg>/dist/` 读文件装入,**不经 import**。
|
||||
* A2/A3 于 2026-07-31 转正:它们的案例模型已简化为「一张执行单办完」,
|
||||
* 剧本与 A1 同一套机制,不再是治理台里那两个 viewTemplates 为空的占位槽。
|
||||
*/
|
||||
const REAL_PACKAGES = [
|
||||
'quote-intake',
|
||||
'quote-alternative',
|
||||
'quote-sourcing',
|
||||
'quote-pricing',
|
||||
'quote-negotiate',
|
||||
'quote-archive',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* 活动包根目录。
|
||||
*
|
||||
* 播种以 `apps/api` 为 cwd 跑(`pnpm --filter @app/api seed`),所以默认往上两级找;
|
||||
* 容器化或换布局时用 `JCAI_ACTIVITIES_DIR` 顶掉 —— 路径是部署事实,不该写死在代码里。
|
||||
*/
|
||||
function activitiesRoot(): string {
|
||||
const override = process.env['JCAI_ACTIVITIES_DIR'];
|
||||
return override === undefined || override.trim() === ''
|
||||
? resolve(process.cwd(), '..', '..', 'activities')
|
||||
: resolve(override);
|
||||
}
|
||||
|
||||
export async function installRealActivities(container: Container): Promise<void> {
|
||||
const log = (line: string) => process.stdout.write(line + '\n');
|
||||
|
||||
/*
|
||||
* **主数据先于活动物化**(2026-07-31)。
|
||||
*
|
||||
* 客户 / 项目 / 供应商 / 市场四域不挂在任何活动名下:主数据不是谁的产出,
|
||||
* 它先于活动存在、活动退役了它还在,活动只是读它做判断、提议更新它。
|
||||
* 挂在活动清单下的后果刚踩过 —— 那个活动一退役,它声明的表就成了没人认领的空壳。
|
||||
*
|
||||
* 放在装活动**之前**:活动声明里若也提到同一张表(它要写的那几列),
|
||||
* 声明合并是「最大化合并、只增不破坏」,平台给的全量结构先落地,活动只补自己那部分。
|
||||
*/
|
||||
const applied = await container.ledger.applyDeclarations(MASTER_OBJECTS);
|
||||
const conflicts = applied.flatMap((r) => r.conflicts);
|
||||
log(
|
||||
` 主数据四域已物化:${MASTER_OBJECTS.length} 张表` +
|
||||
// 冲突要如实报:静默吞掉的话,表结构和声明对不上而没人知道
|
||||
(conflicts.length === 0 ? '' : ` —— 合并冲突 ${conflicts.length} 处:${conflicts.map((c) => c.field).join('、')}`),
|
||||
);
|
||||
/*
|
||||
* 一位样例客户 —— 让「老客户认得出」那条路试得出来(见 `MASTER_SAMPLE_ROWS` 的注)。
|
||||
* 按主键写,重复播种是覆盖不是堆叠;写正式版(不是草稿层),
|
||||
* 因为 A1 第一步查的就是正式版 —— 草稿只有本任务自己看得见。
|
||||
*/
|
||||
for (const row of MASTER_SAMPLE_ROWS) {
|
||||
await container.ledger.systemWrite({
|
||||
address: { domain: row.address.domain, object: row.address.object },
|
||||
key: { ...row.key },
|
||||
values: { ...row.values },
|
||||
cause: { kind: 'governance', ref: 'seed:master-sample' },
|
||||
});
|
||||
}
|
||||
log(` 样例客户已就位:${MASTER_SAMPLE_ROWS[1]?.values.email ?? ''}(样例 2 用它试老客户那条路)`);
|
||||
|
||||
const root = activitiesRoot();
|
||||
|
||||
for (const dir of REAL_PACKAGES) {
|
||||
const dist = join(root, dir, 'dist');
|
||||
let manifestJson: string;
|
||||
let entryJs: string;
|
||||
try {
|
||||
[manifestJson, entryJs] = await Promise.all([
|
||||
readFile(join(dist, 'manifest.json'), 'utf8'),
|
||||
readFile(join(dist, 'index.js'), 'utf8'),
|
||||
]);
|
||||
} catch {
|
||||
log(` 活动包 ${dir} 没有构建产物(${dist})—— 先跑 pnpm activities:build;本次跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const parsed = activityManifestSchema.safeParse(JSON.parse(manifestJson));
|
||||
if (!parsed.success) {
|
||||
// 清单不合协议就别装:装进去的残清单会在起单那一刻才炸,那时更难查
|
||||
log(` 活动包 ${dir} 的清单不合协议:${parsed.error.issues.map((i) => `${i.path.join('.')} ${i.message}`).join('; ')}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* 安装恒新增、版本并存:同版本重装是 no-op(静默丢失 —— 踩过)。
|
||||
* 所以改了内容必须在包里升 `identity.packageVersion`,不然播种看着成功、内容还是旧的。
|
||||
*/
|
||||
try {
|
||||
const verdict = await container.registry.install(parsed.data, entryJs);
|
||||
const version = parsed.data.identity.packageVersion;
|
||||
/*
|
||||
* **装的时候把声明的工具一并授权**(2026-07-31 补)。
|
||||
*
|
||||
* 从前授权只在 `seed-mvp` 里发 —— 那是治理台的整套配置播种。
|
||||
* 于是经安装通道装进来的真活动包一件工具也调不动:A4 的说明书让它去取历史价,
|
||||
* 清单里也白纸黑字声明了 `crm.history-price-read`,运行时却拒回
|
||||
* 「工具不在本活动白名单」,连拒五拍熔断(实测 A4 死于此)。
|
||||
* 「系统外开发、安装即用」里的「即用」,包含它声明过的那些工具能调得动;
|
||||
* 声明了却不授权,等于装了一个结构上办不成事的活动。
|
||||
*
|
||||
* 授权面**就是清单声明**,不多给一件:越权在结构上仍然不可能。
|
||||
*/
|
||||
const declared = parsed.data.declarations.tools.map((t) => t.name);
|
||||
await container.toolbox.setAllowlist(parsed.data.identity.activityId, declared);
|
||||
log(
|
||||
` 活动包已装:${parsed.data.identity.name}(${parsed.data.identity.activityId}@${version})` +
|
||||
(declared.length === 0 ? '' : ` · 已授权工具 ${declared.length} 件`) +
|
||||
(verdict.staticFindings.length === 0
|
||||
? ''
|
||||
: ` —— 静态审提示:${verdict.staticFindings.map((f) => `[${f.severity}] ${f.message}`).join('; ')}`),
|
||||
);
|
||||
} catch (error) {
|
||||
log(` 活动包 ${dir} 未过安装两审:${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user