weir-harness 1.2.3 → 2.1.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 (117) hide show
  1. package/cordis.patch.yml +7 -18
  2. package/lib/client.capability-badge.js +2 -0
  3. package/lib/client.capability-manager.js +46 -179
  4. package/lib/client.capability-model.js +428 -3
  5. package/lib/client.file-lease-badge.js +307 -0
  6. package/lib/client.file-lease-tool-model.js +189 -0
  7. package/lib/client.file-lease-tool-view.js +434 -0
  8. package/lib/client.js +124 -36
  9. package/lib/client.mcp-manager.js +563 -0
  10. package/lib/client.settings-page.js +10 -34
  11. package/package.json +2 -2
  12. package/skills/mcp-server-setup/SKILL.md +124 -0
  13. package/src/capabilities/apply-engine.js +18 -4
  14. package/src/capabilities/capability-remote.js +296 -45
  15. package/src/capabilities/initial-selection.js +3 -2
  16. package/src/capabilities/lifecycle-preload.js +69 -11
  17. package/src/capabilities/lifecycle-snapshot.js +127 -39
  18. package/src/capabilities/mcp-admission.js +2 -2
  19. package/src/capabilities/mcp-config.js +344 -0
  20. package/src/capabilities/mcp-credentials.js +180 -0
  21. package/src/capabilities/mcp-diagnostics.js +169 -0
  22. package/src/capabilities/mcp-drain.js +18 -4
  23. package/src/capabilities/mcp-facade.js +3 -3
  24. package/src/capabilities/mcp-gate.js +51 -52
  25. package/src/capabilities/mcp-install-tool.js +276 -0
  26. package/src/capabilities/mcp-manager.js +368 -142
  27. package/src/capabilities/mcp-mount.js +219 -79
  28. package/src/capabilities/mcp-operations.js +359 -0
  29. package/src/capabilities/mcp-process.js +43 -0
  30. package/src/capabilities/mcp-registry.js +74 -60
  31. package/src/capabilities/mcp-runtime.js +103 -0
  32. package/src/capabilities/read-payloads.js +8 -3
  33. package/src/capabilities/skill-applicability.js +185 -0
  34. package/src/capabilities/skill-builtin-migration.js +2 -1
  35. package/src/capabilities/skill-selection-plugin.js +161 -56
  36. package/src/capabilities/store/checked-contract.d.ts +2 -2
  37. package/src/capabilities/store/fs-adapter.js +2 -3
  38. package/src/context-guard/index.js +2 -2
  39. package/src/delegate/supervision-mount.js +5 -5
  40. package/src/delegate/tool.js +2 -10
  41. package/src/file-lease/client.js +1299 -0
  42. package/src/file-lease/index.js +34 -0
  43. package/src/file-lease/lineage.js +207 -0
  44. package/src/file-lease/node-contracts.d.ts +42 -0
  45. package/src/file-lease/plugin.js +1013 -0
  46. package/src/file-lease/singleton.js +155 -0
  47. package/src/file-lease/spawn.js +62 -0
  48. package/src/file-lease/tools.js +847 -0
  49. package/src/file-lease/view.js +144 -0
  50. package/src/file-lease/watch.js +473 -0
  51. package/src/hashline-edit/index.js +76 -42
  52. package/src/hashline-edit/write-tool.js +90 -0
  53. package/src/intent-gate/index.js +29 -12
  54. package/src/intent-gate/matcher.js +36 -2
  55. package/src/lease-broker/entry.js +98 -0
  56. package/src/lease-broker/node-contracts.d.ts +63 -0
  57. package/src/lease-broker/protocol.js +237 -0
  58. package/src/lease-broker/server.js +402 -0
  59. package/src/lease-broker/state.js +482 -0
  60. package/src/lsp/tools.js +137 -27
  61. package/src/process/index.js +4 -2
  62. package/src/settings/index.js +2 -44
  63. package/src/settings/sections.js +2 -24
  64. package/src/shared/audit.js +9 -1
  65. package/src/shared/child-scope.js +2 -0
  66. package/src/todo-driver/index.js +1 -6
  67. package/src/worktree/cards.js +6 -21
  68. package/src/worktree/lane-core.js +1 -19
  69. package/src/worktree/lane-workflows.js +11 -32
  70. package/src/worktree/rules.js +1 -1
  71. package/lib/client.edit-lock-maintenance.js +0 -592
  72. package/lib/client.edit-lock-panel.js +0 -405
  73. package/src/edit-lock/adapter.js +0 -57
  74. package/src/edit-lock/admin-ledger.js +0 -149
  75. package/src/edit-lock/admin-recovery.js +0 -151
  76. package/src/edit-lock/admission.js +0 -82
  77. package/src/edit-lock/binding-attestation.js +0 -29
  78. package/src/edit-lock/binding-guidance.js +0 -72
  79. package/src/edit-lock/binding-machine.js +0 -312
  80. package/src/edit-lock/call-context.js +0 -65
  81. package/src/edit-lock/derived-facts.js +0 -67
  82. package/src/edit-lock/domain.js +0 -165
  83. package/src/edit-lock/domains.js +0 -170
  84. package/src/edit-lock/host.js +0 -149
  85. package/src/edit-lock/incarnations.js +0 -150
  86. package/src/edit-lock/index.js +0 -1303
  87. package/src/edit-lock/inspect.js +0 -219
  88. package/src/edit-lock/lifecycle.js +0 -579
  89. package/src/edit-lock/limits.js +0 -61
  90. package/src/edit-lock/maintenance.js +0 -310
  91. package/src/edit-lock/manager.js +0 -875
  92. package/src/edit-lock/negotiation.js +0 -132
  93. package/src/edit-lock/operation-history.js +0 -214
  94. package/src/edit-lock/peer-client.js +0 -86
  95. package/src/edit-lock/peer-transport.js +0 -76
  96. package/src/edit-lock/peer.js +0 -87
  97. package/src/edit-lock/publisher.js +0 -191
  98. package/src/edit-lock/read-authority.js +0 -101
  99. package/src/edit-lock/recovery.js +0 -139
  100. package/src/edit-lock/remote.js +0 -362
  101. package/src/edit-lock/request-binding.js +0 -42
  102. package/src/edit-lock/request-data.js +0 -40
  103. package/src/edit-lock/reservation.js +0 -301
  104. package/src/edit-lock/reserved-runtime.js +0 -45
  105. package/src/edit-lock/resource-identity.js +0 -130
  106. package/src/edit-lock/runtime.js +0 -145
  107. package/src/edit-lock/settle.js +0 -144
  108. package/src/edit-lock/snapshot.js +0 -175
  109. package/src/edit-lock/stale-sweep.js +0 -120
  110. package/src/edit-lock/state.js +0 -462
  111. package/src/edit-lock/store.js +0 -327
  112. package/src/edit-lock/tool-scope.js +0 -29
  113. package/src/edit-lock/view.js +0 -170
  114. package/src/edit-lock/write-scopes.js +0 -48
  115. package/src/edit-lock/write-tool.js +0 -45
  116. package/src/shared/delegate-edit-lifecycle.js +0 -36
  117. package/src/worktree/authority.js +0 -56
