merge: candidate activation-preview-precheck-2026-09-28 into dev
This commit is contained in:
@@ -2,10 +2,12 @@
|
||||
|
||||
维护规则:[问题记录与开发经验闭环](../ops/2026-09-23-issue-learning-workflow.md)。每个问题一个稳定 ID;主记录是事实与状态来源,本表为可检索投影。新发现及时登记,修复后保留记录;发布 blocker 仍见 [gap-matrix](../remediation/gap-matrix.md)。
|
||||
|
||||
更新:2026-09-28。非编制人触发小队账本提交被 `uow: permission denied` 拒绝(fixing,待 dev 重跑)。test 注册 INTERNAL:标准角色 role-guest 缺失导致 FK 23503,已补种并重启 test(goose 77,resolved)。重复用户名 400 在新代码单测通过;当前 rc 二进制仍返回 INTERNAL,随下次含本提交的 test 制品生效。管理台重置密码入口被 F4-A 误删(端点仍在),已恢复 UI(resolved)。2026-09-27。上传链路:登记并修复上传文档跨端不可访问(占位模板退化 + 元数据/正文不落服务端,fixing 待 dev 集成)。2026-09-26。docgov 批准链:登记并修复 review_snapshot_id 装载缺失与 uow 行锁自死锁(resolved);memDocs 持久化失败缓存投毒(investigating)。历史 resolved 不表示本日产品回归通过。系统 code review P0/P1 批次修复落账(5 resolved + 1 deferred + 1 open,主记录见 [整改文档](2026-09-26-system-code-review-fixes.md),来源 [审计](2026-09-26-system-code-review.md))。
|
||||
更新:2026-09-28。激活预览:未纳入人工审批的计划前端直接请求导致 400(fixing,待 dev 浏览器复验);管理台 5 个密钥框不在 form 内触发 password DOM 警告(fixing)。非编制人触发小队账本提交被 `uow: permission denied` 拒绝(fixing,待 dev 重跑)。test 注册 INTERNAL:标准角色 role-guest 缺失导致 FK 23503,已补种并重启 test(goose 77,resolved)。重复用户名 400 在新代码单测通过;当前 rc 二进制仍返回 INTERNAL,随下次含本提交的 test 制品生效。管理台重置密码入口被 F4-A 误删(端点仍在),已恢复 UI(resolved)。2026-09-27。上传链路:登记并修复上传文档跨端不可访问(占位模板退化 + 元数据/正文不落服务端,fixing 待 dev 集成)。2026-09-26。docgov 批准链:登记并修复 review_snapshot_id 装载缺失与 uow 行锁自死锁(resolved);memDocs 持久化失败缓存投毒(investigating)。历史 resolved 不表示本日产品回归通过。系统 code review P0/P1 批次修复落账(5 resolved + 1 deferred + 1 open,主记录见 [整改文档](2026-09-26-system-code-review-fixes.md),来源 [审计](2026-09-26-system-code-review.md))。
|
||||
|
||||
| issue_id | 模块 / 检索词 | 问题 | 严重度 | issue_state | owner | 证据日期 / 范围 | 唯一主记录 | 经验 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| ISSUE-20260928-activation-preview-precheck | web / projectSettings / activation-preview / NO_GOVERNED_DOCUMENT / clientDutyIssues | 全部文档未纳入人工审批时前端仍 POST activation-preview,控制台 400 | medium | fixing | Cursor | 2026-09-28;dev 项目 6022 令牌表仅 00:45:08 一行,首次保存后无令牌 | [主记录](../ops/short-term/2026-09-28-activation-preview-precheck.md#issue-20260928-activation-preview-precheck) | 待浏览器复验后补 |
|
||||
| ISSUE-20260928-admin-secret-password-form | web / EnvSettingsSection / password / form / secrets | 管理台 5 个密钥输入不在 form 内,Chromium 报 password DOM 警告 | low | fixing | Cursor | 2026-09-28;与 `secrets.*` 五条 Secret 设置数量一致 | [主记录](../ops/short-term/2026-09-28-activation-preview-precheck.md#issue-20260928-admin-secret-password-form) | 待浏览器复验后补 |
|
||||
| ISSUE-20260928-squad-commit-authority | bizdev / docflow / dutyAuthorizer / uow permission denied / OnBehalfOf / bd_intake | 非编制人触发受控小队任务时,账本提交用触发者身份,被编制人职责门拒绝,正文无法落盘 | high | fixing | Cursor | 2026-09-28;dev task_runs user 74 ≠ author 4(doc_proj6022_bd_intake);单测已绿,dev 重跑未做 | [主记录](../ops/short-term/2026-09-28-squad-commit-authority.md#issue-20260928-squad-commit-authority) | [LESSON-20260928-squad-commit-as-author](../ops/lessons/2026-09-28-squad-commit-as-author.md) |
|
||||
| ISSUE-20260928-register-internal | auth / register / role-guest / system_roles / 23503 / 23505 / INTERNAL | test 缺 role-guest 时注册 FK 失败被脱敏为 INTERNAL;重复用户名唯一冲突同样 500 | high | resolved | Cursor | 2026-09-28;test:8081 补种并重启后注册 200(goose 77,req_regfix_after_restart);重复用户名 400 由单测覆盖,rc 二进制仍为 INTERNAL | [主记录](../ops/short-term/2026-09-28-auth-register-internal.md#issue-20260928-register-internal) | [LESSON-20260928-standard-role-reseed](../ops/lessons/2026-09-28-standard-role-reseed.md) |
|
||||
| ISSUE-20260928-admin-reset-ui-removed | web / UsersSection / 重置密码 / F4-A / adminResetPassword | F4-A 在 F4-B 端点已合入的基线上删除管理台重置密码按钮,管理员无法重置口令 | high | resolved | Cursor | 2026-09-28;dev API :8080 + worktree Vite :3110 真实 UI 探针 PASS(临时口令长度 16、强制改密仅一次、旧口令 401) | [主记录](../ops/short-term/2026-09-28-restore-admin-password-reset.md#issue-20260928-admin-reset-ui-removed) | [LESSON-20260928-ui-dead-control](../ops/lessons/2026-09-28-ui-dead-control.md) |
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
| owner | task | branch | worktree | mutex | status |
|
||||
|---|---|---|---|---|---|
|
||||
| Cursor | activation-preview-precheck-2026-09-28 | fix/activation-preview-400-precheck-2026-09-28 | .worktrees/activation-preview-400-precheck-2026-09-28 | web-activation-preview | in_progress |
|
||||
| Cursor | squad-commit-authority-2026-09-28 | fix/squad-commit-authority-2026-09-28 | .worktrees/squad-commit-authority-2026-09-28 | bizdev+docflow | in_progress |
|
||||
| Cursor | auth-register-internal-2026-09-28 | fix/auth-register-internal-2026-09-28 | .worktrees/auth-register-internal-2026-09-28 | auth-register | done |
|
||||
| Cursor | restore-admin-password-reset-2026-09-28 | feature/restore-admin-password-reset-2026-09-28 | .worktrees/restore-admin-password-reset-2026-09-28 | auth | done |
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: "修复激活预览 400 与管理台密码框警告"
|
||||
task_id: activation-preview-precheck-2026-09-28
|
||||
status: in_progress
|
||||
created: 2026-09-28
|
||||
updated: 2026-09-28
|
||||
owner: Cursor
|
||||
task_type: fix
|
||||
policy_revision: "AGENTS.md + agent-worktree-workflow @ 2026-09-21"
|
||||
branch: fix/activation-preview-400-precheck-2026-09-28
|
||||
worktree: .worktrees/activation-preview-400-precheck-2026-09-28
|
||||
base_dev_sha: "b1ed8ecfe9fc5849288b6febf2bb4780319c8aca"
|
||||
write_scope:
|
||||
- web
|
||||
- .doc
|
||||
mutex: web-activation-preview
|
||||
review_state: pending
|
||||
integration_state: pending
|
||||
deployment: pending
|
||||
cleanup: pending
|
||||
remote_sync: not_requested
|
||||
---
|
||||
|
||||
# 修复激活预览 400 与管理台密码框警告
|
||||
|
||||
## 用户目标与授权
|
||||
|
||||
用户在 dev(`frp-mix.com:58880`)创建项目并添加「测试员工」后,浏览器控制台出现 `POST /api/v1/projects/6022/activation-preview 400`,以及 5 条 password 字段不在 form 内的 DOM 警告。授权:检查 dev 是否存在该问题并修复 dev。
|
||||
|
||||
## 检索
|
||||
|
||||
- 检索词:`activation-preview`、`NO_GOVERNED_DOCUMENT`、`clientDutyIssues`、`type="password"`。
|
||||
- 问题台账无 activation-preview / NO_GOVERNED 既有条目。
|
||||
- catalog overlap(mutex `web-activation-preview`)hard/soft 为空。登记时 `integrate-squad-commit-authority-2026-09-28` scope 为 `.`、`squad-commit-authority-2026-09-28` scope 为 `.doc`+`internal`,二者队列或分支均已是 dev 祖先且无活动 run,已 `task close --status done` 以释放写范围,未删除其 worktree。
|
||||
|
||||
## 事实
|
||||
|
||||
dev 库项目 6022「测试项目」(创建人 4,成员 74「测试员工」):`00:43:51` 保存规划 revision 1,激活令牌表无对应行;`00:45:08` 才发出令牌并在 `00:45:09` 激活。最终快照里仅 `bd_intake` 为 `author_only`,其余文档未纳入人工审批。服务端 `validateActivationPlan` 在零份完整职责时返回 400 `NO_GOVERNED_DOCUMENT`,行为符合门禁。缺口是前端 `clientDutyIssues` 不预检该门禁,用户可直接提交必然失败的计划。
|
||||
|
||||
5 条 DOM 警告对应管理台「模型与密钥」恰好 5 个 `Secret: true` 输入(`EnvSettingsSection`),`type=password` 且不在 `<form>` 内。登录与改密弹窗的密码框已在 form 内。
|
||||
|
||||
## ISSUE-20260928-activation-preview-precheck
|
||||
|
||||
issue_id: ISSUE-20260928-activation-preview-precheck
|
||||
issue_state: fixing
|
||||
module: web / projectSettings / activation-preview
|
||||
symptom: 全部文档未纳入人工审批时,点击「校验并预览」仍请求 activation-preview,控制台 400。
|
||||
cause: 前端预检未对齐 `validateActivationPlan` 的「至少一份完整人工审批」。
|
||||
fix: `needsGovernedDocument` 在非 live_edit 保存前本地拦截;配置页提示同一句文案。
|
||||
verification: `web` `npm test` 560 通过、`npm run build` 通过;dev 浏览器复验见测试报告。
|
||||
|
||||
## ISSUE-20260928-admin-secret-password-form
|
||||
|
||||
issue_id: ISSUE-20260928-admin-secret-password-form
|
||||
issue_state: fixing
|
||||
module: web / admin / EnvSettingsSection
|
||||
symptom: 打开模型与密钥页时 Chromium 报 5 次 Password field is not contained in a form。
|
||||
cause: 5 个密钥 input 使用 password 类型但没有 form。
|
||||
fix: 每个设置行包在 form 内,密钥框 `autoComplete=new-password`。
|
||||
verification: 随激活预检同一轮 dev 浏览器复验。
|
||||
@@ -181,13 +181,19 @@ export const EnvSettingsSection: React.FC = () => {
|
||||
{s.secret && !s.hasValue ? ' · 未配置' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', flexShrink: 0 }}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (canSave && savingKey !== s.key) void handleSave(s);
|
||||
}}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '10px', flexShrink: 0 }}
|
||||
>
|
||||
<input
|
||||
type={s.secret ? 'password' : 'text'}
|
||||
value={String(draft ?? '')}
|
||||
placeholder={s.secret ? (s.hasValue ? '留空保持原值 / 填写新值' : '填写密钥') : undefined}
|
||||
disabled={!s.writable}
|
||||
autoComplete="off"
|
||||
autoComplete={s.secret ? 'new-password' : 'off'}
|
||||
onChange={(e) => setDrafts((prev) => ({ ...prev, [s.key]: e.target.value }))}
|
||||
style={{
|
||||
width: '260px',
|
||||
@@ -202,9 +208,8 @@ export const EnvSettingsSection: React.FC = () => {
|
||||
/>
|
||||
{s.writable && (
|
||||
<button
|
||||
type="button"
|
||||
type="submit"
|
||||
disabled={!canSave || savingKey === s.key}
|
||||
onClick={() => void handleSave(s)}
|
||||
style={{
|
||||
padding: '6px 16px',
|
||||
borderRadius: '6px',
|
||||
@@ -219,7 +224,7 @@ export const EnvSettingsSection: React.FC = () => {
|
||||
{savingKey === s.key ? '保存中…' : '保存'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -33,7 +33,9 @@ import {
|
||||
clientDutyIssues,
|
||||
formatDutyIssues,
|
||||
locateDutyIssue,
|
||||
needsGovernedDocument,
|
||||
needsThreeMembersForDuties,
|
||||
NO_GOVERNED_DOCUMENT_MESSAGE,
|
||||
resolveClientDocTaskDuty,
|
||||
toggleAuthorOnly,
|
||||
toggleHumanApproval,
|
||||
@@ -226,6 +228,10 @@ export const ProjectSettingsModal: React.FC<ProjectSettingsModalProps> = ({
|
||||
setError(`当前项目只有 ${members.length} 名有效成员。纳入人工审批的文档需要分别指定编制、审核、批准负责人。`);
|
||||
return;
|
||||
}
|
||||
if (!liveEdit && !needsGovernedDocument(plan)) {
|
||||
setError(NO_GOVERNED_DOCUMENT_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
@@ -641,6 +647,20 @@ export const ProjectSettingsModal: React.FC<ProjectSettingsModalProps> = ({
|
||||
<p style={{ fontSize: '13px', color: 'var(--text-muted)', margin: 0 }}>
|
||||
允许裁剪的文档可选择是否纳入人工审批。关闭后仍会生成并进入版本账本,只是不走三权。
|
||||
</p>
|
||||
{!liveEdit && !needsGovernedDocument(plan) && (
|
||||
<div
|
||||
style={{
|
||||
padding: '10px 12px',
|
||||
background: 'var(--warning-50, #fffbeb)',
|
||||
border: '1px solid var(--warning-200, #fde68a)',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--warning-800, #92400e)',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
>
|
||||
{NO_GOVERNED_DOCUMENT_MESSAGE}。请勾选「纳入人工审批」并指定负责人;成员不足三人时可用「由编制人兼任审核、批准」。
|
||||
</div>
|
||||
)}
|
||||
{needsThreeMembersForDuties(plan, requireApprovalKeys) && members.length < 3 && (
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
classifyClientDuty,
|
||||
clientDutyIssues,
|
||||
locateDutyIssue,
|
||||
needsGovernedDocument,
|
||||
needsThreeMembersForDuties,
|
||||
resolveClientDocTaskDuty,
|
||||
toggleAuthorOnly,
|
||||
@@ -67,6 +68,36 @@ describe('projectSettingsDuties', () => {
|
||||
assert.equal(locateDutyIssue(switchedOn, 'research')?.code, 'DUTY_ASSIGNMENT_REQUIRED');
|
||||
});
|
||||
|
||||
it('blocks activation preview when no document is under human approval', () => {
|
||||
const unmanaged = plan([
|
||||
{ task_key: 'research', enabled: true, approval_configurable: true, duties: {} },
|
||||
{ task_key: 'quote', enabled: true, approval_configurable: true, duties: {} },
|
||||
]);
|
||||
assert.equal(needsGovernedDocument(unmanaged), false);
|
||||
assert.deepEqual(clientDutyIssues(unmanaged, [{ userId: 1 }]), []);
|
||||
|
||||
const authorOnly = plan([
|
||||
{
|
||||
task_key: 'intake',
|
||||
enabled: true,
|
||||
approval_configurable: true,
|
||||
duty_policy: 'author_only',
|
||||
duties: { author_user_id: 7 },
|
||||
},
|
||||
]);
|
||||
assert.equal(needsGovernedDocument(authorOnly), true);
|
||||
|
||||
const separated = plan([
|
||||
{
|
||||
task_key: 'intake',
|
||||
enabled: true,
|
||||
approval_configurable: true,
|
||||
duties: { author_user_id: 1, reviewer_user_id: 2, approver_user_id: 3 },
|
||||
},
|
||||
]);
|
||||
assert.equal(needsGovernedDocument(separated), true);
|
||||
});
|
||||
|
||||
it('allows an all-none configurable draft without three members', () => {
|
||||
const draft = plan([
|
||||
{ task_key: 'research', enabled: true, approval_configurable: true, duties: {} },
|
||||
|
||||
@@ -189,6 +189,17 @@ export function clientDutyIssues(
|
||||
return issues;
|
||||
}
|
||||
|
||||
/** 对齐服务端 validateActivationPlan:激活前至少一份文档为完整人工审批(含兼任派生的 A/A/A)。 */
|
||||
export const NO_GOVERNED_DOCUMENT_MESSAGE = '至少一份文档必须纳入人工审批才能激活工作流';
|
||||
|
||||
export function needsGovernedDocument(plan: WorkflowPlan): boolean {
|
||||
return plan.stages.some(
|
||||
(stage) =>
|
||||
stage.enabled &&
|
||||
stage.doc_tasks.some((task) => task.enabled && resolveClientDocTaskDuty(task).mode === 'full'),
|
||||
);
|
||||
}
|
||||
|
||||
export function needsThreeMembersForDuties(plan: WorkflowPlan, requireApprovalKeys: string[] = []): boolean {
|
||||
return plan.stages.some(
|
||||
(stage) =>
|
||||
|
||||
Reference in New Issue
Block a user