@sema-agent/settings-schema 1.1.0 → 1.3.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,175 @@
6
6
  > `@sema-agent/registry-core`(更早还曾是 `@sema-ai/registry-core`)。本档 `0.19.0` 以下各节里的
7
7
  > 包名是**历史记录**,如实保留不追改。
8
8
 
9
+ ## 1.3.0 — hooks 域加员 `DirectoryAdded`(CC 2.1.219 新事件;`HooksConfig` 拒键病形修复)
10
+
11
+ **`HOOK_EVENTS` 30 → 31:`POST_198_HOOK_EVENTS` **尾插** `DirectoryAdded`(1.2.0 三员声明序逐字节不动;重排 = BREAKING,
12
+ 免裁)。additive:既有 30 员的 matcher 字段 / owner / 输入形 / `hookSpecificOutput` 臂一字不动(FROZEN 1.2.0 全表字面量夹具钉,
13
+ `test/hooks-directory-added-1.3.0.test.ts`,字面量独立誊自 bfbd140、非从现表派生——1.1.0/1.2.0 safety-merge-spec 夹具同律)。
14
+ 版号 minor = 加词(1.2.0 口径)。**
15
+
16
+ ### 病形(修前)
17
+
18
+ `HooksConfig = z.record(HookEventName, …)` 键集**闭合**(设计如此:wire 上抓 typo)——用户 settings 里 `hooks.DirectoryAdded`
19
+ (壳自 CC 2.1.219 起真会点亮的事件)在 1.2.0 下**整条** hooks 配置被拒(zod `Invalid enum value` at key);同时 `HookInput`
20
+ union 无该臂、`HookEventName` 不收该词。壳认得、schema 不收——与 cli `hooksSchema218.test.ts` 建门帖所述「壳自己认得的
21
+ 输入形,壳自己的注册面不收」同形。
22
+
23
+ ### CC 真形(亲读镜像坐标,非按名推断)
24
+
25
+ 来源 = cli 树 `node_modules/@sema-agent/agent-types`(`@anthropic-ai/claude-agent-sdk@0.3.220` `sdk.d.ts` 的镜像,CC 2.1.220,
26
+ 与 0.3.219 逐字节同):
27
+
28
+ - `hooks.d.ts:582-586` `DirectoryAddedHookInput = BaseHookInput & { hook_event_name:'DirectoryAdded'; directory:string;
29
+ source:'slash_command'|'register_repo_root' }`,`@since CC 2.1.219`(:574-575:目录经 `/add-dir` 或 repo-root 注册加入会话
30
+ 工作集;cli `hooksConfigManager.ts:283-287` 文案:`directory` 为绝对路径)。
31
+ - `hooks.d.ts:638` / `runtime.d.ts:111`:CC 序位于 `FileChanged` 与 `MessageDisplay` 之间;本包**尾插**——声明序非规范,
32
+ 枚举当集合用(与 `MinTrust` 1.2.0 同律);夹具钉「1.2.0 三十员 = `HOOK_EVENTS` 逐字节前缀」。
33
+ - `hooks.d.ts:14-17`:上游**无** `hookSpecificOutput` 臂——本版不加臂,测试钉「缺席」(非遗漏)。
34
+ - **matcher 语义**:cli `src/utils/hooks.ts:1706-1760` `getMatchingHooks` 开关**无** `DirectoryAdded` 臂 → 落 `default` →
35
+ `matchQuery` 空 → 所有 matcher 组皆触发;`hooksConfigManager.ts` 该员亦无 `matcherMetadata` ⇒
36
+ `HOOK_EVENT_MATCHER_FIELD.DirectoryAdded = null`(与 CwdChanged / MessageDisplay 同族;entry 上写 `matcher` 仍是合法配置,
37
+ 只是不被查阅)。
38
+ - **owner = `shell`**(工作集在壳;`ENGINE_OWNED_HOOK_EVENTS` 9 员不动,`HOOK_EVENT_OWNER` 为派生表、自动含新员)。
39
+
40
+ ### API 形(全文)
41
+
42
+ ```ts
43
+ export const POST_198_HOOK_EVENTS = ["CwdChanged", "FileChanged", "MessageDisplay", "DirectoryAdded"] as const; // 尾插
44
+ export const DIRECTORY_ADDED_SOURCES = ["slash_command", "register_repo_root"] as const;
45
+ export const DirectoryAddedHookInput = BaseHookInput.extend({
46
+ hook_event_name: z.literal("DirectoryAdded"),
47
+ directory: z.string(),
48
+ source: z.enum(DIRECTORY_ADDED_SOURCES),
49
+ });
50
+ export type DirectoryAddedHookInput = z.infer<typeof DirectoryAddedHookInput>;
51
+ // HookInput union + 1 臂(尾);HOOK_EVENT_MATCHER_FIELD + 1 行(null);HookSpecificOutput 不动;HooksSlotSemantics 不动(非逐员表)
52
+ ```
53
+
54
+ ### 消费方可见(与 1.2.0 同一课:「表不动」≠「消费方零感知」)
55
+
56
+ - **cli**(`package.json` 范围 `^1.2.0`,但 `package-lock.json:1261-1264` **钉 1.2.0**——`npm ci` 不会静默提到 1.3.0;
57
+ 只有 `npm update` / 无锁重解析才命中):**运行时零影响**——cli `src/` 非测试文件对本包只 import `isAtLeast`
58
+ (`src/sema/settings/resolver.ts:28`),hook 符号零运行时消费(亲 grep)。提货影响全在常驻门
59
+ `src/types/hooksSchema218.test.ts`(以本包 `HOOK_EVENTS` 为 canonical 钉三面集合等式):亲读该门代码推断(**未实测**,
60
+ 不动 cli 树)提货 1.3.0 后 **五条**红——**H2**(T1 缺 `DirectoryAdded`)/ **H6**(计数 30≠31)/ **H7**(字面量
61
+ `CANON.length === 30` 与差集 `=== 3`)/ **H8**(按 CANON 31 键铺满喂 initialize,`DirectoryAdded` 对 T1 的 partialRecord
62
+ 是未知键 ⇒ 拒)/ **H10a**(`OLD_27 = CANON − 3` 亦含新员 ⇒ 同因拒);修法 = 该门自注「三面同动」:`coreSchemas.HOOK_EVENTS`
63
+ 加员 + `HookInputSchema` 加臂 + H7 字面量改 31 / 4。其 :177-178 把「`DirectoryAdded` 该不该进 settings 域」记为候裁,
64
+ 本版加员即回应该候裁项(cli 提货时表态);`coreTypes.HOOK_EVENTS`(31 员)与本版集合恰好相等。发布帖须 @cli 点名
65
+ (能力显式表态制)。
66
+ - 持 `Record<HookEventName, …>` 字面量 / 对 `HookEventName` 穷举 `switch` 的消费方:升级即**编译错**(响亮,正是要的)。
67
+ - 用 `HookEventName` / `HookInput` / `HooksConfig` 校验**外部**值的 1.2.0 消费方:收不下 `DirectoryAdded`(名词拒、整条
68
+ config 拒)——这正是本版修的病形;夹具钉住 1.2.0 行为供对照(frozen `z.record(z.enum(HOOK_EVENTS_1_2_0), …)` 拒、1.3.0 收)。
69
+ - **server**(`package.json:58` **精确钉** `"1.2.0"`,无 caret——永不自动提货):亲读 `src/hooks/hook-runner.ts`——
70
+ `parseHooksConfig` 走 `HooksConfig.safeParse`(:200),`createTaskHooks` 按 `HOOK_EVENT_OWNER` 分流(:1179/:1185);提货后
71
+ wire 上 `settings.hooks.DirectoryAdded` 不再被拒,落 shell-owned 分支 = `debug("hook_events_shell_owned")` 记账不点亮不告警
72
+ (:1185-1188)——「配置合法但 owner 是壳」本就是既定语义(非本版新义)。server 无 `Record<HookEventName,…>` 穷举表、
73
+ 无 30 计数钉(亲 grep src + test),提货零编译红零测试红(推断自 grep,提货批自证)。
74
+
75
+ ### 门
76
+
77
+ - **红先**:修前 solo 跑 `test/hooks.test.ts` + `test/hooks-directory-added-1.3.0.test.ts` = **11 红**(新文件 8:集合/
78
+ `HooksConfig` 收键/`HookInput` 判别/导出符号/owner/matcher/前缀冻结/1.2.0 对照;`hooks.test.ts` 计数钉 3)+ `tsc --noEmit`
79
+ **10 错**(全在测试文件——`Record<HookEventName,…>` 无该键、union 收窄为 `never`);修后 34/34 绿、tsc 0 错。
80
+ - **变异刀 4/4 红**(每刀后源文件复原逐字节一致):K1 `POST_198_HOOK_EVENTS` 去员 → vitest 8 红 + tsc 5 错(矩阵多余键 =
81
+ 编译红,穷举表如约响);K2 输入字段 `directory`→`dir` → 判别钉 2 红 + tsc 1 错;K3 matcher `null`→`"directory"` → 1 红;
82
+ K4 重排到 CC 序(DirectoryAdded 前置 MessageDisplay)→ 前缀冻结钉 2 红。
83
+ - **全门**:`npm run build` exit 0;`npm test` 40 文件 / 631 用例全过;`tsc --noEmit` exit 0;dist 亲验(`node -e` 加载
84
+ `dist/index.js`):`HOOK_EVENTS.length===31`、末员 `DirectoryAdded`、owner `shell`、matcher `null`、`HooksConfig` 收
85
+ `{DirectoryAdded:[…]}`;`dist/hooks.d.ts` 含 `DIRECTORY_ADDED_SOURCES` / `DirectoryAddedHookInput`(值+类型)。
86
+ - README 无 hooks 员数句(模块清单未列 hooks 域),无需同步。
87
+ - **codex 对抗复审 R1**(`adversarial-review`,工作树 diff):verdict needs-attention,**1 条 [high]** =「minor 发版必破
88
+ cli 消费方(`^1.2.0` 自动命中,契约门红),建议先落 cli 再发或改用 `^1.2.0` 选不中的版号」。处置:**不是代码缺陷,是发版
89
+ 协同事实**(且是从本段 CHANGELOG 自述推出的)——实证如上:cli 锁文件钉 1.2.0、cli 运行时零 hook 消费、server 精确钉;
90
+ 「破」的实体 = cli 常驻门(该门建门即自注「三面同动」),提货批修门是 cli 侧既定动作;发布走能力显式表态制 @cli。
91
+ 「改版号避开 `^1.2.0`」= 升 major,与 1.2.0 段 codex R2 严格 semver 异议同类,当时记为**候裁项**——本版沿用 minor 口径,
92
+ 异议原样留档供裁。另:codex 沙箱内 `npm run typecheck && npm test` exit 1——**同命令标准环境复跑 exit 0**(typecheck 0
93
+ 错 + 40 文件 631 用例),归因 codex `--sandbox read-only` 下 vitest 写 `node_modules/.vite/vitest` 缓存失败(目录亲验存在;
94
+ 归因为假设,复跑绿为实测)。
95
+
96
+ ## 1.2.0 — `MinTrust` 排序原语 + `task` 员([5796] 序题 → [5805] schema 认领 / [5807] cli 表态三点)
97
+
98
+ **新导出 `trustRank(layer)` / `isAtLeast(layer, minTrust)`——规范信任阶梯的**单源**(v2-design §1:29 逐字);
99
+ `MinTrust` **尾插** `task`。发布表 `SAFETY_MERGE_SPEC` **一字不动**(1.1.0 全表字面量冻结钉),枚举声明序不重排
100
+ (重排 = BREAKING,免裁);版号 minor = 加词([5805]/[5807] 双方口径「1.2.0 小批 additive」;codex R2 持严格 semver
101
+ 异议,见门段——候裁项,非代码缺陷)。**
102
+
103
+ > ⚠️ **词表加宽在消费方是可见的**(codex R1-F1,措辞订正):「表不动」≠「消费方零感知」。① 类型面:持
104
+ > `Record<MinTrust, …>` 字面量或对 `MinTrust` 穷举 `switch` 的消费方,升级即**编译错**(响亮——这正是要的:那张表
105
+ > 该退役改经 `trustRank`);② 运行时:用本包 schema(`MinTrust` / `MemberAdmission` / `MergeCategorySpec`)校验
106
+ > **外部** spec 值的消费方,自本版起会**收下** `task`,必须能给它排位——用 `trustRank`,勿自铸五词表(五词表对
107
+ > `task` 取值 `undefined`,`<`/`>=` 比较静默为 `false`,方向随消费方写法而定、且不响)。发包日观察值(亲 grep,
108
+ > 观察 ≠ 规范):cli `src/` 对 `MinTrust` **类型/值零 import**——仅注释提及(`src/sema/settings/keyContracts.ts:14-15,36`),
109
+ > 其 `TrustName` / `TRUST_RANK` 自铸且已含 `task`(按 [5807] ③ 删表改 import 时才首次成为类型消费方);1.1.0 段矩阵
110
+ > 其余邻仓零命中。故①②今日无真实受害者,但契约句必须先说真话。
111
+ > 「另铸六词 schema 保旧 `MinTrust`」不采:等于第二张词表,正是 1.1.0「一枚举无第二词表」钉所禁,且 cli ① 点名要
112
+ > 在 `MinTrust` 加员。
113
+
114
+ ### 阶梯序判定(成文理由,亲读坐标)
115
+
116
+ - 规范序 = v2-design §1:29(`sema-internal/core/research/settings-product/04-v2-design.md`)逐字:
117
+ `MANAGED 6 > GLOBAL 5 > PROJECT 4 > LOCAL 3 > SESSION 2 > TASK 1`。
118
+ - `task` 位**不是本仓臆定**:§1:29 本就含 `TASK 1`(最低信任端);§4:74 `KeyContract.minTrust` 词表本就列 `'task'`
119
+ ——它**是** floor 词(1.1.0 JSDoc「`task` 不是 floor 词」为失真句,本版改述);§2:36 SESSION/TASK = 运行期 overlay
120
+ 非文件、TASK 单 run 清、resume 不重放;§1:31 TASK 最高 precedence 但最低 trust(两序在用户端发散)。§5:96 resolver
121
+ 伪码本就写作 `trustRank(L) < trustRank(contract.minTrust) → throw`——原语**名**亦有真源。
122
+ - **数值本身入契约**(不只是序):与 cli 自铸 `TRUST_RANK`(`src/sema/settings/keyContracts.ts:37-44`,managed 6 … task 1)
123
+ 同值,壳删表改 import 后每次比较结果逐字节不变。
124
+ - 声明序仍非规范:`MinTrust.options` = 1.1.0 五词原序 + `task` 尾插;阶梯**只**经 `trustRank`,不索引枚举。
125
+ - **不建模** v2-design §6.4:138 的 `launch-only`(比 session/task 更窄的「仅本进程显式标志」):它是 `bypassPermissions`
126
+ 的入口限制,不是阶梯词;将来需成词另案。
127
+
128
+ ### API 形(全文)
129
+
130
+ ```ts
131
+ export const MinTrust = z.enum(["local", "session", "project", "global", "managed", "task"]);
132
+ export function trustRank(layer: MinTrust): number; // 6/5/4/3/2/1,穷举 switch
133
+ export function isAtLeast(layer: MinTrust, minTrust: MinTrust): boolean; // trustRank(layer) >= trustRank(minTrust)
134
+ ```
135
+
136
+ - **fail-closed**:`trustRank` 是对 `MinTrust` 的穷举 `switch`——枚举加词漏 rank = **编译错**(`never` 臂);运行时未知词
137
+ (JS / 反序列化调用方喂 `__proto__` / `constructor` / 大写 provenance 层词 / `launch-only` …)= **`RangeError`**,永不
138
+ `undefined` / `NaN`(NaN 会让每次比较静默 `false`——方向恰好安全但不响,仍属静默臂)。`isAtLeast` 两参都过 `trustRank`。
139
+ - 既有成员级查询族(`membersOf` / `memberAdmission` / `memberMinTrust` / `resolveMemberAdmission`)**不含比较逻辑**,
140
+ 无需改经;测试侧原本地 `LADDER` 表删除,改用 `isAtLeast`(单源两用);`memberMinTrust` JSDoc 的
141
+ `rank(layer) ≥ rank(floor)` 改指 `isAtLeast`。
142
+ - `task` 今日**零**类目 / 成员行使用(冻结 1.1.0 schema 全表逐字节解析钉证——1.1.0 消费方永不遇到未知词);
143
+ `minTrust:"task"` = 最宽 floor(任何层含 task overlay 皆可 set),对 spec **值**合法(`MemberAdmission` / `MergeCategorySpec` 收)。
144
+
145
+ ### 消费方接线
146
+
147
+ ```ts
148
+ import { isAtLeast, memberMinTrust, membersOf } from "@sema-agent/settings-schema";
149
+ for (const m of membersOf("permissions")) {
150
+ if (!isAtLeast(layer.sourceTrust, memberMinTrust("permissions", m))) { /* loosen ⇒ 待确认建议;tighten 不会走到这 */ }
151
+ }
152
+ ```
153
+
154
+ - cli:删自铸 `TRUST_RANK` 改 import(随 L-29 上收批同落,[5807] ③);四件套成员级门吃同一原语。1.1.0 段「本包尚未导出
155
+ 排序原语」自本版起过期。
156
+
157
+ ### 门
158
+
159
+ - 红先:11 条新钉 + 1 条改经原语的旧钉先红——运行时 10 红(`trustRank`/`isAtLeast is not a function` ×6、`task` 缺席 ×2、
160
+ 声明序文本钉 ×1、单调钉 ×1),tsc 11 处红(缺导出 2 / `"task"` 不可赋 4 / `@ts-expect-error` 未命中 2 / 连带 3)→ 全绿。
161
+ - 变异六刀各红(主题文件 33 钉):M1 session/local 互换 → 4 红(逐字表 / 单调 / 真值表 / 准入集);M2 `isAtLeast` 用 `>` →
162
+ 5 红(含 1.1.0 方向不变量钉——单源两用之证);M3 枚举去 `task`(switch 仍留)→ 5 红;M4 枚举重排(`task` 置首)→ 2 红
163
+ (尾插钉 + 文本钉,其余全绿=声明序确非阶梯);M5 未知词返 `NaN` 不抛 → 只红未知词钉(1/33,定点);M6 `task` 排 7 高于
164
+ managed → 5 红。源文件 sha256 复原逐字节一致。
165
+ - codex adversarial-review R1 三条:F1 [high]「加 `task` 非纯向后兼容」→ **部分采**:头句 / README 措辞订正为「表不动 ≠
166
+ 消费方零感知」+ 消费方迁移句(上 ⚠️ 段);「另铸六词 schema 保旧 `MinTrust`」**驳回**(第二张词表);F2 [medium] 冻结
167
+ 1.1.0 钉拿 `r.data` 对当前值 = 同义反复 → **采**:补独立 1.1.0 全表字面量夹具(commit `9e41eaf` 逐字),第七刀 M7
168
+ `hooks.minTrust` project→local **只红该钉**(1/33;修前此刀全绿);F3 [medium] README「无消费方自铸表」写成既成事实 →
169
+ **采**改述为迁移目标(cli 半场未落)。R1 自报 vitest 在其只读沙箱起不来(环境,非发现)。
170
+ - codex R2:F2 / F3 判**已解**;1.1.0 字面量判**独立且完整**(对 `9e41eaf` 逐值,16 类目);`trustRank` / `isAtLeast` /
171
+ JSDoc / 新钉**无新发现**。余 F1 一条 [high]:「加宽公共枚举按 minor 发,`^1.1.0` 消费方自动收到」→ 建议发 **2.0.0**
172
+ 或另铸六词 schema。处置:另铸 schema **再驳**(第二张词表,理由同上);**major vs minor 是版号政策、不是代码缺陷**
173
+ ——本批按 [5805]/[5807] 双方口径与任务书走 1.2.0,**候主会话 / clay 裁**;若裁 major,只需改 `package.json` /
174
+ `package-lock.json` 根版本 / 本段标题三处,代码零动。
175
+ - 全量 39 文件 / 621 用例绿(1.1.0 基线 610 + 11);tsc 0;build 绿(dist `.d.ts` 含两原语);dist 运行时冒烟
176
+ (`node -e import('./dist/index.js')`)六词/六序/未知词 RangeError 如述。
177
+
9
178
  ## 1.1.0 — `permissions` 成员级准入(权限双通道终裁四件套之④,clay 终裁 [5797];合流形 [5794])
