@sema-agent/settings-schema 1.3.0 → 1.4.1

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/dist/types.js CHANGED
@@ -229,7 +229,7 @@ export const ModelCost = z.object({
229
229
  cacheWrite: z.number().finite().nonnegative().default(0),
230
230
  });
231
231
  // ── Domain: models (MVP, fully specified) ─────────────────────────────────────
232
- // Maps 1:1 onto sema-server's config.models / config.roles / @-model allowlist (core 1.24 role
232
+ // Maps 1:1 onto the worker service's config.models / config.roles / @-model allowlist (core 1.24 role
233
233
  // map). `name` is the catalog key AND the `@name` handle; `id` is the gateway's model id.
234
234
  // ── sealed-box 密钥托管(0.10.5,产品拍板铁律修订:「不存 secret 值」精化为「不存**可解明文**」)──
235
235
  // center 可存"不可解密的密文":浏览器端用执行域公钥 libsodium sealed-box(crypto_box_seal,X25519+
@@ -534,7 +534,7 @@ export const ScenarioEntry = z.object({
534
534
  * not an enum: the worker registry is the authority (unknown → skip+warn there), so the center never needs
535
535
  * a release just to NAME a toolset a newer service ships. */
536
536
  toolset: z.string().regex(/^[a-z][a-z0-9-]{0,63}$/, "toolset key (lowercase slug)"),
537
- // ScenarioEntry.prompt 已废弃(clay 亲裁 2026-07-17,board [1051]③a;第二评审独立同判「second
537
+ // ScenarioEntry.prompt 已废弃(产品裁定 2026-07-17,[ref]③a;第二评审独立同判「second
538
538
  // registry field would become an ambiguous precedence channel」):配置域 prompt 单入口=center
539
539
  // prompts binding(scenario 维度);引擎 API 层 TaskSpec.systemPrompt 保留(不同层,勿混)。
540
540
  // 现网无用户直删;z.object 非 strict ⇒ 存量数据带 prompt 被 strip,parse 不炸。
@@ -670,7 +670,7 @@ export const RuntimeConfig = z.object({
670
670
  maxPrincipalCostUsd: z.number().finite().nonnegative().optional(),
671
671
  costQuotaWindowSec: z.number().int().positive().optional(),
672
672
  });
673
- // ── Domain: limits (cost/限流族的热更新发布位 — 0.19.0, sema-server #322 批1) ──────
673
+ // ── Domain: limits (cost/限流族的热更新发布位 — 0.19.0, 消费方 #322 批1) ──────
674
674
  // 为什么是**新顶层域**而不是往 `runtime` 里加键(消费方亲验,不是推测):`RuntimeConfig` 是 zod 默认的
675
675
  // `"strip"` 对象,`runtime` 文档里的未知键在两条腿上都被剥掉(实测 `{rateLimitPerMin:7,
676
676
  // toolResultTtlSec:999}` → `{rateLimitPerMin:7}`);而未知**顶层域**在 wire 腿是 open-world verbatim
@@ -734,15 +734,23 @@ export const LimitsConfig = z.object({
734
734
  toolResultTtlSec: z.number().finite().optional(),
735
735
  })
736
736
  // 🔴 **开放世界(`.passthrough()`)** —— 与 {@link PrincipalCapsWire} 的 `runtimeCaps`/`budget` 逐字
737
- // 同判据:本域的键集**按消费方的发布节奏生长**(限额面是分批接线的),而消费方的 registry-core 依赖是
738
- // `^0.x` 段(0.x 的脱字号不跨 minor)。默认的 strip 会让「消费方新增了一个限额键、契约层版号还没跟上」
739
- // 变成**键被静默剥掉 ⇒ 那条天花板悄悄回落到部署 env 底** —— 正是本域要根治的那种病换了个位置复发。
740
- // 已知键照旧严判结构(坏值 ⇒ 本域 throw 档 ⇒ 消费方 fail-static 在上一份好配置);未知键原样承运,
741
- // 由消费方(键表在它手里)决定认或不认。附带收益:老 TOC 编辑新 center limits 文档时不会把自己
742
- // 不认识的键写没了(strip 语义下 writeDomainFile 会真的丢键)
743
- // **承运 ≠ 沉默**(codex 复审 round2 F2):未知成员会被 `"unknown-keys-carried"` 警告逐个点名
744
- // (config-fns 的已知键表由本 schema shape 单源派生)——拼错的键名于是有人说,而值仍然到得了
745
- // 消费方手里,两头都不吃亏。
737
+ // 同判据。**1.x 语境下的论证**(1.0.0 改名后重写:此前的理由建立在 0.19 时代「脱字号 range 不跨
738
+ // minor」的前提上,改名进 1.x 后 `^1.x` minor,那条前提失效;决策本身不变,理由如下):
739
+ // · 本域的键集**按消费方的发布节奏生长**(限额面是分批接线的:消费方先在自己那侧开一个新限额键、
740
+ // env 底,契约层随后才补声明)。「消费方认识一个键」与「本包声明了它」之间恒有一个窗口,窗口
741
+ // 的长短由**发包与提货**决定,与依赖 range 的写法无关:消费方要么精确 pin(server `1.3.0` 形),
742
+ // 要么 `^1.x` 但被 lockfile 钉住(cli 形)——两种姿势下新声明都要等一次显式提货才到消费方手里;
743
+ // 「`^1.x` 会跨 minor」只说明解析时允许,不说明它已经发生。
744
+ // · 默认的 strip 会把这个窗口变成**键被静默剥掉 那条天花板悄悄回落到部署 env 底** —— 正是本域
745
+ // 要根治的那种病换了个位置复发。已知键照旧严判结构(坏值 ⇒ 本域 throw 档 ⇒ 消费方 fail-static
746
+ // 在上一份好配置);未知键原样承运,由消费方(键表在它手里)决定认或不认。
747
+ // · 附带收益:老编辑器(TOC)写回新 center 的 limits 文档时不会把自己不认识的键写没了(strip 语义下
748
+ // writeDomainFile 会真的丢键)。
749
+ // · **承运 ≠ 沉默**(对抗复审 复审 round2 F2):未知成员会被 `"unknown-keys-carried"` 警告逐个点名
750
+ // (config-fns 的已知键表由本 schema 的 shape 单源派生)——拼错的键名于是有人说,而值仍然到得了
751
+ // 消费方手里,两头都不吃亏。
752
+ // · 失效条件(如实):若限额面将来收敛为「契约先行、消费方只读已声明键」,本论证不再成立;届时改回
753
+ // 默认 strip / `.strict()` 是**行为面收紧**(承运 → 剥键),须走 BREAKING 通道,不许随文案批顺手改。
746
754
  .passthrough();
747
755
  // ── Domain: governance (治理三件 — split out of runtime, EXPERT-REDESIGN §5) ──────
748
756
  // The governance trio an operator tunes via publish: autonomy ladder + command guardrails + approval-required
@@ -904,6 +912,21 @@ export const EntitlementRuntimeCaps = z.object({
904
912
  * classifier leg itself. */
905
913
  autoMode: z.boolean().optional(),
906
914
  allowUltracode: z.boolean().optional(),
915
+ /** Per-principal governance of the session memory-capture OPT-OUT — the consumer's `RuntimeCaps.allowMemoryOptOut`
916
+ * (design/383 §3.1): may this principal declare `memory.capture: "off"` for a run? `false` = the deployment
917
+ * REQUIRES capture for this principal (compliance / knowledge retention) — the declaration is refused, terminal
918
+ * (`memory.capture_optout_denied`). `true` = explicitly no per-principal restriction. Three-state, ABSENT ≠ false:
919
+ * ABSENT = center says nothing → key NOT emitted on the wire (never coined into a verdict). The consumer's
920
+ * fault-transit contract for THIS member runs opposite to the `{allowWorkflows:false}` degrade habit: a resolver
921
+ * FAULT must surface as ABSENT (or a throw), never as a stand-in `false` — on the privacy axis "deny" is the
922
+ * IRREVERSIBLE side (capturing a session the user said not to capture), so the default/`open` posture reads a
923
+ * fault as "opt-out honoured + named disclosure", and a `governed` posture reads it as "verdict missing ⇒
924
+ * refuse the run". The consumer composes this key from its own local grant store FIRST and the center value
925
+ * second. Compose polarity across bundles is the allow* family's: `false` (capture required) wins — see
926
+ * RUNTIME_CAP_STRICT; the consumer's posture arms are orthogonal to that merge. Consumer-side: the center value
927
+ * reaches the engine caps only once the consumer's cap mapping picks this member up; until then its local grant
928
+ * store is the sole verdict source (the key parses and is carried on the wire from this version on). */
929
+ allowMemoryOptOut: z.boolean().optional(),
907
930
  /** Force this principal's run onto durable-approval (per-tool gate) — center's fleet-wide handle for CC-faithful
908
931
  * per-tool interception (K-3, core confirmed it consumes this). Polarity is INVERTED vs the allow* caps:
909
932
  * here `true` is the STRICT/tightening value (forcing the gate ON), so it wins on compose — see RUNTIME_CAP_STRICT.
@@ -922,6 +945,7 @@ export const RUNTIME_CAP_STRICT = {
922
945
  allowObservers: false,
923
946
  autoMode: false,
924
947
  allowUltracode: false,
948
+ allowMemoryOptOut: false,
925
949
  forceDurableGate: true,
926
950
  };
927
951
  /** A named entitlement bundle (a `tier`, or a composable `role`). Bind a `roster` to inherit its model/skill/mcp
@@ -1061,7 +1085,7 @@ export const PrincipalOrgMemoryWire = z
1061
1085
  scopes: z.record(z.string().regex(/^org:\S+$/, "orgMemory scope must be an `org:` scope string"), z.object({ write: z.boolean().optional() }).strict()),
1062
1086
  })
1063
1087
  .strict();
1064
- /** `?principal=` caps wire **整体**的读边界 schema(center 铸、server 消费同源锚;clay 2026-08-05:
1088
+ /** `?principal=` caps wire **整体**的读边界 schema(center 铸、server 消费同源锚;产品令 2026-08-05:
1065
1089
  * 网络边界读入必 schema,禁裸 as-cast)。**逐面容错极性是裁定过的语义**,严格化会翻转极性的面在此
1066
1090
  * 显式保持宽读(每面注明所依裁定),不许硬转严:
1067
1091
  * - `runtimeCaps`/`budget`(治理面,fail-closed):严格校验已知键 + **passthrough 未知键**——新 center
@@ -1352,13 +1376,13 @@ export const CollabConfig = z.object({ templates: z.array(CollabTemplate).defaul
1352
1376
  });
1353
1377
  });
1354
1378
  // ── Domain: workers (声明式编排 — service design/22, docs/ORCHESTRATOR.md) ──────
1355
- // 每个 worker = 一个 sema-server 实例的声明(一份 env profile)。sema-registry 存 DESIRED(逻辑参数
1379
+ // 每个 worker = 一个 worker 服务实例的声明(一份 env profile)。sema-registry 存 DESIRED(逻辑参数
1356
1380
  // + secret 的 env-NAME 引用,**绝不存 secret 值**);一个 per-host reconciler 拉取后在宿主机本地解析
1357
1381
  // secret → 渲染 docker compose → up/down 收敛。worker spec ↔ service env 的精确映射见 worker-env.ts。
1358
1382
  //
1359
1383
  // 安全(service design/22 经评审):
1360
1384
  // - 不提供任何挂载字段 → 杜绝 hostPath 攻击向量(合法镜像+合法 secretRef+恶意挂载偷宿主机文件)。
1361
- // - image 由 reconciler 按白名单强制(这里 default sema-server:latest)。
1385
+ // - image 由 reconciler 按白名单强制(默认值见下方 `image` 字段——公开镜像名,与 npm 包名同源)。
1362
1386
  // - secretRefs 只是 env-NAME;真值在宿主机,reconciler 解析,永不进此库/网络/日志。
1363
1387
  // 时长串:整数 + 单位 s/m/h/d(如 "30m"/"2h"/"7d")。lib/lifecycle/duration.ts 负责解析成 ms。
1364
1388
  const DURATION = z.string().regex(/^\d+(s|m|h|d)$/, 'duration like "30m", "2h", "7d" (units s/m/h/d)');
@@ -1774,7 +1798,7 @@ export const SkillsManifestSchema = z.object({
1774
1798
  });
1775
1799
  // ── Domain: mcp (MCP server 注册表 — docs/MCP-SKILLS.md, Roadmap B1) ────────────
1776
1800
  // sema-registry = an MCP-server REGISTRY: a scenario mounts a subset by `scenarios[]`. Maps onto core's
1777
- // `McpServerSpec = { name, transport: {kind:"stdio",command,args?,env?} | {kind:"http",url,headers?}, allowTools? }`.
1801
+ // `McpServerSpec = { name, transport: {kind:"stdio",command,args?,env?} | {kind:"http",url,headers?}, allowTools?, contentOrigin? }`.
1778
1802
  // SECRET BOUNDARY (§3.1, design/15): a transport's env/header VALUES may be secret → the center stores only
1779
1803
  // env-NAME references (`envRefs`/`headerRefs`: target-key → an ENV_NAME in the SERVICE's env). The service's
1780
1804
  // `resolveSpec` reads the real values from its own environment — this DB never holds a credential. Like
@@ -1824,6 +1848,14 @@ export const McpTransport = z.discriminatedUnion("kind", [
1824
1848
  principalHeader: HTTP_HEADER_NAME.optional(),
1825
1849
  }),
1826
1850
  ]);
1851
+ /** The closed word table for {@link McpServerSpec}.contentOrigin — a MIRROR literal of the engine's
1852
+ * `ToolContentOrigin`. This package deliberately carries no engine dependency axis (the same ruling that made
1853
+ * PROMPT_ARTIFACT_LIMITS a mirror constant), so the cross-package enforcement point is NOT here: the consumer
1854
+ * compile-pins its own copy against the engine type in both directions (AssertNever pair), and on picking this
1855
+ * package up it should add a same-source assertion against THIS table too — it is the one repo holding both
1856
+ * dependencies. Inside this repo the table is pinned as a literal only (a drift surfaces at the consumer's
1857
+ * pickup gate, never silently on the wire — an out-of-table word is rejected at parse on both sides). */
1858
+ export const MCP_CONTENT_ORIGINS = ["local", "execution", "external"];
1827
1859
  export const McpServerSpec = z.object({
1828
1860
  name: z.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/i, "letters/digits/._- , no spaces"),
1829
1861
  transport: McpTransport,
@@ -1839,6 +1871,23 @@ export const McpServerSpec = z.object({
1839
1871
  * OFF = fail-closed: only an opted-in server may prompt the user mid-tool-call. Service forwards this into the
1840
1872
  * core spec (was read untyped as `(s as {elicitation?})` before this field — service ask). */
1841
1873
  elicitation: z.boolean().default(false),
1874
+ /** Trust-origin declaration for EVERYTHING this server's tools return — maps 1:1 onto the engine's
1875
+ * `McpServerSpec.contentOrigin` (closed word table {@link MCP_CONTENT_ORIGINS}):
1876
+ * · `"local"` — content the deployment itself produces (its own process / socket / service);
1877
+ * · `"execution"` — execution-environment output (the consumer's `execIsExternalContent` knob still applies);
1878
+ * · `"external"` — third-party content, declared EXPLICITLY: a PIN, not a no-op — it cancels any by-name
1879
+ * trusted-tool exemption the consumer would otherwise grant this server.
1880
+ * ABSENT = the engine's pre-declaration behaviour (protocol namespaces are judged external, fail-closed); the key is
1881
+ * `.optional()` with NO default so absence is never coined into a word. It affects memory-write governance (the
1882
+ * "touched foreign content" session mark) only — no gate / policy / roster effect.
1883
+ * 🔴 A TRUST statement, not a routing hint: the class is boundary-relative and independent of transport or
1884
+ * address (a stdio child can be an untrusted package, a loopback URL can be your own service), so nothing here
1885
+ * infers it from topology. It is a DEPLOYMENT-plane key: the publication leg (`config.d/mcp.json` locally,
1886
+ * `/effective.mcp` remotely) is its home; the consumer applies the same word-table gate on its request plane.
1887
+ * Before this key existed both legs stripped the declaration (default-strip object): the operator believed they
1888
+ * declared `"local"`, the engine read ABSENT. A value outside the table is REJECTED at parse — a mistyped trust
1889
+ * word must never be read as a legal one (the consumer's gate is equally severe: no case-folding). */
1890
+ contentOrigin: z.enum(MCP_CONTENT_ORIGINS).optional(),
1842
1891
  notes: z.string().optional(),
1843
1892
  });
1844
1893
  export const McpConfig = z
@@ -1858,7 +1907,7 @@ export const McpConfig = z
1858
1907
  // Plugins are an EXECUTABLE surface (commands/hooks/MCP ride in) → write-gated to `publisher` like skills/
1859
1908
  // mcp (DOMAIN_WRITE_ROLE) and capped per-principal by entitlement `visiblePlugins` (deny-wins, same
1860
1909
  // mechanics as visibleMcp/visibleSkills — B1: a personal plugin ref is a net-new capability).
1861
- /** A plugin/marketplace ORIGIN string: **https only**(contract freeze board [1362] tightening-① /
1910
+ /** A plugin/marketplace ORIGIN string: **https only**(contract freeze [ref] tightening-① /
1862
1911
  * [1366]①,0.10.23 单真源修:此前这条规则只落在 web UI 层的表单校验,API 直写仍放行任意形——
1863
1912
  * 同一条规则两处真源,API 这处敞了 8 天)。`ssh:`/`scp 形`/`git:`/`file:`/裸 `http:`/`owner/repo`
1864
1913
  * shorthand 一律拒:worker 只 clone 声明里的 url,能入库的形必须是消费端能安全物化的形;国内镜像
@@ -1923,7 +1972,7 @@ export const PluginsConfig = z
1923
1972
  });
1924
1973
  });
1925
1974
  // ── The domain registry ───────────────────────────────────────────────────────
1926
- // ═══ prompts 域(center prompts 战役 v1;board [1050] 五定夺/[1051] clay 产品终稿/[1057] M2 下发形)═══
1975
+ // ═══ prompts 域(center prompts 战役 v1;[ref] 五定夺/[1051] 产品终稿/[1057] M2 下发形)═══
1927
1976
  // schema=编译目标非用户界面([1051]⓪):三卡(角色卡/场景说明/行为守则)是 web UI 编译层心智,
1928
1977
  // 本契约只承载编译产物(typed 声明)与 binding。单真源纪律:存储/解析在 center(sema-web),
1929
1978
  // 消费投影在 server(声明数组逐字喂 core typed stableBlocks 钩子),schema 恒在本仓(零第二份)。
@@ -1938,7 +1987,7 @@ export const PromptTextDeclaration = z.object({
1938
1987
  * 整包 contentDigest 对账。契约层显式声明防非 strict parse strip 断锚([1062]③)。 */
1939
1988
  contentHash: z.string().regex(/^sha256:[0-9a-f]{64}$/).optional(),
1940
1989
  });
1941
- /** 三维 binding(board [1050]①a:v1=global/worker/scenario,砍 model 维——per-model 微调走
1990
+ /** 三维 binding([ref]①a:v1=global/worker/scenario,砍 model 维——per-model 微调走
1942
1991
  * ModelEntry.promptGuidance 专用通道)。covering 语义([1050]①b,解析只在 center 一处):
1943
1992
  * 同 slot **层间整体替换**(胜出层该 slot 声明数组整体替换低层同 slot 全部条目;层内数组保序,
1944
1993
  * 与 core assemble「declaration order preserved」无缝);层序 scenario > worker > global。
@@ -1963,7 +2012,7 @@ export const PromptBinding = z.object({
1963
2012
  if (b.level === "global" && (b.worker || b.scenario))
1964
2013
  ctx.addIssue({ code: "custom", message: "level=global must not carry a selector" });
1965
2014
  });
1966
- // ═══ prompts 域 catalog 轴(0.10.20;board [985]a/[1242]/[1248] 过轮定稿;core design/148 S1 seam)═══
2015
+ // ═══ prompts 域 catalog 轴(0.10.20;[ref]a/[1242]/[1248] 过轮定稿;core design/148 S1 seam)═══
1967
2016
  // 形 = core 1.336+ 的 `PublishedPromptArtifactEnvelope`(sema-core src/prompt-assembly/artifact.ts,
1968
2017
  // [1236]① seam 形即终形):v1 骑 declaration 轴的 immutable by-digest artifact——协议 §5.1 全 catalog
1969
2018
  // (overlays/profiles/modelPolicies/bindings)是后续 additive 切片,本轴到时 additive 扩,零迁移。
@@ -2029,7 +2078,7 @@ export const PromptsConfig = z.object({
2029
2078
  * 免 covering 解析,两 lane 自此同形(结束 local lane prompts 不透传的过渡态,[1234]① 位)。 */
2030
2079
  catalog: PublishedPromptArtifactEnvelope.optional(),
2031
2080
  });
2032
- /** effective 下发形(board [1057] 逐字;`GET /api/config/effective?worker=` 的 additive `prompts`
2081
+ /** effective 下发形([ref] 逐字;`GET /api/config/effective?worker=` 的 additive `prompts`
2033
2082
  * key)。语义定死([1057]①):packId=`center:`+contentDigest 前 12(纯内容寻址,无 rev);
2034
2083
  * contentDigest=canonical JSON 全量 sha256 非盐化(operator 对账位,digest 函数在 /node 绑定
2035
2084
  * `promptsContentDigest`);sections=covering 已走完的基线终形(worker/core 零解析义务);
@@ -2069,8 +2118,8 @@ export const DOMAIN_SCHEMAS = {
2069
2118
  entitlement: EntitlementConfig,
2070
2119
  execution: ExecutionPolicy, // 0.8.0: S3 execution-lane policy (scope 级; client-advisory until service gate)
2071
2120
  projects: ProjectsConfig, // 0.10.0: 142-S3 project identity ledger + 无仓铸造权威 (scope=tenant 轴; NOT portable — 租户身份数据)
2072
- prompts: PromptsConfig, // 0.10.18: center prompts 战役 v1(board [1050]/[1051]/[1057];写门/store 携带解锁=[1064]②)
2073
- limits: LimitsConfig, // 0.19.0: cost/限流族热更新发布位(sema-server #322 批1;本地腿闭集缺口的源头修复)
2121
+ prompts: PromptsConfig, // 0.10.18: center prompts 战役 v1([ref] 五定夺/产品终稿/M2 下发形;写门/store 携带解锁=[1064]②)
2122
+ limits: LimitsConfig, // 0.19.0: cost/限流族热更新发布位(消费方 #322 批1;本地腿闭集缺口的源头修复)
2074
2123
  // integrations + access: DELETED in 0.6.0 (EXPERT-REDESIGN §9) — parseDomain/effective/路由随之 404。
2075
2124
  };
2076
2125
  // ── Effective-config PROVENANCE (the unified shape TOC `sema config --effective` and TOB `/effective?principal=`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/settings-schema",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
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",