实现业务附件受控入库 V1(05-28-business-attachment-governance-v1)

这个提交包含在:
2026-05-28 19:44:15 +08:00
父节点 2c067866ef
当前提交 52e5284954
共修改 25 个文件,包含 1119 行新增和 21 行删除
+90
查看文件
@@ -17,3 +17,93 @@
- `.trellis/tasks/` may contain PRD and acceptance context, but only in
redacted form.
- `scripts/qa/check-trellis-governance.mjs` must pass before commit.
## Scenario: Business Attachment Governance
### 1. Scope / Trigger
- Trigger: adding or changing OA business attachments for reimbursement,
purchase, payment, contract, project or company-document workflows.
- Applies when an attachment is stored by the OA and can later be previewed,
downloaded, reviewed or archived.
- Do not introduce a generic file-center or free directory upload unless a
Trellis task explicitly reopens that architecture.
### 2. Signatures
- DB fields on business attachment models:
`governance_status`, `sensitivity_level`, `attachment_type`,
`reviewed_by`, `reviewed_at`, `review_note`, `archived_by`,
`archived_at`, `archive_note`, `download_policy`.
- Upload multipart fields may include:
`governanceStatus`, `sensitivityLevel`, `attachmentType`, `downloadPolicy`.
- API response fields must use camelCase:
`governanceStatus`, `sensitivityLevel`, `attachmentType`, `downloadPolicy`,
`reviewedBy`, `reviewedAt`, `reviewNote`, `archivedBy`, `archivedAt`,
`archiveNote`.
- Download APIs may accept `reason`; sensitive or reason-required attachments
must reject downloads without a non-empty reason.
### 3. Contracts
- Default status is `approved` unless the business workflow has a stricter
state such as `pending_review`.
- Default sensitivity is `internal`; payment receipts and similarly sensitive
financial proof should use `confidential`.
- `index_only` means OA stores the index/metadata, not the original file.
- Frontend labels are informational only; backend permission and download
reason checks remain authoritative.
- Audit logs for downloads must include actor, business target, attachment ID,
attachment type, sensitivity level, governance status and sanitized reason
when provided.
### 4. Validation & Error Matrix
- Unknown governance enum in upload -> normalize to the module default, do not
persist arbitrary strings.
- Sensitive or `reason_required` attachment download without reason ->
`400 VALID_001` with `{ reason: "required" }`.
- Missing attachment or missing stored object -> `404`.
- User lacks business read permission -> `403 PERM_001`.
- Download reason longer than the server limit -> trim before writing audit
metadata.
### 5. Good/Base/Bad Cases
- Good: a bank receipt upload returns `confidential`, `receipt`,
`reason_required`; download first asks for用途 and writes an audit log.
- Base: a reimbursement supporting material upload defaults to `internal`,
`supporting_material`, `standard` and remains downloadable by authorized
participants.
- Bad: a page hides the download button but the backend route still streams the
file without permission or reason checks.
- Bad: Enterprise WeChat messages or reports include original attachments
instead of OA links and desensitized summaries.
### 6. Tests Required
- Prisma validate after schema or migration changes.
- Backend route tests for default governance fields, custom multipart fields
when exposed, sensitive-download reason rejection and audit `extra` metadata.
- Frontend typecheck/build after API type changes.
- Focused UI verification for pages that add attachment governance labels or
download-reason interactions.
### 7. Wrong vs Correct
#### Wrong
```js
return reply.send(stream);
```
#### Correct
```js
const reason = sanitizeDownloadReason(request.query?.reason);
if (requiresDownloadReason(attachment) && !reason) {
throw buildValidationError({ reason: "required" });
}
await prisma.auditLog.create({ data: buildDownloadAudit({ attachment, reason }) });
return reply.send(stream);
```