10
179
 
11
180
  **`SAFETY_MERGE_SPEC` 类目可声明 `members`(成员级 minTrust + 方向);首个也是当前唯一成员化的类目 = `permissions`
@@ -82,7 +251,7 @@ for (const m of membersOf("permissions")) { // ["allow","deny","ask"]
82
251
 
83
252
  - `rank(...)` 仍是消费方自己的阶梯(v2-design §1 `managed > global > project > local > session > task`);本包
84
253
  **尚未**导出排序原语——`MinTrust` 枚举的声明序在 session/local 处与 §1 倒置、且零消费方索引它(非规范),
85
- 排序原语随 [5796] 序题表态另车(本版只把该注释改说真话)。
254
+ 排序原语随 [5796] 序题表态另车(本版只把该注释改说真话)。(→ 1.2.0 已导出 `trustRank` / `isAtLeast`。)
86
255
 
87
256
  ### 门
88
257
 
package/README.md CHANGED
@@ -68,6 +68,15 @@ sound). `npm ls --prod` therefore shows only `zod`. Node built-ins only (`node:c
68
68
  stays the default floor, so consumers that predate `members` behave exactly as before. This package publishes
69
69
  the contract only: it performs no admission check and no "degrade a below-floor allow to a pending suggestion"
70
70
  step — those live in the consumers (engine import door, shell resolver), each on its own release schedule.
71
+ 1.2.0 adds the **trust ordering primitives**: `trustRank(layer)` (v2-design §1 verbatim, `managed 6 > global 5 >
72
+ project 4 > local 3 > session 2 > task 1`) and `isAtLeast(layer, minTrust)` — the ladder every resolver is to
73
+ compare floors against; consumers retire their own rank tables onto it (the CLI's `TRUST_RANK` migration is
74
+ pending on its side, board [5807] ③ — exporting the primitive does not by itself make it the single source).
75
+ `MinTrust` gains the `task` word (the runtime task overlay, the loosest floor; appended, declaration order
76
+ otherwise unchanged — the declaration order is not the ladder, `trustRank` is). The published table does not
77
+ use `task`; the widened vocabulary is visible to consumers that hold a `Record<MinTrust, …>` / exhaustive
78
+ switch (compile error on upgrade — migrate to `trustRank`) or that validate external spec values with these
79
+ schemas (they now accept `task` and must rank it — again `trustRank`, never a five-word table of their own).
71
80
  - **Auth wire contract** (`api/auth.ts`, subpath `./api/auth`): the `/api/v1/auth/*` face — RFC 8628
72
81
  device flow (code / token poll / approve consent) + rotating refresh + logout, the OAuth error envelope,
73
82
  and the pure client helpers `normalizeUserCode` / `nextPollInterval`. Shipped M1 shapes frozen.
package/dist/hooks.d.ts CHANGED
@@ -2,10 +2,10 @@
2
2
  * `hooks` — the CC-parity hooks contract (incl. stop attribution).
3
3
  *
4
4
  * ONE definition of the FULL hook-event set — 27 CC 2.1.198 events (REF: the `jP` array in
5
- * claude-cli-2.1.198.pretty.js) + 3 post-198 events the shell already ships (CwdChanged / FileChanged /
6
- * MessageDisplay). Per the product ruling: the CONTRACT is the full set from day one; the engine
7
- * hook-runner (service) and the shell light events up in PHASES — adding an event later is pure
8
- * implementation, never a schema change.
5
+ * claude-cli-2.1.198.pretty.js) + 4 post-198 events the shell already ships (CwdChanged / FileChanged /
6
+ * MessageDisplay, and DirectoryAdded from CC 2.1.219). Per the product ruling: the CONTRACT is the full set
7
+ * from day one; the engine hook-runner (service) and the shell light events up in PHASES — adding an event
8
+ * later is pure implementation, never a schema change.
9
9
  *
10
10
  * CONSUMERS
11
11
  * - service (sema-server): `TaskRequest.settings.hooks` wire face = `HooksConfig` below; the worker-side
@@ -26,11 +26,15 @@
26
26
  import { z } from "zod";
27
27
  /** CC 2.1.198 full hook-event set (jP array) — 27 events. Order matches the shell's HOOK_EVENTS. */
28
28
  export declare const CC_198_HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "Notification", "UserPromptSubmit", "UserPromptExpansion", "SessionStart", "SessionEnd", "Stop", "StopFailure", "SubagentStart", "SubagentStop", "PreCompact", "PostCompact", "PermissionRequest", "PermissionDenied", "Setup", "TeammateIdle", "TaskCreated", "TaskCompleted", "Elicitation", "ElicitationResult", "ConfigChange", "WorktreeCreate", "WorktreeRemove", "InstructionsLoaded"];
