Archived
资料文件真产出来了;E04/I01 补到验收线,顺手填了几个「墙拦住却不给路」的坑
**资料文件全链**(用户:审核资料这一步要真产出 word/excel/md/html,前端能渲、点资料名能开)
接手时这一步一个文件都产不出来 —— S3 剧本白纸黑字写着「file_ref 本 MVP 阶段可留空」。
文件是台账那一行的投影,不另存一份:
· 声明 `file:{titleColumn,bodyColumn,formatColumn}`,它同时是授权面(没声明就点不开)
· 现造真字节 —— docx/xlsx 是自己打的 OOXML 包(zip-write:store+CRC32,零依赖)
· 读回来渲 —— 解的就是造出来那份字节,渲得出来才证明文件是真的
· 件上 `data-file-of` → 工作区二级界面(Word 纸面/Excel 网格/渲好的 md/沙箱 html)+ 下载原件
放行落地前把每一份冻进 `sys.task_asset_file`(迁移 0029):归档件点开的必须是签字那一刻
那一份,而资料落在主数据上、试跑单不落正式表 —— 不存这一份,收工后一个都点不开。
工作成果里「产出的资料」自成一摞。Markdown 富文本渲染顺带销账(仍零依赖)。
**Harness**
· 画面按「这一拍办完之后」在办的那一步挑 —— 从前拿拍首的局面决定拍尾的事:
收掉 S4、发出 S5 的卡,而屏上还摆着 S4 的只读件,人无处可填(墙要配路第四次现形)
· 验收「重做」真能退回重做,且打回后台账没动就不许再提交(软硬两段,不锁死单子)
· 立监听的授权面是清单声明;发卡的授权面是调人预算,不是计划项
· 读工具同参重调有兜底;空转写身后还有行动就不废串;finish 的 doneWhen 按写完后的草稿算
**界面**
· 翻件与翻页各归各位(从前二选一,多卡多页时页翻不动);没得翻写「—」不写 0/0
· 工作成果详情能翻份;表宽超出能横滑(E04/I01 抄版式时把 overflow-x 抄丢了)
· 查看面灰底滚过一屏变白 —— 两条独立的路都治了(沙箱内不同色 / 弹性子项不长个)
· 测试台「取不到」不再当成「还活着」
**活动**
E04 关系覆盖、I01 品牌内容各自补到验收线:判据全读基座数、判据指向的东西摆上卡面、
样例满载且名字 ≤14 字;提交前要裁的判据搬进说明书(模型会压拍,压过之后只有说明书还在)。
验收:e2e i01 4/4、e04 3/3 全部 released,资料在办中渲得出、放行后存了档点得开;
门禁 typecheck/lint/depcruise/1628 测全绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEJX8Z7CHejvwmDQyPQuBX
This commit is contained in:
@@ -720,7 +720,19 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
|
||||
const denied = await governProcess(c.get('account'));
|
||||
if (denied) return c.json(denied, denyStatus(denied));
|
||||
const taskId = taskIdSchema.parse(c.req.param('taskId'));
|
||||
const task = await container.task.get(taskId);
|
||||
/*
|
||||
* **「这一单没了」是 404,不是 500**(2026-08-23 用户:「推进完成了,但测试台还在
|
||||
* 运行中,没有停下来」查出来的后一半)。这一单被别处清掉(reset-runs / 另一台起单
|
||||
* 先销毁上一单)之后,这条路由抛的是普通 Error → 兜底成 `500 internal`。
|
||||
* 而 500 的意思是「服务器出毛病了,等会儿再来」——面板据此**继续举着上一次的状态
|
||||
* 每 2 秒重试**,屏上就成了「AWAITING-EVENT · 自动刷新」配一条「任务不存在」。
|
||||
* 状态码说错一格,前端就分不清「该松手」与「该等它回来」。
|
||||
*/
|
||||
const task = await container.task.get(taskId).catch(() => undefined);
|
||||
if (task === undefined) {
|
||||
const body: ApiError = { code: 'not-found', message: `这一单已经不在了(${taskId})` };
|
||||
return c.json(body, 404);
|
||||
}
|
||||
|
||||
const memory = await container.memory.window(taskId, 200);
|
||||
const drafts = await container.ledger.taskDrafts(taskId);
|
||||
@@ -1246,7 +1258,20 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
|
||||
const faces = await Promise.all(
|
||||
cards.map(async (card) => {
|
||||
const manifest = await container.registry.manifestOf(card.activityId);
|
||||
type FieldSpec = { type?: string; items?: { type?: string }; title?: string; format?: string };
|
||||
type FieldSpec = {
|
||||
type?: string;
|
||||
items?: { type?: string };
|
||||
title?: string;
|
||||
format?: string;
|
||||
/** 枚举值 —— 声明了就该给下拉选,不该让人对着自由文本框猜合法值 */
|
||||
enum?: unknown[];
|
||||
/** 枚举的人读名(与 `enum` 同序;JSON Schema 无此标准关键字,借业界通行写法) */
|
||||
enumNames?: unknown[];
|
||||
/** JSON Schema 标准关键字:示例值,填不出合法值时给个能抄的样子 */
|
||||
examples?: unknown[];
|
||||
/** 非标准扩展:另一个字段等于某值才出现这一格(级联下拉那类场景) */
|
||||
showWhen?: { field?: string; equals?: string };
|
||||
};
|
||||
const data = manifest.contract.input.data as {
|
||||
required?: string[];
|
||||
properties?: Record<string, FieldSpec>;
|
||||
@@ -1256,6 +1281,17 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
|
||||
/** 类型的人读写法 —— **数组写成 `T[]`**:声明得看得出是一个还是一串 */
|
||||
const typeOf = (spec: FieldSpec | undefined): string =>
|
||||
spec?.type === 'array' ? `${spec.items?.type ?? 'any'}[]` : (spec?.type ?? 'unknown');
|
||||
/** 枚举 → 下拉选项:有 `enumNames` 配对就给人读名,没有就拿枚举值本身当标签 */
|
||||
const optionsOf = (spec: FieldSpec | undefined): { value: string; label: string }[] | undefined => {
|
||||
if (!Array.isArray(spec?.enum) || spec.enum.length === 0) return undefined;
|
||||
return spec.enum.map((v, i) => ({ value: String(v), label: String(spec.enumNames?.[i] ?? v) }));
|
||||
};
|
||||
/** 示例值:JSON Schema `examples` 第一项,对象/数组类型序列化成能直接抄的 JSON 串 */
|
||||
const exampleOf = (spec: FieldSpec | undefined): string | undefined => {
|
||||
const first = spec?.examples?.[0];
|
||||
if (first === undefined) return undefined;
|
||||
return typeof first === 'string' ? first : JSON.stringify(first);
|
||||
};
|
||||
const portFields = (payload: unknown) => {
|
||||
const p = payload as { properties?: Record<string, FieldSpec> } | undefined;
|
||||
return Object.entries(p?.properties ?? {}).map(([name, spec]) => ({ name, type: typeOf(spec) }));
|
||||
@@ -1270,6 +1306,14 @@ export function registerGovernanceOpsRoutes(app: Hono<AppEnv>, container: Contai
|
||||
...(spec?.title === undefined ? {} : { label: spec.title }),
|
||||
// `format: 'file'` = 这一格收的是**真文件**,界面该给文件选择器而不是文本框
|
||||
...(spec?.format === undefined ? {} : { format: spec.format }),
|
||||
// 声明了枚举就给下拉选 —— 不该让人对着自由文本框猜合法值
|
||||
...(optionsOf(spec) === undefined ? {} : { options: optionsOf(spec) }),
|
||||
// 声明了示例就把它摆进占位提示,比裸类型名(`string`)更能抄
|
||||
...(exampleOf(spec) === undefined ? {} : { example: exampleOf(spec) }),
|
||||
// 声明了 showWhen 就只在另一格等于某值时才出现(级联下拉)
|
||||
...(spec?.showWhen?.field === undefined || spec.showWhen.equals === undefined
|
||||
? {}
|
||||
: { showWhen: { field: spec.showWhen.field, equals: spec.showWhen.equals } }),
|
||||
})),
|
||||
// 包自带的样例集:试跑一个活动时照它预填(对着空表单编,编出来的多半跑不通)
|
||||
samples: manifest.samples.map((sample) => ({ name: sample.name, input: sample.input })),
|
||||
|
||||
Reference in New Issue
Block a user