@@ -47,7 +47,7 @@ window.__ModuleLoader__.load({
47
47
  // scatter). test/client-settings-page.test.js pins this list to
48
48
  // RESTART_KEYS from src/settings/sections.js, so a declaration
49
49
  // change on either side cannot drift the two sides.
50
- const RESTART_FIELDS = ["intentGateClassifier", "intentGateProvider", "intentGateModel", "intentGateReasoningEffort", "intentGateTimeoutMs", "jevEndpoint", "jevModel", "jevApiKeyEnv", "todoEnabled", "todoMaxConsecutive", "todoErrorRetryMax", "todoErrorBackoffBaseMs", "todoErrorBackoffCapMs", "hashlineHideStockEdit", "editLockEnabled"];
50
+ const RESTART_FIELDS = ["intentGateClassifier", "intentGateProvider", "intentGateModel", "intentGateReasoningEffort", "intentGateTimeoutMs", "jevEndpoint", "jevModel", "jevApiKeyEnv", "todoEnabled", "todoMaxConsecutive", "todoErrorRetryMax", "todoErrorBackoffBaseMs", "todoErrorBackoffCapMs", "hashlineHideStockEdit"];
51
51
  // ---- condition declaration shorthands (frozen pure data, D2) ----
52
52
  const enabledWhen = (key) => Object.freeze({ key, equals: true });
53
53
  const CLASSIFIER_LLM = Object.freeze({ key: "intentGateClassifier", equals: "llm" });
@@ -107,10 +107,8 @@ window.__ModuleLoader__.load({
107
107
  ] },
108
108
  { id: "editing", fields: [
109
109
  { field: "hashlineHideStockEdit", kind: "boolean" },
110
- { field: "editLockEnabled", kind: "boolean" },
111
- { field: "editLockAutoResume", kind: "boolean", parent: "editLockEnabled", when: enabledWhen("editLockEnabled") },
112
- { field: "editLockStaleSweep", kind: "boolean", parent: "editLockEnabled", when: enabledWhen("editLockEnabled") },
113
- // Diagnostics must remain reachable when enforcement is off.
110
+ // Edit Lock diagnostics seat: the composition root binds no editor
111
+ // for it (edit-lock rebuild), so the row renders nothing.
114
112
  { kind: "custom", slot: "editLockMaintenance" }
115
113
  ] },
116
114
  { id: "worktree", fields: [
@@ -194,14 +192,6 @@ window.__ModuleLoader__.load({
194
192
  guardSoftThreshold: 0.72,
195
193
  guardHardThreshold: 0.88,
196
194
  hashlineHideStockEdit: true,
197
- editLockEnabled: true,
198
- editLockHoldDefaultMinutes: 30,
199
- editLockHoldSingleMaxMinutes: 30,
200
- editLockHoldCumulativeMaxMinutes: 120,
201
- editLockNudgeAttempts: 2,
202
- editLockNudgeFallback: "release",
203
- editLockAutoResume: true,
204
- editLockStaleSweep: true,
205
195
  robashEnabled: true,
206
196
  lspEnabled: false,
207
197
  lspIdleMs: 600000,
@@ -597,7 +587,6 @@ window.__ModuleLoader__.load({
597
587
  const childLabelStyle = { ...labelStyle, fontSize: "13px" };
598
588
  const labelTextStyle = { display: "inline-flex", alignItems: "center", gap: "6px" };
599
589
  const hintStyle = { fontSize: "12px", lineHeight: "16px", color: "var(--dsw-alias-label-secondary)" };
600
- const optOutStyle = { ...hintStyle, color: "var(--dsw-alias-state-warn-primary)", borderLeft: "2px solid var(--dsw-alias-state-warn-primary)", paddingLeft: "8px", marginTop: "4px" };
601
590
  // Card-based group sections (design D7): layered background, l1 border,
602
591
  // radius, the group title inside the card top, hairline l2 separators
603
592
  // between rows. Colors only from theme tokens; geometry hardcoded.
@@ -637,9 +626,6 @@ window.__ModuleLoader__.load({
637
626
  react_jsx_runtime.jsxs("div", { style: labelGroupStyle, children: [
638
627
  labelWithTag(descriptor, t, depth > 0 ? childLabelStyle : labelStyle),
639
628
  react_jsx_runtime.jsx("span", { style: hintStyle, children: t(`${descriptor.field}Hint`) }),
640
- descriptor.field === "editLockEnabled" && resolveEffectiveValue(descriptor, field, field?.saved) !== true
641
- ? react_jsx_runtime.jsx("span", { style: optOutStyle, role: "alert", children: t("editLockOptOutWarning") })
642
- : null
643
629
  ] }),
644
630
  react_jsx_runtime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "4px" }, children: [
645
631
  descriptor.kind === "boolean" ? react_jsx_runtime.jsx(primitives.Switch, {
@@ -925,9 +911,13 @@ window.__ModuleLoader__.load({
925
911
  }));
926
912
  }
927
913
  if (node.slot === "editLockMaintenance") {
928
- // Edit Lock maintenance: profile-wide switch status and
929
- // read-only authority diagnostics (read-only).
930
- return wrap(react_jsx_runtime.jsx(props.editors.EditLockMaintenanceField, { t }));
914
+ // The Edit Lock maintenance seat is GONE (edit-lock rebuild):
915
+ // the composition root no longer binds an editor for it, and
916
+ // the row disappears with the settings keys. An unbound seat
917
+ // must leave the card intact instead of rendering an undefined
918
+ // element type, so the row simply renders nothing here.
919
+ const MaintenanceField = props.editors?.EditLockMaintenanceField;
920
+ return typeof MaintenanceField === "function" ? wrap(react_jsx_runtime.jsx(MaintenanceField, { t })) : null;
931
921
  }
932
922
  // notifyPermissions: the macOS-only permission entry
933
923
  return wrap(react_jsx_runtime.jsx(props.editors.NotifyPermissionsField, { t }));
@@ -1113,13 +1103,6 @@ window.__ModuleLoader__.load({
1113
1103
  guardHardThresholdHint: "Above this fill ratio compaction is requested (default 0.88; not a completion guarantee).",
1114
1104
  hashlineHideStockEdit: "Anchor editing only",
1115
1105
  hashlineHideStockEditHint: "Hides the built-in edit tool; file edits go through hash_edit anchors only.",
1116
- editLockEnabled: "Edit Lock",
1117
- editLockEnabledHint: "Sessions take turns editing the same files instead of overwriting each other.",
1118
- editLockOptOutWarning: "Edit Lock is set to off. After saving, restart DeepSeek Harness to apply the change. Once enforcement is off, ordinary editing is UNPROTECTED and can overwrite another session's work. Old fences and history are retained and may block files when you re-enable Edit Lock.",
1119
- editLockAutoResume: "Message resumes editing",
1120
- editLockAutoResumeHint: "After a session is stopped, sending any message restores its editing right and re-confirms its kept files (default on).",
1121
- editLockStaleSweep: "Sweep stale locks",
1122
- editLockStaleSweepHint: "Locks whose target file was deleted or moved are re-verified and released as you send messages (default on).",
1123
1106
  worktreeEnabled: "Worktree lanes",
1124
1107
  worktreeEnabledHint: "Lets the assistant work in isolated git worktrees: lane tools, the /worktree command, and the lanes panel.",
1125
1108
  worktreeAutoSetup: "Auto-install in new lanes",
@@ -1248,13 +1231,6 @@ window.__ModuleLoader__.load({
1248
1231
  guardHardThresholdHint: "上下文用量超过该比例时请求立即压缩(默认 0.88,不保证压缩完成)。",
1249
1232
  hashlineHideStockEdit: "仅用锚点编辑",
1250
1233
  hashlineHideStockEditHint: "隐藏内置 edit 工具,文件修改一律走 hash_edit 锚点编辑。",
1251
- editLockEnabled: "编辑锁",
1252
- editLockEnabledHint: "多个会话轮流编辑同一批文件,而不是互相覆盖。",
1253
- editLockOptOutWarning: "编辑锁已设为关闭。保存后须重启 DeepSeek Harness 才会生效。停用强制执行后,普通编辑不受保护,可能覆盖其他会话的工作。旧围栏与历史仍会保留,重新启用编辑锁时可能再次阻塞文件。",
1254
- editLockAutoResume: "发消息即恢复编辑",
1255
- editLockAutoResumeHint: "会话被停止后,发送任意消息即自动恢复其编辑权并确认保留的文件(默认开启)。",
1256
- editLockStaleSweep: "自动清理失效锁",
1257
- editLockStaleSweepHint: "目标文件已被删除或移动的锁,在你发消息时自动复核并释放(默认开启)。",
1258
1234
  worktreeEnabled: "Worktree 车道",
1259
1235
  worktreeEnabledHint: "让助手在隔离的 git worktree 中工作:车道工具、/worktree 命令与车道面板。",
1260
1236
  worktreeAutoSetup: "新车道自动安装依赖",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weir-harness",
3
- "version": "1.2.3",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "Weir \u2014 orchestrator-centric DSH agent preset: intent gate, category delegation, todo driver, pull-based background jobs, auto-compaction, hashline edit.",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "./todo-driver": "./src/todo-driver/index.js",
44
44
  "./context-guard": "./src/context-guard/index.js",
45
45
  "./hashline-edit": "./src/hashline-edit/index.js",
46
- "./edit-lock": "./src/edit-lock/index.js",
46
+ "./file-lease": "./src/file-lease/plugin.js",
47
47
  "./blackboard": "./src/blackboard/index.js",
48
48
  "./blackboard-remote": "./src/blackboard/remote.js",
49
49
  "./worktree": "./src/worktree/index.js",
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: mcp-server-setup
3
+ description: "Install, add, or configure a new MCP server with credential-safe preview, exact Host confirmation, strict discovery, and separate session Apply. Do not use for merely calling or discussing an existing MCP tool."
4
+ metadata:
5
+ short-description: Credential-safe MCP server installation
6
+ ---
7
+
8
+ # MCP Server Setup
9
+
10
+ Use this workflow for genuine requests to install, add, or configure a new MCP
11
+ server. It is not a tool-repair or general MCP discussion workflow.
12
+
13
+ ## Boundaries
14
+
15
+ - Weir supports `stdio` and `streamable-http` only. Do not propose legacy SSE,
16
+ OAuth flows, a marketplace, or adoption of an unmanaged server.
17
+ - Never ask the user to paste a token, password, private endpoint, or other
18
+ secret into chat. Use an existing DSH credential reference such as
19
+ `{ "ref": "GITHUB_TOKEN" }`. A reference may include a non-secret `prefix`.
20
+ - Every environment or HTTP-header value on this tool surface is a credential
21
+ reference. Mark each sensitive stdio argument with a reference object instead
22
+ of placing its value in a plain argument string.
23
+ - Any URL containing userinfo, a query, or a fragment is sensitive. Supply the
24
+ whole URL through a credential reference; do not put it in the plan.
25
+ - Installation never selects the server, changes a default, or Applies a
26
+ capability set. Do not imply that a mounted server is already authorized.
27
+
28
+ ## 1. Establish the plan
29
+
30
+ Read the server author's current documentation when it is available. Identify
31
+ the transport, executable or endpoint, non-secret arguments, required credential
32
+ references, working directory, and relevant timeout/reconnect options. Keep the
33
+ plan within the `mcp_install` schema. Unknown fields and cross-transport fields
34
+ are rejected.
35
+
36
+ For stdio, `command` and non-secret `args` describe the process. For
37
+ `streamable-http`, use `url` and credential-referenced `headers`; stdio fields
38
+ are forbidden. `failOnStartupError` is not configurable: initial mounting is
39
+ always strict. Discovering zero tools is still a valid result.
40
+
41
+ Example stdio plan shape (references, not secrets):
42
+
43
+ ```json
44
+ {
45
+ "action": "preview",
46
+ "plan": {
47
+ "identity": "github",
48
+ "label": "GitHub",
49
+ "transport": "stdio",
50
+ "command": "npx",
51
+ "args": ["-y", "@example/mcp-server", { "ref": "MCP_CLI_TOKEN" }],
52
+ "env": { "GITHUB_TOKEN": { "ref": "GITHUB_TOKEN" } }
53
+ }
54
+ }
55
+ ```
56
+
57
+ Example HTTP plan shape:
58
+
59
+ ```json
60
+ {
61
+ "action": "preview",
62
+ "plan": {
63
+ "identity": "private-docs",
64
+ "transport": "streamable-http",
65
+ "url": { "ref": "PRIVATE_MCP_URL" },
66
+ "headers": {
67
+ "Authorization": { "ref": "PRIVATE_MCP_TOKEN", "prefix": "Bearer " }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ## 2. Preview before installation
74
+
75
+ Call `mcp_install` with `action: "preview"` and the plan. Check the bounded
76
+ redacted plan, digest, target conflicts, namespace conflicts, capabilities, and
77
+ validation outcome. Preview does not start a process, contact the server, write
78
+ credentials or configuration, or change selection. Correct the plan and preview
79
+ again when necessary.
80
+
81
+ `mcp_install` is single-server and fresh-identity only. Do not use it to edit,
82
+ restart, remove, batch-import, or set a default. Use the management UI or the
83
+ shared `/capabilities` MCP commands for those operations.
84
+
85
+ ## 3. Let the Host obtain approval
86
+
87
+ After a clean preview, call `mcp_install` with `action: "install"` and the same
88
+ plan. The Host displays the exact redacted plan, its digest, and process or
89
+ network effects. Only the user can approve that card. There is no `confirmed`
90
+ argument; chat text, custom answers, and extra selections are not approval.
91
+
92
+ Cancellation, close, timeout, missing confirmation support, delegated callers,
93
+ ownership changes, plan changes, registry changes, and namespace changes stop
94
+ before probe or write. Report the outcome and request a fresh preview when
95
+ required; never fabricate approval or retry an unchanged conflict in a loop.
96
+
97
+ ## 4. Interpret the actual outcome
98
+
99
+ An approved install probes before registration. A failed probe does not create
100
+ a registry entry. A mounted result reports the real identity, incarnation,
101
+ generation, and bounded discovery summary.
102
+
103
+ - `mounted`: registration and strict mount succeeded.
104
+ - `saved-degraded`: configuration was saved (`saved: true`) but strict mounting
105
+ failed. This is not installation success; offer Edit, Test, or Restart.
106
+ - `owner-changed` or `indeterminate`: inspect actual registry/mount facts. Never
107
+ claim rollback or zero effects.
108
+ - `cleanup-failed` / `cleanup-unknown`: follow the recovery action; do not start
109
+ an immediate same-name replacement.
110
+
111
+ Do not run an arbitrary discovered tool as an installation test. Discovery and
112
+ the reported mount outcome are the evidence.
113
+
114
+ ## 5. Keep authorization separate
115
+
116
+ After a mounted result, direct the user to the separate **Select for this
117
+ session** action and explicit **Apply**. Installation and the optional
118
+ new-session default are independent of the current draft and accepted sets.
119
+ Editing or restarting preserves the registration incarnation; removing and
120
+ re-adding the same identity creates a new one and requires reauthorization.
121
+
122
+ Credential rotation takes effect on the next explicit probe or restart, not as
123
+ a claimed live reconnect. Local credential storage avoids registry/profile and
124
+ transcript disclosure; it is not an isolation boundary against a same-UID agent.
@@ -203,7 +203,7 @@ export function digestApplyRequest(normalized) {
203
203
  */
204
204
  export function createApplyEngine(options) {
205
205
  const {
206
- store, locateSession, inventory, provider,
206
+ store, locateSession, inventory, provider, mcpRegistry,
207
207
  fence = createAdmissionFence(),
208
208
  drain = createDrainCoordinator(),
209
209
  conditions = () => [],
@@ -365,6 +365,19 @@ export function createApplyEngine(options) {
365
365
  const missing = normalized.skills.filter(key => (byKey.get(key) ?? []).length !== 1)
366
366
  if (missing.length) return { status: 'rejected', reason: 'selected-identity-missing', step: 2, missing }
367
367
 
368
+ let mcpIncarnations
369
+ if (mcpRegistry) {
370
+ let current
371
+ try { current = await mcpRegistry.read() } catch (error) {
372
+ return { status: 'rejected', reason: `mcp-registry-unavailable:${message(error)}`, step: 2 }
373
+ }
374
+ if (current.kind !== 'ok' && current.kind !== 'absent') return { status: 'rejected', reason: `mcp-registry-unavailable:${current.kind}`, step: 2 }
375
+ const servers = current.kind === 'ok' ? current.servers : {}
376
+ const missingMcp = normalized.mcpServers.filter(identity => !servers[identity])
377
+ if (missingMcp.length) return { status: 'rejected', reason: 'unregistered-mcp', step: 2, missing: missingMcp }
378
+ mcpIncarnations = Object.fromEntries(normalized.mcpServers.map(identity => [identity, servers[identity].incarnation ?? null]))
379
+ }
380
+
368
381
  // Step 3: prepare a pure snapshot, filtered view and verified content
369
382
  // handles. Nothing is published, installed or notified here.
370
383
  let prepared
@@ -388,7 +401,7 @@ export function createApplyEngine(options) {
388
401
 
389
402
  const previousSets = record.kind === 'ok' ? safeSets(record.payload) : { skills: [], mcpServers: [] }
390
403
  const removedServers = previousSets.mcpServers.filter(server => !normalized.mcpServers.includes(server))
391
- const payload = { skills: prepared.identities, mcpServers: normalized.mcpServers, origin: 'apply' }
404
+ const payload = { skills: prepared.identities, mcpServers: normalized.mcpServers, ...(mcpIncarnations ? { mcpIncarnations } : {}), origin: 'apply' }
392
405
  const requestDigest = digestApplyRequest(normalized)
393
406
 
394
407
  // Step 4: enter the admission fence, close removed-server gates, start draining.
@@ -496,7 +509,7 @@ export function createApplyEngine(options) {
496
509
  status: 'applied',
497
510
  revision: result.revision,
498
511
  receipt: result.receipt,
499
- effective: { skills: structuredClone(prepared.identities), mcpServers: [...normalized.mcpServers] },
512
+ effective: { skills: structuredClone(prepared.identities), mcpServers: [...normalized.mcpServers], ...(mcpIncarnations ? { mcpIncarnations } : {}) },
500
513
  unresolvedWarnings: normalized.unresolved.map(entry => ({ ...entry })),
501
514
  ...(draining.length ? { draining } : {}),
502
515
  ...(warnings.length ? { warnings } : {}),
@@ -564,6 +577,7 @@ export function createApplyEngine(options) {
564
577
  return {
565
578
  skills: Array.isArray(value.skills) ? value.skills : [],
566
579
  mcpServers: Array.isArray(value.mcpServers) ? /** @type {string[]} */ (value.mcpServers) : [],
580
+ ...(value.mcpIncarnations && typeof value.mcpIncarnations === 'object' && !Array.isArray(value.mcpIncarnations) ? { mcpIncarnations: structuredClone(value.mcpIncarnations) } : {}),
567
581
  }
568
582
  }
569
583
 
@@ -613,7 +627,7 @@ export function createApplyEngine(options) {
613
627
  // just-committed record — reported, never fatal.
614
628
  try {
615
629
  const sets = safeSets(payload)
616
- publishSnapshot?.(sessionId, { revision, skills: structuredClone(sets.skills), mcpServers: [...sets.mcpServers] })
630
+ publishSnapshot?.(sessionId, { revision, skills: structuredClone(sets.skills), mcpServers: [...sets.mcpServers], ...(sets.mcpIncarnations ? { mcpIncarnations: structuredClone(sets.mcpIncarnations) } : {}) })
617
631
  } catch (error) {
618
632
  degraded('snapshot-publication-degraded', error)
619
633
  }