29
- /** Post-CC-2.1.198 additions the shell already ships (sema superset; CC added them after 198). */
30
- export declare const POST_198_HOOK_EVENTS: readonly ["CwdChanged", "FileChanged", "MessageDisplay"];
31
- /** The full 30-event contract set = CC 198's 27 + the 3 post-198 shell events. */
32
- export declare const HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "Notification", "UserPromptSubmit", "UserPromptExpansion", "SessionStart", "SessionEnd", "Stop", "StopFailure", "SubagentStart", "SubagentStop", "PreCompact", "PostCompact", "PermissionRequest", "PermissionDenied", "Setup", "TeammateIdle", "TaskCreated", "TaskCompleted", "Elicitation", "ElicitationResult", "ConfigChange", "WorktreeCreate", "WorktreeRemove", "InstructionsLoaded", "CwdChanged", "FileChanged", "MessageDisplay"];
33
- export declare const HookEventName: z.ZodEnum<["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "Notification", "UserPromptSubmit", "UserPromptExpansion", "SessionStart", "SessionEnd", "Stop", "StopFailure", "SubagentStart", "SubagentStop", "PreCompact", "PostCompact", "PermissionRequest", "PermissionDenied", "Setup", "TeammateIdle", "TaskCreated", "TaskCompleted", "Elicitation", "ElicitationResult", "ConfigChange", "WorktreeCreate", "WorktreeRemove", "InstructionsLoaded", "CwdChanged", "FileChanged", "MessageDisplay"]>;
29
+ /**
30
+ * Post-CC-2.1.198 additions the shell already ships (sema superset; CC added them after 198).
31
+ * APPEND-ONLY: new members go at the tail (1.3.0 `DirectoryAdded`, CC 2.1.219 — CC's own tuple places it
32
+ * between FileChanged and MessageDisplay; the declaration order here is not normative, the enum is a set).
33
+ */
34
+ export declare const POST_198_HOOK_EVENTS: readonly ["CwdChanged", "FileChanged", "MessageDisplay", "DirectoryAdded"];
35
+ /** The full 31-event contract set = CC 198's 27 + the 4 post-198 shell events. */
36
+ export declare const HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "Notification", "UserPromptSubmit", "UserPromptExpansion", "SessionStart", "SessionEnd", "Stop", "StopFailure", "SubagentStart", "SubagentStop", "PreCompact", "PostCompact", "PermissionRequest", "PermissionDenied", "Setup", "TeammateIdle", "TaskCreated", "TaskCompleted", "Elicitation", "ElicitationResult", "ConfigChange", "WorktreeCreate", "WorktreeRemove", "InstructionsLoaded", "CwdChanged", "FileChanged", "MessageDisplay", "DirectoryAdded"];
37
+ export declare const HookEventName: z.ZodEnum<["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "Notification", "UserPromptSubmit", "UserPromptExpansion", "SessionStart", "SessionEnd", "Stop", "StopFailure", "SubagentStart", "SubagentStop", "PreCompact", "PostCompact", "PermissionRequest", "PermissionDenied", "Setup", "TeammateIdle", "TaskCreated", "TaskCompleted", "Elicitation", "ElicitationResult", "ConfigChange", "WorktreeCreate", "WorktreeRemove", "InstructionsLoaded", "CwdChanged", "FileChanged", "MessageDisplay", "DirectoryAdded"]>;
34
38
  export type HookEventName = z.infer<typeof HookEventName>;
35
39
  export declare const HookOwner: z.ZodEnum<["engine", "shell"]>;
36
40
  export type HookOwner = z.infer<typeof HookOwner>;
@@ -1586,7 +1590,48 @@ export declare const MessageDisplayHookInput: z.ZodObject<{
1586
1590
  permission_mode?: string | undefined;
1587
1591
  agent_id?: string | undefined;
1588
1592
  }>;
1589
- /** Any hook input discriminated on `hook_event_name` (all 30 events). */
1593
+ /** How a directory joined the session working set: the `/add-dir` slash command or repo-root registration. */
1594
+ export declare const DIRECTORY_ADDED_SOURCES: readonly ["slash_command", "register_repo_root"];
1595
+ /**
1596
+ * Fired after a directory joins the session's working set (CC 2.1.219; agent-types hooks.d.ts:582-586 —
1597
+ * `directory` is the absolute path). Shell-owned; matcher ignored; no hookSpecificOutput arm upstream.
1598
+ */
1599
+ export declare const DirectoryAddedHookInput: z.ZodObject<{
1600
+ session_id: z.ZodString;
1601
+ transcript_path: z.ZodString;
1602
+ cwd: z.ZodString;
1603
+ permission_mode: z.ZodOptional<z.ZodString>;
1604
+ /** Subagent id — present only when the hook fires from within a subagent. */
1605
+ agent_id: z.ZodOptional<z.ZodString>;
1606
+ /** Agent type name (e.g. "general-purpose") — present alongside agent_id, or on --agent main threads. */
1607
+ agent_type: z.ZodOptional<z.ZodString>;
1608
+ } & {
1609
+ hook_event_name: z.ZodLiteral<"DirectoryAdded">;
1610
+ directory: z.ZodString;
1611
+ source: z.ZodEnum<["slash_command", "register_repo_root"]>;
1612
+ }, "strip", z.ZodTypeAny, {
1613
+ source: "slash_command" | "register_repo_root";
1614
+ session_id: string;
1615
+ transcript_path: string;
1616
+ cwd: string;
1617
+ hook_event_name: "DirectoryAdded";
1618
+ directory: string;
1619
+ agent_type?: string | undefined;
1620
+ permission_mode?: string | undefined;
1621
+ agent_id?: string | undefined;
1622
+ }, {
1623
+ source: "slash_command" | "register_repo_root";
1624
+ session_id: string;
1625
+ transcript_path: string;
1626
+ cwd: string;
1627
+ hook_event_name: "DirectoryAdded";
1628
+ directory: string;
1629
+ agent_type?: string | undefined;
1630
+ permission_mode?: string | undefined;
1631
+ agent_id?: string | undefined;
1632
+ }>;
1633
+ export type DirectoryAddedHookInput = z.infer<typeof DirectoryAddedHookInput>;
1634
+ /** Any hook input — discriminated on `hook_event_name` (all 31 events). */
1590
1635
  export declare const HookInput: z.ZodDiscriminatedUnion<"hook_event_name", [z.ZodObject<{
1591
1636
  session_id: z.ZodString;
1592
1637
  transcript_path: z.ZodString;
@@ -2884,6 +2929,39 @@ export declare const HookInput: z.ZodDiscriminatedUnion<"hook_event_name", [z.Zo
2884
2929
  agent_type?: string | undefined;
2885
2930
  permission_mode?: string | undefined;
2886
2931
  agent_id?: string | undefined;
2932
+ }>, z.ZodObject<{
2933
+ session_id: z.ZodString;
2934
+ transcript_path: z.ZodString;
2935
+ cwd: z.ZodString;
2936
+ permission_mode: z.ZodOptional<z.ZodString>;
2937
+ /** Subagent id — present only when the hook fires from within a subagent. */
2938
+ agent_id: z.ZodOptional<z.ZodString>;
2939
+ /** Agent type name (e.g. "general-purpose") — present alongside agent_id, or on --agent main threads. */
2940
+ agent_type: z.ZodOptional<z.ZodString>;
2941
+ } & {
2942
+ hook_event_name: z.ZodLiteral<"DirectoryAdded">;
2943
+ directory: z.ZodString;
2944
+ source: z.ZodEnum<["slash_command", "register_repo_root"]>;
2945
+ }, "strip", z.ZodTypeAny, {
2946
+ source: "slash_command" | "register_repo_root";
2947
+ session_id: string;
2948
+ transcript_path: string;
2949
+ cwd: string;
2950
+ hook_event_name: "DirectoryAdded";
2951
+ directory: string;
2952
+ agent_type?: string | undefined;
2953
+ permission_mode?: string | undefined;
2954
+ agent_id?: string | undefined;
2955
+ }, {
2956
+ source: "slash_command" | "register_repo_root";
2957
+ session_id: string;
2958
+ transcript_path: string;
2959
+ cwd: string;
2960
+ hook_event_name: "DirectoryAdded";
2961
+ directory: string;
2962
+ agent_type?: string | undefined;
2963
+ permission_mode?: string | undefined;
2964
+ agent_id?: string | undefined;
2887
2965
  }>]>;
2888
2966
  export type HookInput = z.infer<typeof HookInput>;
2889
2967
  /** Legacy top-level decision (CC): "approve" ≈ allow, "block" feeds `reason` back to the model. */
@@ -5442,7 +5520,7 @@ export declare const HookMatcherEntry: z.ZodObject<{
5442
5520
  export type HookMatcherEntry = z.infer<typeof HookMatcherEntry>;
5443
5521
  /**
5444
5522
  * The hooks config record — the shape `TaskRequest.settings.hooks` carries on the wire and the shell
5445
- * reads from settings JSON: `{ [eventName]: [{ matcher?, hooks: [...] }] }`. Keys limited to the 30
5523
+ * reads from settings JSON: `{ [eventName]: [{ matcher?, hooks: [...] }] }`. Keys limited to the 31
5446
5524
  * contract events (an unknown event name fails parse — catch typos at the wire); all keys optional.
5447
5525
  */
5448
5526
  export type HooksConfig = Partial<Record<HookEventName, HookMatcherEntry[]>>;
package/dist/hooks.js CHANGED
@@ -2,10 +2,10 @@
2
2
  * `hooks` — the CC-parity hooks contract (incl. stop attribution).
3
3
  *
4
4
  * ONE definition of the FULL hook-event set — 27 CC 2.1.198 events (REF: the `jP` array in
5
- * claude-cli-2.1.198.pretty.js) + 3 post-198 events the shell already ships (CwdChanged / FileChanged /
6
- * MessageDisplay). Per the product ruling: the CONTRACT is the full set from day one; the engine
7
- * hook-runner (service) and the shell light events up in PHASES — adding an event later is pure
8
- * implementation, never a schema change.
5
+ * claude-cli-2.1.198.pretty.js) + 4 post-198 events the shell already ships (CwdChanged / FileChanged /
6
+ * MessageDisplay, and DirectoryAdded from CC 2.1.219). Per the product ruling: the CONTRACT is the full set
7
+ * from day one; the engine hook-runner (service) and the shell light events up in PHASES — adding an event
8
+ * later is pure implementation, never a schema change.
9
9
  *
10
10
  * CONSUMERS
11
11
  * - service (sema-server): `TaskRequest.settings.hooks` wire face = `HooksConfig` below; the worker-side
@@ -55,9 +55,13 @@ export const CC_198_HOOK_EVENTS = [
55
55
  "WorktreeRemove",
56
56
  "InstructionsLoaded",
57
57
  ];
58
- /** Post-CC-2.1.198 additions the shell already ships (sema superset; CC added them after 198). */
59
- export const POST_198_HOOK_EVENTS = ["CwdChanged", "FileChanged", "MessageDisplay"];
60
- /** The full 30-event contract set = CC 198's 27 + the 3 post-198 shell events. */
58
+ /**
59
+ * Post-CC-2.1.198 additions the shell already ships (sema superset; CC added them after 198).
60
+ * APPEND-ONLY: new members go at the tail (1.3.0 `DirectoryAdded`, CC 2.1.219 — CC's own tuple places it
61
+ * between FileChanged and MessageDisplay; the declaration order here is not normative, the enum is a set).
62
+ */
63
+ export const POST_198_HOOK_EVENTS = ["CwdChanged", "FileChanged", "MessageDisplay", "DirectoryAdded"];
64
+ /** The full 31-event contract set = CC 198's 27 + the 4 post-198 shell events. */
61
65
  export const HOOK_EVENTS = [...CC_198_HOOK_EVENTS, ...POST_198_HOOK_EVENTS];
62
66
  export const HookEventName = z.enum(HOOK_EVENTS);
63
67
  // ── ownership (owner-single) ─────────────────────────────────────────────────────────────────────
@@ -119,6 +123,7 @@ export const HOOK_EVENT_MATCHER_FIELD = {
119
123
  CwdChanged: null,
120
124
  FileChanged: "file_path", // matched against basename(file_path)
121
125
  MessageDisplay: null,
126
+ DirectoryAdded: null, // the shell's getMatchingHooks switch has no arm for it → every entry matches (CwdChanged family)
122
127
  };
123
128
  // ── shared enums (CC-verbatim) ─────────────────────────────────────────────────────────────────────────
124
129
  export const PermissionBehavior = z.enum(["allow", "deny", "ask"]);
@@ -382,7 +387,18 @@ export const MessageDisplayHookInput = BaseHookInput.extend({
382
387
  /** Newly completed lines since the prior flush (final flush may end mid-line / be empty). */
383
388
  delta: z.string(),
384
389
  });
385
- /** Any hook input discriminated on `hook_event_name` (all 30 events). */
390
+ /** How a directory joined the session working set: the `/add-dir` slash command or repo-root registration. */
391
+ export const DIRECTORY_ADDED_SOURCES = ["slash_command", "register_repo_root"];
392
+ /**
393
+ * Fired after a directory joins the session's working set (CC 2.1.219; agent-types hooks.d.ts:582-586 —
394
+ * `directory` is the absolute path). Shell-owned; matcher ignored; no hookSpecificOutput arm upstream.
395
+ */
396
+ export const DirectoryAddedHookInput = BaseHookInput.extend({
397
+ hook_event_name: z.literal("DirectoryAdded"),
398
+ directory: z.string(),
399
+ source: z.enum(DIRECTORY_ADDED_SOURCES),
400
+ });
401
+ /** Any hook input — discriminated on `hook_event_name` (all 31 events). */
386
402
  export const HookInput = z.discriminatedUnion("hook_event_name", [
387
403
  PreToolUseHookInput,
388
404
  PostToolUseHookInput,
@@ -414,6 +430,7 @@ export const HookInput = z.discriminatedUnion("hook_event_name", [
414
430
  CwdChangedHookInput,
415
431
  FileChangedHookInput,
416
432
  MessageDisplayHookInput,
433
+ DirectoryAddedHookInput,
417
434
  ]);
418
435
  // ── hook output / decision semantics (CC-verbatim, with the service-receipt design points pinned) ────────
419
436
  //
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ export * from "./cross-domain.js"; // pure half only (no fleet placement)
14
14
  export * from "./remote-exec.js";
15
15
  export * from "./safety-merge-spec.js"; // #14 per-category safety merge-spec (settings resolver authority)
16
16
  export * from "./scheduler-store.js"; // R7 self-wake scheduler store CONTRACT (pure: type + parse/serialize + SchedulerStore interface; fs binding in ./node)
17
- export * from "./hooks.js"; // CC-parity hooks contract (30-event set + owner map + per-event input/output shapes + HooksConfig) & stop attribution (stoppedBy) — pure/browser-safe
17
+ export * from "./hooks.js"; // CC-parity hooks contract (31-event set + owner map + per-event input/output shapes + HooksConfig) & stop attribution (stoppedBy) — pure/browser-safe
18
18
  export * from "./fleet.js"; // scheduling/fleet contract (drain protocol · instance discovery · dispatch policy · quota lease · usage report · quota reset event) — CONTROL-PLANE-SCHEDULING-DESIGN, pure/browser-safe
19
19
  export * from "./migrate.js"; // 0.5→0.6 migration tools (hosts grandfathering + worker.model→catalog migration PLAN — report/draft only, E1; EXPERT-REDESIGN §2-2/§3), pure/browser-safe
20
20
  export * from "./api/auth.js"; // /api/v1/auth/* wire contract (RFC 8628 device flow + rotating refresh + logout + approve) — REGISTRY-REBUILD-DESIGN §2, M1 shipped shapes frozen, pure/browser-safe
@@ -38,18 +38,41 @@
38
38
  * silences classifier asks only; it can never silence a MANDATED ask (shellGate "always", org policy,
39
39
  * requiresRealApproval). Consumers read admission through {@link memberAdmission} / {@link memberMinTrust} /
40
40
  * {@link membersOf} — never through a hardcoded allow/deny/ask table of their own (single source).
41
+ *
42
+ * TRUST ORDERING PRIMITIVES (1.2.0 — board [5796] ordering question → [5805] claim / [5807] consumer statement):
43
+ * The floor words are only meaningful against a LADDER, and that ladder is v2-design §1:29, verbatim:
44
+ * `MANAGED 6 > GLOBAL 5 > PROJECT 4 > LOCAL 3 > SESSION 2 > TASK 1`. {@link trustRank} publishes exactly those
45
+ * numbers and {@link isAtLeast} the comparison a resolver makes (`rank(layer) ≥ rank(floor)` ⇒ admitted), so no
46
+ * consumer keeps a rank table of its own (cli's `TRUST_RANK` retires onto these). `task` joins {@link MinTrust}
47
+ * (v2-design §4:74 lists it in `KeyContract.minTrust`; §2:36 — session/task are runtime overlays, never files):
48
+ * the loosest floor, "any layer including the task overlay may set this". No row of {@link SAFETY_MERGE_SPEC}
49
+ * uses it today (the frozen-1.1.0 conformance pin holds that). The enum's DECLARATION ORDER stays what 1.1.0
50
+ * published, `task` appended — reordering would be breaking ([5807] ②) and the declaration order is NOT the
51
+ * ladder anyway; only {@link trustRank} is.
41
52
  */
42
53
  import { z } from "zod";
43
54
  /** How values from different layers/principals combine for a category. */
44
55
  export declare const MergeShape: z.ZodEnum<["denyFirst", "replace", "runAll", "overrideByName", "concat"]>;
45
56
  export type MergeShape = z.infer<typeof MergeShape>;
46
57
  /** The trust-tier VOCABULARY a category — or, since 1.1.0, one of its members — names as its admission floor
47
- * (the MINIMUM trust a layer/principal must have to set it). The RANK is v2-design §1:
48
- * `managed > global > project > local > session` (`task` is a layer tier below all of these and is not a floor
49
- * word). ⚠️ This enum's DECLARATION ORDER is not that rank (session/local are listed the other way round) and no
50
- * consumer indexes it a published rank primitive is a separate follow-up (board [5796] question). */
51
- export declare const MinTrust: z.ZodEnum<["local", "session", "project", "global", "managed"]>;
58
+ * (the MINIMUM trust a layer/principal must have to set it). The RANK is v2-design §1:29,
59
+ * `managed 6 > global 5 > project 4 > local 3 > session 2 > task 1`, published as {@link trustRank}; compare with
60
+ * {@link isAtLeast}. `task` (1.2.0) is the runtime task overlay the lowest trust, the loosest floor.
61
+ * ⚠️ This enum's DECLARATION ORDER is NOT the rank (it is the 1.1.0 order with `task` appended — kept byte-stable
62
+ * because reordering is breaking); never index it as a ladder — use {@link trustRank}. */
63
+ export declare const MinTrust: z.ZodEnum<["local", "session", "project", "global", "managed", "task"]>;
52
64
  export type MinTrust = z.infer<typeof MinTrust>;
65
+ /** The trust RANK of a floor / layer word — v2-design §1:29 verbatim (`MANAGED 6 > GLOBAL 5 > PROJECT 4 > LOCAL 3 >
66
+ * SESSION 2 > TASK 1`), the ONE published ladder every resolver compares against (1.2.0). Higher = more trusted.
67
+ * The numbers themselves are contract (a consumer may persist or log them), not merely their order.
68
+ * Exhaustive over {@link MinTrust}: a word added to the enum without a rank is a COMPILE error here; a word that
69
+ * is not in the enum at runtime (a JavaScript / deserialized caller) is a loud RangeError — never `undefined` or
70
+ * `NaN`, which would turn every comparison into a silent `false`. */
71
+ export declare function trustRank(layer: MinTrust): number;
72
+ /** `trustRank(layer) ≥ trustRank(minTrust)` — "is a source at `layer` trusted enough to set a key / member whose
73
+ * floor is `minTrust`?" (1.2.0). The comparison a resolver's admission check is; reflexive (a floor admits its
74
+ * own tier). Both arguments are validated by {@link trustRank} (unknown word ⇒ RangeError). */
75
+ export declare function isAtLeast(layer: MinTrust, minTrust: MinTrust): boolean;
53
76
  /** Which way a MEMBER moves the engine's unattended surface (1.1.0). `loosen` = admits an action without a human
54
77
  * that would otherwise ask/deny — a privilege, so it needs trust; `tighten` = only ever narrows — restricting is
55
78
  * safe from anyone, so any settings-file layer may add it. The word is what a consumer branches on for the
@@ -63,13 +86,13 @@ export type MemberDirection = z.infer<typeof MemberDirection>;
63
86
  * only by SAYING so; omission never lowers it (fail-closed by construction). */
64
87
  export declare const MemberAdmission: z.ZodObject<{
65
88
  direction: z.ZodEnum<["loosen", "tighten"]>;
66
- minTrust: z.ZodOptional<z.ZodEnum<["local", "session", "project", "global", "managed"]>>;
89
+ minTrust: z.ZodOptional<z.ZodEnum<["local", "session", "project", "global", "managed", "task"]>>;
67
90
  }, "strip", z.ZodTypeAny, {
68
91
  direction: "loosen" | "tighten";
69
- minTrust?: "local" | "session" | "global" | "project" | "managed" | undefined;
92
+ minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
70
93
  }, {
71
94
  direction: "loosen" | "tighten";
72
- minTrust?: "local" | "session" | "global" | "project" | "managed" | undefined;
95
+ minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
73
96
  }>;
74
97
  export type MemberAdmission = z.infer<typeof MemberAdmission>;
75
98
  /** The RESOLVED admission a consumer branches on — the category fallback already applied, so `minTrust` is never
@@ -129,7 +152,7 @@ export declare const TightenRule: z.ZodDiscriminatedUnion<"rule", [z.ZodObject<{
129
152
  export type TightenRule = z.infer<typeof TightenRule>;
130
153
  export declare const MergeCategorySpec: z.ZodObject<{
131
154
  mergeShape: z.ZodEnum<["denyFirst", "replace", "runAll", "overrideByName", "concat"]>;
132
- minTrust: z.ZodEnum<["local", "session", "project", "global", "managed"]>;
155
+ minTrust: z.ZodEnum<["local", "session", "project", "global", "managed", "task"]>;
133
156
  tighten: z.ZodDiscriminatedUnion<"rule", [z.ZodObject<{
134
157
  rule: z.ZodLiteral<"none">;
135
158
  }, "strip", z.ZodTypeAny, {
@@ -178,13 +201,13 @@ export declare const MergeCategorySpec: z.ZodObject<{
178
201
  * category `minTrust` when the row omits one); the member set is closed — {@link membersOf} enumerates it. */
179
202
  members: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
180
203
  direction: z.ZodEnum<["loosen", "tighten"]>;
181
- minTrust: z.ZodOptional<z.ZodEnum<["local", "session", "project", "global", "managed"]>>;
204
+ minTrust: z.ZodOptional<z.ZodEnum<["local", "session", "project", "global", "managed", "task"]>>;
182
205
  }, "strip", z.ZodTypeAny, {
183
206
  direction: "loosen" | "tighten";
184
- minTrust?: "local" | "session" | "global" | "project" | "managed" | undefined;
207
+ minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
185
208
  }, {
186
209
  direction: "loosen" | "tighten";
187
- minTrust?: "local" | "session" | "global" | "project" | "managed" | undefined;
210
+ minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
188
211
  }>>>;
189
212
  }, "strip", z.ZodTypeAny, {
190
213
  enforcement: "gate" | "server" | "resolver" | "client";
@@ -201,13 +224,13 @@ export declare const MergeCategorySpec: z.ZodObject<{
201
224
  rule: "enumRank";
202
225
  order: string[];
203
226
  };
204
- minTrust: "local" | "session" | "global" | "project" | "managed";
227
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
205
228
  mergeShape: "concat" | "replace" | "denyFirst" | "runAll" | "overrideByName";
206
229
  fail: "closed" | "open";
207
230
  seam: string;
208
231
  members?: Record<string, {
209
232
  direction: "loosen" | "tighten";
210
- minTrust?: "local" | "session" | "global" | "project" | "managed" | undefined;
233
+ minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
211
234
  }> | undefined;
212
235
  coreAxis?: "permissionDecision" | "toolEffect" | "egress" | "irreversibility" | "safetyAxis" | "severity" | "shellGate" | undefined;
213
236
  }, {
@@ -225,13 +248,13 @@ export declare const MergeCategorySpec: z.ZodObject<{
225
248
  rule: "enumRank";
226
249
  order: string[];
227
250
  };
228
- minTrust: "local" | "session" | "global" | "project" | "managed";
251
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
229
252
  mergeShape: "concat" | "replace" | "denyFirst" | "runAll" | "overrideByName";
230
253
  fail: "closed" | "open";
231
254
  seam: string;
232
255
  members?: Record<string, {
233
256
  direction: "loosen" | "tighten";
234
- minTrust?: "local" | "session" | "global" | "project" | "managed" | undefined;
257
+ minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
235
258
  }> | undefined;
236
259
  coreAxis?: "permissionDecision" | "toolEffect" | "egress" | "irreversibility" | "safetyAxis" | "severity" | "shellGate" | undefined;
237
260
  }>;
@@ -446,5 +469,5 @@ export declare function resolveMemberAdmission(spec: MergeCategorySpec, member:
446
469
  * Unknown category or member ⇒ RangeError (never a fallback). */
447
470
  export declare function memberAdmission<C extends SafetyMergeCategory>(category: C, member: SafetyMergeMember<C>): ResolvedMemberAdmission;
448
471
  /** A member's admission FLOOR from the published table — the word a resolver compares the layer's trust against
449
- * (`rank(layer) ≥ rank(floor)` ⇒ admitted). Convenience over {@link memberAdmission}. */
472
+ * (`isAtLeast(layer, floor)` ⇒ admitted; the ladder is {@link trustRank}). Convenience over {@link memberAdmission}. */
450
473
  export declare function memberMinTrust<C extends SafetyMergeCategory>(category: C, member: SafetyMergeMember<C>): MinTrust;
@@ -38,16 +38,54 @@
38
38
  * silences classifier asks only; it can never silence a MANDATED ask (shellGate "always", org policy,
39
39
  * requiresRealApproval). Consumers read admission through {@link memberAdmission} / {@link memberMinTrust} /
40
40
  * {@link membersOf} — never through a hardcoded allow/deny/ask table of their own (single source).
41
+ *
42
+ * TRUST ORDERING PRIMITIVES (1.2.0 — board [5796] ordering question → [5805] claim / [5807] consumer statement):
43
+ * The floor words are only meaningful against a LADDER, and that ladder is v2-design §1:29, verbatim:
44
+ * `MANAGED 6 > GLOBAL 5 > PROJECT 4 > LOCAL 3 > SESSION 2 > TASK 1`. {@link trustRank} publishes exactly those
45
+ * numbers and {@link isAtLeast} the comparison a resolver makes (`rank(layer) ≥ rank(floor)` ⇒ admitted), so no
46
+ * consumer keeps a rank table of its own (cli's `TRUST_RANK` retires onto these). `task` joins {@link MinTrust}
47
+ * (v2-design §4:74 lists it in `KeyContract.minTrust`; §2:36 — session/task are runtime overlays, never files):
48
+ * the loosest floor, "any layer including the task overlay may set this". No row of {@link SAFETY_MERGE_SPEC}
49
+ * uses it today (the frozen-1.1.0 conformance pin holds that). The enum's DECLARATION ORDER stays what 1.1.0
50
+ * published, `task` appended — reordering would be breaking ([5807] ②) and the declaration order is NOT the
51
+ * ladder anyway; only {@link trustRank} is.
41
52
  */
42
53
  import { z } from "zod";
43
54
  /** How values from different layers/principals combine for a category. */
44
55
  export const MergeShape = z.enum(["denyFirst", "replace", "runAll", "overrideByName", "concat"]);
45
56
  /** The trust-tier VOCABULARY a category — or, since 1.1.0, one of its members — names as its admission floor
46
- * (the MINIMUM trust a layer/principal must have to set it). The RANK is v2-design §1:
47
- * `managed > global > project > local > session` (`task` is a layer tier below all of these and is not a floor
48
- * word). ⚠️ This enum's DECLARATION ORDER is not that rank (session/local are listed the other way round) and no
49
- * consumer indexes it a published rank primitive is a separate follow-up (board [5796] question). */
50
- export const MinTrust = z.enum(["local", "session", "project", "global", "managed"]);
57
+ * (the MINIMUM trust a layer/principal must have to set it). The RANK is v2-design §1:29,
58
+ * `managed 6 > global 5 > project 4 > local 3 > session 2 > task 1`, published as {@link trustRank}; compare with
59
+ * {@link isAtLeast}. `task` (1.2.0) is the runtime task overlay the lowest trust, the loosest floor.
60
+ * ⚠️ This enum's DECLARATION ORDER is NOT the rank (it is the 1.1.0 order with `task` appended — kept byte-stable
61
+ * because reordering is breaking); never index it as a ladder — use {@link trustRank}. */
62
+ export const MinTrust = z.enum(["local", "session", "project", "global", "managed", "task"]);
63
+ /** The trust RANK of a floor / layer word — v2-design §1:29 verbatim (`MANAGED 6 > GLOBAL 5 > PROJECT 4 > LOCAL 3 >
64
+ * SESSION 2 > TASK 1`), the ONE published ladder every resolver compares against (1.2.0). Higher = more trusted.
65
+ * The numbers themselves are contract (a consumer may persist or log them), not merely their order.
66
+ * Exhaustive over {@link MinTrust}: a word added to the enum without a rank is a COMPILE error here; a word that
67
+ * is not in the enum at runtime (a JavaScript / deserialized caller) is a loud RangeError — never `undefined` or
68
+ * `NaN`, which would turn every comparison into a silent `false`. */
69
+ export function trustRank(layer) {
70
+ switch (layer) {
71
+ case "managed": return 6;
72
+ case "global": return 5;
73
+ case "project": return 4;
74
+ case "local": return 3;
75
+ case "session": return 2;
76
+ case "task": return 1;
77
+ default: {
78
+ const unknown = layer;
79
+ throw new RangeError(`MinTrust: unknown trust word '${String(unknown)}' (words: ${MinTrust.options.join(", ")})`);
80
+ }
81
+ }
82
+ }
83
+ /** `trustRank(layer) ≥ trustRank(minTrust)` — "is a source at `layer` trusted enough to set a key / member whose
84
+ * floor is `minTrust`?" (1.2.0). The comparison a resolver's admission check is; reflexive (a floor admits its
85
+ * own tier). Both arguments are validated by {@link trustRank} (unknown word ⇒ RangeError). */
86
+ export function isAtLeast(layer, minTrust) {
87
+ return trustRank(layer) >= trustRank(minTrust);
88
+ }
51
89
  /** Which way a MEMBER moves the engine's unattended surface (1.1.0). `loosen` = admits an action without a human
52
90
  * that would otherwise ask/deny — a privilege, so it needs trust; `tighten` = only ever narrows — restricting is
53
91
  * safe from anyone, so any settings-file layer may add it. The word is what a consumer branches on for the
@@ -171,7 +209,7 @@ export function memberAdmission(category, member) {
171
209
  return resolveMemberAdmission(categorySpec(category), member, category);
172
210
  }
173
211
  /** A member's admission FLOOR from the published table — the word a resolver compares the layer's trust against
174
- * (`rank(layer) ≥ rank(floor)` ⇒ admitted). Convenience over {@link memberAdmission}. */
212
+ * (`isAtLeast(layer, floor)` ⇒ admitted; the ladder is {@link trustRank}). Convenience over {@link memberAdmission}. */
175
213
  export function memberMinTrust(category, member) {
176
214
  return memberAdmission(category, member).minTrust;
177
215
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/settings-schema",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Sema settings schema — pure config contract (zod domains + effective-config + roster resolution + ref-integrity + remoteExec) shared by sema-registry, sema-server, and the TOC desktop/CLI. One source of truth. (Renamed at 1.0.0 from @sema-agent/registry-core — name and docs only, no behavior change; note the range moves 0.19.0 -> 1.0.0, so a dependency must become ^1.0.0. The old names @sema-agent/registry-core <=0.19.0 and @sema-ai/registry-core remain published, frozen, for migration.)",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",