@sema-agent/server 7.10.0 → 7.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/adoption/plan.d.ts +152 -0
  2. package/dist/adoption/plan.js +513 -0
  3. package/dist/adoption/runner.d.ts +54 -0
  4. package/dist/adoption/runner.js +505 -0
  5. package/dist/adoption/sql.d.ts +76 -0
  6. package/dist/adoption/sql.js +106 -0
  7. package/dist/adoption/wire.d.ts +250 -0
  8. package/dist/adoption/wire.js +153 -0
  9. package/dist/approval-card.d.ts +24 -0
  10. package/dist/approval-card.js +32 -0
  11. package/dist/boot/adoption.d.ts +30 -0
  12. package/dist/boot/adoption.js +57 -0
  13. package/dist/boot/coordinators.d.ts +4 -0
  14. package/dist/boot/coordinators.js +3 -1
  15. package/dist/boot/parked-revive-gate.d.ts +38 -5
  16. package/dist/boot/parked-revive-gate.js +53 -6
  17. package/dist/boot/runner-deps.d.ts +10 -2
  18. package/dist/boot/runner-deps.js +12 -1
  19. package/dist/config-types.d.ts +16 -1
  20. package/dist/config.js +5 -1
  21. package/dist/http/routes/adoption.d.ts +26 -0
  22. package/dist/http/routes/adoption.js +120 -0
  23. package/dist/http/routes/capabilities.js +12 -0
  24. package/dist/http/routes/rules.d.ts +23 -0
  25. package/dist/http/routes/rules.js +117 -0
  26. package/dist/http/routes/shared-memory.d.ts +31 -0
  27. package/dist/http/routes/shared-memory.js +181 -0
  28. package/dist/http/routes/trace-usage.js +139 -3
  29. package/dist/http/server.d.ts +19 -1
  30. package/dist/http/server.js +42 -0
  31. package/dist/main.js +52 -3
  32. package/dist/observability/fail-open.d.ts +8 -0
  33. package/dist/observability/fail-open.js +8 -0
  34. package/dist/plugins/adoption-log-sql.d.ts +191 -0
  35. package/dist/plugins/adoption-log-sql.js +273 -0
  36. package/dist/plugins/checkpoint-store-sql.d.ts +13 -0
  37. package/dist/plugins/checkpoint-store-sql.js +11 -0
  38. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  39. package/dist/plugins/local-checkpoint-store.js +8 -0
  40. package/dist/plugins/permission-rule-store-sql.d.ts +242 -0
  41. package/dist/plugins/permission-rule-store-sql.js +817 -0
  42. package/dist/plugins/pg-pool.js +37 -0
  43. package/dist/plugins/session-policy-store-sql.d.ts +6 -0
  44. package/dist/plugins/session-policy-store-sql.js +7 -1
  45. package/dist/plugins/shared-memory-store-sql.d.ts +223 -0
  46. package/dist/plugins/shared-memory-store-sql.js +516 -0
  47. package/dist/plugins/store-backend.d.ts +30 -0
  48. package/dist/plugins/store-backend.js +14 -0
  49. package/dist/plugins/tidb-pool.js +47 -0
  50. package/dist/rules-consent.d.ts +126 -0
  51. package/dist/rules-consent.js +198 -0
  52. package/dist/shared-memory-scope-authorizer.d.ts +29 -0
  53. package/dist/shared-memory-scope-authorizer.js +17 -0
  54. package/dist/tool-approval.d.ts +55 -0
  55. package/dist/tool-approval.js +124 -5
  56. package/dist/trace/core-keyset-guard.d.ts +2 -2
  57. package/dist/trace/project.d.ts +1 -0
  58. package/dist/trace/project.js +1 -0
  59. package/package.json +3 -3
