/** * [ref] 车二 + [ref] §2 —— **规则车道的 wire 面**([ref] §7 的导入半场 + [ref] 的撤销半场)。 * * POST /v1/rules → 写一条**收紧**规则(deny/ask)进 durable 分区,单步、无票仪式 * POST /v1/rules/cc-import/prepare → 预览 + 一张**一次性、principal 绑定、有期、载荷绑定**的票 * POST /v1/rules/cc-import/redeem → 原子消费票 → confirm → 批量兑付进店 * GET /v1/rules → 列出自己名下**活着**的规则(keyset 分页,游标绑 rev) * DELETE /v1/rules → 按 (rule, scope) 内容撤销,**恒经** core `removePersistedRule` * * **lane = principal**(不是 operator):用户导的是**他自己**的规则,一条规则的语义就是「这个人自己 * 说过 yes」。operator 在**扩权**这条路上没有位置——他既不该替租户扩权,也不该被要求代人点确认。 * 因此**导入两口**只认已验明的 principal,连 `explicitOperatorOk` 的旁路都不给:一个 operator 想给 * 自己导规则,用他自己的 principal 走同一条路即可(那时他就是租户)。 * * 🔴 **`POST /v1/rules` 是撤销口的对偶,不是导入口的简写**(S-511):它写的只有**收紧**方向 * (deny/ask),而收紧不需要同意仪式 —— 与撤销为什么不需要是同一条理由(core `removePersistedRule` * 头注逐字:「a host may narrow on a user's behalf, it may not widen」)。放宽(allow)仍然只有两条路: * 一张卡上的人点头,或一次显式的 settings 导入。方向表在 `permission-rule-vocab.ts`,不在本文件。 * * 🔴 **撤销面两口多一条 operator 超范围域,而这不是上面那句话的例外,是它的对偶**([ref] §2 设计题 * 一裁定):`?principal=` / body 里的 `principal` 让 operator 读**或收回**任一租户的规则。方向决定授权: * 收回是**收紧**(core 的 `removePersistedRule` 头注逐字:「a host may narrow on a user's behalf, it may * not widen」),而多租户管理员能回收权限正是撤销面的本义。读那一半同样只开给 operator —— * 规则文本里含命令样式,跨 principal 读是信息面外泄,所以**不开第三方 principal 相互读**。 * 判据用 `explicitOperatorOk`(空名单 = 全拒),**绝不**从 service token 推断身份:一个共享的部署凭证 * 不是一个人。 * * **billable = false**:四口都不烧模型(读 settings 文本 / 读写规则行),不进 `isBillableSubmitPath`。 * * 🔴 **四类拒绝在 wire 上同形 404**(信任边界 ①):unknown ticket / 别人的 ticket / 过期 / 已用过 —— * 四个成因返回不同的码就是一台存在性 oracle(拿别人的票撞库能探到「它在别处存在」)。分类只进服务端 * 日志,不上 wire。同族先例 = run/trace 属主门与 `POST /v1/tasks/:id/asks/:askId/decision` 的 404 姿势。 */ import type { IncomingMessage, ServerResponse } from "node:http"; import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js"; export declare const RULES_CC_IMPORT_PREPARE_PATH = "/v1/rules/cc-import/prepare"; export declare const RULES_CC_IMPORT_REDEEM_PATH = "/v1/rules/cc-import/redeem"; /** [ref] A7:File 规则直通票口两条(与 cc-import 两口并列,票语义分家见 `rules-consent.ts` 的 A7 段)。 */ export declare const RULES_LOCAL_IMPORT_PREPARE_PATH = "/v1/rules/local-import/prepare"; export declare const RULES_LOCAL_IMPORT_REDEEM_PATH = "/v1/rules/local-import/redeem"; /** 撤销面两口共用的字面路径(GET 列举 / DELETE 撤销)。 */ export declare const RULES_PATH = "/v1/rules"; export declare function handleRules(req: IncomingMessage, res: ServerResponse, match: RouteMatch, ctx: RouteCtx): Promise; export declare const RULES_ROUTES: readonly [{ readonly id: "rules-cc-import-prepare"; readonly path: "/v1/rules/cc-import/prepare"; readonly methods: readonly ["POST"]; }, { readonly id: "rules-cc-import-redeem"; readonly path: "/v1/rules/cc-import/redeem"; readonly methods: readonly ["POST"]; }, { readonly id: "rules-local-import-prepare"; readonly path: "/v1/rules/local-import/prepare"; readonly methods: readonly ["POST"]; }, { readonly id: "rules-local-import-redeem"; readonly path: "/v1/rules/local-import/redeem"; readonly methods: readonly ["POST"]; }, { readonly id: "rules-list"; readonly path: "/v1/rules"; readonly methods: readonly ["GET"]; }, { readonly id: "rules-write"; readonly path: "/v1/rules"; readonly credentialGated: readonly ["POST"]; readonly methods: readonly ["POST"]; }, { readonly id: "rules-revoke"; readonly path: "/v1/rules"; readonly credentialGated: readonly ["DELETE"]; readonly methods: readonly ["DELETE"]; }, { readonly prefix: "/v1/rules/cc-import/"; readonly credentialGated: readonly ["POST"]; }, { readonly prefix: "/v1/rules/local-import/"; readonly credentialGated: readonly ["POST"]; }]; /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */ export type RulesRouteId = RouteIdsOf; //# sourceMappingURL=rules.d.ts.map