feat(server): W4-A integration endpoints and contract gaps

- POST /acquisition-plans: manual submit via download.plan -> download.submit
  (same authorization/audit/idempotency path as the agent), slotId or entityId
- read-only /media-assets, /asset-locations, /asset-unit-coverage, /path-bindings,
  /reconcile-runs, /server-mappings, /artifacts, GET /title-aliases/{id};
  unimplemented write endpoints removed from api.ts; registry test now checks both directions
- candidate evaluation upsert per (slot, source_release) + migration 0022 dedupe/unique index
- organization records expose downloadTaskId (+ list filter); inventory adds
  subscribed/candidateReasons; feed sources expose derived possibleGap
- GET /events?after=&limit= JSON replay page for API key callers (same sse_event table)
- preference tag revision history from audit_event; subscriptions/identities/aliases
  lists carry mediaEntityTitle; subscription q/mediaKind search; slot subscriptionId
  filter + currentAsset summary; policy preview samples carry title/reasons
- DecisionSummary, IdentityRevisionList, ReleaseIdentityResult, PolicyPreviewSummary exported from contracts

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 21:10:02 +08:00
1 parent ea4c384664
commit d3ab54f2bd
34 files changed
+1083 -123

No files matched your search

+21
View File
@@ -201,6 +201,8 @@ export const PublicOrganizationRecord = z.object({
/** 稳定记录 ID */
id: Id,
taskId: Id.nullable(),
/** 来源下载任务(手动整理本地路径时为 null) */
downloadTaskId: Id.nullable(),
/** 相关资产操作账本 ID */
operationIds: z.array(Id),
media: z
@@ -265,6 +267,7 @@ export const OrganizationListQuery = z.object({
status: OrganizationStatus.optional(),
trigger: OrganizationTrigger.optional(),
mediaEntityId: Id.optional(),
downloadTaskId: Id.optional(),
q: z.string().max(200).optional(),
});
export type OrganizationListQuery = z.infer<typeof OrganizationListQuery>;
@@ -384,6 +387,24 @@ export const InventoryEntry = z.object({
downloading: z.array(
z.object({ downloadTaskId: Id, name: z.string(), status: DownloadStatus, progress: z.number().min(0).max(1) }),
),
/** 有生效中的订阅(active) */
subscribed: z.boolean(),
/**
* 候选理由:该作品各 Slot 对来源资源的最近一次评估(每个 (Slot, 资源) 一条,新→旧,最多 50 条)。
* 工作台按 sourceReleaseId 取当前资源"为什么选 / 不选"。
*/
candidateReasons: z.array(
z.object({
slotId: Id,
slotName: z.string(),
sourceReleaseId: Id,
decision: CandidateEvaluation.shape.decision,
/** 拒绝原因 message + 评估说明,已合并去重 */
reasons: z.array(z.string()),
planId: Id.nullable(),
evaluatedAt: Timestamp,
}),
),
});
export type InventoryEntry = z.infer<typeof InventoryEntry>;