Compare commits
201 Commits
codex/back
...
638b757138
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
638b757138 | ||
|
|
b185e58f31 | ||
|
|
2a1a5991cb | ||
|
|
2a9b3e3273 | ||
|
|
7a3755ac55 | ||
|
|
aa6a7d1250 | ||
|
|
208e1bed59 | ||
|
|
140efd1a2a | ||
|
|
74fafc86d3 | ||
|
|
15da295963 | ||
|
|
044a5669fe | ||
|
|
c59990dc35 | ||
|
|
372e35d62a | ||
|
|
07241b4648 | ||
|
|
787bc3a481 | ||
|
|
242d68c36f | ||
|
|
28b834edd3 | ||
|
|
4940ebc419 | ||
|
|
ee88a36baf | ||
|
|
6bdf65bc24 | ||
|
|
ae3f02c35a | ||
|
|
54754b5502 | ||
|
|
211f85d981 | ||
|
|
5b24630710 | ||
|
|
a662cfe6c3 | ||
|
|
5ed34c2b8f | ||
|
|
11275e4ba6 | ||
|
|
1347366b95 | ||
|
|
22669a9071 | ||
|
|
a616b30cb2 | ||
|
|
653eda0596 | ||
|
|
661990b27b | ||
|
|
9a84e125d0 | ||
|
|
3a5fc3c09f | ||
|
|
c7ba7bb453 | ||
|
|
67c3f30eb2 | ||
|
|
73aee622c7 | ||
|
|
52d57c3be7 | ||
|
|
3a9d154783 | ||
|
|
765cfb40f3 | ||
|
|
08f023243e | ||
|
|
6bdaeed6d4 | ||
|
|
d5a8f84703 | ||
|
|
c4b5fcc067 | ||
|
|
5753899eb3 | ||
|
|
9c3fa80d22 | ||
|
|
43c3ff860c | ||
|
|
3e4b1e1597 | ||
|
|
3a5664c4da | ||
|
|
d139a63e64 | ||
|
|
8a2ae6eb75 | ||
|
|
992cf71fa1 | ||
|
|
54356ba81a | ||
|
|
e9d7c56d5b | ||
|
|
2ebc2756bf | ||
|
|
606a88c805 | ||
|
|
eaada4bc57 | ||
|
|
d321005044 | ||
|
|
59353308a2 | ||
|
|
6b0756a55f | ||
|
|
4d8a606cd6 | ||
|
|
23f7de6cbf | ||
|
|
a12c4bea64 | ||
|
|
e5b03c6601 | ||
|
|
3eb78d343a | ||
|
|
a0f6d9f702 | ||
|
|
bb681aa1f3 | ||
|
|
bc560145a4 | ||
|
|
5311c99d69 | ||
|
|
545b31d32f | ||
|
|
8417a9f542 | ||
|
|
9a5ed0e94a | ||
|
|
50d2dc579a | ||
|
|
f9553a6a1a | ||
|
|
ee730aa31c | ||
|
|
0264a4b5b4 | ||
|
|
332f77389d | ||
|
|
d4ff79f326 | ||
|
|
93212600eb | ||
|
|
73966b3a7b | ||
|
|
1f40ce3df3 | ||
|
|
f17098aa58 | ||
|
|
8094333e3b | ||
|
|
0122f3b250 | ||
|
|
dc4cad2baa | ||
|
|
e725b7f19c | ||
|
|
84a8998e59 | ||
|
|
bc743adef3 | ||
|
|
ded8b39ccb | ||
|
|
ba444a514f | ||
|
|
aa965da69d | ||
|
|
1b04ee1c4c | ||
|
|
103f225f54 | ||
|
|
e42dedaba1 | ||
|
|
607e127f59 | ||
|
|
6d33ba5742 | ||
|
|
08a4fa3577 | ||
|
|
d660a961fb | ||
|
|
669d22e71f | ||
|
|
88e91a5900 | ||
|
|
1986b0d945 | ||
|
|
24b5b71b0f | ||
|
|
8b3495455b | ||
|
|
3b74a330a3 | ||
|
|
8158716e23 | ||
|
|
0cda750ff0 | ||
|
|
81e990ab72 | ||
|
|
47c6a4bb73 | ||
|
|
96c2e1099a | ||
|
|
729d833edb | ||
|
|
304bbe1fd4 | ||
|
|
3d69f8501f | ||
|
|
4d04f4e7af | ||
|
|
3131112952 | ||
|
|
a2f67af13e | ||
|
|
0cde1f8990 | ||
|
|
a6674a1e76 | ||
|
|
127d603e7d | ||
|
|
3f17619e0c | ||
|
|
59ba76c74a | ||
|
|
35372c6661 | ||
|
|
38653fa365 | ||
|
|
c28e99b714 | ||
|
|
43432534d8 | ||
|
|
cce19e4c40 | ||
|
|
b8915a29c0 | ||
|
|
4199feb681 | ||
|
|
0fac8b615f | ||
|
|
a3e5295915 | ||
|
|
1f4681f486 | ||
|
|
09a66c72cb | ||
|
|
0d525fa64c | ||
|
|
470f343b29 | ||
|
|
9f7b8b46a3 | ||
|
|
792741709a | ||
|
|
5747e85acf | ||
|
|
8b952c9a26 | ||
| 336fee9d93 | |||
|
|
25724c354f | ||
|
|
e124e4bbcb | ||
|
|
f60cebadb8 | ||
|
|
1cbf3fee44 | ||
|
|
87da5df91b | ||
|
|
75d5c178e1 | ||
|
|
b9826a1985 | ||
|
|
0f8bc4071a | ||
|
|
cb36d78fa2 | ||
|
|
8e2477587f | ||
|
|
67b81a1bd8 | ||
|
|
9c24a852e7 | ||
|
|
95956afbc6 | ||
|
|
c73178b65d | ||
|
|
8c2f301d85 | ||
|
|
4717ee6086 | ||
|
|
513ff909f9 | ||
|
|
92198549f6 | ||
|
|
59d3bf0f00 | ||
|
|
04f0951b3d | ||
|
|
8887cf5a27 | ||
|
|
34457f9c3e | ||
|
|
e12b140508 | ||
|
|
18d716bc6b | ||
|
|
74d488adfa | ||
|
|
31052d0b98 | ||
|
|
20cb60e247 | ||
|
|
3130c42d76 | ||
|
|
6fc5e66ea1 | ||
|
|
27dd2f0a0d | ||
|
|
faa39e6c06 | ||
|
|
d060f89d30 | ||
|
|
0d6327a990 | ||
|
|
15006a05a7 | ||
|
|
0c74b4ab4a | ||
|
|
ca691f3ee0 | ||
|
|
92444e7eae | ||
|
|
7989f3a159 | ||
|
|
4c59941ec6 | ||
|
|
678f64d772 | ||
|
|
e080105f9f | ||
|
|
64cc76c970 | ||
|
|
99e90798d2 | ||
|
|
064eeb614f | ||
|
|
b383244a29 | ||
|
|
e384318046 | ||
|
|
8a4a777be7 | ||
|
|
04cd6d0f81 | ||
|
|
d4d5d40569 | ||
|
|
cbb98f4469 | ||
|
|
7d32eae74e | ||
|
|
b1a9c8a194 | ||
|
|
2dcc72102d | ||
|
|
df49103f23 | ||
|
|
e7bef0883d | ||
|
|
e1e515ecae | ||
|
|
0e861d8fa6 | ||
|
|
d0e946cf47 | ||
|
|
50b1c3f9a9 | ||
|
|
575f093c74 | ||
|
|
5b388d08c0 | ||
|
|
88ff04bef8 | ||
|
|
1f15699013 |
160
.codex/skills/agent-change-log/SKILL.md
Normal file
160
.codex/skills/agent-change-log/SKILL.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
---
|
||||||
|
name: agent-change-log
|
||||||
|
description: Use when working in X-Financial after bug fixes that need a split dev log, when maintaining document/development/YYYY-MM-DD/dev-logs/bugs, or when generating the daily 17:00 combined work-logs.med from same-day feature docs and bug logs.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agent Change Log
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This skill keeps split development logs for X-Financial. Bug fixes are recorded under the same-day `dev-logs/bugs` folder. Daily summaries combine same-day feature documents and bug records into one `work-logs.med`.
|
||||||
|
|
||||||
|
The log should sound like a careful teammate writing for tomorrow's teammate: concrete, warm, and honest.
|
||||||
|
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
- After fixing a bug.
|
||||||
|
- When a post-commit hook detects a bug-like commit.
|
||||||
|
- At the daily 17:00 summary pass.
|
||||||
|
- Before updating the log in a branch where other agents may have pushed commits.
|
||||||
|
- After verification, so the entry can include what was actually checked.
|
||||||
|
- When a failed attempt changed files, generated artifacts, or revealed a risk worth preserving.
|
||||||
|
|
||||||
|
Do not create legacy `document/work-log/YYYY-MM-DD.md` entries for new work.
|
||||||
|
|
||||||
|
## Log Location
|
||||||
|
|
||||||
|
Use the same date root as development documents:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/YYYY-MM-DD/
|
||||||
|
├── feature/<feature-point>/CONCEPT.md + TODO.md
|
||||||
|
├── dev-logs/bugs/<bug-slug>.md
|
||||||
|
└── work-logs.med
|
||||||
|
```
|
||||||
|
|
||||||
|
If the date folder exists, reuse it. If not, create it.
|
||||||
|
|
||||||
|
## Bug Log Structure
|
||||||
|
|
||||||
|
For bug fixes, create or update one file per bug:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The file must contain `## 修复记录` and timestamped bullets similar to the old `当日工作内容`.
|
||||||
|
Do not add `遗留问题` or `TODO` sections to bug logs.
|
||||||
|
|
||||||
|
Use the helper when possible:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/agent-change-log/update_change_log.py \
|
||||||
|
--kind bug \
|
||||||
|
--bug-title "<bug 名称>" \
|
||||||
|
--bug-slug <bug-slug>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Required Git Check
|
||||||
|
|
||||||
|
Before writing or updating a bug log, manually check Git for upstream and local-ahead commits from other agents.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
date '+%Y-%m-%d %H:%M:%S %Z'
|
||||||
|
git fetch --all --prune
|
||||||
|
git status -sb
|
||||||
|
git rev-parse --abbrev-ref --symbolic-full-name @{u}
|
||||||
|
git log --oneline --decorate --max-count=20 HEAD..@{u}
|
||||||
|
git log --oneline --decorate --max-count=20 @{u}..HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Treat `git fetch --all --prune` as the default safe "pull check"; it updates remote refs without merging into a dirty worktree.
|
||||||
|
- Treat `HEAD..@{u}` as upstream commits not yet in local history.
|
||||||
|
- Treat `@{u}..HEAD` as local commits not yet in upstream history; these may also come from another agent working in the same checkout.
|
||||||
|
- If the worktree is clean and the branch is only behind upstream, `git pull --ff-only` may be used to fast-forward before analysis.
|
||||||
|
- If the worktree is dirty, diverged, or likely to conflict, do not merge/rebase automatically. Record the upstream commits from `HEAD..@{u}` in the bug fix entry.
|
||||||
|
- If there is no upstream branch, record that fact in the bug fix entry and continue with local-only logging.
|
||||||
|
- When `HEAD..@{u}` has commits, summarize those commits in the bug fix entry before describing local edits. Mention commit hash, subject, and inferred impact.
|
||||||
|
- When `@{u}..HEAD` has commits that were not created in the current task, summarize them too, because another local agent may have committed without pushing yet.
|
||||||
|
- When no upstream or local-ahead commits exist, still record "Git 提交检查:未发现 upstream 新提交或本地 ahead 新提交" in the work entry.
|
||||||
|
|
||||||
|
## Bug Entry Rules
|
||||||
|
|
||||||
|
1. Get the current local time first:
|
||||||
|
```bash
|
||||||
|
date '+%Y-%m-%d %H:%M:%S %Z'
|
||||||
|
```
|
||||||
|
2. Run the required Git check and capture whether upstream or local-ahead has new commits.
|
||||||
|
3. Ensure `document/development/YYYY-MM-DD/dev-logs/bugs/` exists.
|
||||||
|
4. Create or update one `<bug-slug>.md` file for the specific bug.
|
||||||
|
5. Append a new timestamped bullet under `## 修复记录`.
|
||||||
|
6. Mention Git commits, changed files or modules, the operation, the intent, and the verification result.
|
||||||
|
7. Do not add leftover issue or TODO sections.
|
||||||
|
|
||||||
|
## Daily Summary
|
||||||
|
|
||||||
|
At 17:00 every day, generate the combined work log:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/agent-change-log/update_change_log.py --kind summary
|
||||||
|
```
|
||||||
|
|
||||||
|
This reads:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/YYYY-MM-DD/feature/
|
||||||
|
document/development/YYYY-MM-DD/dev-logs/bugs/
|
||||||
|
```
|
||||||
|
|
||||||
|
Then writes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/YYYY-MM-DD/work-logs.med
|
||||||
|
```
|
||||||
|
|
||||||
|
The summary should cover:
|
||||||
|
|
||||||
|
- Today's feature points from `feature/*/CONCEPT.md` and `TODO.md`.
|
||||||
|
- Today's bug fixes from `dev-logs/bugs/*.md`.
|
||||||
|
- A concise combined analysis of what changed that day.
|
||||||
|
|
||||||
|
## Entry Rules
|
||||||
|
|
||||||
|
- For each bug entry, append a new timestamped bullet under `## 修复记录`.
|
||||||
|
- Mention Git commits, changed files or modules, the operation, the intent, and the verification result.
|
||||||
|
- Do not write `遗留问题`.
|
||||||
|
- Do not write `TODO`.
|
||||||
|
- If the change is a feature rather than a bug, use the development document skill to keep `feature/<feature-point>/CONCEPT.md` and `TODO.md` current instead of writing a bug log.
|
||||||
|
|
||||||
|
## Writing Style
|
||||||
|
|
||||||
|
- Write in Simplified Chinese.
|
||||||
|
- Be specific and a little human: "我把...", "这次先...", "还需要留意..." are good.
|
||||||
|
- Keep the tone factual. Do not turn the log into a victory lap.
|
||||||
|
- Prefer concise file names and module names in prose, but include enough context to find the change.
|
||||||
|
- Work content should be detailed enough that a future agent can continue without asking "你到底改了啥?"
|
||||||
|
|
||||||
|
## Bug Entry Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- HH:MM:记录 bug 修复:<bug 名称>。
|
||||||
|
- Git 提交检查:<git fetch 后 HEAD..upstream 与 upstream..HEAD 的结果;没有就写未发现 upstream 或本地 ahead 新提交>。
|
||||||
|
- 修改:<文件/模块>,<做了什么>。
|
||||||
|
- 操作:<运行了什么命令、迁移了什么状态、重启了什么服务等>。
|
||||||
|
- 验证:<测试/构建/检查结果;如果没跑,说明原因>。
|
||||||
|
- 影响:<用户可见变化或工程边界变化>。
|
||||||
|
```
|
||||||
|
|
||||||
|
## Final Response Checklist
|
||||||
|
|
||||||
|
Before saying work is complete:
|
||||||
|
|
||||||
|
- For bug fixes, today's bug log exists under `document/development/YYYY-MM-DD/dev-logs/bugs/`.
|
||||||
|
- For non-bug feature work, relevant `feature/<feature-point>` documents are current.
|
||||||
|
- Git check ran for bug logs, and upstream plus local-ahead commits were summarized or explicitly marked as absent.
|
||||||
|
- No new legacy `document/work-log/YYYY-MM-DD.md` entry was created.
|
||||||
|
- The final response mentions whether a bug log, feature document, or daily `work-logs.med` was updated.
|
||||||
4
.codex/skills/agent-change-log/agents/openai.yaml
Normal file
4
.codex/skills/agent-change-log/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Agent Change Log"
|
||||||
|
short_description: "Record bug logs and daily work summaries"
|
||||||
|
default_prompt: "Use $agent-change-log after an X-Financial bug fix or at 17:00 to update document/development/<date>/dev-logs/bugs or work-logs.med."
|
||||||
85
.codex/skills/git-checkpoint-commit/SKILL.md
Normal file
85
.codex/skills/git-checkpoint-commit/SKILL.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
name: git-checkpoint-commit
|
||||||
|
description: Use when a coding task finishes a verified bug fix, key feature, risky refactor checkpoint, local backup commit, checkpoint commit, 提交备份, 本地提交, or when an active session has accumulated about five meaningful edit rounds and should create a scoped local git commit without pushing.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git Checkpoint Commit
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Use this skill to keep a local git backup loop during active development. The goal is to commit verified, task-scoped progress at meaningful checkpoints without mixing unrelated user changes or pushing remotely.
|
||||||
|
|
||||||
|
## Trigger Rules
|
||||||
|
|
||||||
|
Create a local checkpoint commit when any condition is true:
|
||||||
|
|
||||||
|
- A bug fix is implemented and the targeted regression check passes.
|
||||||
|
- A key feature slice is implemented and the smallest relevant verification passes.
|
||||||
|
- A risky refactor reaches a behavior-preserving checkpoint.
|
||||||
|
- The current session has reached about five meaningful edit rounds since the last commit.
|
||||||
|
- The user asks for `提交`, `本地提交`, `备份`, `checkpoint`, `commit`, or `形成提交循环`.
|
||||||
|
|
||||||
|
Do not use this skill when the user explicitly says not to commit, when the change is exploratory and unverified, or when the only available commit would include unrelated dirty files.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Inspect the working tree with `git status --short`.
|
||||||
|
2. Identify files or hunks owned by the current task.
|
||||||
|
3. Run the smallest relevant verification first.
|
||||||
|
- In X-Financial, run backend tests inside `x-financial-main` when backend code is involved.
|
||||||
|
- Use targeted frontend tests/builds for web-only changes.
|
||||||
|
4. Commit only the task-owned files.
|
||||||
|
5. Report the commit hash and verification evidence.
|
||||||
|
6. Reset the session edit counter to zero after a successful checkpoint.
|
||||||
|
|
||||||
|
## Safety Rules
|
||||||
|
|
||||||
|
- Never commit unrelated user changes just to make the tree clean.
|
||||||
|
- Never push as part of this skill.
|
||||||
|
- Never rewrite history, amend old commits, or run destructive git commands.
|
||||||
|
- If the same file contains unrelated hunks, split the staging carefully with `git add -p` or a narrower manual patch.
|
||||||
|
- If the index already contains staged changes, inspect them first; do not mix them into a checkpoint unless they belong to the same current task.
|
||||||
|
- If verification cannot run, say why in the commit body or final report and prefer a `chore(checkpoint)` message rather than a confident `fix` or `feat`.
|
||||||
|
|
||||||
|
## Commit Style
|
||||||
|
|
||||||
|
Use normal semantic messages for completed, verified work:
|
||||||
|
|
||||||
|
- `fix(workbench): keep application preview after draft save failure`
|
||||||
|
- `feat(reimbursements): add attachment association job polling`
|
||||||
|
- `refactor(claims): split draft flow serialization`
|
||||||
|
|
||||||
|
Use checkpoint messages for interim backup points:
|
||||||
|
|
||||||
|
- `chore(checkpoint): backup attachment association flow`
|
||||||
|
- `chore(checkpoint): backup after five edit rounds`
|
||||||
|
|
||||||
|
Keep the subject concise. Add body lines only when the verification state or scope needs clarity.
|
||||||
|
|
||||||
|
## Helper Script
|
||||||
|
|
||||||
|
Use `scripts/checkpoint_commit.py` when a path-scoped local commit is enough:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 .codex/skills/git-checkpoint-commit/scripts/checkpoint_commit.py \
|
||||||
|
--message "chore(checkpoint): backup workbench AI flow" \
|
||||||
|
web/src/composables/workbenchAiMode/useWorkbenchAiExpenseFlow.js \
|
||||||
|
web/tests/workbench-ai-mode-switch.test.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
Preview before committing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 .codex/skills/git-checkpoint-commit/scripts/checkpoint_commit.py \
|
||||||
|
--dry-run \
|
||||||
|
web/src/utils/expenseApplicationPreview.js
|
||||||
|
```
|
||||||
|
|
||||||
|
The script refuses to commit the whole tree unless `--allow-all` is passed. Use `--allow-all` only when the current task truly owns every dirty file.
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
- Mistaking backup for verification. Verify first, then commit.
|
||||||
|
- Staging `.` in a dirty worktree. Stage explicit paths or hunks.
|
||||||
|
- Combining documentation cleanup, feature work, and unrelated local edits in one checkpoint.
|
||||||
|
- Continuing indefinitely after multiple verified slices. Commit once the counter reaches five meaningful edit rounds.
|
||||||
4
.codex/skills/git-checkpoint-commit/agents/openai.yaml
Normal file
4
.codex/skills/git-checkpoint-commit/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Git Checkpoint Commit"
|
||||||
|
short_description: "Create local backup commits at safe milestones"
|
||||||
|
default_prompt: "Use $git-checkpoint-commit to checkpoint verified task changes into a local git commit."
|
||||||
127
.codex/skills/git-checkpoint-commit/scripts/checkpoint_commit.py
Normal file
127
.codex/skills/git-checkpoint-commit/scripts/checkpoint_commit.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Create a path-scoped local git checkpoint commit."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def run_git(args: list[str], cwd: Path, *, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", *args],
|
||||||
|
cwd=cwd,
|
||||||
|
text=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
if check and result.returncode != 0:
|
||||||
|
message = result.stderr.strip() or result.stdout.strip()
|
||||||
|
raise SystemExit(f"git {' '.join(args)} failed: {message}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def repo_root() -> Path:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "rev-parse", "--show-toplevel"],
|
||||||
|
text=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise SystemExit("Not inside a git repository.")
|
||||||
|
return Path(result.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def load_paths(args: argparse.Namespace) -> list[str]:
|
||||||
|
paths = list(args.paths)
|
||||||
|
if args.paths_from_file:
|
||||||
|
raw_lines = Path(args.paths_from_file).read_text(encoding="utf-8").splitlines()
|
||||||
|
paths.extend(line.strip() for line in raw_lines if line.strip() and not line.startswith("#"))
|
||||||
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_clean_index(root: Path) -> None:
|
||||||
|
staged = run_git(["diff", "--cached", "--name-only"], root).stdout.strip()
|
||||||
|
if staged:
|
||||||
|
raise SystemExit(
|
||||||
|
"Refusing to continue because the index already has staged changes:\n"
|
||||||
|
f"{staged}\n"
|
||||||
|
"Commit or unstage them before running this checkpoint helper."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def status_for(root: Path, paths: list[str], allow_all: bool) -> str:
|
||||||
|
command = ["status", "--short"]
|
||||||
|
if not allow_all:
|
||||||
|
command.extend(["--", *paths])
|
||||||
|
return run_git(command, root).stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def infer_message(paths: list[str], allow_all: bool) -> str:
|
||||||
|
if allow_all or not paths:
|
||||||
|
return "chore(checkpoint): backup current task changes"
|
||||||
|
first_parts = [Path(item).parts[0] for item in paths if Path(item).parts]
|
||||||
|
scope = first_parts[0] if first_parts else "task"
|
||||||
|
return f"chore(checkpoint): backup {scope} changes"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("paths", nargs="*", help="Task-owned paths to stage and commit.")
|
||||||
|
parser.add_argument("-m", "--message", help="Commit message subject/body.")
|
||||||
|
parser.add_argument("--paths-from-file", help="Read additional pathspecs from a UTF-8 text file.")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Show matching changes without staging.")
|
||||||
|
parser.add_argument("--allow-all", action="store_true", help="Allow committing all dirty files.")
|
||||||
|
parser.add_argument("--no-verify", action="store_true", help="Pass --no-verify to git commit.")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
root = repo_root()
|
||||||
|
paths = load_paths(args)
|
||||||
|
|
||||||
|
if not paths and not args.allow_all:
|
||||||
|
raise SystemExit("Pass explicit paths, or use --allow-all when the current task owns all changes.")
|
||||||
|
|
||||||
|
current_status = status_for(root, paths, args.allow_all)
|
||||||
|
if not current_status:
|
||||||
|
print("No matching changes to commit.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(current_status)
|
||||||
|
if args.dry_run:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
ensure_clean_index(root)
|
||||||
|
|
||||||
|
# 使用 -A 保留删除/重命名等变更,但只作用于明确传入的 pathspec。
|
||||||
|
if args.allow_all:
|
||||||
|
run_git(["add", "-A"], root)
|
||||||
|
else:
|
||||||
|
run_git(["add", "-A", "--", *paths], root)
|
||||||
|
|
||||||
|
staged_summary = run_git(["diff", "--cached", "--name-status"], root).stdout.strip()
|
||||||
|
if not staged_summary:
|
||||||
|
raise SystemExit("No staged changes after git add.")
|
||||||
|
|
||||||
|
message = args.message or infer_message(paths, args.allow_all)
|
||||||
|
commit_command = ["commit"]
|
||||||
|
if args.no_verify:
|
||||||
|
commit_command.append("--no-verify")
|
||||||
|
commit_command.extend(["-m", message])
|
||||||
|
commit_result = run_git(commit_command, root)
|
||||||
|
sys.stdout.write(commit_result.stdout)
|
||||||
|
|
||||||
|
commit_hash = run_git(["rev-parse", "--short", "HEAD"], root).stdout.strip()
|
||||||
|
print(f"checkpoint_commit={commit_hash}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
125
.codex/skills/write-development-docs/SKILL.md
Normal file
125
.codex/skills/write-development-docs/SKILL.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
name: write-development-docs
|
||||||
|
description: Use when working in X-Financial and the user asks to 落文档, 写开发文档, 沉淀方案, 补 concept/todo, or create/update planning documentation under document/development for a feature, refactor, page, algorithm, rule, or business capability.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Write Development Docs
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
把一个功能、重构、算法、页面或业务能力沉淀为项目标准开发文档。
|
||||||
|
默认落点固定为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/<YYYY-MM-DD>/feature/<具体功能点目录>/
|
||||||
|
├── CONCEPT.md
|
||||||
|
└── TODO.md
|
||||||
|
```
|
||||||
|
|
||||||
|
如果用户说 `concept.md` / `todo.md`,也按仓库现有规范使用大写文件名
|
||||||
|
`CONCEPT.md` 和 `TODO.md`。
|
||||||
|
|
||||||
|
## 工作流
|
||||||
|
|
||||||
|
1. 先读取当前日期,默认使用本地日期 `YYYY-MM-DD` 作为第一层目录。
|
||||||
|
2. 先阅读 `document/development` 中 2-3 组相邻或同类样例。
|
||||||
|
3. 再读取本次能力相关的代码、接口、页面、测试或历史文档。
|
||||||
|
4. 创建或更新 `CONCEPT.md`,先写清业务边界,再写方案和验收。
|
||||||
|
5. 创建或更新 `TODO.md`,每个任务都要回链到 `CONCEPT.md` 的章节。
|
||||||
|
6. 已实现或已验证的 TODO 可以勾选 `[x]`,但必须写证据。
|
||||||
|
7. 交付前检查两份文档互相一致,不额外创建 README、CHANGELOG、SUMMARY。
|
||||||
|
|
||||||
|
## 路径规则
|
||||||
|
|
||||||
|
- 第一层必须是日期目录,例如 `document/development/2026-06-25/`。
|
||||||
|
- 第二层固定是 `feature/`,表示当天沉淀的功能点集合。
|
||||||
|
- 第三层是具体功能点目录,每个独立功能点一个目录。
|
||||||
|
- 每个具体功能点目录内只放 `CONCEPT.md` 和 `TODO.md` 两个核心文件。
|
||||||
|
- 如果一次请求包含多个互不依赖的功能点,拆成多个兄弟目录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document/development/2026-06-25/feature/
|
||||||
|
├── receipt-folder-ocr/
|
||||||
|
│ ├── CONCEPT.md
|
||||||
|
│ └── TODO.md
|
||||||
|
└── risk-review-nudge/
|
||||||
|
├── CONCEPT.md
|
||||||
|
└── TODO.md
|
||||||
|
```
|
||||||
|
|
||||||
|
- 如果用户明确指定日期,使用用户指定日期;否则使用当前本地日期。
|
||||||
|
- 如果是更新历史文档,先查找已有目录并原地更新,不自动迁移旧路径。
|
||||||
|
|
||||||
|
## 目录命名
|
||||||
|
|
||||||
|
- 优先复用用户指定目录名或已有目录名。
|
||||||
|
- 新增英文目录用小写 kebab-case,例如 `receipt-folder`。
|
||||||
|
- 新增中文目录可直接用清晰中文能力名,例如 `费用审批动态路由`。
|
||||||
|
- 同一能力已有目录时更新原目录,不新建近义目录。
|
||||||
|
|
||||||
|
## CONCEPT.md 要求
|
||||||
|
|
||||||
|
可参考 `assets/CONCEPT.md` 模板。必须包含:
|
||||||
|
|
||||||
|
- 标题:`# <功能名> 概念文档`
|
||||||
|
- `更新时间:YYYY-MM-DD`
|
||||||
|
- `## 功能一句话`
|
||||||
|
- `## 背景与问题`
|
||||||
|
- `## 目标与非目标`
|
||||||
|
- `## 用户与场景`
|
||||||
|
- `## 功能能力`
|
||||||
|
- `## 方案设计`
|
||||||
|
- `## 算法与公式`
|
||||||
|
- `## 测试方案`
|
||||||
|
- `## 指标与验收`
|
||||||
|
- `## 风险与开放问题`
|
||||||
|
|
||||||
|
写法要求:
|
||||||
|
|
||||||
|
- 先讲业务问题和边界,再讲技术方案。
|
||||||
|
- 目标与非目标分开写,避免需求无限扩张。
|
||||||
|
- 方案设计按前端、后端、算法/规则、数据、权限、降级策略分块;
|
||||||
|
不涉及的块明确写“当前不涉及”。
|
||||||
|
- 算法与公式必须明确“不涉及”或写出公式、变量说明和适用边界。
|
||||||
|
- 验收标准必须可验证,不写空泛口号。
|
||||||
|
|
||||||
|
## TODO.md 要求
|
||||||
|
|
||||||
|
可参考 `assets/TODO.md` 模板。必须包含:
|
||||||
|
|
||||||
|
- 标题:`# <功能名> 开发 TODO`
|
||||||
|
- `更新时间:YYYY-MM-DD`
|
||||||
|
- `## 使用规则`
|
||||||
|
- 分阶段 checklist
|
||||||
|
|
||||||
|
TODO 条目规则:
|
||||||
|
|
||||||
|
- 每条用 `- [ ]` 或 `- [x]`。
|
||||||
|
- 每条必须包含 `[CONCEPT: <章节名>]`。
|
||||||
|
- 已完成项必须补证据,格式为 `证据:<文件、接口、命令或验证结果>`。
|
||||||
|
- 没有真实证据时不得勾选 `[x]`。
|
||||||
|
|
||||||
|
建议阶段:
|
||||||
|
|
||||||
|
- `## 1. 调研与边界`
|
||||||
|
- `## 2. 契约与设计`
|
||||||
|
- `## 3. 后端实现`
|
||||||
|
- `## 4. 算法/规则实现`
|
||||||
|
- `## 5. 前端实现`
|
||||||
|
- `## 6. 测试与验证`
|
||||||
|
- `## 7. 文档收尾`
|
||||||
|
|
||||||
|
## 更新既有文档
|
||||||
|
|
||||||
|
- 先读现有 `CONCEPT.md` 和 `TODO.md` 全文。
|
||||||
|
- 新需求先补 `CONCEPT.md`,再补 `TODO.md`。
|
||||||
|
- 实现变化时同步更新“非目标”“风险与开放问题”“本轮实现记录”。
|
||||||
|
- 不删除历史证据;除非证据明显错误,才替换为新证据。
|
||||||
|
|
||||||
|
## 验收检查
|
||||||
|
|
||||||
|
- 新建文档路径符合 `document/development/<YYYY-MM-DD>/feature/<具体功能点目录>/`。
|
||||||
|
- `CONCEPT.md` 和 `TODO.md` 都存在于同一个具体功能点目录。
|
||||||
|
- TODO 的 `[CONCEPT: ...]` 都能在 CONCEPT 中找到对应章节或语义段落。
|
||||||
|
- 已勾选项都有证据。
|
||||||
|
- 文档没有遗留模板占位符,例如 `<功能名>`、`<YYYY-MM-DD>`、`待补充`。
|
||||||
4
.codex/skills/write-development-docs/agents/openai.yaml
Normal file
4
.codex/skills/write-development-docs/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "开发文档落地"
|
||||||
|
short_description: "按日期和功能点生成 CONCEPT/TODO"
|
||||||
|
default_prompt: "Use $write-development-docs to create CONCEPT.md and TODO.md under document/development/<date>/feature/<feature-point> for an X-Financial feature."
|
||||||
132
.codex/skills/write-development-docs/assets/CONCEPT.md
Normal file
132
.codex/skills/write-development-docs/assets/CONCEPT.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# <功能名> 概念文档
|
||||||
|
|
||||||
|
更新时间:<YYYY-MM-DD>
|
||||||
|
|
||||||
|
文档路径:document/development/<YYYY-MM-DD>/feature/<具体功能点目录>/CONCEPT.md
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
用一句话说明这个能力解决什么问题、服务谁、交付什么结果。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
- 当前现状:
|
||||||
|
- 用户痛点:
|
||||||
|
- 业务影响:
|
||||||
|
- 为什么现在需要做:
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- [G1]
|
||||||
|
- [G2]
|
||||||
|
- [G3]
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- [NG1] 本轮不做:
|
||||||
|
- [NG2] 本轮不改变:
|
||||||
|
- [NG3] 后续再评估:
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
- 目标用户:
|
||||||
|
- 使用入口:
|
||||||
|
- 核心场景:
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
- 异常场景:
|
||||||
|
-
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- [C1] 输入能力:
|
||||||
|
- [C2] 处理能力:
|
||||||
|
- [C3] 输出能力:
|
||||||
|
- [C4] 状态与权限:
|
||||||
|
- [C5] 边界与降级:
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 页面/组件:
|
||||||
|
- 交互状态:
|
||||||
|
- 展示规则:
|
||||||
|
- 降级处理:
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- 接口/服务:
|
||||||
|
- 权限与校验:
|
||||||
|
- 持久化:
|
||||||
|
- 降级处理:
|
||||||
|
|
||||||
|
### 算法与规则
|
||||||
|
|
||||||
|
- 输入:
|
||||||
|
- 流程:
|
||||||
|
- 输出:
|
||||||
|
- 解释:
|
||||||
|
|
||||||
|
### 数据与契约
|
||||||
|
|
||||||
|
- 核心字段:
|
||||||
|
- 状态枚举:
|
||||||
|
- 兼容策略:
|
||||||
|
- 版本/审计:
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
当前功能不涉及显式数学公式。
|
||||||
|
|
||||||
|
如涉及公式,使用如下格式:
|
||||||
|
|
||||||
|
```text
|
||||||
|
metric = input_a + input_b
|
||||||
|
```
|
||||||
|
|
||||||
|
变量说明:
|
||||||
|
|
||||||
|
- metric:
|
||||||
|
- input_a:
|
||||||
|
- input_b:
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
后端:
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
前端:
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
集成:
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
手工验证:
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- [A1] 功能验收:
|
||||||
|
- [A2] 性能指标:
|
||||||
|
- [A3] 质量指标:
|
||||||
|
- [A4] 安全/权限指标:
|
||||||
|
- [A5] 可观测性:
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
- 风险:
|
||||||
|
- 已处理依赖:
|
||||||
|
- 待确认:
|
||||||
|
- 降级策略:
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
-
|
||||||
73
.codex/skills/write-development-docs/assets/TODO.md
Normal file
73
.codex/skills/write-development-docs/assets/TODO.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# <功能名> 开发 TODO
|
||||||
|
|
||||||
|
更新时间:<YYYY-MM-DD>
|
||||||
|
|
||||||
|
文档路径:document/development/<YYYY-MM-DD>/feature/<具体功能点目录>/TODO.md
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力、方案或验收点。
|
||||||
|
- 只有完成并验证后,才能把 `[ ]` 改成 `[x]`。
|
||||||
|
- 勾选时在任务后补充简短证据,例如文件、接口、命令或验证结果。
|
||||||
|
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 背景与问题] 阅读相关页面、接口、服务、测试和历史文档,记录当前实现事实。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 目标与非目标] 确认本轮开发范围,写清楚不做项。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 标记无法立即确认的依赖、风险和假设。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 2. 契约与设计
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 功能能力] 定义输入、输出、状态、权限和边界条件。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 方案设计] 明确前端、后端、算法、数据的职责边界。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 算法与公式] 补全公式、变量解释或明确当前不涉及公式。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 把验收标准转成可验证的检查点。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 3. 后端实现
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 后端] 新增或调整 schema、service、endpoint、权限和持久化逻辑。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 数据与契约] 保持响应结构、状态枚举和兼容策略清晰。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 4. 算法/规则实现
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 算法与规则] 实现核心处理流程、规则判断或计算逻辑。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 结果解释] 输出可读解释、证据链、贡献项或降级原因。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 5. 前端实现
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 前端] 新增或调整页面、组件、服务 API 和视图模型。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 前端] 实现加载、空态、错误态、权限态和刷新。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 前端] 对齐现有企业后台风格,避免营销页或花哨卡片感。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 6. 测试与验证
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 测试方案] 补充后端 service/API 定向测试,容器内运行,超时控制在 60s 内。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 测试方案] 补充前端视图模型、路由、组件或构建验证。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 记录验证命令、结果和未覆盖风险。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 7. 文档收尾
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 回看所有验收点,确认均有实现或验证证据。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 更新剩余风险、后续任务和明确不做项。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 功能一句话] 确认最终实现没有偏离原始目标。
|
||||||
|
证据:
|
||||||
51
.env
51
.env
@@ -1,51 +0,0 @@
|
|||||||
APP_NAME=X-Financial
|
|
||||||
APP_ENV=local
|
|
||||||
APP_DEBUG=true
|
|
||||||
API_V1_PREFIX=/api/v1
|
|
||||||
SETUP_COMPLETED=true
|
|
||||||
VITE_SETUP_COMPLETED=true
|
|
||||||
|
|
||||||
COMPANY_NAME=YGSOFT
|
|
||||||
COMPANY_CODE=123
|
|
||||||
ADMIN_EMAIL='admin@admin.com'
|
|
||||||
VITE_COMPANY_NAME=YGSOFT
|
|
||||||
VITE_COMPANY_CODE=123
|
|
||||||
VITE_ADMIN_EMAIL='admin@admin.com'
|
|
||||||
# Admin login credentials are stored separately under server/.secrets/
|
|
||||||
|
|
||||||
WEB_HOST=10.10.10.122
|
|
||||||
WEB_PORT=5173
|
|
||||||
VITE_WEB_HOST=10.10.10.122
|
|
||||||
VITE_WEB_PORT=5173
|
|
||||||
|
|
||||||
SERVER_HOST=0.0.0.0
|
|
||||||
SERVER_PORT=8000
|
|
||||||
VITE_SERVER_HOST=0.0.0.0
|
|
||||||
VITE_SERVER_PORT=8000
|
|
||||||
SERVER_STARTUP_TIMEOUT=300
|
|
||||||
SERVER_BLOCKING_STARTUP_TIMEOUT=12
|
|
||||||
VITE_API_BASE_URL=/api/v1
|
|
||||||
VITE_AUTH_IDLE_TIMEOUT_MINUTES=30
|
|
||||||
ONLYOFFICE_ENABLED=true
|
|
||||||
ONLYOFFICE_PUBLIC_URL=http://10.10.10.122:8082
|
|
||||||
ONLYOFFICE_BACKEND_URL=http://main:8000
|
|
||||||
ONLYOFFICE_JWT_SECRET=change-me-onlyoffice
|
|
||||||
HERMES_AGENT_SHARED_TOKEN=change-me-hermes
|
|
||||||
|
|
||||||
POSTGRES_HOST=10.10.10.189
|
|
||||||
POSTGRES_PORT=5432
|
|
||||||
POSTGRES_DB=postgres
|
|
||||||
POSTGRES_USER=root
|
|
||||||
POSTGRES_PASSWORD=8811614287327Leo
|
|
||||||
VITE_POSTGRES_HOST=10.10.10.189
|
|
||||||
VITE_POSTGRES_PORT=5432
|
|
||||||
VITE_POSTGRES_DB=postgres
|
|
||||||
VITE_POSTGRES_USER=root
|
|
||||||
|
|
||||||
DATABASE_URL='postgresql+psycopg://root:8811614287327Leo@10.10.10.189:5432/postgres'
|
|
||||||
SQLALCHEMY_ECHO=false
|
|
||||||
|
|
||||||
REDIS_URL=
|
|
||||||
VITE_REDIS_URL=
|
|
||||||
|
|
||||||
CORS_ORIGINS='["http://10.10.10.122:5173"]'
|
|
||||||
@@ -31,6 +31,7 @@ ONLYOFFICE_PUBLIC_URL=http://127.0.0.1:8082
|
|||||||
ONLYOFFICE_BACKEND_URL=http://127.0.0.1:8000
|
ONLYOFFICE_BACKEND_URL=http://127.0.0.1:8000
|
||||||
ONLYOFFICE_JWT_SECRET=change-me-onlyoffice
|
ONLYOFFICE_JWT_SECRET=change-me-onlyoffice
|
||||||
HERMES_AGENT_SHARED_TOKEN=change-me-hermes
|
HERMES_AGENT_SHARED_TOKEN=change-me-hermes
|
||||||
|
STEWARD_AGENT_RUNTIME=langgraph
|
||||||
|
|
||||||
POSTGRES_HOST=127.0.0.1
|
POSTGRES_HOST=127.0.0.1
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
@@ -48,4 +49,8 @@ SQLALCHEMY_ECHO=false
|
|||||||
REDIS_URL=
|
REDIS_URL=
|
||||||
VITE_REDIS_URL=
|
VITE_REDIS_URL=
|
||||||
|
|
||||||
|
OCR_DEVICE=
|
||||||
|
OCR_TIMEOUT_SECONDS=180
|
||||||
|
OCR_MAX_CONCURRENT_WORKERS=1
|
||||||
|
|
||||||
CORS_ORIGINS='["http://127.0.0.1:5173","http://localhost:5173","http://0.0.0.0:5173"]'
|
CORS_ORIGINS='["http://127.0.0.1:5173","http://localhost:5173","http://0.0.0.0:5173"]'
|
||||||
|
|||||||
10
.githooks/post-commit
Executable file
10
.githooks/post-commit
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Auto-append a minimal X-Financial agent work-log entry after each commit.
|
||||||
|
|
||||||
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
|
||||||
|
cd "$repo_root" || exit 0
|
||||||
|
|
||||||
|
python3 tools/agent-change-log/update_change_log.py \
|
||||||
|
--kind auto \
|
||||||
|
--event "post-commit hook" \
|
||||||
|
>/tmp/x-financial-agent-change-log-hook.log 2>&1 || true
|
||||||
29
.gitignore
vendored
29
.gitignore
vendored
@@ -7,7 +7,18 @@ web/.vite/
|
|||||||
.omc/
|
.omc/
|
||||||
.omx/
|
.omx/
|
||||||
.claude/
|
.claude/
|
||||||
.codex/
|
*.egg-info/
|
||||||
|
.codex/*
|
||||||
|
!.codex/skills/
|
||||||
|
.codex/skills/*
|
||||||
|
!.codex/skills/agent-change-log/
|
||||||
|
!.codex/skills/agent-change-log/**
|
||||||
|
!.codex/skills/git-checkpoint-commit/
|
||||||
|
!.codex/skills/git-checkpoint-commit/**
|
||||||
|
!.codex/skills/write-development-docs/
|
||||||
|
!.codex/skills/write-development-docs/**
|
||||||
|
.codex-temp/
|
||||||
|
.superpowers/
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
@@ -16,3 +27,19 @@ __pycache__/
|
|||||||
server/.venv/
|
server/.venv/
|
||||||
server/.venv-ocr312
|
server/.venv-ocr312
|
||||||
server/.secrets/
|
server/.secrets/
|
||||||
|
server/logs/
|
||||||
|
server/storage/expense_claims/
|
||||||
|
server/storage/finance_reports/
|
||||||
|
server/storage/receipt_folder/
|
||||||
|
server/storage/knowledge/platform/
|
||||||
|
server/storage/knowledge/tenants/
|
||||||
|
test-results/
|
||||||
|
.codex-remote-attachments/
|
||||||
|
tmp-*.png
|
||||||
|
tmp/
|
||||||
|
.zcode/
|
||||||
|
.nezha/
|
||||||
|
.omo/
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|||||||
1
.tmp/Yuxi
Submodule
1
.tmp/Yuxi
Submodule
Submodule .tmp/Yuxi added at fd6803e477
34
AGENTS.md
34
AGENTS.md
@@ -5,6 +5,15 @@
|
|||||||
- 所有分析、解释、计划、提交说明和最终回复默认使用简体中文。
|
- 所有分析、解释、计划、提交说明和最终回复默认使用简体中文。
|
||||||
- 技术结论要直击重点,必要时给出可验证的文件、命令或测试结果。
|
- 技术结论要直击重点,必要时给出可验证的文件、命令或测试结果。
|
||||||
|
|
||||||
|
## 变更日志 Skill 规范
|
||||||
|
|
||||||
|
- 每次修复 bug 后,必须调用项目级 Skill `agent-change-log`,并在 `document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md` 记录该 bug 的修复内容。
|
||||||
|
- 新增功能、重构、配置或项目文档变更不再写入旧的 `document/work-log/YYYY-MM-DD.md`;功能点默认沉淀到 `document/development/YYYY-MM-DD/feature/<具体功能点>/CONCEPT.md` 和 `TODO.md`。
|
||||||
|
- 写 bug 日志前必须先执行 Git 拉取检查:默认运行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`。发现其他智能体已提交到上游或本地 ahead 提交时,要把这些提交摘要写进 bug 修复记录。
|
||||||
|
- 自动化触发由 `tools/agent-change-log/update_change_log.py` 和 `.githooks/post-commit` 提供;新 checkout 需要执行 `tools/agent-change-log/install_post_commit_hook.sh` 安装到本地 `.git/hooks/post-commit` 后,提交后才会按 `--kind auto` 自动识别 bug-like commit 并写入 `dev-logs/bugs`。
|
||||||
|
- bug 日志只保留 `## 修复记录`,记录具体时间、改了什么、操作了什么、验证了什么和影响;不再写 `遗留问题` 和 `TODO` 两块。
|
||||||
|
- 每天 17:00 生成当天综合日志:读取 `document/development/YYYY-MM-DD/feature/` 和 `document/development/YYYY-MM-DD/dev-logs/bugs/`,分析功能点与 bug 修复后写入 `document/development/YYYY-MM-DD/work-logs.med`。
|
||||||
|
|
||||||
## 通用代码拆分规范
|
## 通用代码拆分规范
|
||||||
|
|
||||||
无论写前端、后端还是算法代码,都必须主动避免“所有方法堆在一个类里 / 一个组件里 / 一个模块里”的写法。遇到类、组件或核心模块持续变大时,优先按职责拆分,而不是继续追加方法和状态。
|
无论写前端、后端还是算法代码,都必须主动避免“所有方法堆在一个类里 / 一个组件里 / 一个模块里”的写法。遇到类、组件或核心模块持续变大时,优先按职责拆分,而不是继续追加方法和状态。
|
||||||
@@ -32,8 +41,25 @@
|
|||||||
- 前端大型 Vue 页面:优先拆分 composable、view model、样式分片、业务工具函数和子组件。
|
- 前端大型 Vue 页面:优先拆分 composable、view model、样式分片、业务工具函数和子组件。
|
||||||
- 算法/规则模块:优先拆分输入解析、规则匹配、评分策略、结果解释和异常处理。
|
- 算法/规则模块:优先拆分输入解析、规则匹配、评分策略、结果解释和异常处理。
|
||||||
|
|
||||||
## 验证规范
|
## 容器与运行环境(必读)
|
||||||
|
|
||||||
- 后端改动优先在 Docker 容器 `x-financial-main` 中运行验证。
|
本项目代码是 Docker 容器 `local-x-financial-linux`(镜像 `x-financial-dev:latest`)的源码映射。
|
||||||
- 单元测试设置合理超时,避免长时间卡死。
|
|
||||||
- 每次重构后至少运行对应服务的定向测试;涉及公共协议时补充端到端或接口测试。
|
- **容器映射**:宿主机 `D:\Code\Project\X-Financial` ↔ 容器内 `/app`(`docker-compose.yml` 中 `volumes: - .:/app`,`working_dir: /app`)。
|
||||||
|
- **后端 venv**:容器内位于 `/tmp/x-financial-server-venv`(环境变量 `SERVER_VENV_DIR`),不要假设宿主机上有相同的 venv。
|
||||||
|
- **外部依赖**:Qdrant(`x-financial-qdrant`)、OnlyOffice(`x-financial-onlyoffice`)也在同一 compose 网络里。
|
||||||
|
|
||||||
|
## 验证规范(硬性约束)
|
||||||
|
|
||||||
|
> 本项目代码与运行环境以容器为唯一事实来源。所有后端测试、集成测试、依赖了 Qdrant / OnlyOffice / venv 的验证,都必须在 `local-x-financial-linux` 容器内执行,**不要在宿主机上直接跑 pytest / pip / python**。
|
||||||
|
|
||||||
|
- **进入容器跑命令**(最常用):
|
||||||
|
```bash
|
||||||
|
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv local-x-financial-linux <cmd>
|
||||||
|
```
|
||||||
|
- 跑后端测试:`docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv local-x-financial-linux /tmp/x-financial-server-venv/bin/pytest -q <path>`
|
||||||
|
- 交互式排查:`docker exec -it -w /app local-x-financial-linux bash`(登录后默认已在 `/app`)
|
||||||
|
- **容器不可用时**(未启动、健康检查失败、镜像丢失):先 `docker compose up -d main` 恢复,再继续验证;不要绕开容器在宿主机另装 venv。
|
||||||
|
- **单元测试设置合理超时**,避免长时间卡死。涉及外部服务(Qdrant / OnlyOffice / LLM)的测试要么 mock,要么确认 compose 网络中依赖服务在线。
|
||||||
|
- **每次重构后至少运行对应服务的定向测试**;涉及公共协议时补充端到端或接口测试。
|
||||||
|
- **修改 docker-compose / start.sh / venv 路径相关代码**时,自己也要回容器里跑一次确认改动生效,不要只改文件就声称完成。
|
||||||
|
|||||||
@@ -37,6 +37,11 @@
|
|||||||
|
|
||||||
根目录 `start.sh` 是统一编排入口;前端和后端的子启动脚本分别是 `web/web_start.sh` 与 `server/server_start.sh`。
|
根目录 `start.sh` 是统一编排入口;前端和后端的子启动脚本分别是 `web/web_start.sh` 与 `server/server_start.sh`。
|
||||||
|
|
||||||
|
Docker Compose 运行方式见 `docker/README.md`:
|
||||||
|
|
||||||
|
- `docker-compose.yml`:只启动主应用容器,适合复用已有数据库、ONLYOFFICE 等外部依赖。
|
||||||
|
- `docker-compose.full.yml`:启动主应用、PostgreSQL、Qdrant、ONLYOFFICE 的完整本地开发栈。
|
||||||
|
|
||||||
手动进入前端目录:
|
手动进入前端目录:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
144
docker-compose.full.yml
Normal file
144
docker-compose.full.yml
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
services:
|
||||||
|
main:
|
||||||
|
image: x-financial-dev:latest
|
||||||
|
container_name: local-x-financial-linux
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
onlyoffice:
|
||||||
|
condition: service_started
|
||||||
|
qdrant:
|
||||||
|
condition: service_started
|
||||||
|
environment:
|
||||||
|
WEB_HOST: 0.0.0.0
|
||||||
|
WEB_PORT: "${WEB_PORT:-5173}"
|
||||||
|
SERVER_HOST: 0.0.0.0
|
||||||
|
SERVER_PORT: "${SERVER_PORT:-8000}"
|
||||||
|
SERVER_RELOAD: "${SERVER_RELOAD:-true}"
|
||||||
|
SERVER_VENV_DIR: /tmp/x-financial-server-venv
|
||||||
|
X_FINANCIAL_PREFER_ENV_FILE: "false"
|
||||||
|
POSTGRES_HOST: postgres
|
||||||
|
POSTGRES_PORT: "5432"
|
||||||
|
POSTGRES_DB: "${POSTGRES_DB:-x_financial}"
|
||||||
|
POSTGRES_USER: "${POSTGRES_USER:-x_financial}"
|
||||||
|
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-x_financial}"
|
||||||
|
DATABASE_URL: "postgresql+psycopg://${POSTGRES_USER:-x_financial}:${POSTGRES_PASSWORD:-x_financial}@postgres:5432/${POSTGRES_DB:-x_financial}"
|
||||||
|
ONLYOFFICE_ENABLED: "true"
|
||||||
|
ONLYOFFICE_PUBLIC_URL: "${LOCAL_ONLYOFFICE_PUBLIC_URL:-http://127.0.0.1:${ONLYOFFICE_PORT:-8082}}"
|
||||||
|
ONLYOFFICE_BACKEND_URL: "${LOCAL_ONLYOFFICE_BACKEND_URL:-http://main:${SERVER_PORT:-8000}}"
|
||||||
|
ONLYOFFICE_JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
||||||
|
QDRANT_URL: "http://qdrant:6333"
|
||||||
|
LIGHTRAG_WORKSPACE: "x_financial_knowledge"
|
||||||
|
ports:
|
||||||
|
- "${WEB_PORT:-5173}:${WEB_PORT:-5173}"
|
||||||
|
- "${SERVER_PORT:-8000}:${SERVER_PORT:-8000}"
|
||||||
|
- "2223:22"
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
working_dir: /app
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -lc
|
||||||
|
- >
|
||||||
|
apt-get update &&
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends
|
||||||
|
python3 python3-pip python3-venv fontconfig openssh-server poppler-data mupdf-tools &&
|
||||||
|
if ! fc-match 'Noto Sans CJK SC' | grep -qi 'Noto'; then if ! timeout "${CJK_FONT_INSTALL_TIMEOUT_SECONDS:-45}" sh -lc 'DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends fonts-noto-cjk fonts-noto-cjk-extra'; then printf '%s\n' '[WARN] CJK font installation timed out or failed; continuing startup without blocking the app.'; fi; fi &&
|
||||||
|
printf '%s\n'
|
||||||
|
'<?xml version="1.0"?>'
|
||||||
|
'<!DOCTYPE fontconfig SYSTEM "fonts.dtd">'
|
||||||
|
'<fontconfig>'
|
||||||
|
' <alias><family>SimSun</family><prefer><family>Noto Serif CJK SC</family></prefer></alias>'
|
||||||
|
' <alias><family>NSimSun</family><prefer><family>Noto Serif CJK SC</family></prefer></alias>'
|
||||||
|
' <alias><family>KaiTi</family><prefer><family>Noto Serif CJK SC</family></prefer></alias>'
|
||||||
|
' <alias><family>FangSong</family><prefer><family>Noto Serif CJK SC</family></prefer></alias>'
|
||||||
|
' <alias><family>SimHei</family><prefer><family>Noto Sans CJK SC</family></prefer></alias>'
|
||||||
|
' <alias><family>DengXian</family><prefer><family>Noto Sans CJK SC</family></prefer></alias>'
|
||||||
|
' <alias><family>Microsoft YaHei</family><prefer><family>Noto Sans CJK SC</family></prefer></alias>'
|
||||||
|
'</fontconfig>'
|
||||||
|
> /etc/fonts/local.conf &&
|
||||||
|
fc-cache -f &&
|
||||||
|
mkdir -p /run/sshd && /usr/sbin/sshd &&
|
||||||
|
printf '%s\n' 'cd /app >/dev/null 2>&1 || true' > /etc/profile.d/zz-x-financial-app-dir.sh &&
|
||||||
|
chmod 644 /etc/profile.d/zz-x-financial-app-dir.sh &&
|
||||||
|
touch /root/.bashrc /root/.profile &&
|
||||||
|
if ! grep -qxF 'cd /app >/dev/null 2>&1 || true' /root/.bashrc; then printf '\ncd /app >/dev/null 2>&1 || true\n' >> /root/.bashrc; fi &&
|
||||||
|
if ! grep -qxF 'cd /app >/dev/null 2>&1 || true' /root/.profile; then printf '\ncd /app >/dev/null 2>&1 || true\n' >> /root/.profile; fi &&
|
||||||
|
sed -i 's/\r$//' /app/start.sh /app/web/web_start.sh /app/server/server_start.sh &&
|
||||||
|
chmod +x /app/start.sh /app/web/web_start.sh /app/server/server_start.sh &&
|
||||||
|
cd /app &&
|
||||||
|
./start.sh all
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:${WEB_PORT:-5173}/ >/dev/null || exit 1"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 180s
|
||||||
|
networks:
|
||||||
|
- financial-internal
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: pgvector/pgvector:pg17
|
||||||
|
container_name: x-financial-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "${POSTGRES_DB:-x_financial}"
|
||||||
|
POSTGRES_USER: "${POSTGRES_USER:-x_financial}"
|
||||||
|
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-x_financial}"
|
||||||
|
ports:
|
||||||
|
- "${POSTGRES_HOST_PORT:-55432}:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 30s
|
||||||
|
networks:
|
||||||
|
- financial-internal
|
||||||
|
|
||||||
|
qdrant:
|
||||||
|
image: qdrant/qdrant:latest
|
||||||
|
container_name: x-financial-qdrant
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${QDRANT_HTTP_PORT:-6333}:6333"
|
||||||
|
- "${QDRANT_GRPC_PORT:-6334}:6334"
|
||||||
|
volumes:
|
||||||
|
- qdrant-storage:/qdrant/storage
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "bash -lc 'exec 3<>/dev/tcp/127.0.0.1/6333' || exit 1"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 30s
|
||||||
|
networks:
|
||||||
|
- financial-internal
|
||||||
|
|
||||||
|
onlyoffice:
|
||||||
|
image: onlyoffice/documentserver:latest
|
||||||
|
container_name: x-financial-onlyoffice
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
JWT_ENABLED: "true"
|
||||||
|
JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
||||||
|
ports:
|
||||||
|
- "${ONLYOFFICE_PORT:-8082}:80"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/healthcheck >/dev/null || exit 1"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 60s
|
||||||
|
networks:
|
||||||
|
- financial-internal
|
||||||
|
|
||||||
|
networks:
|
||||||
|
financial-internal:
|
||||||
|
name: financial-internal
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
qdrant-storage:
|
||||||
8
docker-compose.gpu.yml
Normal file
8
docker-compose.gpu.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
main:
|
||||||
|
gpus: all
|
||||||
|
shm_size: "8gb"
|
||||||
|
environment:
|
||||||
|
NVIDIA_VISIBLE_DEVICES: all
|
||||||
|
NVIDIA_DRIVER_CAPABILITIES: compute,utility
|
||||||
|
OCR_DEVICE: "${OCR_DEVICE:-gpu:0}"
|
||||||
4
docker-compose.postgres.yml
Normal file
4
docker-compose.postgres.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# PostgreSQL 已并入默认 docker-compose.yml。
|
||||||
|
# 保留此空覆盖文件,兼容历史启动命令:
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d
|
||||||
|
services: {}
|
||||||
@@ -1,21 +1,32 @@
|
|||||||
services:
|
services:
|
||||||
main:
|
main:
|
||||||
image: x-financial-dev:latest
|
image: x-financial-dev:latest
|
||||||
container_name: x-financial-main
|
container_name: local-x-financial-linux
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
onlyoffice:
|
onlyoffice:
|
||||||
condition: service_started
|
condition: service_healthy
|
||||||
qdrant:
|
qdrant:
|
||||||
condition: service_started
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
WEB_HOST: 0.0.0.0
|
WEB_HOST: 0.0.0.0
|
||||||
|
WEB_PORT: "${WEB_PORT:-5173}"
|
||||||
SERVER_HOST: 0.0.0.0
|
SERVER_HOST: 0.0.0.0
|
||||||
|
SERVER_PORT: "${SERVER_PORT:-8000}"
|
||||||
|
SERVER_RELOAD: "${SERVER_RELOAD:-true}"
|
||||||
SERVER_VENV_DIR: /tmp/x-financial-server-venv
|
SERVER_VENV_DIR: /tmp/x-financial-server-venv
|
||||||
X_FINANCIAL_PREFER_ENV_FILE: "true"
|
X_FINANCIAL_PREFER_ENV_FILE: "false"
|
||||||
ONLYOFFICE_ENABLED: "${ONLYOFFICE_ENABLED:-true}"
|
POSTGRES_HOST: postgres
|
||||||
ONLYOFFICE_PUBLIC_URL: "${ONLYOFFICE_PUBLIC_URL:-http://127.0.0.1:${ONLYOFFICE_PORT:-8082}}"
|
POSTGRES_PORT: "5432"
|
||||||
ONLYOFFICE_BACKEND_URL: "http://main:${SERVER_PORT:-8000}"
|
POSTGRES_DB: "${LOCAL_POSTGRES_DB:-x_financial}"
|
||||||
|
POSTGRES_USER: "${LOCAL_POSTGRES_USER:-x_financial}"
|
||||||
|
POSTGRES_PASSWORD: "${LOCAL_POSTGRES_PASSWORD:-x_financial}"
|
||||||
|
DATABASE_URL: "postgresql+psycopg://${LOCAL_POSTGRES_USER:-x_financial}:${LOCAL_POSTGRES_PASSWORD:-x_financial}@postgres:5432/${LOCAL_POSTGRES_DB:-x_financial}"
|
||||||
|
ONLYOFFICE_ENABLED: "true"
|
||||||
|
ONLYOFFICE_PUBLIC_URL: "${LOCAL_ONLYOFFICE_PUBLIC_URL:-http://127.0.0.1:${ONLYOFFICE_PORT:-8082}}"
|
||||||
|
ONLYOFFICE_BACKEND_URL: "${LOCAL_ONLYOFFICE_BACKEND_URL:-http://main:${SERVER_PORT:-8000}}"
|
||||||
ONLYOFFICE_JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
ONLYOFFICE_JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
||||||
QDRANT_URL: "http://qdrant:6333"
|
QDRANT_URL: "http://qdrant:6333"
|
||||||
LIGHTRAG_WORKSPACE: "x_financial_knowledge"
|
LIGHTRAG_WORKSPACE: "x_financial_knowledge"
|
||||||
@@ -32,7 +43,8 @@ services:
|
|||||||
- >
|
- >
|
||||||
apt-get update &&
|
apt-get update &&
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends
|
||||||
python3 python3-pip python3-venv fontconfig fonts-noto-cjk fonts-noto-cjk-extra &&
|
python3 python3-pip python3-venv fontconfig openssh-server poppler-data mupdf-tools &&
|
||||||
|
if ! fc-match 'Noto Sans CJK SC' | grep -qi 'Noto'; then if ! timeout "${CJK_FONT_INSTALL_TIMEOUT_SECONDS:-45}" sh -lc 'DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends fonts-noto-cjk fonts-noto-cjk-extra'; then printf '%s\n' '[WARN] CJK font installation timed out or failed; continuing startup without blocking the app.'; fi; fi &&
|
||||||
printf '%s\n'
|
printf '%s\n'
|
||||||
'<?xml version="1.0"?>'
|
'<?xml version="1.0"?>'
|
||||||
'<!DOCTYPE fontconfig SYSTEM "fonts.dtd">'
|
'<!DOCTYPE fontconfig SYSTEM "fonts.dtd">'
|
||||||
@@ -66,17 +78,20 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- financial-internal
|
- financial-internal
|
||||||
|
|
||||||
qdrant:
|
postgres:
|
||||||
image: qdrant/qdrant:latest
|
image: pgvector/pgvector:pg17
|
||||||
container_name: x-financial-qdrant
|
container_name: x-financial-local-postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "${LOCAL_POSTGRES_DB:-x_financial}"
|
||||||
|
POSTGRES_USER: "${LOCAL_POSTGRES_USER:-x_financial}"
|
||||||
|
POSTGRES_PASSWORD: "${LOCAL_POSTGRES_PASSWORD:-x_financial}"
|
||||||
ports:
|
ports:
|
||||||
- "${QDRANT_HTTP_PORT:-6333}:6333"
|
- "127.0.0.1:${LOCAL_POSTGRES_HOST_PORT:-55432}:5432"
|
||||||
- "${QDRANT_GRPC_PORT:-6334}:6334"
|
|
||||||
volumes:
|
volumes:
|
||||||
- qdrant-storage:/qdrant/storage
|
- postgres-data:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "bash -lc 'exec 3<>/dev/tcp/127.0.0.1/6333' || exit 1"]
|
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
|
||||||
interval: 15s
|
interval: 15s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
@@ -92,7 +107,7 @@ services:
|
|||||||
JWT_ENABLED: "true"
|
JWT_ENABLED: "true"
|
||||||
JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
JWT_SECRET: "${ONLYOFFICE_JWT_SECRET:-x-financial-onlyoffice-dev-secret}"
|
||||||
ports:
|
ports:
|
||||||
- "${ONLYOFFICE_PORT:-8082}:80"
|
- "127.0.0.1:${ONLYOFFICE_PORT:-8082}:80"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/healthcheck >/dev/null || exit 1"]
|
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/healthcheck >/dev/null || exit 1"]
|
||||||
interval: 15s
|
interval: 15s
|
||||||
@@ -102,9 +117,28 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- financial-internal
|
- financial-internal
|
||||||
|
|
||||||
|
qdrant:
|
||||||
|
image: qdrant/qdrant:latest
|
||||||
|
container_name: x-financial-qdrant
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:${QDRANT_HTTP_PORT:-6333}:6333"
|
||||||
|
- "127.0.0.1:${QDRANT_GRPC_PORT:-6334}:6334"
|
||||||
|
volumes:
|
||||||
|
- qdrant-storage:/qdrant/storage
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "bash -lc 'exec 3<>/dev/tcp/127.0.0.1/6333' || exit 1"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 30s
|
||||||
|
networks:
|
||||||
|
- financial-internal
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
financial-internal:
|
financial-internal:
|
||||||
name: financial-internal
|
name: financial-internal
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
postgres-data:
|
||||||
qdrant-storage:
|
qdrant-storage:
|
||||||
|
|||||||
150
docker/README.md
150
docker/README.md
@@ -1,67 +1,127 @@
|
|||||||
# Docker Compose
|
# Docker Compose
|
||||||
|
|
||||||
This project currently uses the Vite `__setup/*` middleware during the initial setup flow.
|
X-Financial 现在按运行依赖分成两层 Docker Compose:
|
||||||
Because of that, the Docker deployment keeps the web frontend and FastAPI startup chain in
|
|
||||||
the same main container and runs the existing root `start.sh`.
|
|
||||||
|
|
||||||
## Start
|
- `docker-compose.yml`:只启动主应用容器,适合已经有远端 PostgreSQL、ONLYOFFICE 或 Qdrant 的环境。
|
||||||
|
- `docker-compose.full.yml`:启动完整本地开发栈,适合没有外部依赖、希望本机一次性跑齐所有服务的环境。
|
||||||
|
|
||||||
|
主应用容器仍然同时启动 Web 前端和 FastAPI 后端,并复用根目录 `start.sh`。
|
||||||
|
项目根目录会挂载到容器内 `/app`。
|
||||||
|
|
||||||
|
## 轻量启动:只跑主应用
|
||||||
|
|
||||||
|
适合你已经有数据库和 ONLYOFFICE 的情况。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Open:
|
默认只会启动:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
http://<your-linux-host>:5173
|
main
|
||||||
```
|
```
|
||||||
|
|
||||||
## Container Layout
|
打开:
|
||||||
|
|
||||||
- `main`: web + FastAPI main container
|
|
||||||
- `onlyoffice`: ONLYOFFICE Document Server
|
|
||||||
- `postgres`: PostgreSQL database container
|
|
||||||
|
|
||||||
The project root is mounted directly into the main container:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
.:/app
|
http://<your-linux-host>:5273
|
||||||
```
|
```
|
||||||
|
|
||||||
That means the container reads your existing `.env`, source code, `server/.secrets`, logs,
|
这条路径不会主动拉起本地 PostgreSQL、Qdrant 或 ONLYOFFICE。
|
||||||
and generated dependency directories directly from the mapped project folder.
|
数据库、ONLYOFFICE 和 Qdrant 地址都从 `.env` 或外部环境变量读取。
|
||||||
|
|
||||||
This is a `compose`-only setup. There is no custom `Dockerfile`.
|
常见外部依赖变量:
|
||||||
The tradeoff is that the `main` container installs the Python runtime packages it needs
|
|
||||||
when it starts.
|
|
||||||
|
|
||||||
## Persistence
|
```text
|
||||||
|
DATABASE_URL
|
||||||
|
POSTGRES_HOST
|
||||||
|
POSTGRES_PORT
|
||||||
|
ONLYOFFICE_ENABLED
|
||||||
|
ONLYOFFICE_PUBLIC_URL
|
||||||
|
ONLYOFFICE_BACKEND_URL
|
||||||
|
QDRANT_URL
|
||||||
|
```
|
||||||
|
|
||||||
The PostgreSQL data directory is stored in the named volume `postgres_data`.
|
## 完整启动:本地全栈
|
||||||
|
|
||||||
## Notes
|
适合没有远端数据库和 ONLYOFFICE 的情况。
|
||||||
|
|
||||||
- Most configuration should be maintained in the project root `.env`.
|
```bash
|
||||||
- The first `docker compose up -d` does not require an existing `.env`; the compose file
|
docker compose -f docker-compose.full.yml up -d
|
||||||
uses built-in defaults for the PostgreSQL container and the main container database URL.
|
```
|
||||||
- Docker Compose only overrides a few values that must differ inside containers:
|
|
||||||
- `WEB_HOST=0.0.0.0`
|
会启动:
|
||||||
- `SERVER_HOST=0.0.0.0`
|
|
||||||
- `POSTGRES_HOST=postgres`
|
```text
|
||||||
- `POSTGRES_PORT=5432`
|
main
|
||||||
- `DATABASE_URL=...@postgres:...`
|
postgres
|
||||||
- PostgreSQL is also published to the host by default as `127.0.0.1:55432`.
|
qdrant
|
||||||
- ONLYOFFICE is published to the host by default as `127.0.0.1:8082`.
|
onlyoffice
|
||||||
- First boot with `SETUP_COMPLETED=false` starts the setup UI only.
|
```
|
||||||
- After you complete setup in the browser, the Vite setup bridge will start FastAPI in the
|
|
||||||
same container using the saved runtime configuration.
|
本地服务端口:
|
||||||
- On later restarts, `start.sh` will detect the saved setup state and start both web and
|
|
||||||
server automatically.
|
```text
|
||||||
- If you access the system from another machine, make sure `CORS_ORIGINS` in `.env` includes
|
Web: 5273
|
||||||
the frontend address you actually use.
|
FastAPI: 8000
|
||||||
- For Navicat or any host-side client, use `127.0.0.1:55432`.
|
PostgreSQL: 55432 -> 5432
|
||||||
- If you need to access ONLYOFFICE from another machine, override `ONLYOFFICE_PUBLIC_URL`
|
Qdrant: 6333 / 6334
|
||||||
so the browser can reach the document server address you actually expose.
|
ONLYOFFICE: 8082
|
||||||
- For the setup page, using `127.0.0.1` is acceptable in this Docker layout; the internal
|
SSH: 2223
|
||||||
test bridge will resolve that back to the Docker PostgreSQL service.
|
```
|
||||||
|
|
||||||
|
完整栈会把主容器内的数据库地址指向 `postgres:5432`,
|
||||||
|
并把 Qdrant 地址指向 `http://qdrant:6333`。
|
||||||
|
|
||||||
|
ONLYOFFICE 默认使用本机可访问地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://127.0.0.1:8082
|
||||||
|
```
|
||||||
|
|
||||||
|
如果浏览器从另一台机器访问,需要覆盖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LOCAL_ONLYOFFICE_PUBLIC_URL=http://<host>:8082 \
|
||||||
|
docker compose -f docker-compose.full.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 ONLYOFFICE 回调后端也需要外部地址,可以同时覆盖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LOCAL_ONLYOFFICE_BACKEND_URL=http://<host>:8000 \
|
||||||
|
docker compose -f docker-compose.full.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 可选:只额外启动本地 PostgreSQL
|
||||||
|
|
||||||
|
如果只想在轻量主容器旁边补一个本地 PostgreSQL,可以使用覆盖文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
这会启动:
|
||||||
|
|
||||||
|
```text
|
||||||
|
main
|
||||||
|
postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
## 停止与清理
|
||||||
|
|
||||||
|
停止当前默认轻量栈:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
停止完整本地栈:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.full.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
如需删除本地数据卷,先确认不再需要其中数据,再手动执行带 `-v` 的清理命令。
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# 附件自动关联后台任务实施计划
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
把小财管家 AI 模式里的附件关联从前端会话内存任务改为后端可查询后台任务,保证用户退出或刷新当前会话后,附件关联仍能继续完成并可恢复状态。
|
||||||
|
|
||||||
|
## 执行清单
|
||||||
|
|
||||||
|
- [x] 定位当前断链根因:前端依赖 `File` 对象和内存 `Map`。
|
||||||
|
- [x] 确认票据夹已有 `receipt_id`、源文件和关联状态能力。
|
||||||
|
- [x] 落开发方案文档。
|
||||||
|
- [x] 实现后端任务 schema 和内存任务池。
|
||||||
|
- [x] 实现后端任务 API。
|
||||||
|
- [x] 实现后端票据夹源文件归集到报销单明细。
|
||||||
|
- [x] 增加后端测试。
|
||||||
|
- [x] 实现前端任务创建、轮询和恢复。
|
||||||
|
- [x] 增加前端测试断言。
|
||||||
|
- [x] 执行容器后端定向测试。
|
||||||
|
- [x] 执行前端定向测试和构建。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- 后端定向测试:`6 passed`
|
||||||
|
- 前端定向测试:`12 passed`
|
||||||
|
- 前端构建:通过,保留既有 chunk size warning。
|
||||||
|
- 运行时检查:新任务查询路由已加载,未知任务返回“附件关联任务不存在或已失效。”
|
||||||
|
|
||||||
|
## 关键决策
|
||||||
|
|
||||||
|
- 第一版使用后端内存任务池和 FastAPI `BackgroundTasks`,解决前端会话断链。
|
||||||
|
- 第一版不新增数据库任务表,服务重启后的任务恢复作为后续增强。
|
||||||
|
- 前端消息只保存 `job_id`、状态和票据引用,不再保存附件原件。
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
# AI 工作台统一意图识别框架设计
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
AI 工作台当前的输入识别分散在多个前端 flow 中:申请预览、报销草稿、单据查询、草稿删除提示各自判断输入。这样的结构能快速修复单点问题,但会让“删除 3 天前的草稿”“审核合规没有风险的申请”这类组合型请求继续变成关键词补丁。
|
||||||
|
|
||||||
|
本设计把自然语言输入先统一解析成结构化 `IntentFrame`,再根据目标是否明确、安全等级和业务边界决定下一步动作。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
- 让工作台所有自然语言输入先进入统一意图框架,不再在各业务 flow 中散落判断。
|
||||||
|
- 支持动作、对象、筛选条件、上下文指代、安全等级的组合识别。
|
||||||
|
- 对删除、审核、驳回等高风险动作固定走“筛选候选 + 详情确认”,禁止自然语言直接执行。
|
||||||
|
- 保留当前会话内的快速指代能力,例如“删除刚才那个草稿”能定位最近创建的草稿。
|
||||||
|
- 对带筛选条件的请求,例如“删除 3 天前的草稿”“审核无风险申请”,先展示思考过程和候选列表。
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- 第一版不引入 LangGraph,也不把前端本地识别迁到后端状态机。
|
||||||
|
- 第一版不做自然语言直接批量删除、批量审核或批量驳回。
|
||||||
|
- 第一版不改后端审批、删除接口的权限模型。
|
||||||
|
- 第一版不重写现有申请预览和报销草稿流程,只把入口识别前置统一。
|
||||||
|
|
||||||
|
## 总体架构
|
||||||
|
|
||||||
|
统一意图识别分三层:
|
||||||
|
|
||||||
|
1. `IntentFrame Parser`:把用户输入解析为结构化意图。
|
||||||
|
2. `Target Resolver`:结合当前会话、最近动作和筛选条件,判断目标是否唯一。
|
||||||
|
3. `Action Policy`:根据动作风险决定直接查询、展示候选、要求澄清或阻断。
|
||||||
|
|
||||||
|
输入链路应调整为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
用户输入
|
||||||
|
-> IntentFrame Parser
|
||||||
|
-> Action Policy
|
||||||
|
-> Target Resolver
|
||||||
|
-> 业务 flow
|
||||||
|
- 查询候选
|
||||||
|
- 打开详情确认
|
||||||
|
- 进入申请/报销流程
|
||||||
|
- 要求补充条件
|
||||||
|
```
|
||||||
|
|
||||||
|
## IntentFrame 数据结构
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
action: 'query' | 'delete' | 'approve' | 'reject' | 'create' | 'update' | 'ask_policy',
|
||||||
|
objectType: 'draft' | 'application' | 'reimbursement' | 'approval_task' | 'receipt' | 'document',
|
||||||
|
filters: {
|
||||||
|
timeRange: null,
|
||||||
|
status: null,
|
||||||
|
risk: null,
|
||||||
|
documentType: null,
|
||||||
|
amount: null,
|
||||||
|
keyword: null
|
||||||
|
},
|
||||||
|
targetMode: 'current_context' | 'filtered_candidates' | 'ambiguous',
|
||||||
|
safetyLevel: 'read_only' | 'confirm_required' | 'blocked',
|
||||||
|
confidence: 0,
|
||||||
|
normalizedQuery: ''
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段含义
|
||||||
|
|
||||||
|
- `action` 表示用户想做什么,例如查、删、审核、驳回、创建或咨询规则。
|
||||||
|
- `objectType` 表示动作对象,例如草稿、申请单、报销单、待审任务或票据。
|
||||||
|
- `filters` 表示筛选条件,必须可以复用到单据查询 flow。
|
||||||
|
- `targetMode` 表示目标定位方式:
|
||||||
|
- `current_context`:明确指向当前会话最近对象。
|
||||||
|
- `filtered_candidates`:需要查询候选列表。
|
||||||
|
- `ambiguous`:条件不足,需要澄清。
|
||||||
|
- `safetyLevel` 表示动作安全级别:
|
||||||
|
- `read_only`:可直接查询或解释。
|
||||||
|
- `confirm_required`:只展示候选或详情入口,不直接执行。
|
||||||
|
- `blocked`:存在批量破坏性风险或越权风险,必须阻断。
|
||||||
|
- `normalizedQuery` 是给现有查询 flow 使用的可读查询句,例如“我的 3 天前草稿单据”。
|
||||||
|
|
||||||
|
## 识别策略
|
||||||
|
|
||||||
|
### 动作识别
|
||||||
|
|
||||||
|
- 查询:查、看、列出、有哪些、找一下。
|
||||||
|
- 删除:删除、删掉、移除、作废、撤销。
|
||||||
|
- 审核:审核、审批、处理待办、去审批。
|
||||||
|
- 驳回:驳回、退回、拒绝。
|
||||||
|
- 创建:新建、发起、申请、我要报销。
|
||||||
|
- 更新:补充、修改、改成、填入。
|
||||||
|
- 规则咨询:怎么走、能不能、规则、制度、政策、标准。
|
||||||
|
|
||||||
|
### 对象识别
|
||||||
|
|
||||||
|
- 草稿:草稿、未提交、刚才保存的单据。
|
||||||
|
- 申请单:申请、申请单、出差申请、费用申请。
|
||||||
|
- 报销单:报销、报销单、费用报销。
|
||||||
|
- 待审任务:待办、待我审核、待审批、审核单。
|
||||||
|
- 票据:发票、票据、附件、图片。
|
||||||
|
|
||||||
|
### 筛选条件识别
|
||||||
|
|
||||||
|
- 时间:今天、昨天、3 天前、近 7 天、上周、本月、具体日期、日期范围。
|
||||||
|
- 状态:草稿、审批中、已通过、已驳回、待补充。
|
||||||
|
- 风险:无风险、低风险、中风险、高风险、合规、异常、超标。
|
||||||
|
- 金额:超过 1000、500 以下、100 到 300。
|
||||||
|
- 关键词:地点、事由、人员、部门、单号等自由文本。
|
||||||
|
|
||||||
|
## 目标解析规则
|
||||||
|
|
||||||
|
### 当前上下文直达
|
||||||
|
|
||||||
|
当用户使用“刚才那个”“当前”“这个”“上面那个”这类指代,并且当前会话中能找到最近的可操作对象时,`targetMode` 为 `current_context`。
|
||||||
|
|
||||||
|
例子:
|
||||||
|
|
||||||
|
- “删除刚才那个草稿”
|
||||||
|
- “打开这个申请单”
|
||||||
|
- “继续刚才的报销草稿”
|
||||||
|
|
||||||
|
即便目标唯一,删除、审核、驳回仍然只打开详情页或确认入口。
|
||||||
|
|
||||||
|
### 筛选候选
|
||||||
|
|
||||||
|
当用户输入包含时间、风险、金额、状态、类型等筛选条件时,`targetMode` 必须为 `filtered_candidates`。
|
||||||
|
|
||||||
|
例子:
|
||||||
|
|
||||||
|
- “删除 3 天前的草稿”
|
||||||
|
- “审核合规没有风险的申请”
|
||||||
|
- “找一下上海相关的低风险待审申请”
|
||||||
|
|
||||||
|
这类请求必须进入单据查询 flow,展示候选结果,不能直接套最近草稿。
|
||||||
|
|
||||||
|
### 条件不足澄清
|
||||||
|
|
||||||
|
当动作高风险但目标既不唯一,也没有足够筛选条件时,`targetMode` 为 `ambiguous`。
|
||||||
|
|
||||||
|
例子:
|
||||||
|
|
||||||
|
- “把草稿删了”
|
||||||
|
- “帮我审核一下”
|
||||||
|
- “退回这个单”
|
||||||
|
|
||||||
|
系统应提示用户选择候选或补充条件。
|
||||||
|
|
||||||
|
## Action Policy
|
||||||
|
|
||||||
|
| 动作 | 安全等级 | 第一版行为 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 查询 | `read_only` | 直接查询并展示结果 |
|
||||||
|
| 规则咨询 | `read_only` | 走政策/规则解释,不进入单据查询 |
|
||||||
|
| 删除 | `confirm_required` | 展示候选或打开详情页确认,不直接删除 |
|
||||||
|
| 审核通过 | `confirm_required` | 展示待审候选或打开审核详情,不直接通过 |
|
||||||
|
| 驳回/退回 | `confirm_required` | 展示待审候选或打开审核详情,不直接驳回 |
|
||||||
|
| 批量删除/批量审核 | `blocked` | 阻断并要求用户选择具体单据 |
|
||||||
|
|
||||||
|
## 用户可见思考过程
|
||||||
|
|
||||||
|
筛选型命令必须复用现有查询 thinking events,并补充动作意图说明:
|
||||||
|
|
||||||
|
1. 解析自然语言动作和筛选条件。
|
||||||
|
2. 判断操作风险和目标定位方式。
|
||||||
|
3. 查询业务单据接口。
|
||||||
|
4. 按条件组合筛选候选。
|
||||||
|
5. 展示候选卡片和下一步入口。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```text
|
||||||
|
解析:识别到“删除”是高风险动作,对象是“草稿”,时间条件是“3 天前”。
|
||||||
|
策略:不会直接删除,将先查询我的草稿候选。
|
||||||
|
结果:命中 2 张草稿,请打开详情页确认删除目标。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件边界
|
||||||
|
|
||||||
|
- 新增 `workbenchIntentFrameModel.js`:负责解析用户输入到 `IntentFrame`。
|
||||||
|
- 新增 `workbenchIntentActionPolicy.js`:负责动作安全策略和下一步路由判断。
|
||||||
|
- 调整 `workbenchAiCommandIntentModel.js`:保留会话内最近草稿解析,但不再单独拥有顶层意图判断。
|
||||||
|
- 调整 `useWorkbenchAiCommandIntents.js`:基于 `IntentFrame` 分发到当前上下文直达或候选查询。
|
||||||
|
- 调整 `aiDocumentQueryIntent.js` 和 `aiDocumentQueryModel.js`:补充风险筛选、相对日期和筛选摘要。
|
||||||
|
- 补充前端测试,覆盖组合型输入和安全边界。
|
||||||
|
|
||||||
|
## 迁移步骤
|
||||||
|
|
||||||
|
1. 先为 `IntentFrame` 解析补测试:
|
||||||
|
- “删除刚才那个草稿”解析为 `delete + draft + current_context + confirm_required`。
|
||||||
|
- “删除 3 天前的草稿”解析为 `delete + draft + filtered_candidates + confirm_required`。
|
||||||
|
- “审核合规没有风险的申请”解析为 `approve + application + risk:none + filtered_candidates + confirm_required`。
|
||||||
|
- “审批规则怎么走”解析为 `ask_policy`,不进入单据查询。
|
||||||
|
2. 实现 `workbenchIntentFrameModel.js`,不接入 UI。
|
||||||
|
3. 实现风险和相对日期筛选能力,让查询 flow 能承接筛选型命令。
|
||||||
|
4. 接入 `useWorkbenchAiCommandIntents.js`:
|
||||||
|
- 当前上下文直达:生成详情确认入口。
|
||||||
|
- 筛选候选:调用 `handleAiDocumentQueryIntent(normalizedQuery, pendingMessage)`。
|
||||||
|
- 条件不足:提示补充条件或查询可选候选。
|
||||||
|
5. 移除或降级旧的散落正则,让顶层输入先走统一框架。
|
||||||
|
6. 跑定向测试、相邻测试、前端构建和 5173 工作台烟测。
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
- 输入“删除刚才那个草稿”时,系统定位当前会话最近草稿,并只打开详情确认入口。
|
||||||
|
- 输入“删除 3 天前的草稿”时,系统展示筛选思考过程和草稿候选,不使用最近草稿快捷路径。
|
||||||
|
- 输入“审核合规没有风险的申请”时,系统查询待我审核申请,并筛选无风险候选。
|
||||||
|
- 输入“审批规则怎么走”时,不进入单据查询。
|
||||||
|
- 删除、审核、驳回均不通过自然语言直接执行最终动作。
|
||||||
|
- 新增/调整测试全部通过,前端构建通过,`git diff --check` 无空白错误。
|
||||||
|
|
||||||
|
## 后续演进
|
||||||
|
|
||||||
|
- 当后端 steward planner 能稳定输出同等 `IntentFrame` 时,可以把 Parser 层迁到后端,前端只保留策略兜底。
|
||||||
|
- 如果需要跨会话目标记忆,可把最近创建/保存草稿写入会话快照,并附带过期时间和用户确认边界。
|
||||||
|
- 如果未来引入 LangGraph,应把它用于多步状态编排,而不是替代 `IntentFrame` schema 本身。
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# AI 工作台审核单二轮审核命令丢失候选上下文
|
||||||
|
|
||||||
|
日期:2026-06-25
|
||||||
|
文档路径:document/development/2026-06-25/dev-logs/bugs/ai-approval-followup-context.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
- 16:24:记录 bug 修复:AI 工作台审核单二轮审核命令丢失候选上下文。(bug-log:8a2ae6eb)
|
||||||
|
- Git 提交检查:fetch 失败:fatal: unable to access 'https://www.caoxiaozhu.com:13002/YG-Soft/X-Financial.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.caoxiaozhu.com:13002;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交:8a2ae6eb (HEAD -> main) fix(server): gate_classify 复用 _classify_irrelevant_input 修复 off_topic 误杀;992cf71f refactor(server): Phase 1 图拓扑重构 - LangGraph 成为唯一编排者;54356ba8 refactor(server): scene 注册表骨架 + 统一门控管道设计文档;e9d7c56d feat(server): 会话上下文保留(LLM 历史 + 确定性兜底双保险)。
|
||||||
|
- 修改:`workbenchAiCommandIntentModel.js` 新增待审单据候选上下文解析与二轮审批命令提示;`useWorkbenchAiCommandIntents.js` 在 `query_candidates` 前优先复用上一轮待审候选;`workbench-ai-command-intent-model.test.mjs` 覆盖“我有哪些审核单”后继续说“审核通过”的候选承接。
|
||||||
|
- 操作:按 TDD 先补失败用例,再实现最小修复;执行 `tools/agent-change-log/update_change_log.py --kind bug` 创建当天 bug 记录,并手动补全真实修复细节。
|
||||||
|
- 验证:`node --test web/tests/workbench-ai-command-intent-model.test.mjs` 通过;`node --test web/tests/workbench-ai-command-intent-model.test.mjs web/tests/workbench-intent-frame-model.test.mjs web/tests/ai-document-query-model.test.mjs` 通过,31 项前端相关测试全部通过。
|
||||||
|
- 影响:用户在 AI 工作台先查询待审/审核单后,再说“请帮我审核通过”或类似审批命令时,系统会接上刚才候选并要求进入详情确认,不会把二轮命令当成孤立查询或静默失智;仍保留高风险审批动作不直接执行的安全边界。
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# AI模式企业主题光球出现矩形闪动边框
|
||||||
|
|
||||||
|
日期:2026-06-25
|
||||||
|
文档路径:document/development/2026-06-25/dev-logs/bugs/ai-enterprise-orb-frame.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
- 15:02:记录 bug 修复:AI模式企业主题光球出现矩形闪动边框。(bug-log:2ebc2756)
|
||||||
|
- Git 提交检查:15:01 执行 `git fetch --all --prune` 成功;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交:未发现。
|
||||||
|
- 修改:`personal-workbench-ai-mode.css`,把企业主题下 `.workbench-ai-orb` 从白底圆角矩形容器改回透明圆形承载,移除边框与阴影;`settings-theme-section.test.mjs` 增加断言锁定 `border: 0`、`border-radius: 50%`、`background: transparent`、`box-shadow: none`。
|
||||||
|
- 操作:复现时确认企业主题覆盖块把光球容器设置为 `border-radius: 18px`、白底、阴影,导致 GIF 光球外层出现明显闪动方框;修复后用浏览器读取已加载 CSSOM,确认企业主题规则已变为透明圆形版本。
|
||||||
|
- 验证:`node web/tests/settings-theme-section.test.mjs`、`node web/tests/settings-llm-section.test.mjs`、`node web/tests/settings-rendering-section.test.mjs`、`git diff --check`、`npm --prefix web run build` 均通过;本地工作台页面可加载,光球元素存在,页面无 error,浏览器 CSSOM 中企业主题光球规则为 `border: 0px`、`border-radius: 50%`、`background: transparent`、`box-shadow: none`。
|
||||||
|
- 影响:企业沉稳主题下 AI 模式欢迎区光球不再显示矩形闪动边框,动感/专业智能主题保持原有光球表现。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 申请详情退回/草稿状态修改申请卡壳
|
||||||
|
|
||||||
|
日期:2026-06-25
|
||||||
|
文档路径:document/development/2026-06-25/dev-logs/bugs/application-detail-edit-returned-draft.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
- 16:40:记录 bug 修复:申请详情退回/草稿状态修改申请卡壳。(bug-log:8a2ae6eb)
|
||||||
|
- Git 提交检查:fetch 失败:fatal: unable to access 'https://www.caoxiaozhu.com:13002/YG-Soft/X-Financial.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.caoxiaozhu.com:13002;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交:8a2ae6eb (HEAD -> main) fix(server): gate_classify 复用 _classify_irrelevant_input 修复 off_topic 误杀;992cf71f refactor(server): Phase 1 图拓扑重构 - LangGraph 成为唯一编排者;54356ba8 refactor(server): scene 注册表骨架 + 统一门控管道设计文档;e9d7c56d feat(server): 会话上下文保留(LLM 历史 + 确定性兜底双保险)。
|
||||||
|
- 修改:`travelRequestDetailSetup.js` 和 `TravelRequestDetailView.vue` 将“修改申请”入口从仅退回态放宽为申请单草稿/退回归一后的可编辑态,仍要求当前用户是申请人;打开助手时补齐原申请 `draftPayload`、`applicationEditMode` 和可编辑字段白名单。
|
||||||
|
- 修改:`useAppShell.js`、`AppShellRouteView.vue`、`TravelReimbursementCreateView.js`、`useTravelReimbursementCreateViewLifecycle.js` 贯通 `initialDraftPayload`,让申请详情带出的核对表首条消息保留原 `claim_id`,保存草稿/直接提交时走已有 `application_edit_claim_id` 更新链路,不再新建或卡在无上下文状态。
|
||||||
|
- 修改:`expenseApplicationPreview.js` 支持 `editableFields`,在修改申请场景只开放事由、时间、地点、出行方式;`aiApplicationPreviewActions.js` 将白名单写入 `application_editable_fields`,便于后续服务端字段级约束。
|
||||||
|
- 操作:manual 触发 `tools/agent-change-log/update_change_log.py --kind bug` 生成日志后补充真实修复记录;未修改后端接口逻辑,因为 `user_agent_application.py` 已允许 `draft/returned/supplement` 申请按 `application_edit_claim_id` 更新。
|
||||||
|
- 验证:`node --test web/tests/travel-request-detail-risk-advice.test.mjs` 通过;`node --test --test-name-pattern "application edit prefill opens assistant without auto submit" web/tests/app-shell-financial-assistant-entry.test.mjs` 通过;`node --test --test-name-pattern "application edit preview only allows reason time location and transport changes" web/tests/expense-application-fast-preview.test.mjs` 通过;`node web/tests/ai-application-preview-actions.test.mjs` 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。全量跑 `app-shell-financial-assistant-entry.test.mjs` 与 `expense-application-fast-preview.test.mjs` 时仍有既有结构断言漂移,失败点与本次修改无关。
|
||||||
|
- 影响:用户在申请详情里看到草稿和退回申请都能继续修改;修改面被限制在事由、时间、地点、出行方式,其他职级、负责人、费用标准和金额仍由原单或规则测算带入。
|
||||||
|
- 16:51:补充修复:取消底部“修改申请”按钮,改为申请详情格子内联铅笔编辑。
|
||||||
|
- Git 提交检查:fetch 仍失败:fatal: unable to access 'https://www.caoxiaozhu.com:13002/YG-Soft/X-Financial.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.caoxiaozhu.com:13002;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交仍为 8a2ae6eb、992cf71f、54356ba8、e9d7c56d。
|
||||||
|
- 修改:`TravelRequestDetailView.vue` 移除底部“修改申请”按钮,在申请详情事实格子的值旁显示小铅笔;点击后根据字段类型切换为日期框、下拉框或文本输入,并提供保存/取消按钮。
|
||||||
|
- 修改:`travelRequestDetailSetup.js` 新增 `applicationDetailEditor` 状态、`canEditApplicationDetailItem`、`openApplicationDetailEditor`、`saveApplicationDetailEdit` 等内联编辑流程;保存时复用 `runAiApplicationPreviewAction` 的 `save_draft` 路径和 `application_edit_claim_id`,保持更新原申请单。
|
||||||
|
- 修改:`travel-request-detail-view.css` 收窄申请详情标签选择器,避免内联编辑内部 `span` 被误当字段名,并补充铅笔、确认、取消、编辑控件样式。
|
||||||
|
- 验证:先运行新增目标断言失败,确认旧按钮仍存在;实现后 `node --test --test-name-pattern "draft or returned application detail edits allowed facts inline" web/tests/travel-request-detail-risk-advice.test.mjs` 通过;`node --test web/tests/travel-request-detail-risk-advice.test.mjs` 62 条通过;`node web/tests/ai-application-preview-actions.test.mjs` 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。本地 `http://[::1]:5173/app/documents` 可达但跳转登录页,因无登录态未继续真实详情点击。
|
||||||
|
- 影响:草稿/退回申请不再需要先打开 AI 助手才能改,用户可以直接在详情表格中改事由、时间、地点、出行方式;其它字段仍不可编辑,由原单或规则测算带入。
|
||||||
|
- 17:02:补充修复:详情页因残留 `handleModifyApplication` 返回项导致 setup 阶段崩溃。
|
||||||
|
- Git 提交检查:fetch 仍失败:fatal: unable to access 'https://www.caoxiaozhu.com:13002/YG-Soft/X-Financial.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.caoxiaozhu.com:13002;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交仍为 8a2ae6eb、992cf71f、54356ba8、e9d7c56d。
|
||||||
|
- 修改:`travelRequestDetailSetup.js` 删除 return 对象里已经不存在的 `handleModifyApplication`,避免详情页初始化时抛 `ReferenceError`。
|
||||||
|
- 修改:`travel-request-detail-risk-advice.test.mjs` 为内联编辑回归用例增加 `handleModifyApplication` 不得残留的断言;先运行目标用例确认失败,再删除残留返回项后确认转绿。
|
||||||
|
- 验证:`node --test --test-name-pattern "draft or returned application detail edits allowed facts inline" web/tests/travel-request-detail-risk-advice.test.mjs` 先失败后通过;`node --test web/tests/travel-request-detail-risk-advice.test.mjs` 62 条通过;`node web/tests/ai-application-preview-actions.test.mjs` 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。真实本地 `[::1]:5173` 以管理员打开 `AG2YUJ9FB` 详情页无控制台错误;切换申请人 `caoxiaozhu@xf.com` 后同页可见 `申请详情` 和单号,铅笔按钮 5 个,点击后出现 1 个编辑控件,取消后无保存副作用。
|
||||||
|
- 影响:申请/报销详情页 setup 不再因为旧按钮处理函数缺失而白屏,内联铅笔编辑入口保留。
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# 写日志技能拆分 概念文档
|
||||||
|
|
||||||
|
更新时间:2026-06-25
|
||||||
|
|
||||||
|
文档路径:document/development/2026-06-25/feature/agent-change-log-split/CONCEPT.md
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
把原来单文件三段式工作日志拆成按日期聚合的功能点文档、bug 修复日志和每日 17:00 综合工作日志。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
- 当前现状:旧 `agent-change-log` 把所有变更追加到 `document/work-log/YYYY-MM-DD.md`,并固定包含 `当日工作内容`、`遗留问题`、`TODO`。
|
||||||
|
- 用户痛点:功能点规划、bug 修复和当天综合复盘混在一个日志文件里,后续追溯时难以按功能或问题拆开看。
|
||||||
|
- 业务影响:开发资料会越来越长,bug 修复证据和功能点设计边界容易互相干扰。
|
||||||
|
- 为什么现在需要做:`write-development-docs` 已经改为 `document/development/YYYY-MM-DD/feature/<功能点>/`,日志能力也需要跟随同一日期根目录拆分。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- [G1] bug 修复记录落到 `document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md`。
|
||||||
|
- [G2] bug 日志只保留修复记录,不再写 `遗留问题` 和 `TODO` 两块。
|
||||||
|
- [G3] 每天 17:00 汇总当天 `feature/` 和 `dev-logs/bugs/`,生成 `work-logs.med`。
|
||||||
|
- [G4] 保留 Git 双向检查,继续识别 upstream 新提交和本地 ahead 提交。
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- [NG1] 本轮不迁移历史 `document/work-log/*.md`。
|
||||||
|
- [NG2] 本轮不删除旧历史日志,避免破坏既有追溯。
|
||||||
|
- [NG3] 本轮不把非 bug 提交强行写入 bug 日志。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
- 目标用户:使用 Codex/Agent 维护 X-Financial 的开发者和后续接手的智能体。
|
||||||
|
- 使用入口:`agent-change-log` Skill、`tools/agent-change-log/update_change_log.py`、post-commit hook、每日 17:00 Codex automation。
|
||||||
|
- 核心场景:
|
||||||
|
1. 修复 bug 后写入当天 `dev-logs/bugs/<bug-slug>.md`。
|
||||||
|
2. 非 bug 功能点通过 `feature/<功能点>/CONCEPT.md` 和 `TODO.md` 沉淀。
|
||||||
|
3. 每天 17:00 汇总功能点与 bug,生成 `work-logs.med`。
|
||||||
|
- 异常场景:
|
||||||
|
- 没有 feature 或 bug 时,综合日志明确写“未发现”。
|
||||||
|
- 提交标题不像 bug 时,post-commit 自动日志跳过。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- [C1] 输入能力:支持 `--kind bug`、`--kind auto`、`--kind summary` 三种模式。
|
||||||
|
- [C2] 处理能力:按日期创建 `dev-logs/bugs`,按 bug slug 记录修复内容。
|
||||||
|
- [C3] 输出能力:输出 bug 修复记录或每日 `work-logs.med`。
|
||||||
|
- [C4] 状态与权限:沿用 Git fetch/status/log 检查,不主动 merge/rebase。
|
||||||
|
- [C5] 边界与降级:官方 skill 校验脚本不可用时,用脚本单测、frontmatter 和 diff check 兜底。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
当前不涉及。
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
当前不涉及业务后端;只修改仓库级 Skill、脚本和 hook。
|
||||||
|
|
||||||
|
### 算法与规则
|
||||||
|
|
||||||
|
- 输入:commit subject、用户传入的 bug title/slug、当天 feature 和 bug 文档。
|
||||||
|
- 流程:bug 模式写入 bug 文件;auto 模式识别 bug-like commit;summary 模式扫描 `feature/` 和 `dev-logs/bugs/` 后生成综合日志。
|
||||||
|
- 输出:`dev-logs/bugs/*.md` 或 `work-logs.med`。
|
||||||
|
- 解释:summary 中保留来源目录和综合分析,便于复盘追溯。
|
||||||
|
|
||||||
|
### 数据与契约
|
||||||
|
|
||||||
|
- 核心字段:日期、bug slug、bug title、Git 提交检查、修改、操作、验证、影响。
|
||||||
|
- 状态枚举:`auto`、`bug`、`summary`。
|
||||||
|
- 兼容策略:保留旧 `document/work-log` 历史,不新增旧格式。
|
||||||
|
- 版本/审计:本轮变更通过本地 checkpoint commit 保留。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
当前功能不涉及显式数学公式。
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
后端:
|
||||||
|
|
||||||
|
- 当前不涉及后端服务。
|
||||||
|
|
||||||
|
前端:
|
||||||
|
|
||||||
|
- 当前不涉及前端构建。
|
||||||
|
|
||||||
|
集成:
|
||||||
|
|
||||||
|
- 运行 `python3 tools/agent-change-log/test_update_change_log.py`,覆盖 bug 日志、非 bug 自动跳过、每日综合日志聚合。
|
||||||
|
|
||||||
|
手工验证:
|
||||||
|
|
||||||
|
- 运行 `update_change_log.py --kind bug --dry-run` 和 `--kind summary --dry-run` 检查目标路径和输出内容。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- [A1] 功能验收:bug 日志路径为 `document/development/YYYY-MM-DD/dev-logs/bugs/<bug-slug>.md`。
|
||||||
|
- [A2] 性能指标:脚本单测在 60s 内完成。
|
||||||
|
- [A3] 质量指标:不再为新日志写入旧三段式 `document/work-log/YYYY-MM-DD.md`。
|
||||||
|
- [A4] 安全/权限指标:脚本不做 merge/rebase,不删除历史日志。
|
||||||
|
- [A5] 可观测性:17:00 automation 生成 `work-logs.med` 后报告路径和是否发现 feature/bug。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
- 风险:`work-logs.med` 是按用户原文保留的扩展名,可能与常见 `.md` 扩展名不一致。
|
||||||
|
- 已处理依赖:已创建 Codex automation 执行每日 17:00 summary。
|
||||||
|
- 待确认:后续是否需要迁移历史 `document/work-log`。
|
||||||
|
- 降级策略:automation 不可用时可手动运行 `python3 tools/agent-change-log/update_change_log.py --kind summary`。
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
- 2026-06-25:重写 `agent-change-log` Skill 和脚本,新增 split log 测试,创建每日 17:00 Codex automation。
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# 写日志技能拆分 开发 TODO
|
||||||
|
|
||||||
|
更新时间:2026-06-25
|
||||||
|
|
||||||
|
文档路径:document/development/2026-06-25/feature/agent-change-log-split/TODO.md
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力、方案或验收点。
|
||||||
|
- 只有完成并验证后,才能把 `[ ]` 改成 `[x]`。
|
||||||
|
- 勾选时在任务后补充简短证据,例如文件、接口、命令或验证结果。
|
||||||
|
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 背景与问题] 确认旧日志 Skill、脚本、hook 和 AGENTS 仍指向 `document/work-log/YYYY-MM-DD.md`。
|
||||||
|
证据:`rg -n "document/work-log|当日工作内容|遗留问题|TODO" AGENTS.md .codex/skills/agent-change-log tools/agent-change-log .githooks/post-commit`。
|
||||||
|
- [x] [CONCEPT: 目标与非目标] 明确本轮不迁移历史 `document/work-log/*.md`。
|
||||||
|
证据:`CONCEPT.md` 非目标已列明历史不迁移。
|
||||||
|
|
||||||
|
## 2. 契约与设计
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 功能能力] 定义 `auto`、`bug`、`summary` 三种脚本模式。
|
||||||
|
证据:`tools/agent-change-log/update_change_log.py` 的 `--kind` 参数。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 固定新路径 `document/development/YYYY-MM-DD/dev-logs/bugs` 与 `work-logs.med`。
|
||||||
|
证据:`agent-change-log` Skill、AGENTS 和脚本常量。
|
||||||
|
|
||||||
|
## 3. 后端实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 后端] 重写日志脚本,支持 bug 记录、auto 跳过非 bug、summary 聚合。
|
||||||
|
证据:`tools/agent-change-log/update_change_log.py`。
|
||||||
|
- [x] [CONCEPT: 后端] 更新 post-commit hook,改为 `--kind auto`。
|
||||||
|
证据:`.githooks/post-commit`。
|
||||||
|
|
||||||
|
## 4. 算法/规则实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 算法与规则] 实现 bug-like commit 识别规则。
|
||||||
|
证据:`looks_like_bug()` 覆盖 `fix`、`bugfix`、`修复`、`失败`、`异常` 等关键词。
|
||||||
|
- [x] [CONCEPT: 算法与规则] 实现 feature 和 bug 汇总生成 `work-logs.med`。
|
||||||
|
证据:`build_daily_summary()`。
|
||||||
|
|
||||||
|
## 5. 前端实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 前端] 当前不涉及前端页面。
|
||||||
|
证据:本轮只修改仓库 Skill、脚本、hook、文档和 automation。
|
||||||
|
|
||||||
|
## 6. 测试与验证
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 测试方案] 补充脚本回归测试。
|
||||||
|
证据:`tools/agent-change-log/test_update_change_log.py`。
|
||||||
|
- [x] [CONCEPT: 测试方案] 运行脚本单测。
|
||||||
|
证据:`python3 tools/agent-change-log/test_update_change_log.py`,3 tests OK。
|
||||||
|
- [x] [CONCEPT: 测试方案] dry-run 验证 bug 路径和 summary 路径。
|
||||||
|
证据:`--kind bug --dry-run` 输出 `document/development/2026-06-25/dev-logs/bugs/draft-preview-disappears.md`;`--kind summary --dry-run` 输出 `document/development/2026-06-25/work-logs.med`。
|
||||||
|
|
||||||
|
## 7. 文档收尾
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 指标与验收] 更新 `agent-change-log` Skill、AGENTS 和 README。
|
||||||
|
证据:`.codex/skills/agent-change-log/SKILL.md`、`AGENTS.md`、`tools/agent-change-log/README.md`。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 创建每日 17:00 Codex automation。
|
||||||
|
证据:automation id `x-financial-daily-split-work-log`。
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# AI 对话 UI 样式重构与 SaaS 化设计方案
|
||||||
|
|
||||||
|
为了让 X-Financial AI 助手的对话界面展现更专业的金融与 SaaS 企业化视觉,我们对前端全局的 Markdown 渲染样式进行了重构。
|
||||||
|
|
||||||
|
## 设计目标
|
||||||
|
- **中性色打底**:对话块背景、边框移除饱和偏色,统一采用 Slate/Neutral 冷灰色调(如 `#f8fafc` 和 `#cbd5e1`)。
|
||||||
|
- **降低色彩冗余**:将非必须的高亮蓝色改为中性灰色,各单据卡片仅在状态字样与极淡头部透明底中体现状态点缀,避免彩色杂乱堆积。
|
||||||
|
- **界面扁平微阴影**:移除带有斜向彩色发光的阴影与复杂的背景图案,采用 1px 实线描边配合微阴影,契合 Stripe, Jira 等现代 SaaS 产品规范。
|
||||||
|
|
||||||
|
## 详细参数定义
|
||||||
|
- **引用块 (`blockquote` / `.ai-html-callout`)**:
|
||||||
|
- 边框:`3px solid #cbd5e1`
|
||||||
|
- 背景:`#f8fafc`
|
||||||
|
- 文字:`#334155`
|
||||||
|
- **信息网格 (`.ai-html-focus-grid`)**:
|
||||||
|
- 边框:`3px solid #cbd5e1`
|
||||||
|
- 标题颜色:`#475569` (Slate-600)
|
||||||
|
- **单据卡片 (`.ai-document-card`)**:
|
||||||
|
- 描边:`1px solid #e2e8f0`
|
||||||
|
- 投影:`0 1px 2px 0 rgba(15, 23, 42, 0.05)`
|
||||||
|
- 头部默认背景:`rgba(241, 245, 249, 0.5)`
|
||||||
|
- 状态点缀色:
|
||||||
|
- `is-success` (Teal-700): `#0f766e`
|
||||||
|
- `is-warning` (Amber-700): `#b45309`
|
||||||
|
- `is-danger` (Red-700): `#b91c1c`
|
||||||
|
- is-pending (Blue-600): `#2563eb`
|
||||||
|
|
||||||
|
## 消息排版格式规范
|
||||||
|
- **限制 Alert 引用块为单条**:在警示/阻塞状态下,只允许包含一条 `>` 引用块用作 Alert 提示。
|
||||||
|
- **列表扁平化**:发起前核对结果等普通多维状态信息,一律不使用 `>` 引用块,合并为标准的无序列表展示,保持界面视觉扁平、整洁。
|
||||||
|
- **常规表单数据平铺**:时间、单据编号等字段,均采用扁平加粗文本平铺,不再触发 focus-grid 等具有边框线的额外容器。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# 任务计划 (SaaS 风格视觉优化)
|
||||||
|
|
||||||
|
- [x] 优化全局 `blockquote` 与 `.ai-html-callout` 的色彩饱和度,改用 Slate 灰蓝色系打底。
|
||||||
|
- [x] 优化 `.ai-html-focus-grid` 信息网格与竖线的亮蓝色调。
|
||||||
|
- [x] 移除单据卡片上的 `ai-document-card-bg.png` 渐变背景图。
|
||||||
|
- [x] 扁平化单据卡片的描边与中性阴影。
|
||||||
|
- [x] 优化卡片语义状态角标与操作链接的 SaaS 蓝及点缀。
|
||||||
|
- [x] 优化原生 Markdown 列表 `li::marker` 与表格外包边框的色彩层级。
|
||||||
|
- [x] 重构预审与冲突消息的 Markdown 输出格式,消除不必要的 `>` 引用块,攻克“三条大竖杠”的排版痛点。
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
# 主题设置与企业沉稳 AI 模式 概念文档
|
||||||
|
|
||||||
|
更新时间:2026-06-25
|
||||||
|
|
||||||
|
文档路径:document/development/2026-06-25/feature/theme-settings-enterprise-ai-style/CONCEPT.md
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
将系统设置中的“界面皮肤”升级为“主题设置”,从单纯色板选择扩展为产品体验风格选择,并让 AI 模式在“企业沉稳”主题下呈现更符合企业级 SaaS 的低噪声、结构化、克制风格。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
当前系统设置里的外观入口仍偏向“界面皮肤”语义,主要表达颜色和视觉皮肤选择。这个命名过窄,无法承载用户希望配置的完整体验风格。
|
||||||
|
|
||||||
|
现有 AI 模式默认更接近动感活泼风格,使用较多渐变、明亮色块和活跃视觉标识。它适合演示和助手化体验,但在企业级财务、审批、风控、报销场景中,容易显得色彩过重,不够沉稳。
|
||||||
|
|
||||||
|
用户希望在系统设置中明确提供主题类型,至少覆盖:
|
||||||
|
|
||||||
|
1. 动感活泼:保留当前这种更有活力、更有 AI 助手感的主题。
|
||||||
|
2. 企业沉稳:符合企业 SaaS 风格,尤其 AI 模式下的对话图标、样式和整体风格需要克制、稳定,减少颜色渲染。
|
||||||
|
3. 专业智能:作为第三类默认方案,介于前两者之间,保留少量智能化识别感,但整体更收敛。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 将“界面皮肤”入口重命名为“主题设置”,让用户理解这里配置的是整体体验风格。
|
||||||
|
- 将主题从一组颜色选项收敛成三类可理解的主题类型。
|
||||||
|
- 在“企业沉稳”主题下,让 AI 模式呈现企业后台应用的专业感。
|
||||||
|
- 保持现有设置保存链路稳定,优先复用当前 appearance 配置和主题变量机制。
|
||||||
|
- 为后续进一步扩展租户品牌色、暗色模式、组件密度预留结构边界。
|
||||||
|
|
||||||
|
非目标:
|
||||||
|
|
||||||
|
- 不重做整个系统设置信息架构。
|
||||||
|
- 不一次性重写所有业务页面的视觉风格。
|
||||||
|
- 不改变 AI 意图识别、报销流程、审批逻辑和后端业务规则。
|
||||||
|
- 不新增复杂的租户级主题发布、审批或版本管理能力。
|
||||||
|
- 不引入新的前端主题框架或额外依赖。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
管理员在系统设置中进入“主题设置”,选择适合当前组织的产品体验风格。
|
||||||
|
|
||||||
|
演示、培训、轻量工作台场景可以使用“动感活泼”,保留当前更有活力的 AI 交互表达。
|
||||||
|
|
||||||
|
企业正式生产环境可以使用“企业沉稳”,让财务、审批、风控和 AI 对话看起来像成熟的企业应用,而不是营销页或玩具化助手。
|
||||||
|
|
||||||
|
希望保留智能化识别但又不希望过度活泼的组织,可以使用“专业智能”。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
主题设置页面需要提供三类主题:
|
||||||
|
|
||||||
|
- 动感活泼:当前风格延续,允许渐变、轻动效和更明显的 AI 识别色。
|
||||||
|
- 企业沉稳:低饱和色、白灰底、轻描边、少阴影、少渐变,强调信息层级和业务可信度。
|
||||||
|
- 专业智能:更克制的智能风格,允许小面积蓝灰、紫灰或品牌色点缀,但避免大面积彩色渲染。
|
||||||
|
|
||||||
|
页面文案调整:
|
||||||
|
|
||||||
|
- 左侧菜单从“界面皮肤”调整为“主题设置”。
|
||||||
|
- 页面标题从“界面皮肤与企业主色”调整为“主题风格与界面体验”。
|
||||||
|
- 保存反馈从“界面皮肤已保存”调整为“主题设置已保存”。
|
||||||
|
- 说明文案从“皮肤/配色”改为“主题/体验风格”。
|
||||||
|
|
||||||
|
AI 模式联动:
|
||||||
|
|
||||||
|
- 动感活泼:保留当前 AI 模式视觉语言。
|
||||||
|
- 企业沉稳:对 AI 对话区、消息气泡、工具调用状态、思考过程、图标、卡片、提示块做克制化覆写。
|
||||||
|
- 专业智能:保留轻量 AI 识别感,但降低渐变、发光、背景装饰和高饱和强调色。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
配置模型优先沿用当前系统设置外观配置,避免为了命名调整引入后端迁移风险。
|
||||||
|
|
||||||
|
首期可以继续复用 `appearanceForm.themeSkin` 作为持久化字段,将值从原先色板语义逐步映射为主题语义:
|
||||||
|
|
||||||
|
- `vivid`:动感活泼。
|
||||||
|
- `enterprise`:企业沉稳。
|
||||||
|
- `intelligent`:专业智能。
|
||||||
|
|
||||||
|
为了减少现有 CSS 变量和本地存储影响,前端可以在兼容期保留 `themeSkin` 字段,同时在 DOM 上补充更清晰的主题标识:
|
||||||
|
|
||||||
|
```text
|
||||||
|
document.documentElement.dataset.themeSkin = value
|
||||||
|
document.documentElement.dataset.themeMode = value
|
||||||
|
```
|
||||||
|
|
||||||
|
旧值兼容策略:
|
||||||
|
|
||||||
|
- 旧的 `sky`、`blue`、`emerald` 等明亮主题默认映射到“动感活泼”。
|
||||||
|
- 旧的 `navy`、`slate` 等偏稳重主题默认映射到“企业沉稳”。
|
||||||
|
- 无法识别的值回退到“企业沉稳”,保证生产环境默认更克制。
|
||||||
|
|
||||||
|
设置页结构:
|
||||||
|
|
||||||
|
- 保留现有表单保存机制。
|
||||||
|
- 将原色板卡片改为三张主题选项卡。
|
||||||
|
- 每个主题卡展示名称、适用场景、视觉关键词和小型预览。
|
||||||
|
- 当前选中主题需要有明确选中态,但避免大面积彩色边框。
|
||||||
|
|
||||||
|
企业沉稳 AI 模式样式:
|
||||||
|
|
||||||
|
- 通过 `[data-theme-mode="enterprise"]` 或 `[data-theme-skin="enterprise"]` 覆写 `personal-workbench-ai-mode.css` 中的 AI 模式变量。
|
||||||
|
- 背景从多层 radial-gradient 收敛为白灰底或极轻线性渐变。
|
||||||
|
- 对话气泡使用白底、浅灰描边和稳定文字层级。
|
||||||
|
- AI 图标使用低饱和单色或品牌主色的小面积点缀。
|
||||||
|
- 思考过程、工具调用、风险提示等区域使用结构化信息块,减少发光、彩色渐变和装饰性元素。
|
||||||
|
- 风险、成功、警告等语义色保留,但只在图标、状态条或小面积标签上表达。
|
||||||
|
|
||||||
|
专业智能主题样式:
|
||||||
|
|
||||||
|
- 保留少量 AI 识别色,例如主色强调、轻量渐变按钮或小面积状态标识。
|
||||||
|
- 背景和卡片仍以企业应用的可读性为主。
|
||||||
|
- 避免全屏强背景、过多彩色阴影和过密装饰。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
本功能不涉及复杂算法。
|
||||||
|
|
||||||
|
需要定义稳定的主题值映射函数:
|
||||||
|
|
||||||
|
```text
|
||||||
|
normalizeThemeMode(rawTheme):
|
||||||
|
if rawTheme in ["vivid", "sky", "blue", "emerald", "purple"]:
|
||||||
|
return "vivid"
|
||||||
|
if rawTheme in ["enterprise", "navy", "slate", "gray"]:
|
||||||
|
return "enterprise"
|
||||||
|
if rawTheme in ["intelligent"]:
|
||||||
|
return "intelligent"
|
||||||
|
return "enterprise"
|
||||||
|
```
|
||||||
|
|
||||||
|
主题应用顺序:
|
||||||
|
|
||||||
|
```text
|
||||||
|
后端保存值 / 本地缓存值
|
||||||
|
-> normalizeThemeMode
|
||||||
|
-> 写入 appearanceForm.themeSkin
|
||||||
|
-> 写入 root dataset
|
||||||
|
-> 应用 CSS 变量
|
||||||
|
-> AI 模式按 data-theme-mode 覆写组件样式
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
前端单元和静态测试:
|
||||||
|
|
||||||
|
- 断言系统设置外观入口显示为“主题设置”。
|
||||||
|
- 断言页面标题显示为“主题风格与界面体验”。
|
||||||
|
- 断言三类主题均可见:动感活泼、企业沉稳、专业智能。
|
||||||
|
- 断言企业沉稳主题保存后写入稳定主题值。
|
||||||
|
- 断言旧主题值能通过 normalize 逻辑回退到可识别主题。
|
||||||
|
- 断言 AI 模式存在企业沉稳主题 CSS 钩子。
|
||||||
|
|
||||||
|
构建验证:
|
||||||
|
|
||||||
|
- 运行前端构建,确保主题设置改动不破坏现有页面。
|
||||||
|
- 运行已有设置相关测试,确保系统设置保存链路不回归。
|
||||||
|
|
||||||
|
真实页面验收:
|
||||||
|
|
||||||
|
- 打开 `/app/settings?section=appearance`,确认左侧菜单、页面标题、三类主题和保存反馈符合预期。
|
||||||
|
- 切换到“企业沉稳”后打开 AI 工作台,确认对话区域、图标、消息、思考过程和提示块明显减少彩色渲染。
|
||||||
|
- 切回“动感活泼”,确认现有风格仍可正常展示。
|
||||||
|
- 切到“专业智能”,确认介于两者之间,不退化为纯色板换色。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
功能验收:
|
||||||
|
|
||||||
|
- “界面皮肤”在设置入口和页面主标题中完成改名。
|
||||||
|
- 用户可以选择三类主题,而不是面对一堆颜色皮肤。
|
||||||
|
- 选择主题后刷新页面仍保持选中态。
|
||||||
|
- 企业沉稳主题下,AI 模式整体视觉明显更接近企业 SaaS。
|
||||||
|
- 动感活泼主题不丢失现有活力风格。
|
||||||
|
- 专业智能主题具备独立视觉边界。
|
||||||
|
|
||||||
|
设计验收:
|
||||||
|
|
||||||
|
- 企业沉稳主题下不出现大面积高饱和渐变背景。
|
||||||
|
- AI 对话图标和卡片不依赖强发光、强阴影或多彩背景表达层级。
|
||||||
|
- 风险、审批、单据、工具调用等业务信息优先使用结构化排版。
|
||||||
|
- 主题卡片文字不溢出,不在移动端产生拥挤或重叠。
|
||||||
|
|
||||||
|
工程验收:
|
||||||
|
|
||||||
|
- 不新增前端依赖。
|
||||||
|
- 不引入后端表结构迁移。
|
||||||
|
- 旧主题值有明确兼容策略。
|
||||||
|
- 相关测试、构建和真实页面验收通过。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
第三类主题默认命名为“专业智能”。如果后续用户指定更贴合业务的名称,可以只替换展示文案,不影响主题值和实现结构。
|
||||||
|
|
||||||
|
仅靠全局 CSS 变量可能无法完全消除 AI 模式的活泼感。企业沉稳主题需要对 AI 模式局部样式做有针对性的覆写。
|
||||||
|
|
||||||
|
旧色板值如果直接隐藏,可能让已有用户困惑。首期需要在兼容层处理旧值,并保证保存一次后落到新的三类主题值。
|
||||||
|
|
||||||
|
如果后续需要租户品牌色和主题组合,应该把“主题模式”和“品牌主色”拆成两个独立配置,避免再次把体验风格退化成颜色选择。
|
||||||
|
|
||||||
|
## 本轮文档记录
|
||||||
|
|
||||||
|
本轮已完成主题设置功能的前端实现:系统设置入口改名为“主题设置”,主题选项收敛为“动感活泼 / 企业沉稳 / 专业智能”三类,并通过主题归一化兼容旧色板值。
|
||||||
|
|
||||||
|
企业沉稳主题已联动 AI 工作台样式:根节点写入 `data-theme-mode="enterprise"`,AI 模式背景、图标容器、输入框、消息、思考过程和建议动作改为低饱和、轻描边、少渲染的企业 SaaS 风格。
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# 主题设置与企业沉稳 AI 模式 开发 TODO
|
||||||
|
|
||||||
|
更新时间:2026-06-25
|
||||||
|
|
||||||
|
文档路径:document/development/2026-06-25/feature/theme-settings-enterprise-ai-style/TODO.md
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每个任务都需要关联 `CONCEPT.md` 中的章节,格式为 `[CONCEPT: 章节名]`。
|
||||||
|
- 完成实现后再勾选对应任务,不用文档勾选代替代码验证。
|
||||||
|
- 涉及真实页面效果的任务,需要在 5173 页面完成验收后再标记完成。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 背景与问题] 确认当前设置外观入口仍使用“界面皮肤”语义,后续需要改为“主题设置”。
|
||||||
|
- [x] [CONCEPT: 方案设计] 确认当前主题能力主要依赖 `appearanceForm.themeSkin`、主题选项和根节点 dataset。
|
||||||
|
- [x] [CONCEPT: 方案设计] 确认企业沉稳 AI 模式主要需要覆写 `personal-workbench-ai-mode.css` 中的背景、对话、图标和提示块样式。
|
||||||
|
- [x] [CONCEPT: 风险与开放问题] 梳理旧色板值到三类主题值的完整映射清单。
|
||||||
|
- [x] [CONCEPT: 用户与场景] 确认三类主题在设置页中的展示顺序和说明文案。
|
||||||
|
|
||||||
|
## 2. 契约与设计
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 功能能力] 将主题枚举收敛为 `vivid`、`enterprise`、`intelligent`。
|
||||||
|
- [x] [CONCEPT: 功能能力] 明确三类主题的中文名称、适用场景和视觉关键词。
|
||||||
|
- [x] [CONCEPT: 方案设计] 设计 `normalizeThemeMode` 兼容函数,保证旧值和未知值都有稳定回退。
|
||||||
|
- [x] [CONCEPT: 方案设计] 决定是否新增 `themeMode` 前端概念,并保持与 `themeSkin` 字段兼容。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 定义企业沉稳 AI 模式的视觉验收标准。
|
||||||
|
|
||||||
|
## 3. 后端实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 方案设计] 评估后端 settings schema 是否需要补充主题枚举校验。
|
||||||
|
- [x] [CONCEPT: 方案设计] 若继续复用 `themeSkin`,确保后端允许新主题值保存。
|
||||||
|
- [ ] [CONCEPT: 测试方案] 补充或更新设置持久化测试,覆盖三类主题值。
|
||||||
|
- [x] [CONCEPT: 风险与开放问题] 确认不需要数据库结构迁移,并在实现说明中记录。
|
||||||
|
|
||||||
|
## 4. 算法/规则实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 算法与公式] 实现旧主题值到新主题值的 normalize 逻辑。
|
||||||
|
- [x] [CONCEPT: 算法与公式] 为未知值设置默认回退策略,优先回退到“企业沉稳”。
|
||||||
|
- [x] [CONCEPT: 算法与公式] 确保本地缓存、后端返回值和根节点 dataset 使用同一套归一化结果。
|
||||||
|
|
||||||
|
## 5. 前端实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 功能能力] 将设置左侧菜单“界面皮肤”改为“主题设置”。
|
||||||
|
- [x] [CONCEPT: 功能能力] 将页面标题改为“主题风格与界面体验”。
|
||||||
|
- [x] [CONCEPT: 功能能力] 将保存反馈和说明文案从“皮肤”语义调整为“主题”语义。
|
||||||
|
- [x] [CONCEPT: 功能能力] 将原色板式选项调整为三类主题卡片。
|
||||||
|
- [x] [CONCEPT: 功能能力] 为“动感活泼”保留当前视觉风格。
|
||||||
|
- [x] [CONCEPT: 方案设计] 为“企业沉稳”新增 AI 模式样式覆写。
|
||||||
|
- [x] [CONCEPT: 方案设计] 为“专业智能”新增介于活泼和沉稳之间的样式边界。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 检查主题卡片、按钮和说明文字在移动端不溢出、不重叠。
|
||||||
|
- [x] [CONCEPT: 方案设计] 保证刷新页面后主题选择和 AI 模式样式仍然一致。
|
||||||
|
|
||||||
|
## 6. 测试与验证
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 测试方案] 更新设置页相关前端测试,断言“主题设置”和三类主题选项。
|
||||||
|
- [x] [CONCEPT: 测试方案] 补充 normalize 逻辑测试,覆盖旧值、未知值和三类新值。
|
||||||
|
- [x] [CONCEPT: 测试方案] 补充 AI 模式企业沉稳 CSS 钩子测试或静态断言。
|
||||||
|
- [x] [CONCEPT: 测试方案] 运行前端设置相关定向测试。
|
||||||
|
- [x] [CONCEPT: 测试方案] 运行 `npm --prefix web run build`。
|
||||||
|
- [x] [CONCEPT: 测试方案] 运行 `git diff --check`。
|
||||||
|
- [x] [CONCEPT: 测试方案] 在真实 5173 页面验收 `/app/settings?section=appearance`。
|
||||||
|
- [ ] [CONCEPT: 测试方案] 在真实 5173 页面验收 AI 工作台三类主题切换效果。
|
||||||
|
|
||||||
|
## 7. 文档收尾
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 本轮文档记录] 实现完成后更新本文勾选状态。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 在最终交付说明中记录测试、构建和真实页面验收结果。
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 若第三类主题命名发生变化,同步更新概念文档和测试描述。
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# 多 task 串行推进时 task2(业务招待费报销)无法启动
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 12:14:记录 bug 修复:多 task 串行推进时,task1(出差申请)做完后点击"继续处理费用报销",task2(业务招待费报销)根本无法启动,报销草稿交互不起来,task2 语义(招待费/2000元/昨天)全部丢失。
|
||||||
|
- Git 提交检查:`git fetch --all --prune` 因远端 SSL 连接失败未拉到新内容;`HEAD..@{u}` 为空(无 upstream 新提交);`@{u}..HEAD` 本地领先 8 个提交,其中与本 bug 相关的前置提交为 `3a5664c4 feat(web): 多 task 串行推进`、`3e4b1e15 fix(web): 保存草稿/提交成功后也推进到下一个 task`、`43c3ff86 fix(web): steward plan 确认按钮直接拉起申请预览,不丢失 remaining tasks`。
|
||||||
|
- 修改:
|
||||||
|
- `web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js`:`travel_reimbursement` 分支不再硬编码 `requiresApplicationBeforeReimbursement=true`,改为从 `steward_current_task.ontology_fields` 解析费用类型并调 `requiresApplicationBeforeReimbursement(expenseType)` 判断;用 `buildAiExpenseDraftPrefillValues` 把 task 语义(金额/时间/事由/地点)预填进报销草稿;透传 `steward_remaining_tasks`。`ai_application_start_inline` 分支也透传 `prefill_values` 和 `steward_remaining_tasks`,让"查不到申请单→发起申请单"后能回到 task2。`buildNextTaskSuggestedAction` payload 补 `steward_remaining_tasks: remainingTasks.slice(1)`,防 3+ task 断链。
|
||||||
|
- `web/src/composables/workbenchAiMode/useWorkbenchAiExpenseFlow.js`:`startAiExpenseDraft` 扩展第 4 参 `options`(`prefillValues`/`stewardRemainingTasks`);`resolveAiExpenseApplicationLink` 接收 options,查不到申请单时按费用类型动态生成"确认发起业务招待申请"按钮(不再写死"出差申请"),并透传 `prefill_values`/`steward_remaining_tasks`;新增 `attachStewardRemainingTasks`/`resolveStewardRemainingTasks`/`resolveRequiredApplicationLabel`/`buildExpenseDraftNextTaskAction` helper,把 remaining tasks 上下文挂在 draft 上贯穿报销→关联申请单→pollLinkedDraftJob 全流程;`advanceAiExpenseDraft`、`linkAiExpenseApplication`、`replaceInlineAssistantMessage`、`resumePendingLinkedReimbursementDraftJobs` 均补 remaining tasks 透传。
|
||||||
|
- `web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js`:`buildApplicationPreviewNextTaskAction` payload 补 `steward_remaining_tasks: remainingTasks.slice(1)`。
|
||||||
|
- `web/src/utils/aiExpenseDraftModel.js`:`createAiExpenseDraft` 新增第三参 `prefillValues`,按已填值推进 `stepKey` 到第一个未填字段;新增 `buildAiExpenseDraftPrefillValues` 把 task ontology 映射到草稿字段。
|
||||||
|
- `web/src/composables/workbenchAiMode/workbenchAiMessageModel.js`:`createInlineMessage`/`normalizeRuntimeMessage`/`serializeRuntimeMessage` 三处补 `stewardRemainingTasks` 字段读写,避免消息重建/刷新丢失推进上下文。
|
||||||
|
- `web/src/utils/aiWorkbenchConversationStore.js`:`normalizeMessage` 持久化时保留 `stewardRemainingTasks`。
|
||||||
|
- 操作:未提交(工作区有大量预先存在的未提交改动,未自动提交)。容器 `local-x-financial-linux` 此前未运行,已 `docker start` 恢复。
|
||||||
|
- 验证:在容器内(node v22.22.3)跑 `node --test tests/ai-expense-draft-model.test.mjs tests/ai-workbench-conversation-store.test.mjs tests/workbench-ai-action-router.test.mjs`,16 个测试全部通过(含新增的 `buildAiExpenseDraftPrefillValues`、`createAiExpenseDraft` 预填、`stewardRemainingTasks` 持久化、`travel_reimbursement` 分支预填+透传 4 个用例)。其余前端测试失败为基线已存在(git stash 对比确认,与本次改动无关,多为正则匹配源码文本的断言因工作区其他未提交改动而失败)。
|
||||||
|
- 影响:用户一次提问含"出差申请 + 招待费报销"等多 task 时,task1 完成后 task2 现在能正常启动:招待费类型、2000元金额、时间、事由会预填到报销草稿;招待费需要前置招待申请单(业务规则保留),查不到时按钮文案按类型动态展示并承接语义,发起申请单后能回到 task2;3+ task 不再断链;刷新会话后推进上下文不丢失。
|
||||||
|
|
||||||
|
- 12:23:继续修复同一 bug:模型计划已经返回“出差申请 + 业务招待费报销”两个 task 时,AI 工作台入口只消费第一个申请 task,第二个 task 没有自动开始。
|
||||||
|
- Git 提交检查:`git fetch --all --prune` 仍因 `LibreSSL SSL_connect: SSL_ERROR_SYSCALL` 失败;`HEAD..@{u}` 为空,未发现可见 upstream 新提交;`@{u}..HEAD` 本地领先 8 个提交:`43c3ff86 fix(web): steward plan 确认按钮直接拉起申请预览,不丢失 remaining tasks`、`3e4b1e15 fix(web): 保存草稿/提交成功后也推进到下一个 task`、`3a5664c4 feat(web): 多 task 串行推进 - task1 完成后自动展示 task2 确认按钮`、`d139a63e refactor(server): 意图识别改 LLM 驱动,规则只做闲聊拦截+resume 兜底`、`8a2ae6eb fix(server): gate_classify 复用 _classify_irrelevant_input 修复 off_topic 误杀`、`992cf71f refactor(server): Phase 1 图拓扑重构 - LangGraph 成为唯一编排者`、`54356ba8 refactor(server): scene 注册表骨架 + 统一门控管道设计文档`、`e9d7c56d feat(server): 会话上下文保留(LLM 历史 + 确定性兜底双保险)`。
|
||||||
|
- 修改:`workbenchAiIntentPlannerModel.js` 在归一化模型计划时保留 application task 后面的 `stewardRemainingTasks`,并只在存在剩余 task 时放进可执行申请请求;`usePersonalWorkbenchAiMode.js` 把 remaining tasks 传入申请预览,并在预览生成后通过现有 `steward_continue_next_task` 路由自动启动下一个 task;低置信确认按钮 payload 也继续携带队列;`useWorkbenchAiApplicationPreviewFlow.js` 在普通申请预览完成后调用 `onPreviewReadyForNextTask`;`useWorkbenchAiActionRouter.js` 的 `ai_application_confirm_intent` 分支同样透传 remaining tasks 并注册自动续跑回调。
|
||||||
|
- 操作:新增回归测试覆盖“模型返回申请 task + 业务招待报销 task 时,前端可执行请求保留第二个 task”,以及“低置信确认按钮不丢队列并提供自动续跑回调”;保留既有未提交工作区改动,没有回滚或提交其他文件。
|
||||||
|
- 验证:先看到新增测试红灯(`plan.stewardRemainingTasks` 为 `undefined`,确认按钮 options 也没有 remaining tasks),修复后 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-action-router.test.mjs` 通过 24/24;`git diff --check` 通过;`docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-local-linux timeout 60s /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_planner.py -k 'future_travel_without_apply_word_as_application or uses_llm_for_multi_financial_demands'` 通过 2/2;同命令在 `local-x-financial-linux` 收集阶段因该容器缺 `langgraph` 失败,已改用当前映射 5173 的 `x-financial-local-linux` 验证;`npm --prefix web run build` 通过;真实 `http://localhost:5173/api/v1/steward/plans` 采样确认该用户句子返回 `expense_application` 与 `reimbursement` 两个 task。
|
||||||
|
- 影响:用户输入“2月20-23日去上海出差3天,服务国网服务器部署,并且报销昨天的业务招待费2000元”时,前端不再在第一个申请预览处截断队列;申请预览生成后会自动把第二个业务招待费报销 task 交给现有报销流程继续处理,同时刷新/低置信确认路径也不丢 task 队列。
|
||||||
|
|
||||||
|
- 22:30:继续修复同一 bug:用户点击“保存草稿”后,task1 已经完成,但界面仍只展示“继续处理费用报销”按钮,没有自动开始 task2。
|
||||||
|
- Git 提交检查:`git fetch --all --prune` 仍因 `LibreSSL SSL_connect: SSL_ERROR_SYSCALL` 失败;`HEAD..@{u}` 为空,未发现可见 upstream 新提交;`@{u}..HEAD` 本地仍领先 8 个提交:`43c3ff86`、`3e4b1e15`、`3a5664c4`、`d139a63e`、`8a2ae6eb`、`992cf71f`、`54356ba8`、`e9d7c56d`。
|
||||||
|
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在保存草稿/提交申请成功后,如果当前申请消息还有 `stewardRemainingTasks`,就调用 `onApplicationActionCompleted` 自动续跑下一项;自动续跑时结果消息只保留查看详情动作,不再额外保留“继续处理”按钮,避免重复触发同一个 task。`usePersonalWorkbenchAiMode.js` 将该回调接到 `startModelPlannedNextTask`;`useWorkbenchAiActionRouter.js` 的低置信确认路径也同步传入保存/提交成功回调。
|
||||||
|
- 操作:先补红灯断言,要求工作台入口向申请预览流传 `onApplicationActionCompleted`,且申请预览流在保存/提交成功分支调用该回调;然后按同一条 `steward_continue_next_task` 路由复用原有报销启动逻辑。
|
||||||
|
- 验证:红灯阶段 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 失败在缺少 `onApplicationActionCompleted`;修复后 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 17/17 通过,`node --test web/tests/workbench-ai-action-router.test.mjs` 7/7 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。
|
||||||
|
- 影响:复合任务里用户保存第一张出差申请草稿后,系统会立即进入第二个业务招待费报销任务,不再要求用户手动点击“继续处理费用报销”;低置信确认后再保存草稿也保持同样行为。
|
||||||
|
|
||||||
|
- 22:45:继续修复同一 bug:真实页面已经显示“申请草稿已保存”,但保存动作结束后没有继续拉起第二个业务招待费报销 task。
|
||||||
|
- Git 提交检查:`git fetch --all --prune` 仍因 `LibreSSL SSL_connect: SSL_ERROR_SYSCALL` 失败;`HEAD..@{u}` 为空,未发现可见 upstream 新提交;`@{u}..HEAD` 本地领先 13 个提交:`6bdaeed6 chore: 忽略 .zcode 本地目录并更新规则表与开发日志`、`d5a8f847 refactor(web): 应用外壳/差旅详情/报销创建视图适配主题与多 task`、`c4b5fcc0 feat(web): AI 工作台多 task 串行推进与会话适配`、`5753899e feat(web): 主题皮肤系统与 LLM 设置面板重构`、`9c3fa80d feat(server): 设置持久化新增 LLM 模型表与主题字段`,以及前面记录过的 8 个本地 ahead 提交。
|
||||||
|
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在保存草稿/提交申请成功分支新增 `actionCompletedHandler`,优先使用本次 `executeInlineApplicationPreviewAction` / `startAiApplicationPreview` options 传入的 `onApplicationActionCompleted`,没有时再回落到 composable 初始化回调;自动保存草稿时同步把 `options.onApplicationActionCompleted` 透传给保存动作。`workbench-ai-application-context-submit.test.mjs` 新增“自动保存申请草稿后继续剩余 steward task”的行为测试;`workbench-ai-intent-planner-model.test.mjs` 更新结构断言,锁住 options 回调优先和自动保存透传契约。
|
||||||
|
- 操作:先用新增测试复现红灯:自动保存时确实只触发初始化回调,`options` 里的续跑回调没有被使用;再做最小修复,让保存动作按本次调用上下文续跑下一任务。
|
||||||
|
- 验证:红灯阶段 `node --test web/tests/workbench-ai-application-context-submit.test.mjs` 失败在 `fromOptions` 为 `undefined`;修复后该测试 2/2 通过;`node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 17/17 通过;`node --test web/tests/workbench-ai-action-router.test.mjs` 7/7 通过;真实 `http://127.0.0.1:5173/api/v1/steward/plans` 采样确认该用户句子仍返回 `expense_application` + `reimbursement` 两个 task;`npm --prefix web run build` 通过;`git diff --check` 通过。
|
||||||
|
- 影响:保存草稿结果消息到达后,前端会使用当前任务链路传下来的续跑回调立即处理剩余 task;用户截图里的“申请草稿已保存”不再是终点,后续业务招待费报销会自动进入现有报销流程。
|
||||||
17
document/development/2026-06-26/work-logs.med
Normal file
17
document/development/2026-06-26/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 2026-06-26 综合工作日志
|
||||||
|
|
||||||
|
生成时间:2026-06-26 17:45:36 CST
|
||||||
|
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||||
|
|
||||||
|
## 今日功能点
|
||||||
|
|
||||||
|
- 今日未发现功能点文档。
|
||||||
|
|
||||||
|
## 今日 Bugs
|
||||||
|
|
||||||
|
- 多 task 串行推进时 task2(业务招待费报销)无法启动:影响:用户输入“2月20-23日去上海出差3天,服务国网服务器部署,并且报销昨天的业务招待费2000元”时,前端不再在第一个申请预览处截断队列;申请预览生成后会自动把第二个业务招待费报销 task 交给现有报销流程继续处理,同时刷新/低置信确认路径也不丢 task 队列。(文件:`multi-task-reimbursement-not-starting.md`)
|
||||||
|
|
||||||
|
## 综合分析
|
||||||
|
|
||||||
|
- 问题侧记录了 1 个 bug 修复。
|
||||||
|
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||||
17
document/development/2026-06-27/work-logs.med
Normal file
17
document/development/2026-06-27/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 2026-06-27 综合工作日志
|
||||||
|
|
||||||
|
生成时间:2026-06-27 17:43:24 CST
|
||||||
|
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||||
|
|
||||||
|
## 今日功能点
|
||||||
|
|
||||||
|
- 今日未发现功能点文档。
|
||||||
|
|
||||||
|
## 今日 Bugs
|
||||||
|
|
||||||
|
- 今日未发现 bug 修复记录。
|
||||||
|
|
||||||
|
## 综合分析
|
||||||
|
|
||||||
|
- 今日目录下暂无功能点或 bug 记录。
|
||||||
|
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||||
17
document/development/2026-06-28/work-logs.med
Normal file
17
document/development/2026-06-28/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 2026-06-28 综合工作日志
|
||||||
|
|
||||||
|
生成时间:2026-06-28 18:30:35 CST
|
||||||
|
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||||
|
|
||||||
|
## 今日功能点
|
||||||
|
|
||||||
|
- 今日未发现功能点文档。
|
||||||
|
|
||||||
|
## 今日 Bugs
|
||||||
|
|
||||||
|
- 今日未发现 bug 修复记录。
|
||||||
|
|
||||||
|
## 综合分析
|
||||||
|
|
||||||
|
- 今日目录下暂无功能点或 bug 记录。
|
||||||
|
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
# AI 数据飞轮 概念文档
|
||||||
|
|
||||||
|
更新时间:2026-07-03
|
||||||
|
|
||||||
|
文档路径:document/development/2026-07-03/feature/ai-data-flywheel/CONCEPT.md
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
把用户反馈、人工修正、风险样本、评测结果自动沉淀并回流到下一次 LLM 推理与规则生成,让费控系统在不停机的情况下持续提升准确率、降低误报率和人工干预率。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
- 当前现状:项目已具备"聪明"的骨架——RAG(`knowledge_rag_runtime.py` + Qdrant)、风险规则自动生成(`risk_rule_generation*.py`)、反馈样本沉淀(`skills/domain/false-positive-sample-accumulator` 等 3 个 accumulator)、规则回放评测(`risk-algorithm-replay-evaluator`)、行为画像(`employee_behavior_profile*`)、用户反馈表(`agent_feedback.py` + `AgentOperationFeedback`)。
|
||||||
|
- 用户痛点:样本在往 accumulator 池子里堆,但**下一次 LLM 推理时并没有把这些样本检索出来当 few-shot 喂进去**;prompt 散落在各 `*_prompt.py`,没有版本号、没有在线 A/B、没有回归门禁;OCR 抽取的人工修正值没有回流成评测/训练数据;低分反馈只汇总不归因。
|
||||||
|
- 业务影响:系统每次推理都从"初始水平"出发,无法把历史踩过的坑转化成下一次的能力;改 prompt / 改规则无法证明是否变好,存在隐性回归风险;运营和算法同事看不到"系统在进步"的证据。
|
||||||
|
- 为什么现在需要做:飞轮骨架已齐,缺的是把"样本池 → 检索注入 + 评测门禁 → prompt/规则版本"这段断开的箭头接上。补上后整张图就转起来,且改动集中在 prompt 构造层 + 新增 eval 目录,不动业务主链路,风险低。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- [G1] few-shot 在线检索注入:推理前从样本池按 case 特征做向量检索,取 top-k 历史样本(含人工结论)拼进 system prompt。
|
||||||
|
- [G2] 黄金评测集 + 自动回归门禁:版本化 golden set,prompt/规则变更后在 golden set 上自动跑分,分数不达标禁止发布。
|
||||||
|
- [G3] Prompt 版本化 + Canary A/B:prompt 进表带版本号,支持 stable / canary 流量切分,反馈分数对比。
|
||||||
|
- [G4] 抽取修正回流:附件/明细字段的人工修正值记录为 diff,沉淀为抽取评测集与 few-shot 样本。
|
||||||
|
- [G5] 低分反馈自动归因:低分反馈触发归因 agent,拉 trace 诊断错误环节并生成改进任务。
|
||||||
|
- [G6] AI 智商看板:每周自动跑 golden set,输出准确率/召回率/误报率/人工干预率随时间的曲线。
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- [NG1] 本轮不做模型微调 / 自训练:只走 prompt 侧的 in-context learning + 规则学习。
|
||||||
|
- [NG2] 本轮不改变现有业务主链路(申请单、报销、审批)的接口契约。
|
||||||
|
- [NG3] 本轮不替换 Qdrant / LightRAG 底座,复用现有向量存储与 embedding 配置。
|
||||||
|
- [NG4] 政策新鲜度检测(外部政策变更 → 自动重生成规则)后续再评估,本轮只在评测门禁侧预留接口。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
- 目标用户:
|
||||||
|
1. 报销人 / 申请人:感知到系统越来越准,少打回、少补件。
|
||||||
|
2. 财务审批人:误报率下降,审批被打断的次数减少。
|
||||||
|
3. 算法/运营同学:能看到智商曲线、能灰度上线 prompt、能跑回归评测。
|
||||||
|
- 使用入口:
|
||||||
|
- 推理时自动注入 few-shot(对用户透明)。
|
||||||
|
- 后台 Canary 控制台(运营切流量、看分数)。
|
||||||
|
- AI 智商看板(周报 / 在线查询)。
|
||||||
|
- 核心场景:
|
||||||
|
1. 用户提交报销 → 系统预审 → 预审 prompt 自动注入相似历史误报样本 → 给出更准结论。
|
||||||
|
2. 算法同学改了 risk rule 生成 prompt → 发布前自动跑 golden set → 不达标被门禁拦下。
|
||||||
|
3. 用户给低分 → 归因 agent 诊断"是检索没召回 / 规则误判 / 回复格式问题"→ 自动建改进任务并回写样本池。
|
||||||
|
4. 审批人改了 OCR 抽错的金额 → diff 自动沉淀 → 下次同类票据抽取 prompt 多一条 few-shot。
|
||||||
|
- 异常场景:
|
||||||
|
- 样本池为空或检索失败 → 退化为无 few-shot 推理,不阻塞主链路。
|
||||||
|
- 评测门禁服务不可用 → 默认放行 stable,canary 自动暂停。
|
||||||
|
- Canary 候选 prompt 分数劣化 → 自动回滚到 stable。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- [C1] 输入能力:消费 accumulator 样本池、`AgentOperationFeedback`、附件修正 diff、trace 数据作为飞轮原料。
|
||||||
|
- [C2] 处理能力:样本检索(向量 + 元数据过滤)、评测打分(准确率/召回率/误报率/F1)、Canary 流量切分、低分归因。
|
||||||
|
- [C3] 输出能力:few-shot 注入后的 messages、评测报告、智商曲线、改进任务、归因结论。
|
||||||
|
- [C4] 状态与权限:样本带"人工已确认"标签才进可注入集合;prompt 版本有 stable/canary/pinned 状态;评测门禁可由运营关闭(审计可见)。
|
||||||
|
- [C5] 边界与降级:检索失败、评测失败、Canary 失败均降级到 stable,不阻塞业务推理。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 页面/组件:
|
||||||
|
- AI 智商看板(新页面,复用 `finance-report` 看板骨架):准确率/召回率/误报率/人工干预率随时间曲线 + golden set 覆盖度。
|
||||||
|
- Canary 控制台(并入 `SettingsView` / `PoliciesView`):列出各场景 prompt 版本、流量比例、当前分数、一键回滚。
|
||||||
|
- 交互状态:加载/空态(样本不足)/错误态(评测失败)/权限态(仅算法运营)。
|
||||||
|
- 展示规则:曲线按场景(差旅/报销/预算)分面;Canary 显示置信区间,差异不显著时标注。
|
||||||
|
- 降级处理:看板数据不可用时提示"数据生成中",不报错。
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- 接口/服务(新增,按职责拆分,单文件 ≤ 800 行):
|
||||||
|
- `services/few_shot_retrieval.py`:样本检索器,复用 Qdrant,输入 case 特征 → 输出 top-k 样本(带人工结论)。
|
||||||
|
- `services/prompt_registry.py`:prompt 版本注册中心,按场景 + 策略(latest/canary/pinned)取 prompt。
|
||||||
|
- `services/eval_harness.py`:在 golden set 上跑评测,输出指标;被发布门禁和智商看板共用。
|
||||||
|
- `services/feedback_attribution.py`:低分归因 agent,复用 `AgentTraceCenter` 数据。
|
||||||
|
- `services/extraction_correction_recorder.py`:记录 OCR 抽取字段的人工修正 diff。
|
||||||
|
- 改造点(在现有 prompt 构造文件加 inject 钩子,不改业务接口):
|
||||||
|
- `risk_rule_generation_prompt.py`、`user_agent_application.py`、`expense_claim_pre_review.py`、`document_intelligence_rules.py`、`ontology_extraction.py` 的 prompt 构造处。
|
||||||
|
- 权限与校验:Canary 控制台仅算法/运营角色;门禁关闭需审计日志。
|
||||||
|
- 持久化(新表,Alembic 迁移):
|
||||||
|
- `prompt_version`:id / scene / content / version / status(stable/canary/pinned) / eval_score / created_by / created_at。
|
||||||
|
- `golden_set`:id / scene / case_payload / expected / source(accumulator/manual) / confirmed / version。
|
||||||
|
- `extraction_correction`:id / attachment_id / field / raw_value / corrected_value / operator / created_at。
|
||||||
|
- `eval_run`:id / prompt_version_id / scene / metrics_json / started_at / finished_at。
|
||||||
|
- 降级处理:所有飞轮组件故障均降级到无 few-shot + stable prompt,主链路不阻塞。
|
||||||
|
|
||||||
|
### 算法与规则
|
||||||
|
|
||||||
|
- 输入:case 特征向量(场景标签 + 文本摘要 + 关键字段)、golden set、反馈样本、修正 diff。
|
||||||
|
- 流程:
|
||||||
|
1. 推理前:`few_shot_retrieval` 检索 top-k → 拼 system prompt。
|
||||||
|
2. 推理后:结果 + 反馈写入 accumulator。
|
||||||
|
3. 发布前:`eval_harness` 在 golden set 上跑分 → 门禁判定。
|
||||||
|
4. 低分触发:`feedback_attribution` 归因 → 改进任务回写样本池。
|
||||||
|
- 输出:few-shot 样本块、评测指标、归因结论、智商曲线数据点。
|
||||||
|
- 解释:few-shot 注入在 prompt 中保留"参考案例(历史已确认)"段落,可追溯;评测报告附错误 case 列表;归因输出错误环节标签 + 证据 trace 片段。
|
||||||
|
|
||||||
|
### 数据与契约
|
||||||
|
|
||||||
|
- 核心字段:scene、case_signature、few_shot_samples、metrics(acc/recall/fpr/f1)、prompt_version_id、status。
|
||||||
|
- 状态枚举:
|
||||||
|
- prompt: `stable` / `canary` / `pinned` / `archived`。
|
||||||
|
- golden case: `draft` / `confirmed` / `deprecated`。
|
||||||
|
- eval: `pass` / `fail` / `blocked`。
|
||||||
|
- 兼容策略:prompt_registry 找不到版本时回退到当前硬编码 prompt(保证向后兼容)。
|
||||||
|
- 版本/审计:每次 prompt / 规则 / golden set 变更记 `eval_run`,可回放历史。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
### few-shot 检索排序
|
||||||
|
|
||||||
|
```text
|
||||||
|
score(sample, case) = α * sim(emb(sample), emb(case)) + β * match(meta(sample), meta(case))
|
||||||
|
```
|
||||||
|
|
||||||
|
变量说明:
|
||||||
|
|
||||||
|
- score:样本与当前 case 的综合相似度。
|
||||||
|
- sim:余弦相似度,复用 Qdrant 现有 embedding。
|
||||||
|
- match:元数据硬匹配得分(场景同 / 域同 / 级别同),取 0 或 1。
|
||||||
|
- α、β:权重,默认 α=0.8、β=0.2,可在 prompt_registry 中按场景覆盖。
|
||||||
|
- 适用边界:仅取 `confirmed=true` 的样本;top-k 默认 k=3,按 token 预算动态裁剪。
|
||||||
|
|
||||||
|
### 评测指标
|
||||||
|
|
||||||
|
```text
|
||||||
|
precision = TP / (TP + FP)
|
||||||
|
recall = TP / (TP + FN)
|
||||||
|
f1 = 2 * precision * recall / (precision + recall)
|
||||||
|
```
|
||||||
|
|
||||||
|
变量说明:
|
||||||
|
|
||||||
|
- TP/FP/FN:在 golden set 上推理结论与 expected 比对得出(结论为风险标记/字段值/分类标签三类场景各有比对器)。
|
||||||
|
- 发布门禁默认阈值:recall ≥ 上一版 stable 的 recall 且 f1 不下降超过 2 个百分点,否则 `fail`。
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
后端:
|
||||||
|
|
||||||
|
- `few_shot_retrieval` 单测:样本池空 / 检索失败 / top-k 截断 / 仅取 confirmed 样本。
|
||||||
|
- `eval_harness` 单测:golden set 跑分指标正确性、门禁通过/拦截逻辑、空 golden set 降级。
|
||||||
|
- `prompt_registry` 单测:按策略取版本、回退到硬编码、Canary 流量切分比例。
|
||||||
|
- `feedback_attribution` 单测:mock trace 数据,归因标签正确性。
|
||||||
|
|
||||||
|
前端:
|
||||||
|
|
||||||
|
- AI 智商看板视图模型:空态、加载态、错误态、曲线渲染。
|
||||||
|
- Canary 控制台:列表、切流量、回滚交互。
|
||||||
|
|
||||||
|
集成:
|
||||||
|
|
||||||
|
- 端到端:构造一份 golden set → 改 prompt → 发布被门禁拦截 / 通过 → 智商看板出现新数据点。
|
||||||
|
- 容器内运行:`docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv local-x-financial-linux /tmp/x-financial-server-venv/bin/pytest -q server/tests/...`,超时 60s。
|
||||||
|
|
||||||
|
手工验证:
|
||||||
|
|
||||||
|
- 在 AI 工作台触发一次预审,确认 prompt 中出现 few-shot 块。
|
||||||
|
- 在 Canary 控制台发布一版劣化 prompt,确认被门禁拦下。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- [A1] 功能验收:推理时 prompt 中可见 few-shot 块,且样本来自 confirmed 池。
|
||||||
|
- [A2] 性能指标:few-shot 检索 ≤ 200ms(P95),不显著拖慢主链路;eval 单场景 ≤ 60s。
|
||||||
|
- [A3] 质量指标:golden set 覆盖至少 5 个核心场景;门禁能正确拦截劣化 prompt。
|
||||||
|
- [A4] 安全/权限指标:Canary 控制台仅算法/运营可操作;门禁关闭记审计日志。
|
||||||
|
- [A5] 可观测性:AI 智商看板按周生成曲线;每次 eval_run 可回放。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
- 风险:
|
||||||
|
- few-shot 注入增加 prompt 长度,可能触发 token 上限或拖慢推理 → 用 token 预算裁剪 + P95 监控兜底。
|
||||||
|
- 样本池噪音(错误标注)污染推理 → 只取 confirmed 样本 + 评测门禁把关。
|
||||||
|
- 评测 golden set 与线上分布漂移 → 季度复审 golden set,标注漂移度。
|
||||||
|
- 已处理依赖:复用 Qdrant / LightRAG / accumulator / AgentTraceCenter / risk_rule_generation 现有能力。
|
||||||
|
- 待确认:
|
||||||
|
- Canary 流量切分的具体比例(建议 90/10)需与业务确认。
|
||||||
|
- 智商看板放哪个一级菜单(Settings 还是独立"AI 运营"菜单)。
|
||||||
|
- 政策新鲜度检测是否本轮接入。
|
||||||
|
- 降级策略:任何飞轮组件故障 → 无 few-shot + stable prompt + 跳过门禁(仅 stable),保证业务连续。
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
- 2026-07-03:完成数据飞轮概念文档与开发 TODO 拆分,作为后续改造的总纲。
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# AI 数据飞轮 开发 TODO
|
||||||
|
|
||||||
|
更新时间:2026-07-03
|
||||||
|
|
||||||
|
文档路径:document/development/2026-07-03/feature/ai-data-flywheel/TODO.md
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力、方案或验收点。
|
||||||
|
- 只有完成并验证后,才能把 `[ ]` 改成 `[x]`。
|
||||||
|
- 勾选时在任务后补充简短证据,例如文件、接口、命令或验证结果。
|
||||||
|
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||||
|
- 实施顺序建议:阶段 1 → 2 → 3(飞轮 1+2 是地基)→ 4/5/6 并行 → 7。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 背景与问题] 盘点现有 accumulator / feedback / RAG / 规则生成能力,确认飞轮骨架已存在、断点在"检索注入 + 评测门禁"。
|
||||||
|
证据:`server/src/app/skills/domain/{false-positive-sample-accumulator,risk-feedback-sample-accumulator,risk-clue-collector}`、`services/agent_feedback.py`、`services/knowledge_rag_runtime.py`、`services/risk_rule_generation*.py`。
|
||||||
|
- [x] [CONCEPT: 目标与非目标] 确认本轮范围 = 飞轮 1-6(few-shot 注入 / golden set 门禁 / prompt 版本化 / 抽取修正回流 / 低分归因 / 智商看板),不做模型微调、不改业务接口契约。
|
||||||
|
证据:CONCEPT.md「目标与非目标」章节。
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 与业务确认 Canary 流量比例、智商看板菜单位置、政策新鲜度检测是否本轮接入。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 2. 契约与设计
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 功能能力] 定义 4 张新表的字段、状态枚举(prompt stable/canary/pinned/archived、golden draft/confirmed/deprecated、eval pass/fail/blocked、correction)。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 方案设计] 明确 5 个新 service 的职责边界与 inject 钩子点(risk_rule_generation_prompt / user_agent_application / expense_claim_pre_review / document_intelligence_rules / ontology_extraction)。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 算法与公式] 确认 few-shot 检索排序公式权重(默认 α=0.8 β=0.2)与门禁阈值(recall 不降、f1 下降 ≤ 2pp)。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 把验收点 A1-A5 转成可验证检查项,附命令与期望结果。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 3. 后端实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 后端] 新增 `services/few_shot_retrieval.py`:复用 Qdrant,按 case 特征检索 top-k confirmed 样本,带 token 预算裁剪。
|
||||||
|
证据:`server/src/app/services/few_shot_retrieval.py`;`server/src/app/services/few_shot_store.py`(独立 Qdrant collection `few_shot_samples`);`server/src/app/services/embedding_provider.py`(公共 EmbeddingProvider,复用 knowledge_rag_runtime 的 HTTP 调用)。
|
||||||
|
- [ ] [CONCEPT: 后端] 新增 `services/prompt_registry.py`:prompt 版本 CRUD + 策略取版(latest/canary/pinned)+ 回退硬编码。
|
||||||
|
证据:飞轮 3(prompt 版本化 + Canary)未启动,本轮只做飞轮 1。
|
||||||
|
- [ ] [CONCEPT: 后端] 新增 `services/eval_harness.py`:在 golden set 上跑评测,输出 precision/recall/f1,供门禁与看板共用。
|
||||||
|
证据:飞轮 2(golden set + 门禁)未启动,本轮只做飞轮 1。
|
||||||
|
- [ ] [CONCEPT: 后端] 新增 `services/feedback_attribution.py`:低分反馈触发,复用 AgentTraceCenter trace 做归因,输出错误环节标签 + 改进任务。
|
||||||
|
证据:飞轮 5(低分归因)未启动,本轮只做飞轮 1。
|
||||||
|
- [ ] [CONCEPT: 后端] 新增 `services/extraction_correction_recorder.py`:在附件/明细字段更新处记录 raw vs corrected diff。
|
||||||
|
证据:飞轮 4(抽取修正回流)未启动,本轮只做飞轮 1。
|
||||||
|
- [ ] [CONCEPT: 后端] Alembic 迁移:prompt_version / golden_set / extraction_correction / eval_run 四张表。
|
||||||
|
证据:本轮新增的是 FewShotSample 一张表(`server/src/app/models/few_shot_sample.py`),项目靠 `Base.metadata.create_all()` 建表(无 alembic versions/ 目录),已注册到 `db/base.py` 和 `models/__init__.py`。其余三表随对应飞轮再建。
|
||||||
|
- [x] [CONCEPT: 后端] 新增 `services/few_shot_ingestion.py`:RiskObservation confirmed/false_positive → FewShotSample + Qdrant 向量,在 `risk_observations.create_feedback` commit 后 hook 触发。
|
||||||
|
证据:`server/src/app/services/few_shot_ingestion.py`;`server/src/app/services/risk_observations.py:324-345`(`_maybe_ingest_few_shot` hook,带 feature flag + try/except 兜底)。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 在现有 prompt 构造文件加 few-shot 注入,不改业务接口。
|
||||||
|
证据:`server/src/app/services/risk_rule_generation_prompt.py`(新增 `few_shot_samples` 可选 kwarg,合并进 examples 字段);`server/src/app/services/risk_rule_generation.py:271-292`(`_retrieve_few_shot_samples` 在构造 messages 前调用,失败降级为空)。
|
||||||
|
|
||||||
|
## 4. 算法/规则实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 算法与规则] 实现few-shot 检索排序(向量相似度 + 元数据硬匹配),只取 confirmed 样本。
|
||||||
|
证据:`server/src/app/services/few_shot_store.py`(Qdrant 余弦相似度 + payload 过滤 scene/label/status);`few_shot_retrieval.py` 去重 + token 预算 + 单条字符上限裁剪。检索仅取 label ∈ {confirmed, false_positive}。
|
||||||
|
- [ ] [CONCEPT: 算法与规则] 实现评测指标比对器(风险标记 / 字段值 / 分类标签 三类场景)。
|
||||||
|
证据:飞轮 2,未启动。
|
||||||
|
- [ ] [CONCEPT: 算法与规则] 接入发布门禁:`agent_asset_risk_rule_publish` 前调 eval_harness,不达标 block。
|
||||||
|
证据:飞轮 2,未启动。
|
||||||
|
- [ ] [CONCEPT: 算法与规则] 接入 Canary 流量切分(默认 90 stable / 10 canary)+ 劣化自动回滚。
|
||||||
|
证据:飞轮 3,未启动。
|
||||||
|
- [x] [CONCEPT: 结果解释] few-shot 块在 prompt 中保留 `source: "historical_confirmed"` 标记,可追溯。
|
||||||
|
证据:`risk_rule_generation_prompt.py` 合并 examples 时每条历史样本带 `source`/`label`/`conclusion` 字段。
|
||||||
|
|
||||||
|
## 5. 前端实现
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 前端] AI 智商看板新页面:准确率/召回率/误报率/人工干预率随时间曲线 + golden set 覆盖度,复用 `finance-report` 看板骨架。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 前端] Canary 控制台(并入 Settings/Policies):prompt 版本列表、流量比例、分数、一键回滚。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 前端] 实现加载/空态(样本不足)/错误态(评测失败)/权限态(仅算法运营)。
|
||||||
|
证据:
|
||||||
|
- [ ] [CONCEPT: 前端] 对齐现有企业后台风格(参考 `chat-ui-saas-styling` / `theme-settings-enterprise-ai-style`),避免营销页观感。
|
||||||
|
证据:
|
||||||
|
|
||||||
|
## 6. 测试与验证
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 测试方案] 后端单测:embedding_provider(GLM/Ollama 分支、维度缓存、HTTP 错误降级)、few_shot_ingestion(confirmed/false_positive 入库、ignored 跳过、幂等去重、hook 触发、feature flag、吞异常)、few_shot_retrieval(去重、token 预算、超长截断)+ prompt 注入(合并 examples、向后兼容)。
|
||||||
|
证据:`server/tests/test_embedding_provider.py`、`server/tests/test_few_shot_ingestion.py`、`server/tests/test_few_shot_retrieval_and_prompt.py`,容器内 `pytest -q` 20 passed。
|
||||||
|
- [ ] [CONCEPT: 测试方案] 前端:智商看板与 Canary 控制台视图模型 + 构建验证。
|
||||||
|
证据:飞轮 3/6 前端,未启动。
|
||||||
|
- [ ] [CONCEPT: 测试方案] 集成:golden set → 改 prompt → 门禁拦截/通过 → 看板新增数据点,容器内跑通。
|
||||||
|
证据:飞轮 2 集成,未启动。
|
||||||
|
- [x] [CONCEPT: 测试方案] 回归:现有 RAG / risk_observations / risk_rule_generation 测试全过。
|
||||||
|
证据:容器内 `pytest -q server/tests/test_risk_observations_service.py server/tests/test_knowledge_rag_runtime.py server/tests/test_risk_rule_generation.py server/tests/test_risk_rule_generation_failure.py` → 35 passed,EmbeddingProvider 抽离零回归。
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 记录验证命令与结果,确认 P95 检索 ≤ 200ms、单场景评测 ≤ 60s。
|
||||||
|
证据:性能指标待飞轮 2 评测上线后连同 golden set 一起量。
|
||||||
|
|
||||||
|
## 7. 文档收尾
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 指标与验收] 飞轮 1(few-shot 注入)A1 功能验收已达成:推理时 prompt 中可见带 `source: "historical_confirmed"` 的 few-shot 块,且样本来自 confirmed/false_positive 池。A5 可观测性部分达成(可追溯 source)。A2/A3/A4 随飞轮 2/3 补齐。
|
||||||
|
证据:见阶段 3/4/6 已勾选项。
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 更新 Canary 比例、看板菜单位置、政策新鲜度检测的最终结论与剩余风险。
|
||||||
|
证据:飞轮 2-6 启动后再定稿。
|
||||||
|
- [x] [CONCEPT: 功能一句话] 确认飞轮 1 实现没有偏离"让系统越用越聪明"的原始目标。
|
||||||
|
证据:人工确认风险观测 → 自动入库 + 向量化 → 下次规则编译时检索注入相似历史样本,形成"用得越多 → 样本越丰富 → 推理越准"的闭环。飞轮 2-6 待后续迭代。
|
||||||
17
document/development/2026-07-08/work-logs.med
Normal file
17
document/development/2026-07-08/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 2026-07-08 综合工作日志
|
||||||
|
|
||||||
|
生成时间:2026-07-08 17:01:43 CST
|
||||||
|
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||||
|
|
||||||
|
## 今日功能点
|
||||||
|
|
||||||
|
- 今日未发现功能点文档。
|
||||||
|
|
||||||
|
## 今日 Bugs
|
||||||
|
|
||||||
|
- 今日未发现 bug 修复记录。
|
||||||
|
|
||||||
|
## 综合分析
|
||||||
|
|
||||||
|
- 今日目录下暂无功能点或 bug 记录。
|
||||||
|
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||||
17
document/development/2026-07-09/work-logs.med
Normal file
17
document/development/2026-07-09/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 2026-07-09 综合工作日志
|
||||||
|
|
||||||
|
生成时间:2026-07-09 17:01:25 CST
|
||||||
|
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||||
|
|
||||||
|
## 今日功能点
|
||||||
|
|
||||||
|
- 今日未发现功能点文档。
|
||||||
|
|
||||||
|
## 今日 Bugs
|
||||||
|
|
||||||
|
- 今日未发现 bug 修复记录。
|
||||||
|
|
||||||
|
## 综合分析
|
||||||
|
|
||||||
|
- 今日目录下暂无功能点或 bug 记录。
|
||||||
|
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||||
17
document/development/2026-07-10/work-logs.med
Normal file
17
document/development/2026-07-10/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 2026-07-10 综合工作日志
|
||||||
|
|
||||||
|
生成时间:2026-07-10 17:00:31 CST
|
||||||
|
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||||
|
|
||||||
|
## 今日功能点
|
||||||
|
|
||||||
|
- 今日未发现功能点文档。
|
||||||
|
|
||||||
|
## 今日 Bugs
|
||||||
|
|
||||||
|
- 今日未发现 bug 修复记录。
|
||||||
|
|
||||||
|
## 综合分析
|
||||||
|
|
||||||
|
- 今日目录下暂无功能点或 bug 记录。
|
||||||
|
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||||
17
document/development/2026-07-11/work-logs.med
Normal file
17
document/development/2026-07-11/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 2026-07-11 综合工作日志
|
||||||
|
|
||||||
|
生成时间:2026-07-11 17:09:02 CST
|
||||||
|
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||||
|
|
||||||
|
## 今日功能点
|
||||||
|
|
||||||
|
- 今日未发现功能点文档。
|
||||||
|
|
||||||
|
## 今日 Bugs
|
||||||
|
|
||||||
|
- 今日未发现 bug 修复记录。
|
||||||
|
|
||||||
|
## 综合分析
|
||||||
|
|
||||||
|
- 今日目录下暂无功能点或 bug 记录。
|
||||||
|
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||||
17
document/development/2026-07-12/work-logs.med
Normal file
17
document/development/2026-07-12/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 2026-07-12 综合工作日志
|
||||||
|
|
||||||
|
生成时间:2026-07-12 17:11:32 CST
|
||||||
|
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||||
|
|
||||||
|
## 今日功能点
|
||||||
|
|
||||||
|
- 今日未发现功能点文档。
|
||||||
|
|
||||||
|
## 今日 Bugs
|
||||||
|
|
||||||
|
- 今日未发现 bug 修复记录。
|
||||||
|
|
||||||
|
## 综合分析
|
||||||
|
|
||||||
|
- 今日目录下暂无功能点或 bug 记录。
|
||||||
|
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 15:33:记录 bug 修复:AI 新建费用申请直接提交绕过统一事务。
|
||||||
|
- Git 提交检查:`git fetch --all --prune` 后未发现 upstream 新提交;本地 ahead 2 个既有提交,分别为 `653eda05 feat(auth): add opaque bearer sessions`(不透明 Bearer 会话与认证收口)和 `661990b2 feat(expenses): add transactional expense case events`(Expense Case 与事务业务事件基础)。
|
||||||
|
- 修改:`user_agent_application.py` 将 AI 新建并直接提交改为先创建草稿,再统一调用 `ExpenseClaimService.submit_claim`,删除入口内手写提交状态和平台风险评估;提交异常或未找到单据时主动回滚,避免外层工具日志提交失败草稿。
|
||||||
|
- 修改:`budget.py` 的预算就绪检查改为复用当前 Session 连接执行 metadata 检查,避免 Engine 连接隐式提交已经 `flush` 的申请和预算数据;`test_reimbursement_endpoints.py`、`test_user_agent_service.py` 补齐成功提交、事件失败整体回滚、保存草稿无副作用和必填部门上下文回归。
|
||||||
|
- 操作:所有后端验证均在 `x-financial-local-linux` 容器内执行,单条命令使用 `timeout 60s`;没有修改数据库结构,没有对持久化开发数据库执行迁移,也没有重启服务。
|
||||||
|
- 验证:AI 直接提交/失败回滚/保存草稿 3 项、申请提交主链路 5 项、预算与 Expense Case 13 项,共 21 项通过;相关 Python 文件 `ruff --select F,I` 通过。整份 `test_reimbursement_endpoints.py` 运行结果为 13 项通过、3 项未通过,失败分别位于既有附件风险等级断言、申请审批路由断言和中文测试请求头编码,不属于本次 AI 提交事务断言。
|
||||||
|
- 影响:AI 一键新建申请现在与编辑重提共用同一提交语义,预算预占、提交校验、申请风险标记、Expense Case 事件和审批状态保持一致;业务事件写入失败时,申请、预算额度、预算流水、预算预占和 Case 关联不会残留部分成功数据。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 22:59:记录 bug 修复:AI 工作台重复保存请求会创建多张申请草稿和多条事件。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`,未发现 upstream 新提交;本地 ahead 4 个既有提交,分别为 `22669a90 feat(expenses): show unified expense event timeline`(真实费用事件时间线)、`a616b30c fix(expenses): unify AI application submission transaction`(AI 申请提交事务)、`653eda05 feat(auth): add opaque bearer sessions`(不透明 Bearer 会话)和 `661990b2 feat(expenses): add transactional expense case events`(Expense Case 事务事件基础)。
|
||||||
|
- 修改:新增 `expense_application_draft_events.py`,以租户、操作人、run ID 和稳定草稿快照生成固定长度幂等键及 UUID5 聚合 ID;`user_agent_application.py` 在新建前复用已完成动作,并在并发主键竞争后回滚、重查已提交事件。持久化兜底的当前时间不参与指纹,原始申请时间仍通过申请详情快照区分。
|
||||||
|
- 操作:先在容器中分别复现“带日期相同请求”和“缺日期半成品相同请求”均生成两个不同 `claim_id`,再补动作级幂等和数据库并发仲裁路径;业务单号继续使用既有随机格式,没有修改数据库结构或执行迁移。
|
||||||
|
- 验证:相同 HTTP 保存请求连续执行两次返回同一 `claim_id` 和 `claim_no`,数据库只有一张申请草稿与一条 `claim_draft_created`;同一 run 的不同快照仍分别留痕,事件失败时草稿、Case、Link 和事件整体回滚。容器内受影响后端定向回归 36 项、前端时间线兼容测试 9 项及 Python `ruff --select F,I` 通过。
|
||||||
|
- 影响:网络重试、用户重复点击或客户端重放不会制造重复申请草稿;并发请求由稳定聚合主键仲裁。真实 PostgreSQL 双会话并发集成测试仍是后续可补的非阻断验证,不影响当前顺序重放与事务契约。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 22:59:记录 bug 修复:AI 申请预览入口可保留请求体伪造的身份和管理员权限。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`,未发现 upstream 新提交;本地 ahead 4 个既有提交,分别为 `22669a90 feat(expenses): show unified expense event timeline`(真实费用事件时间线)、`a616b30c fix(expenses): unify AI application submission transaction`(AI 申请提交事务)、`653eda05 feat(auth): add opaque bearer sessions`(不透明 Bearer 会话)和 `661990b2 feat(expenses): add transactional expense case events`(Expense Case 事务事件基础)。
|
||||||
|
- 修改:`reimbursements.py` 将 `user_id`、租户、角色、管理员标记、用户名、姓名、部门、职位、职级、员工编号和直属经理全部强制绑定到服务端当前会话,不再对请求体同名字段使用 `setdefault`;`test_reimbursement_endpoints.py` 增加伪造管理员与他人身份编辑退回申请的对抗用例。
|
||||||
|
- 操作:先在容器中复现修复前接口返回 200 且允许修改他人申请,再完成服务端身份覆盖;没有修改数据库结构,没有执行迁移或重启服务。
|
||||||
|
- 验证:修复后恶意请求返回 400,目标申请的事由、状态和审批节点保持不变,也没有新增费用事件;正常保存草稿与直接提交回归通过。本轮受影响后端定向回归共 36 项、前端时间线兼容测试 9 项和 Python `ruff --select F,I` 均在容器内通过。
|
||||||
|
- 影响:AI 工作台快速申请入口不能再通过伪造 `user_id`、`is_admin`、角色或员工编号绕过申请所有权检查,授权事实与其他受保护接口统一来自服务端会话。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 14:43:记录 bug 修复:客户端身份头可伪造且已初始化系统仍存在匿名重配置入口。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;未发现 upstream 新提交,本地 ahead 1 个提交 `661990b2 feat(expenses): add transactional expense case events`,它是本任务上一切片的费用事件事务提交,当前认证改造继续建立在该提交之上。
|
||||||
|
- 修改:新增 `AuthSession` 不透明 Bearer 会话和摘要存储,后端从服务端会话及员工表解析身份/角色;移除生产 `X-Auth-*` 授权来源,保护 Bootstrap、Settings、模型连通性、缓存、员工、分析、Agent、风险观测、审计及日志接口;前端统一 Bearer、`sessionStorage`、集中 `401` 和原子登出;Vite Setup 桥在初始化完成后锁定重配置。
|
||||||
|
- 操作:新增 `20260713_0002_auth_sessions.py` 并只生成 upgrade/downgrade 离线 SQL,没有对持久化数据库执行迁移或重启;领域回归测试使用仅存在于测试目录的身份依赖覆盖,生产代码没有兼容伪造头。测试过程中发现规则初始化用例会重写 5 份 Excel 工件,已停止继续运行该类测试并保持这些文件不进入本次提交。
|
||||||
|
- 验证:容器 `x-financial-local-linux` 内认证/Bootstrap/费用事件/OpenAPI 21 项通过,受保护端点 13 项通过,全量收集 805 项成功;前端 17 项通过,生产构建成功;认证安全核心文件 Ruff 通过,`git diff --check` 通过,生产源码未检出 `X-Auth-*`;Alembic 离线升级包含 `CREATE TABLE auth_sessions`,离线降级包含 `DROP TABLE auth_sessions`。
|
||||||
|
- 影响:用户登录后必须使用服务端签发的短期 Bearer 会话,伪造用户名、角色或管理员头不再生效;业务经理不能冒充平台管理员,已初始化系统的基础设施信息与重配置入口不再向匿名请求开放。租户级查询守卫、会话清理/全部退出、登录限流和 SSO 仍按 P0 后续任务推进。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 11:57:记录 bug 修复:关联申请归档/解绑时嵌套审计提前提交业务事务。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`、upstream 与 local-ahead 检查,未发现 upstream 新提交或本地 ahead 新提交;工作区原有规则表和历史开发文档改动已保留。
|
||||||
|
- 修改:`AuditLogService` 与 `AuditLogRepository` 增加由调用方控制的提交方式;`ExpenseClaimApplicationHandoffMixin` 的关联申请归档和解绑审计改为 `flush`,由付款/删除外层事务统一提交。
|
||||||
|
- 操作:新增费用事件事务回滚测试;在付款状态与结构化 Outbox 事件之间制造失败,确认付款、申请归档和嵌套审计能够整体回滚。
|
||||||
|
- 验证:容器 `x-financial-local-linux` 内新增及核心费用流程共 29 项测试通过,静态检查、OpenAPI 校验、启动脚本语法和 Alembic upgrade/downgrade 离线 SQL 验证通过。
|
||||||
|
- 影响:付款或关联解绑中途失败时,不再留下“申请已归档但付款/事件未提交”的半完成状态,为事务 Outbox 提供可靠边界。
|
||||||
@@ -0,0 +1,628 @@
|
|||||||
|
# AI 费用闭环与价值证明 概念文档
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
文档路径:document/development/2026-07-13/feature/ai-expense-closed-loop-and-value-proof/CONCEPT.md
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
把申请、消费、票据、报销、审批、付款、入账、分析和持续学习连接成同一个费用事件,让员工少填表、审批人只处理例外、财务能确认真实节省,并让 AI 在可控边界内越用越准确。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
- 当前现状:项目已经具备费用申请、AI 建单、票据夹、OCR、预算检查、风险预审、动态审批、员工画像、财务看板、知识库、Agent trace、反馈表和 few-shot 样本等较完整的功能骨架。容器运行时 OpenAPI 已覆盖 156 个业务操作,功能广度已经足够。
|
||||||
|
- 用户痛点:申请、票据、报销、审批和分析仍以多个页面、多个服务和多套状态存在。用户需要在不同入口之间理解系统,而不是由系统主动接住费用事件;移动端主流程仍有 mock;审批通过后的真实付款、回执、ERP 凭证和对账没有形成完整闭环。
|
||||||
|
- AI 痛点:当前系统能够记录会话、运行轨迹、风险反馈和员工画像,但没有统一记录“AI 建议了什么、用户改了什么、后来是否退回、最终是否付款或产生节省”。AI 能看到历史,却不能稳定把业务结果转化为下一次的个性化、自动化和风险校准。
|
||||||
|
- 企业痛点:现有看板主要回答花了多少、预算用了多少、发现了多少风险,尚不能可信回答省了多少钱、为什么省、哪些建议被执行、哪些费用仍可优化。
|
||||||
|
- 工程问题:旧 `ReimbursementRequest` 与当前 `ExpenseClaim` 能力重叠,申请与报销又复用部分字段和 JSON 标记;审批、预算、付款、归档和关系引用部分混入 `risk_flags_json`,不利于事务一致性、事件追溯和持续演进。
|
||||||
|
- 商业影响:如果不能持续证明处理效率、风险改善和真实节省,产品只能按“报销工具”定价;如果能形成价值闭环,则可以扩展为智能风控、预算经营、价值洞察和企业集成平台。
|
||||||
|
- 为什么现在需要做:现有模块已经足够支撑一条完整费用闭环,下一阶段继续横向新增页面会扩大编排和数据割裂。应先收口费用领域、业务事件、AI 决策、学习记忆和节省归因,再逐步开放自动化。
|
||||||
|
|
||||||
|
相关既有方案:
|
||||||
|
|
||||||
|
- `document/development/2026-07-03/feature/ai-data-flywheel/CONCEPT.md`:作为本功能的 AI 样本回流、评测门禁、Prompt 版本和 Canary 子能力,不重复建设。
|
||||||
|
- `document/development/AI意图规划器/UNIFIED_GATE_PIPELINE.md`:作为 AI 场景识别和后端唯一编排入口的架构前置,不在前端继续增加影子门控。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- [G1] 建立统一 `Expense Case`,把一次费用从需求表达、申请、票据、报销、审批、付款、入账到归档视为同一业务事件。
|
||||||
|
- [G2] 建立零录入报销体验,自动匹配申请、预算、票据、费用类型、项目、成本中心和历史偏好,让用户主要核对异常项。
|
||||||
|
- [G3] 建立按动作授权的安全自动化等级,从解释、建议、预填、可逆自动化逐步升级到低风险直通。
|
||||||
|
- [G4] 建立“AI 决策 → 用户反馈 → 工作流结果 → 分层记忆 → 再决策”的学习闭环。
|
||||||
|
- [G5] 建立节省机会、执行任务、实际结果和财务确认组成的 Savings Ledger,区分风险暴露、预计节省和已实现节省。
|
||||||
|
- [G6] 建立从描述性统计到异常归因、预算预测、供应商分析、政策模拟和节省任务的费用经营分析能力。
|
||||||
|
- [G7] 建立企业租户、用量计量、模型成本和客户价值指标,为年度订阅、用量超额和增值模块提供可审计基础。
|
||||||
|
- [G8] 保持高风险动作、资金支付、制度发布和敏感主数据变更的人为授权、审计和回滚能力。
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- [NG1] 本轮不自建商旅、企业卡、银行或支付供应链网络,只定义标准连接器和业务事件契约。
|
||||||
|
- [NG2] 本轮不一次性替换所有现有报销接口;先建立统一费用事件和旁路事件账本,再分阶段迁移旧模型。
|
||||||
|
- [NG3] 本轮不做模型微调或无监督自训练,优先使用结构化反馈、few-shot、规则学习、Prompt 版本和回归门禁。
|
||||||
|
- [NG4] 本轮不允许 AI 无人值守执行资金支付、高风险批准/驳回、制度发布和收款账户变更。
|
||||||
|
- [NG5] 本轮不同时覆盖所有费用场景,首个试点只选择一个高频、可测量、可闭环的场景。
|
||||||
|
- [NG6] 本轮不做未经授权的跨租户学习和企业间明细 Benchmark;后续只允许严格聚合、隐私保护并获得授权的对标能力。
|
||||||
|
- [NG7] 本轮不把风险关联单据总额、暂缓付款金额或未采纳建议直接计为企业节省。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
### 目标用户
|
||||||
|
|
||||||
|
1. 报销人/申请人:少填字段、少整理票据、少补件、少追问进度。
|
||||||
|
2. 审批人:只处理必要性、例外和高风险问题,快速获得证据与建议。
|
||||||
|
3. 财务运营:减少重复审核、退回、对账和月末整理,集中处理异常。
|
||||||
|
4. CFO/管理层:了解费用增长原因、预算趋势、节省机会和实际 ROI。
|
||||||
|
5. 风控/审计:查看规则依据、风险证据、算法版本、人工覆盖和审计结果。
|
||||||
|
6. 系统管理员/IT:配置租户、组织、权限、连接器、数据保留、模型成本和自动化上限。
|
||||||
|
|
||||||
|
### 使用入口
|
||||||
|
|
||||||
|
- AI 工作台自然语言入口。
|
||||||
|
- 统一费用事件详情页。
|
||||||
|
- 移动端拍票、报销和审批入口。
|
||||||
|
- 票据夹和自动票据收件箱。
|
||||||
|
- 审批例外工作台。
|
||||||
|
- CFO 费用价值看板。
|
||||||
|
- AI 记忆与自动化策略设置。
|
||||||
|
|
||||||
|
### 核心场景
|
||||||
|
|
||||||
|
1. 员工说“下周去上海出差三天”,系统自动生成合规申请、预算影响和可选节省方案,用户核对后提交。
|
||||||
|
2. 消费期间,邮箱发票、拍照票据、企业卡或商旅订单进入统一票据收件箱,系统按时间、金额、地点和商户自动归集到费用事件。
|
||||||
|
3. 行程结束后,系统自动生成报销草稿,只要求用户处理缺票、归属或异常金额。
|
||||||
|
4. 提交前,系统按事实、规则、证据和风险等级给出绿色通过、黄色修正、红色复核结果。
|
||||||
|
5. 审批人看到预算影响、历史相似单、政策依据、风险证据和建议意见,低风险单据进入快速通道。
|
||||||
|
6. 财务完成付款、回执、ERP 凭证和对账,所有结果回写费用事件并形成审计链。
|
||||||
|
7. 月末系统识别预算超支、供应商价格漂移、重复采购、异常路线和流程瓶颈,生成有负责人和目标金额的节省任务。
|
||||||
|
8. 用户修正字段、审批人覆盖建议、财务确认误报或节省结果后,系统更新用户、部门和企业记忆,下次减少重复操作。
|
||||||
|
|
||||||
|
### 异常场景
|
||||||
|
|
||||||
|
- OCR、模型、向量检索或外部连接器不可用时,回退到人工录入或稳定规则,不阻塞草稿保存和主链路。
|
||||||
|
- 票据、申请或费用事件匹配置信度不足时,只展示候选,不自动绑定。
|
||||||
|
- 企业规则与个人偏好冲突时,企业规则优先,必须解释冲突原因。
|
||||||
|
- 高风险、超金额阈值、敏感账户变更或证据不足时,强制人工确认。
|
||||||
|
- 自动化动作失败时必须幂等、可重试、可撤销,并保留错误状态和操作证据。
|
||||||
|
- 预计节省没有财务确认或实际结果时,只能标记为机会,不得计入客户 ROI。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- [C1] 费用事件能力:统一表达申请、消费、票据、报销、审批、付款、入账和归档关系。
|
||||||
|
- [C2] 零录入能力:自动抽取并匹配申请、预算、票据、费用类型、项目、成本中心、参与人和历史偏好。
|
||||||
|
- [C3] 预审与修复能力:输出事实、规则、证据、判断和建议动作,并提供一键补件、修正和解释入口。
|
||||||
|
- [C4] 审批例外能力:风险分级、证据摘要、预算影响、推荐路由、意见草稿、批量处理、委托、转交、加签和超时升级。
|
||||||
|
- [C5] 支付入账能力:以连接器方式接收付款批次、支付回执、ERP 凭证、银行流水和对账结果。
|
||||||
|
- [C6] 学习记忆能力:记录 AI 建议、用户修改、审批覆盖、退回、付款和审计结果,形成用户、部门、企业三级记忆。
|
||||||
|
- [C7] 自动化策略能力:按动作、金额、场景、风险、置信度、证据、可逆性和抽检率配置自动化等级。
|
||||||
|
- [C8] 节省价值能力:把节省机会、建议动作、负责人、目标时间、预计节省、实际节省和财务确认形成闭环。
|
||||||
|
- [C9] 费用经营能力:费用结构、预算预测、异常归因、供应商分析、政策模拟、流程成本和客户 ROI。
|
||||||
|
- [C10] 商业计量能力:企业租户、套餐、配额、用量、模型/OCR 成本、增值模块、客户贡献毛利和价值证明。
|
||||||
|
- [C11] 状态与权限:服务端认证、租户隔离、角色权限、动作授权、审计日志和敏感数据保留策略。
|
||||||
|
- [C12] 边界与降级:外部依赖失败时主链路可用;高风险动作始终保留人工控制;所有学习与自动化可关闭、遗忘或回滚。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
#### 统一费用事件
|
||||||
|
|
||||||
|
- 新增或重构费用事件详情容器,不再让用户理解申请单、票据夹、报销草稿、审批单和付款状态之间的内部关系。
|
||||||
|
- 页面按“计划 → 消费 → 报销 → 审批 → 付款/入账 → 复盘”展示时间线和当前待办。
|
||||||
|
- 每个 AI 填充字段显示来源、置信度和修改入口;低置信度字段集中进入“需要确认”区。
|
||||||
|
- 退回、补件和断点续办直接回到对应问题,不要求用户重新开始对话。
|
||||||
|
|
||||||
|
#### 零录入报销首个切片
|
||||||
|
|
||||||
|
- 用户在小财管家上传已完成 OCR 的票据并发送后,附件关联任务必须以当前租户、当前员工和 `Expense Case` 为边界,优先寻找已审批申请自动生成的可编辑报销草稿。
|
||||||
|
- 匹配信号首期使用费用事件关系、申请状态、票据日期、行程城市、费用场景和草稿状态;返回结构化分数、置信度和命中原因,不把模型自由文本作为授权依据。
|
||||||
|
- 只有唯一高置信候选允许自动归集。低置信、多个接近候选、申请缺少系统生成草稿、票据已属于其他单据或证据不足时,任务以 `requires_confirmation=true` 成功返回候选和异常,不修改任何报销单或票据关系。
|
||||||
|
- 自动归集成功后直接返回费用事件、申请、报销草稿、归集数量、跳过数量、风险项、缺失项和可解释原因;前端只展示完成结果或“需要确认”异常,不要求用户再次上传同一票据。
|
||||||
|
- 自动归集是 L3 可逆动作,只允许写入草稿、票据关系和业务事件;不自动提交、审批、付款或重建历史遗留申请缺失的草稿。
|
||||||
|
- 相同租户、用户、票据和目标草稿重试必须幂等;已归集到同一草稿的票据计入跳过数,不重复创建费用明细、附件或业务事件。
|
||||||
|
|
||||||
|
#### 移动端
|
||||||
|
|
||||||
|
- 把拍照、相册选票、报销列表、审批列表和 AI 助手接入真实后端。
|
||||||
|
- 支持离线拍票、失败重试和后台上传状态。
|
||||||
|
- 未实现能力必须隐藏或明确标为不可用,不保留无响应的主按钮。
|
||||||
|
|
||||||
|
#### 审批例外工作台
|
||||||
|
|
||||||
|
- 以风险、金额、预算影响、等待时长和证据完整度排序。
|
||||||
|
- 支持批量处理低风险事项,行内保留真实按钮和键盘可访问入口。
|
||||||
|
- 高风险审批展示模型版本、规则版本、政策依据、证据和人工覆盖原因。
|
||||||
|
- 正式审批任务切片由 `/api/v1/approval-tasks` 返回租户安全的数据库分页投影,优先级由风险、SLA、金额和证据完整度共同计算;任务保存节点进入时间、处理人、乐观版本、批量资格和权威动作集合。旧 `/approval-workbench/items` 仍可承担 advisory-only 摘要,但不再作为“待我审核”的业务动作事实源。
|
||||||
|
- 批准、退回和付款均使用动作协议:客户端在确认时冻结 `request_id`、预期状态和预期审批节点;服务端以租户 + 操作人 + 请求 ID 唯一账本、请求指纹、PostgreSQL advisory lock、Claim 行锁和同事务业务事件确保相同请求可安全重放,不同内容或陈旧快照以 409 拒绝。
|
||||||
|
- 高风险门禁同时读取持久化风险观察和尚未落表的原始风险标记;未处置的严重/高危可行动风险在路由、预算和状态修改前阻断,原始重大风险持久化失败时 fail-closed。仅明确标记为 `route_review` 的路由型风险进入对应审批节点而不冒充已解决。
|
||||||
|
- 门禁只消费与当前单据业务阶段一致的显式风险:申请阶段不会被明确标记为 `reimbursement` 的风险阻断,反之亦然;缺少阶段的未知重大风险仍保守处理。`route_review` 是显式路由语义,审批路由可以继续但风险仍保留在证据和后续复核中。
|
||||||
|
- 风险处置采用判定与生命周期双状态:支持确认风险、误报、补件、开始整改、申请豁免、财务/管理层批准或拒绝豁免,以及完成处置;每个动作绑定请求指纹、预期版本、操作人和只追加事件。申请人不得自批/自拒,仅在职 finance/executive 且非申请人可决定,过期申请 fail-closed。完整证据只对管理员或当前审批人开放,风险池对财务/管理/预算角色开放。
|
||||||
|
- 并发锁顺序固定为 Claim → RiskObservation → RiskDisposition,审批动作、人工处置和 Hermes 扫描共用 Claim 锁。扫描在锁外计算、锁内校验状态与更新时间,过期快照丢弃并等待下一轮,避免旧图结果覆盖刚完成的人工处置。
|
||||||
|
- 所有单据动作首次完整 API 响应写入动作账本;风险处置首次完整响应在 append-only 事件 INSERT 前原子写入。相同请求重放只返回首次版本,风险旧事件无快照时也只从目标版本及以前的审计链重建,不读取当前 Claim、当前处置投影或后续事件。
|
||||||
|
- 审批任务支持低风险批量通过、委托/撤销委托、永久转交、顺序加签、并行会签和手动/调度 SLA 升级;SLA 以当前节点进入时间为基准。前端“待我审核”完全消费服务端分页、筛选、`available_actions` 与版本,筛选和页码可从详情返回恢复。尚未完成的主要边界是统一必要性/政策证据摘要、真实企微/钉钉/邮件触达及外部结果回写。
|
||||||
|
|
||||||
|
#### CFO 价值看板
|
||||||
|
|
||||||
|
- 首页优先展示财务确认现金节省、已核验工时价值、安全智能直通率和重大风险护栏。
|
||||||
|
- 支持按部门、项目、费用类型、供应商、城市、时间和费用事件下钻。
|
||||||
|
- 每项节省可打开来源、基线、建议、执行、确认和去重证据。
|
||||||
|
|
||||||
|
#### AI 记忆与自动化设置
|
||||||
|
|
||||||
|
- 用户可以查看“系统记住了什么、为什么记住、在哪里使用”,并支持修改、忘记和关闭个性化。
|
||||||
|
- 企业管理员配置部门/企业记忆边界、有效期、敏感等级和自动化上限。
|
||||||
|
- 首个个人记忆切片只在申请核对表展示“常用出行方式”来源、证据数量和可编辑状态,并提供“忘记此偏好”;不在本轮新建大型设置页,也不把风险画像当作个人偏好。
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
#### 费用领域与编排
|
||||||
|
|
||||||
|
- 新增 `ExpenseCaseService` 作为跨阶段编排入口,避免继续扩大万能 `ExpenseClaimService`。
|
||||||
|
- `ExpenseCaseService` 只负责阶段协调,申请、票据、报销、审批、支付、入账、记忆和节省由独立协作者负责。
|
||||||
|
- 复用统一 AI 场景注册与 LangGraph 编排,前端只按后端返回的 plan/action 渲染,不再增加业务门控。
|
||||||
|
- 零录入票据归集由独立匹配器计算候选和证据,由独立关联编排器执行可逆写入;后台 Job 只负责身份绑定、状态保存和结果投影,不继续堆评分、附件和事件逻辑。
|
||||||
|
- 票据存储命名空间必须至少包含租户与用户;后台任务查看和执行同时校验租户及用户,平台管理员也不能跨租户读取任务结果。
|
||||||
|
|
||||||
|
#### 业务事件与 AI 决策
|
||||||
|
|
||||||
|
- 所有关键动作写入 append-only `business_events`,使用 `correlation_id` 串联同一费用事件、Agent run、审批和外部连接器事件。
|
||||||
|
- 业务状态变更与对应 Outbox 事件必须在同一数据库事务中持久化;消费者按事件 ID 幂等处理,避免审计、学习和 ROI 数据因异步失败永久丢失。
|
||||||
|
- 只有画像刷新、分析聚合和消息通知等可重建派生任务允许异步失败并重试,不得把申请、提交、退回、审批、支付和入账事件降级为可丢弃旁路日志。
|
||||||
|
- 每个 AI 输出写入 `ai_decisions`,记录建议值、置信度、证据、模型、Prompt、规则、政策版本、风险等级和自动化模式。
|
||||||
|
- 用户接受、修改、拒绝或忽略写入 `ai_decision_feedback`;审批、退回、付款和审计写入 `workflow_outcomes`。
|
||||||
|
|
||||||
|
#### 记忆与学习
|
||||||
|
|
||||||
|
- `memory_entries` 支持用户、部门、企业作用域,以及 candidate/active/suppressed 状态。
|
||||||
|
- `memory_evidence_links` 保留记忆与业务事件、决策、反馈、结果的来源关系。
|
||||||
|
- 企业制度优先于部门基线,部门基线优先于个人偏好;冲突时返回解释。
|
||||||
|
- 复用既有 AI 数据飞轮的 few-shot、golden case、Prompt 版本、Canary 和回归门禁。
|
||||||
|
- 首个切片使用关系库精确键检索,只允许低敏枚举 `travel_application.transport_mode`;不复用当前缺少完整租户键的 FewShot/Qdrant 或员工风险画像存储个人记忆。
|
||||||
|
- 记忆证据只从认证动作事务中的服务端核验纠正产生,关联 Decision、Feedback、Outcome 和 Expense Case;草稿、客户端观察、重复请求和同一 Case 多次修改不能重复计票。
|
||||||
|
- 个人记忆只填充当前申请的空白软字段;当前明确输入、服务端 HR/组织主数据、企业制度和规则计算结果都优先于个人记忆。每次应用或抑制都返回可解释原因,不把优先级交给 Prompt 或 LLM 自行判断。
|
||||||
|
- 分层记忆首个组织级切片继续只允许 `travel_application.transport_mode` 的“飞机/火车/轮船”。企业记忆由租户管理员显式确认,部门记忆必须绑定稳定的 `organization_unit_id` 并只把部门名称作为展示标签;隐式证据暂只生成个人 Candidate,不允许少量个人行为自动升级为部门或企业制度。
|
||||||
|
- 分层解析顺序固定为“当前显式输入/规则禁用 > 企业记忆 > 部门记忆 > 个人记忆”。不同层值冲突时只应用最高优先级可用项,并返回候选层级、冲突值、未采用原因、有效期和衰减后置信度;不得静默覆盖当前输入,也不得把优先级交给模型自由判断。
|
||||||
|
- 管理员显式确认的组织记忆可直接激活,但必须记录创建人、确认来源、适用作用域和有效期;隐式个人记忆仍使用 3 个不同 Case、2 次审批通过、证据跨 7 天的最小样本门槛。组织记忆过期、撤销或服务异常时降级到下一层,不阻塞申请预览。
|
||||||
|
- 已确认历史案例接入报销预审前,`few_shot_samples` 与 Qdrant payload 必须补齐租户、业务场景、制度/规则标识、规则版本和 active 状态;每次向量命中还必须回关系库按租户和样本状态二次校验,样本改判时删除或稳定覆盖旧向量,禁止跨租户或陈旧结论进入提示。
|
||||||
|
- 历史案例只能输出结构化 `historical_case_evidence`,明确标记 `advisory_only`、人工确认结论、相似度、制度和版本是否匹配;它不得修改确定性预审的 `decision`、`passed`、`blocking_count`,不得改变预算复核和审批路由。旧版本只可作为降权参考,Qdrant 或 Embedding 不可用时返回空证据并继续规则链。
|
||||||
|
|
||||||
|
#### 节省与价值
|
||||||
|
|
||||||
|
- `savings_opportunities` 记录基线、风险暴露、建议动作、预计节省、负责人和截止时间。
|
||||||
|
- `savings_realizations` 记录执行结果、实际节省、财务确认人、证据、归因状态和去重键。
|
||||||
|
- 风险关联金额、暂缓付款和未采纳建议不得自动转为实际节省。
|
||||||
|
|
||||||
|
#### 连接器
|
||||||
|
|
||||||
|
- 定义统一 `ExpenseConnector` 协议,覆盖票据邮箱、税务验真、企业卡、商旅、支付、银行流水、ERP、HR、SSO、企微/钉钉。
|
||||||
|
- 外部事件必须具备幂等键、来源系统、原始事件 ID、发生时间、处理状态、错误码和重试次数。
|
||||||
|
|
||||||
|
#### 商业计量
|
||||||
|
|
||||||
|
- 以租户、套餐、模块和时间窗口记录模型、OCR、文档、分析任务、连接器与人工实施用量。
|
||||||
|
- 成本事件与客户价值事件分开记账,支持计算客户贡献毛利、实施成本摊销和私有部署成本。
|
||||||
|
- 套餐与配额只控制商业权益,不改变安全规则、租户隔离和高风险人工复核边界。
|
||||||
|
|
||||||
|
### 算法与规则
|
||||||
|
|
||||||
|
#### 自动化决策
|
||||||
|
|
||||||
|
- 自动化等级按动作计算,不按 Agent 整体授权。
|
||||||
|
- 动作风险、模型置信度、证据完整度、历史命中率、金额阈值、可逆性、企业上限和抽检率共同决定是否执行。
|
||||||
|
- L0 只读解释;L1 建议;L2 预填;L3 可逆自动化;L4 低风险直通;L5 资金支付和高风险决策始终保留人工。
|
||||||
|
|
||||||
|
#### 记忆激活
|
||||||
|
|
||||||
|
- 明确偏好后续可由用户认证确认后立即激活;首个切片只实现隐式候选,不把普通字段编辑冒充明确授权。
|
||||||
|
- 隐式出行方式偏好需要同租户、同员工、同场景和同归一化值的 3 个不同 Expense Case 服务端核验纠正,其中至少 2 个出现 `application_approved`,且首尾证据至少跨 7 天;同一 Case、重试、草稿续签只计一票。
|
||||||
|
- `accepted`、`client_observed`、`draft_saved` 和单纯 `application_submitted` 只能作为观察信息,不能单独激活;退回、反向纠正、结果 reversed 或制度冲突会抑制现有记忆。
|
||||||
|
- Candidate 默认 90 天有效,Active 默认 180 天;状态支持 `candidate / active / suppressed / expired / revoked`。相反值先抑制旧记忆并建立新候选,不原地覆盖;用户“忘记”后立即 revoked,清除可恢复值且不可自动复活。
|
||||||
|
- 错误标注、违规习惯、一次性例外、自由文本和敏感字段不能直接变为默认记忆。
|
||||||
|
|
||||||
|
#### 风险与预审
|
||||||
|
|
||||||
|
- 统一输出事实、规则、证据、风险、建议和可执行修复动作。
|
||||||
|
- 报销提交必须先完成服务端预审握手。预审结果至少包含稳定 `review_id`、输入指纹、规则集指纹、流水线版本、`ready / needs_fix / ready_with_review` 决策、结构化发现和修复动作;客户端不得仅凭旧的 `passed` 标记提交。
|
||||||
|
- 输入字段、明细、票据、风险事实或规则集发生变化时,旧预审自动失效并在提交事务内重算;同一输入和规则集重复预审复用相同 `review_id`,避免生成重复事件。
|
||||||
|
- 只有 high/critical 且 `fixable_by_submitter` 的未解决风险阻断提交;预算治理、领导判断和财务复核风险随单进入对应审批角色,不把所有高风险简单放行或全部拦截。
|
||||||
|
- `needs_fix` 必须在预算占用和 `claim_submitted` 之前阻断,保持草稿状态并返回 HTTP 409 结构化整改信息;可提交结果的预审事件与提交事件共享 correlation,并通过 causation 形成可追溯链路。
|
||||||
|
- 使用已确认正/负样本校准误报与漏检;规则或 Prompt 发布前运行 golden case。
|
||||||
|
- 新策略先进入 shadow,再 Canary,最后按动作开放自动化。
|
||||||
|
|
||||||
|
#### 费用分析与节省
|
||||||
|
|
||||||
|
- 描述性分析回答“发生了什么”;诊断分析回答“为什么”;建议分析回答“做什么”;价值闭环回答“是否执行并产生了多少结果”。
|
||||||
|
- 供应商、费用类型、城市、项目和部门基线必须保存数据窗口、样本量、算法版本和政策版本。
|
||||||
|
- 节省归因必须区分现金节省、可释放工时价值和不可货币化效率改善。
|
||||||
|
|
||||||
|
### 数据与契约
|
||||||
|
|
||||||
|
#### 核心数据对象
|
||||||
|
|
||||||
|
- `expense_cases`:统一费用事件和当前阶段。
|
||||||
|
- `expense_case_links`:连接申请、报销、票据、审批、付款、凭证和外部对象。
|
||||||
|
- `business_events`:append-only 业务事件账本。
|
||||||
|
- `ai_application_preview_decisions`:申请落单前由服务端重新规范化并短期签发的预览建议;不创建空 Expense Case,不保存建议明文,动作成功后一次消费。
|
||||||
|
- `ai_decisions`:结构化 AI 建议与版本证据。
|
||||||
|
- `ai_decision_feedback`:接受、修改、拒绝和忽略。
|
||||||
|
- `workflow_outcomes`:退回、补件、审批、付款、审计和最终结果。
|
||||||
|
- `memory_entries`:按租户、作用域主体、场景、字段和值指纹保存候选/激活/抑制/过期/撤销状态,只允许白名单低敏值可恢复。
|
||||||
|
- `memory_evidence_links`:把记忆与服务端 Decision、Feedback、Outcome、Expense Case 和业务结果关联;唯一键保证同一 Case 对同一候选最多计一票。
|
||||||
|
- `automation_policies` / `automation_grants`:动作级自动化策略和授权。
|
||||||
|
- `profile_baseline_snapshots`:持久化员工、部门、供应商、费用和流程基线。
|
||||||
|
- `savings_opportunities` / `savings_realizations`:节省机会和实现结果。
|
||||||
|
- `usage_meter_events`:租户、模块、模型、OCR、文档和分析用量。
|
||||||
|
|
||||||
|
#### 零录入附件关联任务契约
|
||||||
|
|
||||||
|
- 保留现有 `status`、`message`、`claim_id`、`claim_no`、`uploaded_count`、`skipped_count` 字段,追加字段保持前端向后兼容。
|
||||||
|
- 任务追加 `resolution`、`requires_confirmation`、`expense_case_id`、`application_claim_id`、`application_claim_no`、`confidence`、`confidence_score`、`match_reasons`、`exceptions`、`missing_fields`、`risk_items` 和 `candidates`。
|
||||||
|
- `status=succeeded` 只表示匹配流程完成;`resolution=auto_associated` 表示已经完成可逆归集,`resolution=requires_confirmation` 表示没有业务写入,需要用户选择候选或处理异常。
|
||||||
|
- 候选最少返回目标类型、费用事件 ID、申请 ID/编号、草稿 ID/编号、分数、置信度和命中原因;不得返回其他租户或当前员工数据范围之外的候选。
|
||||||
|
- `receipt_received` 和 `attachment_associated` 使用票据 ID 与目标草稿组成稳定幂等键,并与票据 Link、附件写入在同一数据库事务中完成;若文件存储写入失败,数据库事务回滚且任务进入失败态。
|
||||||
|
|
||||||
|
#### 最小事件词典
|
||||||
|
|
||||||
|
- `expense_case_created`
|
||||||
|
- `historical_claim_imported`:迁移前旧单的当前快照;只证明已纳入统一费用事件,不重建或伪造迁移前审批历史。
|
||||||
|
- `application_generated` / `application_submitted` / `application_approved`
|
||||||
|
- `application_pre_review_completed` / `claim_pre_review_completed`
|
||||||
|
- `receipt_received` / `receipt_verified` / `ocr_corrected`
|
||||||
|
- `field_suggested` / `field_accepted` / `field_edited` / `field_rejected`
|
||||||
|
- `attachment_associated` / `draft_saved` / `claim_submitted`
|
||||||
|
- `claim_returned` / `supplement_completed`
|
||||||
|
- `risk_flagged` / `risk_confirmed` / `risk_false_positive`
|
||||||
|
- `route_suggested` / `route_overridden`
|
||||||
|
- `claim_approved` / `payment_requested` / `payment_completed`
|
||||||
|
- `accounting_entry_created` / `reconciliation_completed`
|
||||||
|
- `saving_opportunity_created` / `saving_action_completed` / `saving_confirmed`
|
||||||
|
|
||||||
|
#### 状态枚举
|
||||||
|
|
||||||
|
- Expense Case:`planning` / `approved_to_spend` / `spending` / `claiming` / `reviewing` / `paying` / `accounting` / `closed` / `cancelled`。
|
||||||
|
- AI Decision:`suggested` / `accepted` / `edited` / `rejected` / `ignored` / `executed` / `rolled_back`。
|
||||||
|
- Memory:`candidate` / `active` / `suppressed` / `expired` / `revoked`。
|
||||||
|
- Automation:`explain` / `recommend` / `prefill` / `reversible_auto` / `low_risk_straight_through` / `human_only`。
|
||||||
|
- Savings:`identified` / `accepted` / `in_progress` / `realized` / `verified` / `rejected` / `expired`。
|
||||||
|
|
||||||
|
#### 兼容策略
|
||||||
|
|
||||||
|
- 迁移初期可用 shadow 事件校验现有 `ExpenseClaim` 的映射;某个状态一旦正式纳入事件模型,其业务写入与 Outbox 事件必须进入同一事务。
|
||||||
|
- 迁移前已有 `ExpenseClaim` 通过显式维护命令写入一条 `historical_claim_imported` 快照事件:事件发生时间使用真实回填时间,原创建、发生、提交和更新时间只进入内部 payload;`history_reconstructed=false`,不得按当前状态反推并伪造历史提交、审批或付款动作。
|
||||||
|
- 历史回填默认 dry-run,必须显式提供租户、创建时间边界和数据库目标;apply 还要求精确目标确认、迁移 head、advisory lock 和分批事务。稳定幂等键、源快照指纹、已有 Link 跳过及孤立 Event 冲突拒绝共同保证可审计重跑。
|
||||||
|
- 历史快照事件使用 `delivery_status=suppressed`,供时间线和分析读取,但不进入实时 Outbox 投递;用户态 API 仍只返回事件白名单,不暴露回填批次、源指纹、幂等键和投递状态。
|
||||||
|
- 旧 `ReimbursementRequest` 进入只读兼容和迁移状态,停止新增第二套业务编排。
|
||||||
|
- 现有 `risk_flags_json` 保持读取兼容,新审批、付款、归档和关系事件写入结构化表。
|
||||||
|
- API 新字段优先追加,不在同一阶段破坏现有前端契约。
|
||||||
|
- 从未成功签发 `decision_id` 的旧申请预览,或签发接口失败且服务端没有当前会话有效决策时,保存/提交继续走现有 `client_observed` 路径;当前会话一旦存在有效服务端决策,省略 `decision_id` 必须拒绝,不能主动降级绕过核验。兼容路径不得在用户已经编辑后把最终值反向登记成“原始建议”。
|
||||||
|
- 新预览使用 30 分钟有效的服务端 `decision_id`。保存草稿或提交成功后一次消费;保存草稿会尽力签发基于已保存事实的新 `decision_id`,供后续提交继续核验,但续签属于业务提交后的派生能力,续签失败不能把已成功保存返回为失败。相同动作请求 ID、动作和最终指纹允许安全重放,其他重放拒绝。
|
||||||
|
- AI 工作台、小财管家和通用 Orchestrator 的结构化申请预览统一复用服务端签发/消费工作流。小财管家签发失败时保持可编辑和可重试,但保存、提交必须 fail-closed,不再降级到旧 Orchestrator 副作用;只有没有结构化预览的历史消息保留旧兼容入口。
|
||||||
|
- Orchestrator 将已签发 decision 保存在服务端会话状态,后续保存/提交只消费该状态,不采信浏览器回传的 preview 或 decision。保存草稿返回的新 decision 继续写回会话,服务端消息中的结构化预览可在刷新或换端后恢复。
|
||||||
|
- 迁移桥接期集中维护 migration-owned 表集合;所有 legacy bootstrap 只能创建集合之外的表。标准启动在 `alembic upgrade head` 前只读核对 revision 与自有表集合,发现漂移时拒绝启动,不自动猜测、stamp 或修复。
|
||||||
|
- 所有表结构通过 Alembic 迁移,不继续在请求路径执行 DDL。
|
||||||
|
|
||||||
|
#### 版本与审计
|
||||||
|
|
||||||
|
- 事件记录 actor、tenant、source、run、model、Prompt、rule、policy 和 schema 版本。
|
||||||
|
- 记忆、自动化策略、规则、Prompt 和基线均支持 supersede、有效期和回滚。
|
||||||
|
- 高风险人工覆盖必须记录原因、证据和审批主体。
|
||||||
|
|
||||||
|
### 权限与安全
|
||||||
|
|
||||||
|
- 登录后签发服务端可验证会话或 JWT,服务端从会话和数据库解析用户、租户、角色和数据范围。
|
||||||
|
- 移除客户端 `X-Auth-*` 作为授权事实来源;管理面、Bootstrap、设置和模型连通性接口必须受平台管理员保护。
|
||||||
|
- P0 采用不透明 Bearer 会话:明文 token 仅在登录成功时返回,数据库只保存 SHA-256 摘要;每次请求从服务端会话和员工数据重新解析当前身份与角色,过期、撤销或不存在的 token 统一拒绝。
|
||||||
|
- Web 端只在 `sessionStorage` 保存 token 和过期时间,普通请求、流式请求及页面关闭收尾统一携带 Bearer;任一 `401` 触发本地会话清理,登出时会话指标与 token 撤销在同一事务完成。
|
||||||
|
- 已初始化系统的 Bootstrap 状态只返回脱敏信息,Bootstrap 写入、系统设置、模型连通性、缓存、审计与系统日志等敏感管理面由平台管理员权限保护;Vite 本地 Setup 桥在初始化完成后锁定重新配置入口。
|
||||||
|
- P0 数据契约即引入最小 `tenant_id`、数据库约束、行级过滤、向量库命名空间和对象存储前缀隔离;删除传播、数据导出和私有部署加固可在商业化阶段继续完善。
|
||||||
|
- Expense Case 用户态查询只返回流程摘要:Case 基础状态、关系类型、事件类型、操作人、发生时间及白名单业务载荷;不返回关联资源 ID、幂等键、correlation、causation、聚合标识或 Outbox 投递状态。有权查看入口单据的本人、当前审批人、财务和管理员可查看整 Case 的安全摘要,无权限与跨租户查询继续以 404 隐藏资源存在性。
|
||||||
|
- 申请预览签发与消费同时绑定 `tenant_id`、actor、Bearer 登录会话和会话 ID;动作入口按这些服务端事实锁行校验,不能只凭随机 UUID 授权。浏览器提供的模型来源、`finalValue`、用户和角色声明均不作为可信事实。
|
||||||
|
- 个人记忆主体优先使用服务端 `employee_id`,缺失时才使用规范化用户名;所有唯一键、读取、证据外键和忘记动作都必须以 `tenant_id` 开头。同邮箱或同员工号在不同租户形成完全独立的记忆。
|
||||||
|
- 记忆值采用 deny-by-default 白名单;首个切片仅保存规范枚举“飞机/火车/轮船”。事由、地点、日期、金额、客户/项目自由文本、附件、银行卡、收款人与支付账户均不得进入记忆明文、Prompt、向量 payload 或日志。
|
||||||
|
- 外部 `/orchestrator/run` 的用户消息、定时任务和系统事件都必须携带有效登录会话,不能由请求体中的 `source` 自行声明可信内部来源;其中 `schedule` / `system_event` 只允许平台管理员触发。服务端使用登录态覆盖用户、租户、角色、管理员、员工、审批人和调度操作人别名,并移除客户端 preview/decision 状态。最近会话查询与删除只使用当前登录用户和租户,查询参数中的 `user_id` 仅保留协议兼容,不参与授权。
|
||||||
|
- 预览与学习账本中的字段值使用独立、可版本化的费用申请密钥计算 HMAC-SHA256 指纹,密钥目录和文件权限分别为 `0700`、`0600`;核验旧决策必须使用其签发版本,缺失版本直接拒绝,不静默生成替代密钥。字段指纹同时编码“字段是否存在”,避免新增或删除字段被误判为采纳;训练资格仍保持关闭,直至审批、付款或人工核验闭环完成。
|
||||||
|
- 自动化权限按动作、金额、场景、风险和有效期授予,不使用全局“允许 Agent 自动执行”开关。
|
||||||
|
- 收款账户变更、资金支付、制度发布、高风险驳回和敏感主数据变更执行双人或更高等级复核。
|
||||||
|
|
||||||
|
### 降级策略
|
||||||
|
|
||||||
|
- LLM 不可用:回退到规则和人工填写。
|
||||||
|
- OCR 不可用:文件保留并进入待识别队列,用户可手工补录。
|
||||||
|
- Qdrant/few-shot 不可用:使用 stable Prompt 和基础规则,不阻塞主流程。
|
||||||
|
- 历史案例证据只返回固定的脱敏标签与摘要,不返回样本 ID、费用单号、人工评论或历史结论原文;它只能辅助复核,不参与确定性规则、阻断数量、预算复核和审批路由计算。
|
||||||
|
- 外部支付/ERP/税务连接器不可用:事件进入 retryable 状态,保留人工处理入口和幂等键。
|
||||||
|
- 记忆冲突或可信度不足:只展示建议,不自动应用。
|
||||||
|
- 结构化预览签发超时或不可用:允许继续编辑和重新签发,但保存、提交保持 fail-closed;只有不存在结构化预览的历史兼容入口保留原有降级路径,且只能记录 `client_observed` 行为证据。
|
||||||
|
- 记忆服务不可用、过期或校验失败:回退为“无个人记忆 + 当前规则”,不阻塞预览,也不读取跨租户、旧缓存或无租户向量样本。
|
||||||
|
- Savings 证据不足:保留机会状态,不进入已实现节省。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
### 自动化资格分数
|
||||||
|
|
||||||
|
```text
|
||||||
|
automation_score
|
||||||
|
= w1 * model_confidence
|
||||||
|
+ w2 * evidence_completeness
|
||||||
|
+ w3 * historical_precision
|
||||||
|
+ w4 * reversibility
|
||||||
|
- w5 * action_risk
|
||||||
|
- w6 * amount_risk
|
||||||
|
```
|
||||||
|
|
||||||
|
变量说明:
|
||||||
|
|
||||||
|
- `model_confidence`:模型或规则对当前建议的置信度。
|
||||||
|
- `evidence_completeness`:发票、申请、预算、合同、订单和政策证据完整度。
|
||||||
|
- `historical_precision`:相同租户、场景、动作和版本的历史正确率。
|
||||||
|
- `reversibility`:动作是否可以无损撤销或回滚。
|
||||||
|
- `action_risk`:动作类型风险,支付和制度发布最高。
|
||||||
|
- `amount_risk`:金额相对企业阈值和历史基线的风险。
|
||||||
|
- `w1...w6`:按企业和动作配置的权重。
|
||||||
|
- 适用边界:分数只决定候选自动化等级,仍需满足企业硬性白名单、金额上限、抽检率和人审要求。
|
||||||
|
|
||||||
|
### 记忆激活置信度
|
||||||
|
|
||||||
|
```text
|
||||||
|
memory_confidence
|
||||||
|
= consistent_evidence_weight
|
||||||
|
+ outcome_success_weight
|
||||||
|
+ explicit_confirmation_weight
|
||||||
|
- conflict_weight
|
||||||
|
- age_decay
|
||||||
|
```
|
||||||
|
|
||||||
|
变量说明:
|
||||||
|
|
||||||
|
- `consistent_evidence_weight`:多次一致修改或选择产生的证据。
|
||||||
|
- `outcome_success_weight`:建议最终一次通过、付款或审计确认的权重。
|
||||||
|
- `explicit_confirmation_weight`:用户或管理员明确确认的权重。
|
||||||
|
- `conflict_weight`:与企业制度、部门规则或其他记忆冲突的惩罚。
|
||||||
|
- `age_decay`:记忆随时间衰减。
|
||||||
|
- 适用边界:敏感信息、一次性例外和违规行为不得依赖分数自动激活。
|
||||||
|
|
||||||
|
### 安全智能直通率
|
||||||
|
|
||||||
|
```text
|
||||||
|
safe_straight_through_rate
|
||||||
|
= qualified_completed_cases_without_manual_correction_or_return
|
||||||
|
/ eligible_completed_cases
|
||||||
|
```
|
||||||
|
|
||||||
|
分子还必须满足必要审批完成,且事后抽样审计未发现重大问题。
|
||||||
|
|
||||||
|
### 客户可验证 ROI
|
||||||
|
|
||||||
|
```text
|
||||||
|
verified_value
|
||||||
|
= verified_cash_savings
|
||||||
|
+ verified_releasable_labor_value
|
||||||
|
|
||||||
|
customer_roi
|
||||||
|
= (verified_value - customer_total_cost) / customer_total_cost
|
||||||
|
```
|
||||||
|
|
||||||
|
变量说明:
|
||||||
|
|
||||||
|
- `verified_cash_savings`:客户财务确认的重复支付阻止、超标准调整、采购或预算优化等实际现金节省。
|
||||||
|
- `verified_releasable_labor_value`:基于上线前后人工分钟、单据量和角色完全成本计算,并经客户认可的工时价值。
|
||||||
|
- `customer_total_cost`:订阅、用量、实施、集成和客户内部运营成本。
|
||||||
|
- 适用边界:现金节省和工时价值分开披露;风险暴露、未采纳建议和暂缓付款不得计入。
|
||||||
|
|
||||||
|
### 客户贡献毛利
|
||||||
|
|
||||||
|
```text
|
||||||
|
customer_contribution_margin
|
||||||
|
= subscription_revenue
|
||||||
|
+ usage_revenue
|
||||||
|
+ module_revenue
|
||||||
|
+ verified_savings_share
|
||||||
|
- llm_ocr_storage_cost
|
||||||
|
- third_party_cost
|
||||||
|
- support_cost
|
||||||
|
- amortized_implementation_cost
|
||||||
|
```
|
||||||
|
|
||||||
|
用于判断高收入但高度定制或私有部署客户是否实际盈利。
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- Expense Case 状态机、关系绑定、幂等和非法状态跃迁单元测试。
|
||||||
|
- 历史 ExpenseClaim 回填的 dry-run 零写、租户/时间边界、快照真实性、批次回滚、冲突拒绝、重复执行和数据库目标防误连测试。
|
||||||
|
- Business Event、AI Decision、Feedback、Outcome、Memory、Automation Policy、Savings Ledger service 单元测试。
|
||||||
|
- 服务端会话、租户隔离、角色和动作权限的正向/越权测试。
|
||||||
|
- 连接器事件幂等、重试、回执、失败恢复和重复支付防护测试。
|
||||||
|
- Alembic baseline、升级、回滚边界和旧数据迁移测试。
|
||||||
|
- 现有报销、预算、风险、知识库和 Agent 回归测试。
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 费用事件时间线、异常确认、断点续办和操作反馈视图模型测试。
|
||||||
|
- 移动拍票、上传失败恢复、真实列表和审批 mutation 测试。
|
||||||
|
- 审批例外工作台键盘操作、焦点管理和权限态测试。
|
||||||
|
- CFO 价值看板对预计/实际/确认节省的展示和下钻测试。
|
||||||
|
- AI 记忆查看、修改、忘记和关闭个性化测试。
|
||||||
|
- 生产构建、lint、typecheck 和浏览器关键流程测试。
|
||||||
|
|
||||||
|
### 算法与规则
|
||||||
|
|
||||||
|
- 自动化分数、硬阈值、金额上限、动作白名单和抽检策略测试。
|
||||||
|
- 记忆候选、激活、冲突、过期、撤销和污染样本测试。
|
||||||
|
- 风险规则、Prompt、few-shot 和 golden case 回归门禁测试。
|
||||||
|
- Savings 去重、基线、归因和确认状态测试。
|
||||||
|
|
||||||
|
### 集成
|
||||||
|
|
||||||
|
- 一句话申请 → 票据归集 → 自动报销 → 预审 → 审批 → 付款事件 → 入账 → 归档端到端。
|
||||||
|
- 持久开发库只读流式克隆 → Alembic 升级 → 历史回填 dry-run/apply/重复 apply → 服务端登录 → 旧单时间线查询 → 持久库不变验证。
|
||||||
|
- AI 建议 → 用户修改 → 退回/通过 → 记忆候选 → 下次建议变化闭环。
|
||||||
|
- 常用出行方式 1/2 次纠正保持候选,第 3 个不同 Case 且至少 2 次审批通过、跨 7 天后激活;当前输入覆盖、跨租户拒绝、忘记后不再预填。
|
||||||
|
- 风险命中 → 人工确认/误报 → few-shot → 新版本回放 → Canary/回滚闭环。
|
||||||
|
- 节省机会 → 负责人执行 → 实际结果 → 财务确认 → ROI 看板闭环。
|
||||||
|
- 多租户同名员工、同号单据、向量检索和对象存储隔离测试。
|
||||||
|
|
||||||
|
### 容器验证
|
||||||
|
|
||||||
|
所有后端、集成、数据库迁移和外部依赖验证必须在项目容器内执行,单条测试命令最大超时 60s:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv \
|
||||||
|
x-financial-local-linux \
|
||||||
|
/tmp/x-financial-server-venv/bin/pytest -q server/tests/test_expense_case_service.py
|
||||||
|
```
|
||||||
|
|
||||||
|
如果本地 Compose 使用不同容器名,先通过 `docker ps` 确认当前主应用容器,不得改为宿主机直接运行后端测试。
|
||||||
|
|
||||||
|
### 手工验证
|
||||||
|
|
||||||
|
- 在真实容器页面完成申请、拍票、报销、退回补件、审批、付款和价值看板流程。
|
||||||
|
- 检查每个 AI 建议是否能展示来源、置信度、修改结果和后续业务结果。
|
||||||
|
- 检查用户能否查看、修改和忘记 AI 记忆。
|
||||||
|
- 检查高风险动作无法绕过人工,低风险可逆动作能够撤销和回放。
|
||||||
|
- 检查每项实际节省能够追溯到基线、建议、执行、确认人和证据。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
以下目标在缺少真实客户基线前均为首轮试点的方向性目标,正式数值必须在试点基线采集后冻结。
|
||||||
|
|
||||||
|
- [A1] 全流程验收:任一费用事件可以从申请追溯到票据、报销、审批、付款、凭证、归档、AI 决策和节省结果。
|
||||||
|
- [A2] 体验指标:首个试点场景报销创建时间 P50 目标不超过 60 秒。
|
||||||
|
- [A3] 自动化指标:可结构化字段的自动填充率方向性目标不低于 85%,且字段修改率持续下降。
|
||||||
|
- [A4] 质量指标:首次提交完整率方向性目标不低于 90%,退回率和每单人工触点低于试点基线。
|
||||||
|
- [A5] 智能指标:AI 字段采纳率、重复纠正率、建议接受率和记忆复用成功率可按租户、场景、版本统计。
|
||||||
|
- [A6] 风险护栏:重大风险漏检率、误报干扰率、人工覆盖率和抽样审计结果可计算;自动化提升不得以护栏恶化为代价。
|
||||||
|
- [A7] 价值指标:每项节省区分机会、预计、执行、实现和财务确认;客户月度 ROI 可回放。
|
||||||
|
- [A8] 商业指标:早期可计算付费试点转年度合同率、持续产生可验证价值的客户率和按客户贡献毛利;成熟后计算 NRR。
|
||||||
|
- [A9] 性能指标:同步页面接口 P95、AI 预填 P95、OCR 队列等待、后台任务恢复和连接器重试达到试点 SLA,具体阈值在基线后确定。
|
||||||
|
- [A10] 安全指标:客户端无法伪造管理员、跨租户访问返回拒绝、敏感动作具备二次授权和审计。
|
||||||
|
- [A11] 可观测性:每个费用事件、AI 决策、工具调用、连接器事件、自动化执行和失败重试使用统一 correlation ID。
|
||||||
|
- [A12] 工程验收:相关后端定向测试、前端测试、lint、typecheck、构建和端到端验证在容器事实环境中通过。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
### 风险
|
||||||
|
|
||||||
|
- 范围过大:费用闭环、AI 学习、价值分析和商业化不能同时全量实现,需要按 P0/P1/P2 阶段交付。
|
||||||
|
- 领域模型迁移:旧 `ReimbursementRequest`、`ExpenseClaim` 和 JSON 状态并存,必须旁路记录、小步迁移和双读校验。
|
||||||
|
- 历史证据边界:旧单快照只表达回填时可确认的当前状态,迁移前逐节点办理过程仍以原单据和既有审计为准;后续分析不得把快照事件误当成历史审批事实。
|
||||||
|
- 认证和租户:当前客户端身份头不适合自动化和 SaaS,多租户、记忆和高风险动作开发前必须修复。
|
||||||
|
- 会话运维:不透明会话已经替代客户端身份头,但仍需补充定时清理、活跃会话查看/全部退出、密钥轮换策略、登录限流和企业 SSO;当前 `tenant_id` 仍是最小契约,不代表跨租户查询守卫已经完成。
|
||||||
|
- Agent 会话租户边界:Orchestrator 与 Steward 动作运行时都把可信 `tenant_id` 写入会话状态;创建、恢复、幂等检查点重放、单条删除和批量删除同时校验租户与用户名,同名用户不能跨租户复用 decision 或动作结果。历史无租户状态的会话在认证入口 fail-closed,不会被恢复或删除。`agent_conversations` 仍缺少独立 `tenant_id` 列和数据库复合约束,后续迁移需要把当前应用层守卫下沉为结构化租户键。
|
||||||
|
- 费用事件读取边界:用户态精简 DTO 和首批 HTTP 权限测试已完成;剩余风险是同一 URL 若已有外部客户端依赖旧内部字段会产生契约变更,且未来新增敏感 payload 字段必须继续显式进入白名单,不能恢复任意字典透传。
|
||||||
|
- 反馈投毒:一次点击或违规习惯不能直接成为记忆,需要候选态、最小样本、制度约束和结果权重。
|
||||||
|
- 记忆值恢复:现有学习账本只保存 HMAC 指纹,无法恢复具体偏好值;必须由独立记忆表在认证事务中只提取白名单枚举,不能为了复用把自由文本重新塞回账本。
|
||||||
|
- 记忆与制度冲突:首个切片的三种交通方式当前均只有估价和舱等规则,没有“禁止某种交通方式”的制度维度;后续一旦增加按职级、路线或金额禁用交通方式的规则,必须先提供服务端允许性判定、抑制原因和回归用例,再允许个人记忆参与预填。
|
||||||
|
- 自动化失控:高准确率不代表高风险动作可以无人值守,必须按动作授权并支持 shadow、Canary、抽检和回滚。
|
||||||
|
- 虚假节省:风险暴露金额、暂缓付款和工时估算容易被夸大,必须由客户财务确认并执行去重。
|
||||||
|
- 外部依赖:税务、支付、银行、商旅、ERP 和消息平台连接器存在可用性、资质和交付周期风险。
|
||||||
|
- 移动端成熟度:拍票基础已有,但主业务仍有 mock,必须先完成真实登录、列表、草稿和审批闭环。
|
||||||
|
- 成本失控:高频 LLM、OCR、向量检索和私有部署可能降低毛利,需要按租户和模块计量成本。
|
||||||
|
- 数据稀疏:本地开发库缺少报销、反馈、风险和 golden 样本,正式目标必须来自真实试点而不是演示数据。
|
||||||
|
|
||||||
|
### 已处理依赖
|
||||||
|
|
||||||
|
- 已有预算、票据夹、OCR、报销草稿、风险规则、审批路由、知识库、员工画像、Agent trace、few-shot 和财务看板可复用。
|
||||||
|
- 已有 AI 数据飞轮概念与阶段 1 few-shot 实现,不重复建设样本检索底座。
|
||||||
|
- 已有统一门控管道设计,可作为 AI 场景收口依据。
|
||||||
|
|
||||||
|
### 待确认
|
||||||
|
|
||||||
|
- 首个目标客户规模和部署形态:中小 SaaS、中大型企业 SaaS 或集团私有部署。
|
||||||
|
- 首个 90 天付费试点场景:差旅报销、业务招待、日常费用或预算风控。
|
||||||
|
- 是否进入企业支付、商旅预订或公司卡领域;如果不进入,应明确聚焦 AI 费控与经营分析。
|
||||||
|
- 客户是否接受工时价值进入 ROI,以及对应角色完全成本口径。
|
||||||
|
- L3/L4 自动化允许的动作、金额阈值、抽检率和授权主体。
|
||||||
|
- 税务验真、银行、ERP、企微/钉钉等连接器的首批合作范围。
|
||||||
|
- SaaS 定价的员工档位、包含用量、超额计价和增值模块边界。
|
||||||
|
|
||||||
|
### 降级策略
|
||||||
|
|
||||||
|
- 首期只选择一个费用场景做完整闭环,其他场景保留现有流程。
|
||||||
|
- 正式切换前允许 shadow 事件用于一致性校验;正式切换后的关键业务状态与 Outbox 事件必须原子提交,失败时整体回滚并向用户返回可重试状态。
|
||||||
|
- 画像刷新、分析聚合、消息通知等可重建派生任务失败时进入重试/死信队列,不阻塞已成功提交的关键业务状态。
|
||||||
|
- 自动化默认停留在 L1/L2,只有试点评估和风险护栏通过后才逐项开放 L3/L4。
|
||||||
|
- 外部连接器未完成前保留人工导入、确认和对账入口,但状态语义和审计事件按正式契约记录。
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
- 2026-07-16(审批任务正式化):新增 `approval_tasks` 与 append-only `approval_task_events`,报销提交、批准、退回和下一节点推进统一生成/完成任务。支持数据库分页筛选、可解释优先级、当前节点 SLA、委托/撤销、转交、顺序加签、并行会签、手动/调度升级、独立事务批量部分成功、乐观版本和不可变幂等响应;历史回填默认 dry-run。
|
||||||
|
- 2026-07-16(风险豁免决定):豁免申请新增范围、条件与到期时间,只有在职 finance/executive 且非申请人可批准或拒绝;过期 fail-closed。风险变更在同一事务刷新开放审批任务的风险、证据、批量资格和优先级,不重置 SLA 窗口;前端展示完整追加审计链并在到期时刷新权威投影。
|
||||||
|
- 2026-07-16(审批工作台接入):单据中心“审核单”切换到 `ApprovalTaskWorkspace`,移除旧 Claim 预览列表和静态“批量通过 23 条”提示。工作台提供真实批准、退回、委托、转交、加签、会签、升级和批量动作;未筛选 pending 摘要与队列筛选结果分离,详情返回恢复任务筛选/页码,相同负载安全重试、编辑负载自动换新幂等键。
|
||||||
|
- 2026-07-16(阶段验证):容器内相关后端 `122 passed, 4 skipped`、前端审批/单据中心/风险专项 `60 passed`、Ruff 与 Vite 构建通过;一次性 PostgreSQL 17 空库迁移循环 `1 passed`、审批任务和风险并发 `3 passed`。完整前端套件未新增真实回归,仍有 31 项既有陈旧断言/质量债待后续硬化阶段清理。
|
||||||
|
- 2026-07-16(审批动作协议):批准、退回和付款统一接入租户化幂等账本、请求指纹、乐观状态/节点前置条件、advisory lock 与 Claim 行锁;完全相同的网络重试不重复扣减预算、写审批、生成事件或付款,不同内容和陈旧状态稳定返回 409。前端确认流在用户确认时冻结请求和前置条件,并保持服务端状态与展示标签分离。
|
||||||
|
- 2026-07-16(风险门禁与处置):高危/严重可行动风险在任何审批副作用前阻断;类型化风险处置覆盖确认、误报、补件、整改、豁免申请和完成处置,具备乐观版本、权限守卫和只追加审计。门禁合并持久观察与未匹配原始风险,重大观察落库失败 fail-closed;审批、处置和 Hermes 扫描通过 Claim 公共锁协调,扫描过期快照不会覆盖新事实。
|
||||||
|
- 2026-07-16(审批工作台):审批中心接入可解释优先级投影,展示风险、预算、金额、等待时长、证据完整度和 advisory-only AI 建议;风险证据卡读取真实持久字段、显示生命周期和明确动作,但不提供绕过风险门禁的直接审批入口。
|
||||||
|
- 2026-07-16(审批与风险验证):容器内审批/风险/费用服务组合 174 项、迁移/所有权 54 项、前端 73 项及 Vite 生产构建通过;一次性 tmpfs PostgreSQL 17 完成 13 项迁移和 1 项真实并发验证,临时数据库自动清理且持久开发库未修改。变更 Python 文件 Ruff F/I 与 `git diff --check` 通过。
|
||||||
|
- 2026-07-16(严格响应重放):修复审批和风险动作只保证副作用幂等、却在重放时返回当前投影的问题。审批账本保存完整 `ExpenseClaimRead`;风险 append-only 事件通过 `20260716_0012` 保存完整处置响应。单据或风险后续推进后,旧请求只返回首次快照和当时事件,不暴露后续状态;历史无快照事件按目标版本安全重建。门禁同步增加申请/报销阶段匹配和显式 `route_review` 路由语义。容器内审批、费用 Case、路由、风险和严格重放组合 182 项通过,一次性 PostgreSQL 17 迁移与并发共 17 项通过。
|
||||||
|
- 2026-07-16(分层组织记忆):`travel_application.transport_mode` 已形成当前输入/规则 > 企业 > 部门 > 个人的解析链。租户管理员可维护 30-365 天有效的企业/部门低敏记忆;同级冲突 fail-closed,低级覆盖、过期和撤销均返回可解释但脱敏的状态。创建、更新、撤销使用作用域锁、稳定幂等键和请求指纹,响应丢失可安全重放,请求内容变化返回冲突。
|
||||||
|
- 2026-07-16(租户化历史案例):风险观测、人工反馈、few-shot 关系数据和 Qdrant 向量统一绑定租户、场景、制度和规则版本;Hermes 按租户分别构建图与历史,风险规则生成不再回落到默认租户。报销预审与 AI 助手接入只读 `historical_case_evidence`,旧版本显式标 stale,检索故障降级为空证据。
|
||||||
|
- 2026-07-16(历史证据隐私边界):公开协议仅返回“历史已确认/历史误报,仅供复核”的固定摘要,不暴露样本 ID、费用单号、人工评论和历史结论原文;历史命中不改变 review ID、规则 findings、passed、blocking count、预算复核或审批路由。
|
||||||
|
- 2026-07-16(分层学习验证):容器内分层与组织记忆 44 项、历史案例/风险/预审/规则生成 90 项、报销接口与费用服务 137 项、Golden 与历史注入 28 项、迁移/模型/租户组合 70 项通过;前端 18 项和 Vite 生产构建通过。一次性 tmpfs PostgreSQL 17 完整迁移循环 9 项通过,临时容器自动清理且开发数据库未修改;变更 Python 文件 Ruff F/I、compileall 与 `git diff --check` 通过。
|
||||||
|
- 2026-07-16(P0 提交前预审握手):新增稳定预审决策契约、输入/规则/动态 findings 指纹和结构化整改动作;申请与报销提交前均由服务端重新计算完整风险上下文,可整改重大风险在预算占用前返回 409,握手期间风险变化返回 `PRE_REVIEW_CHANGED`,预算治理和人工判断风险继续进入领导与 P8。
|
||||||
|
- 2026-07-16(P0 费用事件续接):新增 `application_pre_review_completed` / `claim_pre_review_completed` 时间线语义,预审与提交共享 correlation/causation;票据归集完成后刷新预审,申请批准生成空报销草稿时 Expense Case 保持 `approved_to_spend`,首张票据归集后再进入 `claiming`。
|
||||||
|
- 2026-07-16(租户与路由修复):AI 草稿、申请预览、Steward 和差旅测算透传真实租户;Claim 核心访问和关联草稿后台任务按 CaseLink 与服务端租户隔离;仅未整改的人工判断/预算治理高风险申请进入同部门 P8,已解决风险不再重复升级。
|
||||||
|
- 2026-07-16(结构化风险回填):409 findings 保留原始 source、business stage、risk domain 与 actionability,前端即使详情刷新失败也能生成可见整改卡,不再被 `ai_pre_review` 汇总过滤规则丢弃。
|
||||||
|
- 2026-07-16(工程拆分):提取申请关联、职级标准调整、预审端点、风险清单指纹与租户查询范围职责,所有本轮触及的核心服务、端点和前端提交模块均回到 800 行硬上限以内。
|
||||||
|
- 2026-07-16(验证):容器内费用服务与审批路由 126 项、费用事件/接口/租户/票据关联 63 项、报销接口全量 20 项和前端预审/时间线 24 项通过;Vite 生产构建、Python compileall、关键未定义符号检查与 `git diff --check` 通过。
|
||||||
|
|
||||||
|
- 2026-07-13:完成产品能力、端到端费用旅程、AI 学习飞轮、KPI 和商业模式分析,形成总功能概念文档。
|
||||||
|
- 2026-07-13(规划阶段):只沉淀功能规划,没有修改业务代码、数据库结构或现有接口。
|
||||||
|
- 2026-07-13(P0 基础切片):新增 `ExpenseCase`、`ExpenseCaseLink`、`BusinessEvent` 模型与 `ExpenseCaseService`,提供 `/api/v1/expense-cases/by-claim/{claim_id}` 时间线查询接口。
|
||||||
|
- 2026-07-13(P0 基础切片):草稿、提交、退回、审批、申请转报销、付款和申请归档开始旁写结构化事件;事件具备租户字段、correlation、causation、幂等键和待投递状态,并与业务状态在同一事务提交。
|
||||||
|
- 2026-07-13(事务修复):关联申请归档/解绑的内部审计改为 `flush`,由外层业务事务统一提交,避免审计日志提前提交付款或归档状态。
|
||||||
|
- 2026-07-13(迁移桥接):新增第一条 migration-owned schema revision;服务启动先执行 Alembic,旧 `create_all` 明确排除三张迁移表。完整历史 schema baseline、正式数据库 upgrade/rollback 和停止旧 DDL 仍未完成。
|
||||||
|
- 2026-07-13(验证):容器内新增测试与既有差旅主链路回归共 24 项通过;Alembic PostgreSQL upgrade/downgrade 离线 SQL、启动脚本语法、OpenAPI 路由和新增文件静态检查通过。未对持久化开发数据库执行迁移。
|
||||||
|
- 2026-07-13(P0 认证安全切片):新增 `AuthSession` 不透明 Bearer 会话及 `20260713_0002_auth_sessions.py`,登录 token 只返回一次、数据库只保存摘要;`/auth/me`、会话结束和登出均从服务端会话解析身份,登出与使用指标收尾原子提交。
|
||||||
|
- 2026-07-13(管理面收口):移除生产代码中的 `X-Auth-*` 授权来源,保护 Settings、模型连通性、缓存、员工、分析、Agent 运行/轨迹、风险观测、审计及系统日志;已初始化 Bootstrap 返回脱敏状态并拒绝匿名重配置,Vite Setup 桥同步锁定。
|
||||||
|
- 2026-07-13(前端会话):Web 请求、流式响应和页面关闭收尾统一使用 Bearer,token 与过期时间只保存在 `sessionStorage`,集中处理 `401`、空闲过期和服务端过期;业务经理不再被前端视为平台管理员。
|
||||||
|
- 2026-07-13(认证验证):容器内认证/Bootstrap/费用事件/OpenAPI 定向测试 21 项通过,受保护业务端点回归 13 项通过,全量测试收集 805 项成功;前端会话、请求、Setup 锁和权限测试 17 项通过,生产构建通过。迁移仅生成并检查 upgrade/downgrade 离线 SQL,未写入持久化开发数据库。
|
||||||
|
- 2026-07-13(P0 提交一致性补缝):AI 新建费用申请并直接提交时,先持久化为草稿,再统一委托 `ExpenseClaimService.submit_claim`;提交校验、预算预占、申请提交风险标记、`application_submitted` 事件和审批状态不再由 AI 入口分别维护。
|
||||||
|
- 2026-07-13(事务边界修复):预算表运行时就绪检查改为复用当前 Session 连接,避免通过 Engine 执行 metadata 检查时隐式提交已 `flush` 的申请;即使预算预占后 Expense Case 事件写入失败,申请、预算额度、预算流水、预占和 Case 数据也会整体回滚。
|
||||||
|
- 2026-07-13(提交一致性验证):容器内 AI 申请直接提交、失败回滚、保存草稿副作用、申请提交主链路及预算/Expense Case 回归共 21 项通过,`ruff --select F,I` 通过;未修改数据库结构,未对持久化开发数据库执行迁移。
|
||||||
|
- 2026-07-13(P0 前端时间线):申请/报销详情在原有横向进度下接入真实 `/api/v1/expense-cases/by-claim/{claim_id}`,新增稳定排序、中文事件语义、状态迁移、退回原因、操作人和发生时间展示;未知事件使用通用文案,不暴露 correlation、幂等键或 Outbox 投递状态。
|
||||||
|
- 2026-07-13(前端兼容与验证):单据切换时清空旧时间线并通过请求序列防止乱序覆盖;未纳入 Expense Case 的 404 显示非阻断兼容态,其他接口错误支持重试且不影响原有进度与单据操作。容器内 99 条定向前端测试和 Vite 生产构建通过。
|
||||||
|
- 2026-07-13(P0 用户态事件契约):Expense Case 响应收口为 Case 基础状态、关系类型和事件安全摘要,事件载荷采用递归白名单;关联单据 ID、聚合信息、幂等与关联链、Outbox 投递字段及未知嵌套内部字段不再通过用户接口返回。
|
||||||
|
- 2026-07-13(P0 草稿事件补缝):AI 工作台与小财管家新建/更新费用申请草稿分别写入 `claim_draft_created` / `claim_draft_updated`;租户从服务端身份透传,事件、Case、Link 与草稿同事务提交,失败整体回滚。动作幂等组合租户、操作人、run ID 与稳定草稿快照:完全相同的 HTTP 保存重放复用同一草稿和事件,并发竞争由稳定聚合 ID 与数据库主键仲裁,同一 run 内的真实内容变化仍保留独立事件。
|
||||||
|
- 2026-07-13(AI 申请身份边界):申请预览快速入口不再接受请求体中的 `user_id`、管理员标记、角色、员工编号或其他身份字段作为授权事实,全部强制绑定服务端会话;伪造管理员身份编辑他人退回申请会返回 400,且原申请与费用事件不发生变化。
|
||||||
|
- 2026-07-13(安全与事务验证):容器内受影响后端定向回归 36 项、Expense Case 前端兼容测试 9 项和 Python `ruff --select F,I` 通过;覆盖本人、当前审批人、财务、管理员、无权限、跨租户、整 Case 安全摘要、草稿新建/更新、失败回滚、HTTP 创建重放、相同快照去重、不同快照留痕、Steward 重放及伪造身份越权。未修改数据库结构,未执行持久化开发数据库迁移。
|
||||||
|
- 2026-07-13(联调边界):当前持久化开发数据库尚无 `auth_sessions`、`expense_cases`、`expense_case_links` 和 `business_events` 表,浏览器登录无法获得有效认证凭证,因此本轮未声称完成真实页面端到端联调,也未擅自执行数据库迁移。计划、消费/票据、入账、对账和复盘事件仍待后续补齐。
|
||||||
|
- 2026-07-14(迁移所有权加固):新增统一 `schema_ownership.py`,七个运行时初始化入口只创建 legacy 表;标准启动在 Alembic upgrade 前执行只读漂移预检,revision 与 migration-owned 表集合不一致时 fail-fast,且不会自动 stamp 或修改数据库。
|
||||||
|
- 2026-07-14(真实迁移验证):在主应用容器连接的一次性 tmpfs PostgreSQL 17 中完成空库升级、重复升级、关键约束/索引、真实外键级联、降级到 base、legacy 哨兵保留、无版本自有表漂移拒绝和再次升级,`test_alembic_migrations.py` 4 项通过,最终 revision 为 `20260713_0002`;临时容器已自动清理,持久化开发数据库复查仍未迁移。
|
||||||
|
- 2026-07-14(剩余边界):当前两条 revision 只覆盖 Expense Case、Business Event 和 Auth Session,完整 legacy schema baseline 及停止其余运行时 DDL 仍未完成;本轮受影响服务回归 46 项通过,既有员工目录历史部门归一化用例仍单独失败,未混入本次迁移安全范围。
|
||||||
|
- 2026-07-14(历史旧单接入):新增独立 `ExpenseCaseLegacyBackfillService` 和 `backfill_legacy_expense_claim_cases.py`。命令只读取显式 `DATABASE_URL`,默认 dry-run;apply 强制目标核验、精确确认、迁移 head、advisory lock 和批次事务。每张旧单只创建一条 `historical_claim_imported` 系统快照,保留源时间和指纹、明确不重建历史,并以 `suppressed` 阻止实时投递。
|
||||||
|
- 2026-07-14(克隆库联调):将持久开发库以只读 `pg_dump` 流式恢复到一次性 tmpfs PostgreSQL 17,原 40 张表、4 张费用单、105 名员工、248 条预算和 62 个 Agent 资产完整保留。升级到 `20260713_0002` 后首次 dry-run 识别 4 张旧单,apply 创建 4 组 Case/Link/Event,重复 apply 创建 0 条;隔离后端完成登录、`/auth/me`、旧单时间线 200 和登出,内部回填字段未出现在 API。临时数据库和隔离进程已清理,持久库复查仍为原数据签名且没有 migration-owned 表。
|
||||||
|
- 2026-07-14(安全复核与质量验证):交叉审查后补齐超长租户幂等键稳定哈希、Link/Case 租户一致性、孤立 Case 冲突、URL 路由参数覆盖防护和部分批次失败进度摘要;前端将快照语义明确为“纳入时状态/节点”。容器内 65 项后端定向测试、11 项前端测试、Ruff 和 Vite 生产构建通过。全库代码体积门禁仍被本次未修改的 `RiskRuleGenerationService` 807 行存量问题阻断,未混入当前功能提交。
|
||||||
|
- 2026-07-14(AI 行为闭环首个切片):新增 `AIDecision`、`AIDecisionFeedback` 和 `WorkflowOutcome` 三类独立事实,把申请预填建议、用户确认或显式字段纠正、草稿保存或申请提交结果通过稳定 `decision_id` 关联。只有已认证的申请预览快速入口显式传入服务端 `CurrentUserContext` 时才写账本;通用 User Agent、模板预览和单据详情编辑路径均不写入,避免伪造身份和非 AI 操作污染样本。
|
||||||
|
- 2026-07-14(字段纠正与隐私边界):申请表编辑器在当前会话中维护首次建议值与最终显式编辑值,多次修改保留最初建议,改回原值会移除差异;日期调整同时记录 `time` 与派生 `days`。服务端最终值始终从解析后的申请 facts 重建,不信任客户端 `finalValue`;持久化层只保存字段名和带密钥版本的 HMAC-SHA256 值指纹,不复制事由、地点、人员、金额等原始值,原始对话、Prompt、模型全文、附件和支付敏感信息也不进入学习账本。
|
||||||
|
- 2026-07-14(可信度边界):浏览器编辑轨迹仍标记为 `verification_status=client_observed`、`trust_level=behavioral_analytics_only` 且 `training_eligible=false`,只能用于受限产品统计;服务端签发预览消费后提升为 `server_verified`,仅证明“服务端签发快照与最终业务事实的差异已核验”,不证明浏览器声称的模型来源,也仍不直接训练模型、自动放行风险或激活企业记忆。
|
||||||
|
- 2026-07-14(事务与一致性):学习三表与申请、预算及 Business Event 使用同一 Session,在最终 commit 前 `flush`,失败整体回滚;提交场景通过提交服务的事务内回调关联真实 `application_submitted` 事件。Case、Business Event 和 Decision 使用包含 `tenant_id + expense_case_id` 的复合外键,既拒绝跨租户,也拒绝同租户跨 Case 串联。前端估算使用消息级请求版本和输入指纹,只合并估算派生字段,旧响应不会覆盖后续编辑。
|
||||||
|
- 2026-07-14(0003 迁移与验证):新增 `20260714_0003_ai_learning_loop.py`,三张学习账本表纳入集中迁移所有权;AgentRun 与旧 ExpenseClaim 保持带索引软引用以兼容空库迁移顺序。一次性 tmpfs PostgreSQL 17 的升级、重复升级、跨租户/同租户跨 Case 外键拒绝、降级和再次升级 4 项通过;容器内学习账本、入口边界和迁移所有权定向 29 项,日期联动及异步估算关键场景 5 项通过。持久开发库只读复查仍为 `40|4|105|248|62`,7 张 migration-owned 表数量为 0。
|
||||||
|
- 2026-07-14(服务端预览决策):新增认证的 `POST /api/v1/reimbursements/application-previews`。服务端从原始申请文本和当前登录身份重新解析字段、重跑规则测算,再签发 30 分钟有效的 `decision_id`;浏览器返回的模型来源和建议值不被直接认证。预览绑定租户、actor、登录会话和 conversation,字段只保存独立版本密钥生成的 HMAC 指纹;字段存在性也纳入指纹,新增、删除和改值都能由服务端识别。
|
||||||
|
- 2026-07-14(消费与续签):快速保存/提交显式携带动作类型、`decision_id` 和稳定请求 ID。服务端锁行校验后,在 Claim、Expense Case、Business Event、AIDecision、Feedback、Outcome 的同一事务中一次消费;当前会话已有有效决策时禁止省略 ID 降级。保存草稿提交成功后再尽力签发基于服务端事实的新 `decision_id`,续签失败只返回无新 ID,不反转已成功业务动作;相同请求安全重放不会重复建单或重复写学习账本。
|
||||||
|
- 2026-07-14(0004 迁移与验证):新增 `20260714_0004_ai_application_preview_decisions.py`,预览决策表和正式 Decision 复合租户外键纳入集中迁移所有权。一次性隔离 PostgreSQL 17 完整 upgrade、重复 upgrade、约束/索引、downgrade 和再次 upgrade 4 项通过;容器内新增决策安全用例 9 项、旧快速保存/提交 4 项、申请学习账本 9 项、迁移与所有权 26 项通过且条件型 PostgreSQL 用例 1 项跳过,3 组前端定向测试及 Vite 生产构建通过。持久开发库只读确认 8 张 migration-owned 表数量仍为 0。
|
||||||
|
- 2026-07-14(多入口统一决策闭环):抽取 `ExpenseApplicationPreviewWorkflow`,认证签发、锁行消费、动作幂等、学习落账和草稿续签不再由 HTTP 端点重复编排。Steward、小财管家结构化预览和通用 Orchestrator 均复用该工作流;用户纠正字段后仍消费原始 decision,使服务端能够把建议值与最终业务事实记为 `server_verified` 接受或纠正证据。
|
||||||
|
- 2026-07-14(Orchestrator 身份与会话边界):外部用户消息、定时任务和系统事件统一强制认证,`schedule` / `system_event` 进一步要求平台管理员;登录态覆盖请求中的身份、租户和调度操作人别名,堵住匿名或普通用户伪造来源触发 Hermes 管理任务的路径。会话创建、恢复和删除同时绑定可信租户与用户名,历史无租户状态会话 fail-closed。申请 decision 只从服务端会话恢复,客户端 preview/decision 在进入编排前被移除;保存草稿后的 next decision 回写会话并支持刷新/换端恢复结构化预览。
|
||||||
|
- 2026-07-14(小财管家 fail-closed):完整预览展示前先使用稳定 request ID 签发 canonical decision;签发失败时允许继续编辑和以同一 ID 重试,但禁止保存/提交且不回退旧副作用链路。保存/提交失败重试复用稳定 action request ID,保存成功同步草稿信息与 next decision。
|
||||||
|
- 2026-07-14(decision 生命周期加固):同一租户、用户、登录会话、conversation 和 HMAC 快照即使使用不同签发 request ID,也复用当前 active decision,避免多个并行 decision 命中草稿幂等捷径后遗留未消费状态。动作发现 decision 过期、已消费或不可用时,前端清空旧 ID、生成新的签发 request ID 并开放重新签发。
|
||||||
|
- 2026-07-14(跨租户检查点加固):Steward 动作会话创建显式写入服务端租户;租户 A/B 即使使用相同用户名、conversation 和 trace,也会获得独立会话与 decision,不再跨租户返回幂等结果或敏感预览内容。
|
||||||
|
- 2026-07-14(统一闭环验证):容器内服务端预览、Steward 动作/图运行、Orchestrator 外部来源授权及决策消费组合回归 50 项通过,Python Ruff F/I 通过;前端结构化动作、会话恢复、工作台路由、富确认和动作脚本共 18 项通过,Vite 生产构建通过。并行读取用户正在变动的规则工作簿曾触发 `openpyxl` ZIP 句柄竞争,相关套件改为串行后全部通过,未修改规则工作簿。
|
||||||
|
- 2026-07-14(个人出行方式记忆):新增租户化 `memory_entries` 与 `memory_evidence_links`,首个切片只存储 `travel_application.transport_mode` 的“飞机/火车/轮船”。同一租户、员工、场景和值需要 3 个不同 Expense Case 的服务端核验纠正、至少 2 个当前审批通过且证据跨 7 天才激活;Candidate/Active 默认有效期分别为 90/180 天。
|
||||||
|
- 2026-07-14(证据信任边界):记忆证据严格绑定当前 tenant、actor、employee、Case owner 和 Claim owner,并要求 Decision、Feedback、Outcome 指向同一申请、同一 Case 和同一个真实 `application_submitted` 业务事件;事件聚合必须是对应 `expense_claim`。同 Case 重放、invalidated/reversed 事实、最新退回、反向纠正和非白名单纠正不会继续支持 active 记忆。
|
||||||
|
- 2026-07-14(应用、抑制与遗忘):个人记忆只补空白出行方式,任何当前显式值都优先。命中后服务端重新计算交通与总额估算再签发 canonical decision;记忆异常降级为无记忆,不阻塞预览。反向或非白名单纠正抑制 active,用户忘记后状态改为 revoked、清空可恢复值和指纹,旧请求不可复活。
|
||||||
|
- 2026-07-14(前端记忆解释):申请核对表新增独立 `TravelReimbursementMemoryPanel`,展示已应用的常用出行方式、证据数量、学习回执和“忘记此偏好”入口;本地会话快照支持跨刷新恢复,忘记偏好不会篡改当前申请字段。
|
||||||
|
- 2026-07-14(个人记忆验证):容器内个人记忆专项 16 项、记忆/预览决策/迁移/所有权组合回归 56 项通过且 1 项条件跳过、前端申请与记忆组合 83 项通过;一次性 PostgreSQL 迁移循环、Ruff、Vite 生产构建和 `git diff --check` 均通过。当前规则中心尚不存在交通方式禁用维度,制度冲突守卫作为后续规则扩展的前置门禁保留,不以恒真占位判断冒充已实现。
|
||||||
|
- 2026-07-16(零录入票据首个闭环):附件后台任务从“大而全”的内存编排中拆出只读 `ExpenseReceiptMatcher` 与可逆 `ExpenseReceiptAssociationService`。系统按当前租户、员工、Expense Case、已审批申请、票据日期、城市、路线和场景选择草稿;只有唯一高置信草稿自动归集,低置信、多候选、无草稿及仅有申请时均以需要确认的安全终态返回并保持零业务写入。
|
||||||
|
- 2026-07-16(事务、幂等与租户边界):票据 Link、`receipt_received`、附件写入和 `attachment_associated` 由同一关联编排器提交,数据库快照与文件目录备份共同补偿中途失败,事件或文件元数据异常时恢复 Claim/Case/Link/Event/票据及旧附件目录;相同票据任务按租户、owner 和票据集合持久去重。票据目录加入无碰撞租户摘要,任务、候选 Claim 和票据同时绑定租户与本人,平台管理员也不能跨租户读取任务。
|
||||||
|
- 2026-07-16(持久任务与并发):新增 migration-owned `attachment_association_jobs` 和 `20260716_0006`。任务状态、结构化结果、owner 上下文、attempt、租约与 generation 写入数据库;GET 可恢复 queued 或租约过期任务,`attempt_count + running` 作为栅栏阻止旧 worker 或迟到回调覆盖新终态。同票据和同 Claim 分别使用进程锁与 PostgreSQL advisory lock 串行化,Claim 锁内清理旧事务并重新匹配,避免不同票据并发选择同一空明细。待确认或失败任务保留原代历史,再次发起创建新 generation 并重新评估;已自动关联成功的代际继续幂等复用。评分改为纯只读查询,每份票据必须独立达到最小证据,避免无关票据被同批强证据带入。
|
||||||
|
- 2026-07-16(小财管家交互与验证):任务结果新增 Case、申请、置信度、原因、异常、缺失项、风险项、候选和草稿载荷;前端可跨会话恢复,自动完成直接查看草稿,仅申请候选查看申请,待确认不伪装成功;幂等重放显示“已关联”,成功结果仍展示风险和复核要求。容器内后端归集专项 20 项、归集与相邻服务/迁移所有权组合回归 71 项、前端关联链路组合回归 29 项、Ruff F/I/UP 和 Vite 生产构建通过;一次性 tmpfs PostgreSQL 17 的 0006 完整迁移循环 4 项通过并已清理,持久开发库未修改。既有大型报销服务与接口套件仍存在旧审批、删除和风控断言失败,未把这些基线问题误报为已解决。
|
||||||
|
- 2026-07-16(保留边界):预算、项目、成本中心和个人记忆偏好尚未接入票据候选评分;任务已持久化并支持租约恢复,但尚未建设独立消息队列、运维重试面板和死信治理;完整 G2 与平台级异步任务治理仍未完成。
|
||||||
|
- 2026-07-17(费用价值闭环):新增 Savings Ledger、CFO 价值分析和财务确认/冲回链路;现金、工时、风险暴露和预计机会严格分账。住宿标准调整从服务端锁定原金额创建机会,付款/生产连接器回执产生 actual realization,独立财务确认后才进入现金 KPI,退款以负向追加事实冲回。
|
||||||
|
- 2026-07-17(财务连接器):建立配置生命周期、HMAC、防重放、事件幂等、对账、ERP 入账、退款/冲回、operational event 和 mock/test/staging 隔离。端到端以本地自签的 production-mode 事件验证申请、票据、报销、预审、审批、回执、ERP、归档、Savings 与商业价值契约;它不代表真实银行/ERP 回执,模拟回执也不会改变核心财务事实。
|
||||||
|
- 2026-07-17(真实发布遥测):规则发布从真实 observation、可信 disposition/reviewer label、独立盲审负样本和保守 recall 进入 Monitor/Guard;collecting、积压、聚合失败或低 precision 均不晋级并保持 stable。0023 提供 audit sample、双人票、append-only 和 PostgreSQL 并发保护。
|
||||||
|
- 2026-07-17(商业闭环):新增套餐、订阅、账期、权益、配额、用量、成本、ROI、毛利和定价走廊;中央 Orchestrator、Runtime Chat、OCR、金融连接器和附件源文件写入按权威数量预占、结算或释放,客户价值、平台收入和内部成本不混账。
|
||||||
|
- 2026-07-17(全链租户安全):新增 0025-0028,统一 Tenant、Employee、Claim、Agent Asset、Knowledge、Ontology、Hermes、Finance Report、Qdrant、文件和缓存作用域;ONLYOFFICE 改为数据库一次性会话与 SSRF/重放保护。审批员工解析按认证企业或结构化 Claim 企业首层过滤,跨租户身份和相同名称不再命中。
|
||||||
|
- 2026-07-17(工程收口验证):176 个后端测试文件在主应用容器内分片或专项通过,费用主服务 121 项通过;fresh PostgreSQL 迁移/并发总探针 `87 passed / 0 skipped / 0 failed`,head 为 0028;Web 全量 `815 passed / 0 failed` 与 Vite build 通过;Mobile lint/typecheck、197 个新增 Python 文件 Ruff、目标 compileall、受门禁核心类/组件 800 行检查和 `git diff --check` 通过。
|
||||||
|
- 2026-07-17(完成边界):工程代码和容器验收已收口;生产 ONLYOFFICE DNS/TLS、备份副本迁移演练、逐租户 SMTP、真实 provider/会计规则、移动/浏览器实机联调、30/90 天企业试点、客户财务签字和合同定价仍必须使用目标环境与真实业务证据完成,不能由 mock 或本地测试替代。总验收见 `document/development/2026-07-17/feature/engineering-closure-and-production-readiness/`。
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
# AI 费用闭环与价值证明 开发 TODO
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
文档路径:document/development/2026-07-13/feature/ai-expense-closed-loop-and-value-proof/TODO.md
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每个 TODO 必须对应 `CONCEPT.md` 中的目标、能力、方案或验收点。
|
||||||
|
- 只有完成实现并验证后,才能把 `[ ]` 改成 `[x]`。
|
||||||
|
- 已完成项必须补充文件、接口、命令、容器测试或真实页面证据。
|
||||||
|
- 如果需求发生变化,先更新 `CONCEPT.md`,再调整本 TODO。
|
||||||
|
- 实施顺序遵循:P0 费用闭环与数据基础 → P1 安全自动化与学习 → P2 费用经营与价值证明 → P3 商业化复制。
|
||||||
|
- 所有后端、集成、迁移和依赖外部服务的验证只允许在项目容器内运行,单条测试命令最大超时 60s。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 背景与问题] 盘点申请、票据、报销、预算、风险、审批、付款、分析、Agent trace、反馈和 AI 数据飞轮现状。
|
||||||
|
证据:`server/src/app/api/v1/endpoints`、`server/src/app/services`、`web/src`、`mobile/app/src`、容器运行时 OpenAPI 共 156 个业务操作。
|
||||||
|
- [x] [CONCEPT: 背景与问题] 确认当前主要断点是消费/取票、真实支付入账、结果回流、节省归因和商业计量,而不是页面数量不足。
|
||||||
|
证据:`CONCEPT.md`“背景与问题”;`expense_claim_approval_flow.py` 付款为状态更新;本地容器关键反馈/样本表聚合结果为 0。
|
||||||
|
- [x] [CONCEPT: 目标与非目标] 确认本功能采用一个总功能点分阶段实施,不一次性替换所有现有接口,不自建支付和商旅供应链网络。
|
||||||
|
证据:`CONCEPT.md`“目标与非目标”。
|
||||||
|
- [x] [CONCEPT: 风险与开放问题] 记录认证、租户、旧模型迁移、反馈污染、虚假节省、自动化失控、连接器和数据稀疏风险。
|
||||||
|
证据:`CONCEPT.md`“风险与开放问题”。
|
||||||
|
## 2. 开发前置门禁:试点与基线
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 待确认] 确认首个目标客户规模、部署形态、购买决策人和最小租户边界。
|
||||||
|
- [ ] [CONCEPT: 待确认] 确认首个 90 天试点费用场景,只允许一个场景进入 P0 开发。
|
||||||
|
- [ ] [CONCEPT: 待确认] 确认是否进入企业支付、商旅预订或公司卡领域,并冻结首期连接器范围。
|
||||||
|
- [ ] [CONCEPT: 待确认] 确认客户 ROI 中现金节省与工时价值的口径、基线窗口和签字人。
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 在 P0 开发前采集人工分钟、退回率、完成周期、人工触点、风险反馈、费用金额和付款结果基线。
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 为 P0/P1/P2 分别冻结一条可纵向跑通的闭环验收,不以“模块代码已完成”代替业务结果。
|
||||||
|
|
||||||
|
## 3. 契约与设计
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 费用领域与编排] 定义 `Expense Case`、申请、票据、报销、审批、付款、凭证和归档的领域边界与迁移关系。
|
||||||
|
证据:`expense_cases.py`、Expense Case/Link/Event 模型、连接器付款/ERP/归档与 Savings 冲回 E2E。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 定义 `expense_cases`、`expense_case_links` 和最小状态机,明确非法状态跃迁。
|
||||||
|
证据:`20260713_0001_expense_case_business_events.py`、`ExpenseCaseService` 与状态/事件回归。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 定义 `business_events` 事件信封、事件词典、correlation ID、幂等键和版本策略。
|
||||||
|
证据:`BusinessEvent` schema/service、业务事件词典及申请/报销/审批/支付/ERP/Savings 回归。
|
||||||
|
- [x] [CONCEPT: 业务事件与 AI 决策] 定义 `ai_decisions`、`ai_decision_feedback` 和 `workflow_outcomes` 契约。
|
||||||
|
证据:`ai_learning.py`、`expense_application_learning.py`、`20260714_0003_ai_learning_loop.py`;三类事实分别表达 AI 建议、用户采纳/显式字段纠正和业务结果,技术执行成功、用户反馈与工作流结果不复用同一状态。
|
||||||
|
- [x] [CONCEPT: 业务事件与 AI 决策] 定义落单前服务端预览决策的签发、授权、过期、一次消费、重放和续签契约。
|
||||||
|
证据:`ai_application_preview.py`、`expense_application_preview_decisions.py`、`expense_application_snapshot.py`;预览不创建空 Case,绑定租户、actor、登录会话与 conversation,30 分钟过期,保存/提交成功后一次消费;保存草稿在业务提交后尽力返回基于服务端事实的新 `decision_id`,续签失败不反转已成功动作。
|
||||||
|
- [x] [CONCEPT: 记忆与学习] 定义并实现 `memory_entries`、`memory_evidence_links`、优先级、有效期、敏感白名单、撤销和遗忘契约;首个切片仅覆盖 `travel_application.transport_mode`。
|
||||||
|
证据:`ai_memory.py`、`expense_application_memory.py`、`expense_application_memory_evidence.py`、`20260714_0005_ai_memory.py`;记忆使用租户、主体、场景、字段和值指纹精确隔离,值只允许“飞机/火车/轮船”,服务异常降级为不应用记忆。
|
||||||
|
- [x] [CONCEPT: 自动化决策] 定义动作风险、金额阈值、置信度、证据完整度、可逆性、抽检率和企业授权策略。
|
||||||
|
证据:`automation_eligibility.py` 与 `test_automation_eligibility.py`;资金、制度和高风险动作始终人控。
|
||||||
|
- [x] [CONCEPT: 节省与价值] 定义 Savings Ledger 的机会、执行、实现、确认、去重和归因状态。
|
||||||
|
证据:Savings 模型、0015 迁移、状态服务与并发/E2E 测试。
|
||||||
|
- [ ] [CONCEPT: 连接器] 定义票据邮箱、税务、企业卡、商旅、支付、银行、ERP、消息和 SSO 连接器协议。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 定义服务端会话、租户数据范围、角色和动作级授权契约。
|
||||||
|
证据:不透明 Bearer Session、`CurrentUserContext`、租户访问策略和 endpoint role dependencies;0025-0028 多租户安全迁移。
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 完成首个试点指标字典、基线采集方案、分子分母、数据源和负责人。
|
||||||
|
- [x] [CONCEPT: 方案设计] 完成分阶段架构评审,确认新增 service 不继续堆入 `ExpenseClaimService`、`UserAgentService` 或大型前端 composable。
|
||||||
|
证据:访问策略、员工解析、附件、预审、Savings、商业、连接器、发布遥测及前端 composable 均按职责拆分;受门禁核心类/组件 800 行检查通过。
|
||||||
|
|
||||||
|
## 4. P0 后端实现:费用闭环与数据基础
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 权限与安全] 实现服务端可验证会话/JWT,移除客户端身份头作为授权事实来源。
|
||||||
|
证据:`auth_sessions.py`、`auth_session.py`、`deps.py`、`auth.py`、`authSessionStorage.js`;登录签发不透明 Bearer token,数据库仅保存 SHA-256 摘要,生产代码不再信任 `X-Auth-*`,过期/撤销/伪造会话回归测试通过。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 为管理面、Bootstrap、Settings、模型连通性、缓存、审计和系统日志补齐平台管理员保护。
|
||||||
|
证据:`bootstrap.py`、`settings.py`、`audit_logs.py`、`agent_traces.py`、`system_logs.py`、`vite.config.js`;已初始化 Bootstrap 脱敏且拒绝匿名重配,平台管理员/业务经理权限边界和 Vite Setup 锁测试通过。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 继续盘点并收口风险规则发布、制度发布及其他尚未纳入本轮的敏感动作,按动作定义平台管理员或双人复核权限。
|
||||||
|
证据:Agent Asset/Rule Editor/Reviewer、发布盲审双人票、风险豁免独立决定、平台资产只读和 ONLYOFFICE 写入权限均有服务端守卫与安全测试。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 为本轮新增表及 Claim、Employee、Agent Asset、Knowledge、Ontology、Hermes 和 Report 等纳入范围的共享核心数据补齐 `tenant_id`、约束、查询守卫和默认租户迁移。
|
||||||
|
证据:0025-0028 迁移、复合租户外键、首层 SQL 过滤和 tenant security 测试;fresh PostgreSQL 迁移总探针通过。
|
||||||
|
- [ ] [CONCEPT: 权限与安全] 将 `agent_conversations` 等仍依赖 JSON tenant 的 legacy 状态迁移到结构化租户列和数据库约束。
|
||||||
|
证据要求:后继迁移、历史回填、复合约束和跨租户回归;当前只能由服务层校验 `state_json.tenant_id`,不得表述为数据库边界已完成。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 为 Expense Case 查询提供面向用户的精简事件 DTO,移除幂等键、correlation、causation 和 Outbox 投递字段,并明确审批意见、退回原因和操作人可见范围。
|
||||||
|
证据:`expense_case.py`、`test_expense_case_endpoints.py`;用户态响应只保留安全流程摘要,关联资源 ID 和未知嵌套 payload 被递归过滤,本人、当前审批人、财务、管理员、无权限及跨租户边界测试通过。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 为 Qdrant collection/namespace、对象存储前缀和缓存键补齐租户隔离回归测试。
|
||||||
|
证据:Knowledge tenant scope、RAG workspace、Qdrant namespace、票据/附件路径、运行缓存和财务快照 tenant fingerprint 回归。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 建立 Alembic baseline 和正式迁移链,停止请求路径运行 DDL。
|
||||||
|
证据:migration ownership/preflight 与 0001-0028 正式链;fresh PostgreSQL 完整 upgrade/downgrade/re-upgrade 通过,请求路径不再创建 migration-owned 表。
|
||||||
|
- [x] [CONCEPT: 兼容策略] 集中 migration-owned 表所有权并在标准启动迁移前执行只读漂移预检,禁止 legacy bootstrap 越权建表。
|
||||||
|
证据:`schema_ownership.py`、`migration_preflight.py`、`server_start.sh`、`test_migration_preflight.py`、`test_schema_ownership.py`;无版本自有表、缺表、多表、未知/多 revision 均 fail-fast,不自动 stamp 或修改数据库。
|
||||||
|
- [x] [CONCEPT: 费用领域与编排] 新增 `ExpenseCaseService` 和费用事件查询接口,保持编排与具体职责分离。
|
||||||
|
证据:`server/src/app/services/expense_cases.py`、`server/src/app/api/v1/endpoints/expense_cases.py`、`GET /api/v1/expense-cases/by-claim/{claim_id}`;容器 OpenAPI 校验通过。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 新增 `expense_cases`、`expense_case_links` 和 `business_events` 表及迁移。
|
||||||
|
证据:`20260713_0001_expense_case_business_events.py`;一次性 PostgreSQL 17 已验证表、唯一约束、复合索引、外键级联、降级和再次升级。
|
||||||
|
- [x] [CONCEPT: 业务事件与 AI 决策] 新增 `ai_decisions`、`ai_decision_feedback`、`workflow_outcomes` 表及迁移。
|
||||||
|
证据:`20260714_0003_ai_learning_loop.py`、`schema_ownership.py`、`migration_preflight.py`;三表具备租户幂等约束,Case/Event/Decision 使用包含费用 Case 的复合租户外键。一次性 PostgreSQL 17 完整升级、重复升级、跨租户及同租户跨 Case 外键拒绝、降级和再次升级 4 项通过,持久开发库未迁移。
|
||||||
|
- [x] [CONCEPT: 业务事件与 AI 决策] 新增服务端预览决策表、认证签发接口和正式 Decision 关联迁移。
|
||||||
|
证据:`20260714_0004_ai_application_preview_decisions.py`、`expense_application_previews.py`、`reimbursements.py`;动作消费与 Claim、Case、Business Event 和学习三表同事务,跨登录会话重放被拒绝;独立版本密钥生成的 HMAC 指纹不保存低熵字段明文,并编码字段存在性。只有从未成功签发的旧调用可无 ID 降级,当前会话已有有效决策时省略 ID 会返回 409。
|
||||||
|
- [ ] [CONCEPT: 数据与契约] 为申请、票据、草稿、提交、退回、审批、付款和归档接入统一 correlation ID。
|
||||||
|
当前进度:报销显式预审与提交已共享 correlation/causation,AI 草稿和票据归集已透传租户与稳定幂等键;申请、审批、付款和外部连接器仍需继续统一。
|
||||||
|
- [ ] [CONCEPT: 业务事件与 AI 决策] 建立事务 Outbox:申请、提交、退回、审批、支付和入账状态与事件同事务提交,消费端按事件 ID 幂等处理。
|
||||||
|
- [x] [CONCEPT: 业务事件与 AI 决策] 完成首批草稿、提交、退回、审批、申请转报销、付款和申请归档事件旁写,具备 correlation、causation、幂等键及事务回滚。
|
||||||
|
证据:`expense_claim_draft_flow.py`、`expense_claims.py`、`expense_claim_approval_flow.py`、`test_expense_case_service.py`;容器测试覆盖同 Case 关联、重复事件去重及 Outbox 失败整体回滚。
|
||||||
|
- [x] [CONCEPT: 风险与预审] 建立申请与报销提交前服务端预审握手,使用稳定 review ID、输入/规则/动态结果指纹、三态决策和结构化整改动作阻止旧预审复用。
|
||||||
|
证据:`expense_claim_pre_review_decision.py`、`expense_claim_pre_review.py`、`expense_claims.py`、`reimbursement_pre_review.py`;提交前始终重算完整风险上下文,`needs_fix` 在预算占用前保持草稿并返回结构化 409,握手期间风险变化返回 `PRE_REVIEW_CHANGED`,相同输入、规则和 findings 重试只保留一条预审事件。申请与报销使用同一契约:提交人可整改风险必须先处理,人工判断或预算治理风险才进入领导与 P8。
|
||||||
|
- [x] [CONCEPT: 统一费用事件] 补齐票据归集 → 预审 → 提交切片的阶段与事件衔接,并修复非默认租户在 AI 草稿、预览和 Steward 链路回落到 default 的问题。
|
||||||
|
证据:`expense_receipt_association.py`、`expense_claim_approval_flow.py`、`expense_claim_draft_flow.py`、`expense_claim_review_preview.py`、`steward_action_executor.py`;申请批准生成空报销草稿后 Case 保持 `approved_to_spend`,票据关联后进入 `claiming`,跨租户 Case/Event 不能串线。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 将 Claim 核心查询和关联报销后台任务纳入服务端租户范围,拒绝同用户名跨租户读取、修改、审批或查询任务。
|
||||||
|
证据:`expense_claim_tenant_scope.py`、`expense_claim_access_policy.py`、`linked_reimbursement_draft_jobs.py`;默认租户仅为无 CaseLink 的历史单保留兼容,后台任务强制覆盖客户端 tenant,关联申请按 CaseLink 与租户共同校验。
|
||||||
|
- [x] [CONCEPT: 业务事件与 AI 决策] 收口 AI 新建申请直接提交入口,统一复用申请提交事务,并消除预算 metadata 检查对外层事务的隐式提交。
|
||||||
|
证据:`user_agent_application.py`、`budget.py`、`test_reimbursement_endpoints.py`、`test_user_agent_service.py`;容器测试覆盖有效预算预占、`application_submitted` 事件、提交风险标记、事件失败整体回滚及保存草稿无提交副作用。
|
||||||
|
- [x] [CONCEPT: 业务事件与 AI 决策] 为 AI 工作台和小财管家申请草稿补齐新建/更新事件、租户透传、事务回滚和动作幂等。
|
||||||
|
证据:`expense_application_draft_events.py`、`user_agent_application.py`、`reimbursements.py`、`steward_action_executor.py`、`test_user_agent_application_draft_events.py`、`test_reimbursement_endpoints.py`、`test_steward_action_executor.py`;完全相同 HTTP 保存重放复用同一草稿和事件,同一 run 内不同快照分别留痕,事件失败后草稿与 Case 数据整体回滚。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 将 AI 申请预览快速入口的用户、租户、角色与管理员身份强制绑定到服务端会话,拒绝请求体伪造身份编辑他人申请。
|
||||||
|
证据:`reimbursements.py`、`test_reimbursement_endpoints.py`;对抗用例修复前返回 200,修复后返回 400,且目标申请和费用事件保持不变。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 收口通用 Orchestrator 用户消息和会话管理的认证边界,拒绝客户端身份与 decision 注入。
|
||||||
|
证据:`orchestrator.py`、`agent_conversations.py`、`orchestrator_expense_application_workflow.py`、`steward_graph_action_runtime.py`、`test_orchestrator_auth_endpoints.py`、`test_steward_action_executor.py`、`test_orchestrator_review_flow.py`;用户消息、定时任务和系统事件无认证均返回 401,普通用户触发 `schedule` / `system_event` 返回 403,登录态覆盖请求身份和调度操作人别名;Orchestrator 会话创建/恢复/删除及 Steward 幂等检查点同时校验租户与用户名,申请动作只消费服务端会话 decision。
|
||||||
|
- [x] [CONCEPT: 兼容策略] 建立迁移桥接:服务启动先执行 Alembic,旧 metadata bootstrap 排除 migration-owned 表。
|
||||||
|
证据:`server_start.sh`、`schema_ownership.py`、`migration_preflight.py`、`20260713_0001_expense_case_business_events.py`、`20260713_0002_auth_sessions.py`;容器 Shell/静态检查及一次性 PostgreSQL 完整 upgrade/downgrade/re-upgrade 通过。
|
||||||
|
- [x] [CONCEPT: 兼容策略] 为迁移前已有 `ExpenseClaim` 提供显式、幂等且不伪造办理历史的费用事件快照回填。
|
||||||
|
证据:`expense_case_legacy_backfill.py`、`maintenance_database_target.py`、`backfill_legacy_expense_claim_cases.py`;默认 dry-run,apply 强制租户/截止时间、精确目标、迁移 head、advisory lock 和批次事务,事件使用真实回填时间、`history_reconstructed=false` 与 `delivery_status=suppressed`。一次性克隆库首次创建 4 组 Case/Link/Event,重复 apply 创建 0 条。
|
||||||
|
- [ ] [CONCEPT: 兼容策略] 正式切换前以 shadow 事件校验现有 `ExpenseClaim` 映射;切换后禁止关键事件可丢弃写入。
|
||||||
|
- [ ] [CONCEPT: 兼容策略] 制定旧 `ReimbursementRequest` 只读兼容、迁移和停止新增编排的计划。
|
||||||
|
- [ ] [CONCEPT: 兼容策略] 把新增审批、付款、归档和关系事件移出 `risk_flags_json`,保留旧数据读取兼容。
|
||||||
|
- [ ] [CONCEPT: 数据与契约] 补齐撤回、取消、驳回、作废、补件、支付失败、对账异常和归档状态。
|
||||||
|
- [x] [CONCEPT: 连接器] 实现财务连接器统一事件信封、认证、幂等、重试、错误状态和回执事件。
|
||||||
|
证据:`financial_connector_auth.py`、`financial_connector_ingestion.py`、`payment_reconciliation.py`、配置生命周期及 operational events;服务/API/并发测试通过。
|
||||||
|
- [ ] [CONCEPT: 连接器] 实现支付批次、回执、重复付款防护、ERP 凭证和对账的内部契约,首期允许 mock connector 但不得再只写单一“已付款”状态。
|
||||||
|
- [x] [CONCEPT: 降级策略] 将附件关联和关联报销草稿后台任务迁为可持久化、可恢复、可幂等的任务状态。
|
||||||
|
证据:新增 migration-owned `attachment_association_jobs` 与 `20260716_0006`;任务使用租户、owner、票据集合和 generation 去重,运行态带租约与 `attempt_count + running` 栅栏,GET 可恢复 queued/租约过期任务。同票据和同 Claim 均由进程锁与 PostgreSQL advisory lock 串行化,Claim 锁内重新匹配;待确认或失败历史保留原代并以新 generation 重新评估,自动关联成功代继续幂等复用。进程状态清空、租约过期、旧 worker 回写、不同票据并发同 Claim 和代际重评估回归通过。
|
||||||
|
- [x] [CONCEPT: 零录入附件关联任务契约] 扩展附件关联任务的向后兼容结果契约,区分自动完成与需要确认,并返回 Case、申请、置信度、原因、异常、缺失项、风险项和候选。
|
||||||
|
证据:`attachment_association_job.py` 与 `attachmentAssociationJobModel.js` 保留旧字段并追加结构化结果;`status=succeeded + resolution=requires_confirmation` 明确表示匹配完成但零业务写入。
|
||||||
|
- [x] [CONCEPT: 零录入报销首个切片] 新增独立票据匹配器,按当前租户、员工、Expense Case、已审批申请、日期、城市和场景计算候选;唯一高置信才允许自动归集。
|
||||||
|
证据:`expense_receipt_matcher.py` 只读评分;覆盖唯一高置信、相近候选、无草稿、仅有已审批申请和已关联重放,申请缺少系统草稿时只返回 `approved_application` 候选。
|
||||||
|
- [x] [CONCEPT: 零录入报销首个切片] 新增独立归集编排器,把票据 Link、`receipt_received`、附件写入和 `attachment_associated` 纳入同一事务,并保证同票据重试不重复建明细或事件。
|
||||||
|
证据:`expense_receipt_association.py` 统一编排数据库写入,文件系统使用元数据快照和附件目录补偿;稳定幂等键保证相同票据重试只增加跳过数。事件写入失败回归证明 Claim、Case、Link、Event、票据元数据和附件最终状态均回退。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 将票据夹存储命名空间和附件关联后台任务授权同时绑定租户与用户,补充同用户名跨租户隔离回归。
|
||||||
|
证据:`receipt_folder.py` 使用租户与用户联合命名空间,任务状态绑定 `owner_tenant_id + owner_username`;普通用户及平台管理员跨租户查询均返回 404,同用户名跨租户不能读取票据。
|
||||||
|
|
||||||
|
## 5. P0 前端实现:一键报销与真实移动端
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 统一费用事件] 在申请/报销详情接入已有真实 Expense Case 事件时间线,展示申请提交、报销提交、退回、审批、自动生成报销草稿、付款和归档事件。
|
||||||
|
证据:`TravelRequestExpenseCaseTimeline.vue`、`useExpenseCaseTimeline.js`、`expenseCases.js`、`expenseCaseTimeline.js`;保留原有横向进度,404 与接口异常均安全降级,容器内 99 条定向前端回归和 Vite 生产构建通过。
|
||||||
|
- [ ] [CONCEPT: 统一费用事件] 补齐计划、消费/票据、入账、对账和复盘事件,并在真实迁移数据库上跑通完整时间线。
|
||||||
|
- [ ] [CONCEPT: 统一费用事件] 自动匹配申请、预算、票据、费用类型、项目、成本中心和常用字段。
|
||||||
|
首个切片先交付申请、票据、费用事件、日期、城市和费用场景匹配;预算、项目、成本中心和记忆偏好随后接入同一候选契约。
|
||||||
|
- [ ] [CONCEPT: 统一费用事件] 增加“需要确认”区,只展示低置信、缺失或冲突字段。
|
||||||
|
首个切片在小财管家附件关联结果中复用统一异常模型;后续再收口全屏助手和票据夹入口。
|
||||||
|
- [x] [CONCEPT: 零录入报销首个切片] 在小财管家实现“上传并发送 → 自动归集已审批申请生成的草稿 → 查看草稿”,不再次上传票据;低置信时只展示候选和异常卡片。
|
||||||
|
证据:附件任务 composable 将待确认视为安全终态,不发送伪成功通知;自动完成提供“查看草稿”,申请候选提供“查看候选申请”,不伪造报销草稿。
|
||||||
|
- [x] [CONCEPT: 零录入附件关联任务契约] 会话刷新后恢复新任务结果、候选和异常状态,旧任务结果仍可正常展示。
|
||||||
|
证据:`workbenchAiMessageModel.js` 持久化新协议,规范化层兼容旧字段;容器内前端票据关联、协议、卡片、幂等重放、风险复核和会话恢复组合回归 29 项通过。
|
||||||
|
- [x] [CONCEPT: 风险与预审] 报销详情提交前先执行真实预审,`needs_fix` 直接展示结构化风险并停止提交,其他结果携带 review ID 与输入指纹完成握手。
|
||||||
|
证据:`reimbursements.js`、`api.js`、`useTravelRequestDetailRiskSubmit.js`;错误响应保留 status/code/detail/review,风险可即时回填并触发详情刷新,前端预审、409、请求头、时间线组合测试 24 项和 Vite 生产构建通过。
|
||||||
|
- [ ] [CONCEPT: 风险与预审] 风险卡提供一键补件、修正、解释和重新预审入口。
|
||||||
|
- [ ] [CONCEPT: 统一费用事件] 退回后直接定位问题字段,支持断点续办,不要求重新发起对话。
|
||||||
|
- [ ] [CONCEPT: 移动端] 接通真实登录恢复、路由守卫、报销列表、详情、草稿、上传和审批 API。
|
||||||
|
- [ ] [CONCEPT: 移动端] 完成拍照/相册 → OCR → 票据夹 → 费用事件 → 报销草稿真实闭环。
|
||||||
|
- [ ] [CONCEPT: 移动端] 删除或禁用没有真实行为的报销、审批、发送和语音主按钮。
|
||||||
|
- [ ] [CONCEPT: 前端] 修复表格整行点击键盘不可达、弹窗焦点管理、焦点环和移动触控目标。
|
||||||
|
- [ ] [CONCEPT: 前端] 移除页面级全局 `transform: scale()`,使用真实响应式布局承载费用闭环页面。
|
||||||
|
|
||||||
|
## 6. P1 算法与规则实现:安全自动化与持续学习
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 自动化决策] 实现 L0-L5 动作级自动化等级和资格计算器。
|
||||||
|
证据:`AutomationEligibilityCalculator` 将 L0-L4 资格与 L5 人控分开计算,不直接执行动作;单元测试覆盖保守降级。
|
||||||
|
- [x] [CONCEPT: 自动化决策] 为每个可自动动作实现硬白名单、金额上限、证据要求、抽检率和企业上限。
|
||||||
|
证据:系统硬白名单与企业白名单取交集;资金、制度和高风险动作永远人控,金额、置信度、证据、历史精度、样本和抽检任一不足即降级。
|
||||||
|
- [x] [CONCEPT: 风险与预审] 统一风险输出为事实、规则、证据、判断、建议动作和降级原因。
|
||||||
|
证据:结构化 pre-review findings、平台风险投影、整改动作、historical evidence 和 fail-closed 原因已用于申请/报销提交与审批风险卡。
|
||||||
|
- [x] [CONCEPT: 记忆激活] 为首个个人出行方式切片实现 candidate/active/suppressed/expired/revoked 记忆状态机。
|
||||||
|
证据:Candidate/Active 默认有效期分别为 90/180 天;反向或非白名单纠正抑制已激活记忆,过期后新证据创建新 generation,忘记后清值并阻止旧请求复活。
|
||||||
|
- [x] [CONCEPT: 记忆激活] 实现用户、部门、企业记忆优先级、冲突解释、时间衰减和最小样本要求。
|
||||||
|
证据:首个切片限定 `travel_application.transport_mode`,按当前输入/规则 > 企业 > 部门 > 个人解析;组织记忆由管理员显式激活并受 30-365 天有效期约束,个人记忆继续执行 3 Case、2 次审批通过、跨 7 天门槛和时间衰减。同优先级不同值 fail-closed,低优先级覆盖关系返回脱敏解释,过期、撤销和并发换代均不会继续应用。
|
||||||
|
- [x] [CONCEPT: 记忆与学习] 新增租户化个人记忆与证据链接迁移,唯一键保证同一 Expense Case 对同一候选最多计一票。
|
||||||
|
证据:记忆、证据、Decision、Feedback、Outcome、Expense Case 和 Claim 采用租户复合外键;证据同时校验 actor、员工和 Case/单据 owner,跨租户或跨主体关系不能计票。
|
||||||
|
- [x] [CONCEPT: 记忆激活] 为常用出行方式实现隐式激活门槛:3 个不同 Case 的一致服务端纠正、至少 2 次审批通过、证据跨 7 天;草稿、client_observed、accepted 和重试不激活。
|
||||||
|
证据:只有当前有效的 `server_verified` 编辑、`application_submitted` 结果和最新审批状态参与计算;提交事件还必须绑定对应 `expense_claim` 与 Claim ID,invalidated/reversed 事实、同 Case 重放和旧请求均被排除。
|
||||||
|
- [x] [CONCEPT: 记忆与学习] 在服务端申请预览签发前只为空白出行方式应用 active 个人记忆,并返回 memory id、证据数与来源;当前输入、主数据和企业规则优先。
|
||||||
|
证据:`expense_application_preview_workflow.py` 应用记忆后重新执行规则测算和签名;仅缺出行方式时前端仍请求服务端,命中记忆后可直接生成完整核对表。
|
||||||
|
- [x] [CONCEPT: AI 记忆与自动化设置] 提供当前用户记忆查询与“忘记此偏好”接口;撤销后清除可恢复值并在后续预览中不再应用。
|
||||||
|
证据:`GET /api/v1/expense-application-memories/me` 与 `DELETE /api/v1/expense-application-memories/{id}` 均绑定当前租户和主体,越权以 404 隐藏资源存在性。
|
||||||
|
- [ ] [CONCEPT: 记忆与学习] 当规则中心新增按职级、路线或金额禁用交通方式的制度维度时,在个人记忆应用前接入显式允许性判定、冲突抑制原因和回归测试。
|
||||||
|
- [x] [CONCEPT: 记忆与学习] 为 AI 申请预填记录用户原样采纳、显式字段修改和草稿/提交结果证据。
|
||||||
|
证据:`expenseApplicationDecisionFeedback.js`、`useApplicationPreviewEditor.js`、`expense_application_learning.py`、`expense_application_preview_decisions.py`;改回原建议会清除字段差异,日期联动同步记录天数,最终值由服务端 facts 重建。旧预览保持 `client_observed`;服务端签发预览由版本化 HMAC 快照与最终 facts 逐字段比对,字段新增、删除或改值都标记为 `server_verified` 编辑。两者均保持 `training_eligible=false`,尚不直接训练模型或激活记忆。
|
||||||
|
- [x] [CONCEPT: 记忆与学习] 将小财管家、Steward 与通用 Orchestrator 的申请预览切换到认证签发与消费链路,并把结构化预览失败策略收口为 fail-closed。
|
||||||
|
证据:`expense_application_preview_workflow.py`、`orchestrator_expense_application_workflow.py`、`steward_action_executor.py`、`useTravelReimbursementApplicationPreviewActions.js`;签发/动作请求 ID 可稳定重试,草稿续签回写服务端会话,字段接受/纠正按 `server_verified` 落账,未签发结构化预览不能保存或提交。
|
||||||
|
- [ ] [CONCEPT: 记忆与学习] 从字段接受/修改/拒绝、退回、审批覆盖、付款和审计结果生成记忆证据。
|
||||||
|
- [x] [CONCEPT: 记忆与学习] 将已确认 few-shot 扩展到报销预审和审批辅助,并按租户、场景、制度版本过滤。
|
||||||
|
证据:`few_shot_ingestion.py`、`few_shot_retrieval.py`、`expense_claim_historical_evidence.py`、`expense_claim_pre_review.py`;样本关系库和 Qdrant 同时绑定租户、场景、制度与规则版本,检索命中后再由关系库校验,旧版本标记 stale,依赖异常返回空证据。公开 `historical_case_evidence` 与 AI 助手只显示“历史已确认/历史误报,仅供复核”的固定脱敏摘要,不暴露 sample ID、单号、人工评论或结论原文,也不改变确定性结论、阻断数量、预算复核和审批路由。
|
||||||
|
- [x] [CONCEPT: 风险与预审] 完成 golden case、Prompt/规则版本、Canary、回归门禁和自动回滚的工程闭环。
|
||||||
|
证据:Golden evaluator、版本化资产、真实 observation/label、盲审负样本、Release Monitor/Guard、Canary 与 stable 回滚组合测试通过;生产效果仍由试点章节单独验收。
|
||||||
|
- [x] [CONCEPT: 自动化决策] 实现默认 shadow、按动作开放 L3、L4 单独受控的代码策略。
|
||||||
|
证据:Automation Policy 的 `release_stage`、企业上限、硬白名单和 L4 资格检查;资金/制度/高风险动作不进入自动执行。
|
||||||
|
- [ ] [CONCEPT: 自动化决策] 在真实企业按周运行 shadow 并依据签字阈值逐项开放 L3/L4。
|
||||||
|
证据要求:生产样本、抽检、回滚演练和企业授权;本地代码测试不得替代。
|
||||||
|
- [x] [CONCEPT: 降级策略] 对模型、OCR、Qdrant、连接器和记忆服务实现稳定降级和可观测状态。
|
||||||
|
证据:Runtime Chat attempt、OCR worker、Knowledge RAG、连接器 operational events/health、记忆 fail-closed 与发布告警均有明确失败状态和回归。
|
||||||
|
|
||||||
|
## 7. P1 前端实现:审批例外与 AI 记忆
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 审批例外工作台] 按风险、金额、预算影响、等待时长和证据完整度排序审批事项。
|
||||||
|
证据:`GET /api/v1/approval-workbench/items` 由 `ApprovalWorkbenchService` 统一生成可解释优先级、风险/SLA/预算/金额/证据分项和排序原因;审批中心使用该投影,不再用前端临时状态覆盖服务端乐观并发状态。
|
||||||
|
- [x] [CONCEPT: 审批例外工作台] 展示预算压力、风险证据、材料完整度、历史标签和简短 AI 复核建议。
|
||||||
|
证据:工作台返回证据完整度、缺失证据、预算影响、风险摘要、历史确认标签及 `advisory_only` AI 建议;详情页风险卡读取持久化证据、政策依据、贡献度和决策轨迹,并明确禁止从风险卡直接审批。
|
||||||
|
- [ ] [CONCEPT: 审批例外工作台] 补齐统一必要性摘要、完整政策/相似单证据和可编辑审批意见草稿。
|
||||||
|
- [x] [CONCEPT: 审批例外工作台] 为批准、退回和付款建立幂等动作协议与乐观前置条件。
|
||||||
|
证据:动作请求冻结 `request_id`、`expected_status` 和 `expected_approval_stage`;服务端以租户 + 操作人 + 请求 ID 唯一账本、请求指纹、advisory lock、Claim 行锁和同事务事件保证同请求安全重放,不同内容或过期快照返回 409。首次完整 API 响应以不可变快照落账,单据后续推进后重放仍只返回首次结果,不读取或泄露后续状态。
|
||||||
|
- [x] [CONCEPT: 审批例外工作台] 为风险处置动作建立不可变原响应快照和历史安全重放。
|
||||||
|
证据:`20260716_0012` 为 append-only 风险事件增加 `response_json`;新事件在 INSERT 前原子写入完整响应,重放只返回目标事件版本及以前的事件。0012 前无快照事件从 `after_json` 与 `version <= target` 的审计链安全重建,不读取当前处置投影。
|
||||||
|
- [x] [CONCEPT: 审批例外工作台] 建立高风险门禁与类型化风险处置生命周期。
|
||||||
|
证据:严重/高危可行动风险在任何审批副作用前阻断;确认风险、误报、补件、整改、豁免申请和完成处置具有乐观版本、请求指纹、租户权限、只追加事件和 Claim 共用锁。未持久化的原始高风险仍保守阻断,重大风险持久化失败 fail-closed。
|
||||||
|
- [x] [CONCEPT: 审批例外工作台] 补齐批量审批、委托、转交、加签、会签和超时升级交互。
|
||||||
|
证据:`approval_tasks`/`approval_task_events`、`/api/v1/approval-tasks`、`ApprovalTaskWorkspace.vue` 与 `20260716_0013`;支持数据库分页筛选、节点进入时间 SLA、委托/撤销、永久转交、顺序加签、并行会签、手动/调度升级和逐项独立事务批量结果。服务端动作集合与版本为唯一权限事实,筛选/页码可从详情返回恢复。
|
||||||
|
- [x] [CONCEPT: 审批例外工作台] 完成风险豁免申请、批准、拒绝、到期和审计展示闭环。
|
||||||
|
证据:`20260716_0014`、`risk_waiver_decision_policy.py`、风险读投影与 `RiskWaiverActionDialog.vue`/`RiskWaiverRecord.vue`;申请人不能决定自己的申请,仅在职 finance/executive 可决定,过期 fail-closed,全部事件保留版本、人员、请求 ID、原因和关键前后快照。
|
||||||
|
- [ ] [CONCEPT: 审批例外工作台] 接入真实企微/钉钉/邮件触达和处理结果回写。
|
||||||
|
- [ ] [CONCEPT: AI 记忆与自动化设置] 新增“我的 AI 记忆”,支持来源解释、修改、忘记和关闭个性化。
|
||||||
|
- [x] [CONCEPT: AI 记忆与自动化设置] 在申请核对表展示常用出行方式的记忆来源、证据数量和“忘记此偏好”,并在保存/提交后区分候选记录与已应用回执。
|
||||||
|
证据:`TravelReimbursementMemoryPanel.vue` 与独立样式分片承载记忆解释、学习回执和可访问的忘记入口;会话快照可跨刷新恢复已应用记忆。
|
||||||
|
- [x] [CONCEPT: AI 记忆与自动化设置] 新增企业/部门出行方式记忆管理首个低敏切片。
|
||||||
|
证据:租户管理员可创建、查看、更新和撤销企业/部门记忆,配置 30-365 天有效期;创建、更新、撤销使用请求指纹和稳定幂等键,作用域锁与数据库唯一约束阻止并发静默换代。接口只接受“飞机/火车/轮船”,自由文本、金额、客户、项目、附件和支付信息保持禁用。
|
||||||
|
- [ ] [CONCEPT: AI 记忆与自动化设置] 扩展可配置保留策略、敏感等级目录和动作级自动化上限管理;在新增字段前先完成制度允许性与数据分级评审。
|
||||||
|
- [ ] [CONCEPT: 前端] 展示自动化动作、执行依据、撤销入口、抽检状态和版本信息。
|
||||||
|
|
||||||
|
## 8. P2 实现:费用经营与价值证明
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 节省与价值] 新增 Savings baseline、opportunity、realization、evidence、event 和去重/确认表结构。
|
||||||
|
证据:`models/savings.py`、`20260716_0015_savings_value_ledger.py` 与模型/迁移测试。
|
||||||
|
- [x] [CONCEPT: 节省与价值] 实现预计机会、接受、执行中、实际结果、财务确认、拒绝、过期和冲回的严格状态转换。
|
||||||
|
证据:Savings actions/realization 服务、版本/指纹、canonical benefit 去重与 PostgreSQL 并发测试;风险暴露保持独立护栏,不混入节省金额。
|
||||||
|
- [x] [CONCEPT: 费用分析与节省] 持久化员工、部门、费用类型、城市、项目和流程基线,记录窗口和样本量。
|
||||||
|
证据:`savings_baseline_generation.py` 与 baseline/insight 测试;维度、窗口、样本、算法版本、查询指纹和质量等级均冻结。
|
||||||
|
- [ ] [CONCEPT: 费用分析与节省] 接入租户化供应商、合同价、数量和单位价格事实后生成供应商基线。
|
||||||
|
证据要求:真实供应商主数据和合同/采购事实;当前明确返回 `supplier_dimension_unavailable`,不从模拟应付数据伪造。
|
||||||
|
- [x] [CONCEPT: 费用分析与节省] 实现预算预测、异常归因、重复小额浪费和只读政策模拟准备项。
|
||||||
|
证据:Savings insight budget/analysis/attribution;缺正式反事实时 `estimated_savings=None` 且不创建机会。
|
||||||
|
- [ ] [CONCEPT: 费用分析与节省] 接入真实供应商价格漂移分析。
|
||||||
|
证据要求:租户化合同价、数量、单位价和供应商证据;当前保持 coverage gap。
|
||||||
|
- [x] [CONCEPT: CFO 价值看板] 展示现金节省、工时价值、直通率、风险护栏、节省来源和责任人。
|
||||||
|
证据:CFO API/analytics/dashboard;现金、工时、风险和机会分卡,缺数据使用 collecting/unavailable。
|
||||||
|
- [x] [CONCEPT: CFO 价值看板] 实现部门、项目、费用类型、供应商、城市、时间和单据下钻。
|
||||||
|
证据:CFO filters、`cfoValueSourceLinks.js`、URL 状态恢复与前端回归;供应商无事实时显示缺口而非零。
|
||||||
|
- [x] [CONCEPT: CFO 价值看板] 每项节省支持查看基线、建议、执行、实际结果、确认人和证据。
|
||||||
|
证据:`CfoValueOpportunityDrawer.vue` 与 Savings detail projection;无证据时不开放实际结果登记。
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 生成客户月度 ROI 报告,现金节省与工时价值分开披露。
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 建立节省归因复核、重复收益去重和客户财务签字流程。
|
||||||
|
|
||||||
|
## 9. P3 实现:商业化与规模复制
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 商业计量] 在 P0 最小租户隔离基础上新增套餐、订阅、账期、配额、用量和增值模块授权模型。
|
||||||
|
证据:商业模型、0016/0019/0021/0024 迁移、管理/查询 API 与商业工作台。
|
||||||
|
- [x] [CONCEPT: 商业计量] 按客户和权威 meter 记录模型、OCR、附件存储、连接器、分析任务与模块用量/成本。
|
||||||
|
证据:Orchestrator、Runtime Chat、OCR、附件、连接器 permit/预占/结算;已识别运行入口资源组合 63 项通过。
|
||||||
|
- [x] [CONCEPT: 商业计量] 建立客户 ROI、平台贡献毛利、成本明细和私有部署成本输入模型。
|
||||||
|
证据:`commercial_analytics.py`、商业价值/成本前端;客户价值、平台收入和内部成本分账,多币种不合并。
|
||||||
|
- [ ] [CONCEPT: 目标与非目标] 定义年度基础订阅、用量超额、智能风控、预算经营、价值洞察、企业集成和私有部署包。
|
||||||
|
- [x] [CONCEPT: 目标与非目标] 定价引擎仅允许对财务确认的已实现节省计算可选价值分享上限。
|
||||||
|
证据:`commercial_pricing.py` 使用 confirmed value、成本下限和成功费封顶;实际合同仍需客户签署。
|
||||||
|
- [ ] [CONCEPT: 连接器] 建立 ERP、HR、SSO、支付、电子档案和消息平台标准实施模板。
|
||||||
|
- [ ] [CONCEPT: 权限与安全] 完成删除传播、数据导出、审计增强和私有部署安全验收,不把基础租户隔离留到本阶段。
|
||||||
|
|
||||||
|
## 10. 测试与验证
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 测试方案] 完成 Expense Case/Link、业务事件幂等、租户边界、同事务回滚、申请转报销同 Case 和付款归档事件首批测试。
|
||||||
|
证据:容器内 `pytest -q server/tests/test_expense_case_service.py` 7 项通过;联合差旅主链路定向回归共 24 项通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为 AI 新建申请直接提交补充统一事务回归,覆盖提交成功、事件失败回滚和仅保存草稿三条边界。
|
||||||
|
证据:容器内直接提交定向测试 3 项、申请提交回归 5 项、预算与 Expense Case 回归 13 项通过;相关 Python 文件 `ruff --select F,I` 通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为 Expense Case 状态机、事件账本、AI 决策、结果、记忆、自动化和节省服务补充单元测试。
|
||||||
|
证据:Case/Event、AI learning/memory、`test_automation_eligibility.py`、Savings model/service/API/E2E 与全量后端分片均通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为服务端会话、管理员保护和 Bootstrap 重配置补充首批安全回归测试。
|
||||||
|
证据:`test_auth_session_endpoints.py`、`test_auth_service.py`、`test_bootstrap_security.py`;容器定向测试覆盖 token 摘要、伪造身份头、过期/撤销、登出原子收尾、业务经理越权和初始化后匿名重配置拒绝。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为租户隔离、跨租户访问、规则/制度发布和双人复核等敏感动作补充安全回归测试。
|
||||||
|
证据:Tenant Identity、Agent Asset、Knowledge、Ontology/Employee、Hermes/Report、Steward、Finance Dashboard、Approval、Release Review 与 ONLYOFFICE 安全测试通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为 Expense Case GET 接口补充 owner、审批人、财务、管理员、无权限用户和整 Case 关联事件可见范围的 HTTP 权限测试。
|
||||||
|
证据:`test_expense_case_endpoints.py` 容器内 8 项通过,覆盖跨租户、无 Case、申请与报销关联摘要以及内部字段递归过滤。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为 Alembic baseline、升级、旧数据迁移和回滚边界补充 PostgreSQL 集成测试。
|
||||||
|
证据:fresh PostgreSQL 最终总探针 `87 passed / 0 skipped / 0 failed`,覆盖 62 项迁移、完整降级/再升级、旧结构迁移、复合租户约束、append-only 和有事实回滚保护,head 为 0028。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为当前 migration-owned schema 切片补充一次性 PostgreSQL 集成测试和危险 URL 防误连门禁。
|
||||||
|
证据:`test_alembic_migrations.py` 默认无显式 URL 时跳过,主机和库名必须带 disposable 标记;当前 0012 Head 在 tmpfs PostgreSQL 17 中通过完整迁移验证,覆盖空库升级、重复升级、审批动作账本、风险处置复合租户约束、只追加事件触发器、不可变响应快照及有数据降级拒绝、组织 active 脏数据升级前拒绝、版本化 few-shot 无损降级拒绝、外键级联、base 降级、legacy 哨兵保留、漂移拒绝和再次升级;临时容器自动清理,持久化开发库未被修改。完整 legacy baseline 仍保留在上一条未完成项中。
|
||||||
|
- [x] [CONCEPT: 测试方案] 验证审批动作幂等、任务编排、风险门禁、豁免决定权限和共同锁顺序。
|
||||||
|
证据:容器内本阶段后端回归 `122 passed, 4 skipped`、前端审批/单据中心/风险专项 `60 passed`、Ruff 与生产构建通过;一次性 PostgreSQL 17 空库迁移循环 `1 passed`、审批任务和风险并发 `3 passed`,证明同 request ID 并发只生成一个事件、陈旧版本只有一个胜者、风险重新打开与审批竞争时按 Claim 公共锁读取最新事实。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为连接器幂等、重试、回执、失败恢复、重复付款和对账补充测试。
|
||||||
|
证据:连接器 service/endpoint/config/operational/concurrency 测试覆盖签名、防重放、冲突、错配、失败、ERP、退款和非生产隔离。
|
||||||
|
- [x] [CONCEPT: 测试方案] 跑通申请 → 票据 → 报销 → 预审 → 审批 → 付款 → 入账 → 归档端到端。
|
||||||
|
证据:`test_expense_financial_value_chain_e2e.py` 使用测试密钥自签 production-mode 事件,覆盖 HMAC、ERP posted、独立财务确认、Savings、商业价值和退款冲回契约;不声明真实 provider 或真实现金。
|
||||||
|
- [x] [CONCEPT: 测试方案] 跑通首个个人出行方式切片的 AI 建议 → 用户修改 → 工作流结果 → 记忆激活 → 下次建议变化闭环。
|
||||||
|
证据:容器内记忆、预览决策、迁移与所有权组合回归 56 项通过、1 项条件跳过;一次性 PostgreSQL 迁移循环 1 项通过;前端申请快速预览、个人记忆、Steward 与会话恢复组合 83 项通过,Vite 生产构建通过,Python Ruff F/I 与 `git diff --check` 通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 跑通首个行为采集切片:AI 申请预填 → 用户接受/显式修改 → 草稿或提交结果同事务落账。
|
||||||
|
证据:`test_user_agent_application_draft_events.py`、`test_reimbursement_endpoints.py`、`expense-application-decision-feedback.test.mjs`、`expense-application-fast-preview.test.mjs`;覆盖可信入口、模板/详情排除、隐私指纹、同事务事件关联、日期联动及异步乱序响应。容器内学习账本与迁移所有权定向 29 项、一次性 PostgreSQL 迁移 4 项和前端关键场景 5 项通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 跑通风险反馈 → few-shot → golden case → Canary → 回滚工程闭环。
|
||||||
|
证据:风险处置学习、Golden evaluator、发布 runtime/telemetry/review/recall/monitor 与 PostgreSQL 并发测试;低 precision 和失败路径恢复 stable。
|
||||||
|
- [x] [CONCEPT: 测试方案] 跑通节省机会 → 执行 → 实现 → 财务确认 → ROI 看板闭环。
|
||||||
|
证据:`test_savings_value_e2e.py`、`test_expense_financial_value_chain_e2e.py` 和 CFO analytics/frontend 测试。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为已有 Expense Case 事件时间线补充视图模型、404 降级、详情页接入及相关响应式回归,并完成前端生产构建。
|
||||||
|
证据:容器内 `node --test` 定向执行 99 项通过;`npm --prefix web run build` 通过。一次性克隆迁移库上的隔离后端已完成真实登录、身份读取和旧单时间线 200 联调;持久开发库仍未迁移,因此日常本地页面仍保持兼容提示。
|
||||||
|
- [x] [CONCEPT: 测试方案] 为 AI 申请草稿事件补充事务失败回滚、同快照幂等、同 run 多版本留痕和 Steward 重放回归。
|
||||||
|
证据:本轮受影响后端定向回归 36 项、Expense Case 前端兼容测试 9 项和 Python `ruff --select F,I` 在容器内通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 在现有开发数据的只读一次性克隆上验证迁移、历史回填和真实认证时间线链路。
|
||||||
|
证据:源库与克隆初始签名均为 40 张表、4 张费用单、105 名员工、248 条预算、62 个 Agent 资产;克隆升级后 dry-run 为 eligible=4,apply created=4,重复 apply created=0,四条事件均为 system/suppressed;登录、`/auth/me`、旧单时间线和登出均返回 200,持久库复查不变且仍无 migration-owned 表。
|
||||||
|
- [ ] [CONCEPT: 测试方案] 补充其余前端组件、键盘操作、移动真实接口和完整浏览器关键流程验证。
|
||||||
|
- [x] [CONCEPT: 测试方案] 验证服务端预览决策签发、字段新增/改值差异判定、跨会话拒绝、无 ID 降级防绕过、安全重放、独立密钥权限、续签失败容错、旧路径兼容、会话恢复和真实 PostgreSQL 迁移。
|
||||||
|
证据:容器内新增决策安全用例 9 项、旧快速保存/提交 4 项、申请学习账本 9 项、迁移与所有权 26 项通过且条件型 PostgreSQL 用例 1 项跳过;一次性 PostgreSQL 17 迁移 4 项、前端定向 3 组及 Vite 生产构建已通过。临时 PostgreSQL 已清理,持久开发库 8 张 migration-owned 表数量仍为 0。
|
||||||
|
- [x] [CONCEPT: 测试方案] 验证小财管家、Steward、通用 Orchestrator 的统一预览闭环、认证绑定、fail-closed、稳定重试和跨刷新恢复。
|
||||||
|
证据:容器内后端组合回归 50 项通过,覆盖服务端预览、Steward 动作/图运行、跨租户 checkpoint、Orchestrator 匿名/普通用户/管理员来源授权及决策消费,Python Ruff F/I 通过;前端结构化动作、会话恢复、工作台路由、富确认和 `ai-application-preview-actions` 共 18 项通过,Vite 生产构建通过。共享规则工作簿相关套件按容器内串行执行,避免并行读取正在变动的 XLSX 产生非业务性 ZIP 竞争。
|
||||||
|
- [x] [CONCEPT: 测试方案] 所有后端、集成和迁移测试在当前主应用容器内执行,单条命令最大超时 60s。
|
||||||
|
证据:176 个后端测试文件按有界分片或专项执行;PostgreSQL 探针由应用容器运行,未在宿主机安装替代 venv。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 记录测试、lint、typecheck、构建、端到端和未覆盖风险证据。
|
||||||
|
证据:后端分片与费用主服务通过;PostgreSQL `87/0/0`;Web `815/0` 与 Vite build;Mobile lint/typecheck;新增 Python Ruff、compileall、受门禁核心类/组件 800 行检查和 `git diff --check`。未覆盖的生产/试点项保留在第 11-12 节。
|
||||||
|
|
||||||
|
## 11. 分阶段试点与价值验证
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 确认开发前置门禁已完成,试点场景、客户群体、基线和纵向验收未发生未经评审的漂移。
|
||||||
|
- [ ] [CONCEPT: 自动化决策] 前四周只运行建议和 shadow,不开放高风险自动化。
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 验证报销创建时间、自动填充率、首次提交完整率、人工触点和风险护栏。
|
||||||
|
- [ ] [CONCEPT: 节省与价值] 由客户财务确认现金节省、工时价值、归因口径和去重结果。
|
||||||
|
- [ ] [CONCEPT: 商业计量] 计算试点客户的订阅、模型、OCR、实施、支持和集成贡献毛利。
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 输出 90 天 ROI 报告和年度合同/扩展模块建议。
|
||||||
|
|
||||||
|
## 12. 文档收尾
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 将试点实际基线替换方向性目标,冻结正式验收阈值。
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 更新目标客户、试点场景、支付边界、连接器范围和自动化授权结论。
|
||||||
|
- [x] [CONCEPT: 本轮实现记录] 每个工程阶段完成后补充实现文件、迁移、接口和容器测试证据。
|
||||||
|
证据:本 CONCEPT/TODO、2026-07-16/17 分功能文档与 `engineering-closure-and-production-readiness` 总验收文档。
|
||||||
|
- [x] [CONCEPT: 功能一句话] 确认最终工程实现持续服务于“不用填表、少被退回、真正省钱”的核心结果。
|
||||||
|
证据:零录入票据、服务端预审、审批例外、可信学习、Savings/CFO 与商业计量形成同一费用闭环;真实成效仍由第 11 节企业试点验证。
|
||||||
19
document/development/2026-07-13/work-logs.med
Normal file
19
document/development/2026-07-13/work-logs.med
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 2026-07-13 综合工作日志
|
||||||
|
|
||||||
|
生成时间:2026-07-13 17:01:12 CST
|
||||||
|
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||||
|
|
||||||
|
## 今日功能点
|
||||||
|
|
||||||
|
- AI 费用闭环与价值证明 概念文档:把申请、消费、票据、报销、审批、付款、入账、分析和持续学习连接成同一个费用事件,让员工少填表、审批人只处理例外、财务能确认真实节省,并让 AI 在可控边界内越用越准确。(TODO 完成 15 项,未完成 98 项;目录:`ai-expense-closed-loop-and-value-proof`)
|
||||||
|
|
||||||
|
## 今日 Bugs
|
||||||
|
|
||||||
|
- bugs:影响:AI 一键新建申请现在与编辑重提共用同一提交语义,预算预占、提交校验、申请风险标记、Expense Case 事件和审批状态保持一致;业务事件写入失败时,申请、预算额度、预算流水、预算预占和 Case 关联不会残留部分成功数据。(文件:`ai-application-submit-bypasses-transaction.md`)
|
||||||
|
- bugs:影响:用户登录后必须使用服务端签发的短期 Bearer 会话,伪造用户名、角色或管理员头不再生效;业务经理不能冒充平台管理员,已初始化系统的基础设施信息与重配置入口不再向匿名请求开放。租户级查询守卫、会话清理/全部退出、登录限流和 SSO 仍按 P0 后续任务推进。(文件:`client-auth-forgery-and-bootstrap-reconfiguration.md`)
|
||||||
|
- bugs:影响:付款或关联解绑中途失败时,不再留下“申请已归档但付款/事件未提交”的半完成状态,为事务 Outbox 提供可靠边界。(文件:`nested-audit-premature-commit.md`)
|
||||||
|
|
||||||
|
## 综合分析
|
||||||
|
|
||||||
|
- 功能侧沉淀了 1 个功能点,问题侧记录了 3 个 bug 修复。
|
||||||
|
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# 迁移自有表被运行时 bootstrap 越权创建
|
||||||
|
|
||||||
|
日期:2026-07-14
|
||||||
|
文档路径:document/development/2026-07-14/dev-logs/bugs/migration-owned-table-bootstrap-drift.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
- 09:21:记录 bug 修复:迁移自有表被运行时 bootstrap 越权创建。(bug-log:1347366b)
|
||||||
|
- Git 提交检查:fetch 成功;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交:1347366b (HEAD -> main) feat(expenses): secure timeline and draft events;22669a90 feat(expenses): show unified expense event timeline;a616b30c fix(expenses): unify AI application submission transaction;653eda05 feat(auth): add opaque bearer sessions;661990b2 feat(expenses): add transactional expense case events。
|
||||||
|
- 修改:新增 `schema_ownership.py` 统一四张 migration-owned 表的所有权边界,把 Agent Foundation、员工、预算、设置、系统看板、数字员工看板和演示数据初始化中的全量 `create_all` 改为只创建 legacy 表;新增 `migration_preflight.py`,在启动迁移前只读核对 Alembic revision 与自有表集合,遇到无版本表、缺表、多表、未知版本或多版本时直接拒绝,不自动 stamp 或修复。
|
||||||
|
- 操作:调整 `server_start.sh` 的启动顺序和 `alembic.ini` 的脚本绝对定位方式;在无端口、无持久卷、tmpfs 数据目录的一次性 PostgreSQL 17 容器中执行空库升级、重复升级、降级到 base、漂移拦截和再次升级,结束后自动删除临时容器;没有执行持久化开发数据库迁移。
|
||||||
|
- 验证:迁移与预检定向测试 `19 passed, 1 skipped`;一次性 PostgreSQL 真实迁移测试 `4 passed`,最终 revision 为 `20260713_0002`,关键唯一约束、复合索引和外键级联均通过,legacy 哨兵跨降级/再升级保留;受影响服务回归 `46 passed, 1 failed`,唯一失败为既有员工目录历史部门归一化用例,单独运行同样失败,与本次建表 helper 无关;新增文件 Ruff 全规则、旧服务 F/I/B/UP、Shell 语法、Alembic heads 和 `git diff --check` 均通过。
|
||||||
|
- 影响:运行时 bootstrap 不再可能越权重建 Alembic 管理的表,启动会在可能破坏数据前暴露版本/表漂移;完整 legacy schema baseline 仍未建立,后续迁移旧表时必须继续小步验证。
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Orchestrator 未认证与费用申请决策绕过
|
||||||
|
|
||||||
|
日期:2026-07-14
|
||||||
|
文档路径:document/development/2026-07-14/dev-logs/bugs/orchestrator-auth-preview-decision-bypass.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 15:43:记录 bug 修复:Orchestrator 未认证与费用申请决策绕过。
|
||||||
|
- Git 提交检查:`git fetch --all --prune` 成功,`origin/main` 没有新提交;本地 ahead 9 条,依次为 `5b246307` 服务端申请预览决策、`a662cfe6` 申请反馈账本、`5ed34c2b` 历史费用 Case 回填、`11275e4b` 迁移所有权安全、`1347366b` 安全时间线与草稿事件、`22669a90` 统一费用时间线、`a616b30c` AI 申请提交事务、`653eda05` Bearer 会话、`661990b2` 事务型费用事件;工作树为 dirty,未执行合并或变基。
|
||||||
|
- 修改:`orchestrator.py` 的所有外部来源改为强制认证,`schedule` / `system_event` 进一步要求平台管理员;`OrchestratorService` 用登录态覆盖客户端身份与调度操作人别名并丢弃 preview/decision 注入。`agent_conversations.py` 与 Steward 动作运行时使用可信租户状态隔离会话创建、恢复、幂等检查点和删除;统一申请预览工作流要求 Steward、工作台和小财管家先签发再保存/提交,并复用同会话同快照的 active decision。
|
||||||
|
- 操作:把 Orchestrator 申请 decision 保存到服务端会话,草稿保存后的 next decision 继续回写;工作台 Steward 申请动作改为先打开结构化签发预览。过期/已消费 decision 会清空旧 ID 并进入可重新签发状态;租户 A/B 使用相同用户名、conversation 和 trace 时创建独立会话与 decision。所有验证均在 `x-financial-local-linux` 容器 `/app` 内执行,未修改七个用户规则工作簿。
|
||||||
|
- 验证:后端组合回归 50 项通过,覆盖服务端预览、Steward 动作/图运行、跨租户 checkpoint、Orchestrator 匿名/普通用户/管理员来源授权与 decision 消费,Python Ruff F/I 通过;前端结构化动作、会话恢复、工作台动作路由、富确认和动作脚本 18 项通过,Vite 生产构建通过。共享工作簿并行读取曾触发 `openpyxl` ZIP 竞争,串行复跑相关套件后全部通过。
|
||||||
|
- 影响:未登录用户不能再通过伪造 `source` 调用 Orchestrator,普通用户也不能伪装调度来源触发 Hermes 管理任务;同名用户不能跨租户恢复、删除或重放 Steward 会话检查点。申请保存/提交只消费当前登录会话签发的 decision,重复签发、过期和失败均有确定的安全处理。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 18:14:记录 bug 修复:Agent Run 详情绕过财务看板权限暴露跨租户快照与工具响应。
|
||||||
|
- Git 提交检查:18:13 执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交,本地比 `origin/main` ahead 17 个提交,范围为 `661990b2 feat(expenses): add transactional expense case events` 至 `242d68c3 feat(approval): add task workflow and waiver decisions`,本次未合并或改写这些提交。
|
||||||
|
- 修改:新增 `agent_run_access_policy.py`,识别 `finance_dashboard_snapshot` 运行记录并同时核对 route/ontology 中的 `tenant_id`、`data_scope` 及当前用户财务角色;两份范围标签缺失、不一致、跨租户或与当前数据范围不符时一律 fail-closed。
|
||||||
|
- 修改:`agent_runs.py` 在列表返回前过滤无权查看的财务快照,在详情返回 `snapshot_payload`、工具请求和工具响应前执行同一访问策略;跨租户(包括其他租户 admin)按 404 处理,同租户普通用户按 403 处理,只有同租户 `finance`、`executive` 或 admin 能读取完整快照。
|
||||||
|
- 操作:在 `test_finance_dashboard_tenant_security.py` 构造 tenant-a、tenant-b、无租户旧快照和损坏 data scope 快照,覆盖列表、详情、普通用户、财务用户和跨租户管理员路径;所有命令均在 `local-x-financial-linux` 容器执行。
|
||||||
|
- 验证:Ruff 检查与格式检查通过;财务看板、Agent Run 服务和 Ontology 端点定向回归共 17 个测试通过,证明合法同租户详情仍可读取,跨租户载荷、无范围旧记录和损坏范围记录均不可见。
|
||||||
|
- 影响:已登录用户不能再通过猜测或复用 `run_id` 绕过财务看板领域权限读取其他租户的报销金额快照和工具调用明细,列表入口也不会泄露这些快照的摘要记录。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 22:33:记录 bug 修复:Agent Run 轻量列表遗漏语义解析结果。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..@{u}` 为空,未发现 upstream 新提交;本地 `main` ahead 17。审批链相关提交为 `242d68c3`、`28b834ed`、`4940ebc4`,AI 报销学习与申请链相关提交为 `ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`,费用事件与事务链相关提交为 `5ed34c2b`、`1347366b`、`22669a90`、`a616b30c`、`661990b2`,另有迁移安全 `11275e4b` 与会话鉴权 `653eda05`;这些均为当前任务开始前已有的本地提交,本次没有改写或合并。
|
||||||
|
- 修改:在 `agent_run.py` 的轻量仓储查询中按列表已经筛选出的 run_id 批量读取每个 Run 的首条语义解析;在 `agent_runs.py` 中恢复 `AgentRunRead.semantic_parse` 的既有列表契约,同时保留工具调用和大 JSON 字段的轻量预览策略。
|
||||||
|
- 操作:先在容器复现 seeded trace 用例失败,再核对 foundation seed、详情序列化和前端消费字段;没有修改 seed fixture 来掩盖列表序列化断层。
|
||||||
|
- 验证:容器内 `test_agent_runs_service.py`、`test_agent_run_tenant_security.py`、`test_agent_trace_service.py` 与 OnlyOffice 定向测试共 11 项通过;`test_agent_asset_service.py` 全部 28 项通过;相关实现文件 Ruff 与 `git diff --check` 通过。
|
||||||
|
- 影响:Agent Run 列表重新携带真实语义解析摘要,seeded trace、运行轨迹界面和依赖 `semantic_parse` 的流程可继续使用;补充查询仅以租户过滤后的 run_id 为输入,不扩大数据作用域。
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 22:10:记录 bug 修复:Agent Run 普通日志跨租户读取与统计泄露。
|
||||||
|
- Git 提交检查:22:10 执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交,本地比 `origin/main` ahead 17 个提交,范围为 `661990b2 feat(expenses): add transactional expense case events` 至 `242d68c3 feat(approval): add task workflow and waiver decisions`,本次未合并或改写这些既有提交。
|
||||||
|
- 修改:`agent_run.py` repository 将 `route_json.tenant_id` 与 `ontology_json.tenant_id` 的双重一致性条件下推到 SQL,在排序和 `limit` 前完成租户过滤;任一标记缺失、空作用域或两处标记冲突的记录均 fail-closed,详情也在数据库查询阶段按租户收窄。
|
||||||
|
- 修改:`agent_runs.py` service 新增显式的租户级列表、统计和详情入口,保留带注释的受信任内部跨作用域入口;`agent_run_access_policy.py` 增加返回前二次租户校验,并把财务快照的角色与 `data_scope` 门禁同步下推,防止不可见快照挤占列表和统计窗口。
|
||||||
|
- 修改:Agent Run API 的列表、统计和详情统一使用当前认证租户;普通 run 的跨租户详情、无作用域旧记录和冲突标记记录均返回 404,跨租户管理员也没有旁路,工具 `request_json`/`response_json`、语义 `raw_query` 与错误统计不会跨租户暴露。
|
||||||
|
- 修改:`create_run` 支持显式 `tenant_id` 并同时写入 route/ontology,后续整体更新或 route 合并会保留已验证的双重标记,发现调用方已有冲突标记时拒绝写入;Orchestrator、Ontology、知识同步和财务快照的认证/租户感知创建路径已传入可信 tenant,知识同步的活动任务复用也改为租户内查询。
|
||||||
|
- 操作:新增 `test_agent_run_tenant_security.py`,构造 tenant-a、tenant-b、无作用域、冲突作用域和同租户财务快照,覆盖 limit 前过滤、列表、统计、详情、跨租户 admin 及敏感载荷反向断言;全部命令均在 `local-x-financial-linux` 容器内以 60 秒超时执行。
|
||||||
|
- 验证:Agent Run、财务快照、Ontology 端点、Orchestrator 认证和知识服务定向回归 31 个测试通过;核心变更文件 Ruff、`git diff --check`、PostgreSQL 方言 SQL 编译与代码行数检查通过,最大核心文件 `agent_runs.py` 为 760 行,低于 800 行硬上限。额外 Ontology 全文件回归共 82 个通过、4 个既有业务信号识别用例失败,失败堆栈位于未由本次改动触碰的 `_has_supported_business_signal` 判定。
|
||||||
|
- 影响:Agent Run 日志现在以认证租户为强边界,其他租户、历史无归属记录及损坏作用域记录不再进入列表、统计或详情响应,同时保留内部后台任务按明确可信入口读取的兼容性。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 19:19:修复 AI 报销申请预检的阻断提示与既有交互文案契约不一致问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||||
|
- 修改:`aiApplicationPrecheckModel.js` 将受限状态和证据快照提示统一为“请先检查”“请先核对”,恢复动作前置关系并与页面测试契约一致。
|
||||||
|
- 操作:检查在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX。
|
||||||
|
- 验证:AI 申请预检模型定向前端回归 `4 passed`。
|
||||||
|
- 影响:用户在提交前能清楚理解必须先完成的检查动作,避免因提示语义弱化而误以为可直接继续。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 19:01:修复一个 AI 决策关联多条工作流结果后,原申请动作幂等重放可能命中多行的问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,从 `661990b2 feat(expenses): add transactional expense case events` 到 `242d68c3 feat(approval): add task workflow and waiver decisions`,其中包含认证、费用 Case、AI 反馈/记忆、迁移安全、预审、风险处置和审批任务能力;本次未拉取、合并或改写这些历史。
|
||||||
|
- 修改:`ExpenseApplicationLearningService._find_existing()` 不再按 `decision_id` 使用可返回多行的无序 scalar 查询,而是根据原始动作的租户、幂等键和稳定 UUID 精确取回首次 Feedback/Outcome,并二次校验租户与 Decision 关联。
|
||||||
|
- 修改:工作流结果桥接只关联事件发生前最新的已提交 AI Decision;原动作重放只回填同 correlation 的预审结论,不会把后续重新提交的退回或审批结果污染到旧决策。
|
||||||
|
- 操作:在 `local-x-financial-linux` 容器中使用 `/tmp/x-financial-server-venv` 运行 Ruff 和 AI 预览/工作流学习回归;未在宿主机执行 Python 或 pytest。
|
||||||
|
- 验证:`test_expense_application_preview_decisions.py` 与 `test_expense_workflow_learning.py` 组合回归 `17 passed`;新用例验证第二次提交后的退回仅关联最新 Decision,多 Outcome/Feedback 不影响原请求重放。
|
||||||
|
- 影响:申请动作重试不再因后续付款、退回或审计结果增多而出现多行异常,也不会将新一轮流程结果错标到历史 AI 建议上。
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 2026-07-16 15:10:42 CST:修复审批、退回和付款动作缺少稳定请求标识、预期状态与预期审批节点校验的问题。新增租户 + 操作人 + 请求 ID 唯一账本、请求指纹、PostgreSQL advisory lock 与 Claim 行锁;完全相同的重试返回原结果,请求内容变化或状态已过期返回 409,不重复扣减预算、写审批记录、生成业务事件或付款结果。
|
||||||
|
- 2026-07-16 15:10:42 CST:修复高风险观察仅展示但不阻止审批的问题。审批流程在任何路由、预算和业务状态变更之前检查同租户高危/严重风险;只有已判定误报或完成处置的观察允许继续,阻断响应返回稳定错误码和观察状态,整笔动作账本随事务回滚。
|
||||||
|
- 2026-07-16 15:10:42 CST:前端确认弹窗冻结请求 ID、预期单据状态和审批节点,网络结果不确定时复用同一请求,避免用户重试造成重复业务副作用。
|
||||||
|
- 2026-07-16 15:10:42 CST:验证在 `local-x-financial-linux` 容器完成:费用服务 116 项、审批动作/风险门禁/风险处置组合 26 项、报销接口回归 22 项;一次性 tmpfs PostgreSQL 17 迁移循环 13 项通过。变更 Python 文件 Ruff F/I 与格式检查通过。
|
||||||
|
- 2026-07-16 15:10:42 CST:Git 拉取检查已执行;上游没有本地尚未包含的新提交,当前分支相对上游 ahead 14。已有本地提交包括 `ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据闭环等,本次修复在这些能力之上继续演进。
|
||||||
|
- 2026-07-16 15:33:03 CST:交叉审查后修复三项并发绕过:审批动作不再调用会隐式提交的读取修复逻辑,列表/分页 GET 也不再修改审批节点;审批中心保留服务端原始状态并单独生成展示标签;门禁将持久化风险观察与未匹配原始高风险合并,重大观察持久化失败时 fail-closed。
|
||||||
|
- 2026-07-16 15:33:03 CST:审批、风险处置和 Hermes 扫描统一以 Claim 行锁为公共协调点,锁顺序固定为 Claim → Observation → Disposition;Hermes 锁外计算、锁内刷新并核对状态和更新时间,旧快照被丢弃。一次性 PostgreSQL 并发用例证明风险重新打开与审批竞争时,审批等待后读取最新风险并阻断,Claim 节点和动作账本均保持无副作用。
|
||||||
|
- 2026-07-16 15:33:03 CST:最终容器回归为审批/风险/费用服务 174 项、迁移/所有权 54 项、前端审批/风险 73 项通过,Vite 生产构建通过;一次性 tmpfs PostgreSQL 17 上 13 项迁移和 1 项真实并发测试通过,临时数据库自动清理。再次执行 Git 拉取检查,上游仍无新增提交,本地 ahead 14。
|
||||||
|
- 2026-07-16 15:44:39 CST:提交后交叉审查发现“副作用幂等”不等于“响应幂等”:动作账本虽然已有 `response_json`,重放却重新读取当前 Claim,后续节点变化会返回新状态。现改为首次动作在同一事务保存完整 `ExpenseClaimRead`,首次和重放均从该快照构造响应;快照缺失、单据/状态/节点身份不一致时 fail-closed 返回冲突,不再退回当前投影。
|
||||||
|
- 2026-07-16 15:44:39 CST:新增“首次批准 → 单据继续推进并写入后续内部状态 → 旧 request_id 重放”服务和 HTTP 回归,确认响应逐字段等于首次结果、后续风险字段不泄露、数据库当前状态不被回退;审批动作与报销接口组合 29 项通过。
|
||||||
|
- 2026-07-16 15:44:39 CST:再次完成 Git 拉取检查,上游无新增提交,本地 ahead 15,新增 ahead 为 `4940ebc4` 审批风险安全底座;用户维护的财务规则工作簿和历史日志继续保持未暂存。
|
||||||
|
- 2026-07-16 15:48:38 CST:扩大回归范围后发现两个兼容边界:首次动作若直接返回 Pydantic 快照会破坏既有服务层 ORM 可变语义,因此首次执行继续返回已提交并刷新的 ORM,只有重放从不可变快照构造;HTTP 首次与重放仍逐字段一致。另为风险门禁增加申请/报销阶段匹配,显式错阶段风险不阻断当前流程,未知阶段重大风险仍 fail-closed。
|
||||||
|
- 2026-07-16 15:48:38 CST:路由型重大风险统一使用显式 `route_review`,它允许当前审批进入预算/财务复核,但不会被标记为已解决;普通无 actionability 的原始重大风险继续阻断。容器内费用服务、审批路由、租户/Case、动作协议、接口和风险处置组合 182 项通过。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 2026-07-16 16:50:44 CST:修复 SQLAlchemy Session 关闭 autoflush 后的两处审批任务状态错误。根任务完成后先显式 flush 再生成下一节点,避免仍被查询为开放任务并误标 superseded;SLA 扫描每次升级后 flush,避免同一事务重复扫描生成相同版本事件。
|
||||||
|
- 2026-07-16 16:50:44 CST:修复审批队列前后端 SLA 与权限动作契约不一致。前端统一使用 `due_soon`,服务端正式支持 `escalated` 并拒绝未知筛选;批准/退回仍要求 `can_act`,委托撤销、管理员转交和 SLA 升级则严格按服务端 `available_actions` 展示,不再被前端错误隐藏。
|
||||||
|
- 2026-07-16 16:50:44 CST:修复任务筛选总数污染全局待办数、批量审批后摘要不刷新、队列错误与动作错误相互覆盖,以及详情返回丢失任务筛选/页码的问题。工作台独立查询未筛选 pending 摘要,批量成功聚合回流单据更新,审核路由保存专属风险/SLA/关键词/页码状态,旧响应由请求代次丢弃。
|
||||||
|
- 2026-07-16 16:50:44 CST:修复审批动作网络失败后允许编辑负载却继续复用旧幂等键的问题。单项、委托/转交、加签/会签和批量审批均对请求负载生成稳定指纹;完全相同的重试复用 request ID,理由、候选人、参与人或批量意见变化时自动生成新 ID,避免同键不同内容冲突被误解为操作失败。
|
||||||
|
- 2026-07-16 16:50:44 CST:验证全部在 `local-x-financial-linux` 容器完成。审批/风险/迁移相关后端 `122 passed, 4 skipped`,变更 Python 文件 Ruff 通过;审批、单据中心和风险前端专项 `60 passed`,Vite 生产构建通过;一次性 PostgreSQL 17 空库迁移循环 `1 passed`,审批任务与风险锁并发 `3 passed`,探针数据库均已销毁。
|
||||||
|
- 2026-07-16 16:50:44 CST:执行 `git fetch --all --prune`、工作区状态和上下游提交差异检查;上游无新增提交,本地相对 `origin/main` ahead 16,最近两个检查点为 `28b834ed` 不可变审批响应重放和 `4940ebc4` 安全风险处置。本次继续排除 7 个用户维护的财务规则工作簿及历史未跟踪开发文档。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 19:09:修复 CFO 价值看板允许发起无证据手工实际结果、与后端证据门禁冲突的问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||||
|
- 修改:`CfoValueOpportunityDrawer.vue` 过滤 `record_realization` 空证据入口,并明确提示实际结果必须来自平台付款事件或可追溯的支付、银行、ERP 凭证;`CfoValueActionDialog.vue` 删除不可履行证据要求的手工金额表单;`useCfoValueDashboard.js` 增加防御性拒绝,避免旁路重新提交空证据。
|
||||||
|
- 修改:`cfo-value-dashboard.test.mjs` 将序列化样例改为带外部凭证的请求,并增加前端不暴露空证据登记入口的回归断言。
|
||||||
|
- 操作:全部检查均在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX 或历史开发文档。
|
||||||
|
- 验证:CFO/应用壳/财务看板组合前端回归 `22 passed`;Vite 生产构建成功(`2227 modules transformed`,`built in 5.08s`);`git diff --check -- web` 通过。
|
||||||
|
- 影响:用户不会再遇到“页面允许登记、后端必然拒绝”的假动作;在真实连接器或凭证上传入口接入前,现金节省仍只能依靠可信付款事件落账并由独立财务确认。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 21:40:记录 bug 修复:定价毛利率边界舍入越过后端上限。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新增提交;本地 `main` ahead 17 个既有提交,分别为 `242d68c3` 审批任务流、`28b834ed` 不可变动作回放、`4940ebc4` 风险处置、`ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据关联、`54754b55` 个人申请记忆、`211f85d9` 统一申请流、`5b246307` 申请预览决策、`a662cfe6` 申请反馈台账、`5ed34c2b` 历史申请回填、`11275e4b` 迁移归属校验、`1347366b` 费用时间线与草稿事件、`22669a90` 统一费用事件时间线、`a616b30c` AI 申请事务、`653eda05` bearer 会话和 `661990b2` 费用案例事件;均非本次修复产生。
|
||||||
|
- 修改:`commercialWorkspaceModel.js` 先把百分比量化为后端允许的六位小数,再校验目标贡献毛利率严格小于 `0.95`;`CommercialPricingScenarioPanel.vue` 同步把可输入上限收紧到 `94.9999%`,并在模型测试中覆盖舍入临界值。
|
||||||
|
- 操作:在容器 `local-x-financial-linux` 内执行边界探针、商业模型与组件定向测试、全量前端测试、code-size 门禁和 Vite production build。
|
||||||
|
- 验证:临界探针确认 `94.9999%` 序列化为 `0.949999`,`94.999999%` 在请求前被拒绝;商业定向测试 35 项通过,修复后的模型与组件回归 28 项通过,全量前端 802 项通过,code-size 通过,Vite 完成 2246 个模块转换。
|
||||||
|
- 影响:管理员无法再提交一个表面小于 95%、但六位小数量化后等于后端禁值 `0.95` 的场景,避免无意义的 422 往返,同时不改变合法目标毛利率的精度。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 21:09:记录 bug 修复:未配置硬配额被前端误显示为剩余 0。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新增提交;本地 `main` ahead 17 个既有提交,分别为 `242d68c3` 审批任务流、`28b834ed` 不可变动作回放、`4940ebc4` 风险处置、`ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据关联、`54754b55` 个人申请记忆、`211f85d9` 统一申请流、`5b246307` 申请预览决策、`a662cfe6` 申请反馈台账、`5ed34c2b` 历史申请回填、`11275e4b` 迁移归属校验、`1347366b` 费用时间线与草稿事件、`22669a90` 统一费用事件时间线、`a616b30c` AI 申请事务、`653eda05` bearer 会话和 `661990b2` 费用案例事件;均非本次修复产生。
|
||||||
|
- 修改:`commercialWorkspaceModel.js` 的数值规范化显式把 `null`、`undefined` 和空字符串保留为“未知”,不再依赖 JavaScript 的 `Number(null) === 0` 隐式转换。
|
||||||
|
- 操作:在容器 `local-x-financial-linux` 内运行商业服务、模型和 Vue 组件定向 Node 测试。
|
||||||
|
- 验证:商业服务、模型和组件定向测试共 28 项通过;全量 `web/tests/*.test.mjs` 共 795 项通过;`code-size-limits` 通过;Vite production build 完成 2233 个模块转换。权益测试同时覆盖不限量、未配置硬上限和失败关闭状态。
|
||||||
|
- 影响:真实硬配额为 0 时仍显示 0;未配置硬配额时显示“未配置硬上限”,避免管理员把未知配置误判为已耗尽配额。
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 19:37:修复商业分析 ROI 口径、商业合同可见角色和已消费权益历史可变三类问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||||
|
- 修改:`commercial_analytics.py` 将客户现金 ROI 统一为“(财务确认现金节省-客户合同收费代理值)/ 客户合同收费代理值”,并把 ratio numerator 改为净收益;禁止截止时间早于窗口开始及无时区分析时间。
|
||||||
|
- 修改:`commercial_access_policy.py` 移除直属经理对套餐、订阅和配额的默认读取权限,仅保留财务、executive 和平台管理员。
|
||||||
|
- 修改:`commercial_admin.py` 在权益已有用量后禁止回改配额、计价配置和有效期,只允许暂停/恢复;完全相同的 PUT 不再无意义增加版本。
|
||||||
|
- 修改:`commercial.py` 强制套餐、订阅、权益、用量和成本事实时间显式带时区,避免跨时区账期歧义。
|
||||||
|
- 操作:全部检查均在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX 或历史开发文档。
|
||||||
|
- 验证:商业模型、服务与 HTTP 定向回归 `10 passed`;相关 Ruff 检查通过。
|
||||||
|
- 影响:商业 ROI 与既定合同口径一致,普通直属经理不能查看敏感商业合同,历史用量不会因事后回改权益配置而改变含义。
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 21:56:记录 bug 修复:非成功 Agent 工具调用可能被商业账本误计量。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新增提交;本地 `main` ahead 17 个既有提交,分别为 `242d68c3` 审批任务流、`28b834ed` 不可变动作回放、`4940ebc4` 风险处置、`ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据关联、`54754b55` 个人申请记忆、`211f85d9` 统一申请流、`5b246307` 申请预览决策、`a662cfe6` 申请反馈台账、`5ed34c2b` 历史申请回填、`11275e4b` 迁移归属校验、`1347366b` 费用时间线与草稿事件、`22669a90` 统一费用事件时间线、`a616b30c` AI 申请事务、`653eda05` bearer 会话和 `661990b2` 费用案例事件;均非本次修复产生。
|
||||||
|
- 修改:`commercial_runtime_policy.py` 把工具调用状态明确分为可计量、采集中和不可计量;`commercial_runtime_metering.py` 只允许 `succeeded/success/ok/completed` 的真实终态调用进入用量与成本账本,`running/pending/queued` 保留待终态补偿语义,`blocked/failed/skipped/cancelled` 不再产生商业事实。同步用 `commercial_runtime_bridge.py` 将未配置租户兼容放行、显式配置后的执行前配额门禁、成功调用后的幂等追加和故障补偿接入真实 `AgentToolCall` 生命周期。
|
||||||
|
- 操作:在 `AgentRunService` 的创建与终态更新后触发桥接计量;在中央工具执行器调用真实 executor 前执行权益预检;将工具执行职责拆到 `orchestrator_tool_execution.py`,让核心编排文件回落到 762 行;所有命令均在 `local-x-financial-linux` 容器内运行并设置 60 秒超时。
|
||||||
|
- 验证:商业运行计量 17 项通过,商业模型/服务/接口/运行计量合计 28 项通过,AgentRun 与 Orchestrator 鉴权相关回归合计 34 项通过;范围内 Ruff、`compileall`、diff 检查和类级 800 行检查通过。全库 code-size 门禁仍被本次范围外的 `RiskRuleGenerationService` 817 行阻断,未在本修复中改动该类。
|
||||||
|
- 影响:未成功完成的工具调用不会再消耗客户配额或形成内部成本;未配置商业计量的既有租户继续执行;已配置租户在执行前受配额约束。计量系统故障时真实工具调用记录保持不变,返回 `requires_reconciliation` 并可按同一工具调用 ID 幂等重试,避免把计量失败伪装成已入账。
|
||||||
|
|
||||||
|
- 22:31:修复执行前只读配额在并发下可超卖、直接调用绕过预占及补偿误判问题。
|
||||||
|
- Git 提交检查:再次执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`;上游无新增提交,本地仍 ahead 17 个既有提交:`242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`,均非本次修复产生。
|
||||||
|
- 修改:新增 `commercial_runtime_reservations` 运营占位及 `0019` 迁移;在订阅、权益行锁内按“已用量 + 有效预占 + 本次预占”原子校验硬配额。中央 Orchestrator 先生成稳定 tool call ID 并预占,再执行真实工具;成功且真实量不超过预占才追加用量/成本并提交,失败或阻断释放,变量基准没有执行器 hard max 时失败关闭。
|
||||||
|
- 修改:新增持久 `reconciliation_required` 和过期补偿器。缺少执行前预占的旧直接调用不再静默补写正常用量,而是冻结对应容量;补偿器仅在真实工具成功、失败或运行已终止且无调用时结算/释放,运行中或来源不确定继续保留。修复历史过期订阅/权益错误启用门禁、无租户旧运行错误进入补偿、相同预占重试被自身占位判定为额度耗尽三个边界。
|
||||||
|
- 操作:拆出运行时成本解析、周期键、标量校验、预占和补偿模块,相关核心类均低于 800 行;更新模型注册、迁移所有权、前置检查、商业配额投影、AgentRun/中央工具调用点和迁移/并发/补偿测试。所有 Python、Alembic 和 PostgreSQL 验证均在 `local-x-financial-linux` 容器内以 60 秒超时执行。
|
||||||
|
- 验证:运行时定向 25 项通过;商业、Agent、权限、迁移组合 140 项通过;一次性 PostgreSQL 17 商业并发 4 项通过,两个线程竞争一份硬配额时只有一个预占成功;全新 PostgreSQL 0019→0020 完整 Alembic 升降级循环 1 项通过;范围内 Ruff、`compileall`、`git diff --check` 和相关类 800 行检查通过。Orchestrator review 套件保持既有 5 项本体/申请流失败、11 项通过;全库 code-size 仍仅被范围外 `RiskRuleGenerationService` 817 行阻断。
|
||||||
|
- 影响:商业硬配额从“执行前提示”升级为数据库原子 permit,真实工具并发不能再穿透上限;成功事实可幂等结算,计量或成本故障保留可补偿状态且不伪造成功;未配置 runtime meter 的路径继续兼容,历史失效配置不会误拦截用户。
|
||||||
|
|
||||||
|
- 22:39:修复“用量已提交、成本写入失败”缺少持久补偿身份的问题。
|
||||||
|
- Git 提交检查:再次执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`;上游仍无新增提交,本地仍 ahead 17 个既有提交:`242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`,均非本次修复产生。
|
||||||
|
- 修改:为运行时预占增加 `committed_reconciliation_required` 状态。真实用量已经追加但内部成本失败时,保留真实数量、结算时间和失败原因并进入补偿队列;按同一 tool call 重试只幂等补写成本,成功后恢复 committed。该状态不再计入有效预占,避免真实用量和冻结量重复消耗配额。
|
||||||
|
- 修改:直连调用发生后若当前唯一匹配合同已暂停,允许补偿记录绑定原订阅/权益快照并进入 `reconciliation_required`;正常执行前 reserve 仍严格要求 active/trialing 订阅和 active 权益,不放宽真实执行许可。
|
||||||
|
- 验证:容器内运行时定向 27 项、商业/Agent/权限/迁移组合 183 项通过,另 1 项因未显式配置外部迁移库跳过;全新 PostgreSQL 完整迁移循环 1 项、商业并发 4 项通过。成本故障用例验证 `used=1`、`reserved=0`,重试后用量仍为 1 且只新增 1 条成本;范围内 Ruff 通过。
|
||||||
|
- 影响:成本账本短暂故障不再只依赖日志发现,也不会让客户额度被重复扣减;暂停发生与工具终态竞态时,已发生业务仍有持久、租户隔离的补偿证据。
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 2026-07-16 11:03 CST:执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`。上游没有新增提交;本地相对 `origin/main` ahead 12,依次包含 `ae3f02c3` 零录入票据关联、`54754b55` 个人申请记忆、`211f85d9` 认证申请工作流、`5b246307` 预览决策签发、`a662cfe6` 申请反馈账本、`5ed34c2b` 历史费用 Case 回填、`11275e4b` 迁移所有权安全、`1347366b` 安全时间线与草稿事件、`22669a90` 统一费用时间线、`a616b30c` AI 申请提交事务、`653eda05` Bearer 会话和 `661990b2` 事务费用事件。
|
||||||
|
- 2026-07-16 11:03 CST:修复报销提交只检查旧 `ai_pre_review` 标记、前端不执行真实预审、规则或单据变化后仍可复用过期结果的问题。新增稳定 review ID、输入/规则指纹、流水线版本、`ready / needs_fix / ready_with_review` 决策、结构化发现与整改动作;高危且可由提交人修复的风险在预算占用前保持草稿并返回结构化 409,预算治理和人工复核风险继续进入审批。
|
||||||
|
- 2026-07-16 11:03 CST:修复预审和提交事件关联断裂、票据归集后预审未刷新、申请批准生成空报销草稿过早把 Expense Case 推入 `claiming` 的问题。预审与提交事件共享 correlation/causation,同输入和规则重试只保留一条预审事件;Case 在申请批准后保持 `approved_to_spend`,票据关联后再进入 `claiming`。
|
||||||
|
- 2026-07-16 11:03 CST:修复 AI 报销草稿、申请预览、Steward 和差旅测算链路未透传非默认租户、事件回落到 `default` 的问题;新增租户隔离与 Case 阶段回归,阻止跨租户 Case/Event 串线。
|
||||||
|
- 2026-07-16 11:03 CST:修复高风险费用申请未进入同部门 P8 预算审批、缺失 P8 未报错、P8 直属领导合并审批缺少审计标记及预算预占提前转移的问题;高风险恢复动态预算路由,中等普通预算预警与低风险申请仍保持快速流转。
|
||||||
|
- 2026-07-16 11:03 CST:修复职责拆分后发票去重助手未接回 `ExpenseClaimService`、英文 reimbursement/travel application 意图被财务门禁拒绝,以及 OCR 成功返回空文档时普通图片被降为“待识别中风险”的问题;复用统一票据键提取器,补齐英文财务信号,并把无有效 OCR 信号附件进入高风险校验。
|
||||||
|
- 2026-07-16 11:03 CST:容器内验证费用服务与审批路由 126 项、费用事件/接口/租户/票据关联 63 项、报销接口全量 20 项和前端预审/时间线 24 项通过;Vite 生产构建、Python compileall、关键未定义符号检查及 `git diff --check` 通过。影响范围为报销预审与提交、费用事件、申请预算路由、租户隔离、票据去重、英文财务意图和空 OCR 附件风险分级,不修改用户维护的财务规则工作簿。
|
||||||
|
- 2026-07-16 11:39 CST:再次执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`。上游仍无新增提交;本地相对 `origin/main` ahead 12,提交摘要与 11:03 检查一致,当前未提交改动继续排除 7 个用户维护的财务规则工作簿和历史未跟踪开发文档。
|
||||||
|
- 2026-07-16 11:39 CST:根据独立审查修复旧 `ready` 预审未覆盖动态风险上下文的问题。提交前申请与报销均重算完整风险,review ID 新增动态 findings 指纹;风险在握手期间变化时返回 `PRE_REVIEW_CHANGED`,新增重复发票/历史风险变化和嵌套无序集合回归,实际提交事件始终关联本次有效预审事件。
|
||||||
|
- 2026-07-16 11:39 CST:修复结构化 409 findings 被前端 `ai_pre_review` 汇总过滤规则丢弃、首票归集后 Case 未进入 `claiming`、申请可整改风险可绕过前端直接 API 提交,以及已解决高风险仍被路由至 P8 的问题。findings 现在保留来源、业务阶段、风险域和 actionability;申请与报销统一在预算前阻断提交人可整改风险,仅人工判断和预算治理风险进入领导/P8。
|
||||||
|
- 2026-07-16 11:39 CST:完成 Claim 核心访问、关联报销后台任务、草稿 ID/单号/最近候选查询和历史风险统计的租户隔离。同用户名、同员工 ID 在不同租户下不能读取、修改、关联或污染预审/P8;默认租户仅对无 CaseLink 历史单保留兼容。新增租户范围 helper 和跨租户对抗测试。
|
||||||
|
- 2026-07-16 11:39 CST:修复规则工作簿损坏或保存中间态导致报销主链抛出 `BadZipFile` / `InvalidFileException` 的问题;单个不可读工作簿现在安全跳过,默认规则和其他已加载规则继续可用。修复只增加运行时容错与临时文件单测,未写入、回滚或替换任何用户 XLSX。
|
||||||
|
- 2026-07-16 11:39 CST:按工程硬约束拆出申请关联、预审端点、职级标准调整、规则清单指纹和租户范围职责;本轮触及的核心服务、端点和前端提交模块均不超过 800 行。容器最终验证核心费用/审批/预审/规则容错 133 项、接口/Case/租户/任务/票据 74 项、前端 25 项通过;Vite 生产构建、全量 Python compileall、变更文件未定义符号检查和 `git diff --check` 通过。
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 17:11:记录 bug 修复:财务看板跨租户聚合、快照串租户复用与普通用户越权读取。
|
||||||
|
- Git 提交检查:执行 `git fetch --all --prune` 后未发现 `HEAD..origin/main` 新提交;本地比 `origin/main` ahead 17 个提交,最新为 `242d68c3 feat(approval): add task workflow and waiver decisions`,其余为 `28b834ed` 至 `661990b2` 的审批、费用闭环、AI 记忆、迁移安全和认证能力提交,本次没有合并或改写这些历史。
|
||||||
|
- 修改:`finance_dashboard.py` 通过 `ExpenseClaimTenantScopeMixin` 按 Expense Case Link 聚合当前租户报销单;缺少 `tenant_id` 的旧预算表仅允许 `default` 租户读取,其他租户返回带原因的明确空预算;新增 `finance_dashboard_scope.py` 统一声明报销与预算数据范围。
|
||||||
|
- 修改:`finance_dashboard_snapshot.py` 把 `tenant_id`、数据范围和完整时间参数纳入无歧义缓存键,并在 SQL 查询、Agent Run 路由及工具请求中同时校验租户和范围;`finance_dashboard_scheduler.py` 显式固定 `default` 系统租户,非默认租户不能调用默认定时快照入口。
|
||||||
|
- 修改:新增 `finance_dashboard_access_policy.py`,`analytics.py` 显式接收 `CurrentUserContext`,仅允许 `finance`、`executive` 或 admin 只读访问财务看板,普通用户和仅有 `budget_monitor` 角色的用户返回 403。
|
||||||
|
- 操作:所有检查均在 `local-x-financial-linux` 容器内执行;新增租户 A/B、default 历史单、旧预算、快照缓存与接口权限测试,没有触碰受保护的财务规则 XLSX 和历史开发文档。
|
||||||
|
- 验证:Ruff 对本次 7 个 Python 模块及新增测试检查通过;`test_finance_dashboard_tenant_security.py` 与 `test_finance_dashboard_service.py` 共 8 个测试通过。补充运行财务报告与数字员工回归时 4 个测试通过、1 个既有财务周报用例失败,原因是用例将“当前时间减 2 天”的数据断言进“上一完整周”窗口,和本次租户过滤无关。
|
||||||
|
- 影响:财务看板不再读取其他租户的报销单或把 default 旧预算暴露给非默认租户;相同时间参数的租户快照不会互相命中,后台默认快照和前台读取权限也有了可审计的显式边界。
|
||||||
|
|
||||||
|
- 18:14:补齐财务看板租户 fail-closed 边界与模块拆分验证。
|
||||||
|
- Git 提交检查:18:13 再次执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交,本地仍比 `origin/main` ahead 17 个提交,范围为 `661990b2 feat(expenses): add transactional expense case events` 至 `242d68c3 feat(approval): add task workflow and waiver decisions`,未合并、改写或覆盖这些提交及工作区内其他智能体改动。
|
||||||
|
- 修改:`finance_dashboard_access_policy.py` 对空白租户上下文直接拒绝,避免异常认证上下文回落到 default;将预算摘要、预算卡片和预算瓶颈投影提取到 `finance_dashboard_budget.py`,`finance_dashboard.py` 从 920 行降至 746 行,保持租户过滤和原 API 不变。
|
||||||
|
- 操作:只在 `local-x-financial-linux` 容器内执行 Ruff、财务看板服务与接口测试、Agent Run 服务回归和 Ontology 端点回归;未触碰财务规则 XLSX、Savings 迁移或前端文件。
|
||||||
|
- 验证:Ruff 检查与格式检查通过;财务看板、Agent Run 服务和 Ontology 端点定向回归共 17 个测试通过。另跑数字员工、系统看板和财务报告回归时 6 个通过、1 个既有周报窗口用例失败;该用例在周四写入“当前时间减 2 天”的单据,却断言它属于“上一完整周”,失败与本次改动无关。
|
||||||
|
- 影响:租户身份缺失时财务看板不再隐式读取 default 数据;预算展示职责被独立封装,后续继续扩展财务指标时不会把核心聚合模块推过项目 800 行硬上限。
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 22:29:记录 bug 修复:非生产财务回执会进入核心付款状态机,连接器配置缺少版本化生命周期审计,normalized payload 冗余保存完整单号。
|
||||||
|
- Git 提交检查:执行 `git fetch --all --prune` 后未发现 `HEAD..origin/main` 新提交;当前 `main` 相对 `origin/main` ahead 17,范围为 `661990b2..242d68c3`,包含认证、费用事件、AI 学习、审批任务/风险处置和迁移所有权等既有基础提交,本轮未合并或改写这些提交。
|
||||||
|
- 修改:`financial_connector_ingestion.py` 与新增 `financial_connector_simulation.py` 将 test/mock/staging 六类事件收口为 `simulation_only` 只读事实,禁止修改 Claim、对账、ERP、Business Event、归档和 Savings;生产 origin 查询只接受同来源生产事实,不能引用模拟结算触发冲回。
|
||||||
|
- 修改:`financial_connector_config_lifecycle.py`、`financial_connector_config_audit.py`、配置 schema/API 和 `FinancialConnectorConfigEvent` 新增带 expected version、认证 actor、request ID、reason 的 activate/disable/rotate 状态机;新配置只能 disabled 创建,激活/轮换前解析服务端 `secret_ref` 并校验 HMAC 密钥强度,审计前后快照不保存密钥引用或明文。
|
||||||
|
- 修改:`20260716_0020_financial_connector_config_lifecycle.py` 基于 0019 增加配置 version、复合租户约束和 PostgreSQL append-only 审计 trigger;受控移除历史 connector event normalized payload 中的完整 `claim_reference`,保留金额/币种、必要尾号和内容指纹,并把可能已有旧非生产副作用的响应标记为 `legacy_nonproduction_effect_unknown`,不伪装为新策略下的无副作用模拟事实。
|
||||||
|
- 修改:保留并验证 HMAC v2 对 tenant/provider/key version/timestamp/method/path 的绑定,拒绝共享密钥跨来源重放;ERP 回执按协议只要求 origin、Claim、金额和币种,不再错误强制重复支付参考号;空白密钥即使长度足够也按强度不足失败关闭。
|
||||||
|
- 操作:在独立一次性 PostgreSQL 17 容器完成空库升级到 0020、schema/约束/trigger 探针、并发激活单版本胜者、0019→0020 历史脱敏探针和完整升降级循环;验证完成后删除临时容器。同步更新模型注册、迁移所有权、preflight、HEAD revision、迁移断言及连接器 CONCEPT/TODO。
|
||||||
|
- 验证:容器内连接器/配置/费用价值链/迁移组合回归 `140 passed, 1 skipped`;PostgreSQL 连接器并发 `3 passed`;全新 PostgreSQL 完整迁移循环 `1 passed`,商业迁移 agent 在另一空库复跑同样 `1 passed`;Ruff、全树 `git diff --check` 均通过,连接器核心文件最大 509 行,低于 800 行硬上限。
|
||||||
|
- 影响:模拟、测试和预发布环境现在可以安全演练完整事件协议而不会改账;只有 `production_verified` 且精确匹配的回执能够推进付款、ERP 与 Savings。管理员可以安全激活、停用和轮换密钥,所有配置动作可追溯且不泄露密钥或完整单号。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 19:01:修复风险规则 Golden 评测的 FP/FN 统计颠倒、修订版本未进门禁以及异常/空用例默认放行问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||||
|
- 修改:`risk_rule_golden_evaluator.py` 将 false positive 改为“期望不命中但实际命中”,false negative 改为“期望命中但实际未命中”,Precision/Recall 分母恢复正确。
|
||||||
|
- 修改:初次发布和 revision 发布均强制执行 Golden 门禁;缺规则文档、缺 rule code、缺 active Golden case 或评测异常均 fail-closed。仅当 `GOLDEN_SET_GATE_ENABLED=false` 被显式配置时允许跳过,且仍写入 status=skipped 的 `AgentAssetTestRun`。
|
||||||
|
- 操作:在 `local-x-financial-linux` 容器中运行 Golden、发布、修订和安全自动化定向回归,并执行 Ruff;未修改财务规则 XLSX。
|
||||||
|
- 验证:Golden、release guard 和自动化资格组合回归 `29 passed`;并行定向发布/修订回归纳入总计 `62 passed`,评测异常、空用例和缺配置均留下 failed 记录并拦截发布。
|
||||||
|
- 影响:误报不再被错计为漏报,Precision/Recall 可用于可信的 Canary 与回滚判断;新规则和修订规则不能因评测器失败或没有黄金用例而静默上线。
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 2026-07-16 13:42:57 CST:在分层记忆回归测试中发现,AI 新建非 `default` 租户费用申请后会立即调用 `submit_claim()`,但此时尚未建立租户化 Expense Case Link;提交服务重新查询单据时按 Case Link 执行租户过滤,因此刚创建的申请返回不可见并报“未找到可提交的申请单”。
|
||||||
|
- 2026-07-16 13:42:57 CST:在 `UserAgentApplicationSlotMixin._create_expense_application_record()` 完成 Claim `flush` 后、进入草稿或提交分支前,使用当前认证租户同步建立 Expense Case 与 Claim Link,使新建记录、租户归属和后续提交保持在同一数据库事务;失败时仍由既有外层回滚,不留下半成品 Case 或 Link。
|
||||||
|
- 2026-07-16 13:42:57 CST:按项目规范执行 `git fetch --all --prune`、上下游状态与提交差异检查;当前分支相对 `origin/main` ahead 13,未发现新的上游提交,本地 ahead 包含最近的费用闭环、个人记忆、零录入票据和权威预审阶段提交。
|
||||||
|
- 2026-07-16 13:42:57 CST:在 Docker 容器 `local-x-financial-linux` 内运行原失败用例、分层记忆服务与组织记忆接口测试,结果 `9 passed`;验证非默认租户申请可完成预览提交并生成学习回执,组织记忆生命周期和租户隔离未受影响。
|
||||||
|
- 影响:修复 AI 助手、Steward 和 Orchestrator 共用申请创建链路在非默认租户下无法直接提交的问题;默认租户与既有草稿事件继续复用同一 Case,不改变确定性预审、预算和审批规则。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 22:33:记录 bug 修复:OnlyOffice 回调测试仍 patch 已拆分前的网络符号。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..@{u}` 为空,未发现 upstream 新提交;本地 `main` ahead 17。审批链相关提交为 `242d68c3`、`28b834ed`、`4940ebc4`,AI 报销学习与申请链相关提交为 `ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`,费用事件与事务链相关提交为 `5ed34c2b`、`1347366b`、`22669a90`、`a616b30c`、`661990b2`,另有迁移安全 `11275e4b` 与会话鉴权 `653eda05`;这些均为当前任务开始前已有的本地提交,本次没有改写或合并。
|
||||||
|
- 修改:把 `test_onlyoffice_callback_summary.py` 的网络 patch 目标切换到实际查找符号的 `agent_asset_onlyoffice` 模块,并删除对旧版本元数据方法及“回调自行生成 change_note”的过期假设;测试现在验证下载内容、回调用户和 `onlyoffice` 来源被完整委托给统一上传流程。
|
||||||
|
- 操作:没有在 `agent_assets` 中恢复底层 `urlopen` 兼容导出,因为该符号不是公共 API,且兼容别名也无法拦截拆分模块中的真实调用;差异摘要仍由 `upload_rule_spreadsheet` 统一生成和审计,已有服务测试覆盖其工作表/单元格统计。
|
||||||
|
- 验证:容器内回调定向测试通过;包含该测试的 Agent Run/租户/轨迹组合共 11 项通过,`test_agent_asset_service.py` 全部 28 项通过;测试文件 Ruff 与 `git diff --check` 通过。
|
||||||
|
- 影响:OnlyOffice 回调测试重新拦截真实网络边界,不会发出外部请求,也不会因内部模块拆分误报;生产回调与摘要生成职责保持不变。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 2026-07-16 14:29:13 CST:组织记忆创建在“检查 active → 新建 generation”之间存在竞态,后到请求可能静默压制并发产生的新版本;创建、更新的幂等键也没有绑定请求内容,撤销操作则无法在响应丢失后安全重试。
|
||||||
|
- 2026-07-16 14:29:13 CST:为企业/部门记忆增加租户与作用域级事务锁、幂等请求锁、请求 payload 指纹和数据库唯一约束;创建、更新、撤销现在都满足“同键同内容重放、同键不同内容 409”,新 generation 激活前严格核对预期 active 集合。锁协调职责拆到 `organization_memory_locks.py`,主服务从接近 800 行降至 761 行。
|
||||||
|
- 2026-07-16 14:29:13 CST:加固 `20260716_0009` 迁移:active 唯一索引只约束企业/部门作用域,DDL 前先检测重复 active 并 fail-fast,避免个人学习记忆换代受到影响;非 PostgreSQL 在任何结构变更前明确拒绝,避免 SQLite 留下半迁移列。
|
||||||
|
- 2026-07-16 14:29:13 CST:执行 `git fetch --all --prune`、工作区状态和上下游提交差异检查;`origin/main` 没有新增提交,当前分支 ahead 13,最近本地检查点为权威提交前预审、持久化零录入票据和个人申请记忆,本次未改写这些提交。
|
||||||
|
- 2026-07-16 14:29:13 CST:全部验证在 `local-x-financial-linux` 内执行并设置 60 秒超时。分层与组织记忆回归 44 项、迁移/模型/租户组合 70 项通过;前端记忆与申请链路 18 项通过,Vite 生产构建通过,变更 Python 文件 Ruff F/I 与 compileall 通过。一次性 tmpfs PostgreSQL 17 完整迁移循环 9 项通过,临时容器自动清理,开发数据库未修改。
|
||||||
|
- 影响:管理员并发维护组织记忆时不再发生静默覆盖,网络重试不会产生重复版本或把不同请求伪装成成功;个人记忆状态机保持原有最小样本与换代语义,迁移遇到脏数据时在写 DDL 前停止并给出明确错误。
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# 报销审批非参与者错误映射与陈旧测试契约
|
||||||
|
|
||||||
|
日期:2026-07-16
|
||||||
|
文档路径:document/development/2026-07-16/dev-logs/bugs/reimbursement-approval-task-access-error-mapping.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
- 21:38:记录 bug 修复:报销审批非参与者错误映射与陈旧测试契约。(bug-log:242d68c3)
|
||||||
|
- Git 提交检查:已手工执行 `git fetch --all --prune`,upstream `origin/main` 无本地尚未包含的新提交;当前分支 ahead 17 且工作区已有其他智能体和用户的未提交改动,因此未自动合并或变基。ahead 包括审批任务与风险链 `242d68c3`、`28b834ed`、`4940ebc4`,AI/费用学习与预审链 `ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`,Expense Case/时间线与事务链 `5ed34c2b`、`1347366b`、`22669a90`、`a616b30c`、`661990b2`,以及迁移安全 `11275e4b`、认证会话 `653eda05`。
|
||||||
|
- 修改:`reimbursement_approval_actions.py` 将审批任务访问策略抛出的 `LookupError` 显式映射为 404,避免非任务参与者通过报销审批/退回接口触发 500;`test_expense_claim_service.py` 与 `test_reimbursement_endpoints.py` 同步到正式审批任务的资源隐藏与任务冲突契约,并增加申请人审批、退回均无状态和动作账本副作用的 HTTP 回归。
|
||||||
|
- 操作:完整读取 `agent-change-log` Skill,沿 `ExpenseClaimActionProtocolMixin → ApprovalTaskLifecycleService → ApprovalTaskAccessPolicy` 诊断调用链;保留“非参与者不可读取任务”的权限语义,没有放宽审批人、管理员、申请人或租户边界;随后运行日志 helper 创建本记录并补齐实际证据。
|
||||||
|
- 验证:在 `local-x-financial-linux` 容器中,原失败用例与新增 HTTP 用例 2 项通过;审批任务、审批路由和报销接口组合 65 项通过;费用服务审批、退回和付款相关筛选回归 30 项通过;相关 Python 文件 Ruff F/I 检查通过。
|
||||||
|
- 影响:申请人或其他非任务参与者调用审批动作时稳定返回 404,不再出现服务端 500,也不会创建动作账本、审批事件或修改 Claim;可见但不可操作的任务仍按既有策略返回 403,状态/版本冲突继续返回 409。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# AI 发布门禁全局资产越权与快照损坏静默放行
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 19:48:记录 AI 分阶段发布的租户越权与运行时完整性失效修复。
|
||||||
|
- Git 提交检查:执行 `git fetch --all --prune` 后未发现 `HEAD..origin/main` 上游新提交;本地 `main` 比上游 ahead 17,最新为 `242d68c3 feat(approval): add task workflow and waiver decisions`,其余为审批安全、AI 费用学习、报销预审、迁移安全与会话认证等既有检查点,本次未合并或改写这些提交。
|
||||||
|
- 修改:`agent_asset_release_guard.py` 为所有发布查询、启动、评测、晋级和回滚入口增加平台全局资产管理边界;未绑定租户的共享资产只允许平台管理员管理,租户级 manager 统一返回不可见。`agent_asset_releases.py` 与既有风险规则发布入口只从认证上下文传递平台管理员权限,不能由请求体或自报 actor 绕过。
|
||||||
|
- 修改:`expense_claim_risk_rule_loader.py` 在候选快照与稳定快照均无法通过 SHA-256 完整性校验时生成强制阻断信号,`expense_claim_platform_risk.py` 将该信号转换为 critical/block 风险,而不是把损坏规则当成“未命中”静默跳过。
|
||||||
|
- 操作:仅通过 `apply_patch` 修改源码和测试;保护现有财务规则 XLSX、历史未跟踪开发目录及其他智能体改动,未执行提交、推送或数据库破坏操作。
|
||||||
|
- 验证:在 `local-x-financial-linux` 容器内运行 Ruff 定向检查通过;`test_agent_asset_release_guard.py` 与 `test_agent_asset_release_runtime.py` 共 12 项测试全部通过,新增覆盖租户 manager 无权管理全局发布资产、平台 admin 可管理,以及双快照损坏后报销自动流转被阻断。
|
||||||
|
- 影响:单租户管理员不再能影响所有企业共享的风险规则;受控发布元数据损坏时系统优先停流并提示平台恢复稳定版本,避免风险规则失效后继续自动审批。
|
||||||
|
|
||||||
|
- 19:53:继续修复发布质量指标可由管理人员手工伪造的问题。
|
||||||
|
- Git 提交检查:再次执行 `git fetch --all --prune`,`HEAD..origin/main` 仍无上游新提交;本地仍 ahead 17,提交范围与 19:48 检查一致,未自动合并、变基或覆盖共享工作区改动。
|
||||||
|
- 修改:新增 `agent_asset_release_monitor_auth.py`,评测请求必须使用至少 32 字节独立密钥,对时间戳、租户、资产、release ID、当前阶段和规范化请求体摘要做 HMAC-SHA256 签名;仅允许 5 分钟时钟窗口并使用常量时间比较。`agent_asset_releases.py` 在写入评测记录前验证签名,密钥未配置返回 503,缺失、过期或错误签名返回 401。
|
||||||
|
- 操作:保留服务层直接写入能力供同进程可信监控使用,但关闭普通 HTTP manager 仅凭自报数字写入“通过”证据的路径;签名绑定 release ID 和阶段,旧阶段请求不能在晋级后重复使用。
|
||||||
|
- 验证:容器内 Ruff 定向检查通过;发布 guard/runtime 共 12 项测试通过,HTTP 用例新增未签名评测返回 401,同时签名监控数据仍可触发 shadow→Canary→active 和指标越界自动回滚。
|
||||||
|
- 影响:人工管理权限与机器监控证据分离,发布门禁不再把未经认证的手工数字当成可信质量结果。
|
||||||
|
|
||||||
|
- 21:51:补齐“签名合法但指标仍可伪造”的第二层真实性修复,并接通真实证据自动回滚。
|
||||||
|
- Git 提交检查:执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 与 `git log @{u}..HEAD`;`origin/main` 无新提交,本地仍 ahead 17,最新提交仍为 `242d68c3 feat(approval): add task workflow and waiver decisions`,其余 ahead 提交为既有审批、AI 费用学习、报销预审、迁移安全和会话认证检查点。本次未合并、变基、提交或推送,也未触碰受保护财务规则 XLSX。
|
||||||
|
- 修改:Release Monitor HTTP 契约改为禁止额外字段的空触发请求,HMAC 继续绑定 tenant/asset/release/stage,但 `total`、`precision` 等质量数字只能由服务端查询 append-only observation/label 聚合;即使签名正确,携带伪造汇总字段也返回 422。
|
||||||
|
- 修改:真实 shadow/Canary/active manifest 执行追加脱敏 observation,类型化 `RiskDispositionEvent` 追加可信标签并即时触发 Guard;相同 release 聚合快照复用同一测试运行,低 precision 或结构化运行失败自动回滚 stable。即时监控失败由租户周期调度补偿,`collecting` 不写虚假 passed。
|
||||||
|
- 修改:新增 `0018` 两张发布遥测表、复合租户/release 外键、幂等唯一约束和 PostgreSQL append-only 触发器,并登记主 metadata、迁移所有权与启动前置检查;将风险处置发布同步拆到独立服务,使核心 `risk_dispositions.py` 保持 800 行以内。
|
||||||
|
- 操作:全部源码与文档通过 `apply_patch` 修改;用独立 `pgvector/pgvector:pg17` 一次性数据库验证完整迁移链及数据库约束,未连接或变更生产数据库。
|
||||||
|
- 验证:容器内发布 Guard/Runtime/Telemetry/Monitor/风险处置组合 44 项通过;调度器、Monitor 与风险处置定向 24 项通过;迁移/schema owner 静态回归 112 项、启动前置检查 77 项、一次性 PostgreSQL 完整迁移循环 1 项通过;PostgreSQL savings/commercial/financial connector/approval 并发探针共 14 项通过。Ruff 定向检查与 `git diff --check` 通过(文档回填后仍需最终全量复验)。
|
||||||
|
- 影响:发布质量门禁不再信任“会签名的调用方”提交的汇总数字,而是由数据库真实执行与可信人工结论生成;遥测暂时失败只会阻止晋级,不会撤销已成功人工处置或关闭现有 stable 保护。
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 2026-07-16 15:10:42 CST:修复风险观察列表和完整证据链读取范围过宽的问题。风险池仅允许管理员、财务、管理层和预算监控角色访问;单据完整风险证据只允许管理员或当前审批人查看,申请人和无权主体统一以 404 隐藏资源存在性,所有查询继续绑定可信租户。
|
||||||
|
- 2026-07-16 15:10:42 CST:修复风险反馈只有自由文本状态、无法可靠表达处置过程的问题。新增类型化判定与生命周期投影,区分确认风险、误报、补件、整改、豁免申请和完成处置;使用乐观版本、请求指纹、行锁和仅追加事件表保证并发与审计一致性,已完成状态不可重新判定。
|
||||||
|
- 2026-07-16 15:10:42 CST:修复前端读取错误 JSON 字段名导致证据、政策依据、贡献度和决策轨迹可能丢失的问题;风险证据卡改用服务端持久字段并展示处置状态、版本和明确动作,高风险卡不提供直接审批入口。
|
||||||
|
- 2026-07-16 15:10:42 CST:验证在 `local-x-financial-linux` 容器完成:风险观察、风险处置、审批工作台与风险门禁组合 26 项,前端审批/风险/动作协议定向 72 项,Vite 生产构建通过;一次性 PostgreSQL 验证了复合租户外键、唯一约束、仅追加触发器和非空降级保护。
|
||||||
|
- 2026-07-16 15:10:42 CST:Git 拉取检查已执行;上游没有本地尚未包含的新提交,当前分支相对上游 ahead 14。已有 ahead 提交均保留在修复上下文中,未修改用户正在维护的财务规则工作簿和历史未跟踪日志。
|
||||||
|
- 2026-07-16 15:33:03 CST:交叉审查后收紧处置动作语义:误报、补件和豁免申请必须提供服务端校验的说明,完成处置必须提供处理结果;版本冲突返回明确消息并触发前端刷新,已完成生命周期不再展示重复动作。
|
||||||
|
- 2026-07-16 15:33:03 CST:处置服务在 Claim 公共锁内重新确认当前审批人,Observation 和 Disposition 使用租户范围行锁及固定顺序;风险观察写入同样获取 Claim 锁。完整证据、风险池和处置权限分别执行最小授权,跨租户和无权资源继续以 404 隐藏。
|
||||||
|
- 2026-07-16 15:33:03 CST:最终验证全部在 `local-x-financial-linux` 容器完成并设置 60 秒超时;相关后端 174 项、迁移/所有权 54 项、前端 73 项与生产构建通过,一次性 PostgreSQL 17 的结构和并发验证 14 项通过。规则工作簿和历史未跟踪日志未被改动或纳入提交。
|
||||||
|
- 2026-07-16 15:44:39 CST:交叉审查发现风险处置重放在权限复核前读取当前 Disposition 及全部事件,旧 request_id 可能看到首次动作之后的处置状态。新增 `20260716_0012`:完整响应在 append-only Event 的 INSERT 前原子写入 `response_json`,重放只返回该不可变快照并将 `replayed=true`;不得通过 UPDATE 补写审计事件。
|
||||||
|
- 2026-07-16 15:44:39 CST:兼容 0012 前历史事件时,不回读当前 Disposition,只使用目标 Event 的 `after_json` 和 `version <= target` 的只追加事件重建;快照身份、租户、观察、处置、请求和版本任一不一致均 fail-closed。新增后续补件/整改后重放首个裁决的服务与 HTTP 回归,确认只返回版本 1 和当时事件。
|
||||||
|
- 2026-07-16 15:44:39 CST:容器内风险处置、迁移和前置检查 69 项通过、1 项条件跳过,审批快照与报销接口 29 项通过;一次性 tmpfs PostgreSQL 17 的 0012 Head 迁移与审批风险真实并发共 17 项通过,临时数据库自动清理且持久开发库未修改。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 2026-07-16 16:50:44 CST:修复首次风险尚无 disposition 时前端丢弃 Observation 顶层 `available_actions/read_only_reason`、导致“确认风险”等首次处置入口永久隐藏的问题。前端只消费服务端权威投影,缺少 disposition 时构造 version 0 展示模型,不进行角色猜测或本地越权推导。
|
||||||
|
- 2026-07-16 16:50:44 CST:修复页面持续打开跨过豁免到期点后仍显示“豁免有效”的问题。证据卡按 `waiver_expires_at` 注册到期刷新,切换单据和卸载时清理定时器;服务端过期门禁继续 fail-closed,前端不再长期展示过期授权。
|
||||||
|
- 2026-07-16 16:50:44 CST:修复风险豁免界面只展示当前快照、拒绝后重新申请会让上一轮审计信息消失的问题。服务保留 actor、request ID、before/after JSON,记录组件展示申请、批准、拒绝的追加事件、版本、人员、时间、原因、申请关键字段和请求 ID。
|
||||||
|
- 2026-07-16 16:50:44 CST:风险豁免申请、批准、拒绝和到期后的任务投影刷新与风险处置同事务执行,保持 Claim → Observation → Disposition → Task 固定锁顺序;刷新风险、证据、批量资格和优先级时保留 SLA 时间窗与显式升级原因。
|
||||||
|
- 2026-07-16 16:50:44 CST:容器内风险处置、门禁与审批投影定向 `30 passed`,扩展回归 `39 passed, 3 skipped`,前端风险专项 `12 passed`,生产构建通过;一次性 PostgreSQL 并发验证包含风险重新打开与审批竞争,未发现绕过或脏写。
|
||||||
|
- 2026-07-16 16:50:44 CST:Git 拉取检查确认上游无新增提交、本地 ahead 16;本记录基于当前未提交修复,未纳入用户维护的财务规则工作簿和历史开发日志。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 19:19:修复商业计量迁移上线后,标准调整 Savings 回填脚本因精确锁定旧版本而错误拒绝执行的问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||||
|
- 修改:`backfill_standard_adjustment_savings.py` 不再要求当前 revision 必须精确等于 `20260716_0015`,改为遍历 Alembic `down_revision` 祖先链,只有当前迁移确实包含 Savings 数据契约时才允许回填。
|
||||||
|
- 修改:`test_standard_adjustment_savings_backfill.py` 覆盖所需版本本身、后继商业迁移 `0016`、更旧版本和未迁移数据库四类边界。
|
||||||
|
- 操作:全部检查均在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX 或历史开发文档。
|
||||||
|
- 验证:Ruff 通过;标准调整 Savings 回填定向回归 `12 passed`。
|
||||||
|
- 影响:数据库升级到 `0016` 及未来合法后继版本后仍可安全执行历史节省回填;更旧、未迁移或不包含目标契约的版本仍会 fail-closed。
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 19:24:修复历史费用偏离分析可能读取分析截止时间之后才冻结的基线、形成时间穿越的问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||||
|
- 修改:`savings_insight_analysis.py` 在历史基线查询中增加 `frozen_at <= as_of`,确保候选分析只能使用截止时间当时已经存在的冻结快照。
|
||||||
|
- 修改:`test_savings_baseline_insights.py` 新增未来冻结基线回归;确认这类快照不会产生历史价格偏离候选,并返回基线不可用的数据质量提示。
|
||||||
|
- 操作:全部检查均在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX 或历史开发文档。
|
||||||
|
- 验证:Savings 基线/洞察、端点与 CFO 组合回归 `10 passed`;相关 Ruff 检查通过。
|
||||||
|
- 影响:`as_of` 历史回放不再引用未来才生成的知识,CFO 候选洞察和审计结果保持时间一致性。
|
||||||
|
|
||||||
|
- 22:50:继续修复预算预测读取分析窗口或 `as_of` 之后预算配置与核销流水的问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地仍相对 upstream ahead 17 个既有提交(`242d68c3` 至 `661990b2`,内容为审批任务/风险处置、AI 学习与预审、Expense Case、迁移安全和认证等共享能力),未发现新的上游提交,也未合并或改写共享工作树。
|
||||||
|
- 修改:`savings_insight_budget.py` 以 `min(as_of, window_end)` 作为预算分析截止点,只读取当时已经创建且最后更新的预算配置,以及预算期间开始至截止点的核销/回滚事实;部门范围优先使用稳定部门 ID,缺 ID 才使用部门名称或成本中心。
|
||||||
|
- 修改:`test_savings_baseline_insights.py` 增加窗口后核销、截止时间后配置、稳定重放与零机会副作用回归。
|
||||||
|
- 操作:所有 pytest 和 Ruff 均在 `local-x-financial-linux` 容器内以 60 秒超时执行;未接触财务规则 XLSX、商业/连接器/发布迁移或迁移 HEAD。
|
||||||
|
- 验证:Savings/CFO 定向组合 `34 passed, 6 skipped`(6 项为未配置 PostgreSQL 专用测试 URL 的预期跳过),相关 Ruff 检查通过。
|
||||||
|
- 影响:历史预算预测不再使用报告窗口之后才出现的配置或交易,异常归因、政策模拟候选与 CFO 审计回放保持同一时间边界。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 18:11:记录 bug 修复:接受住宿职级标准调整时不再信任客户端金额、天数或金额快照。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 未发现 upstream 新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3 feat(approval): add task workflow and waiver decisions`、`28b834ed fix(approval): replay immutable action responses`、`4940ebc4 feat(approval): add safe risk disposition workflow`、`ee88a36b feat(ai): add tenant-safe hierarchical expense learning`、`6bdf65bc feat(expenses): add authoritative pre-review workflow`、`ae3f02c3 feat(expense): add persistent zero-entry receipt association`、`54754b55 feat(ai): add personal expense application memory`、`211f85d9 feat(ai): unify verified expense application workflow`、`5b246307 feat(ai): issue verified application preview decisions`、`a662cfe6 feat(ai): add expense application feedback ledger`、`5ed34c2b feat(expenses): backfill historical claims into expense cases`、`11275e4b fix(migrations): enforce schema ownership safety`、`1347366b feat(expenses): secure timeline and draft events`、`22669a90 feat(expenses): show unified expense event timeline`、`a616b30c fix(expenses): unify AI application submission transaction`、`653eda05 feat(auth): add opaque bearer sessions`、`661990b2 feat(expenses): add transactional expense case events`;这些提交均早于本次未提交修复,本次没有拉取、合并或覆盖共享工作树。
|
||||||
|
- 修改:`expense_claim_standard_adjustment.py` 只从已锁定的 `ExpenseClaimItem.item_amount` 读取原始金额,只接受服务端差旅规则计算出的最终可报金额;客户端携带的 `application_days`、`original_amount`、`reimbursable_amount` 仅作为旧界面兼容展示字段,不参与计算。服务端快照新增规则名、规则版本(无发布版本时使用内容指纹)、地点、匹配城市、职级、职级档、天数、每日住宿标准、住宿标准总额及计算指纹。
|
||||||
|
- 修改:为标准调整增加 PostgreSQL advisory lock + Claim/Item 行锁和非 PostgreSQL 进程内串行锁;请求支持 `request_id` 幂等键与 `expected_updated_at` 乐观前置条件。相同请求直接重放且不改 `created_at`/计算快照,同请求号改选其他明细会被拒绝;单次调整只替换被选明细的快照,不再误删其他明细已接受的标准调整。
|
||||||
|
- 操作:在 `local-x-financial-linux` 容器及 `/tmp/x-financial-server-venv` 中运行定向、接口全量和服务全量测试;运行 scoped Ruff 与 `git diff --check`,未在宿主机运行 Python/pytest,也未修改规则表或其他用户文件。
|
||||||
|
- 验证:标准调整定向回归(含非 PostgreSQL 同租户同单据锁竞争)`9 passed`;`test_reimbursement_endpoints.py` 全量 `22 passed`;Scoped Ruff 与 `git diff --check` 通过。`test_expense_claim_service.py` 全量为 `112 passed, 8 failed`,8 个失败均位于既有审批任务配置/旧错误文案断言(直属领导任务、费用申请提交、本人审批、重复退回),不经过标准调整实现;本次新增及关联标准调整用例全部通过。
|
||||||
|
- 影响:伪造低原金额、任意可报金额或超长住宿天数不能降低或抬高实际报销额;规则缺失时整次操作失败关闭且不改金额。审批人看到的原额、可报额和差额均可追溯到数据库明细与服务端规则证据,重复点击和并发请求不会重写金额证据。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 2026-07-16 14:29:13 CST:历史风险观测、few-shot 样本、Qdrant 向量和规则生成链路原先缺少完整租户、场景及制度版本边界;Hermes 全局扫描还可能把不同租户的 Claim 放进同一风险图,历史检索结果若直接公开也可能泄露样本 ID、费用单号或人工评语。
|
||||||
|
- 2026-07-16 14:29:13 CST:风险观测与 few-shot 关系数据改为租户复合唯一,风险接口、反馈、查询和 Hermes 图构建按认证租户隔离;Qdrant 写入与检索显式携带 tenant、scene、policy_ref、rule_version,使用稳定向量 ID并清理旧向量,命中后再由关系库校验租户、状态和版本。风险规则生成与再生成从认证用户透传租户,缺失租户时禁用历史注入而不回落到 default。
|
||||||
|
- 2026-07-16 14:29:13 CST:报销预审新增只读 `historical_case_evidence`,检索或向量服务异常时降级为空证据。公开协议和 AI 助手只返回固定的“历史已确认/历史误报,仅供复核”标签与脱敏摘要,不返回 sample ID、claim_no、人工评论或历史结论原文;历史证据不参与 review ID、确定性 findings、passed、blocking count、预算复核或审批路由计算。
|
||||||
|
- 2026-07-16 14:29:13 CST:加固 `20260716_0008` 迁移:`RiskObservation.claim_id` 明确改为旧 Claim 表的 view-only 软引用,使空库与旧表采用路径的 Head 结构一致;降级若发现非默认租户或非空 policy/rule 版本数据则在 DDL 前 fail-fast,禁止静默丢失隔离和版本信息;非 PostgreSQL 在变更前明确拒绝。
|
||||||
|
- 2026-07-16 14:29:13 CST:执行 `git fetch --all --prune`、工作区状态和上下游提交差异检查;`origin/main` 没有新增提交,当前分支 ahead 13,最近本地检查点为权威提交前预审、持久化零录入票据和个人申请记忆,本次未改写这些提交。
|
||||||
|
- 2026-07-16 14:29:13 CST:全部验证在 `local-x-financial-linux` 内执行并设置 60 秒超时。历史案例、风险观测、风险图、预审和规则生成组合 90 项,报销接口与费用服务 137 项,补充 Golden/规则历史注入 28 项通过;迁移/模型/租户组合 70 项通过,Ruff F/I、compileall 和 `git diff --check` 通过。一次性 tmpfs PostgreSQL 17 完整迁移循环 9 项通过,临时容器自动清理,开发数据库未修改。
|
||||||
|
- 影响:同名用户、相同样本键或相似向量不能跨租户读取、覆盖或进入 Prompt;历史案例开始帮助预审与复核,但只以脱敏、可降级、非决策证据存在,不会扩大 AI 自动放行或审批权限。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 19:37:修复 Workbench AI 超大运行时导致职责耦合,以及前端回归测试仍绑定旧单体文件和宿主机缺失 Pillow 的问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||||
|
- 修改:将会话滚动、流式响应、持久化/重置提取到 `useWorkbenchAiConversationRuntime.js`,把模型意图规划、低置信确认和多任务衔接提取到 `useWorkbenchAiIntentExecution.js`;`usePersonalWorkbenchAiMode.js` 降至 768 行。
|
||||||
|
- 修改:Workbench、会话删除、报销关联与快速申请预览测试改为联合审计入口与职责模块;视觉验证复用容器已有 ImageMagick,保留像素和动画断言,不再依赖未安装的 Pillow。
|
||||||
|
- 操作:全部 node 测试和构建均在 `local-x-financial-linux` 容器内执行,未修改后端或财务规则 XLSX。
|
||||||
|
- 验证:Workbench AI/会话组合 `85 passed`,快速申请预览 `67 passed`;Vite 生产构建成功(2229 modules);`git diff --check` 通过。
|
||||||
|
- 影响:会话清理、详情智能录入、关联门禁和申请预览的回归测试不再因内部职责迁移误报,核心运行时恢复到项目 800 行硬上限内。
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# 零录入票据归集跨租户隔离与失败回滚
|
||||||
|
|
||||||
|
日期:2026-07-16
|
||||||
|
文档路径:document/development/2026-07-16/dev-logs/bugs/zero-entry-receipt-tenant-and-rollback.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
- 09:35:记录 bug 修复:零录入票据归集跨租户隔离与失败回滚。(bug-log:54754b55)
|
||||||
|
- Git 提交检查:2026-07-16 09:35 CST 执行 `git fetch --all --prune` 成功;`origin/main` 没有远端新提交;本地 ahead 11 条,依次是 `54754b55` 个人费用申请记忆、`211f85d9` 统一核验申请工作流、`5b246307` 申请预览决策、`a662cfe6` 申请反馈账本、`5ed34c2b` 历史费用单回填、`11275e4b` 迁移所有权安全、`1347366b` 费用时间线与草稿事件安全、`22669a90` 统一费用事件时间线、`a616b30c` AI 申请提交事务、`653eda05` Bearer 会话、`661990b2` 事务化费用事件;这些提交均为本轮开始前已有的本地检查点,本次未合并或改写历史。
|
||||||
|
- 修改:`receipt_folder.py` 把票据命名空间从仅用户名收口为租户与用户联合边界,并用稳定摘要消除有损清洗碰撞;`attachment_association_jobs.py` 同时校验任务租户和 owner,平台管理员也不能跨租户读取。`expense_receipt_matcher.py` 改用纯只读本人 Claim 查询并要求每份票据独立满足证据门槛。`expense_receipt_association.py` 将 Claim、Case、Link、业务事件、票据元数据和新旧附件目录纳入统一失败补偿,避免事件或文件中途异常留下半关联状态。
|
||||||
|
- 操作:新增持久化 `attachment_association_jobs` 模型、仓储和 `20260716_0006` 迁移;任务按租户、owner、票据集合和 generation 去重,运行态使用租约、`attempt_count + running` 栅栏、进程锁和 PostgreSQL advisory lock。同票据与同 Claim 分别串行化,Claim 锁内清理旧事务并重新匹配;待确认或失败任务保留原代审计历史,以新 generation 重新评估,自动关联成功代继续幂等复用。低置信、相近候选、混入无关票据、无草稿和仅有申请统一返回需要确认的零写入结果;同步扩展前端任务协议、会话恢复、候选卡片、幂等重放和成功风险复核提示,并更新功能 CONCEPT/TODO。
|
||||||
|
- 验证:所有后端验证均在项目主容器内执行并设置 60 秒超时。后端归集专项 20 项、归集与相邻服务及迁移所有权组合回归 71 项通过,覆盖纯只读确认、逐票据证据、事件失败、文件元数据失败、既有申请 Case 保留、同票据并发、不同票据并发同 Claim、租约过期恢复、旧 worker/迟到回调栅栏、待确认重评估、失败代际历史保留、普通用户及管理员跨租户隔离;前端关联链路组合回归 29 项通过;相关 Python 文件 `ruff --select F,I,UP` 通过;Vite 生产构建通过。一次性 tmpfs PostgreSQL 17 中完整迁移循环 4 项通过,临时数据库已清理,持久开发库未修改。
|
||||||
|
- 影响:同用户名在不同租户的票据、任务和候选 Claim 不再互相可见;自动归集中途失败不会留下已改 Claim、孤立 Case/Link/Event、错误票据状态或被覆盖的旧附件目录;低证据和混合票据批次只提示确认,不扩大自动化权限;任务重启或多 worker 后仍可查询和恢复。非默认租户原先落在旧用户名目录中的历史票据改为 fail-closed,不做未经确认的跨目录迁移。
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
# AI 分阶段发布真实遥测 概念文档
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
把真实 shadow/Canary 规则执行、正负样本盲审真值和服务端保守聚合串成可审计证据链,只有精确率、召回率下界与运行质量同时满足门禁时才允许晋级,越界时自动回滚到稳定版本。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
风险规则已经具备 Golden Case、`shadow → canary → active → rolled_back` 状态机、稳定流量路由和自动回滚能力,但线上评测仍有一个关键证据缺口:`ReleaseEvaluationInput` 由外部调用方直接提交 `total`、`failure_count`、`precision` 和 `baseline_precision` 汇总数字,Release Guard 无法证明这些数字来自哪一次真实规则执行、哪一个租户、哪一个 release,也无法证明精度分子和分母来自可信人工结论。
|
||||||
|
|
||||||
|
现有运行与学习链路的事实边界如下:
|
||||||
|
|
||||||
|
- `ExpenseClaimRiskRuleLoader` 能按租户和稳定路由键选择 stable、shadow 或 Canary 候选版本,并在快照损坏时保守阻断。
|
||||||
|
- `evaluate_platform_risk_rules()` 会返回 shadow 候选的 `asset_id / rule_code / rule_version / release_stage / hit / severity`;Canary 命中会进入带版本和阶段的风险 flag,但当前返回值不会持久化成发布样本。
|
||||||
|
- `RiskDispositionEvent` 是类型化、租户化、只追加的人工处置事实,`confirm` 和 `false_positive` 可作为正例命中的可信真值来源。
|
||||||
|
- `RiskObservationFeedback` 的自由评论、`AIDecisionFeedback` 和 `WorkflowOutcome` 可以支持业务学习,但它们没有同时绑定 `asset_id + release_id + stage + version`,不能直接作为某次发布的精度标签。
|
||||||
|
- Release Monitor 的 HMAC 能认证请求来自持有共享密钥的调用方,并限制传输重放;它不能证明请求体中的汇总数字由真实数据库 observation 和人工 label 计算得出。
|
||||||
|
|
||||||
|
因此,线上门禁必须从“相信外部汇总数字”改为“服务端从只追加事实计算指标”。本能力是 `ai-data-flywheel` 在线质量闭环和 `ai-expense-closed-loop-and-value-proof` 分阶段发布目标的证据层,不替代离线 Golden Case。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- [G1] 为每次真实候选规则执行记录租户、资产、release、阶段、版本、规则、命中、基线命中和结构化运行状态。
|
||||||
|
- [G2] 将专用发布复核或数据库中可信 `RiskDispositionEvent` 转换成只追加的正例判断;将独立盲审转换成与模型预测语义分离的 `risk_present / risk_absent` 真值。
|
||||||
|
- [G3] 使用稳定幂等键处理同一执行或人工动作的安全重放,不同内容复用同一来源时拒绝冲突。
|
||||||
|
- [G4] 从真实 observation 和最新可信 label 聚合运行总量、运行失败、候选 precision 和基线 precision。
|
||||||
|
- [G5] 未标注候选命中保持 `collecting`,不得把“没有人工结论”当成成功或正确。
|
||||||
|
- [G6] 聚合结果可转换成现有 `ReleaseEvaluationInput`,供 shadow/Canary 门禁、自动晋级和自动回滚使用。
|
||||||
|
- [G7] 全链路租户隔离、数据脱敏、append-only,并拒绝跨租户和陈旧 release/stage/version 标签。
|
||||||
|
- [G8] 对候选未命中人群实施分层盲审:基线命中分歧样本全量复核,其余负样本按不可预测稳定分数随机抽检;证据不足时 recall/FN 仍显式不可用。
|
||||||
|
- [G9] 使用抽样漏检率估计总体 FN,并以 Wilson 上界反推保守召回率下界;发布门禁只使用下界,不把点估计冒充确定事实。
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- [NG1] 不用线上遥测替代离线 Golden Case;前者验证真实分布,后者验证覆盖明确预期的回归集合。
|
||||||
|
- [NG2] 不把候选未命中直接判为 false negative,也不从“后续未退回”反推规则正确。
|
||||||
|
- [NG3] 不保存报销事由、票据内容、人工评论、单号、操作者账号原值或原始风险 payload。
|
||||||
|
- [NG4] 不接受客户端、浏览器或普通管理员提交的 precision/recall 作为发布真值。
|
||||||
|
- [NG5] 不因为 HMAC 校验通过就信任汇总数字;HMAC 只解决传输来源和重放,不解决数据生成真实性。
|
||||||
|
- [NG6] 不让通过质量门禁的规则绕过审批、风险处置或资金动作的人类控制。
|
||||||
|
- [NG7] 不把遥测或自动回滚做成绕开共享风险循环、迁移所有权、审批控制或稳定版本保护的旁路。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
### 用户
|
||||||
|
|
||||||
|
1. 风险运营/规则管理员:查看某个 release 的真实样本数、待标注数、误报率和基线比较,决定是否继续采集或人工回滚。
|
||||||
|
2. 财务审计/审批人:在既有风险处置或专用发布复核队列中给出类型化确认/误报结论,不接触发布汇总公式。
|
||||||
|
3. Release Monitor:只从数据库聚合当前 release,满足证据条件后把服务端计算结果交给 Release Guard。
|
||||||
|
4. 平台审计员:按租户、release、版本和来源指纹回放 observation、label、评测与状态转换,不读取业务正文。
|
||||||
|
|
||||||
|
### 核心场景
|
||||||
|
|
||||||
|
1. shadow 阶段同时执行 stable 和候选规则;记录候选 hit/miss,并记录同一单据上 stable 是否命中。
|
||||||
|
2. 候选或 stable 命中进入人工复核;`confirm` 表示风险事实成立,`false_positive` 表示该次正向命中是误报。
|
||||||
|
3. 每条成功 observation 同步决定盲审层:候选命中全量入队、候选未命中但基线命中全量入队、双方均未命中按发布时冻结比例随机入队。
|
||||||
|
4. 复核队列混排正负样本,只提供业务单据、规则和业务阶段,不返回 candidate/baseline hit;复核人只回答“存在真实风险/确认无该风险”。
|
||||||
|
5. 所有候选未命中样本必须由两个不同复核人给出一致结论;同一人的重复动作不增加法定票数,冲突时继续 collecting。
|
||||||
|
6. 候选命中仍有任何未标注项时,聚合状态保持 `collecting`,Release Monitor 不提交通过评测。
|
||||||
|
7. 候选正例全部标注后,服务端计算 candidate precision;基线正例也全部标注时才计算 baseline precision。
|
||||||
|
8. 负样本达到最小独立复核量后,服务端分别披露实际观察 FN、总体估计 FN、FN 置信上界、recall 点估计和 recall 置信下界。
|
||||||
|
9. Canary 路由中的候选 hit、miss 和结构化运行失败继续追加;错误率、precision 或 recall 下界越界时 Release Guard 自动回滚稳定快照。
|
||||||
|
10. release 已晋级、回滚或被新 release 替代后,旧 observation/sample/label 仍保留,但不能再接收新标签或作为当前晋级输入。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- [C1] 运行样本生产:消费现有 `shadow_evaluations`、Canary/active flag,并提供 manifest 执行循环级 hook 记录 Canary 未命中和结构化失败。
|
||||||
|
- [C2] 可信标签:支持认证发布复核动作和数据库中真实 `RiskDispositionEvent`;不接受评论文本作为标签。
|
||||||
|
- [C3] 保守聚合:分别计算 observation、completed、runtime failure、candidate hit/labeled/pending、baseline hit/labeled/pending。
|
||||||
|
- [C4] 门禁转换:仅 `ready` 聚合可生成 `ReleaseEvaluationInput`;`collecting` 调用转换时明确拒绝。
|
||||||
|
- [C5] 证据隔离:运行来源、标签来源、操作者均只保存租户内、带密钥版本的
|
||||||
|
HMAC-SHA-256 指纹;原始 claim、事件和账号值不进入遥测表。
|
||||||
|
- [C6] 幂等与冲突:同一租户、release、阶段、版本、规则和来源形成稳定键;相同重放复用首次记录,不同载荷冲突。
|
||||||
|
- [C7] 历史不可变:observation 与 label 均只追加;标签纠正追加新 label,聚合取最新可信标签,不原地覆盖旧事实。
|
||||||
|
- [C8] 盲审抽样:候选正例和候选/基线分歧样本全量入队,其余负样本按发布策略的 `negative_sample_percent` 与 HMAC 来源伪名生成稳定随机分数。
|
||||||
|
- [C9] 真值盲化:样本表不保存明文单据 ID,业务来源加密保存;API 不返回 candidate/baseline hit,前端也拒绝接收预测字段。
|
||||||
|
- [C10] 保守召回:证据未满足时 `false_negative_count / estimated_false_negative_count / recall / recall_lower_bound` 保持 `null`;满足后分别披露,不用点估计替代下界。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 证据链与自动判定
|
||||||
|
|
||||||
|
```text
|
||||||
|
[真实 stable / candidate 执行]
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[append-only Observation]
|
||||||
|
tenant + asset + release + stage + version + hit + runtime status
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[append-only Audit Sample]
|
||||||
|
正例全量 + 分歧全量 + 其余负例稳定随机抽样
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[盲化可信 Label]
|
||||||
|
typed disposition / release review / blind release review
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[服务端 Aggregate]
|
||||||
|
runtime + precision + baseline + sampled FN + recall lower bound
|
||||||
|
│
|
||||||
|
┌───────┴────────┐
|
||||||
|
│ collecting │ ready
|
||||||
|
▼ ▼
|
||||||
|
[继续采集/告警] [ReleaseEvaluationInput]
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[Release Guard 判定]
|
||||||
|
shadow → canary → active
|
||||||
|
或自动 rolled_back
|
||||||
|
```
|
||||||
|
|
||||||
|
该链路中的每一层只消费上一层可验证的结构化事实。离线 Golden Case 仍是进入 shadow 前的回归门禁;线上 telemetry 是进入 Canary、active 及运行中回滚的分布证据,两者不能互相替代。
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 发布控制台分别展示运行样本、候选待标注、候选/基线 precision、运行失败、负样本池/抽样/积压、实际 FN、估计 FN、FN 上界、recall 点估计和置信下界。
|
||||||
|
- 盲审队列只接收服务端安全字段;`candidate_hit / baseline_hit` 即使误入响应也不会进入页面状态。
|
||||||
|
- 复核按钮使用中性业务语义“存在真实风险 / 确认无该风险”,不使用“模型命中/误报”暗示预测;来源单据在新标签页打开并隔离 opener。
|
||||||
|
- `null` 指标统一显示“证据不足/暂不可用”,不得渲染成 0;`collecting`、`ready`、`failed/rolled_back` 使用不同状态。
|
||||||
|
- `collecting`、`ready`、`failed/rolled_back` 使用不同状态,不把空样本或缺标签显示为 100%。
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- `AgentAssetReleaseTelemetryService.record_expense_risk_result()` 可从当前风险评测返回值生产 shadow 样本和 Canary/active 命中样本。
|
||||||
|
- `record_manifest_evaluation()` 设计为风险 manifest 执行循环 hook,可记录 Candidate 未命中及 `evaluator_error / artifact_integrity_error / unsupported_evaluator / timeout` 等结构化失败。
|
||||||
|
- `record_review_label()` 只接收类型化 label、认证 actor ID 和 request ID;actor 与 request 进入表前被指纹化。
|
||||||
|
- `AgentAssetReleaseSamplingService` 在 observation 同一事务内完成分层选择与来源加密;加密失败会连同 observation 一起回滚,避免留下无法复核的孤立事实。
|
||||||
|
- `AgentAssetReleaseReviewService` 只查询当前租户、当前 release 的样本,混排后输出去预测队列;发布发起人不可自审,负样本强制两个不同 actor。
|
||||||
|
- `agent_asset_release_aggregation` 将精度和召回证据拆开聚合;`agent_asset_release_recall` 使用随机层漏检率与 Wilson 上界生成总体 FN 估计和 recall 下界。
|
||||||
|
- `record_risk_disposition_label()` 会重新查询数据库中的同租户 `RiskDispositionEvent` 和 `RiskObservation`,校验 action、claim/rule 来源指纹和版本,不信任调用方提供的人工结论副本。
|
||||||
|
- `aggregate()` 只读取同租户、同资产、同 release、同阶段、同版本事实,并验证它仍是资产当前 release。
|
||||||
|
- `to_release_evaluation_input()` 只允许 `ready` 聚合转换;未标注、无候选正例或空样本会抛出 collecting 错误。
|
||||||
|
- `AgentAssetReleaseMonitor` 与周期调度器只在服务端查询事实并调用上述方法;HTTP 入口只接受签名触发,不再接收外部汇总数字。
|
||||||
|
- Agent 资产基础 CRUD/表格/版本接口与风险规则生成、测试、启停和发布子路由分离;
|
||||||
|
资产版本只读投影由独立序列化 mixin 承担,Release Guard 只编排状态与持久化,
|
||||||
|
阈值归一化和质量门禁计算下沉为无数据库副作用的纯策略模块。
|
||||||
|
|
||||||
|
### 算法/规则
|
||||||
|
|
||||||
|
- shadow 同时保留候选与 stable 的命中信息,用同一人工真值分别估计 candidate precision 和 baseline precision。
|
||||||
|
- Canary 使用稳定路由键分流;必须在 manifest 执行循环记录候选 miss,否则只从最终 flag 采集会产生“只有命中样本”的选择偏差。
|
||||||
|
- candidate 正向命中使用 `confirmed / false_positive` 计算 precision;盲审统一使用 `risk_present / risk_absent` 描述业务真值,再在聚合层规范化,不向复核人暴露预测结论。
|
||||||
|
- candidate miss 只有进入服务端抽样表并完成独立双人盲审后才可形成 FN/TN 证据;未抽中的个体不能直接被标签,也不能由“后续无退回”反推正确。
|
||||||
|
- 运行失败与业务误报分开:`failure_count` 表示 evaluator/快照/超时等结构化运行失败,`false_positive_count` 只进入 precision。
|
||||||
|
- 阶段最小样本数、最大错误率、最低 precision 和最大 precision drop 仍由 `ReleaseGuardPolicy` 统一判定。
|
||||||
|
- `ReleaseGuardPolicy.reviewer_quorum` 以 `1..2` 的受控整数随 release 策略固化,
|
||||||
|
telemetry 只统计不同 actor 指纹的最新票;票数不足或不同复核人结论冲突时保持
|
||||||
|
`collecting`,不会把部分意见交给 Release Guard。
|
||||||
|
- 新发布默认开启 recall 门禁,并冻结 `negative_sample_percent / negative_min_reviewed / min_recall / recall_confidence_level`;旧 release 未携带开关时保持兼容,不追溯伪造历史抽样事实。
|
||||||
|
- recall 门禁只比较 `recall_lower_bound` 与 `min_recall`;点估计再高,只要保守下界不足也不能晋级。明显低 precision 可直接失败,不必等待召回样本凑齐。
|
||||||
|
|
||||||
|
### 数据
|
||||||
|
|
||||||
|
#### `agent_asset_release_observations`
|
||||||
|
|
||||||
|
- 身份:`tenant_id / asset_id / release_id / stage / version / rule_code`。
|
||||||
|
- 运行事实:`candidate_hit / baseline_hit / runtime_status / failure_code / business_stage`。
|
||||||
|
- 脱敏来源:`source_kind / source_fingerprint`;不保存 claim ID、单号或业务正文。
|
||||||
|
- 一致性:租户幂等键唯一,保存 payload fingerprint;同一来源不同内容冲突。
|
||||||
|
|
||||||
|
#### `agent_asset_release_labels`
|
||||||
|
|
||||||
|
- 身份复制:tenant、observation、asset、release、stage、version,并通过复合外键绑定原 observation。
|
||||||
|
- 标签:正例判断使用 `confirmed / false_positive`,盲审真值使用 `risk_present / risk_absent`。
|
||||||
|
- 来源:`typed_risk_disposition / release_review / blind_release_review`;数据库组合约束禁止标签语义与来源交叉使用。
|
||||||
|
- 历史:只追加;纠正写新行,不修改或删除旧标签。
|
||||||
|
|
||||||
|
#### `agent_asset_release_audit_samples`
|
||||||
|
|
||||||
|
- 身份复制:tenant、observation、asset、release、stage、version,通过复合外键绑定原 observation。
|
||||||
|
- 分层:`candidate_positive_census / candidate_disagreement_census / candidate_negative_random`。
|
||||||
|
- 抽样事实:保存入样概率和稳定选择分数;同租户 observation 最多一条样本,重放必须匹配 payload fingerprint。
|
||||||
|
- 来源保护:原始单据引用使用 SecretBox 加密,表中不保存明文;只有通过租户与复核角色检查的队列读取才解密。
|
||||||
|
|
||||||
|
三类模型同时具备 ORM 层 UPDATE/DELETE 拒绝。`0018` 建立 observation/label,后继 `0023` 建立 audit sample、扩展标签约束并复用 PostgreSQL append-only 触发器;存在盲审事实或新标签语义时拒绝有损降级。
|
||||||
|
|
||||||
|
### 权限
|
||||||
|
|
||||||
|
- 所有写入、读取和聚合以 `tenant_id` 为第一条件;租户绑定资产不允许其他租户观察或标签。
|
||||||
|
- 平台共享资产可为不同租户分别保存 observation/label,但各租户样本和 precision 不混算。
|
||||||
|
- 标签前重新校验资产当前 `release_id + stage + candidate_version`;旧 release、已晋级阶段或已回滚阶段拒绝新增标签。
|
||||||
|
- 类型化处置标签必须来自数据库中真实存在且同租户的 `RiskDispositionEvent`,action 只允许 `confirm / false_positive`。
|
||||||
|
- 专用复核队列只允许 `manager` 或 `admin`;租户和 actor 全部来自认证上下文,
|
||||||
|
跨租户资产返回 404,非复核角色返回 403,发布发起人自审返回 400。
|
||||||
|
- 标签写入必须携带 `X-Request-Id`;新客户端只发送 `risk_present / risk_absent`,旧客户端的 `confirmed / false_positive` 仅在复核 API 边界映射为盲审真值。标签、
|
||||||
|
actor 指纹和 request 来源只追加保存,客户端不能覆盖 tenant、release 或 actor。
|
||||||
|
- `reviewer_quorum=2` 时必须由两个不同复核人给出相同结论;同一人的重复提交不增加票数,
|
||||||
|
冲突结论进入待仲裁状态并继续阻止晋级。
|
||||||
|
|
||||||
|
### HMAC 与数据真实性边界
|
||||||
|
|
||||||
|
- HMAC 可以证明传输请求由持有密钥的一方生成、请求在允许时间窗口内且签名未被修改。
|
||||||
|
- HMAC 不能证明调用方提交的 `total=100`、`precision=0.99` 真的来自 100 条数据库 observation,也不能证明人工标签存在。
|
||||||
|
- 因此 HMAC 只保留为自动 Monitor 的传输认证和防重放手段;指标必须由接收端使用当前数据库 observation/label 重新计算。
|
||||||
|
- 最终 Monitor 请求应只携带受控 release 触发信息或聚合作业游标,而非可被签名后照单采用的质量汇总数字。
|
||||||
|
- 即使 HMAC 认证失败,也不得影响 stable 规则继续保护业务;应停止晋级、记录安全告警并保持 `collecting`。
|
||||||
|
|
||||||
|
### 降级策略
|
||||||
|
|
||||||
|
- 遥测表或写入暂不可用:不阻断已生效 stable 风险规则和报销主流程,但当前 release 不能晋级,状态保持 collecting 并告警。
|
||||||
|
- 人工标签迟到:保留 observation,待标签追加后重新聚合;不使用默认正确值填补。
|
||||||
|
- 运营端同时展示待标注数量和最早积压时长;超过 24 小时生成结构化逾期告警。
|
||||||
|
- 处置事件与 observation 无法安全关联:拒绝标签,不按相似文本、姓名或评论做模糊匹配。
|
||||||
|
- baseline 标签不完整:`baseline_precision = null`;候选指标可继续采集,但不能声称已完成可靠基线比较。
|
||||||
|
- 负样本未抽中、未完成双人复核或未达到最小复核量:recall、估计 FN 和置信下界保持不可用,继续 collecting 并告警;不会用零填充。
|
||||||
|
- 聚合或 Guard 判定越界:按现有冻结快照恢复 stable;回滚不删除候选 observation 和 label。
|
||||||
|
- 周期聚合异常形成 `release_aggregation_failed` 告警并隔离到单资产;运行失败率、
|
||||||
|
precision 下降、baseline 不可用和自动回滚分别使用独立告警码,稳定版本继续服务。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
### 候选精确率
|
||||||
|
|
||||||
|
```text
|
||||||
|
candidate_precision = candidate_confirmed / (
|
||||||
|
candidate_confirmed + candidate_false_positive
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 分母只包含候选 `candidate_hit=true` 且已有可信最新标签的 observation。
|
||||||
|
- 任一候选正向命中仍未标注时,聚合保持 `collecting`,不得将部分 precision 交给 Release Guard 作为通过证据。
|
||||||
|
|
||||||
|
### 基线精确率
|
||||||
|
|
||||||
|
```text
|
||||||
|
baseline_precision = baseline_confirmed / (
|
||||||
|
baseline_confirmed + baseline_false_positive
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 只使用同一 shadow 样本上 `baseline_hit=true` 的可信标签。
|
||||||
|
- 任一基线正向命中待标注时,baseline precision 显式不可用,不用部分样本制造有利比较。
|
||||||
|
|
||||||
|
### 运行错误率
|
||||||
|
|
||||||
|
```text
|
||||||
|
runtime_error_rate = runtime_failure_count / observed_count
|
||||||
|
```
|
||||||
|
|
||||||
|
- `observed_count` 是该 release/stage/version 的真实运行 observation 数。
|
||||||
|
- `runtime_failure_count` 只统计结构化执行失败,不把业务误报混成技术错误。
|
||||||
|
- 误报通过 precision 体现;运行错误通过 `ReleaseGuardPolicy.max_error_rate` 体现。
|
||||||
|
|
||||||
|
### Recall 与 false negative
|
||||||
|
|
||||||
|
```text
|
||||||
|
recall = TP / (TP + FN)
|
||||||
|
```
|
||||||
|
|
||||||
|
线上负样本分为两层,不能把抽检样本数直接当总体 FN:
|
||||||
|
|
||||||
|
```text
|
||||||
|
disagreement_FN = 全量复核(candidate_hit=false, baseline_hit=true)中的真实风险数
|
||||||
|
random_FN_rate = 随机盲审层真实风险数 / 已完成双人复核的随机样本数
|
||||||
|
estimated_FN = disagreement_FN + random_FN_rate * random_negative_population
|
||||||
|
recall_point = TP / (TP + estimated_FN)
|
||||||
|
|
||||||
|
random_FN_rate_upper = WilsonUpper(random_FN, reviewed, confidence)
|
||||||
|
FN_upper = disagreement_FN + random_FN_rate_upper * random_negative_population
|
||||||
|
recall_lower_bound = TP / (TP + FN_upper)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `false_negative_count` 仅表示已完成法定复核样本中实际观察到的 FN,不等于总体 FN。
|
||||||
|
- `estimated_false_negative_count` 是总体点估计,`false_negative_upper_bound` 是保守上界,二者必须分字段展示。
|
||||||
|
- 随机层存在但尚无已复核样本、仍有抽中样本待审或未达到最小复核量时,上述估计统一保持 `null`。
|
||||||
|
- 没有随机负例人群时可使用全量复核的精确 recall;否则发布门禁只消费 `recall_lower_bound`。
|
||||||
|
- 离线 Golden Case recall 只证明测试集表现,不能冒充线上 recall;线上抽样也不能替代 Golden Case 的边界覆盖。
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
- 模型:租户/release 复合身份、sample/label 到 observation 复合外键、标签来源组合约束和 append-only。
|
||||||
|
- 样本生产:shadow hit/miss、stable baseline hit、Canary hit、manifest 循环 Canary miss 和结构化运行失败。
|
||||||
|
- 标签:专用复核、真实 `RiskDispositionEvent`、盲审语义、负样本双人法定票、来源/actor 脱敏。
|
||||||
|
- 幂等:相同 observation/label 稳定重放;同一来源不同载荷返回冲突。
|
||||||
|
- 租户与时效:跨租户隐藏、陈旧 release/stage/version 拒绝、错误规则/单据来源拒绝。
|
||||||
|
- 聚合:无样本、无候选正例、无标签、部分标签、完整标签、baseline 部分标签、运行失败和 precision drop。
|
||||||
|
- 运营告警:待标注数量/最早时长、24 小时积压、运行失败率、聚合失败、baseline
|
||||||
|
不可用、precision 下降和自动回滚使用去敏结构化告警。
|
||||||
|
- 证据边界:未抽中负样本拒绝标签;预测字段不进入队列/API;抽样不足时 recall/FN 为 null,充分时点估计与下界分离。
|
||||||
|
- 组合回归:Telemetry 生成的 `ReleaseEvaluationInput` 可被现有 Release Guard 消费,且不改变 shadow/Canary/回滚状态机。
|
||||||
|
- PostgreSQL:0018/0023 upgrade/downgrade、复合外键、标签组合约束、数据库 append-only、并发同幂等键单赢家、同 actor 去重和标签/阶段竞争。
|
||||||
|
- 所有验证在 `local-x-financial-linux` 容器内执行,单条命令最大 60 秒。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- [A1] 每条线上发布样本可追溯到 tenant、asset、release、stage、version、rule 和来源指纹,且不含业务正文。
|
||||||
|
- [A2] 相同运行/标签重放只保留一条事实;不同载荷复用同一来源 100% 拒绝。
|
||||||
|
- [A3] 任一候选正向命中未标注时状态为 collecting,不能生成 Release Guard 通过输入。
|
||||||
|
- [A4] precision 和 baseline precision 只由真实 observation 与可信类型化标签计算,外部汇总值不作为权威事实。
|
||||||
|
- [A5] recall/FN 证据不足时明确 unavailable;充分时同时披露实际 FN、估计 FN、FN 上界、recall 点估计和置信下界,门禁只使用下界。
|
||||||
|
- [A6] 跨租户、陈旧 release/stage/version、错误处置来源和纯负样本伪标签均被拒绝。
|
||||||
|
- [A7] 质量越界时自动回滚 stable,遥测故障或 HMAC 故障时停止晋级但不关闭既有稳定保护。
|
||||||
|
- [A8] PostgreSQL 迁移、append-only、并发、后端组合回归、Ruff 和 `git diff --check` 全部在容器内通过。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
- 模型注册、真实 manifest hook、类型化处置标签、盲审抽样、服务端聚合、即时 Guard、
|
||||||
|
租户周期调度、发布复核队列和运营控制台已经接通;`0023` 后继迁移与完整 PostgreSQL
|
||||||
|
循环仍须完成最终验证后才能关闭本能力。
|
||||||
|
- 租户调度器只自动处理租户绑定资产。平台共享资产可以按租户保存隔离样本,但在建设跨租户、加权且可审计的聚合口径前,不能由单租户样本自动回滚全局版本。
|
||||||
|
- 线上标签可能集中在高风险或有争议样本,precision 仍可能受人工复核选择偏差影响;控制台必须同时披露 hit、labeled 和 pending 数量。
|
||||||
|
- 规则稀有时可能长期没有候选正例;不能为了晋级降低为“零命中等于 100% precision”,需要延长 shadow 或补充经审核 Golden Case。
|
||||||
|
- 标签纠正采用追加新事实和“每个 actor 最新票”聚合;数据库索引、复合外键和只追加
|
||||||
|
约束已在 PostgreSQL 验证,同 observation/label 的并发单赢家、阶段晋级竞争和调度器
|
||||||
|
advisory leader lease 均有 PostgreSQL 并发验证。
|
||||||
|
- HMAC 密钥泄露会让攻击者通过传输认证,但仍不应允许其伪造数据库 observation/label;服务端重算是不可省略的第二道边界。
|
||||||
|
- 线上 recall 已有分层抽样、预测盲化、双人复核、冲突保持 collecting、最小样本量和置信下界;仍需用试点数据校准抽样比例、人工一致率与业务风险容忍度,不能把默认阈值当行业通用真理。
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
- 2026-07-16:完成现有 Loader、平台风险评测、shadow_evaluations、风险处置和 AI workflow feedback/outcome 的只读审计,确认 `RiskDispositionEvent` 是当前最可信线上人工标签源,通用学习结果缺少 release 身份不能直接用于门禁。
|
||||||
|
- 2026-07-16:新增独立 observation/label 模型与服务,完成脱敏、append-only、稳定幂等、租户/陈旧 release 拒绝、shadow/Canary 样本生产、可信标签和保守聚合。
|
||||||
|
- 2026-07-16:独立切片阶段新增遥测测试 7 项,与当时 Release Guard/Runtime 组合共 19 项通过;该阶段留下的共享注册、迁移和运行 hook 已在后续记录中完成。
|
||||||
|
- 2026-07-16:完成主模型注册、迁移所有权与 `0018`;一次性 PostgreSQL 17 完整迁移循环、复合外键和数据库 append-only 探针通过。
|
||||||
|
- 2026-07-16:真实 shadow/Canary/active manifest 执行已写 observation;类型化风险处置自动追加标签并即时触发 Guard,相同聚合快照幂等复用测试运行,低 precision 或运行失败自动恢复 stable。
|
||||||
|
- 2026-07-16:Release Monitor HTTP 改为空触发 + HMAC,禁止调用方提交 precision/total;新增租户级周期调度作为即时监控失败的补偿链。发布组合回归 44 项、调度/风险定向回归 24 项通过。
|
||||||
|
- 2026-07-16:完成后端大文件职责拆分:`agent_assets.py` endpoint 降至 714 行、
|
||||||
|
`AgentAssetService` 降至 675 行、`AgentAssetReleaseGuardService` 降至 636 行;
|
||||||
|
风险规则子路由、资产序列化和发布纯策略分别独立,旧路由路径、公开类型导入和状态机行为保持兼容。
|
||||||
|
- 2026-07-16:发布纯策略新增 `reviewer_quorum`(默认 1、范围 1..2)并随 release state 保存,
|
||||||
|
专用去敏复核队列按独立 actor 计票,禁止发布人自审,双人同意前或结论冲突时保持 collecting。
|
||||||
|
- 2026-07-16:发布控制台展示真实样本、命中、待审、最早积压时长、运行失败率、
|
||||||
|
candidate/baseline precision 和 recall 不可用;新增积压、超时、运行故障、聚合失败、
|
||||||
|
precision 下降、baseline 不可用和自动回滚结构化告警。
|
||||||
|
- 2026-07-16:新增 append-only 盲审样本、正例/分歧全量与其余负例稳定随机抽样;来源引用加密保存,observation 与 sample 同事务写入,保护失败整体回滚。
|
||||||
|
- 2026-07-16:发布复核队列改为预测盲化混排,负样本强制两个不同复核人;新增实际/估计 FN、Wilson FN 上界、recall 点估计与保守下界,Release Guard 只消费下界。
|
||||||
|
- 2026-07-16:前端拒绝 prediction hit 字段,使用中性业务真值动作,并显示负样本池、抽样进度、积压及置信方法;证据缺失保持不可用,不渲染为零。
|
||||||
|
- 2026-07-16:新增 `0023` 后继迁移和标签来源组合约束;最终 PostgreSQL 全链升级/降级、并发和全量回归结果待验证后回填。
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# AI 分阶段发布真实遥测 开发 TODO
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每项必须回链 `CONCEPT.md` 对应章节;没有代码、迁移、接口或容器证据不得勾选。
|
||||||
|
- observation、label、聚合和 Release Guard 输入必须按证据层分开,不能用 HMAC 请求或客户端汇总值替代数据库事实。
|
||||||
|
- `collecting` 不得包装成通过;recall/false-negative 没有达到独立盲审证据阈值时必须保持 unavailable。
|
||||||
|
- 所有后端、迁移和并发验证只在 `local-x-financial-linux` 容器内执行,单条命令最长 60 秒。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 背景与问题] 审计 Loader、平台风险评测、shadow_evaluations、风险处置和 AI workflow feedback/outcome 链路,确认线上发布评测缺少持久真实样本。
|
||||||
|
证据:`expense_claim_risk_rule_loader.py`、`expense_claim_platform_risk.py`、`risk_dispositions.py`、`expense_workflow_learning.py`、`ai_learning.py` 只读审计。
|
||||||
|
- [x] [CONCEPT: 背景与问题] 确认 `RiskDispositionEvent confirm/false_positive` 是当前可绑定风险命中的可信人工结论,通用 feedback/outcome 缺少完整 release 身份。
|
||||||
|
证据:`risk_disposition.py`、`risk_dispositions.py` 与 `agent_asset_release_telemetry.py` 的可信事件查询和关联校验。
|
||||||
|
- [x] [CONCEPT: HMAC 与数据真实性边界] 冻结 HMAC 只负责传输认证、防篡改和防重放,不证明汇总指标的数据真实性。
|
||||||
|
证据:`CONCEPT.md`“HMAC 与数据真实性边界”;现有 `agent_asset_release_monitor_auth.py` 与 `ReleaseEvaluationInput` 契约对照审计。
|
||||||
|
- [x] [CONCEPT: 目标与非目标] 明确未标注 collecting、敏感数据不落遥测表、线上 recall/FN 证据不足不可用。
|
||||||
|
证据:`CONCEPT.md`“目标与非目标”“Recall 与 false negative”。
|
||||||
|
|
||||||
|
## 2. 契约与设计
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 数据] 定义 observation 与 label 的 tenant/asset/release/stage/version 复合身份、稳定幂等键和只追加边界。
|
||||||
|
证据:`server/src/app/models/agent_asset_release_telemetry.py`。
|
||||||
|
- [x] [CONCEPT: 证据链与自动判定] 定义 observation → trusted label → aggregate → ReleaseEvaluationInput → Release Guard 的证据链。
|
||||||
|
证据:`CONCEPT.md`“证据链与自动判定”、`ReleaseTelemetryAggregate.to_release_evaluation_input()`。
|
||||||
|
- [x] [CONCEPT: 算法/规则] 分开定义运行失败、业务误报、candidate precision、baseline precision 和缺失负样本真值。
|
||||||
|
证据:`agent_asset_release_telemetry.py` 的 `aggregate()`;`test_agent_asset_release_telemetry.py` 的 collecting、baseline 和 FN 不可用断言。
|
||||||
|
- [x] [CONCEPT: 权限] 冻结专用发布复核接口的角色矩阵、双人复核阈值和跨租户 HTTP 错误契约。
|
||||||
|
证据:`require_rule_reviewer_user` 只允许 manager/admin;跨租户 404、非角色 403、
|
||||||
|
发布人自审 400;`reviewer_quorum` 限制 1..2 且按不同 actor 指纹计票。
|
||||||
|
|
||||||
|
## 3. 独立模型与服务
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 数据] 新增 append-only observation/label ORM 模型、复合租户/release 外键和更新/删除拒绝。
|
||||||
|
证据:`server/src/app/models/agent_asset_release_telemetry.py`。
|
||||||
|
- [x] [CONCEPT: 后端] 实现真实 shadow 结果、Canary/active 命中和 manifest 执行循环样本生产器。
|
||||||
|
证据:`AgentAssetReleaseTelemetryService.record_expense_risk_result()`、`record_manifest_evaluation()`。
|
||||||
|
- [x] [CONCEPT: 后端] 实现专用发布复核标签和可信 `RiskDispositionEvent` 标签转换,不接收自由评论。
|
||||||
|
证据:`record_review_label()`、`record_risk_disposition_label()`。
|
||||||
|
- [x] [CONCEPT: 后端] 实现租户隔离、陈旧 release/stage/version 拒绝、来源关联校验和稳定幂等冲突。
|
||||||
|
证据:`_require_current_release()`、`_observation_replay()`、`_label_replay()` 及对应测试。
|
||||||
|
- [x] [CONCEPT: 算法与公式] 实现保守聚合;未标注候选命中保持 collecting,基线标签不完整时 baseline precision 不可用。
|
||||||
|
证据:`ReleaseTelemetryAggregate`、`aggregate()`、`to_release_evaluation_input()`。
|
||||||
|
- [x] [CONCEPT: 数据] 实现 claim、事件、actor 来源指纹化,不保存业务正文、评论和账号原值。
|
||||||
|
证据:模型无自由文本业务字段;`_fingerprint()`;脱敏测试断言。
|
||||||
|
|
||||||
|
## 4. 共享注册、迁移与运行接入
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 数据] 在 `db/base.py` 与 `models/__init__.py` 注册 `AgentAssetReleaseObservation` 和 `AgentAssetReleaseLabel`,保证主应用 metadata 与迁移所有权检查可见。
|
||||||
|
证据:`db/base.py`、`models/__init__.py`、`schema_ownership.py`、`migration_preflight.py` 已登记两张迁移自有表;前置检查 77 项通过。
|
||||||
|
- [x] [CONCEPT: 数据] 新增后继 `20260716_0018` Alembic 迁移,创建两张表、复合租户/release 约束、检查约束、索引和数据库级 append-only UPDATE/DELETE 触发器。
|
||||||
|
证据:`20260716_0018_agent_asset_release_telemetry.py`;一次性 PostgreSQL 17 完整升级/降级循环 1 项通过,静态迁移/schema owner 回归 112 项通过。
|
||||||
|
- [x] [CONCEPT: 算法/规则] 在真实候选 manifest 执行循环接入 `record_manifest_evaluation()`,完整记录 shadow/Canary hit、miss 和结构化执行失败。
|
||||||
|
证据:`expense_claim_platform_risk.py`、`expense_claim_release_telemetry.py`;`test_agent_asset_release_runtime.py` 覆盖 shadow、Canary、active 和损坏快照,`test_agent_asset_release_telemetry.py` 覆盖结构化运行失败。
|
||||||
|
- [x] [CONCEPT: 后端] 在类型化风险处置事务接入 release label,按同租户、同 claim/rule、当前 release 精确关联 observation;关联失败保守拒绝。
|
||||||
|
证据:`agent_asset_release_disposition_labels.py`、`risk_disposition_release_sync.py`;风险确认/误报会追加标签,误报越界自动回滚 stable。
|
||||||
|
- [x] [CONCEPT: 后端] 将现有 Release Monitor 从“提交外部汇总数字”改为“触发服务端聚合”,只在 aggregate ready 时构造 `ReleaseEvaluationInput`。
|
||||||
|
证据:`AgentAssetReleaseMonitor.evaluate_current()` 只接受 release 身份;HTTP body 为禁止额外字段的空触发契约,真实聚合未 ready 时不调用 Guard。
|
||||||
|
- [x] [CONCEPT: HMAC 与数据真实性边界] 保留 HMAC 作为 Monitor 传输认证,但禁止签名请求覆盖数据库聚合结果,并补充签名通过但汇总伪造的反向测试。
|
||||||
|
证据:`agent_asset_releases.py`、`AgentAssetReleaseMonitorTriggerWrite`;带有效签名的伪造 `precision/total` 请求仍返回 422。
|
||||||
|
- [x] [CONCEPT: 降级策略] 遥测持久化或聚合失败时保持 stable 规则、停止晋级并记录结构化告警,不让观测故障阻断正常报销。
|
||||||
|
证据:`ExpenseClaimReleaseTelemetryRecorder`、`risk_disposition_release_sync.py` 将遥测/标签/监控故障隔离并记录日志;未获得 ready 真实聚合不会写 passed,也不会改变 stable 路由。
|
||||||
|
- [x] [CONCEPT: 后端] 按职责拆分 Agent 资产接口、版本只读投影和 Release Guard 纯策略计算,保持公开 API 与状态机行为稳定。
|
||||||
|
证据:`agent_assets.py` endpoint 714 行、`agent_asset_risk_rules.py` 534 行、`agent_assets.py` service 675 行、`agent_asset_serialization.py` 217 行、`agent_asset_release_guard.py` 636 行、`agent_asset_release_policy.py` 201 行,相关核心文件均低于 800 行;纯策略模块保存范围为 1..2 的 `reviewer_quorum`,第 5 节已完成复核执行与运营闭环。
|
||||||
|
|
||||||
|
## 5. 自动聚合、告警与运营闭环
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 证据链与自动判定] 实现按 tenant/asset/release/stage/version 的周期聚合作业与幂等快照。
|
||||||
|
证据:`agent_asset_release_scheduler.py` 按租户有界扫描;`AgentAssetReleaseMonitor._existing_evaluation()` 复用相同 release 聚合快照,不重复生成测试运行。
|
||||||
|
- [x] [CONCEPT: 证据链与自动判定] aggregate ready 后自动调用 Release Guard;collecting 只更新采集状态,不写虚假 passed test run。
|
||||||
|
证据:人工标签提交后即时触发 monitor,后台 scheduler 提供失败补偿;`test_agent_asset_release_monitor.py` 覆盖 collecting 不调用 Guard、低 precision/运行失败自动回滚与相同快照幂等。
|
||||||
|
- [x] [CONCEPT: 降级策略] 增加待标注数量/时长、运行失败率、precision 下降、baseline 不可用、聚合失败和自动回滚告警。
|
||||||
|
证据:`agent_asset_release_alerts.py`、`AgentAssetReleaseMonitor._metrics()`;24 小时
|
||||||
|
待审逾期和单资产聚合失败均返回结构化告警,定向 Monitor/Telemetry/Runtime 29 项通过。
|
||||||
|
- [x] [CONCEPT: 前端] 在发布控制台展示 observed/hit/labeled/pending、候选/基线 precision、运行失败和召回证据。
|
||||||
|
证据:`AuditReleaseMonitorPanel.vue` 展示负样本池、抽样进度、积压、实际/估计 FN、FN 上界、recall 点估计/下界/置信方法;空值不渲染为零。
|
||||||
|
- [x] [CONCEPT: 权限] 实现认证发布复核队列、操作审计和需要时的双人复核,不允许规则发布人独自伪造所有标签。
|
||||||
|
证据:`agent_asset_release_review.py`、`agent_asset_release_label_votes.py`、专用 GET/POST
|
||||||
|
API;`X-Request-Id`、append-only label、actor HMAC 指纹、发布人隔离和独立双人同意均有测试。
|
||||||
|
- [x] [CONCEPT: Recall 与 false negative] 实现独立分层负样本抽样、预测盲化、双人标注、冲突保持 collecting 和保守召回估计。
|
||||||
|
证据:`agent_asset_release_sampling.py`、`agent_asset_release_review.py`、
|
||||||
|
`agent_asset_release_aggregation.py`、`agent_asset_release_recall.py`;前端只发送
|
||||||
|
`risk_present / risk_absent`,负样本要求两个不同 actor,门禁只读取 recall 下界。
|
||||||
|
- [x] [CONCEPT: 数据] 完成 `0023` audit sample 迁移注册、数据库标签组合约束、append-only 触发器和无损降级验证。
|
||||||
|
证据:`20260716_0023_agent_asset_release_blind_audit.py`、`release_telemetry_migration_assertions.py`;fresh PostgreSQL 完整迁移链、约束、触发器及降级/再升级均通过。
|
||||||
|
|
||||||
|
## 6. 测试与验证
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 测试方案] 独立模型/服务测试覆盖 shadow、Canary、baseline、真实 typed disposition、脱敏、幂等、租户、陈旧 release、append-only、collecting 和 FN 不可用。
|
||||||
|
证据:容器内 `test_agent_asset_release_telemetry.py` 7 项通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 与现有 Release Guard 和 Runtime 组合回归通过。
|
||||||
|
证据:容器内 `test_agent_asset_release_guard.py`、`test_agent_asset_release_runtime.py`、`test_agent_asset_release_telemetry.py` 共 19 项通过;Ruff 通过,`git diff --check` 通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 新增 0018 upgrade/downgrade、schema owner、复合外键和数据库 append-only PostgreSQL 验证。
|
||||||
|
证据:一次性 `pgvector/pgvector:pg17` 数据库中完整迁移循环 1 项通过;迁移运行探针验证复合租户外键、幂等唯一约束和两张表的数据库级 UPDATE/DELETE 拒绝。
|
||||||
|
- [x] [CONCEPT: 测试方案] 新增 PostgreSQL 并发同 observation、同 label、标签与阶段晋级竞争和幂等冲突测试。
|
||||||
|
证据:一次性 PostgreSQL 17 中 `test_agent_asset_release_telemetry_concurrency_postgres.py` 4 项通过;相同重放只保留一条,不同载荷单赢家,阶段转换持锁后旧标签保守拒绝。
|
||||||
|
- [x] [CONCEPT: 测试方案] 验证 audit sample 并发单赢家、同 actor 重复不增加负样本法定票、第二独立 actor 完成双人复核,以及预测字段不进入前端队列状态。
|
||||||
|
证据:`test_agent_asset_release_telemetry_concurrency_postgres.py` 覆盖单赢家和独立双人票;`test_agent_asset_release_telemetry.py` 与 `agent-release-monitor-panel.test.mjs` 验证队列不暴露 candidate/baseline 命中预测。
|
||||||
|
- [x] [CONCEPT: 测试方案] 跑通真实风险循环 → observation → disposition label → aggregate → Release Guard 回滚端到端。
|
||||||
|
证据:`test_agent_asset_release_runtime.py` 与 `test_risk_dispositions.py` 覆盖真实执行样本、类型化处置标签、服务端聚合、低 precision 自动恢复 stable;发布组合回归 44 项通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 验证职责拆分后的资产服务、风险规则子路由、Release Guard/Runtime、Monitor/Scheduler/Telemetry 和 API schema 兼容性。
|
||||||
|
证据:容器内资产服务 27 项、Guard/Runtime 13 项、Monitor/Scheduler/Telemetry 22 项、风险规则生成/解释 30 项、修订/反馈 14 项通过;迁移后的既有 publish HTTP 防绕过用例通过,Ruff 与 `git diff --check` 通过。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 验证 HMAC 失败、遥测库失败、标签积压和聚合失败均停止晋级但不破坏 stable 业务保护。
|
||||||
|
证据:`test_agent_asset_release_runtime.py` 覆盖签名失败;`test_agent_asset_release_monitor.py` 覆盖 collecting、积压和聚合失败;`ExpenseClaimReleaseTelemetryRecorder` 隔离遥测持久化异常,stable 报销路径继续服务。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 完成相关后端全量回归、Ruff、迁移完整循环和 `git diff --check`,逐项回填 A1-A8 证据。
|
||||||
|
证据:遥测组合 45 项通过;fresh PostgreSQL 总探针 `87 passed / 0 skipped / 0 failed`,其中发布遥测并发 7 项;Web 全量 815 项及 Vite build 通过;新增 Python 文件 Ruff 与 `git diff --check` 通过。全仓既有 Ruff 基线债不伪装为本轮新增错误。
|
||||||
|
|
||||||
|
## 7. 文档收尾
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 本轮实现记录] 新建独立 CONCEPT/TODO,记录证据边界、已实现切片和共享集成缺口。
|
||||||
|
证据:`document/development/2026-07-16/feature/ai-release-real-telemetry/CONCEPT.md`、`TODO.md`。
|
||||||
|
- [x] [CONCEPT: 风险与开放问题] 共享集成完成后回填 0018、运行 hook、自动作业、PostgreSQL 和端到端证据。
|
||||||
|
证据:本 TODO 第 4-6 节与 `CONCEPT.md`“本轮实现记录”已回填;发布面板已展示积压、失败、precision 和 recall,生产运营效果由下一条真实流量验收单独保留。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 与上位 AI 闭环 TODO 对齐代码与容器验证状态。
|
||||||
|
证据:`document/development/2026-07-13/feature/ai-expense-closed-loop-and-value-proof/TODO.md` 已回填 Golden、Canary、盲审和回滚的工程证据。
|
||||||
|
- [ ] [CONCEPT: 指标与验收] 使用生产真实流量、独立复核样本和试点阈值验证 Golden/Canary/自动回滚运营效果。
|
||||||
|
证据要求:目标企业生产 observation/label、盲审样本、阈值签字和真实回滚演练;本地测试不得替代。
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
# 商业计量、客户 ROI 与可持续定价 概念文档
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
把套餐、订阅、权益、真实用量、内部成本、客户确认价值和平台毛利拆成可审计事实,并只在成本与价值证据同时成立时给出可持续定价走廊。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
- 平台要成为可长期经营的产品,既要证明客户省了钱,也要知道每个租户消耗了多少 OCR、AI、存储、连接器和支持成本。
|
||||||
|
- “风险金额”“预计节省”“流程耗时”不能直接作为客户 ROI;平台收入、平台内部成本和客户价值也不能混在一个指标里。
|
||||||
|
- 只有套餐创建而没有暂停、取消、历史查询和配额硬门禁,商业后台无法真正运营。
|
||||||
|
- 仅在执行前读取 `SUM(usage)` 再放行无法抵抗并发;两个工具可同时看到剩余额度并一起执行,事后计量再准确也已形成不可逆超卖。
|
||||||
|
- 固定拍一个价格无法适配客户规模、真实成本和价值覆盖。需要先计算平台可持续下限,再计算客户价值可接受上限;没有交集时不能强行报价。
|
||||||
|
- 试点期通常缺少 30/90 天真实成本和财务确认收益,因此系统必须显示“采集中”,而不是用 mock 或估计值伪造单位经济性。
|
||||||
|
|
||||||
|
本能力是 `2026-07-13/feature/ai-expense-closed-loop-and-value-proof` 中商业模式与价值证明部分的实施拆分。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- [G1] 建立租户隔离、版本化的套餐、订阅和权益配置。
|
||||||
|
- [G2] 建立追加式用量与内部成本事实,支持幂等、冲回、配额和并发门禁。
|
||||||
|
- [G3] 把商业权益门禁与安全门禁做收紧式合并,付费不能绕过高风险人工审核。
|
||||||
|
- [G4] 严格分开客户收费、内部成本、平台贡献毛利、财务确认现金节省、客户 ROI 和工时价值。
|
||||||
|
- [G5] 支持套餐/订阅/权益生命周期、历史查询、用量成本查询和数据质量状态。
|
||||||
|
- [G6] 根据真实成本和财务确认节省给出基础费下限、价值上限和可封顶成功费,不自动改合同。
|
||||||
|
- [G7] 形成“试点采集 → 基础订阅 → 基础费 + 封顶成功费”的可验证商业演进路径。
|
||||||
|
- [G8] 用不可变账期承载收费、用量、成本与配额历史,并对商业配置和自动续期保留脱敏追加式审计。
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- [NG1] 不在代码中硬编码某个客户的最终价格、税率、折扣或合同条款。
|
||||||
|
- [NG2] 不把风险暴露、预计机会、未确认结果、未锁定汇率或未经客户认可的工时估值计入价值定价。
|
||||||
|
- [NG3] 不让套餐或配额放宽审批、风险、租户和人工确认门禁。
|
||||||
|
- [NG4] 不自建开票、税务、收款或第三方订阅扣费网络;只保存受控外部订阅引用。
|
||||||
|
- [NG5] 不跨币种直接求和,也不在缺少同币种成本/价值时生成综合 ROI。
|
||||||
|
- [NG6] 不把定价场景结果自动写成生效套餐,最终合同仍需平台商业负责人审批。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
### 用户
|
||||||
|
|
||||||
|
1. 平台商业管理员:维护套餐版本、订阅、权益、状态和定价场景。
|
||||||
|
2. 平台运营/财务:查看用量、成本、毛利和异常数据质量。
|
||||||
|
3. 客户 CFO/财务负责人:查看自己租户的当前套餐、用量、配额和客户价值口径。
|
||||||
|
4. 产品运行时:在执行 OCR、AI 或连接器能力前检查配额,并写入真实用量。
|
||||||
|
|
||||||
|
### 核心场景
|
||||||
|
|
||||||
|
1. 试点客户先配置 `pilot` 套餐和明确的合同周期,开始采集真实用量、成本与已确认价值。
|
||||||
|
2. 运行时检查某项权益;只有商业配额允许且安全决策为 allow 时才最终允许。
|
||||||
|
3. 同一用量事件重试返回首次结果;不同内容复用幂等键返回冲突。
|
||||||
|
4. 客户暂停、逾期、取消或过期时,商业权益即时失败关闭,历史用量和成本不被删除。
|
||||||
|
5. 商业负责人选择 90 天窗口,输入目标贡献毛利率和客户最大价值分享比例,系统按币种输出定价走廊。
|
||||||
|
6. 成本下限高于价值上限时,系统建议先优化单位经济性或扩大可信价值,不生成强行报价。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- [C1] 套餐版本:subscription、usage、hybrid、pilot、custom,支持生效区间和旧版本退役。
|
||||||
|
- [C2] 订阅快照:合同周期、计费周期、席位、基础费、外部订阅引用和状态历史。
|
||||||
|
- [C3] 权益与配额:feature、metered、unlimited,包含量、硬上限、重置周期和超额策略。
|
||||||
|
- [C4] 用量事实:usage、credit、adjustment、reversal,保存主体、来源、correlation 和首次请求指纹。
|
||||||
|
- [C5] 成本事实:AI、OCR、存储、连接器、支持、实施、基础设施、支付等分类及汇率快照。
|
||||||
|
- [C6] 商业分析:收费、成本、贡献毛利、确认节省、客户 ROI 和工时价值分账展示。
|
||||||
|
- [C7] 定价走廊:最低可持续收费、最高价值对齐收费、最大成功费和证据状态。
|
||||||
|
- [C8] 生命周期与查询:暂停、恢复、逾期、取消、过期,以及套餐/订阅/权益/用量/成本历史。
|
||||||
|
- [C9] 账期与审计:月/季/年自动续期、合同边界失败关闭、账期历史和商业管理追加审计。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 商业工作台分为“当前账户”“套餐与订阅”“权益与配额”“用量与成本”“价值与定价”五块。
|
||||||
|
- 客户 ROI 与平台贡献毛利必须使用不同卡片、不同说明,不允许用一个“综合收益”混合展示。
|
||||||
|
- 多币种按币种分行;缺成本、缺确认价值、仅有试点数据和证据冲突使用不同状态。
|
||||||
|
- 暂停、取消和定价场景均需要确认;终态操作明确提示不能原地恢复。
|
||||||
|
- 普通客户财务只能查看本租户账户;平台级配置、成本、毛利和定价仅平台管理员可见。
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- `CommercialAdminService` 管理套餐、订阅、权益和状态转换。
|
||||||
|
- `CommercialBillingPeriodService` 签发和定位不可变账期;用量、成本和运行时预占必须绑定真实账期编号。
|
||||||
|
- `CommercialSubscriptionRolloverService` 在订阅行锁内按月/季/年边界幂等续期;合同制或跨越 `ends_at` 时失败关闭。
|
||||||
|
- `CommercialRolloverScheduler` 以 PostgreSQL advisory lock 选举单一执行者,再按订阅行锁串行签发到期账期。
|
||||||
|
- `CommercialAdminAuditService` 只记录字段白名单快照,排除合同正文、配置、外部订阅编号、元数据和凭证类字段。
|
||||||
|
- `CommercialQueryService` 负责租户范围内历史与追加事实查询。
|
||||||
|
- `CommercialEntitlementService` 计算配额和商业/安全合并门禁。
|
||||||
|
- `CommercialMeteringService` 写追加式用量和成本、执行幂等与冲回。
|
||||||
|
- `CommercialRuntimeReservationService` 在订阅/权益锁内完成执行前额度预占,管理 reserved、committed、released、expired、reconciliation_required 和 committed_reconciliation_required 状态。
|
||||||
|
- `CommercialRuntimeBridge` 把可信 AgentRun、中央工具执行、真实 AgentToolCall 和商业事实接成预占—执行—结算链;未配置商业计量时保持兼容。
|
||||||
|
- `CommercialRuntimeReconciler` 只根据可验证的工具/运行终态补偿过期预占,不按超时猜测业务是否发生。
|
||||||
|
- `CommercialAnalyticsService` 按窗口和币种分账聚合,不伪造缺失数据。
|
||||||
|
- `CommercialPricingService` 只读分析真实成本和确认节省,输出价格区间,不写套餐。
|
||||||
|
- HTTP 管理入口仅平台管理员可用;租户账户读取仅 finance、executive 或平台管理员可用。
|
||||||
|
|
||||||
|
### 算法/规则
|
||||||
|
|
||||||
|
- 用量硬配额在数据库锁内计算,最终用量超过限制时整个写入失败。
|
||||||
|
- 真实工具执行前按 `已用量 + 有效预占 + 本次预占 <= 硬上限` 原子判断;成功时真实量不得超过预占,失败/阻断只释放预占,不写用量或成本。
|
||||||
|
- call 基准可直接预占一次;token、duration 等变量基准必须由执行器声明并强制最大量。缺少可信 hard max 时拒绝执行,不用正文长度或估算值代替。
|
||||||
|
- 已有相同 tool call 的预占请求按指纹稳定重放;真实 AgentToolCall 的用量/成本继续使用追加式幂等键。用量已写入但成本失败时进入 `committed_reconciliation_required`,重试只补缺失成本,不重复占用额度或追加用量。
|
||||||
|
- 权益已有用量后,配额、定价和有效期不可回改,只允许暂停/恢复;结构变化必须新建订阅版本。
|
||||||
|
- 客户 ROI 只使用财务确认 canonical 现金节省与客户收费。
|
||||||
|
- 贡献毛利只使用平台收费与内部成本,不混入客户节省。
|
||||||
|
- 定价只在相同币种内计算,并保留成本/价值证据状态。
|
||||||
|
|
||||||
|
### 数据
|
||||||
|
|
||||||
|
- `tenant_commercial_plans`:租户、套餐编码、版本、价格模型、基础费、币种、生效期和合同条款摘要。
|
||||||
|
- `tenant_subscriptions`:租户、套餐、周期、基础费快照、状态、席位、外部引用和版本。
|
||||||
|
- `commercial_entitlements`:租户、订阅、权益键、计量键、配额、状态和有效期。
|
||||||
|
- `usage_meter_events`:追加式用量、冲回引用、幂等键、请求指纹和关联链。
|
||||||
|
- `commercial_cost_events`:追加式内部成本、原币/报告币、汇率、分摊键和冲回引用。
|
||||||
|
- `commercial_runtime_reservations`:工具执行前的可变运营占位,保存 tenant/subscription/entitlement/run/tool call、基准、预占量、真实量、周期、配置快照、状态和补偿原因;它参与配额但不是客户用量事实。
|
||||||
|
- `commercial_billing_periods`:不可变账期签发事实,保存订阅/套餐引用、窗口、顺序、币种、基础费、席位、计价模式和来源快照;PostgreSQL 禁止更新和删除。
|
||||||
|
- `commercial_admin_events`:套餐、订阅、权益、账期和续期动作的脱敏追加审计,保存 tenant、actor、`X-Request-Id`、原因、动作、资源版本和白名单 before/after。
|
||||||
|
- `usage_meter_events.period_key` 表示真实账期键,`quota_period_key` 独立表示权益重置周期;成本与运行时预占同样通过 `billing_period_id` 绑定账期,避免订阅当前周期滚动后污染历史。
|
||||||
|
- 事实表在 PostgreSQL 使用触发器禁止 UPDATE/DELETE,租户复合外键防止跨租户引用。
|
||||||
|
|
||||||
|
运行时占位状态如下:
|
||||||
|
|
||||||
|
```text
|
||||||
|
reserved -> committed # 成功且 actual <= reserved
|
||||||
|
reserved -> released # 工具失败或执行前阻断
|
||||||
|
reserved -> expired # 运行已终止且没有真实工具调用
|
||||||
|
reserved -> reconciliation_required # 已发生调用但缺少执行前预占等人工补偿场景
|
||||||
|
committed -> committed_reconciliation_required -> committed
|
||||||
|
# 用量已提交、成本等后续事实失败,幂等补齐后恢复
|
||||||
|
```
|
||||||
|
|
||||||
|
过期但运行仍在进行、运行记录缺失或工具终态不确定时继续保留额度,不允许补偿器仅因 TTL 到期释放后造成超卖。
|
||||||
|
`committed_reconciliation_required` 已有真实用量事实,因此不再计入有效预占;配额只消费一次,同时保留明确的待补偿队列。
|
||||||
|
|
||||||
|
### 权限
|
||||||
|
|
||||||
|
- 平台管理员可配置所有租户商业账户,但商业配置不能授予财务确认或风险审批能力。
|
||||||
|
- finance/executive 只读本租户当前账户,不读取平台内部成本或其他租户数据。
|
||||||
|
- manager、employee 默认无商业账户与商业分析权限。
|
||||||
|
- 所有管理接口从认证上下文判断平台管理员,目标租户来自受控路径参数。
|
||||||
|
|
||||||
|
### 降级策略
|
||||||
|
|
||||||
|
- 无订阅:返回 unavailable 和明确说明,不自动赠送无限权益。
|
||||||
|
- 订阅非 active/trialing:配额保留展示但最终消费失败关闭。
|
||||||
|
- 缺成本:贡献毛利和可持续价格下限不可用。
|
||||||
|
- 缺财务确认价值:客户 ROI、价值上限和成功费不可用,建议继续试点采集。
|
||||||
|
- 多币种缺少共同币种:分别展示,禁止跨币种净额。
|
||||||
|
- 并发或幂等冲突:返回 409,不覆盖首次事实。
|
||||||
|
- 生产中央工具路径未配置 runtime meter:兼容执行且不生成商业事实;配置只在当前订阅周期和权益有效期内参与门禁,历史过期配置不会误触发 enforcement。
|
||||||
|
- 已发生的旧直接工具路径缺少执行前预占:不补写为正常用量,持久化 `reconciliation_required` 并冻结对应容量,等待受控补偿;即使当前合同已暂停,也保留其唯一可验证的租户、订阅和权益归属。
|
||||||
|
- 用量已追加但内部成本写入失败:持久化 `committed_reconciliation_required`,配额以真实用量为准且预占归零;按相同 tool call 重试只补成本,完成后回到 committed。
|
||||||
|
- 自动续期只处理 `trialing/active + auto_renew`;月、季、年按自然月边界滚动。合同制、缺少可推导边界或下一完整账期越过 `ends_at` 时失败关闭,不创建部分账期或猜测续约。
|
||||||
|
- 调度器即使发生重复扫描或多进程竞争,也先获取 leader lease,再锁定订阅;账期窗口和幂等键的唯一约束保证同一周期最多签发一次。数据库触发器还会按租户与订阅获取事务级 advisory lock,并拒绝任何半开区间重叠账期,防止绕过服务层直接写入破坏时间线。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
### 客户 ROI
|
||||||
|
|
||||||
|
```text
|
||||||
|
customer_roi = (verified_cash_savings - customer_charges) / customer_charges
|
||||||
|
```
|
||||||
|
|
||||||
|
- `verified_cash_savings` 只包含独立财务确认、canonical、已计冲回的现金节省。
|
||||||
|
- `customer_charges` 来自订阅基础费快照和有可信同币种单价的用量计费。
|
||||||
|
- 分母必须大于 0;否则状态为 unavailable。
|
||||||
|
|
||||||
|
### 平台贡献毛利
|
||||||
|
|
||||||
|
```text
|
||||||
|
contribution_margin = customer_charges - internal_costs
|
||||||
|
contribution_margin_rate = contribution_margin / customer_charges
|
||||||
|
```
|
||||||
|
|
||||||
|
- 内部成本来自追加式成本账本,不使用估算页面数字。
|
||||||
|
|
||||||
|
### 可持续定价走廊
|
||||||
|
|
||||||
|
```text
|
||||||
|
minimum_sustainable_charge = internal_costs / (1 - target_margin_rate)
|
||||||
|
maximum_value_aligned_charge = verified_cash_savings * max_value_share
|
||||||
|
maximum_success_fee = max(0, maximum_value_aligned_charge - minimum_sustainable_charge)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 当 `minimum_sustainable_charge <= maximum_value_aligned_charge` 时,建议 hybrid:基础费不低于成本下限,成功费封顶为剩余价值空间。
|
||||||
|
- 只有成本证据时建议 subscription;成本与价值都不足时建议 pilot_collecting。
|
||||||
|
- 成本下限高于价值上限时建议 optimize_unit_economics,不自动提高客户报价。
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
- 模型/迁移:租户复合外键、状态检查、金额符号、冲回引用和 append-only。
|
||||||
|
- 服务:套餐版本、订阅终态、权益不可回改、配额硬门禁、幂等、冲回和多币种。
|
||||||
|
- 权限:平台管理员、租户财务、manager、employee 与跨租户访问。
|
||||||
|
- 分析:收费/成本/节省/毛利/ROI 分账,缺证据状态和 `as_of` 回放。
|
||||||
|
- 定价:可行区间、成本高于价值、仅成本、完全无证据和多币种。
|
||||||
|
- 前端:状态、权限、操作确认、空态、错误态、币种分组与生产构建。
|
||||||
|
- PostgreSQL:并发配额、同幂等键、成本冲回单赢家和迁移完整性。
|
||||||
|
- PostgreSQL 账期:0020→0021→0020 升降级、账期/审计 UPDATE/DELETE 拒绝、重叠账期 INSERT 拒绝、历史事实账期绑定和双线程续期单赢家。
|
||||||
|
- 运行时预占:无配置兼容、成功结算、失败释放、变量 hard max、真实量超预占、幂等重放、历史配置隔离、直接路径补偿和过期补偿。
|
||||||
|
- 所有命令在 `local-x-financial-linux` 容器内执行,单次最长 60 秒。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- [A1] 每个商业消费可追溯到租户、订阅、权益、用量事件、来源和 correlation。
|
||||||
|
- [A2] 并发不能突破硬配额;重复事件稳定重放,冲突内容被拒绝。
|
||||||
|
- [A3] 付费状态不能绕过安全或人工审核门禁。
|
||||||
|
- [A4] 客户 ROI、平台毛利、客户节省和工时价值在 API/UI 中不混算。
|
||||||
|
- [A5] 暂停、恢复、取消、过期和历史查询可操作,终态不可原地复活。
|
||||||
|
- [A6] 定价场景只使用真实同币种成本与确认节省;证据不足时不输出虚假价格。
|
||||||
|
- [A7] 相关后端、PostgreSQL、前端、构建、Ruff 与迁移验证在容器内通过。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
- 真实计费仍需把 OCR、LLM、存储、连接器和支持运行事件自动接入用量/成本账本;手工管理员写入只能用于校验,不是最终生产采集。
|
||||||
|
- 中央 Orchestrator 工具已经执行前预占;绕过中央执行器的其他生产入口仍须逐一迁移到 permit 契约,当前只会形成可见补偿积压,不会伪装成正常计量。
|
||||||
|
- 合同制自动续期仍不推断新合同窗口;管理员或外部订阅连接器必须先提供显式续约事实,再创建后继合同/订阅版本。
|
||||||
|
- 首个客户的实际套餐金额、席位、包含量、毛利目标、价值分享比例和折扣需要商业负责人确认。
|
||||||
|
- 发票、税率、回款、坏账、渠道分成与收入确认尚未接入,当前 `customer_charges` 是合同/用量计费基准,不等同已收现金。
|
||||||
|
- 价值分享合同必须定义基线、排除项、冲回、确认人、封顶和争议期。
|
||||||
|
- 工时价值默认不进入现金 ROI,只有客户确认活跃工时基线、角色成本和可释放比例后才单独披露。
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
- 2026-07-16:完成五张商业事实表与 0016 迁移、套餐/订阅/权益、用量/成本、配额门禁、幂等和冲回服务。
|
||||||
|
- 2026-07-16:完成客户收费、内部成本、贡献毛利、财务确认现金节省、客户 ROI 和工时价值分账分析;修正 ROI 为净收益口径并收紧角色、时区和权益历史不可变边界。
|
||||||
|
- 2026-07-16:补齐订阅暂停/逾期/取消/过期、恢复和套餐/订阅/权益/用量/成本历史查询。
|
||||||
|
- 2026-07-16:完成基于真实成本下限与确认价值上限的定价场景,禁止风险暴露、预计节省或跨币种混入报价。
|
||||||
|
- 2026-07-16:完成商业工作台五块能力、订阅生命周期确认、用量/成本与价值分账、按币种定价场景两步确认;全量前端 802 项、code-size 和生产构建通过。
|
||||||
|
- 2026-07-16:一次性 PostgreSQL 17 并发配额、幂等和成本冲回 3 项通过;真实开票、回款与客户合同参数继续保留为外部试点边界,不以 mock 标记完成。
|
||||||
|
- 2026-07-16:新增 `0019` 运行时预占迁移和中央 Agent 工具 permit;在订阅/权益锁内按已用量加有效预占原子控额,成功按真实 AgentToolCall 结算,失败/阻断释放,变量用量超过预占拒绝写入。
|
||||||
|
- 2026-07-16:新增持久补偿状态和过期补偿器;旧直接调用缺少预占时冻结容量并进入 reconciliation_required,运行终态不确定时不按 TTL 误释放;用量已提交但成本失败进入 committed_reconciliation_required,相同预占与用量/成本均可幂等重试。
|
||||||
|
- 2026-07-16:新增 `0021` 不可变账期和商业管理审计;订阅创建即签发首期,月/季/年到期由 leader 调度器和订阅行锁幂等滚动,合同制与越界周期失败关闭。
|
||||||
|
- 2026-07-16:用量、成本、运行时预占和配额查询改为绑定 `billing_period_id`,并以独立 `quota_period_key` 保留权益重置语义;客户收费基础费改从账期快照聚合,不再读取可变订阅当前周期。
|
||||||
|
- 2026-07-16:容器内商业/迁移前置定向 131 项、前端商业 35 项和 Vite 构建通过;一次性 PostgreSQL 17 商业并发 5 项通过。0021 的升级、模型/约束/触发器(含账期重叠阻断)/运行不变量及 0021→0020 降级通过;全链 44/45 唯一失败来自后继 0023 尚未接管的盲审临时表,不属于 0021。
|
||||||
|
- 2026-07-16:容器内商业/Agent/迁移组合 183 项通过、1 项因未显式配置外部迁移库跳过;运行时定向 27 项、PostgreSQL 商业并发 4 项和 0019→0020 完整迁移循环通过;全局 code-size 仅剩共享 `RiskRuleGenerationService` 817 行既有门禁失败,本轮所有相关核心类低于 800 行。
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# 商业计量、客户 ROI 与可持续定价 开发 TODO
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每项必须回链 `CONCEPT.md` 对应章节。
|
||||||
|
- 只有代码、接口或容器验证提供证据后才能勾选。
|
||||||
|
- 客户价值、平台收入、内部成本和工时估值必须分账;mock 与手工事件不得标记为生产事实。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 背景与问题] 明确商业权益、用量、成本、客户 ROI、平台毛利和定价不是同一事实。
|
||||||
|
证据:`CONCEPT.md`“背景与问题”“目标与非目标”。
|
||||||
|
- [x] [CONCEPT: 目标与非目标] 确认不硬编码客户价格、不混算风险暴露、不跨币种求和、不让付费绕过安全门禁。
|
||||||
|
证据:`CONCEPT.md`“目标与非目标”。
|
||||||
|
|
||||||
|
## 2. 契约与设计
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 数据] 定义套餐、订阅、权益、用量和内部成本五类事实及状态。
|
||||||
|
证据:`commercial.py` 模型与 schema、`20260716_0016_commercial_metering.py`。
|
||||||
|
- [x] [CONCEPT: 算法与公式] 定义客户 ROI、贡献毛利和可持续定价走廊公式。
|
||||||
|
证据:`CONCEPT.md`“算法与公式”、`commercial_analytics.py`、`commercial_pricing.py`。
|
||||||
|
- [x] [CONCEPT: 权限] 定义平台配置、租户只读和商业/安全门禁分离。
|
||||||
|
证据:`commercial_access_policy.py`、`commercial_entitlements.py`。
|
||||||
|
|
||||||
|
## 3. 后端实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 数据] 新增五张商业表、复合租户约束、幂等、冲回和 append-only 迁移。
|
||||||
|
证据:`models/commercial.py`、`20260716_0016_commercial_metering.py`、迁移/模型测试。
|
||||||
|
- [x] [CONCEPT: 数据] 新增运行时预占运营表、状态约束、复合租户外键、全局 tool call 幂等和迁移所有权。
|
||||||
|
证据:`models/commercial_runtime.py`、`20260716_0019_commercial_runtime_reservations.py`、`commercial_migration_assertions.py`;0019→0020 一次性 PostgreSQL 完整升降级循环通过。
|
||||||
|
- [x] [CONCEPT: 后端] 实现套餐版本、订阅、权益、配额、用量、成本与商业分析服务。
|
||||||
|
证据:`commercial_admin.py`、`commercial_entitlements.py`、`commercial_metering.py`、`commercial_analytics.py`。
|
||||||
|
- [x] [CONCEPT: 生命周期与查询] 实现暂停、逾期、取消、过期、恢复与五类历史查询。
|
||||||
|
证据:`CommercialAdminService.transition_subscription()`、`commercial_queries.py`、`/commercial/admin/tenants/{tenant_id}/...` 分资源接口。
|
||||||
|
- [x] [CONCEPT: 定价走廊] 实现成本下限、确认价值上限、成功费封顶和商业模式建议。
|
||||||
|
证据:`commercial_pricing.py`、`POST /commercial/admin/tenants/{tenant_id}/pricing-scenarios`。
|
||||||
|
- [x] [CONCEPT: 后端] 把中央 Orchestrator 工具接入执行前预占、真实 AgentToolCall 结算和失败释放。
|
||||||
|
证据:`orchestrator_tool_execution.py`、`agent_runs.py`、`commercial_runtime_bridge.py`;无配置兼容,配置后先 reserve 再执行,成功只追加真实用量/成本,失败和阻断不计量。
|
||||||
|
- [x] [CONCEPT: 降级策略] 持久化缺预占和计量故障补偿状态,并安全处理过期预占。
|
||||||
|
证据:`commercial_runtime_reservations.py`、`commercial_runtime_reconciler.py`;直接调用形成 reconciliation_required,运行中/未知终态继续持有额度,终态无调用才过期释放;用量成功但成本失败形成 committed_reconciliation_required,重试只补成本且不重复冻结额度。
|
||||||
|
- [x] [CONCEPT: 风险与开放问题] 把已识别的权威运行入口迁移到 permit 契约。
|
||||||
|
证据:中央 Orchestrator、`ocr_commercial.py`、`runtime_chat_commercial.py`、`financial_connector_commercial.py` 和 `expense_claim_attachment_commercial.py` 已接通预占/结算/释放;资源组合 63 项通过。
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 对后续新增的知识库/ONLYOFFICE 存储、实施和支持等资源入口持续执行 meter 盘点,不允许绕过 permit。
|
||||||
|
证据要求:新增真实资源入口时提供权威数量口径、事务边界、成本来源和回归测试;当前不把未发生的未来入口伪装成已计量。
|
||||||
|
- [x] [CONCEPT: 风险与开放问题] 通过不可变 billing period 和幂等 rollover 实现 `auto_renew` 周期滚动。
|
||||||
|
证据:`20260716_0021_commercial_billing_periods.py`、`commercial_billing_periods.py`、`commercial_subscription_rollover.py`、`commercial_rollover_scheduler.py`;月/季/年自动滚动,合同制和 `ends_at` 越界失败关闭,PostgreSQL 双线程仅生成一个账期,数据库触发器拒绝重叠账期。
|
||||||
|
- [x] [CONCEPT: 数据] 让用量、成本、运行时预占和配额历史绑定不可变账期,并分离配额重置键。
|
||||||
|
证据:`UsageMeterEvent`、`CommercialCostEvent`、`CommercialRuntimeReservation` 的 `billing_period_id`,以及 usage/reservation 的 `quota_period_key`;收费分析从账期快照读取基础费和币种。
|
||||||
|
- [x] [CONCEPT: 权限] 为套餐、订阅、权益和续期建立脱敏追加式审计与租户安全历史 API。
|
||||||
|
证据:`commercial_admin_events`、`commercial_admin_audit.py`、`commercial_billing.py`;管理写接口强制 `X-Request-Id` 和原因,普通 finance/executive 只能读本租户账期,审计仅平台管理员可读。
|
||||||
|
- [ ] [CONCEPT: 非目标] 对接真实开票/收款/订阅提供商并区分合同计费、已开票与已收现金。
|
||||||
|
证据:等待目标客户和 provider 选择,不使用 mock 冒充完成。
|
||||||
|
|
||||||
|
## 4. 前端实现
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 前端] 完成商业工作台的账户、套餐/订阅、权益/配额、用量/成本、价值/定价五块。
|
||||||
|
证据:`CommercialWorkspace.vue` 组合账户、生命周期、权益、用量成本、价值分析与定价场景面板。
|
||||||
|
- [x] [CONCEPT: 前端] 接入订阅暂停/取消/恢复、历史查询和操作确认。
|
||||||
|
证据:`useCommercialWorkspace.js`、`CommercialSubscriptionLifecyclePanel.vue`;终态和定价均有确认步骤,操作后按租户重新加载。
|
||||||
|
- [x] [CONCEPT: 前端] 严格分开展示客户 ROI 与平台毛利,并支持多币种和证据缺口状态。
|
||||||
|
证据:`CommercialValueAnalysisPanel.vue`、`CommercialPricingScenarioPanel.vue`、`commercialWorkspaceModel.js`;按币种分组,null/unavailable 显示“不可用”,不跨币种合计。
|
||||||
|
- [x] [CONCEPT: 前端] 接入现有应用入口、权限态、移动端和生产构建。
|
||||||
|
证据:`OverviewView.vue`/顶部导航已接入“商业化管理”;商业定向 35 项、全量 web 802 项、code-size 与 Vite 2246 modules 构建通过。
|
||||||
|
|
||||||
|
## 5. 测试与验证
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 测试方案] 后端模型、服务、HTTP、权限、生命周期、查询和定价回归通过。
|
||||||
|
证据:容器内 Ruff 通过;`test_commercial_models.py`、`test_commercial_services.py`、`test_commercial_endpoints.py` 当前 12 项通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 一次性 PostgreSQL 并发用量、原子预占、硬配额和成本冲回验证通过并记录当前命令结果。
|
||||||
|
证据:一次性 PostgreSQL 17 中 `test_commercial_concurrency_postgres.py` 4 项通过;两个并发工具竞争 1 份额度时仅一个 reservation 成功。
|
||||||
|
- [x] [CONCEPT: 测试方案] 运行时预占、结算、释放、幂等、变量上限、历史配置和补偿回归通过。
|
||||||
|
证据:`test_commercial_runtime_metering.py`、`test_commercial_runtime_reservations.py` 27 项通过;商业/Agent/权限/迁移相关组合 183 项通过、1 项因未显式配置外部迁移库跳过,Ruff、compileall 和相关类 800 行检查通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 前端行为测试、全量 web 测试、code-size 门禁和 Vite 构建通过。
|
||||||
|
证据:商业定向 35 项、全量 web 802 项通过;code-size 通过;Vite production build 转换 2246 个模块。
|
||||||
|
- [x] [CONCEPT: 测试方案] 不可变账期、脱敏审计、自动续期和调度器验证通过。
|
||||||
|
证据:容器内商业/迁移前置定向 131 项、前端商业 35 项及 Vite build 通过;PostgreSQL 17 商业并发 5 项通过,含双线程续期单赢家;0021 升级、重叠账期阻断、运行不变量和 0021→0020 降级通过。
|
||||||
|
- [x] [CONCEPT: 指标与验收] 逐项核对 A1-A7,并回填最终文件、接口和容器证据。
|
||||||
|
证据:商业模型/服务/API/前端、硬配额、账期、生命周期、ROI/毛利分账和定价走廊均有回归;资源边界组合 63 项、PostgreSQL 商业并发 5 项、Web 全量 815 项及 Vite build 通过。
|
||||||
|
|
||||||
|
## 6. 商业与试点收尾
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 用真实试点 30/90 天数据冻结目标毛利率、最大价值分享、包含量、超额策略和封顶。
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 确认发票、税率、回款、坏账、渠道和收入确认边界。
|
||||||
|
- [x] [CONCEPT: 本轮实现记录] 同步更新上位闭环文档与工程验收手册,不删除证据不足项。
|
||||||
|
证据:上位 AI 闭环 TODO 与 `engineering-closure-and-production-readiness` CONCEPT/TODO 已区分工程完成、生产上线和真实试点。
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
# 财务连接器与支付对账闭环 概念文档
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
把经过租户、来源、密钥版本和请求路径共同认证的 production-mode 财务事件契约接入付款、ERP、对账与 Savings 闭环,同时让所有非生产回执严格停留在只读模拟事实层;真实外部现金仍以目标 provider 联调为准。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
当前报销单可以由财务人员在平台内确认“已付款”,并能联动申请归档和 Savings 实现记录,但这只是内部业务状态,不是银行、支付平台或 ERP 的外部现金事实。若继续把单一状态当成真实回执,会留下重复付款、金额/币种错配、回执伪造、凭证缺失、对账异常未处置和虚假现金节省等风险。
|
||||||
|
|
||||||
|
本功能把外部财务系统接入收敛成统一、可审计的连接器契约。首期允许使用 mock adapter 验证协议和流程,但 mock 必须完整模拟签名、幂等、失败、重试、乱序、退款、凭证和对账差异,不以直接写“已付款”代替连接器事实。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- 建立租户隔离、来源可验证、追加式的支付/银行/ERP 事件账本。
|
||||||
|
- 支持支付批次、结算成功、支付失败、退款/冲回、ERP 入账凭证和对账结果。
|
||||||
|
- 只有单据、金额、币种、外部引用和签名均通过校验时,才推进报销付款状态。
|
||||||
|
- 把外部事件与 Expense Case、Business Event、Savings Evidence、归档事件用同一 correlation 链关联。
|
||||||
|
- 对重复、冲突、乱序和不完整事件 fail-closed,并提供可人工处理的对账异常。
|
||||||
|
- 以版本化 activate/disable/rotate 状态机管理连接器密钥,所有配置动作形成不含密钥材料的追加式审计。
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- 不自建银行清算、企业支付网络、税务开票网络或 ERP 总账。
|
||||||
|
- 不保存银行卡号、银行流水原文、完整付款人账号或连接器密钥明文。
|
||||||
|
- 不允许连接器绕过报销审批、风险门禁、租户权限或财务确认。
|
||||||
|
- 不把 mock 回执标记为生产级外部现金证据;运行环境和证据等级必须显式区分。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
- 财务付款员:提交或查看付款批次,处理失败与待匹配回执。
|
||||||
|
- 财务复核员:复核金额/币种/收款主体和对账异常,确认或拒绝处置。
|
||||||
|
- 财务负责人/CFO:查看已匹配、待对账、失败、退款和未入账金额。
|
||||||
|
- 平台管理员:配置连接器公钥/密钥版本、来源白名单和健康状态,但不能代替财务确认业务结果。
|
||||||
|
- 外部连接器:按租户和来源签名推送支付、银行或 ERP 事件,安全重试并读取幂等结果。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- 连接器注册与密钥版本:来源、环境、允许事件、时钟偏差、启停和轮换状态。
|
||||||
|
- 统一事件信封:tenant、provider、event ID、event type、occurred at、payload hash、correlation、signature version。
|
||||||
|
- 支付批次与回执:批次创建、提交、受理、成功、失败和部分成功。
|
||||||
|
- ERP 入账:凭证号、会计期间、入账时间、受控摘要和原始内容哈希。
|
||||||
|
- 对账:按单据、金额、币种和外部引用自动匹配;差异进入人工处置。
|
||||||
|
- 冲回:退款、撤销和补付使用新事件,不更新或删除原事件。
|
||||||
|
- 可观测性:最近成功时间、失败率、重试次数、积压、签名失败和对账差异。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 模块职责
|
||||||
|
|
||||||
|
- `financial_connector_auth`:验证来源、签名、时间戳、密钥版本和重放窗口。
|
||||||
|
- `financial_connector_ingestion`:规范化事件、计算指纹、幂等写入、生产/模拟分流和冲突检测。
|
||||||
|
- `financial_connector_simulation`:只读校验 test/mock/staging 回执,不创建或修改 Claim、对账、ERP、Business Event 或 Savings。
|
||||||
|
- `financial_connector_mock_adapter`:平台管理员显式触发的非生产场景适配器;用已激活配置的真实签名链确定性生成成功、失败、乱序、重复、冲突、退款和 ERP 回执,但不提供任意 payload 注入能力。
|
||||||
|
- `financial_connector_observability`:按租户和配置聚合最近成功、失败率、重试、积压、签名失败、冲突和对账异常;只读取最小化事实与追加式运行事件。
|
||||||
|
- `financial_connector_payment_evidence`:从 Claim 的付款事实中提取统一证据 DTO,明确区分 production-mode 外部回执分类、非生产模拟回执和人工付款内部状态;分类本身不证明真实 provider 已接通。
|
||||||
|
- `financial_connector_config_lifecycle`:执行带 expected version 的激活、停用和原子密钥轮换。
|
||||||
|
- `payment_reconciliation`:匹配 Claim、金额、币种和状态,生成 matched / exception 结果。
|
||||||
|
- `financial_connector_actions`:在可信匹配后调用现有付款动作;支付失败、退款和 ERP 入账分别旁写事件。
|
||||||
|
- `financial_connector_projection`:为财务工作台提供脱敏列表、详情、差异和健康度。
|
||||||
|
- 供应商 adapter 只负责供应商字段映射,不直接修改 Claim、Savings 或预算。
|
||||||
|
|
||||||
|
### 数据与契约
|
||||||
|
|
||||||
|
首期新增以下 migration-owned 表,均带 `tenant_id`:
|
||||||
|
|
||||||
|
- `financial_connector_configs`:provider、environment、allowed event types、secret/key version、status、last success/error;只保存密钥引用或不可逆验证材料。
|
||||||
|
- `financial_connector_config_events`:created、activated、disabled、rotation started/replacement created 的追加式配置审计;保存 actor、request、reason、expected version 和脱敏前后状态,不保存 `secret_ref`。
|
||||||
|
- `financial_connector_events`:方向、事件类型、外部事件 ID、请求指纹、原始内容哈希、发生/接收时间、验证等级、处理状态、关联单据/Case 和错误码;UPDATE/DELETE 禁止。
|
||||||
|
- `payment_reconciliation_cases`:Claim、期望/实际金额与币种、匹配状态、差异、处置版本、负责人和最后事件;作为可变投影,历史动作另存事件。
|
||||||
|
- `payment_reconciliation_events`:创建、自动匹配、人工确认、拒绝、重开、退款和关闭的追加式审计事件,保存请求指纹和首次响应。
|
||||||
|
- `financial_connector_operational_events`(`0022`):保存 `replay / auth_failure / payload_conflict` 三类运行事实的 tenant/config/provider/environment、受控原因码、两类 HMAC 指纹、幂等键和发生时间;不保存原始 payload、签名、外部事件 ID、Claim 引用、correlation 或密钥材料。复合租户外键防止跨租户归属,PostgreSQL 触发器禁止 UPDATE/DELETE;存在运行事实时拒绝有损降级。
|
||||||
|
|
||||||
|
关键约束:
|
||||||
|
|
||||||
|
- `(tenant_id, provider, external_event_id)` 唯一。
|
||||||
|
- 配置从 `disabled/version=1` 创建;激活和停用必须命中 expected version,轮换原子地产生新 active key version 并把旧版本置为 rotating。
|
||||||
|
- 同幂等键不同 payload hash 返回冲突,不能覆盖首次事件。
|
||||||
|
- 同一个运行事实 candidate 的补偿写入按 `(tenant_id, idempotency_key)` 幂等;不同 HTTP 尝试即使请求内容相同,也因可信发生时间不同而分别计数,避免把真实重放次数永久压成一次。
|
||||||
|
- Claim、Case、配置和对账记录使用复合租户外键。
|
||||||
|
- 退款/冲回必须引用同租户原结算事件。
|
||||||
|
- 生产事件必须通过已激活密钥验证;test/mock/staging 即使签名和业务字段全部匹配,也只能生成 `projection_scope=simulation_only` 的连接器事实,绝不进入核心财务状态机。
|
||||||
|
|
||||||
|
### 接口
|
||||||
|
|
||||||
|
- `POST /api/v1/integrations/financial-events`:连接器签名事件入口;返回稳定接收/重放结果。
|
||||||
|
- `POST /api/v1/financial-connectors/admin/tenants/{tenant}/configs/{id}/activate|disable|rotate`:带版本、actor、request ID 和 reason 的配置状态机。
|
||||||
|
- `POST /api/v1/financial-connectors/admin/tenants/{tenant}/configs/{id}/simulate`:平台管理员运行确定性非生产场景;production 配置和未激活配置 fail-closed。
|
||||||
|
- `GET /api/v1/financial-connectors/admin/tenants/{tenant}/config-events`:读取不含密钥材料的配置审计时间线。
|
||||||
|
- `GET /api/v1/financial-connectors/admin/tenants/{tenant}/observability`:平台管理员读取目标租户脱敏运行指标;从 config/event/reconciliation/operational event 聚合指定窗口真实值。
|
||||||
|
- `GET /api/v1/financial-connectors/observability`:财务角色读取当前租户脱敏运行指标;返回 `window_started_at / as_of / generated_at / source_revision`,以及 replay、认证失败、签名失败、payload conflict 的真实计数与最近发生时间。`0022` 起四类指标均标记 `available`,没有事实时真实返回零而不是“待采集”占位。
|
||||||
|
- `GET /api/v1/financial-connectors/payment-evidence/{claim}`:有单据读取权限的当前租户用户读取付款证据等级,不返回原始连接器内容。
|
||||||
|
- `GET /api/v1/financial-reconciliation/cases`:财务角色分页查看匹配与异常。
|
||||||
|
- `GET /api/v1/financial-reconciliation/cases/{id}`:查看脱敏证据和追加式时间线。
|
||||||
|
- `POST /api/v1/financial-reconciliation/cases/{id}/confirm`:独立财务确认差异或人工匹配。
|
||||||
|
- `POST /api/v1/financial-reconciliation/cases/{id}/reject`:拒绝错误回执并记录原因。
|
||||||
|
- 连接器配置管理接口仅平台管理员可用,业务确认接口仅财务角色可用,两类权限不互相继承。
|
||||||
|
|
||||||
|
### 匹配算法
|
||||||
|
|
||||||
|
1. 验证 tenant/provider/key version/timestamp/signature 和事件类型;HMAC canonical request 同时绑定固定 HTTP method/path,禁止共享密钥跨 provider 或 key version 重放。
|
||||||
|
2. 以 canonical JSON 生成 payload hash;按外部事件 ID 与幂等键检查首次请求。
|
||||||
|
3. 通过受控引用解析 Claim,不允许仅凭模糊姓名或备注自动匹配。
|
||||||
|
4. 校验 Claim 已完成审批且处于待付款;比较金额、币种、外部业务引用和事件方向。
|
||||||
|
5. 只有 `production_verified` 且完全一致时才自动 matched;非生产来源只生成模拟投影,任何生产差异进入 exception 且不改变 Claim。
|
||||||
|
6. matched 事件在同事务调用付款动作、记录 Business Event,并把外部事件哈希作为 Savings 证据。
|
||||||
|
7. ERP posted 只表示入账,不重复触发付款;refund/reversal 追加负向业务与 Savings 冲回候选。
|
||||||
|
|
||||||
|
### 权限与安全
|
||||||
|
|
||||||
|
- 连接器入口不使用普通用户会话,使用租户绑定的签名认证;普通 Bearer token 不能伪装连接器。
|
||||||
|
- HMAC/签名比较使用常量时间函数,限制时间窗口并记录 nonce/外部事件 ID 防重放。
|
||||||
|
- 激活和轮换前由服务端解析 `secret_ref` 并校验至少 128-bit HMAC 密钥;数据库和审计 DTO 均不返回引用或明文。
|
||||||
|
- 日志、响应和 DTO 最多暴露外部引用后八位或不可逆摘要,不返回原始 payload、签名或密钥。
|
||||||
|
- replay 随成功重放事务提交;认证失败与 payload 冲突先回滚失败业务事务,再独立提交脱敏运行事实,审计写入异常不得覆盖原始 401/409 响应。
|
||||||
|
- 只有服务端按 tenant/provider/key version 解析出 active 配置,并成功解析该配置的服务端密钥后,才构造可归属的 operational context。缺认证头、请求 tenant 不一致、未知/未激活配置或密钥不可用均不接受客户端自报租户,只写不带租户归属的结构化警告;签名、时间窗和事件白名单失败才可安全归入已解析配置。
|
||||||
|
- 运行表只保存 `hmac-sha256:` 请求/外部事件指纹和 `sha256:` 幂等键。HMAC 输入可以包含外部 ID 和业务引用,但这些原值不会进入表、DTO 或错误日志。
|
||||||
|
- 不可变连接器事实的 normalized payload 不再保存完整 `claim_reference`;`0020` 受控迁移会删除历史冗余值,保留内容哈希、受控 Claim ID 与必要尾号。
|
||||||
|
- 跨租户资源统一按 404 隐藏;配置管理员不能确认对账,付款申请人不能确认自己的异常。
|
||||||
|
- 连接器故障、未知密钥、签名异常、金额/币种不一致和数据库异常均 fail-closed。
|
||||||
|
|
||||||
|
### 状态转换
|
||||||
|
|
||||||
|
- 连接器事件:`received → verified → processed`,失败进入 `rejected`;事实本身追加只读。
|
||||||
|
- 连接器配置:`disabled → active → rotating → disabled`;正常启用走 `disabled → active`,轮换时旧 active 原子进入 rotating、新 key version 以 active 创建。
|
||||||
|
- 对账记录:`pending → matched | exception → confirmed | rejected`;退款可从 confirmed 进入 `reopened`,重新处置后关闭。
|
||||||
|
- Claim 仅在可信 `payment_settled + matched` 后从 `pending_payment` 进入 `paid`。
|
||||||
|
- ERP 凭证从 `pending_posting` 进入 `posted | posting_failed`,不反向伪造支付成功。
|
||||||
|
|
||||||
|
### 降级策略
|
||||||
|
|
||||||
|
- 连接器离线:保留待付款,不自动标记已付;显示积压和最后成功时间。
|
||||||
|
- 回执乱序:先保存事实,等待前置事件或进入 pending,不猜测状态。
|
||||||
|
- 回执冲突:保留首次事实并返回 409,生成对账异常。
|
||||||
|
- ERP 未接入:付款事实可进入已付,但“已入账/凭证号”保持待采集。
|
||||||
|
- test/mock/staging:返回 `simulation_only`,只保留追加式 connector fact/response projection,不创建对账 Case、不修改 Claim/ERP/归档/Savings,也不进入生产现金证明。
|
||||||
|
- 显式 mock adapter 只能选择预定义场景和当前租户 Claim;事件 ID、correlation 和受控外部引用由 tenant/config/scenario/request ID 确定性派生。重复执行同一请求只产生稳定重放,不能借模拟接口注入生产配置或任意字段。
|
||||||
|
- 升级前若已有非生产事件且响应曾关联对账 Case,`0020` 将其标记为 `legacy_nonproduction_effect_unknown` 供审计,不伪装成新策略下的无副作用模拟事实,也不在迁移中猜测性冲回历史财务状态。
|
||||||
|
|
||||||
|
### 兼容策略
|
||||||
|
|
||||||
|
- 保留现有人工“确认已付款”作为低等级内部证据;付款证据 DTO 使用 `internal_manual_payment`,生产连接器使用 `external_cash`,非生产连接器使用 `simulated_connector`/`staging_connector`。UI 必须同时展示来源标签和可信等级,不能只显示“已付款”。
|
||||||
|
- 新连接器路径复用现有幂等付款动作、Case 时间线和 Savings 实现服务,不复制第二套状态机。
|
||||||
|
- 现有 `risk_flags_json` 付款摘要继续只读兼容,新连接器事实进入正式事件表。
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
- 单元:签名、时间窗口、canonical hash、幂等重放、冲突、乱序和字段白名单。
|
||||||
|
- 权限:跨租户、普通用户伪造、管理员越权、申请人自证和密钥停用。
|
||||||
|
- PostgreSQL:复合外键、唯一键、append-only、并发同事件、不同 payload 冲突和安全降级。
|
||||||
|
- 集成:申请 → 票据 → 报销 → 预审 → 审批 → 外部付款 → 对账 → ERP 入账 → 归档 → Savings 待确认。
|
||||||
|
- 反向:支付失败不推进、金额/币种错配不推进、重复回执只写一次、退款追加冲回。
|
||||||
|
- adapter:逐场景验证确定性结果、重复请求、跨租户 Claim 隐藏、production 拒绝,以及 Claim/对账/ERP/Business Event/Savings 零副作用。
|
||||||
|
- 可观测性:验证租户隔离、财务/管理员权限、窗口边界、签名失败与重放计数,并断言 DTO/日志无原始 payload、签名和密钥。
|
||||||
|
- 运行事件:验证同 candidate 并发/补偿重试单赢家、不同请求尝试分别计数、冲突回滚后独立持久化、复合租户外键、HMAC 格式检查和数据库 append-only。
|
||||||
|
- 所有验证只在 `local-x-financial-linux` 容器内执行,每条命令最长 60 秒。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
本能力不做概率预测,核心是确定性门禁:
|
||||||
|
|
||||||
|
```text
|
||||||
|
canonical_effect_allowed = (
|
||||||
|
verification_level == production_verified
|
||||||
|
AND signature_valid
|
||||||
|
AND tenant_provider_key_path_bound
|
||||||
|
AND claim_amount_currency_reference_match
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
任一条件为假都不得产生核心财务副作用;非生产环境无论其他条件是否为真,`canonical_effect_allowed` 固定为 false。
|
||||||
|
|
||||||
|
运行指标使用确定性窗口聚合,不从应用日志估算:
|
||||||
|
|
||||||
|
```text
|
||||||
|
operational_count(type, window) = COUNT(
|
||||||
|
tenant_id = current_tenant
|
||||||
|
AND event_type = type
|
||||||
|
AND window_started_at <= occurred_at <= as_of
|
||||||
|
)
|
||||||
|
|
||||||
|
operational_idempotency_key = SHA256(
|
||||||
|
source_revision, tenant, config, type, reason,
|
||||||
|
HMAC(request), HMAC(external_event), occurred_at
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
`source_revision=20260716_0022` 表示当前运行指标的数据源与聚合契约版本;它不是 provider 协议版本。发生时间属于本次接收尝试,因此同一 candidate 重试仍稳定,而新尝试会形成新的真实计数。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- 100% 外部结算事件具有租户、来源、签名版本、payload hash、外部 ID 和接收时间。
|
||||||
|
- 重复相同事件稳定重放,冲突 payload 100% 拒绝。
|
||||||
|
- 任何金额/币种/Claim/审批状态不一致均不会推进已付款。
|
||||||
|
- 外部支付、ERP 凭证、对账处置、归档和 Savings 证据可由 correlation 链回放。
|
||||||
|
- 连接器离线或异常时不出现虚假“已付款”“已入账”或现金节省。
|
||||||
|
- observability 响应 100% 标明窗口起止和 source revision;三类运行事实按租户真实计数,并提供各类最近发生时间。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
- 首期真实 provider、签名算法、事件字段和 SLA 需要目标客户确认。
|
||||||
|
- 部分 ERP 只有批次级凭证,需要明确批次到单据的拆分和舍入规则。
|
||||||
|
- 多币种付款需要锁定汇率来源和会计期间;本功能不自行猜测汇率。
|
||||||
|
- 退款、补付、员工自担调整是否进入现金节省,仍需客户财务签字口径。
|
||||||
|
- 对账大额阈值及双人复核需按企业策略配置。
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
- 2026-07-16:完成现有内部付款、申请归档、Business Event、Savings 实现链路盘点,并冻结统一连接器、安全认证、对账状态和完整 E2E 方案;实现与容器证据保留在同目录 TODO 中继续执行。
|
||||||
|
- 2026-07-16:完成 `0017` 四表迁移、HMAC/密钥版本/时间窗认证、租户隔离、追加式事件、幂等冲突和对账投影;一次性 PostgreSQL 17 迁移循环与 2 项并发探针通过。
|
||||||
|
- 2026-07-16:支付结算只在金额、币种、Claim、审批状态与来源全部匹配时复用正式付款动作;ERP、失败、退款/冲回、Savings 失效和财务处置均进入同一 correlation 审计链,连接器服务/HTTP 7 项通过。
|
||||||
|
- 2026-07-16:真实 provider、批次拆分、汇率、会计期间和大额双人复核仍由目标客户确认;当前 mock/test 证据始终标记 simulated,不冒充生产现金事实。
|
||||||
|
- 2026-07-16:补齐 `0020` 配置版本与追加式审计迁移;新配置只能停用创建,激活前校验服务端密钥,轮换原子切换 key version,HTTP 时间线不暴露 `secret_ref`。
|
||||||
|
- 2026-07-16:把 test/mock/staging 六类事件收口到 `simulation_only` 只读投影;生产事件继续完成付款、ERP、归档、Savings 和冲回,生产冲回也不能引用模拟原事件。
|
||||||
|
- 2026-07-16:normalized payload 删除完整 `claim_reference`,历史冗余值由 `0020` 受控脱敏,HMAC v2 继续以内容哈希和 tenant/provider/key version/path 证明请求边界。
|
||||||
|
- 2026-07-16:容器内连接器/配置/费用价值链组合 16 项、PostgreSQL 并发 3 项、迁移静态 124 项及全新 PostgreSQL 17 完整升降级循环通过;独立 0019→0020 探针确认版本回填、历史脱敏和 legacy 不确定性标记符合契约。
|
||||||
|
- 2026-07-16:新增显式非生产 adapter,以 tenant/config/claim/scenario/request ID 派生稳定签名回执,覆盖成功、失败、乱序、重复、冲突、退款和 ERP 场景;HTTP 与服务测试确认 simulation-only 且 Claim、对账、Business Event、ERP 与 Savings 零副作用。
|
||||||
|
- 2026-07-16:新增租户级脱敏可观测性 API 与财务看板面板,现有 config/event/reconciliation 表提供最后成功、失败率、积压和对账异常真实值;retry/auth_failure 在 `0022` 运行事件落地前明确显示 unavailable,不伪造零值。
|
||||||
|
- 2026-07-16:新增付款证据等级 DTO 和 UI 口径,通过 production-mode 签名契约的回执分类为 `external_cash`,人工确认标为低等级 `internal_manual_payment`,模拟/预发布回执明确不进入核心账;当前本地自签测试不作为真实现金或 provider 接通证据。
|
||||||
|
- 2026-07-17:完成 `0022` 追加式运行事实迁移与服务接入,耐久记录 replay、可信归属后的 auth failure 和 payload conflict;请求与外部事件只保存 HMAC 指纹,失败事务回滚后独立补偿提交。
|
||||||
|
- 2026-07-17:可观测性改为返回 `20260716_0022` source revision、明确窗口、真实计数和最近时间;同一 candidate 补偿重试幂等,不同 HTTP 尝试分别计数。迁移总头继续串到既有 `0023`,`0022` 不越界创建后继 AI 表。
|
||||||
|
- 2026-07-17:全新一次性 PostgreSQL 17 完整迁移循环 51 项、连接器并发 4 项、后继盲审并发 7 项通过;验证租户复合外键、HMAC 格式、运行事实 append-only 和并发单赢家。
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# 财务连接器与支付对账闭环 TODO
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每项必须回链 `CONCEPT.md`;只有代码、迁移、接口或容器验证提供证据后才能勾选。
|
||||||
|
- 外部回执、内部付款状态、ERP 入账和财务确认必须分开,mock 不得伪装成生产现金事实。
|
||||||
|
|
||||||
|
## 1. 契约与安全
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 背景与问题] 盘点内部付款、申请归档、Business Event、Savings 实现和证据边界。
|
||||||
|
证据:`expense_claim_approval_flow.py`、`expense_claim_application_handoff.py`、`expense_cases.py`、`savings_realization.py` 只读审计。
|
||||||
|
- [x] [CONCEPT: 目标与非目标] 冻结签名事件、幂等、对账、ERP 凭证、冲回和 mock 环境边界。
|
||||||
|
证据:`CONCEPT.md`“目标与非目标”“数据与契约”“匹配算法”“降级策略”。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 实现连接器签名认证、密钥版本、时间窗口、来源白名单和防重放。
|
||||||
|
证据:`financial_connector_auth.py`、`financial_connector_ingestion.py`;HMAC 使用常量时间比较,tenant/provider/key version/timestamp/method/path 均进入签名边界,共享密钥跨 provider/key version 重放被拒绝,相同事件幂等重放、冲突 payload 返回 409。
|
||||||
|
- [x] [CONCEPT: 权限与安全] 实现平台配置权限与财务处置权限分离、跨租户 404 和申请人自证拒绝。
|
||||||
|
证据:`financial_connectors.py`、`financial_connector_projection.py`;HTTP/服务回归覆盖普通用户、平台管理员、财务角色、跨租户隐藏与申请人自证拒绝。
|
||||||
|
|
||||||
|
## 2. 数据与迁移
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 数据与契约] 新增配置、外部事件、对账投影和对账事件模型。
|
||||||
|
证据:`models/financial_connector.py`、`schemas/financial_connector.py`。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 新增后继 Alembic 迁移、迁移所有权、复合租户外键、唯一/检查约束和 append-only 触发器。
|
||||||
|
证据:`20260716_0017_financial_connector_reconciliation.py`、`schema_ownership.py`、`migration_preflight.py`;一次性 PostgreSQL 17 完整迁移循环通过。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 实现外部事件与退款/冲回引用、首次响应和 payload 指纹冲突。
|
||||||
|
证据:`financial_connector_ingestion.py`、`payment_reconciliation.py`;同外部事件不同 payload 拒绝,退款/冲回必须绑定同租户已处理结算原事件。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 新增配置 version、追加式配置审计和历史 normalized payload 脱敏迁移。
|
||||||
|
证据:`20260716_0020_financial_connector_config_lifecycle.py`、`FinancialConnectorConfigEvent`;配置审计使用 PostgreSQL append-only trigger,历史 `claim_reference` 从不可变事件的 normalized payload 中受控移除,内容哈希继续保留;全新 PostgreSQL 17 完整升降级循环 1 项通过,另一个独立库验证 0019→0020 历史数据脱敏与 legacy 标识。
|
||||||
|
|
||||||
|
## 3. 服务与接口
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 模块职责] 拆分认证、ingestion、reconciliation、action 和 projection 服务,核心文件不超过 800 行。
|
||||||
|
证据:`financial_connector_auth.py`、`financial_connector_ingestion.py`、`payment_reconciliation.py`、`financial_connector_actions.py`、`financial_connector_projection.py` 职责独立,最大核心文件低于 800 行。
|
||||||
|
- [x] [CONCEPT: 接口] 实现统一事件入口、连接器配置、对账列表/详情、确认和拒绝接口。
|
||||||
|
证据:`api/v1/endpoints/financial_connectors.py`。
|
||||||
|
- [x] [CONCEPT: 接口] 实现带 expected version、actor、request ID、reason 的 activate/disable/rotate 状态机与脱敏审计查询。
|
||||||
|
证据:`financial_connector_config_lifecycle.py`、`financial_connector_config_audit.py`、`FinancialConnectorConfigLifecycleAction`、`FinancialConnectorConfigRotateAction`;激活/轮换前解析服务端密钥并校验强度,轮换原子切换新旧 key version。
|
||||||
|
- [x] [CONCEPT: 匹配算法] 完全匹配时复用现有幂等付款动作;任何金额、币种、单据或审批状态差异不产生付款副作用。
|
||||||
|
证据:`FinancialConnectorActionService` 复用 `ExpenseClaimService.mark_claim_paid_from_connector()`;反向测试验证 mismatch/failure/conflict 无付款副作用。
|
||||||
|
- [x] [CONCEPT: 证据与审计] 把外部事件哈希写入 Expense Case/Business Event/Savings 证据 correlation 链。
|
||||||
|
证据:连接器结算动作写入脱敏内容哈希、verification/evidence classification 与 correlation;服务 E2E 可回放付款、Case、Business Event 和 Savings evidence。
|
||||||
|
- [x] [CONCEPT: 状态转换] 实现支付失败、ERP posted/posting_failed、退款/冲回和对账重开。
|
||||||
|
证据:`PaymentReconciliationService` 对六类生产事件分流;ERP 不重复付款,生产退款/冲回恢复 Claim 并追加 Savings 冲回事实。
|
||||||
|
- [x] [CONCEPT: 降级策略] test/mock/staging 六类事件只写 simulation-only connector fact/response projection,不修改核心财务状态。
|
||||||
|
证据:`financial_connector_simulation.py`、`financial_connector_ingestion.py`;`test_financial_connector_services.py` 参数化覆盖三类非生产环境和六类事件,Claim、申请归档、对账、Business Event、ERP 与 Savings 均保持不变。
|
||||||
|
|
||||||
|
## 4. Mock 与可观测性
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 降级策略] 实现明确标识 test/mock 的 adapter,覆盖成功、失败、乱序、重复、冲突、退款和 ERP 回执。
|
||||||
|
证据:`financial_connector_mock_adapter.py`、`FinancialConnectorSimulationCreate/Read` 与平台管理员 simulate API;仅 active test/mock/staging 可运行,tenant/config/claim/scenario/request ID 确定性派生事件,production、disabled 和跨租户请求 fail-closed;7 场景服务/HTTP 回归通过。
|
||||||
|
- [x] [CONCEPT: 可观测性] 从现有事实输出最后成功时间、失败率、积压和对账异常,并在安全 DTO/UI 声明未采集指标。
|
||||||
|
证据:`financial_connector_observability.py`、当前租户/管理员 observability API、`FinancialConnectorHealthPanel.vue`;所有查询先绑定 tenant,仅聚合 config/event/reconciliation 最小化事实,不返回原始 payload、签名和密钥。
|
||||||
|
- [x] [CONCEPT: 可观测性] 用后继 `0022` 追加式运行事件补齐 replay、auth_failure 和 payload conflict 耐久计数。
|
||||||
|
证据:`20260716_0022_financial_connector_operational_events.py`、`financial_connector_operational_events.py`、`financial_connector_auth.py`、`financial_connector_ingestion.py`、`financial_connector_observability.py`;只有可信配置与服务端密钥解析后才归属认证失败,表内只保存 HMAC 指纹;同 candidate 重试幂等、不同接收尝试分别计数,API 返回窗口、source revision、真实数量和最近时间。
|
||||||
|
- [x] [CONCEPT: 兼容策略] 保留人工付款为低等级内部证据,并在 DTO/UI 区分外部回执和内部确认。
|
||||||
|
证据:`financial_connector_payment_evidence.py`、payment-evidence API、`FinancialPaymentEvidenceRead` 与面板证据口径;人工付款=`internal_manual_payment`,通过 production-mode 契约验证的外部回执分类=`external_cash`,非生产回执=`simulated_connector|staging_connector`。真实 provider 仍待联调。
|
||||||
|
|
||||||
|
## 5. 测试与验收
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 测试方案] 签名、防重放、幂等、冲突、字段白名单和错误恢复单元测试通过。
|
||||||
|
证据:`test_financial_connector_services.py`、`test_financial_connector_endpoints.py`、`test_financial_connector_config_lifecycle.py` 与费用价值链 E2E 共 16 项通过;乱序原事件缺失保守进入 exception,不推进付款。
|
||||||
|
- [x] [CONCEPT: 测试方案] PostgreSQL 迁移、复合租户约束、append-only、并发和安全降级验证通过。
|
||||||
|
证据:fresh PostgreSQL 17 最终迁移总探针 62 项通过;`financial_connector_migration_assertions.py` 验证配置/事件/运行事实 trigger、租户约束、HMAC 格式和 version check;`test_financial_connector_concurrency_postgres.py` 4 项通过,覆盖同事件、冲突补偿事实、配置单版本胜者和 operational candidate 单赢家;最终 head 为 `20260717_0028`。
|
||||||
|
- [x] [CONCEPT: 测试方案] 申请 → 票据 → 报销 → 预审 → 审批 → 外部付款 → 对账 → ERP 入账 → 归档端到端通过。
|
||||||
|
证据:`test_expense_financial_value_chain_e2e.py` 使用测试密钥自签 production-mode HMAC 事件,覆盖申请审批、报销审批、ERP 入账、独立财务确认、Savings、商业价值与退款冲回契约;与连接器定向组合共 16 项通过,不代表真实 provider 回执或现金。
|
||||||
|
- [x] [CONCEPT: 测试方案] 支付失败、金额/币种错配、重复回执和退款反向链路通过。
|
||||||
|
证据:`test_financial_connector_services.py` 覆盖 production-mode 失败/错配无副作用、稳定重放、ERP、reversal/refund 追加 Savings 冲回,以及非生产回执完全不创建 Savings。
|
||||||
|
- [x] [CONCEPT: 容器验证] 相关 pytest、Ruff、前端测试和构建均在 `local-x-financial-linux` 内通过。
|
||||||
|
证据:历史连接器/配置/费用价值链/迁移组合 `140 passed, 1 skipped`;adapter/观测/证据 DTO 与既有连接器回归 `21 passed, 3 skipped`。2026-07-17 的 `0022` 收尾在容器内新增/定向回归 `115 passed`,全新 PostgreSQL 17 完整迁移循环 `51 passed`,连接器并发 `4 passed`,后继 `0023` 并发 `7 passed`;相关 Ruff、文件行数和全树 `git diff --check` 通过。历史连接器前端 3 项与 Vite 生产构建已通过;共享前端曾有 5 项旧路径测试失败,已单独记录且不属于本切片。
|
||||||
|
|
||||||
|
## 6. 客户配置待确认
|
||||||
|
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 确认首个 provider、签名算法、字段映射、事件 SLA 和重试窗口。
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 确认批次到单据映射、多币种汇率、会计期间、大额双人复核和退款口径。
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
# 节省事实账本与 CFO 经营价值看板 概念文档
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
把费用优化从“发现风险和预计能省”推进为“执行、实际结果、独立财务确认、可回放冲回”的事实账本,并让 CFO 只看到有来源、有基线、有证据、可去重的企业价值。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
- 现有财务看板能够回答支出、单量、待付款、预算使用和风险分布,但不能可信回答企业已经节省了多少钱。
|
||||||
|
- 风险观察金额、暂缓付款、未使用预算和未执行建议都不是现金节省;如果直接汇总,会形成虚假 ROI。
|
||||||
|
- 当前最接近真实节省的链路是“住宿超标准 → 用户接受职级标准重算 → 审批 → 付款”。它已经真实降低报销金额,但尚未形成独立机会、付款后结果、财务签字、去重和冲回记录。
|
||||||
|
- `ExpenseClaim`、预算和应付旧表没有结构化租户键,Claim 只能通过 `ExpenseCaseLink` 判断租户;旧 `FinanceDashboardService` 仍有全表读取和跨租户快照复用风险,不能作为客户 ROI 的事实源。
|
||||||
|
- 现有 `accept_standard_adjustment` 优先使用客户端传入的原金额,客户端理论上可以放大差额;任何节省计算必须改为只使用服务端锁定的明细金额和政策计算快照。
|
||||||
|
- 当前“已付款”是财务人员在系统内确认的业务状态,尚没有银行流水、支付回执或 ERP 凭证。因此付款只能把机会推进到“实际结果待确认”,不能直接进入财务确认 KPI。
|
||||||
|
- 报销提交时间到同租户首个 `payment_completed` 业务事件可以形成可审计的端到端流程周期,但它包含等待与系统处理,不是人工活跃工时;在没有人工计时基线、角色成本和客户认可释放比例前,工时价值必须显示“待采集”,不能伪造为 0 或现金节省。
|
||||||
|
|
||||||
|
本方案是 `2026-07-13/feature/ai-expense-closed-loop-and-value-proof` 中 P2“费用经营与价值证明”的实施拆分。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
- [G1] 建立租户安全的 Savings Ledger,完整区分风险暴露、预计机会、执行中、实际结果、财务确认和冲回。
|
||||||
|
- [G2] 建立不可变基线和证据链,所有金额都能追溯到费用事件、单据明细、政策版本、执行动作、付款事件和确认人。
|
||||||
|
- [G3] 建立经济收益去重键和归因约束,避免同一单据、付款义务或政策差额被多个风险重复计入。
|
||||||
|
- [G4] 建立严格状态机、乐观版本、幂等响应和数据库并发约束,防止重复确认、陈旧操作和跨租户访问。
|
||||||
|
- [G5] 建立 CFO 价值看板,分开展示财务确认现金节省、可释放工时价值、安全智能直通率、经营漏斗和风险护栏。
|
||||||
|
- [G6] 支持部门、项目、费用类型、供应商、城市、时间、负责人、来源和单据下钻,并显示数据覆盖、口径和新鲜度。
|
||||||
|
- [G7] 持久化费用基线快照,记录窗口、样本量、算法版本、政策版本、来源指纹和数据质量。
|
||||||
|
- [G8] 修复旧财务聚合与快照的租户边界,禁止真实接口失败时回退成看似真实的演示数字。
|
||||||
|
|
||||||
|
### 非目标
|
||||||
|
|
||||||
|
- [NG1] 不把风险关联金额、暂缓付款金额、未采纳建议、未使用预算或预计金额计入已确认节省。
|
||||||
|
- [NG2] 首个切片不宣称已具备外部银行或 ERP 付款凭证;后续通过连接器补齐。
|
||||||
|
- [NG3] 首个切片不使用缺少租户、合同价、采购数量和付款凭证的 `AccountsPayableRecord` 计算供应商节省。
|
||||||
|
- [NG4] 不把申请金额与最终报销差额默认归因给 AI;缺少具体 AI 决策、采纳动作和结果链时,AI 归因金额为 0。
|
||||||
|
- [NG5] 不把流程经过时长换算为人工工时,不直接暴露个人薪酬或个人成本。
|
||||||
|
- [NG6] 不跨币种直接求和;没有锁定汇率的金额只按原币展示并进入数据质量提醒。
|
||||||
|
- [NG7] 不删除、覆盖已确认收益;补付、退款、申诉或归因修正使用追加负向冲回事件。
|
||||||
|
- [NG8] 不在本阶段重写整个 Overview,也不把不可信的预算中心模拟数据接入价值看板。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
### 目标用户
|
||||||
|
|
||||||
|
1. CFO/管理层:查看企业已经确认的现金价值、价值兑现速度和风险护栏。
|
||||||
|
2. 财务运营:复核机会、实际结果、重复归因、凭证和冲回事项。
|
||||||
|
3. 费用治理负责人:领取机会、执行动作、补充结果和跟进逾期。
|
||||||
|
4. 预算负责人:只在授权部门或成本中心范围内查看机会与驱动。
|
||||||
|
5. 审计/风控:回放基线、政策、执行、付款、确认、冲回和操作事件。
|
||||||
|
6. 普通员工:仅在自己的费用事件中看到与本人相关的调整说明,不访问企业 CFO 汇总。
|
||||||
|
|
||||||
|
### 核心场景
|
||||||
|
|
||||||
|
1. 员工接受住宿职级标准重算。服务端锁定明细原金额、城市、天数、职级、政策版本和可报销上限,同事务生成唯一节省机会。
|
||||||
|
2. 机会进入执行后仍只展示预计金额;审批未通过、单据取消或超过期限时保留失败/到期事实,不从兑现率分母中消失。
|
||||||
|
3. 单据完成付款业务事件后,系统根据冻结差额记录实际结果,但不进入财务确认 KPI。
|
||||||
|
4. 与机会负责人和结果填报人不同的财务人员检查证据、去重、币种和成本后确认;确认后才计入 CFO 现金节省。
|
||||||
|
5. 后续发生例外补付或申诉时,追加负向冲回并保留原确认,历史月报按报告 `as_of` 可回放。
|
||||||
|
6. CFO 从价值总览下钻到部门、项目、费用类型、城市、负责人和具体单据,查看基线、建议、执行、实际、确认人和证据。
|
||||||
|
7. 费用治理负责人查看异常集中维度和只读政策模拟准备项;历史中位数只能作为异常信号,缺少正式政策反事实时不显示预计节省,也不自动创建机会。
|
||||||
|
|
||||||
|
### 异常场景
|
||||||
|
|
||||||
|
- 服务端政策无法计算、明细金额缺失或差额不为正:原报销流程可继续,但不创建可货币化节省机会。
|
||||||
|
- Claim 没有合法 `ExpenseCaseLink` 或租户不一致:fail-closed,不自动归入默认租户。
|
||||||
|
- 相同请求重复发送:返回首次不可变响应;相同请求 ID 内容不同:409 拒绝。
|
||||||
|
- 陈旧版本、重复付款事件或重复经济收益:通过版本锁、事件唯一键和收益去重键拒绝。
|
||||||
|
- 缺少付款/凭证、汇率、独立确认或证据不完整:停留在实际待确认,不进入主 KPI。
|
||||||
|
- 看板接口失败、无权限、无数据、基线不足或快照过期:分别展示明确状态,绝不使用模拟数字伪装真实指标。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- [C1] 机会发现:从服务端核验的政策调整、后续分析洞察或风险复核创建机会。
|
||||||
|
- [C2] 状态管理:支持 identified、accepted、in_progress、realized、verified、rejected、expired 和 reversed 事实。
|
||||||
|
- [C3] 实现记录:保存实际毛收益、新增执行成本、净收益、发生时间、币种和结果证据。
|
||||||
|
- [C4] 财务确认:独立确认人复核去重、证据、汇率、成本和归因后签字。
|
||||||
|
- [C5] 证据与审计:只追加事件、内容指纹、before/after、首次响应和 correlation 全链路回放。
|
||||||
|
- [C6] 基线快照:按员工、部门、费用类型、供应商、城市、项目和流程持久化窗口、样本量和版本。
|
||||||
|
- [C7] 价值分析:经营漏斗、兑现率、周期、逾期、来源、责任人和数据质量。
|
||||||
|
- [C8] CFO 看板:真实指标、全局筛选、URL 恢复、下钻、移动端和口径抽屉。
|
||||||
|
- [C9] 安全边界:租户、角色、数据范围、自证禁止、管理员业务权限分离和字段白名单。
|
||||||
|
- [C10] 冲回能力:补付、退款、申诉或归因修正只能追加负向记录,不改历史。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- 在现有“分析看板”增加 `value` / “经营价值看板”,复用统一时间筛选,不新增一级导航。
|
||||||
|
- `OverviewView.vue` 只负责挂载独立 `CfoValueDashboard.vue`;价值加载、筛选和展示模型拆到 `useCfoValueDashboard.js`、`cfoValueDashboardModel.js` 与 `analyticsValue.js`,避免继续扩大接近 800 行的 `useOverviewView.js`。
|
||||||
|
- 默认视图从上到下为:主 KPI 与护栏、价值漏斗、现金节省趋势、来源/组织驱动、机会执行表、数据质量和口径说明。
|
||||||
|
- 全局筛选只保留时间、部门、费用类型和价值类型;项目、供应商、城市、负责人、状态和置信度进入高级筛选。
|
||||||
|
- 看板状态同步到 URL query;当前机会使用 `value_opportunity` 保存,刷新、浏览器前进/后退和分享链接能够恢复同一抽屉。非法 ID、403/404、跨租户不可见或不再符合当前筛选/时间窗口的机会会 fail-closed 清理,避免残留上一租户详情。
|
||||||
|
- 机会详情展示基线、建议、执行、实际结果、财务确认、去重与证据时间线;证据只使用服务端可见性 DTO。来源动作由独立 helper 根据 Claim、Expense Case、AI Decision、维度和 Evidence Resource 构造,不在抽屉组件内拼接路由规则。
|
||||||
|
- 单据来源进入 `app-document-detail`;风险来源优先进入关联单据,并只携带风险 focus、观察/决策 ID 与现有锚点。详情返回动作恢复 `dashboard=value`、时间窗口和 `value_*` 查询。
|
||||||
|
- 预算来源进入 `app-budget` 的“预算配置视图”,按授权范围应用部门和费用类型焦点;页面明确说明配置、阈值及当前演示金额不是该机会的真实预算事实。未配置的费用科目显示“未找到配置”,不得解释为预算为零。
|
||||||
|
- 部门、项目、费用类型、供应商、城市、负责人和来源维度可返回 CFO 看板相应筛选;切换维度时移除旧机会 ID,避免筛选与抽屉详情不一致。
|
||||||
|
- 实际结果登记必须具备真实付款事件或可追溯外部凭证;外部凭证上传/连接器尚未接入时,前端隐藏无证据手工登记并解释下一步,不发送必然失败或可能污染价值账本的空证据请求。
|
||||||
|
- 真实为 0、无数据、基线不足、无权限、接口失败、部分数据和快照过期使用不同状态。
|
||||||
|
- 禁止复用 `data/metrics.js`、`BudgetCenterView` 静态种子或遗留 `demoTotals` 作为 CFO 真实回退。
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- `SavingsDiscoveryService` 只负责从可信业务事实发现/创建机会,不提交事务。
|
||||||
|
- `SavingsActionService` 负责机会状态动作、版本、权限、幂等和事件。
|
||||||
|
- `SavingsRealizationService` 负责付款后实际结果、财务确认、拒绝和冲回。
|
||||||
|
- `SavingsQueryService` 负责租户安全分页、详情和可见动作投影。
|
||||||
|
- `SavingsFactScopeReader` 只读取当前租户与授权部门中的已归档报销事实,并以报告窗口和 `as_of` 排除未来单据、修改和完成事件。
|
||||||
|
- `SavingsBaselineGenerationService` 分开冻结金额中位数与流程历时中位数;流程只使用提交时间和首个付款完成业务事件,指标固定为 elapsed minutes。
|
||||||
|
- `SavingsAnomalyAttributionAnalyzer` 只生成描述性异常集中归因和政策模拟准备项,不声称因果,不写 `SavingsOpportunity`。
|
||||||
|
- `CfoValueAnalyticsService` 只从 Savings Ledger、风险事实和明确资格快照聚合,不从 UI mock 或风险金额推导节省。
|
||||||
|
- 标准重算只使用数据库行锁中的 `ExpenseClaimItem.item_amount` 作为原金额;客户端原金额和可报销金额只可作为展示输入,不能成为节省事实。
|
||||||
|
- 标准重算在同一事务内写 Claim 调整、机会、证据、Savings 事件和 `saving_opportunity_created` 业务事件;API 边界统一提交。
|
||||||
|
- 付款动作在 Claim → Opportunity 的固定锁顺序中创建 actual realization 和 `saving_action_completed`,与 `payment_completed` 同事务。
|
||||||
|
- 财务确认写 `saving_confirmed`,拒绝和冲回写对应只追加事件;相同请求安全重放。
|
||||||
|
- 旧 `/analytics/finance-dashboard` 必须接收可信 `CurrentUserContext`,Claim 通过 `ExpenseCaseLink` 限定租户;快照键至少包含租户与数据权限范围,后台任务必须显式指定租户。
|
||||||
|
|
||||||
|
### 算法与规则
|
||||||
|
|
||||||
|
#### 第一条可信机会
|
||||||
|
|
||||||
|
```text
|
||||||
|
server_original_amount = locked ExpenseClaimItem.item_amount
|
||||||
|
policy_target_amount = server policy calculator result
|
||||||
|
estimated_net_saving = max(0, server_original_amount - policy_target_amount)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 仅当政策计算成功、输入快照完整、币种一致、差额大于 0 时创建可货币化机会。
|
||||||
|
- 机会唯一键首期为 `tenant + claim + item + policy_version + policy_input_fingerprint`。
|
||||||
|
- 接受重算表示建议已采纳,机会进入 `in_progress`;付款完成后进入 `realized`,独立财务确认后进入 `verified`。
|
||||||
|
- 员工自行承担差额同时是员工体验护栏,必须跟踪申诉和例外补付率,防止通过不合理转嫁美化节省。
|
||||||
|
|
||||||
|
#### 流程基线、异常归因与政策模拟
|
||||||
|
|
||||||
|
```text
|
||||||
|
workflow_elapsed_minutes
|
||||||
|
= first_tenant_payment_completed_event.occurred_at - claim.submitted_at
|
||||||
|
```
|
||||||
|
|
||||||
|
- 流程窗口按首个付款完成事件归属;提交时间缺失、完成早于提交、跨租户事件、`as_of` 之后完成或截止后被修改的单据全部排除。
|
||||||
|
- 流程快照使用 `median_submission_to_payment_elapsed_minutes`、`minutes` 单位、独立算法版本和来源指纹;证据元数据固定声明 `elapsed_cycle_not_active_labor`。
|
||||||
|
- 异常归因按部门、费用类型、城市和项目聚合质量合格的历史偏离候选,只表示异常集中度,不表示该维度导致支出。
|
||||||
|
- 政策模拟候选只输出版本化政策、生效期、适用范围、限额和例外规则等必需输入;历史中位数不是政策反事实,缺少正式反事实时 `estimated_savings=None`。
|
||||||
|
- 预算预测复用现有预算分配和核销事实,以 `min(as_of, window_end)` 为截止点;旧预算表没有租户字段时仅允许 default 租户,部门权限优先按稳定部门 ID 收紧。
|
||||||
|
- 供应商缺少核验 ID、数量和单位价格时继续返回 unavailable,不读取 `AccountsPayableRecord` 演示或应付种子。
|
||||||
|
|
||||||
|
#### 收益去重
|
||||||
|
|
||||||
|
- `benefit_key` 表达同一个经济结果,不表达同一个风险观察。
|
||||||
|
- 多条风险可指向一个机会;同一发票、付款义务、报销明细或价格变化只能有一个 canonical 确认收益。
|
||||||
|
- 同一收益多个动作的归因比例之和不得超过 1。
|
||||||
|
- 确认后大额、超预计、手工基线、缺外部凭证和归因异常进入二次复核或数据质量队列。
|
||||||
|
|
||||||
|
#### 状态转换
|
||||||
|
|
||||||
|
```text
|
||||||
|
identified -> accepted -> in_progress -> realized -> verified -> reversed
|
||||||
|
| | | |
|
||||||
|
+-------- rejected -------+------------+
|
||||||
|
+-------- expired --------+
|
||||||
|
```
|
||||||
|
|
||||||
|
- `identified`:冻结基线、方法、价值类型、币种、预计净值、负责人、截止时间、去重键和来源证据。
|
||||||
|
- `accepted`:负责人明确采纳。
|
||||||
|
- `in_progress`:保存执行动作、执行人、开始时间和动作证据;预计值不得静默上调。
|
||||||
|
- `realized`:保存实际结果、净值、发生时间、归因和付款/结果证据,但不计主 KPI。
|
||||||
|
- `verified`:完成去重、币种、成本、证据和独立财务确认。
|
||||||
|
- `reversed`:追加负向冲回,原确认不可删除。
|
||||||
|
- `rejected/expired`:保留失败事实,防止只保留成功机会美化兑现率。
|
||||||
|
|
||||||
|
### 数据与契约
|
||||||
|
|
||||||
|
#### `profile_baseline_snapshots`
|
||||||
|
|
||||||
|
- 租户、基线类型、稳定维度 ID、指标、单位和原币。
|
||||||
|
- 基线值、窗口开始/结束、样本量、方法、查询指纹和数据质量。
|
||||||
|
- 算法版本、政策版本、冻结时间/人和有效期。
|
||||||
|
- 历史群组基线强制窗口与样本量;政策反事实基线强制政策版本、生效区间和目标明细。
|
||||||
|
- 金额基线按员工、部门、费用类型、城市和项目分组;流程基线按稳定流程键分组,使用独立 metric/unit,不能与币种金额比较或求和。
|
||||||
|
|
||||||
|
#### `savings_opportunities`
|
||||||
|
|
||||||
|
- 租户、费用事件、Claim 软引用、来源类型/ID、类别和价值类型。
|
||||||
|
- 风险暴露只作护栏;基线、目标、预计毛收益、预计成本、预计净收益和区间分开保存。
|
||||||
|
- 原币、报告币、负责人、截止时间、状态、版本、`benefit_key` 和去重组。
|
||||||
|
- 部门、项目、费用类型、供应商、城市和流程维度使用明确快照字段或受控 JSON。
|
||||||
|
- 唯一约束至少覆盖 `(tenant_id, opportunity_key)`。
|
||||||
|
|
||||||
|
#### `savings_realizations`
|
||||||
|
|
||||||
|
- 租户、机会、费用事件、Claim、BusinessEvent 和实际发生时间。
|
||||||
|
- 实际毛收益、新增执行成本、实际净收益、原币、报告金额和汇率快照。
|
||||||
|
- 归因方法/比例、`benefit_key`、去重状态和 canonical realization。
|
||||||
|
- 财务确认/拒绝/冲回人、时间、说明和证据。
|
||||||
|
- 只追加金额事实;确认投影可更新,但每次变更必须有不可变事件。
|
||||||
|
|
||||||
|
#### `savings_evidence_links` 与 `savings_events`
|
||||||
|
|
||||||
|
- 证据保存实体、证据角色、资源类型/ID、来源系统、外部事件 ID、内容哈希、发生/采集时间和验证状态。
|
||||||
|
- 事件保存动作、请求 ID、操作人、版本、指纹、before/after、首次响应、correlation 和时间。
|
||||||
|
- PostgreSQL 触发器禁止修改或删除 `savings_events`。
|
||||||
|
|
||||||
|
### 权限
|
||||||
|
|
||||||
|
- `finance`、`executive` 可读取租户 CFO 汇总;预算负责人仅看被授权部门/成本中心。
|
||||||
|
- 普通员工、普通经理不得读取 CFO 汇总;只能看到本人费用事件中的最小调整说明。
|
||||||
|
- 机会接受、拒绝和指派需要 finance/executive 或明确负责人权限。
|
||||||
|
- 财务确认必须是 finance/executive,且不能是机会负责人或实际结果填报人。
|
||||||
|
- 只有 `admin` 而没有财务角色时允许运维只读,不允许业务确认。
|
||||||
|
- 所有 API、聚合、快照、导出和后台任务强制 `tenant_id` 与数据范围;不允许默认全表扫描。
|
||||||
|
- Claim 通过 `ExpenseCaseLink` 校验租户;缺 Link 的非默认历史数据不自动猜测归属。
|
||||||
|
|
||||||
|
### 降级策略
|
||||||
|
|
||||||
|
- 政策或基线服务失败:不创建货币化机会,原报销主流程保留人工处理。
|
||||||
|
- 外部付款/ERP 连接器未接入:付款业务事件只能推进到 realized,必须人工财务确认。
|
||||||
|
- 汇率缺失:保留原币明细,不进入跨币种总计。
|
||||||
|
- 工时基线缺失:显示“待采集”,不显示 0,不计扩展 ROI。
|
||||||
|
- 流程历时可用但活跃工时缺失:只展示 elapsed cycle 驱动指标,CFO 工时价值仍保持 collecting。
|
||||||
|
- CFO 聚合失败:显示错误和重试,不加载演示值;旧财务支出看板独立可用。
|
||||||
|
- 快照过期:展示过期提示并触发受控刷新,不能跨租户复用旧快照。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
### 主 KPI 1:财务确认净现金节省
|
||||||
|
|
||||||
|
```text
|
||||||
|
verified_net_cash_savings
|
||||||
|
= sum(actual_gross_saving - incremental_execution_cost + reversal_amount)
|
||||||
|
where value_kind = cash
|
||||||
|
and confirmation_status = finance_confirmed
|
||||||
|
and dedupe_status = canonical
|
||||||
|
and confirmed_at <= report_as_of
|
||||||
|
```
|
||||||
|
|
||||||
|
- 按 `realized_at` 归属业务期间,按 `confirmed_at` 和报告 `as_of` 保证历史可回放。
|
||||||
|
- 风险暴露、预计金额、执行中金额和未确认实际金额不得进入。
|
||||||
|
- 多币种只有存在锁定汇率时才折算;否则按原币分组。
|
||||||
|
|
||||||
|
### 主 KPI 2:财务确认可释放工时价值
|
||||||
|
|
||||||
|
```text
|
||||||
|
verified_releasable_labor_value
|
||||||
|
= max(0, baseline_active_minutes_per_unit - actual_active_minutes_per_unit)
|
||||||
|
* eligible_units
|
||||||
|
* approved_role_cost_per_minute
|
||||||
|
* approved_releasable_ratio
|
||||||
|
```
|
||||||
|
|
||||||
|
- 现金与工时分账、分卡、分报告,默认不相加。
|
||||||
|
- 缺少上线前后活跃分钟、角色完全成本、生效期或客户认可释放比例时不可计算。
|
||||||
|
|
||||||
|
### 主 KPI 3:安全智能直通率
|
||||||
|
|
||||||
|
```text
|
||||||
|
safe_straight_through_rate
|
||||||
|
= qualified_completed_cases_without_manual_correction_or_return
|
||||||
|
and no_major_post_audit_issue
|
||||||
|
/ eligible_completed_cases_frozen_at_creation
|
||||||
|
```
|
||||||
|
|
||||||
|
- 必须保存 eligibility 快照、策略版本、必要审批完成和事后抽检结果。
|
||||||
|
|
||||||
|
### 驱动指标
|
||||||
|
|
||||||
|
- 现金兑现率:同一成熟机会队列的财务确认净现金 / 冻结预计净现金。
|
||||||
|
- 机会到财务确认 P50 天数、逾期负责人占比。
|
||||||
|
- 提交至首个付款完成的端到端 P50 elapsed minutes;它与每单人工活跃分钟分开,后者在采集前保持不可用。
|
||||||
|
- 人工触点、首次提交完整率和 AI 字段采纳率。
|
||||||
|
|
||||||
|
### 风险护栏
|
||||||
|
|
||||||
|
- 开放且已确认的高危/重大风险暴露,按单据或经济义务去重;它不是节省。
|
||||||
|
- 重大风险漏检率、事后审计重大问题率、误报率和人工覆盖率。
|
||||||
|
- 财务确认后冲回率、实际超过预计异常率、去重待复核金额和证据不完整金额。
|
||||||
|
- 标准重算员工申诉/例外补付率。
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- 状态机合法/非法转换、确认人独立性、管理员只读和角色权限。
|
||||||
|
- 机会/实现/确认/冲回幂等、请求内容冲突、陈旧版本和租户隔离。
|
||||||
|
- 服务端明细金额锁定、客户端放大原金额无效、政策快照完整性。
|
||||||
|
- 付款事件重复、经济收益去重、跨币种、成本扣除、冲回和归因上限。
|
||||||
|
- 看板按租户、时间、部门、项目、费用类型、城市、来源和负责人聚合对账。
|
||||||
|
- 六维基线验证员工、部门、费用类型、城市、项目和流程的窗口、样本量、算法版本、来源指纹、租户/部门范围与稳定重放。
|
||||||
|
- 洞察验证预算截止点、描述性归因、政策模拟准备项、供应商 unavailable、所有无反事实候选 `estimated_savings=None` 且不会创建机会。
|
||||||
|
- 旧财务看板 Claim、预算和缓存键租户隔离回归。
|
||||||
|
- Alembic 空库升级、重复升级、约束、append-only 触发器、无损降级和 PostgreSQL 并发。
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- API snake/camel 归一化、部分数据和过期快照。
|
||||||
|
- verified、realized、estimated 和 risk exposure 严格分区,不得混算。
|
||||||
|
- loading/error/empty/partial/stale/permission-denied/baseline-missing 状态。
|
||||||
|
- 时间、部门、费用类型、价值类型筛选与 URL 恢复。
|
||||||
|
- 机会详情证据链、可用动作、版本冲突、幂等重放和确认反馈。
|
||||||
|
- 单据、风险、预算和维度下钻参数。
|
||||||
|
- 机会 `value_opportunity` 的恢复、关闭清理、非法格式、403/404、跨筛选和时间窗口清理。
|
||||||
|
- 风险来源最小定位参数、单据返回 CFO、预算配置焦点和“非真实预算金额”口径。
|
||||||
|
- 响应式、键盘操作、44px 触控目标和生产构建。
|
||||||
|
|
||||||
|
### 集成
|
||||||
|
|
||||||
|
- 住宿超标准 → 服务端重算 → 机会 → 审批 → 付款 → actual realization → 独立财务确认 → CFO 看板。
|
||||||
|
- 相同重算/付款/确认并发只产生一个经济收益和一条对应版本事件。
|
||||||
|
- 确认后补付/申诉 → 负向冲回 → 历史报告 `as_of` 可回放。
|
||||||
|
- 多租户同单号、同员工名、相同 request ID 和快照缓存隔离。
|
||||||
|
|
||||||
|
### 容器验证
|
||||||
|
|
||||||
|
所有 pytest、Alembic、PostgreSQL 并发、前端测试和构建必须在 `local-x-financial-linux` 容器内完成,单条命令超时不超过 60 秒。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- [A1] 任一 verified cash saving 可追溯到费用事件、明细原金额、政策快照、执行动作、付款事件、确认人、去重键和证据。
|
||||||
|
- [A2] 风险暴露、预计、执行中、实际待确认、财务确认和冲回在 API、数据库和 UI 中均不混算。
|
||||||
|
- [A3] 客户端伪造原金额、跨租户访问、陈旧版本、自证确认和重复经济收益被服务端拒绝。
|
||||||
|
- [A4] CFO 三个主 KPI 有口径、时间窗口、来源、新鲜度、数据覆盖和护栏;缺数据时明确“待采集”。
|
||||||
|
- [A4.1] 流程 elapsed cycle 有独立 metric/unit/算法版本/来源指纹,且不会进入工时价值或现金节省。
|
||||||
|
- [A5] 价值看板支持部门、项目、费用类型、供应商、城市、时间、负责人、来源和单据下钻。
|
||||||
|
- [A6] 真实接口失败不出现演示数字;零值、无数据、无权限、错误和过期可区分。
|
||||||
|
- [A7] 迁移在一次性 PostgreSQL 空库完成升级、重复升级、约束验证和安全降级边界测试。
|
||||||
|
- [A8] 相关后端、前端、Ruff、构建、端到端和并发测试全部在容器内通过。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
### 风险
|
||||||
|
|
||||||
|
- 员工承担差额不一定等同企业创造价值;需要跟踪申诉、补付和政策公平性,避免激励扭曲。
|
||||||
|
- 当前付款是人工状态,不是外部现金事实;确认页必须清晰披露证据等级。
|
||||||
|
- 旧 Claim/预算缺租户列,读取必须经过 Case Link 或正式迁移,不能依赖默认租户猜测。
|
||||||
|
- 数据稀疏且包含模拟种子,试点目标值必须在真实基线采集后冻结。
|
||||||
|
- 当前预算中心仍是配置演示视图;CFO 来源入口只用于定位部门/费用类型配置,页面和价值计算均不得把其中金额当成预算事实或节省事实。
|
||||||
|
- 多币种、分摊收益和跨期冲回会显著增加财务口径复杂度,必须保留原始事实和版本。
|
||||||
|
- 工时价值若没有活跃时间采集与客户认可成本率,容易被夸大,因此默认不计现金 ROI。
|
||||||
|
- 历史异常集中不是因果,历史中位数也不是政策反事实;模拟候选必须在正式政策版本和客户确认适用口径补齐后才可货币化。
|
||||||
|
|
||||||
|
### 已处理决策
|
||||||
|
|
||||||
|
- 首条闭环选择“住宿职级标准重算”,不选择缺少付款事实的重复支付阻止。
|
||||||
|
- 价值看板进入现有分析看板,独立拆组件和 composable。
|
||||||
|
- 只设 3 个主 KPI,其余作为驱动和护栏;现金与工时分开披露。
|
||||||
|
- Savings Ledger 直接带结构化租户键,不复用旧财务快照作为价值事实源。
|
||||||
|
- 流程基线采用提交至首个付款完成的端到端 elapsed cycle;人工活跃工时继续作为独立待采集事实。
|
||||||
|
- 异常归因仅做描述性集中度,政策模拟候选保持只读,不自动写入节省机会。
|
||||||
|
|
||||||
|
### 待后续真实客户确认
|
||||||
|
|
||||||
|
- 独立财务确认是否要求双人复核及大额阈值。
|
||||||
|
- 报告币、汇率来源和月末汇率锁定规则。
|
||||||
|
- 工时价值是否进入扩展 ROI、角色成本口径和可释放比例。
|
||||||
|
- 标准重算差额在客户会计政策中属于现金节省、成本避免还是员工自担调整。
|
||||||
|
- 试点 30/90 天目标、CFO 月报签字人和节省分成合同边界。
|
||||||
|
|
||||||
|
## 本轮实现记录
|
||||||
|
|
||||||
|
- 2026-07-16:完成现有财务、预算、风险、付款、标准重算、前端入口与 KPI 口径盘点;确认旧财务聚合租户边界和客户端原金额信任问题。
|
||||||
|
- 2026-07-16:冻结 Savings Ledger、CFO KPI、状态机、权限、去重、证据和首条纵向闭环方案;尚未完成的代码与验证全部保留在同目录 TODO 中继续执行。
|
||||||
|
- 2026-07-16:完成 Savings Ledger 五表与 0015 迁移、租户/权限/幂等/并发/证据/冲回契约,并把住宿标准重算和付款动作接入同事务价值链。
|
||||||
|
- 2026-07-16:完成独立财务确认、证据复核留痕、canonical 收益去重、负向冲回、`as_of` 回放和历史标准调整安全回填。
|
||||||
|
- 2026-07-16:完成 CFO 价值分析 API;只将已确认 canonical 现金结果计入主 KPI,多币种分组,工时、安全直通率和审计事实缺口显式披露,不使用演示数据回退。
|
||||||
|
- 2026-07-16:完成 CFO 经营价值前端入口、URL 筛选恢复、主 KPI、漏斗、趋势、驱动维度、护栏、数据质量、机会证据链与响应式状态;详情明确展示财务确认人、时间和说明。
|
||||||
|
- 2026-07-16:收紧无证据手工登记边界。真实付款事件仍可自动形成待确认实际结果;在支付/银行/ERP 凭证连接器接入前,页面不再提交空证据,并向用户解释必须先完成付款或等待外部回执。
|
||||||
|
- 2026-07-16:完成租户隔离的员工、部门、费用类型、城市和项目五维历史中位数冻结基线,以及预算预测偏差、重复小额模式和历史中位数偏离候选;非政策反事实信号只披露暴露,不生成节省金额,并补齐 `as_of` 基线时间一致性门禁。
|
||||||
|
- 2026-07-16:补齐第六维流程周期基线,以同租户首个付款完成业务事件冻结提交到完成的 elapsed minutes;窗口、样本量、算法版本、来源指纹、质量状态和证据完整保存,明确禁止推算人工活跃工时。
|
||||||
|
- 2026-07-16:新增部门/费用类型/城市/项目异常集中归因和版本化政策模拟准备项;复用预算预测并收紧配置/交易截止点,所有缺少反事实的候选保持 `estimated_savings=None`、零机会副作用,供应商分析继续明确 unavailable。
|
||||||
|
- 2026-07-16:完成机会抽屉 `value_opportunity` 深链、刷新/前进/后退恢复和非法/越权/跨筛选安全清理;来源动作可跳关联单据、风险证据位置、预算配置视图及 CFO 维度筛选,单据返回时恢复经营价值查询状态。
|
||||||
|
- 2026-07-16:预算来源只应用授权部门与费用类型配置焦点;未覆盖科目显示“未找到配置”,所有入口和页面均明确演示预算金额不是当前机会的真实预算事实。
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# 节省事实账本与 CFO 经营价值看板 开发 TODO
|
||||||
|
|
||||||
|
更新时间:2026-07-17
|
||||||
|
|
||||||
|
## 使用规则
|
||||||
|
|
||||||
|
- 每条 TODO 必须回链 `CONCEPT.md` 的章节或语义段落。
|
||||||
|
- 只有代码、接口或容器验证提供真实证据后才能勾选 `[x]`。
|
||||||
|
- 现金节省、工时价值、风险暴露和预计机会始终分开,不以演示数据代替缺失事实。
|
||||||
|
- 实施顺序为:安全前置 → 账本 → 首条闭环 → 基线/分析 → CFO 前端 → PostgreSQL/E2E → 文档收口。
|
||||||
|
|
||||||
|
## 1. 调研与边界
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 背景与问题] 盘点财务看板、预算、Claim、Expense Case、Business Event、风险、审批、付款和标准重算事实源。
|
||||||
|
证据:`finance_dashboard.py`、`finance_dashboard_snapshot.py`、`financial_record.py`、`expense_cases.py`、`expense_claim_standard_adjustment.py`、`expense_claim_approval_flow.py` 的只读审计。
|
||||||
|
- [x] [CONCEPT: 目标与非目标] 确认风险金额、未用预算、预计机会和未确认结果不得进入已确认节省。
|
||||||
|
证据:CONCEPT「目标与非目标」「算法与公式」。
|
||||||
|
- [x] [CONCEPT: 用户与场景] 选择住宿职级标准重算作为首条纵向闭环,不选择证据不足的重复支付或供应商议价。
|
||||||
|
证据:现有标准重算和付款业务事件可形成最短可信证据链;`AccountsPayableRecord` 已有租户字段,但仍缺合同价、数量、单位价格和外部付款事实。
|
||||||
|
- [x] [CONCEPT: 风险与开放问题] 识别旧财务聚合全表读取、快照键缺租户、接口缺角色限制和标准重算信任客户端原金额问题。
|
||||||
|
证据:`FinanceDashboardMetricMixin._fetch_claims()`、`_fetch_budget_allocations()`、`FinanceDashboardSnapshotService._cache_key()`、`accept_standard_adjustment()`。
|
||||||
|
|
||||||
|
## 2. 契约与设计
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 数据与契约] 定义 baseline、opportunity、realization、evidence 和 append-only event 的职责与关键字段。
|
||||||
|
证据:CONCEPT「数据与契约」。
|
||||||
|
- [x] [CONCEPT: 算法与规则] 定义 identified → accepted → in_progress → realized → verified → reversed 状态和 rejected/expired 终态。
|
||||||
|
证据:CONCEPT「状态转换」。
|
||||||
|
- [x] [CONCEPT: 算法与规则] 定义 benefit key、canonical realization、归因比例和追加冲回规则。
|
||||||
|
证据:CONCEPT「收益去重」。
|
||||||
|
- [x] [CONCEPT: 权限] 定义租户、finance/executive、预算范围、管理员只读、自证禁止和数据范围。
|
||||||
|
证据:CONCEPT「权限」。
|
||||||
|
- [x] [CONCEPT: 算法与公式] 定义 3 个主 KPI、驱动指标、风险护栏和缺数据边界。
|
||||||
|
证据:CONCEPT「算法与公式」。
|
||||||
|
|
||||||
|
## 3. 安全前置
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 后端] 让旧财务看板显式接收可信租户与数据范围,Claim/预算查询不得全表混算。
|
||||||
|
证据:`finance_dashboard_access_policy.py`、`finance_dashboard_scope.py`、`finance_dashboard_budget.py`、`finance_dashboard.py`;Claim 直接按结构化 `ExpenseClaim.tenant_id` 首层过滤,预算历史兼容仅限 default 租户。
|
||||||
|
- [x] [CONCEPT: 后端] 给财务快照缓存键和后台任务加入租户/数据范围,禁止跨租户复用。
|
||||||
|
证据:`finance_dashboard_snapshot.py`、`finance_dashboard_scheduler.py`;快照键包含 tenant 与 scope fingerprint,调度入口必须显式携带租户。
|
||||||
|
- [x] [CONCEPT: 权限] 给财务与 CFO 分析接口增加后端角色和范围校验,管理员身份不自动获得业务确认权。
|
||||||
|
证据:`finance_dashboard_access_policy.py`、`savings_access_policy.py`、`agent_run_access_policy.py`、`GET /api/v1/analytics/cfo-value`;容器租户/缓存/角色回归 17 项通过。
|
||||||
|
- [x] [CONCEPT: 第一条可信机会] 标准重算只使用锁定数据库明细原金额,客户端原金额不能影响结果和节省。
|
||||||
|
证据:`expense_claim_standard_adjustment.py`;原金额只读锁定 `ExpenseClaimItem.item_amount`,政策结果只由服务端重算,陈旧版本使用内容指纹并降低基线质量等级。
|
||||||
|
- [x] [CONCEPT: 后端] 将标准重算、审计和账本写入收口到同一事务边界。
|
||||||
|
证据:`expense_claim_standard_adjustment.py`以 `commit=False` 写审计,同一 API 事务写 Claim、Baseline、Opportunity、Evidence、SavingsEvent 和 BusinessEvent;事务失败整体回滚。
|
||||||
|
|
||||||
|
## 4. Savings Ledger 后端
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 数据与契约] 新增 `profile_baseline_snapshots`、`savings_opportunities`、`savings_realizations`、`savings_evidence_links` 和 `savings_events` 模型。
|
||||||
|
证据:`savings.py`(模型)及 `test_savings_models.py`;五类事实分离保存基线、机会、结果、证据和不可变操作。
|
||||||
|
- [x] [CONCEPT: 数据与契约] 新增 Alembic 0015、迁移所有权、复合租户外键、唯一键、检查约束、索引和 append-only 触发器。
|
||||||
|
证据:`20260716_0015_savings_value_ledger.py`、`migration_preflight.py`、`schema_ownership.py`;一次性 PostgreSQL 17 空库完整迁移循环通过。
|
||||||
|
- [x] [CONCEPT: 后端] 实现 discovery、action、realization、query、access policy 和 response builder 独立服务。
|
||||||
|
证据:`savings_discovery.py`、`savings_actions.py`、`savings_realization.py`、`savings_query.py`、`savings_access_policy.py`、`savings_read_projection.py`、`savings_protocol.py`。
|
||||||
|
- [x] [CONCEPT: 后端] 实现分页、筛选、排序、详情、可用动作、证据和事件 DTO。
|
||||||
|
证据:`GET /api/v1/savings/opportunities`、`GET /api/v1/savings/opportunities/{id}`、`savings.py`(schema/API)、`test_savings_endpoints.py`。
|
||||||
|
- [x] [CONCEPT: 算法与规则] 实现版本锁、请求指纹、不可变响应重放、收益去重和归因比例约束。
|
||||||
|
证据:`SavingsRequestProtocol`、数据库唯一/检查约束与 `test_savings_concurrency_postgres.py`;同 request 并发仅一次写入,同 benefit 双确认仅一个 canonical winner。
|
||||||
|
- [x] [CONCEPT: 状态转换] 实现 accept/start/record/verify/reject/expire/reverse 合法与非法转换。
|
||||||
|
证据:`savings_actions.py`、`savings_realization.py`、`test_savings_ledger_services.py`;负向 reversal 只追加,不改写原已确认金额。
|
||||||
|
- [x] [CONCEPT: 证据与审计] 把 Savings 关键动作写入同 Case 的 `BusinessEvent` 并保留 correlation/causation。
|
||||||
|
证据:`ExpenseCaseService` 资源链接与 `savings_*` 服务;端到端测试核验 opportunity/payment/action/confirm/reverse 事件同 Case 可回放。
|
||||||
|
- [x] [CONCEPT: 后端] 新增历史标准重算机会 dry-run/apply 回填脚本;缺 Case/租户/政策证据只输出数据质量报告。
|
||||||
|
证据:`savings_standard_adjustment_backfill.py`、`backfill_standard_adjustment_savings.py`;显式租户/目标库、指纹重算、批次锁、稳定键重放,容器直接测试 8 项通过。
|
||||||
|
|
||||||
|
## 5. 首条真实纵向闭环
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 第一条可信机会] 接受住宿标准重算时冻结服务端原金额、政策输入/版本、目标金额、差额、维度和证据。
|
||||||
|
证据:`SavingsDiscoveryService`冻结 `ProfileBaselineSnapshot` 与内容指纹,政式发布版本为 complete,内容指纹版本显式标记 partial。
|
||||||
|
- [x] [CONCEPT: 第一条可信机会] 同事务以稳定 opportunity key 创建或重放机会,并进入 in_progress。
|
||||||
|
证据:稳定键由 tenant + claim + item + policy version + calculation fingerprint 生成;重试返回原机会。
|
||||||
|
- [x] [CONCEPT: 后端] 付款业务事件后同事务创建 actual realization,重复付款事件不重复计入。
|
||||||
|
证据:`expense_claim_approval_flow.py`调用 `realize_paid_claim()`;PostgreSQL 同付款事件并发结果为 `[0, 1]`,仅一条 realization 和一条完成事件。
|
||||||
|
- [x] [CONCEPT: 财务确认] 实现独立财务 verify/reject,未确认实际结果不得进入主 KPI。
|
||||||
|
证据:所有人工实际结果必须至少一条可追溯证据;独立确认人会固化证据复核人/时间,pending 结果在 CFO 口径中为 0。
|
||||||
|
- [x] [CONCEPT: 冲回能力] 实现补付/申诉/归因修正的负向 reversal,原确认不可删除。
|
||||||
|
证据:原 actual 保留 `finance_confirmed`,新增负向 canonical reversal;`as_of` 可回放冲回前结果。
|
||||||
|
- [x] [CONCEPT: 权限] 验证申请人、机会负责人、结果填报人、纯管理员和跨租户用户不能自证或越权。
|
||||||
|
证据:`SavingsAccessPolicy`与 PostgreSQL 并发安全测试;owner/recorder/admin-only 自证拒绝,独立 finance 可确认,跨租户无副作用。
|
||||||
|
|
||||||
|
## 6. 费用基线与经营分析
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 基线快照] 按员工、部门、费用类型、城市、项目和流程从租户安全真实数据生成基线窗口、样本量和版本。
|
||||||
|
证据:`savings_fact_scope.py`、`savings_baseline_generation.py`、`savings_insights.py`;金额五维使用已归档明细中位数,流程维度只使用同租户报销提交时间与首个 `payment_completed` 业务事件,冻结 elapsed minutes、窗口、样本量、算法版本、查询指纹、质量、证据和审计事件;`source_workflow_cycle_count` 明确披露覆盖,活跃工时保持 unavailable。
|
||||||
|
- [x] [CONCEPT: 基线快照] 供应商数据缺少租户/合同事实时显示 coverage gap,不从应付模拟种子生成可信基线。
|
||||||
|
证据:基线和洞察 API 均返回 `supplier_dimension_unavailable` / `supplier_price_drift_unavailable`,要求核验供应商、数量和单位价格;不会读取 `AccountsPayableRecord` 模拟事实或创建货币化机会。
|
||||||
|
- [x] [CONCEPT: 算法与规则] 实现预算预测、描述性异常归因、重复小额浪费、历史偏离和只读政策模拟准备项;证据不足时不自动货币化。
|
||||||
|
证据:`savings_insight_budget.py` 复用预算配置/核销事实并以 `min(as_of, window_end)` 截止;`savings_insight_analysis.py`、`savings_insight_attribution.py` 输出历史偏离、部门/费用类型/城市/项目异常集中和版本化政策模拟必需输入。所有缺少正式反事实的候选 `estimated_savings=None`、`created_opportunity_ids=[]`,稳定重放不写 `SavingsOpportunity`。
|
||||||
|
- [x] [CONCEPT: 后端] 实现 `GET /analytics/cfo-value`,统一时间、维度、币种、状态和 `as_of` 口径。
|
||||||
|
证据:`cfo_value.py`(API/schema)、`cfo_value_analytics.py`;租户、角色、预算范围、时间和维度过滤均在服务端执行。
|
||||||
|
- [x] [CONCEPT: 算法与公式] 实现确认现金、工时待采集、安全直通率资格、漏斗、兑现周期、逾期、来源和护栏聚合。
|
||||||
|
证据:确认现金只求和 finance_confirmed + canonical + cash;工时和安全直通率显式返回 collecting;漏斗、趋势、驱动、逾期、冲回和数据质量不与主 KPI 混算。
|
||||||
|
- [x] [CONCEPT: 降级策略] 多币种、工时、外部付款和审计结果缺失时返回明确数据质量状态,不伪造 0。
|
||||||
|
证据:多币种按原币分组;无人工活跃时间/审计事实时返回 collecting/unavailable 和 coverage gap,不使用 mock 回退。
|
||||||
|
|
||||||
|
## 7. CFO 前端
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 前端] 在分析看板新增 `value` 入口,并把 dashboard 状态同步 URL。
|
||||||
|
证据:`useTopBarOverviewRange.js`、`AppShellRouteView.vue`、`OverviewView.vue`;`dashboard=value` 与价值筛选/页码写入 query,刷新和返回可恢复。
|
||||||
|
- [x] [CONCEPT: 前端] 新增独立 CFO 组件、composable、API service 和展示模型,不继续扩大 `useOverviewView.js`。
|
||||||
|
证据:`CfoValueDashboard.vue`、`CfoValueTrendChart.vue`、`CfoValueOpportunityDrawer.vue`、`CfoValueActionDialog.vue`、`useCfoValueDashboard.js`、`analyticsValue.js`、`cfoValueDashboardModel.js`;业务组件和状态职责已拆分,核心文件均低于 800 行。
|
||||||
|
- [x] [CONCEPT: CFO 看板] 实现主 KPI、护栏、价值漏斗、趋势、来源/组织驱动、机会表和数据质量。
|
||||||
|
证据:`CfoValueDashboard.vue` 与 `cfo-value-dashboard.css`;现金、工时和直通率分卡,趋势按币种切换,预计/实际/确认/冲回不混算,缺数据显式展示。
|
||||||
|
- [x] [CONCEPT: 前端] 实现时间、部门、费用类型、价值类型筛选和项目/供应商/城市/负责人高级筛选。
|
||||||
|
证据:顶部时间窗口与价值 query 联动,`createEmptyValueFilters`、`readValueFiltersFromQuery`、`writeValueFiltersToQuery` 和 API query 白名单覆盖全部筛选字段。
|
||||||
|
- [x] [CONCEPT: 前端] 实现基线、建议、执行、实际、确认、去重和证据详情。
|
||||||
|
证据:`CfoValueOpportunityDrawer.vue` 展示冻结基线、机会状态、实际净值、记录人、canonical 去重、财务确认人/时间/说明、证据索引和不可变事件;无可追溯凭证时不开放手工实际结果登记。
|
||||||
|
- [x] [CONCEPT: 前端] 实现单据、风险、预算、维度下钻与返回状态恢复。
|
||||||
|
证据:`cfoValueSourceLinks.js` 统一构造来源路由;Claim 进入 `app-document-detail`,风险携带最小 focus/观察/决策参数和现有锚点,预算进入 `app-budget` 配置视图并应用部门/费用类型焦点,维度返回 `app-overview?dashboard=value` 相应筛选。`useCfoValueDashboard.js` 以 `value_opportunity` 恢复抽屉,并在非法 ID、403/404、跨租户不可见或不符合当前筛选/时间窗口时安全清除;`useAppShell.js` 从单据详情恢复 CFO 查询。限制:预算中心仍是演示配置视图,页面明确金额不是当前机会的真实预算事实;未覆盖费用科目显示未配置而不是零预算。
|
||||||
|
- [x] [CONCEPT: 降级策略] 区分零、无数据、基线不足、无权限、失败、部分数据和快照过期;删除 CFO 演示回退。
|
||||||
|
证据:`classifyCfoDashboardState`、`buildValueKpis` 与页面状态区;接口失败不读取 `data/metrics.js` 或 demo/fallback 数字。
|
||||||
|
- [x] [CONCEPT: 前端] 完成移动端、键盘、焦点、44px 触控和无障碍状态提示。
|
||||||
|
证据:CFO 样式移动断点、44px 按钮、语义化 `label`/`role=alert`/`aria-live`、抽屉关闭标签及趋势表格降级;生产构建通过。
|
||||||
|
|
||||||
|
## 8. 测试与验证
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 测试方案] 后端状态、金额、权限、租户、幂等、证据、去重、冲回和看板聚合单测通过。
|
||||||
|
证据:容器组合回归 84 项通过;本轮 Savings/CFO 基线、洞察、端点、账本、回填与 E2E 组合 `34 passed, 6 skipped`,6 项为未配置 PostgreSQL 专用 URL 的预期跳过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 标准重算客户端金额伪造、政策失败、Case 缺失和事务回滚测试通过。
|
||||||
|
证据:`test_expense_claim_service.py -k standard_adjustment` 8 项加付款集成 1 项通过,HTTP 标准重算 1 项通过。
|
||||||
|
- [x] [CONCEPT: 测试方案] 前端数据归一化、状态、筛选、URL、证据动作和响应式测试通过。
|
||||||
|
证据:容器内 `cfo-value-dashboard.test.mjs` 14 项通过,新增机会 URL 恢复/清理、筛选上下文、风险/单据/预算/维度链接和预算非事实口径断言;与 App Shell 返回链、路由加载和筛选样式组合回归 37 项通过;带凭证序列化和无证据入口 fail-closed 均有断言。
|
||||||
|
- [x] [CONCEPT: 测试方案] 一次性 PostgreSQL 空库迁移、重复升级、约束、append-only、无损降级和并发测试通过。
|
||||||
|
证据:一次性 PostgreSQL 17 迁移循环通过;`test_savings_concurrency_postgres.py` 6 项通过,覆盖重放、canonical 竞态、跨租户、独立确认、付款单事实和 append-only DB 触发器。
|
||||||
|
- [x] [CONCEPT: 集成] 住宿标准重算 → 机会 → 审批 → 付款 → 实际 → 财务确认 → CFO 看板 E2E 通过。
|
||||||
|
证据:`test_savings_value_e2e.py` 从服务端政策差额、付款动作、待确认排除、独立财务确认到 CFO 金额对账单项通过。
|
||||||
|
- [x] [CONCEPT: 集成] 确认后负向冲回和报告 `as_of` 回放 E2E 通过。
|
||||||
|
证据:`test_savings_value_e2e.py`与 `test_cfo_value_analytics.py`同时验证当前净值归零和冲回前历史金额回放。
|
||||||
|
- [x] [CONCEPT: 容器验证] 相关 pytest、Ruff、前端测试和生产构建均在 `local-x-financial-linux` 内通过。
|
||||||
|
证据:Savings/CFO 相关切片与端到端均通过;fresh PostgreSQL 总探针 `87 passed / 0 skipped / 0 failed`,其中 Savings 并发 6 项;Web 全量 `815 passed / 0 failed` 与 Vite build 通过;新增 Python 文件 Ruff 和 `git diff --check` 通过。
|
||||||
|
|
||||||
|
## 9. 文档收尾
|
||||||
|
|
||||||
|
- [x] [CONCEPT: 指标与验收] 逐项核对 A1-A8,并把文件、接口、迁移、测试和运行结果写回证据。
|
||||||
|
证据:A1 纵向闭环由 `test_savings_value_e2e.py`;A2-A4/A4.1 由 Savings schema、`cfo_value_analytics.py`、基线/分析测试;A5-A6 由 CFO 组件、来源下钻和前端状态测试;A7 由 0015 迁移与 PostgreSQL 并发;A8 由本节最终容器汇总证明。
|
||||||
|
- [ ] [CONCEPT: 风险与开放问题] 记录付款证据等级、汇率、双人复核、工时口径和试点目标的最终边界。
|
||||||
|
证据:
|
||||||
|
- [x] [CONCEPT: 本轮实现记录] 同步更新上位 `ai-expense-closed-loop-and-value-proof` 文档,不删除历史证据。
|
||||||
|
证据:上位 TODO 已回填 Savings Ledger、状态机、CFO 看板、下钻、证据详情和 E2E;历史证据原样保留。
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 13:08:修复 AgentAsset 全局读写、跨租户子记录注入、风险样本串租户、审核身份伪造和 ONLYOFFICE 匿名/可重放回调问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`;`origin/main` 无新提交,本地 `main` ahead 17,工作区包含多智能体并行未提交变更且未被清理或覆盖。
|
||||||
|
- 本地 ahead 摘要:`242d68c3` 审批任务与豁免、`28b834ed` 审批动作幂等回放、`4940ebc4` 风险处置流程、`ee88a36b` 租户安全分层学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据关联、`54754b55` 个人报销记忆、`211f85d9` 已验证申请工作流、`5b246307` 申请预览决策、`a662cfe6` AI 反馈账本、`5ed34c2b` 历史申请回填、`11275e4b` 迁移 ownership 安全、`1347366b` 时间线与草稿事件安全、`22669a90` 统一费用时间线、`a616b30c` AI 申请事务、`653eda05` 不透明会话、`661990b2` 费用案例事务事件。
|
||||||
|
- 修改:为 AgentAsset、Version、Review、TestRun 和 RuleFeedback 增加结构化 `tenant_id + scope`;所有资产、发布、监控、召回、遥测、调度、foundation 和风险运行时查询接入企业/平台作用域;同编码按企业优先、平台回退解析,跨企业资源统一不可见,平台资产仅平台管理员可写。
|
||||||
|
- 修改:真实风险场景强制显式目标企业并先按 `ExpenseClaim.tenant_id` 过滤;测试证据归目标企业;版本、审核和规则表变更主体改为登录会话中的稳定 employee/username 标识,客户端 actor/reviewer 不能覆盖审计事实。
|
||||||
|
- 修改:新增 DB-backed ONLYOFFICE content/callback 会话和 `active → processing → consumed|failed` 原子状态机;token 绑定租户、资源、资产、document key/version/fingerprint、权限、actor、audience、时间和 JTI;平台只读、跨资产、旧版本、错 key、过期和重放回调均拒绝。
|
||||||
|
- 修改:回调复用安全下载器,限制配置 origin,校验 DNS 全部地址并固定已验证公网 IP,拒绝重定向、超限、错误 MIME、危险 ZIP 和异常 OOXML;新增 `20260717_0026` 迁移并对无法归属的旧数据和有企业事实的 downgrade fail-closed。
|
||||||
|
- 操作:拆出 AgentAsset access/serialization/ONLYOFFICE security 与风险规则字段推断模块;将核心文件控制在 800 行以内;补齐功能 CONCEPT/TODO 和迁移、权限、安全回归测试。
|
||||||
|
- 验证:容器内发布/监控/召回/运行时/调度/遥测/租户安全/ONLYOFFICE 汇总 58 项通过;AgentAsset service/foundation 28 项通过;风险生成/修订/golden 49 项通过;相关文件 Ruff、py_compile、ORM mapper(84 张表)和 `git diff --check` 均通过。
|
||||||
|
- 验证:一次性 PostgreSQL 探针完成旧平台数据升级、同编码多企业、scope/check、跨租户复合外键和有事实 downgrade 保护;新库 `base → head(0028)` 与 `head → 0025 → head` 均成功,AgentAsset 与 Knowledge ONLYOFFICE 会话表正常创建。
|
||||||
|
- 影响:企业只能读取本企业和平台只读资产,无法观察或修改其他企业的资产、版本、审核和测试证据;规则测试不会抽取其他企业费用;审计身份不可由请求伪造;文档回写失败时保持原文件不变,也不会向任意或内部地址发起下载。
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 13:40:修复 0026 Agent 资产租户安全迁移在真实历史建表路径下无法完整回退的问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`、`git status -sb`、`git rev-parse --abbrev-ref --symbolic-full-name @{u}`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`;`origin/main` 无新提交,本地 `main` ahead 17,工作区包含多智能体并行变更,未合并、覆盖或提交。
|
||||||
|
- 本地 ahead 摘要:17 个提交覆盖审批任务与幂等回放(`242d68c3`、`28b834ed`、`4940ebc4`)、租户安全费用学习/预审/票据关联/申请记忆与反馈(`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`)、历史回填与迁移安全(`5ed34c2b`、`11275e4b`)、费用时间线与事务(`1347366b`、`22669a90`、`a616b30c`、`661990b2`)及不透明认证会话(`653eda05`)。
|
||||||
|
- 根因:空库先经过 0026 时 Agent 资产旧表尚不存在,迁移会按设计跳过这些表;之后旧表由当前模型补建,PostgreSQL 为列级租户外键生成 `*_tenant_id_fkey`,而 0026 回退硬编码删除 `fk_*_tenant`,首个 `agent_asset_rule_feedback` 约束不存在即中断。
|
||||||
|
- 修改:`20260717_0026_agent_asset_tenant_security.py` 新增带 PostgreSQL 标识符引用的约束安全删除器,0026 回退对它负责的复合外键、租户外键、范围检查和唯一约束统一使用 `DROP CONSTRAINT IF EXISTS`;升级路径与最终升级约束保持不变,模型自动命名的列级外键仍随租户列删除安全清理。
|
||||||
|
- 操作:在 `financial-internal` 网络启动独立 `postgres:16-alpine` 一次性数据库,使用应用容器和项目 venv 执行真实 Alembic 循环;验证结束后停止探针,并确认 `--rm` 已删除容器。
|
||||||
|
- 验证:相关 Ruff 检查通过;PostgreSQL-only 迁移防误用测试 42 项通过;一次性 PostgreSQL 上的完整迁移循环 1 项通过(7.00 秒),覆盖空库升级至 0028、回退至 0008、再次升级、完整回退至 base、最终再次升级至 0028,同时验证约束存在与缺失两种删除路径。
|
||||||
|
- 影响:真实历史路径与当前模型补建路径都能安全回退 0026;缺少迁移命名约束时不再失败,已有约束仍被正常移除,且不会改变 head 升级结构。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 13:25:记录 bug 修复:未配置商业计量器时独立 SQLite Session 回滚调用方事务。
|
||||||
|
- Git 提交检查:执行 `git fetch --all --prune` 后,`HEAD..origin/main` 无新提交;本地 `main` ahead 17 个既有提交,依次为 `242d68c3` 审批任务与豁免、`28b834ed` 审批幂等响应、`4940ebc4` 风险处置、`ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 票据零入口归集、`54754b55` 个人申请记忆、`211f85d9` 申请流程统一、`5b246307` 预览决策、`a662cfe6` 反馈账本、`5ed34c2b` 历史费用 Case 回填、`11275e4b` migration ownership、`1347366b` 时间线与草稿事件、`22669a90` 费用时间线、`a616b30c` AI 申请提交事务、`653eda05` bearer session、`661990b2` 费用 Case 事务事件;这些提交均早于本轮且未改写。
|
||||||
|
- 修改:`commercial_direct_operation.py` 增加调用方 `lookup_session` 的只读未配置短路,OCR、RuntimeChat、金融连接器和附件 observer 均显式传入当前 Session。这样没有计量器时不再创建第二个 Session,也不会在 SQLite `StaticPool` 共用连接上意外 rollback 已 flush 的费用明细。
|
||||||
|
- 操作:先用附件归集回归复现 `expense_claim_items expected to update 1 row; 0 were matched`,再将未配置判断前移到调用方 Session;保留真正配置计量器时的独立事务预占与结算。
|
||||||
|
- 验证:容器内附件归集与票据夹 `31 passed`;商业资源边界 `9 passed`;报销端点与连接器组合 `38 passed`;相关 Ruff 通过。
|
||||||
|
- 影响:未启用商业计量的开发、测试和兼容租户不再因为商业探测破坏调用方事务;生产 PostgreSQL 的独立商业事务语义保持不变。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 13:25:记录 bug 修复:业务回滚释放预占后相同请求无法安全重试。
|
||||||
|
- Git 提交检查:执行 `git fetch --all --prune` 后,`HEAD..origin/main` 无新提交;本地 `main` ahead 17 个既有提交,依次为 `242d68c3` 审批任务与豁免、`28b834ed` 审批幂等响应、`4940ebc4` 风险处置、`ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 票据零入口归集、`54754b55` 个人申请记忆、`211f85d9` 申请流程统一、`5b246307` 预览决策、`a662cfe6` 反馈账本、`5ed34c2b` 历史费用 Case 回填、`11275e4b` migration ownership、`1347366b` 时间线与草稿事件、`22669a90` 费用时间线、`a616b30c` AI 申请提交事务、`653eda05` bearer session、`661990b2` 费用 Case 事务事件;这些提交均早于本轮且未改写。
|
||||||
|
- 修改:`commercial_runtime_reservations.py` 在相同指纹、相同订阅/权益/账期下允许 `released → reserved`,重开时重新锁定合同、校验 meter 快照并执行硬配额判断;跨账期重试继续失败关闭。
|
||||||
|
- 操作:补充 Direct operation 的 not-sent 后重试测试,并把金融连接器“业务 rollback 后同一外部事件重试并提交”加入资源边界回归。
|
||||||
|
- 验证:容器内 Direct + 商业资源组合 `22 passed`,商业资源边界 `9 passed`;回滚阶段无 usage,重试提交后仅一个 usage 且预占终态为 committed。
|
||||||
|
- 影响:数据库瞬时失败或显式回滚不再把同一幂等业务请求永久卡在 released;重试仍受当前硬配额和账期约束,不会绕过额度。
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# 默认 Compose 未统一管理本地 PostgreSQL
|
||||||
|
|
||||||
|
日期:2026-07-17
|
||||||
|
文档路径:document/development/2026-07-17/dev-logs/bugs/default-compose-local-postgres-lifecycle.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
- 22:47:记录 bug 修复:默认 Compose 未统一管理本地 PostgreSQL。(bug-log:787bc3a4)
|
||||||
|
- Git 提交检查:fetch 成功;upstream `origin/main`;upstream 新提交:未发现;本地 ahead 提交:787bc3a4 (HEAD -> main) feat(platform): close AI expense value loop;242d68c3 feat(approval): add task workflow and waiver decisions;28b834ed fix(approval): replay immutable action responses;4940ebc4 feat(approval): add safe risk disposition workflow;ee88a36b feat(ai): add tenant-safe hierarchical expense learning;6bdf65bc feat(expenses): add authoritative pre-review workflow;ae3f02c3 feat(expense): add persistent zero-entry receipt association;54754b55 feat(ai): add personal expense application memory;... 另有 10 条。
|
||||||
|
- 修改:`docker-compose.yml` 将 PostgreSQL、健康依赖、本机端口和持久卷并入默认启动链路;本地数据库插值改用独立的 `LOCAL_POSTGRES_*` 命名空间,避免根 `.env` 的外部数据库账号污染 Compose;`docker-compose.postgres.yml` 保留为空兼容覆盖文件。
|
||||||
|
- 修改:`start.sh` 与 `server_start.sh` 在读取 `.env` 前保存并在读取后恢复完整 PostgreSQL 运行参数,保证 Compose 注入的 host、port、database、user、password 和 URL 始终优先;`test_env_file_precedence.py` 增加根脚本、后端脚本和默认 Compose 回归。
|
||||||
|
- 操作:先将旧库备份到 `/tmp/x-financial-local-pre-compose-20260717.dump`,再把旧卷只读复制到 Compose 管理的 `x-financial_postgres-data`;旧卷未删除。统一本地开发角色凭据后执行 `docker compose up -d`,迁移前置检查从 `unversioned/base` 通过并升级到 `20260717_0028`。
|
||||||
|
- 过程披露:第一次 Compose 重建暴露环境优先级问题时,主容器仍沿用了根 `.env` 指向的外部数据库,并按既有启动流程将其从 `20260716_0006` 升级到 `20260717_0028`;迁移日志无失败,未对外部库执行回滚。修复后已确认当前主容器只连接本地 PostgreSQL 容器。
|
||||||
|
- 验证:容器内定向测试 `107 passed`;Ruff、格式、两个 shell 脚本语法、默认/兼容 Compose 配置和 `git diff --check` 均通过;重复执行 `docker compose up -d` 只保持两个服务运行,没有创建额外容器。
|
||||||
|
- 验证:`main` 与 `postgres` 均为 healthy;前端代理健康接口返回数据库 `ok=true`;应用实际连接 PostgreSQL 容器地址;本地库为 85 张 public 表、105 条员工数据、Alembic head `20260717_0028`,PostgreSQL 日志无 FATAL/ERROR/PANIC。
|
||||||
|
- 影响:以后在仓库根目录执行一次默认 `docker compose up -d` 即可同时启动应用和本地数据库,并复用 Compose 管理的数据卷;外部数据库 `.env` 配置不再悄悄覆盖本地 Compose 连接。
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 员工导入与成员资格部分提交
|
||||||
|
|
||||||
|
日期:2026-07-17
|
||||||
|
文档路径:document/development/2026-07-17/dev-logs/bugs/employee-import-membership-transaction.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
- 14:07:记录 bug 修复:员工导入与成员资格部分提交。(bug-log:242d68c3)
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`、状态、upstream 和双向日志检查;`origin/main` 无新提交,本地 `main` ahead 17。ahead 摘要:17 个提交覆盖审批/风险处置、租户安全费用学习、权威预审、票据关联、个人记忆、历史回填、迁移安全、费用事件事务和不透明认证会话;本轮未改写这些提交。
|
||||||
|
- 根因:`EmployeeImportCoordinator._apply_import_rows()` 在写员工和上级关系后先 commit,`EmployeeService.import_employees()` 才补 `TenantMembership` 并第二次 commit;成员资格失败时接口报错,但员工已永久落库。
|
||||||
|
- 修改:协调器只 flush 员工、角色、组织、上级和变更日志,不再拥有 commit;外层服务仅在成功结果后补齐租户成员资格,并对两部分执行一次统一 commit,任一异常统一 rollback。
|
||||||
|
- 操作:新增成员资格同步注入失败测试,导入新员工后故意抛错并验证员工记录不存在;校验失败结果不触发成员资格或无意义提交。
|
||||||
|
- 验证:容器内员工服务、导入、认证和行为画像 35 项通过;差旅计算器 5 项通过;目标 Ruff、format check、compileall、代码体积门禁和 `git diff --check` 通过。
|
||||||
|
- 影响:员工批量导入现在满足“全部员工数据与认证成员资格一起成功或一起失败”,不会出现接口失败但部分账号已经创建/修改的状态。
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 13:19:修复用户会话结算测试身份错配、缓存命中后旧部门不再归一化,以及 Excel 导入清空上级时误清空员工租户的问题。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`;`origin/main` 无新提交,本地 `main` ahead 17,工作区仍包含多智能体并行变更,未做清理、覆盖或提交。
|
||||||
|
- 本地 ahead 摘要:17 个提交覆盖审批任务与幂等回放(`242d68c3`、`28b834ed`、`4940ebc4`)、租户安全费用学习/预审/票据关联/申请记忆与反馈(`ee88a36b` 至 `a662cfe6`)、历史回填与迁移安全(`5ed34c2b`、`11275e4b`)、费用时间线/事务(`1347366b`、`22669a90`、`a616b30c`、`661990b2`)及不透明认证会话(`653eda05`)。
|
||||||
|
- 修改:会话结算正向用例改用会话真实所有者认证,保留服务端 username ownership 校验;新增其他用户不能关闭该会话的反向测试,避免用放宽授权掩盖 `durationMs=0`。
|
||||||
|
- 修改:目录建表/种子初始化继续按 bind 与租户缓存,但缓存命中时仍以租户过滤查询旧部门编码并持久化映射到规范部门;不再因初始化缓存永久跳过外部同步产生的旧编码。
|
||||||
|
- 修改:Employee 与 OrganizationUnit 的复合租户关系只把 `organization_unit_id`、`manager_id` 标记为 SQLAlchemy 可同步外键,`tenant_id` 只参与关联过滤;清空部门或上级不会再把员工租户写成 `NULL`,数据库复合外键仍阻止跨租户关联。
|
||||||
|
- 测试:补充会话所有权反例、导入后 `tenant_id/manager_id` 持久化断言,以及旧部门归一化后租户与数据库组织归属断言。
|
||||||
|
- 验证:容器内三个原失败点与新增反例 4 项通过;员工服务、Excel 导入、行为画像/会话和认证会话相关回归 31 项通过;ORM mapper 确认关系同步列仅为 `organization_unit_id/manager_id`;相关文件 Ruff 与 `git diff --check` 均通过。
|
||||||
|
- 影响:会话时长能在正确登录主体下正常结算,其他用户仍不能关闭该会话;旧组织编码会持续收敛到标准部门;员工导入或资料更新清空上级/部门时不会破坏不可为空的租户归属。
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 报销审批身份解析跨租户串读
|
||||||
|
|
||||||
|
日期:2026-07-17
|
||||||
|
文档路径:document/development/2026-07-17/dev-logs/bugs/expense-claim-approver-tenant-isolation.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
- 13:48:记录 bug 修复:报销审批身份解析跨租户串读。(bug-log:242d68c3)
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`、`git status -sb`、upstream 解析及双向日志检查;`origin/main` 无新提交,本地 `main` ahead 17。ahead 摘要:`242d68c3/28b834ed/4940ebc4` 为审批任务、不可变回放和风险处置,`ee88a36b/6bdf65bc/ae3f02c3/54754b55/211f85d9/5b246307/a662cfe6` 为租户安全费用学习、预审、票据关联、个人记忆和申请反馈,`5ed34c2b/11275e4b` 为历史回填与迁移安全,`1347366b/22669a90/a616b30c/661990b2` 为费用时间线及事务,`653eda05` 为不透明会话;本轮未改写这些提交。
|
||||||
|
- 修改:`expense_claim_access_policy.py` 的当前员工、申请人、直属领导、部门预算负责人和财务负责人解析全部先绑定认证用户或报销单的结构化 `tenant_id`;员工、组织和下属子查询增加租户首层谓词,避免相同姓名、邮箱前缀、部门或角色在另一企业命中。
|
||||||
|
- 修改:把身份候选、申请人回填和唯一姓名判断拆入 `expense_claim_employee_resolver.py`,保持公开策略 API 不变,并将访问策略主文件降到 701 行;无可信租户、跨租户关联或结构化归属冲突统一失败关闭。
|
||||||
|
- 操作:同步补齐审批任务、风险并发、层级记忆和报销测试夹具的显式企业归属;没有放宽生产授权,也没有用默认企业兼容掩盖跨租户错误。
|
||||||
|
- 验证:容器内 `test_expense_claim_service.py` 121 项通过,访问策略文件大小与租户作用域定向 10 项通过;审批任务、PostgreSQL 并发与全量后端分片均通过,fresh PostgreSQL 专项最终 `87 passed / 0 skipped / 0 failed`;新 Python 文件 Ruff 和 `git diff --check` 通过。
|
||||||
|
- 影响:审批队列、审批人快照、退回/通过权限和历史回填不会再因另一企业存在同名员工或同名部门而串租户;多租户环境下报销审批保持可解释且失败关闭。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 13:33:记录 bug 修复:财务驾驶舱租户安全测试仍按旧 Case Link 契约构造报销单。
|
||||||
|
- Git 提交检查:`git fetch --all --prune` 后未发现 upstream 新提交;本地相对 `origin/main` ahead 17 个既有提交,分别为 `242d68c3` 审批任务工作流、`28b834ed` 不可变响应重放、`4940ebc4` 风险处置工作流、`ee88a36b` 租户安全分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据关联、`54754b55` 个人申请记忆、`211f85d9` 申请工作流统一、`5b246307` 申请预览决策、`a662cfe6` 反馈账本、`5ed34c2b` 历史 Expense Case 回填、`11275e4b` 迁移所有权安全、`1347366b` 时间线与草稿事件安全、`22669a90` 统一事件时间线、`a616b30c` AI 申请事务统一、`653eda05` 不透明 bearer 会话、`661990b2` 事务化 Expense Case 事件;本次未改写这些提交。
|
||||||
|
- 修改:`test_finance_dashboard_tenant_security.py` 的 Claim 构造器显式接收并写入 `tenant_id`,租户夹具不再只依赖历史 `ExpenseCaseLink`;新增“结构化 Claim 租户与旧 Link 不一致时以 Claim 为准”的隔离回归。
|
||||||
|
- 操作:先在容器内单跑复现 3 个失败,确认不是测试顺序或全局 monkeypatch 污染,再执行最小夹具修复并串行复跑财务、连接器与 Hermes 相邻测试组。
|
||||||
|
- 验证:单文件 `8 passed`;排序相邻组 `67 passed, 4 skipped`;目标文件 Ruff 检查与 `git diff --check` 均通过。
|
||||||
|
- 影响:财务驾驶舱测试与新的结构化租户模型保持一致,同时固定了旧关联索引不能移动报销单租户归属的安全边界;生产 fail-close 与首条 SQL 租户过滤没有放宽。
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 财务连接器运行事件计数与固定时钟偏差
|
||||||
|
|
||||||
|
日期:2026-07-17
|
||||||
|
文档路径:document/development/2026-07-17/dev-logs/bugs/financial-connector-operational-counting-clock.md
|
||||||
|
|
||||||
|
## 修复记录
|
||||||
|
|
||||||
|
- 11:39:记录 bug 修复:财务连接器相同载荷的不同运行尝试被永久合并,固定时钟场景的接收时间偏离观测窗口。
|
||||||
|
- Git 提交检查:已执行 `git fetch --all --prune`;upstream `origin/main` 无新提交;本地 ahead 17 条,包含 `242d68c3 feat(approval): add task workflow and waiver decisions`、`28b834ed fix(approval): replay immutable action responses`、`4940ebc4 feat(approval): add safe risk disposition workflow`、`ee88a36b feat(ai): add tenant-safe hierarchical expense learning` 等共享工作区既有提交,本次未改写这些提交。
|
||||||
|
- 修改:`financial_connector_operational_events.py` 把规范化 UTC 发生时间纳入运行事实幂等命名空间,使同一 candidate 的补偿重试保持单条、不同 HTTP 尝试分别计数;`financial_connector_ingestion.py` 让注入的可信 `now_epoch` 同时驱动接收时间和配置健康时间;`financial_connector_observability.py` 统一把数据库时间规范为 UTC,避免 SQLite/驱动返回 naive datetime 时窗口结果不稳定。
|
||||||
|
- 操作:补充运行事实服务、HTTP 冲突、可信认证归属、敏感原值不落库和 PostgreSQL 并发探针;所有 Python、pytest、Ruff 与迁移操作均在 `local-x-financial-linux` 容器中执行,PostgreSQL 验证使用新建的一次性 `disposable-probe` 容器,没有连接项目配置中的外部数据库。
|
||||||
|
- 验证:容器内连接器与迁移前置定向 `115 passed`,全新 PostgreSQL 17 完整迁移循环 `51 passed`,连接器并发 `4 passed`,后继 0023 并发 `7 passed`;相关 Ruff 和 `git diff --check` 通过。
|
||||||
|
- 影响:可观测性不会再把多次真实重放压成一次,也不会因测试/模拟可信时钟与实际系统日期不同而漏掉事件;同一补偿 candidate 仍由数据库唯一约束保证幂等。
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user