feat(rules): rule_group/rule_atom (0033), /rules endpoints, evaluation via referenced rule groups
- migration 0033: rule_group (layers, hard excludes, scope, source, revision,
fingerprint, default_order) and rule_atom; acquisition_slot.rule_group_ids;
preference tags migrated into rule groups (same id/fingerprint), suppressions
linked; seeded default groups for acquisition / upgrade / search
- contracts: RuleGroup/RuleAtom/RuleExpr/RuleDefaults, parse/serialize/preview
schemas; Api.rules, Api.ruleAtoms, Api.rulePolicies, Api.ruleSuppressions
- server: GET/POST/PATCH/DELETE /rules, /rules/{id}/revisions, /rules/{id}/preview,
/rules/parse, /rules/serialize, /rules/keywords, /rules/defaults, /rules/atoms;
disable/delete writes suppression, agent re-add -> 409
- /preference-tags now aliases rule groups; /policy-revisions and
/preference-suppressions kept as aliases of /rules/policies, /rules/suppressions
- evaluateAcquisition resolves slot -> subscription -> default chain (+ upgrade
defaults) and records pri / rule_group@revision / per-layer trace
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
a0946bf480
commit
0105e5c6ac
11 files changed
+2296
-340
No files matched your search
@@ -751,6 +751,166 @@ export const Api = {
|
||||
R,
|
||||
),
|
||||
|
||||
// ── 规则(W9-D):规则组 = 收录过滤条件 + 偏好标签合一。静态路径先于 /rules/{id} 注册 ──
|
||||
rules: {
|
||||
keywords: ep({
|
||||
method: "get",
|
||||
path: "/rules/keywords",
|
||||
scope: "read",
|
||||
summary: "关键字表:内置(对齐 MP,含判定依据与上游正则)+ 自定义条件",
|
||||
response: policy.RuleKeywordList,
|
||||
}),
|
||||
parse: ep({
|
||||
method: "post",
|
||||
path: "/rules/parse",
|
||||
scope: "read",
|
||||
summary: "规则串 → 结构化层(`>` 分层,层内 ! & | 与括号);错误逐条给位置与相近名",
|
||||
body: policy.RuleParseRequest,
|
||||
response: policy.RuleParseResult,
|
||||
}),
|
||||
serialize: ep({
|
||||
method: "post",
|
||||
path: "/rules/serialize",
|
||||
scope: "read",
|
||||
summary: "结构化层 → 规范化规则串",
|
||||
body: policy.RuleSerializeRequest,
|
||||
response: policy.RuleSerializeResult,
|
||||
}),
|
||||
defaults: ep({
|
||||
method: "get",
|
||||
path: "/rules/defaults",
|
||||
scope: "read",
|
||||
summary: "三套默认规则组链:订阅 / 洗版 / 搜索",
|
||||
response: policy.RuleDefaults,
|
||||
}),
|
||||
setDefaults: ep({
|
||||
method: "put",
|
||||
path: "/rules/defaults",
|
||||
scope: "admin",
|
||||
summary: "设置默认规则组链(按数组顺序串联,pri 取最后一个带层的组)",
|
||||
body: policy.RuleDefaults,
|
||||
response: policy.RuleDefaults,
|
||||
}),
|
||||
...crud(
|
||||
"/rules",
|
||||
"规则组",
|
||||
{
|
||||
row: policy.RuleGroup,
|
||||
create: policy.RuleGroupCreate,
|
||||
update: policy.RuleGroupUpdate,
|
||||
filters: {
|
||||
kind: policy.RuleGroupKind.optional(),
|
||||
source: policy.RuleGroupSource.optional(),
|
||||
enabled: z.enum(["true", "false"]).optional(),
|
||||
includeDeleted: z.enum(["true", "false"]).optional(),
|
||||
q: z.string().max(80).optional(),
|
||||
},
|
||||
},
|
||||
R,
|
||||
),
|
||||
/** 删除 = 标记 deleted_at + 写抑制记录 */
|
||||
remove: del("/rules/{id}", "删除规则组(留抑制记录;被引用时 409)"),
|
||||
revisions: ep({
|
||||
method: "get",
|
||||
path: "/rules/{id}/revisions",
|
||||
scope: "read",
|
||||
summary: "规则组修订历史(新→旧;取自审计事件)",
|
||||
params: IdParam,
|
||||
response: policy.RuleGroupRevisionList,
|
||||
}),
|
||||
preview: ep({
|
||||
method: "post",
|
||||
path: "/rules/{id}/preview",
|
||||
scope: "read",
|
||||
summary: "试算:对最近 N 条已识别资源(或指定资源/标题)跑本组,给 pri、命中层与拒绝原因",
|
||||
params: IdParam,
|
||||
body: policy.RulePreviewRequest,
|
||||
response: policy.RulePreviewResult,
|
||||
}),
|
||||
},
|
||||
ruleAtoms: {
|
||||
...crud(
|
||||
"/rules/atoms",
|
||||
"自定义条件",
|
||||
{ row: policy.RuleAtom, create: policy.RuleAtomCreate, update: policy.RuleAtomUpdate },
|
||||
R,
|
||||
),
|
||||
remove: del("/rules/atoms/{id}", "删除自定义条件(被规则组引用时 409)"),
|
||||
},
|
||||
/** /policy-revisions 的新路径(旧路径保留为别名,见 docs/runbook/rules.md) */
|
||||
rulePolicies: {
|
||||
list: ep({
|
||||
method: "get",
|
||||
path: "/rules/policies",
|
||||
scope: "read",
|
||||
summary: "收录策略版本列表",
|
||||
query: listQuery({ policyKey: z.string().optional(), status: policy.PolicyStatus.optional() }),
|
||||
response: cursorPage(policy.PolicyRevision),
|
||||
}),
|
||||
get: ep({
|
||||
method: "get",
|
||||
path: "/rules/policies/{id}",
|
||||
scope: "read",
|
||||
summary: "收录策略版本",
|
||||
params: IdParam,
|
||||
response: policy.PolicyRevision,
|
||||
}),
|
||||
create: ep({
|
||||
method: "post",
|
||||
path: "/rules/policies",
|
||||
scope: "admin",
|
||||
summary: "新建策略草案",
|
||||
body: policy.PolicyRevisionCreate,
|
||||
response: policy.PolicyRevision,
|
||||
}),
|
||||
update: ep({
|
||||
method: "patch",
|
||||
path: "/rules/policies/{id}",
|
||||
scope: "admin",
|
||||
summary: "修改策略草案",
|
||||
params: IdParam,
|
||||
body: policy.PolicyRevisionUpdate,
|
||||
response: policy.PolicyRevision,
|
||||
}),
|
||||
preview: ep({
|
||||
method: "post",
|
||||
path: "/rules/policies/{id}/preview",
|
||||
scope: "read",
|
||||
summary: "预览策略草案",
|
||||
params: IdParam,
|
||||
response: policy.PolicyRevision,
|
||||
}),
|
||||
publish: ep({
|
||||
method: "post",
|
||||
path: "/rules/policies/{id}/publish",
|
||||
scope: "admin",
|
||||
summary: "发布策略版本",
|
||||
params: IdParam,
|
||||
body: z.object({ expectedRevision: z.number().int().min(1) }),
|
||||
response: policy.PolicyRevision,
|
||||
}),
|
||||
rollback: ep({
|
||||
method: "post",
|
||||
path: "/rules/policies/{id}/rollback",
|
||||
scope: "admin",
|
||||
summary: "回滚到该策略版本",
|
||||
params: IdParam,
|
||||
response: policy.PolicyRevision,
|
||||
}),
|
||||
},
|
||||
/** /preference-suppressions 的新路径(旧路径保留为别名) */
|
||||
ruleSuppressions: crud(
|
||||
"/rules/suppressions",
|
||||
"规则抑制记录",
|
||||
{
|
||||
row: policy.PreferenceSuppression,
|
||||
create: policy.PreferenceSuppressionCreate,
|
||||
update: policy.PreferenceSuppressionUpdate,
|
||||
filters: { active: z.enum(["true", "false"]).optional() },
|
||||
},
|
||||
R,
|
||||
),
|
||||
|
||||
// ── 执行 ──
|
||||
tasks: {
|
||||
...crud(
|
||||
|
||||
Reference in new issue
Block a user