@@ -0,0 +1,106 @@
1
+ /** 第 `i` 个占位符(1-based)。TiDB `?` / PG `$i` —— 这是本文件唯一的方言分岔点。 */
2
+ export function ph(dialect, i) {
3
+ return dialect === "tidb" ? "?" : `$${i}`;
4
+ }
5
+ /**
6
+ * 目的地冲突探针(phase 0,**任何 UPDATE 之前**)。
7
+ *
8
+ * 判据 = 「to 侧已有的行,与 from 侧的行,是否共享同一个**逻辑键**」。逻辑键 =
9
+ * `leg.residualKey`(该表最紧的唯一键**去掉身份轴**之后剩下的列):
10
+ * · `undefined` ⇒ 没有唯一键含这根轴 ⇒ 结构性无冲突,本函数不该被调用(调用方按 `undefined` 短路);
11
+ * · `[]` ⇒ 唯一键**就是**这根轴(PK(scope))⇒ 两侧各有任意一行即冲突,退化成笛卡尔计数;
12
+ * · 非空 ⇒ 逐列相等的自连接计数。
13
+ *
14
+ * 参数序:`[toValue, fromValue]`。
15
+ */
16
+ export function buildConflictProbeSql(dialect, leg) {
17
+ const on = (leg.residualKey ?? []).map((c) => ` AND a.${c} = b.${c}`).join("");
18
+ return (`SELECT COUNT(*) AS n FROM ${leg.table} a, ${leg.table} b` +
19
+ ` WHERE a.${leg.matchColumn} = ${ph(dialect, 1)} AND b.${leg.matchColumn} = ${ph(dialect, 2)}${on}`);
20
+ }
21
+ /**
22
+ * 桶迁/行迁的等值 UPDATE。`columns` 里的每一列都被设成**同一个**新值(字节孪生 `*_key` 与可读列一起走),
23
+ * 谓词打在 `matchColumn` 上。参数序:`[...新值 × columns.length, 旧值]`。
24
+ *
25
+ * 幂等性来自谓词本身:重跑时旧值已经不在库里 ⇒ 零行匹配 ⇒ 零行变化。
26
+ */
27
+ export function buildRebindSql(dialect, leg) {
28
+ const sets = leg.columns.map((c, i) => `${c} = ${ph(dialect, i + 1)}`).join(", ");
29
+ return `UPDATE ${leg.table} SET ${sets} WHERE ${leg.matchColumn} = ${ph(dialect, leg.columns.length + 1)}`;
30
+ }
31
+ /**
32
+ * memory 族的 scope 值扫描:把「属于这个 principal 的 scope 键」从库里**读出来**,而不是用 LIKE 去猜。
33
+ *
34
+ * 🔴 为什么不是 `LIKE '<前缀>%'`:scope 的 tenant 段是**百分号编码**的(`user%3Aweb-demo`),而 `%` 在
35
+ * LIKE 里是通配符 —— 不逐字符转义就会误命中别的租户的盘(**跨租户误迁**,不是性能问题)。`LEFT(col, n) = ?`
36
+ * 是纯等值比较,没有元字符面,两方言同形。
37
+ *
38
+ * 参数序:`[userScope, projHead.length, projHead, userProjHead.length, userProjHead]`。
39
+ */
40
+ export function buildMemoryScopeScanSql(dialect, table) {
41
+ return (`SELECT DISTINCT scope FROM ${table}` +
42
+ ` WHERE scope = ${ph(dialect, 1)}` +
43
+ ` OR LEFT(scope, ${ph(dialect, 2)}) = ${ph(dialect, 3)}` +
44
+ ` OR LEFT(scope, ${ph(dialect, 4)}) = ${ph(dialect, 5)}`);
45
+ }
46
+ /**
47
+ * 残留计数:某条腿上**仍挂在旧身份名下**的行数。参数序:`[fromValue]`。
48
+ *
49
+ * 用途见 wire.ts 的 `current.residualSourceRows`:收编不给普通写者上栅栏(183 I1 = 运维前提「引擎先停」),
50
+ * 所以「终态之后旧身份下又长出行」是一个真实可能的形 —— 这条查询把它变成可见读数,而不是靠祈祷。
51
+ */
52
+ export function buildResidualCountSql(dialect, table, matchColumns) {
53
+ // 🔴 **一张表一条 COUNT**,谓词是该表全部身份轴的**并**(codex R3-F4):逐腿各数一次会把一条
54
+ // 两根轴都还带旧身份的行数成 2(background_agent 甚至能数成 3),而字段名承诺的是「行数」。
55
+ const where = matchColumns.map((c, i) => `${c} = ${ph(dialect, i + 1)}`).join(" OR ");
56
+ return `SELECT COUNT(*) AS n FROM ${table} WHERE ${where}`;
57
+ }
58
+ /**
59
+ * `blob-rewrite` 腿的扫描:按**旧**身份值把行连同 JSON 载荷读出来。
60
+ * 匹配面 = `matchColumn` 与 `blob.alsoMatch` 的**并**(同表两根轴都可能带旧值)。
61
+ * 参数序:`[fromValue × (1 + alsoMatch.length)]`。
62
+ */
63
+ export function buildBlobScanSql(dialect, leg) {
64
+ const blob = leg.blob;
65
+ if (blob === undefined)
66
+ throw new Error(`buildBlobScanSql: leg ${leg.leg} has no blob spec`);
67
+ const cols = [...blob.pk, blob.revColumn, blob.column].join(", ");
68
+ // 🔴 匹配面同时收**旧值与新值**两个桶(codex R2-F1 的可见性半):投影列已经迁了、载荷还停在旧身份的
69
+ // 那种「劈开」行(列腿与载荷腿之间插进来的写、或上一轮扫描漏掉的行),只按旧值找是**永远找不到**的
70
+ // ——而那正是最危险的一类残留:黑盒上看起来已迁移。载荷里真带旧身份才会被改写,所以多扫一个桶零副作用。
71
+ const cells = [leg.matchColumn, ...(blob.alsoMatch ?? [])];
72
+ const match = [...cells.map((c, i) => `${c} = ${ph(dialect, i + 1)}`), ...cells.map((c, i) => `${c} = ${ph(dialect, cells.length + i + 1)}`)].join(" OR ");
73
+ return `SELECT ${cols} FROM ${leg.table} WHERE ${match}`;
74
+ }
75
+ /**
76
+ * `blob-rewrite` 腿的逐行回写。参数序:`[新载荷文本, ...pk 值]`。
77
+ *
78
+ * 幂等性:重跑时载荷里的身份字段已是新值 ⇒ 调用方算出的新载荷与库里逐字节相同 ⇒ 语义无操作
79
+ * (行数可能仍报 1,故回执的行数按「**内容真变了**才计」在调用侧收窄)。
80
+ */
81
+ export function buildBlobWriteSql(dialect, leg) {
82
+ const blob = leg.blob;
83
+ if (blob === undefined)
84
+ throw new Error(`buildBlobWriteSql: leg ${leg.leg} has no blob spec`);
85
+ const where = blob.pk.map((c, i) => `${c} = ${ph(dialect, i + 2)}`).join(" AND ");
86
+ // 🔴 `AND rev = ?` = 乐观并发守卫(codex R2-F1):这三张表的正常写路径都用 rev 做 CAS,收编不许绕过。
87
+ // 读到的 rev 与写时不符 ⇒ `affected === 0` ⇒ 调用方响亮抛、整事务回滚,而不是把别人刚写的载荷盖掉。
88
+ // 🔴 `SET rev = rev + 1` 同样是承重的(codex R3-F1):只**读**不**推**这把守卫,一个在收编写入**之前**
89
+ // 就读到 rev=N 的正常写者,在收编提交之后仍然能以 rev=N 的 CAS 写成功,把刚迁好的载荷原样盖回旧身份
90
+ // —— 收编那时已经报 adopted。推进 rev 才让那些「站在过去」的写者按 OCC 的本意输掉并重试。
91
+ return `UPDATE ${leg.table} SET ${blob.column} = ${ph(dialect, 1)}, ${blob.revColumn} = ${blob.revColumn} + 1 WHERE ${where} AND ${blob.revColumn} = ${ph(dialect, blob.pk.length + 2)}`;
92
+ }
93
+ /** session_policy 重键腿的扫描:拿到 from 侧每一行的 `(policy_key, session_id)`。参数序:`[fromPrincipal]`。 */
94
+ export function buildSessionPolicyScanSql(dialect) {
95
+ return `SELECT policy_key, session_id FROM session_policy WHERE principal = ${ph(dialect, 1)}`;
96
+ }
97
+ /**
98
+ * session_policy 的逐行重键改写。`policy_key = sha256(JSON.stringify([sessionId, principal ?? null]))`,
99
+ * 身份在**主键的 hash 原像**里 ⇒ 等值 UPDATE 够不着,必须读出来重算再改写。
100
+ * 参数序:`[newPolicyKey, toPrincipal, oldPolicyKey]`。
101
+ */
102
+ export function buildSessionPolicyRewriteSql(dialect) {
103
+ return (`UPDATE session_policy SET policy_key = ${ph(dialect, 1)}, principal = ${ph(dialect, 2)}` +
104
+ ` WHERE policy_key = ${ph(dialect, 3)}`);
105
+ }
106
+ //# sourceMappingURL=sql.js.map
@@ -0,0 +1,250 @@
1
+ /**
2
+ * design/183 §6 —— 收编回执的**唯一包络**(form b:纯身份重绑,数据已在 SQL、承运腿为零)。
3
+ *
4
+ * 🔴 为什么 schema 在这里而不是散在路由里(clay 最高宪法「边界必 schema 禁裸 as-cast」):这份包络是
5
+ * **跨仓 wire 契约** —— 下游(cli / web-admin / 围栏 E2E)要按字段名逐字钉围栏。请求体在入口 zod 解析,
6
+ * 响应体从这里的类型铸出,于是「路由改了字段名而围栏不知道」是编译红而不是运行期静默漂移。
7
+ *
8
+ * ── 与 183 §6 的**逐条差异**(如实登记,不假装同形)────────────────────────────────────────────────
9
+ * ① `AdoptionReport.credentials` **缺席**。183 的那一格服务的是 form a「云铸新身份 ⇒ 凭据领取位」;
10
+ * form b 的 `toPrincipal` 是**运维在请求体里点名的既存身份**,本车不铸任何凭据 ⇒ 给一个 `ref` 就是
11
+ * 凭空发明一个兑取不到的 claim(而 183 §4.3 恰恰要求该 ref 是 durable 可重复兑取的)。缺席是诚实,
12
+ * 假字段是谎。form a 承运车落地时由那台车补这一格。
13
+ * ② `AdoptionReceipt.current.quarantined` **恒 0**。隔离位是承运腿(对端拒收行)的概念,form b 零承运
14
+ * ⇒ 结构位在场、读数恒 0。保留字段是为了 form a 上车时包络不变形(下游围栏不用改)。
15
+ * ③ `notMigratedByDesign` 是 **v2 小票新增**的机读位(183 只在散文里说「按设计不迁」)。成员标识符
16
+ * ({@link NotMigratedFace})与字段名一样是**冻结的机读词表**,下游按它写正向断言(§10.5:把「按
17
+ * 设计不迁」与「漏了没迁」在黑盒上分开)。加成员=加行为面,减成员=下游围栏红。
18
+ *
19
+ * `immutableReport` 逐字节恒同 = 幂等判据(183 §8):它在 phase 6 一次性落库,此后任何重跑原样回放,
20
+ * **绝不重算**。`current` 反过来每次现算(配置账未见证项、隔离计数),两半各钉各的(§10.6)。
21
+ */
22
+ import { z } from "zod";
23
+ /** 收编来源(form b 只有具名 principal 一形;`local-owner` 是 form a 的形,本车不产出也不接受)。 */
24
+ export declare const AdoptionSourceSchema: z.ZodObject<{
25
+ kind: z.ZodLiteral<"principal">;
26
+ principal: z.ZodString;
27
+ }, z.core.$strict>;
28
+ export type AdoptionSource = z.infer<typeof AdoptionSourceSchema>;
29
+ /** per-store 腿的动作词(183 §6 闭集;form b 只会产出前两个 + `none`)。 */
30
+ export declare const AdoptionLegActionSchema: z.ZodEnum<{
31
+ reset: "reset";
32
+ none: "none";
33
+ "bucket-rebind": "bucket-rebind";
34
+ "row-rewrite": "row-rewrite";
35
+ carried: "carried";
36
+ }>;
37
+ export type AdoptionLegAction = z.infer<typeof AdoptionLegActionSchema>;
38
+ export declare const AdoptionLegSchema: z.ZodObject<{
39
+ store: z.ZodString;
40
+ action: z.ZodEnum<{
41
+ reset: "reset";
42
+ none: "none";
43
+ "bucket-rebind": "bucket-rebind";
44
+ "row-rewrite": "row-rewrite";
45
+ carried: "carried";
46
+ }>;
47
+ rows: z.ZodOptional<z.ZodNumber>;
48
+ quarantined: z.ZodOptional<z.ZodNumber>;
49
+ }, z.core.$strict>;
50
+ export type AdoptionLeg = z.infer<typeof AdoptionLegSchema>;
51
+ /**
52
+ * 受影响部署配置清单(183 §6 表)。`migrated` 对 server 半场**恒 false**——这些配置全在别的部署单元
53
+ * (BFF / cli / env / core 声明位),收编动作**够不到**它们,而 183 §3.3 的明令是「改不到的配置,不假装
54
+ * 改了」。清单在场 + `migrated:false` + 配置账不清 = 下游围栏「配置不迁如实红」的凭据(§10.4)。
55
+ */
56
+ export declare const AdoptionConfigEntrySchema: z.ZodObject<{
57
+ deployment: z.ZodString;
58
+ key: z.ZodString;
59
+ requiredValue: z.ZodString;
60
+ migrated: z.ZodBoolean;
61
+ ack: z.ZodBoolean;
62
+ witnessedAtMs: z.ZodNullable<z.ZodNumber>;
63
+ }, z.core.$strict>;
64
+ export type AdoptionConfigEntry = z.infer<typeof AdoptionConfigEntrySchema>;
65
+ /**
66
+ * **机读词表**:server 侧按设计不迁的面,逐项挂 183 的裁决号。
67
+ *
68
+ * 🔴 这是**冻结的闭集**(下游按成员名写正向断言)。改名/删员 = wire 破坏;新增 = 新行为面,要先过三问。
69
+ * 判据形见 183 §10.5:「按设计不迁」与「漏了没迁」在黑盒上必须可分,所以它是**正向声明**而不是沉默。
70
+ */
71
+ export declare const NOT_MIGRATED_FACES: readonly ["usage-window", "cost-quota", "rate-limit", "approval-exemption-grantor", "approval-ask-decision-actor", "image-bake-requested-by"];
72
+ export type NotMigratedFace = (typeof NOT_MIGRATED_FACES)[number];
73
+ export declare const AdoptionNotMigratedSchema: z.ZodObject<{
74
+ face: z.ZodEnum<{
75
+ "usage-window": "usage-window";
76
+ "cost-quota": "cost-quota";
77
+ "rate-limit": "rate-limit";
78
+ "approval-exemption-grantor": "approval-exemption-grantor";
79
+ "approval-ask-decision-actor": "approval-ask-decision-actor";
80
+ "image-bake-requested-by": "image-bake-requested-by";
81
+ }>;
82
+ ruling: z.ZodEnum<{
83
+ D1: "D1";
84
+ D2: "D2";
85
+ }>;
86
+ reason: z.ZodString;
87
+ }, z.core.$strict>;
88
+ export type AdoptionNotMigrated = z.infer<typeof AdoptionNotMigratedSchema>;
89
+ /** 终态快照(**不可变史实**,phase 6 一次落库,逐字节恒同 —— 183 I4 + §8 幂等判据)。 */
90
+ export declare const AdoptionReportSchema: z.ZodObject<{
91
+ adoptionId: z.ZodString;
92
+ form: z.ZodLiteral<"b">;
93
+ from: z.ZodObject<{
94
+ kind: z.ZodLiteral<"principal">;
95
+ principal: z.ZodString;
96
+ }, z.core.$strict>;
97
+ toPrincipal: z.ZodString;
98
+ atMs: z.ZodNumber;
99
+ affectedDeploymentConfigs: z.ZodArray<z.ZodObject<{
100
+ deployment: z.ZodString;
101
+ key: z.ZodString;
102
+ requiredValue: z.ZodString;
103
+ migrated: z.ZodBoolean;
104
+ ack: z.ZodBoolean;
105
+ witnessedAtMs: z.ZodNullable<z.ZodNumber>;
106
+ }, z.core.$strict>>;
107
+ legs: z.ZodArray<z.ZodObject<{
108
+ store: z.ZodString;
109
+ action: z.ZodEnum<{
110
+ reset: "reset";
111
+ none: "none";
112
+ "bucket-rebind": "bucket-rebind";
113
+ "row-rewrite": "row-rewrite";
114
+ carried: "carried";
115
+ }>;
116
+ rows: z.ZodOptional<z.ZodNumber>;
117
+ quarantined: z.ZodOptional<z.ZodNumber>;
118
+ }, z.core.$strict>>;
119
+ notMigratedByDesign: z.ZodArray<z.ZodObject<{
120
+ face: z.ZodEnum<{
121
+ "usage-window": "usage-window";
122
+ "cost-quota": "cost-quota";
123
+ "rate-limit": "rate-limit";
124
+ "approval-exemption-grantor": "approval-exemption-grantor";
125
+ "approval-ask-decision-actor": "approval-ask-decision-actor";
126
+ "image-bake-requested-by": "image-bake-requested-by";
127
+ }>;
128
+ ruling: z.ZodEnum<{
129
+ D1: "D1";
130
+ D2: "D2";
131
+ }>;
132
+ reason: z.ZodString;
133
+ }, z.core.$strict>>;
134
+ }, z.core.$strict>;
135
+ export type AdoptionReport = z.infer<typeof AdoptionReportSchema>;
136
+ /**
137
+ * 唯一回执包络。**首次成功与一切幂等重跑同形**(183 §6 r7);别处不得另造第二种回执。
138
+ *
139
+ * `status`:`adopted` = 终态在;`stalled` = 在飞(锁被别的副本持着 / 崩溃后尚未续完)。
140
+ * **`rejected` 不在这个闭集里**:被拒的收编从来不产出回执,POST 与 GET 一律以 typed 409 应答
141
+ * (`adoption.destination_conflict` / `adoption.source_already_bound`),否则「拒」与「成」共用一个 200
142
+ * 包络,消费端要靠读字段猜——那正是 183 §7.3「拒绝可判别」要消灭的形。
143
+ */
144
+ export declare const AdoptionReceiptSchema: z.ZodObject<{
145
+ status: z.ZodEnum<{
146
+ stalled: "stalled";
147
+ adopted: "adopted";
148
+ }>;
149
+ immutableReport: z.ZodObject<{
150
+ adoptionId: z.ZodString;
151
+ form: z.ZodLiteral<"b">;
152
+ from: z.ZodObject<{
153
+ kind: z.ZodLiteral<"principal">;
154
+ principal: z.ZodString;
155
+ }, z.core.$strict>;
156
+ toPrincipal: z.ZodString;
157
+ atMs: z.ZodNumber;
158
+ affectedDeploymentConfigs: z.ZodArray<z.ZodObject<{
159
+ deployment: z.ZodString;
160
+ key: z.ZodString;
161
+ requiredValue: z.ZodString;
162
+ migrated: z.ZodBoolean;
163
+ ack: z.ZodBoolean;
164
+ witnessedAtMs: z.ZodNullable<z.ZodNumber>;
165
+ }, z.core.$strict>>;
166
+ legs: z.ZodArray<z.ZodObject<{
167
+ store: z.ZodString;
168
+ action: z.ZodEnum<{
169
+ reset: "reset";
170
+ none: "none";
171
+ "bucket-rebind": "bucket-rebind";
172
+ "row-rewrite": "row-rewrite";
173
+ carried: "carried";
174
+ }>;
175
+ rows: z.ZodOptional<z.ZodNumber>;
176
+ quarantined: z.ZodOptional<z.ZodNumber>;
177
+ }, z.core.$strict>>;
178
+ notMigratedByDesign: z.ZodArray<z.ZodObject<{
179
+ face: z.ZodEnum<{
180
+ "usage-window": "usage-window";
181
+ "cost-quota": "cost-quota";
182
+ "rate-limit": "rate-limit";
183
+ "approval-exemption-grantor": "approval-exemption-grantor";
184
+ "approval-ask-decision-actor": "approval-ask-decision-actor";
185
+ "image-bake-requested-by": "image-bake-requested-by";
186
+ }>;
187
+ ruling: z.ZodEnum<{
188
+ D1: "D1";
189
+ D2: "D2";
190
+ }>;
191
+ reason: z.ZodString;
192
+ }, z.core.$strict>>;
193
+ }, z.core.$strict>;
194
+ current: z.ZodObject<{
195
+ outstandingConfigs: z.ZodArray<z.ZodString>;
196
+ quarantined: z.ZodNumber;
197
+ residualSourceRows: z.ZodNumber;
198
+ }, z.core.$strict>;
199
+ }, z.core.$strict>;
200
+ export type AdoptionReceipt = z.infer<typeof AdoptionReceiptSchema>;
201
+ /**
202
+ * 库里那三个 JSON 文本列(`legs` / `configs` / `reject_detail`)读回内域时的 schema。
203
+ *
204
+ * 🔴 为什么不是「解析完直接裸铸形」(clay 最高宪法 §2 + `boundary-cast-gate`):DB 行与 HTTP body 同属
205
+ * **进程外数据**。裸 `as` 是「库里的字节直接冒充内域类型」—— 一次手改库/一次旧版本残留行,坏形就会带着
206
+ * 一个说谎的类型annotation 流进回执,而回执是下游围栏的判据源。
207
+ */
208
+ export declare const AdoptionLegsSchema: z.ZodArray<z.ZodObject<{
209
+ store: z.ZodString;
210
+ action: z.ZodEnum<{
211
+ reset: "reset";
212
+ none: "none";
213
+ "bucket-rebind": "bucket-rebind";
214
+ "row-rewrite": "row-rewrite";
215
+ carried: "carried";
216
+ }>;
217
+ rows: z.ZodOptional<z.ZodNumber>;
218
+ quarantined: z.ZodOptional<z.ZodNumber>;
219
+ }, z.core.$strict>>;
220
+ export declare const AdoptionConfigsSchema: z.ZodArray<z.ZodObject<{
221
+ deployment: z.ZodString;
222
+ key: z.ZodString;
223
+ requiredValue: z.ZodString;
224
+ migrated: z.ZodBoolean;
225
+ ack: z.ZodBoolean;
226
+ witnessedAtMs: z.ZodNullable<z.ZodNumber>;
227
+ }, z.core.$strict>>;
228
+ export declare const AdoptionRejectDetailSchema: z.ZodObject<{
229
+ detail: z.ZodString;
230
+ conflicts: z.ZodArray<z.ZodObject<{
231
+ leg: z.ZodString;
232
+ table: z.ZodString;
233
+ overlapping: z.ZodNumber;
234
+ }, z.core.$strict>>;
235
+ }, z.core.$strict>;
236
+ export type AdoptionRejectDetail = z.infer<typeof AdoptionRejectDetailSchema>;
237
+ /**
238
+ * 拒绝码的**闭集**(与附录 A 的 `adoption.*` 族逐字同名)。住在 wire 契约里而不是实现里,因为落库的
239
+ * `adoption_log.reject_code` 与 wire 上的 `errorCode` 必须是**同一个词表** —— 两处各写一份字符串字面量,
240
+ * 就是「库里记了一个 wire 上不存在的码」这类漂移的入口。路由的 switch 对它穷举:加成员即编译红。
241
+ */
242
+ export declare const ADOPTION_REJECT_CODES: readonly ["adoption.destination_conflict", "adoption.destination_unrepresentable"];
243
+ export type AdoptionRejectCode = (typeof ADOPTION_REJECT_CODES)[number];
244
+ /** `POST /v1/adoption` 的请求体。`.strict()` = 写面纪律:多余键是调用方错误,当场 400(不静默吞)。 */
245
+ export declare const AdoptionRequestSchema: z.ZodObject<{
246
+ fromPrincipal: z.ZodString;
247
+ toPrincipal: z.ZodString;
248
+ }, z.core.$strict>;
249
+ export type AdoptionRequest = z.infer<typeof AdoptionRequestSchema>;
250
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1,153 @@
1
+ /**
2
+ * design/183 §6 —— 收编回执的**唯一包络**(form b:纯身份重绑,数据已在 SQL、承运腿为零)。
3
+ *
4
+ * 🔴 为什么 schema 在这里而不是散在路由里(clay 最高宪法「边界必 schema 禁裸 as-cast」):这份包络是
5
+ * **跨仓 wire 契约** —— 下游(cli / web-admin / 围栏 E2E)要按字段名逐字钉围栏。请求体在入口 zod 解析,
6
+ * 响应体从这里的类型铸出,于是「路由改了字段名而围栏不知道」是编译红而不是运行期静默漂移。
7
+ *
8
+ * ── 与 183 §6 的**逐条差异**(如实登记,不假装同形)────────────────────────────────────────────────
9
+ * ① `AdoptionReport.credentials` **缺席**。183 的那一格服务的是 form a「云铸新身份 ⇒ 凭据领取位」;
10
+ * form b 的 `toPrincipal` 是**运维在请求体里点名的既存身份**,本车不铸任何凭据 ⇒ 给一个 `ref` 就是
11
+ * 凭空发明一个兑取不到的 claim(而 183 §4.3 恰恰要求该 ref 是 durable 可重复兑取的)。缺席是诚实,
12
+ * 假字段是谎。form a 承运车落地时由那台车补这一格。
13
+ * ② `AdoptionReceipt.current.quarantined` **恒 0**。隔离位是承运腿(对端拒收行)的概念,form b 零承运
14
+ * ⇒ 结构位在场、读数恒 0。保留字段是为了 form a 上车时包络不变形(下游围栏不用改)。
15
+ * ③ `notMigratedByDesign` 是 **v2 小票新增**的机读位(183 只在散文里说「按设计不迁」)。成员标识符
16
+ * ({@link NotMigratedFace})与字段名一样是**冻结的机读词表**,下游按它写正向断言(§10.5:把「按
17
+ * 设计不迁」与「漏了没迁」在黑盒上分开)。加成员=加行为面,减成员=下游围栏红。
18
+ *
19
+ * `immutableReport` 逐字节恒同 = 幂等判据(183 §8):它在 phase 6 一次性落库,此后任何重跑原样回放,
20
+ * **绝不重算**。`current` 反过来每次现算(配置账未见证项、隔离计数),两半各钉各的(§10.6)。
21
+ */
22
+ import { z } from "zod";
23
+ /** 收编来源(form b 只有具名 principal 一形;`local-owner` 是 form a 的形,本车不产出也不接受)。 */
24
+ export const AdoptionSourceSchema = z.object({ kind: z.literal("principal"), principal: z.string().min(1) }).strict();
25
+ /** per-store 腿的动作词(183 §6 闭集;form b 只会产出前两个 + `none`)。 */
26
+ export const AdoptionLegActionSchema = z.enum(["bucket-rebind", "row-rewrite", "carried", "none", "reset"]);
27
+ export const AdoptionLegSchema = z
28
+ .object({
29
+ /** 腿的稳定标识 = `<表名>#<身份轴列>`(下游按它对账;表名/列名都是本仓 schema 的真名)。 */
30
+ store: z.string().min(1),
31
+ action: AdoptionLegActionSchema,
32
+ /** 本次真改了几行。**重跑恒 0**,而 `immutableReport` 里冻的是**首次**的读数(史实)。 */
33
+ rows: z.number().int().nonnegative().optional(),
34
+ quarantined: z.number().int().nonnegative().optional(),
35
+ })
36
+ .strict();
37
+ /**
38
+ * 受影响部署配置清单(183 §6 表)。`migrated` 对 server 半场**恒 false**——这些配置全在别的部署单元
39
+ * (BFF / cli / env / core 声明位),收编动作**够不到**它们,而 183 §3.3 的明令是「改不到的配置,不假装
40
+ * 改了」。清单在场 + `migrated:false` + 配置账不清 = 下游围栏「配置不迁如实红」的凭据(§10.4)。
41
+ */
42
+ export const AdoptionConfigEntrySchema = z
43
+ .object({
44
+ deployment: z.string().min(1),
45
+ key: z.string().min(1),
46
+ requiredValue: z.string(),
47
+ migrated: z.boolean(),
48
+ /** 运维表态位(只作记录,**不清账**——183 r3 按 codex r2-F6:手写布尔可以把账清成假绿)。 */
49
+ ack: z.boolean(),
50
+ /** 消费端运行时真读回 requiredValue 的 typed 回执时刻。**null = 未见证 = 未清账**。 */
51
+ witnessedAtMs: z.number().int().nonnegative().nullable(),
52
+ })
53
+ .strict();
54
+ /**
55
+ * **机读词表**:server 侧按设计不迁的面,逐项挂 183 的裁决号。
56
+ *
57
+ * 🔴 这是**冻结的闭集**(下游按成员名写正向断言)。改名/删员 = wire 破坏;新增 = 新行为面,要先过三问。
58
+ * 判据形见 183 §10.5:「按设计不迁」与「漏了没迁」在黑盒上必须可分,所以它是**正向声明**而不是沉默。
59
+ */
60
+ export const NOT_MIGRATED_FACES = [
61
+ "usage-window",
62
+ "cost-quota",
63
+ "rate-limit",
64
+ "approval-exemption-grantor",
65
+ "approval-ask-decision-actor",
66
+ "image-bake-requested-by",
67
+ ];
68
+ export const AdoptionNotMigratedSchema = z
69
+ .object({
70
+ face: z.enum(NOT_MIGRATED_FACES),
71
+ /** 183 §12 的裁决号(`D1` = 配额窗清零重计;`D2` = 史实字段不改写)。 */
72
+ ruling: z.enum(["D1", "D2"]),
73
+ reason: z.string().min(1),
74
+ })
75
+ .strict();
76
+ /** 终态快照(**不可变史实**,phase 6 一次落库,逐字节恒同 —— 183 I4 + §8 幂等判据)。 */
77
+ export const AdoptionReportSchema = z
78
+ .object({
79
+ adoptionId: z.string().min(1),
80
+ /** 本车只做 form b;`form` 显式在场是为了 form a 上车后回执可分辨(下游不必按缺席位推断)。 */
81
+ form: z.literal("b"),
82
+ from: AdoptionSourceSchema,
83
+ toPrincipal: z.string().min(1),
84
+ atMs: z.number().int().nonnegative(),
85
+ affectedDeploymentConfigs: z.array(AdoptionConfigEntrySchema),
86
+ legs: z.array(AdoptionLegSchema),
87
+ notMigratedByDesign: z.array(AdoptionNotMigratedSchema),
88
+ })
89
+ .strict();
90
+ /**
91
+ * 唯一回执包络。**首次成功与一切幂等重跑同形**(183 §6 r7);别处不得另造第二种回执。
92
+ *
93
+ * `status`:`adopted` = 终态在;`stalled` = 在飞(锁被别的副本持着 / 崩溃后尚未续完)。
94
+ * **`rejected` 不在这个闭集里**:被拒的收编从来不产出回执,POST 与 GET 一律以 typed 409 应答
95
+ * (`adoption.destination_conflict` / `adoption.source_already_bound`),否则「拒」与「成」共用一个 200
96
+ * 包络,消费端要靠读字段猜——那正是 183 §7.3「拒绝可判别」要消灭的形。
97
+ */
98
+ export const AdoptionReceiptSchema = z
99
+ .object({
100
+ status: z.enum(["adopted", "stalled"]),
101
+ immutableReport: AdoptionReportSchema,
102
+ current: z
103
+ .object({
104
+ /** 配置账里 `witnessedAtMs === null` 的项(`<deployment>:<key>`)。空 = 账已清。 */
105
+ outstandingConfigs: z.array(z.string()),
106
+ /** form b 恒 0(零承运 ⇒ 无拒收行);字段在场见本文件顶注差异②。 */
107
+ quarantined: z.number().int().nonnegative(),
108
+ /**
109
+ * **仍挂在旧身份名下的行数**(每次现算,跨全部迁移腿求和)。
110
+ *
111
+ * 🔴 为什么这一格是承重的(codex 对抗复审 R1-F1,采纳其可检测半):收编**不**给普通写者上栅栏
112
+ * ——183 I1 的口径是「引擎必须先停」,那是**运维前提**,不是机器保证。于是一个还在按旧身份注入的
113
+ * 调用方(配置未随迁 ⇒ `outstandingConfigs` 非空)会在终态之后继续往旧身份下写行,而终态 + 幂等
114
+ * 短路会让回执一直说「adopted」。
115
+ *
116
+ * 这一格把那件事**变成可见的读数**:`>0` = 这个部署仍在往旧身份写,配置随迁没做完。
117
+ * 每次 **POST**(写动词)会顺带把这些迟到行扫进新身份并重报此数;**GET** 只读不扫。
118
+ * `immutableReport` 是史实、不受影响 —— 史实与现势各住各的字段(183 §6)。
119
+ */
120
+ residualSourceRows: z.number().int().nonnegative(),
121
+ })
122
+ .strict(),
123
+ })
124
+ .strict();
125
+ /**
126
+ * 库里那三个 JSON 文本列(`legs` / `configs` / `reject_detail`)读回内域时的 schema。
127
+ *
128
+ * 🔴 为什么不是「解析完直接裸铸形」(clay 最高宪法 §2 + `boundary-cast-gate`):DB 行与 HTTP body 同属
129
+ * **进程外数据**。裸 `as` 是「库里的字节直接冒充内域类型」—— 一次手改库/一次旧版本残留行,坏形就会带着
130
+ * 一个说谎的类型annotation 流进回执,而回执是下游围栏的判据源。
131
+ */
132
+ export const AdoptionLegsSchema = z.array(AdoptionLegSchema);
133
+ export const AdoptionConfigsSchema = z.array(AdoptionConfigEntrySchema);
134
+ export const AdoptionRejectDetailSchema = z
135
+ .object({
136
+ detail: z.string(),
137
+ conflicts: z.array(z.object({ leg: z.string(), table: z.string(), overlapping: z.number().int().nonnegative() }).strict()),
138
+ })
139
+ .strict();
140
+ /**
141
+ * 拒绝码的**闭集**(与附录 A 的 `adoption.*` 族逐字同名)。住在 wire 契约里而不是实现里,因为落库的
142
+ * `adoption_log.reject_code` 与 wire 上的 `errorCode` 必须是**同一个词表** —— 两处各写一份字符串字面量,
143
+ * 就是「库里记了一个 wire 上不存在的码」这类漂移的入口。路由的 switch 对它穷举:加成员即编译红。
144
+ */
145
+ export const ADOPTION_REJECT_CODES = ["adoption.destination_conflict", "adoption.destination_unrepresentable"];
146
+ /** `POST /v1/adoption` 的请求体。`.strict()` = 写面纪律:多余键是调用方错误,当场 400(不静默吞)。 */
147
+ export const AdoptionRequestSchema = z
148
+ .object({
149
+ fromPrincipal: z.string().min(1).max(190),
150
+ toPrincipal: z.string().min(1).max(190),
151
+ })
152
+ .strict();
153
+ //# sourceMappingURL=wire.js.map
@@ -50,6 +50,14 @@ export declare const ApprovalCardSchema: z.ZodObject<{
50
50
  requiresRealApproval: z.ZodBoolean;
51
51
  }, z.core.$strict>;
52
52
  governanceForced: z.ZodOptional<z.ZodLiteral<true>>;
53
+ ruleSuggestions: z.ZodOptional<z.ZodArray<z.ZodObject<{
54
+ rule: z.ZodString;
55
+ match: z.ZodEnum<{
56
+ exact: "exact";
57
+ prefix: "prefix";
58
+ }>;
59
+ command: z.ZodString;
60
+ }, z.core.$strict>>>;
53
61
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
54
62
  sourceTaskId: z.ZodOptional<z.ZodString>;
55
63
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -81,6 +89,14 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
81
89
  requiresRealApproval: z.ZodBoolean;
82
90
  }, z.core.$strict>;
83
91
  governanceForced: z.ZodOptional<z.ZodLiteral<true>>;
92
+ ruleSuggestions: z.ZodOptional<z.ZodArray<z.ZodObject<{
93
+ rule: z.ZodString;
94
+ match: z.ZodEnum<{
95
+ exact: "exact";
96
+ prefix: "prefix";
97
+ }>;
98
+ command: z.ZodString;
99
+ }, z.core.$strict>>>;
84
100
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
85
101
  sourceTaskId: z.ZodOptional<z.ZodString>;
86
102
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -116,6 +132,14 @@ export interface ApprovalCardSource {
116
132
  /** [2942]/[2943]:治理来源标 —— 与 wire 帧**同一份素材**(`ToolApprovalFrame` 结构上满足本接口),
117
133
  * 于是 live 帧 / `card_json` / 重放帧三面同源,不是三处各判一遍。 */
118
134
  governanceForced?: true;
135
+ /** #154 车二:引擎铸的规则候选(**只在规则店装配时**由发帧点填;语义与在场性契约见
136
+ * `ApprovalCardSchema.ruleSuggestions`)。与 wire 帧**同一份素材** ⇒ live 帧 / `card_json` / 重放帧
137
+ * 三面同源。 */
138
+ ruleSuggestions?: ReadonlyArray<{
139
+ rule: string;
140
+ match: "exact" | "prefix";
141
+ command: string;
142
+ }>;
119
143
  fromSubagent?: true;
120
144
  sourceTaskId?: string;
121
145
  /** 已 redactSecrets。 */
@@ -21,6 +21,7 @@
21
21
  * 没有方法、没有捕获的行为。
22
22
  */
23
23
  import { z } from "zod";
24
+ import { MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
24
25
  /** 模型自由文本(`sourceAgentName` / `delegation.agentName`)的限长(设计稿 §6.2)。设计 §3.1 把
25
26
  * 「限长 + 脱敏」写成 **server 新增责任**(引擎无此层):spawning model 挑的名字是自由文本,
26
27
  * 不能指望壳去截——一条 100KB 的 agentName 在 server 侧就该被拒,而不是变成一张撑爆呈卡面的卡。 */
@@ -78,6 +79,32 @@ export const ApprovalCardSchema = z
78
79
  * park(fail-safe),回滚窗结束即自愈。
79
80
  */
80
81
  governanceForced: z.literal(true).optional(),
82
+ /**
83
+ * #154 车二(core 5.18.0 design/179):引擎为这次 ask 铸的**规则候选**(`Bash(git status)` 精确 /
84
+ * `Bash(git status:*)` 词界前缀)—— 呈卡面据它渲「不再询问」。
85
+ *
86
+ * 🔴 **在场性即承诺**:这一格只在**规则店真装配**(`RunnerDeps.permissionRuleStore` 在场 ⇔ manifest
87
+ * 的 `permissionRules.storeWired: true`)时投影。店缺席仍投 = 给客户端渲一格它按下去无处可兑的
88
+ * 「不再询问」,那是 wire 谎言,比缺席更坏(判据逐字见 `core-keyset-guard.ts` ④ 面本键那一段)。
89
+ * 缺席 ⇒ 这只 ask 没有可兑的候选(命令不可匹配,或本部署没接店),两种都读作「本卡无此选项」。
90
+ * 值**逐字**取自 core(闭词表 `match` + 引擎铸的文本),server 不重铸、不重排。
91
+ *
92
+ * ⚠️ **兑付面的射程,如实说清**(codex 交叉复审 round1 [medium],验真后按「真实但已知」登记):
93
+ * 本键随卡进 `card_json`,于是重放帧也带它;而它的兑付通道**只有一条** —— 重放帧自己携带的
94
+ * `approvalId` 所指的那条 live 回决口(`POST /v1/tool-approvals/:id/respond` 的 `persistRule`)。
95
+ * 那条口是**进程内**的(pending map 随副本走),所以:同副本重连 ⇒ 兑得动;换副本/重启之后 ⇒ 那个
96
+ * `approvalId` 已经 404,人只能走 durable 回决口(`POST /v1/tasks/:id/asks/:askId/decision`),
97
+ * 而**那条口 v1 不带规则位**(它的 zod 体是 `.strict()`,多给一个 `persistRule` 是响亮 400,不是静默丢)。
98
+ * 这不是本键独有的射程:`approvalId` 本身就是同一个可用性信封(卡还能不能被这条通道决,取决于它)。
99
+ * ⇒ 处置 = 不缩键(缩了,同副本重连这条真实可用的路也一起没了),把「durable 回决腿的规则位」
100
+ * 登记为后续件(车4 域)。
101
+ */
102
+ ruleSuggestions: z
103
+ .array(z
104
+ .object({ rule: z.string().max(MAX_RULE_TEXT_CHARS), match: z.enum(["exact", "prefix"]), command: z.string().max(MAX_RULE_TEXT_CHARS) })
105
+ .strict())
106
+ .max(4)
107
+ .optional(),
81
108
  /** 委派出处(子代 ask 才在场;判别键 = `fromSubagent`,core RB-39②)。 */
82
109
  fromSubagent: z.literal(true).optional(),
83
110
  sourceTaskId: z.string().max(MAX_IDENT).optional(),
@@ -168,6 +195,11 @@ export function buildApprovalCard(source, req, requiresRealApproval) {
168
195
  },
169
196
  // [2942]/[2943]:只在为真时投影(`=== true` 严判:非布尔真值不得把一张普通卡染成治理卡)。
170
197
  ...(source.governanceForced === true ? { governanceForced: true } : {}),
198
+ // #154 车二:候选**逐字**投影(空数组 ⇒ 不投键 —— 「没有候选」与「本部署不供候选」在卡面上都读作
199
+ // 「本卡无此选项」,铸一个空数组只会让消费端多一条无意义的分支)。拷贝一份:卡不与调用方共享可变引用。
200
+ ...(source.ruleSuggestions !== undefined && source.ruleSuggestions.length > 0
201
+ ? { ruleSuggestions: source.ruleSuggestions.map((s) => ({ rule: s.rule, match: s.match, command: s.command })) }
202
+ : {}),
171
203
  ...(source.fromSubagent === true ? { fromSubagent: true } : {}),
172
204
  ...(sourceTaskId !== undefined ? { sourceTaskId } : {}),
173
205
  ...(sourceAgentName !== undefined ? { sourceAgentName } : {}),
@@ -0,0 +1,30 @@
1
+ import type { StoreBackend } from "../plugins/store-backend.js";
2
+ export interface AdoptionBootCtx {
3
+ backend: StoreBackend | undefined;
4
+ logger: {
5
+ info?(msg: string, meta?: unknown): void;
6
+ warn?(msg: string, meta?: unknown): void;
7
+ error?(msg: string, meta?: unknown): void;
8
+ };
9
+ now?: () => number;
10
+ }
11
+ export interface AdoptionBootReading {
12
+ /** 本次扫到的在飞行数(0 = 常态)。 */
13
+ scanned: number;
14
+ /** 推到终态的弧。 */
15
+ resumed: string[];
16
+ /** 推不动的弧(锁被别的副本持着,或还差一步)—— 留在库里,下一个副本/下一次读面继续推。 */
17
+ stalled: string[];
18
+ /** 续跑时撞上目的地冲突而落 rejected 终态的弧。 */
19
+ rejected: string[];
20
+ /** 推不动**且抛了异常**的弧(逐弧兜住 —— 一条坏行不许绑架其它弧的恢复)。 */
21
+ failed: string[];
22
+ /** 扫描本身失败(库不可达等)。**不吞** —— 消息进启动日志。 */
23
+ error?: string;
24
+ }
25
+ /**
26
+ * boot 期的在飞收编扫描。无 SQL 后端 ⇒ 零动作(收编面在那种部署上本来就 501)。
27
+ * 绝不抛:一次库抖动不该拒掉整台 worker 的启动,但**必须**留下响亮读数(`error` 位 + error 级日志)。
28
+ */
29
+ export declare function runAdoptionBootScan(ctx: AdoptionBootCtx): Promise<AdoptionBootReading>;
30
+ //# sourceMappingURL=adoption.d.ts.map