@sema-agent/client-core 0.77.1 → 0.78.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 (382) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +6 -5
  3. package/dist/abortableSleep.d.ts +0 -28
  4. package/dist/abortableSleep.js +0 -28
  5. package/dist/adapt/arms.d.ts +0 -46
  6. package/dist/adapt/arms.js +9 -544
  7. package/dist/adapt/ids.d.ts +0 -58
  8. package/dist/adapt/ids.js +0 -63
  9. package/dist/adapt/instanceLedger.d.ts +0 -25
  10. package/dist/adapt/instanceLedger.js +0 -20
  11. package/dist/adapt/panelTasks.d.ts +0 -69
  12. package/dist/adapt/panelTasks.js +0 -95
  13. package/dist/adapt/textSegmentAuthority.d.ts +1 -137
  14. package/dist/adapt/textSegmentAuthority.js +0 -54
  15. package/dist/adapt/textStream.d.ts +0 -252
  16. package/dist/adapt/textStream.js +2 -281
  17. package/dist/adapt/toolCards.d.ts +0 -46
  18. package/dist/adapt/toolCards.js +0 -23
  19. package/dist/adapt/turnFlags.d.ts +0 -54
  20. package/dist/adapt/turnFlags.js +1 -17
  21. package/dist/adapt/wireShapes.d.ts +0 -92
  22. package/dist/adapt/wireShapes.js +0 -83
  23. package/dist/adapt.d.ts +0 -55
  24. package/dist/adapt.js +1 -120
  25. package/dist/adapter/activeRunSelfHeal.d.ts +22 -515
  26. package/dist/adapter/activeRunSelfHeal.js +10 -625
  27. package/dist/adapter/downstream/eventToSdkMessage.d.ts +2 -305
  28. package/dist/adapter/downstream/eventToSdkMessage.js +2 -867
  29. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -314
  30. package/dist/adapter/downstream/terminalToSdkResult.js +13 -560
  31. package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +0 -95
  32. package/dist/adapter/downstream/turnUsageToModelUsage.js +2 -31
  33. package/dist/adapter/runStream.d.ts +0 -206
  34. package/dist/adapter/runStream.js +6 -596
  35. package/dist/adapter/types.d.ts +0 -111
  36. package/dist/adapter/types.js +0 -29
  37. package/dist/agentSession/backgroundView.d.ts +0 -106
  38. package/dist/agentSession/backgroundView.js +3 -49
  39. package/dist/agentSession/contract.d.ts +0 -98
  40. package/dist/agentSession/contract.js +0 -12
  41. package/dist/agentsWireCaps.d.ts +0 -72
  42. package/dist/agentsWireCaps.js +4 -72
  43. package/dist/approvalsStreamLiveCapability.d.ts +0 -25
  44. package/dist/approvalsStreamLiveCapability.js +0 -31
  45. package/dist/argvFlagValue.d.ts +0 -33
  46. package/dist/argvFlagValue.js +3 -35
  47. package/dist/attachmentsWireCaps.d.ts +0 -127
  48. package/dist/attachmentsWireCaps.js +0 -144
  49. package/dist/autoModeUnavailable.d.ts +0 -120
  50. package/dist/autoModeUnavailable.js +0 -144
  51. package/dist/classifierStatus.d.ts +0 -72
  52. package/dist/classifierStatus.js +0 -167
  53. package/dist/classifierVerdictWire.d.ts +0 -54
  54. package/dist/classifierVerdictWire.js +0 -133
  55. package/dist/clientContextWireCaps.d.ts +0 -37
  56. package/dist/clientContextWireCaps.js +0 -36
  57. package/dist/clientSlice.d.ts +0 -63
  58. package/dist/clientSlice.js +0 -45
  59. package/dist/cloudConfigWireCaps.d.ts +0 -86
  60. package/dist/cloudConfigWireCaps.js +2 -65
  61. package/dist/compensations.d.ts +0 -52
  62. package/dist/compensations.js +2 -63
  63. package/dist/controlRouter.d.ts +2 -208
  64. package/dist/controlRouter.js +1 -129
  65. package/dist/coreValuePorts.d.ts +0 -84
  66. package/dist/coreValuePorts.js +0 -29
  67. package/dist/decideReceipt.d.ts +0 -110
  68. package/dist/decideReceipt.js +0 -84
  69. package/dist/detachWire.d.ts +0 -130
  70. package/dist/detachWire.js +1 -130
  71. package/dist/deviceExecutorManagementCapability.d.ts +2 -35
  72. package/dist/deviceExecutorManagementCapability.js +1 -37
  73. package/dist/diagnostics.d.ts +0 -8
  74. package/dist/diagnostics.js +0 -8
  75. package/dist/diff/patch.d.ts +0 -17
  76. package/dist/diff/patch.js +0 -19
  77. package/dist/effectiveFacts.d.ts +0 -40
  78. package/dist/effectiveFacts.js +0 -22
  79. package/dist/effortWire.d.ts +0 -11
  80. package/dist/effortWire.js +0 -12
  81. package/dist/engineAgentPanelStore.d.ts +0 -169
  82. package/dist/engineAgentPanelStore.js +13 -293
  83. package/dist/engineCapReader.d.ts +0 -62
  84. package/dist/engineCapReader.js +1 -43
  85. package/dist/engineCapsCache.d.ts +0 -177
  86. package/dist/engineCapsCache.js +0 -199
  87. package/dist/engineCapsGenerationGuard.d.ts +0 -2
  88. package/dist/engineCapsGenerationGuard.js +0 -11
  89. package/dist/engineErrorCodes.d.ts +1 -316
  90. package/dist/engineErrorCodes.js +0 -427
  91. package/dist/engineHttpTools.d.ts +0 -29
  92. package/dist/engineHttpTools.js +0 -29
  93. package/dist/engineIdentity.d.ts +0 -83
  94. package/dist/engineIdentity.js +0 -88
  95. package/dist/engineInlineTaskStats.d.ts +0 -57
  96. package/dist/engineInlineTaskStats.js +1 -42
  97. package/dist/engineNoticeCodes.d.ts +0 -190
  98. package/dist/engineNoticeCodes.js +0 -182
  99. package/dist/engineSessionParam.d.ts +0 -30
  100. package/dist/engineSessionParam.js +0 -52
  101. package/dist/engineToolLabelStore.d.ts +0 -29
  102. package/dist/engineToolLabelStore.js +0 -31
  103. package/dist/engineWireSdk.d.ts +0 -90
  104. package/dist/engineWireSdk.js +0 -74
  105. package/dist/engineWireTarget.d.ts +0 -14
  106. package/dist/engineWireTarget.js +0 -39
  107. package/dist/env/localeGeo.d.ts +0 -12
  108. package/dist/env/localeGeo.js +2 -77
  109. package/dist/env/localeTag.d.ts +0 -34
  110. package/dist/env/localeTag.js +0 -32
  111. package/dist/env/uiLanguage.d.ts +0 -13
  112. package/dist/env/uiLanguage.js +0 -25
  113. package/dist/envFlag.d.ts +0 -37
  114. package/dist/envFlag.js +0 -40
  115. package/dist/executionLaneCapability.d.ts +0 -48
  116. package/dist/executionLaneCapability.js +0 -54
  117. package/dist/finalVerifyWire.d.ts +0 -67
  118. package/dist/finalVerifyWire.js +3 -43
  119. package/dist/fleet/fleetLedger.d.ts +0 -308
  120. package/dist/fleet/fleetLedger.js +10 -429
  121. package/dist/fleet/fleetProjection.d.ts +0 -240
  122. package/dist/fleet/fleetProjection.js +0 -189
  123. package/dist/fleet/fleetRowAgentType.d.ts +0 -6
  124. package/dist/fleet/fleetRowAgentType.js +1 -32
  125. package/dist/fleet/workflowSizeWarning.d.ts +0 -47
  126. package/dist/fleet/workflowSizeWarning.js +1 -46
  127. package/dist/fleetAgentPanelProjection.d.ts +0 -48
  128. package/dist/fleetAgentPanelProjection.js +8 -173
  129. package/dist/fleetTaskDesc.d.ts +0 -39
  130. package/dist/fleetTaskDesc.js +0 -69
  131. package/dist/forkWireCaps.d.ts +0 -23
  132. package/dist/forkWireCaps.js +1 -24
  133. package/dist/gateOutcome.d.ts +0 -140
  134. package/dist/gateOutcome.js +0 -85
  135. package/dist/gateVocabulary.d.ts +0 -114
  136. package/dist/gateVocabulary.js +3 -161
  137. package/dist/goalStopHook.d.ts +0 -127
  138. package/dist/goalStopHook.js +0 -178
  139. package/dist/headlessPermissionModeWire.d.ts +0 -76
  140. package/dist/headlessPermissionModeWire.js +1 -155
  141. package/dist/headlessReconnectWire.d.ts +0 -84
  142. package/dist/headlessReconnectWire.js +13 -67
  143. package/dist/hitl/approvalDecisionNoteAudit.d.ts +0 -49
  144. package/dist/hitl/approvalDecisionNoteAudit.js +0 -56
  145. package/dist/hitl/approvalOutcomeNote.d.ts +0 -2
  146. package/dist/hitl/approvalOutcomeNote.js +0 -20
  147. package/dist/hitl/approvalResolution.d.ts +0 -113
  148. package/dist/hitl/approvalResolution.js +0 -66
  149. package/dist/hitl/approvalsFeed.d.ts +0 -183
  150. package/dist/hitl/approvalsFeed.js +12 -243
  151. package/dist/hitl/armedGateRegistry.d.ts +0 -55
  152. package/dist/hitl/armedGateRegistry.js +0 -146
  153. package/dist/hitl/askGateWire.d.ts +0 -86
  154. package/dist/hitl/askGateWire.js +1 -95
  155. package/dist/hitl/askParkRowRouting.d.ts +0 -123
  156. package/dist/hitl/askParkRowRouting.js +1 -89
  157. package/dist/hitl/crashConverged.d.ts +0 -148
  158. package/dist/hitl/crashConverged.js +0 -214
  159. package/dist/hitl/editedRuleTextPrecheck.d.ts +0 -109
  160. package/dist/hitl/editedRuleTextPrecheck.js +0 -71
  161. package/dist/hitl/frameRouter.d.ts +0 -134
  162. package/dist/hitl/frameRouter.js +6 -378
  163. package/dist/hitl/gateIdentity.d.ts +0 -39
  164. package/dist/hitl/gateIdentity.js +0 -41
  165. package/dist/hitl/gateLedger.d.ts +0 -267
  166. package/dist/hitl/gateLedger.js +0 -121
  167. package/dist/hitl/hitlBridge.d.ts +7 -431
  168. package/dist/hitl/hitlBridge.js +7 -476
  169. package/dist/hitl/hitlHostSurface.d.ts +0 -150
  170. package/dist/hitl/hitlHostSurface.js +0 -169
  171. package/dist/hitl/livePendingAsk.d.ts +0 -91
  172. package/dist/hitl/livePendingAsk.js +0 -74
  173. package/dist/hitl/localAllowRule.d.ts +0 -62
  174. package/dist/hitl/localAllowRule.js +1 -33
  175. package/dist/hitl/parkOwnership.d.ts +0 -56
  176. package/dist/hitl/parkOwnership.js +0 -22
  177. package/dist/hitl/parkResolver.d.ts +3 -88
  178. package/dist/hitl/parkResolver.js +8 -349
  179. package/dist/hitl/parkRowBirthWait.d.ts +2 -26
  180. package/dist/hitl/parkRowBirthWait.js +3 -93
  181. package/dist/hitl/persistedRulesWire.d.ts +23 -371
  182. package/dist/hitl/persistedRulesWire.js +62 -329
  183. package/dist/hitl/planReviewWire.d.ts +4 -176
  184. package/dist/hitl/planReviewWire.js +9 -312
  185. package/dist/hitl/resumeRunningCard.d.ts +0 -105
  186. package/dist/hitl/resumeRunningCard.js +0 -105
  187. package/dist/hitl/sessionPolicyWire.d.ts +11 -191
  188. package/dist/hitl/sessionPolicyWire.js +0 -149
  189. package/dist/hitl/suspendedReopen.d.ts +3 -24
  190. package/dist/hitl/suspendedReopen.js +0 -14
  191. package/dist/hitl/toolApprovalWire.d.ts +5 -1308
  192. package/dist/hitl/toolApprovalWire.js +5 -945
  193. package/dist/hooksWireCaps.d.ts +0 -38
  194. package/dist/hooksWireCaps.js +0 -190
  195. package/dist/host.d.ts +0 -105
  196. package/dist/host.js +0 -33
  197. package/dist/hostEnv.d.ts +0 -14
  198. package/dist/hostEnv.js +0 -13
  199. package/dist/imagesWireCaps.d.ts +0 -21
  200. package/dist/imagesWireCaps.js +0 -22
  201. package/dist/index.d.ts +0 -134
  202. package/dist/index.js +0 -476
  203. package/dist/interactiveHalt.d.ts +5 -153
  204. package/dist/interactiveHalt.js +0 -111
  205. package/dist/interactiveToolsWire.d.ts +0 -62
  206. package/dist/interactiveToolsWire.js +1 -67
  207. package/dist/leaderConflict.d.ts +0 -59
  208. package/dist/leaderConflict.js +0 -48
  209. package/dist/limitsWire.d.ts +0 -125
  210. package/dist/limitsWire.js +2 -120
  211. package/dist/liveInitToolFace.d.ts +0 -77
  212. package/dist/liveInitToolFace.js +1 -109
  213. package/dist/liveModelCatalog.d.ts +0 -52
  214. package/dist/liveModelCatalog.js +0 -48
  215. package/dist/liveQuestionStore.d.ts +0 -87
  216. package/dist/liveQuestionStore.js +1 -56
  217. package/dist/mcpLiveness.d.ts +0 -151
  218. package/dist/mcpLiveness.js +0 -122
  219. package/dist/mcpPanel.d.ts +0 -104
  220. package/dist/mcpPanel.js +0 -49
  221. package/dist/mcpReconnect.d.ts +0 -58
  222. package/dist/mcpReconnect.js +0 -69
  223. package/dist/mcpWireCaps.d.ts +0 -55
  224. package/dist/mcpWireCaps.js +1 -13
  225. package/dist/memoryComplianceCapability.d.ts +3 -57
  226. package/dist/memoryComplianceCapability.js +0 -57
  227. package/dist/memoryEntriesWire.d.ts +6 -198
  228. package/dist/memoryEntriesWire.js +0 -144
  229. package/dist/memoryOriginCapability.d.ts +3 -53
  230. package/dist/memoryOriginCapability.js +0 -50
  231. package/dist/memorySpecWire.d.ts +0 -116
  232. package/dist/memorySpecWire.js +0 -150
  233. package/dist/model/catalog.d.ts +0 -111
  234. package/dist/model/catalog.js +0 -87
  235. package/dist/model/catalogLoader.d.ts +0 -114
  236. package/dist/model/catalogLoader.js +2 -139
  237. package/dist/model/modelSupplyRules.d.ts +1 -55
  238. package/dist/model/modelSupplyRules.js +0 -62
  239. package/dist/model/providerAuth.d.ts +0 -103
  240. package/dist/model/providerAuth.js +2 -38
  241. package/dist/model/providerCatalog.d.ts +0 -45
  242. package/dist/model/providerCatalog.js +0 -37
  243. package/dist/model/providerPresets.d.ts +0 -33
  244. package/dist/model/providerPresets.js +2 -91
  245. package/dist/model/tierVocabulary.d.ts +0 -31
  246. package/dist/model/tierVocabulary.js +0 -27
  247. package/dist/modelBudgetRule.d.ts +0 -39
  248. package/dist/modelBudgetRule.js +0 -39
  249. package/dist/modelCapabilityProbe.d.ts +0 -175
  250. package/dist/modelCapabilityProbe.js +0 -147
  251. package/dist/modelWireCaps.d.ts +0 -13
  252. package/dist/modelWireCaps.js +0 -13
  253. package/dist/notifications.d.ts +0 -233
  254. package/dist/notifications.js +14 -492
  255. package/dist/oneShotWireCaps.d.ts +0 -34
  256. package/dist/oneShotWireCaps.js +0 -35
  257. package/dist/ownKey.d.ts +0 -33
  258. package/dist/ownKey.js +0 -33
  259. package/dist/panelRunningHistory.d.ts +0 -28
  260. package/dist/panelRunningHistory.js +0 -43
  261. package/dist/peerFrames.d.ts +0 -71
  262. package/dist/peerFrames.js +0 -168
  263. package/dist/peerLaneCapability.d.ts +0 -44
  264. package/dist/peerLaneCapability.js +0 -52
  265. package/dist/permissionRuleIssue.d.ts +0 -30
  266. package/dist/permissionRuleIssue.js +0 -78
  267. package/dist/permissionRulesWriteCapability.d.ts +0 -46
  268. package/dist/permissionRulesWriteCapability.js +0 -54
  269. package/dist/permissionWireCaps.d.ts +0 -37
  270. package/dist/permissionWireCaps.js +0 -37
  271. package/dist/postureKnob.d.ts +0 -71
  272. package/dist/postureKnob.js +0 -86
  273. package/dist/principalWire.d.ts +0 -17
  274. package/dist/principalWire.js +0 -17
  275. package/dist/printToolResultFrame.d.ts +0 -100
  276. package/dist/printToolResultFrame.js +0 -33
  277. package/dist/promptProfileWireCaps.d.ts +0 -13
  278. package/dist/promptProfileWireCaps.js +0 -13
  279. package/dist/readFacePosture.d.ts +0 -43
  280. package/dist/readFacePosture.js +0 -46
  281. package/dist/request/printNotification.d.ts +0 -20
  282. package/dist/request/printNotification.js +0 -55
  283. package/dist/request/taskRequest.d.ts +1 -249
  284. package/dist/request/taskRequest.js +20 -498
  285. package/dist/resumeRefusalCopy.d.ts +0 -136
  286. package/dist/resumeRefusalCopy.js +1 -117
  287. package/dist/retainBackgroundWireCaps.d.ts +0 -48
  288. package/dist/retainBackgroundWireCaps.js +0 -48
  289. package/dist/retryStatus.d.ts +2 -221
  290. package/dist/retryStatus.js +0 -107
  291. package/dist/rewindWireCaps.d.ts +0 -27
  292. package/dist/rewindWireCaps.js +0 -24
  293. package/dist/runCancelContext.d.ts +1 -20
  294. package/dist/runCancelContext.js +0 -34
  295. package/dist/runTerminal.d.ts +0 -273
  296. package/dist/runTerminal.js +0 -177
  297. package/dist/sandboxWire.d.ts +0 -38
  298. package/dist/sandboxWire.js +0 -82
  299. package/dist/scenarioWire.d.ts +0 -60
  300. package/dist/scenarioWire.js +0 -68
  301. package/dist/scratchpadWireCaps.d.ts +0 -11
  302. package/dist/scratchpadWireCaps.js +0 -32
  303. package/dist/sdkWireTransit.d.ts +0 -38
  304. package/dist/sdkWireTransit.js +0 -30
  305. package/dist/seam.d.ts +18 -853
  306. package/dist/seam.js +0 -52
  307. package/dist/seatContract.d.ts +0 -555
  308. package/dist/seatContract.js +5 -304
  309. package/dist/selfOrchestrationDenial.d.ts +0 -189
  310. package/dist/selfOrchestrationDenial.js +0 -162
  311. package/dist/selfOrchestrationWireCaps.d.ts +0 -38
  312. package/dist/selfOrchestrationWireCaps.js +0 -38
  313. package/dist/sessionMap.d.ts +0 -60
  314. package/dist/sessionMap.js +0 -37
  315. package/dist/sessionMemoryStatus.d.ts +11 -114
  316. package/dist/sessionMemoryStatus.js +0 -112
  317. package/dist/sessionModelLatch.d.ts +0 -29
  318. package/dist/sessionModelLatch.js +0 -40
  319. package/dist/sessionPolicyCapability.d.ts +3 -53
  320. package/dist/sessionPolicyCapability.js +0 -52
  321. package/dist/sessionSlot.d.ts +0 -26
  322. package/dist/sessionSlot.js +0 -17
  323. package/dist/skillsWireCaps.d.ts +0 -87
  324. package/dist/skillsWireCaps.js +0 -38
  325. package/dist/sqlEngineCapability.d.ts +0 -118
  326. package/dist/sqlEngineCapability.js +0 -134
  327. package/dist/sseIdleTriage.d.ts +0 -79
  328. package/dist/sseIdleTriage.js +1 -80
  329. package/dist/steering.d.ts +0 -54
  330. package/dist/steering.js +0 -63
  331. package/dist/subagent/engineCompactWire.d.ts +0 -34
  332. package/dist/subagent/engineCompactWire.js +4 -134
  333. package/dist/subagent/engineDelegatedPrompt.d.ts +0 -31
  334. package/dist/subagent/engineDelegatedPrompt.js +1 -103
  335. package/dist/subagent/engineRowStopGate.d.ts +0 -20
  336. package/dist/subagent/engineRowStopGate.js +0 -49
  337. package/dist/subagent/engineSubagentOutput.d.ts +0 -7
  338. package/dist/subagent/engineSubagentOutput.js +1 -51
  339. package/dist/subagent/engineSubagentResume.d.ts +2 -279
  340. package/dist/subagent/engineSubagentResume.js +2 -129
  341. package/dist/subagent/engineSubagentSteer.d.ts +0 -26
  342. package/dist/subagent/engineSubagentSteer.js +0 -63
  343. package/dist/subagent/engineSubagentTail.d.ts +0 -38
  344. package/dist/subagent/engineSubagentTail.js +2 -110
  345. package/dist/subagent/engineTaskHandleWire.d.ts +6 -67
  346. package/dist/subagent/engineTaskHandleWire.js +0 -126
  347. package/dist/subagent/subagentOwnerAbsence.d.ts +0 -2
  348. package/dist/subagent/subagentOwnerAbsence.js +0 -25
  349. package/dist/subagentContentStore.d.ts +3 -260
  350. package/dist/subagentContentStore.js +7 -540
  351. package/dist/systemReminderTag.d.ts +0 -49
  352. package/dist/systemReminderTag.js +0 -61
  353. package/dist/toolResult.d.ts +1 -211
  354. package/dist/toolResult.js +1 -358
  355. package/dist/toolRoster.d.ts +0 -140
  356. package/dist/toolRoster.js +0 -66
  357. package/dist/typePins.d.ts +0 -46
  358. package/dist/types/engineState.d.ts +0 -71
  359. package/dist/types/engineState.js +0 -16
  360. package/dist/ultracodeWireCaps.d.ts +0 -75
  361. package/dist/ultracodeWireCaps.js +0 -92
  362. package/dist/unrefTimer.d.ts +0 -27
  363. package/dist/webSearchBackendCapability.d.ts +0 -72
  364. package/dist/webSearchBackendCapability.js +0 -79
  365. package/dist/webSearchWireCaps.d.ts +0 -37
  366. package/dist/webSearchWireCaps.js +0 -34
  367. package/dist/websearch/searchProviderPresets.d.ts +0 -81
  368. package/dist/websearch/searchProviderPresets.js +0 -27
  369. package/dist/wireErrorTriage.d.ts +4 -160
  370. package/dist/wireErrorTriage.js +0 -171
  371. package/dist/wireRefusalCopy.d.ts +0 -28
  372. package/dist/wireRefusalCopy.js +0 -28
  373. package/dist/workflow.d.ts +0 -40
  374. package/dist/workflow.js +0 -52
  375. package/dist/workflowClient.d.ts +0 -122
  376. package/dist/workflowClient.js +8 -360
  377. package/dist/workflowMonitor.d.ts +2 -72
  378. package/dist/workflowMonitor.js +0 -26
  379. package/dist/writeProtectionCapability.d.ts +0 -112
  380. package/dist/writeProtectionCapability.js +0 -109
  381. package/docs/INTEGRATION-CLIENTS.md +78 -5
  382. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -28,7 +28,7 @@
28
28
  > 既有行)⇒ 标题**不回改、且永远不会被改**(`b165f3f` 那个 commit 的字节是历史,任何未来提交
29
29
  > 都改不到它)⇒ 这条勘误与门侧窄豁免(`run-integration-doc-freshness-test.mjs` ④b
30
30
  > `KNOWN_HEADING_ERRATA`,登记 `version: '0.36.0', releasedAt: 'b165f3f'`)**都是永久的**,不是
31
- > 「下一版删掉」的临时态(对抗复审 finding①:那样写会让豁免一删,门在**任何**后续版本上
31
+ > 「下一版删掉」的临时态(外部复核意见①:那样写会让豁免一删,门在**任何**后续版本上
32
32
  > 都会重新对这个永久冻结的标题判红,退休条件不可能被满足)。门侧核验两件事把这条勘误钉死、
33
33
  > 不许悄悄漂:豁免登记的 `releasedAt` 与 `FROZEN` 账上 0.36.0 那一行逐字相等;本段(点名版本号
34
34
  > `0.36.0` + 关键字「勘误」)必须还在这份头注里 —— 删掉本段而不同批把门侧豁免一起处理,门当场红。
@@ -49,6 +49,47 @@
49
49
  > 挡住 ⇒ 本批把它机械化——④a0 对 `pending` 行**要求段头已是日期形**(`(未发布)` 直接红),阶段一
50
50
  > commit 漏转在发布前就红,不再靠人记。
51
51
 
52
+ ## 0.78.0(2026-09-21)
53
+
54
+ > 主题:**peer sdk 地板抬到 11.x + 单步写口读回按活性判别 + 第十词专句 + 整只工具面卸载进车道表**(🔴 **minor**:peer 地板 `>=9.8.1` → **`>=11.0.1`**;单步写口结局联合**多一臂 `revoked`**、`RulesFacade.write` 转必填、三只自铸型改为 sdk 别名(型面 BREAKING 三处,编译器会报);公面运行期导出 **1143 不变**;wire 新键一枚 `excludeAllTools`(sdk 具名位进车道表))—— 抬地板(CC-106)· 单步写口 200 体按 `stillLive` 判别联合改读(CC-103)· `DeniedBy` 第十词 `read_boundary` 专句(CC-106 ④)· `excludeAllTools` 车道行。**成文改口段见 §82 82y,按表态制点名三端。**
55
+
56
+ ### 🔴 BREAKING(型面)
57
+
58
+ - **peer `@sema-agent/sdk` 地板 `>=9.8.1` → `>=11.0.1`**(CC-106):11.x 声明 `DeniedBy` 第十词 `read_boundary`、`rules.write` 200 体按 `stillLive` 判别的联合、`RemovalLiveness` / `RuleWriteRequest` / `RuleWriteResult` / `RuleWriteBehavior` 包根导出、`TaskRequest.excludeAllTools`;本包按 11.0.1 编译,消费端实装已在 10.x / 11.x,9.8.1 失去物料见证。四处同批抬齐(package.json peer + devDep / lockfile 根包两处 / README / 接入文档 §0a),`run-sdk-floor-test.mjs` 的 `FLOOR` 与负控锚同批。装着 <11.0.1 的端:`gateDeniedByDetail('read_boundary')` 仍答专句(镜像表不依赖 sdk 运行期),但 `rules.write` 回体在 sdk 10.x 的型面上看不见 `stillLive`(编译期不报)—— 请同批抬 sdk,别混装。
59
+ - **`writePersistedRule` 结局联合 +1 臂 `{ status: 'revoked', wrote: 'persisted' | 'no_op', rev, message }`**(CC-103;server ≥7.92.2 起可达):写**落了**(`rev` 是真的)而读回它时一条并发撤销已先到 ⇒ 请求的收紧**现在不站着**。🔴 **不要重试**(那会把刚被人撤掉的收紧重新立起来 —— 正是它不能折进 `unknown` 的理由,那一臂的处置是顺序重发);处置只有一个:`listAllPersistedRules` 看现状。键集与其余四臂两两不相交(没有 `rule`:店里没有活行)。穷举式 `switch` 的端在这一臂上编译红,那就是通知。
60
+ - **`RulesFacade.write` 可选 → 必填**:peer 地板 ≥11.0.1 起 sdk 的 `rules` 资源自带这个动词,宿主从真 client 装配一定拼得出;运行期守卫与 `refused / client_too_old` 一格**保留**(注入的假件 / 比这条口老的宿主客户端仍可能没这个动词,「连发都没发」是确知的拒,不折 unknown)。自铸 facade 的测试假件补 `write`。
61
+ - **三只自铸型改为取自 sdk**:`PersistedRuleWriteRequest` = `RuleWriteRequest`、`PersistedRuleWriteWireResult` = `RuleWriteResult`(判别联合:`rule` 只住 `stillLive: "yes"` 支),`PersistedRuleWriteBehavior` 仍由运行期表派生并加编译期等值钉证明与 `RuleWriteBehavior` 是同一张。名字不变;请求体结构不变;回体型变宽(直接读 `facade.write()` 回体的端从此必须先判 `stillLive` 再读 `rule`,不判 = 编译错)。
62
+
63
+ ### Added
64
+
65
+ - **`DeniedBy` 第十词 `read_boundary` 专句**(CC-106 ④):`GATE_DENIED_BY_WORDS` 十词(顺序同源 sdk 11.0.1),`gateDeniedByDetail('read_boundary')` = `denied by the read boundary`(读边界站拒:拒表路径命中的读在任何审批之前就被拦,或一次已批准的改写被重判到拒表行)。0.77.2 上这个词走「本版不认识这个层名」兜底句,从此走专句;兜底句仍只给本包不认识的词。
66
+ - **`PersistedRuleLiveness`**(= sdk `RemovalLiveness`,`yes` / `no` / `unknown`):撤销口 `stillLive` 与写口 200 体共用的活性三词以本包的名转口;端从此读包名(此前只能派生 sdk 的 `RuleRevokeResult['stillLive']` 或手抄三词)。
67
+ - **`excludeAllTools` 进请求装配车道表**(sdk 11.x 具名位;server ≥7.90.0 请求面):两条提交车道有座、live 门后;字面 `true` 单成员闭集 —— `true` 才 stamp,`false` 与缺席同义(不发、不进回执:server 只把 literal true 写上任务规格,`false` 是替引擎重申它自己的缺省),坏值(非布尔)构造期 `TypeError` 只报形状不回显值(收紧方向的声明不许被打字错误安静丢掉,与 `memoryCapture` 同律);读序也同律 —— 先于其余键读取并种进快照,排在后面的可执行属性改不掉已读的声明。🔴 与 `excludeTools` 两种语义刻意不折叠:`"*"` 是一个合法的工具名,两键同带各自生效。0.77.2 上这个键会被表外键判据以 `TypeError` 点名拒;从此有座。没接线的 server(≤7.89.x)对它 400 `request.body_shape` 并在 `unknownKeys` 里点名 —— 探测靠键闭集,没有能力位,本包不替它猜。
68
+
69
+ ### Changed(判据)
70
+
71
+ - **单步写口 200 体读法**(CC-103):认证字段只凭回包**自有数据属性**一次性快照(`status` / `rev` / `stillLive` / `rule` 任一只在原型链上、或是访问器属性 ⇒ `malformed_result`;getter / Proxy trap 抛错 ⇒ `malformed_result` 而不是 reject;原型链上只有无关键照常读);先读 `stillLive` 再读 `rule` —— `"yes"` 走行窄化 + 身份三元组对账(与 0.77.x 同答);**缺席 = 老服务(≤7.92.1,那一代恒带 `rule`、不带这一格)按行在场读,不补默认值、不合成一个 `yes`**(证据是那一行,不是本包替旧服务说的词);`"no"` 却带 `rule`(两个判别位打架)/ `"unknown"`(本口契约上不可达)/ 表外词 / boolean 时代的 `true` `false` / 键在场读不出 ⇒ `unknown / malformed_result`(读不懂的 200 拒认,不折成 `persisted` 也不折成 `revoked`);`no-op` → `no_op` 的拼法归一收成一处,三臂共用。`unknown / indeterminate`(503 `state.rule_write_failed`)的注解收窄:≥7.92.2 起「写落了而读回时并发撤销先到」走 200 `stillLive: "no"`,不再折在 503 里。
72
+ - 词汇门 `run-gate-vocabulary-test.mjs` 成文改口:A1「上游九词」→ 十词、A5 十句互异、A6 加 `read_boundary` 逐字锚;上游联合解析器改走 TypeScript AST(具名 TypeAliasDeclaration → 字面量成员;逃生口 `(string & {})` 同一棵树判别):sdk 11.0.1 的真形是联合成员之间夹一段带 `;` 与引号串的注释,旧的 `[^;]*` 正则把十词读成八词、A1 假红;行尾注释里的 `;` / 字符串成员里的注释标记 / 注释里的假声明三形各一枚正控。
73
+ - 能力位处置台账门 `run-engine-caps-ledger-test.mjs` 补两行:`peerLane` / `permissionRulesWrite`(0.76.1 起本包已按结构读,sdk 11.x 型面到货 ⇒ 键集对账红,显式登记为 read;读点不变)。
74
+ - `run-sdk-floor-test.mjs` 字符串序正控随地板走:地板 major 进两位数后,原「10.0.0 判绿 ∧ major < 10」自己失去判别力;改为取「数值上高一个位数、字典序却排在地板之前」的版本判绿,并断言字典序确实判反。
75
+
76
+ ### Gates
77
+
78
+ - `run-persisted-rule-write-test.mjs` → 319(新 W11 五组:yes 支同答 / no 支 `revoked` 三形 / 打架与表外十三形拒认 / 缺席按行读 / 三型取自 sdk 与 `write` 必填的型面钉)· `run-task-request-omission-receipt-test.mjs` 156 → 174(A6:`excludeAllTools` 两车道座位、非 live 回执、`false` 值级缺席、与 `excludeTools:["*"]` 不折叠、五种坏值响亮拒只报形状、`null` 合法缺席)· `run-client-core-pure-test.mjs` +5(W3b)· `run-gate-vocabulary-test.mjs` → 228(十词 + 解析器两枚正控)· `run-sdk-floor-test.mjs` 地板 11.0.1 · 负控:sdk-floor 锚 11.0.1 → 11.0.2;词汇门负控在 11.0.1 真形上仍于编译期先红。
79
+ - 撤销口假件的 `stillLive` 从 boolean 时代的 `false` 改三词 `'no'`(本包不读这一格,只为假件与 sdk 10.x+ 同形)。
80
+
81
+ ## 0.77.2(2026-09-21)
82
+
83
+ > 主题:**出包面结构卫生 + 门词汇兜底句改口**(patch;型面零变、行为零变;制品面:dist 零注释)。
84
+
85
+ ### Changed(制品面)
86
+
87
+ - **dist 零注释**(CC-100):构建期 `removeComments` —— dist `.js` 49,116 → 26,846 行、`.d.ts` 26,785 → 7,113 行,tarball 体积约减半;源码注释从此是内部面,不再随包公开。**三端可见的唯一差别 = IDE 悬停不再显示 JSDoc**;接入文档 `docs/INTEGRATION-CLIENTS.md` 本来就是唯一契约面(消费本包先读接入文档),行为与型面逐字节同 0.77.1(去注释后的归一化 diff 见发车帖)。新门 `run-dist-comments-test.mjs`:用 TypeScript 扫描器逐文件数注释 trivia(`DIST-COMMENT-FAIL`;字符串里的 `//` 与生成器方法不误判);卫生门词表从 21 条扩到 32 条(人名 / 协作过程词 / 别仓台账票号形 / 仓名作仓引用;`CC-nnn` 票号与 `[nnnn]` 帖号是不透明追溯引用,允许),对外文档只向前执法(CHANGELOG 自本版、接入文档自 §81;已发段冻结不动);负控两枚(禁表词进 README / 普通注释一行进 dist 各自红)。字符串字面量与元组类型里的文案仍会出包,按对外口径写。
88
+
89
+ ### Changed(闭集读数改口)
90
+
91
+ - **`gateDeniedByDetail` 表外词兜底句改「本版不认识这个层名」**(CC-111 ①,cli [7824]):此前说「这条门记录本不该长这样 … so this one is damaged」—— 闭集只在「引擎的词表 == 本包镜像」时成立;引擎比本包新的每一天(server 7.92.0 已过境第十词 `read_boundary`,本包钉 sdk 9.8.1 九词),它词表里多出的词在它那边不是出集、不会被拦,到本包就是一个表外词,而它是真实的拒绝层。新句:`denied by a layer this build does not know (<word>) — this build does not know this layer name: a newer engine may have added it, or the record may be damaged; this client cannot tell which`。第十词 `read_boundary` 的人话句(含读边界含义)随 sdk 10 抬地板在 0.78.0(CC-106)。词汇门 A7 两格改口(不许断言 damaged;明说两种可能)。
92
+
52
93
  ## 0.77.1(2026-09-20)
53
94
 
54
95
  > 主题:**读器族边界残留一只 + 外部验收本批发现**(patch;型面零变)。
package/README.md CHANGED
@@ -35,7 +35,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
35
35
 
36
36
  ## Scope
37
37
 
38
- **Version:** 0.77.1
38
+ **Version:** 0.78.0
39
39
 
40
40
  - **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
41
41
  B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
@@ -67,7 +67,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
67
67
  against — the tables live upstream precisely so this package does not keep a second copy that can
68
68
  fall behind. The browser bundle really bundles the SDK through (the portability guard would
69
69
  exit 3 rather than quietly mark it external).
70
- - The declared floor is `>=9.8.1` (raised from `>=9.7.1` in 0.75.0: `Capabilities.deviceExecutor.management` is typed from 9.8.x on, the package now compiles against 9.8.1, and no consumer ships 9.7.x any more, so the older floor lost its witness; before that raised from `>=9.6.0` in 0.74.0: `Capabilities.approvalsStreamLive` / `.executionLane`, `LivePendingRow.frame`, the `live_*` approval-stream events and `gates[].toolCallId` are typed from 9.7.x on, and no consumer ships 9.6.0 any more, so the older floor lost its witness; before that raised from `>=9.4.0` in 0.71.0: the `tool_disclosure` / `tool_progress` frames and `ToolApprovalFrame.readRootCandidate` are typed there; earlier: raised from `>=8.8.0` in 0.69.0: the `reasoning_end` frame and `McpStatusPanel.lastLegMcp` are typed from 9.4.0 on), and it is *witnessed*: the guard checks that an actually
70
+ - The declared floor is `>=11.0.1` (raised from `>=9.8.1` in 0.78.0: `DeniedBy` carries its tenth word `read_boundary`, `rules.write` answers a `stillLive`-discriminated body, `RemovalLiveness` / `RuleWriteRequest` / `RuleWriteResult` / `RuleWriteBehavior` are exported from the SDK root and `TaskRequest.excludeAllTools` is typed from 11.x on, the package now compiles against 11.0.1, and no consumer ships 9.8.x any more, so the older floor lost its witness; before that raised from `>=9.7.1` in 0.75.0: `Capabilities.deviceExecutor.management` is typed from 9.8.x on, the package now compiles against 9.8.1, and no consumer ships 9.7.x any more, so the older floor lost its witness; before that raised from `>=9.6.0` in 0.74.0: `Capabilities.approvalsStreamLive` / `.executionLane`, `LivePendingRow.frame`, the `live_*` approval-stream events and `gates[].toolCallId` are typed from 9.7.x on, and no consumer ships 9.6.0 any more, so the older floor lost its witness; before that raised from `>=9.4.0` in 0.71.0: the `tool_disclosure` / `tool_progress` frames and `ToolApprovalFrame.readRootCandidate` are typed there; earlier: raised from `>=8.8.0` in 0.69.0: the `reasoning_end` frame and `McpStatusPanel.lastLegMcp` are typed from 9.4.0 on), and it is *witnessed*: the guard checks that an actually
71
71
  installed SDK at that line still exports every value-level symbol this package imports and still
72
72
  declares `TaskStats.costMicroUsd` (the key `costOrNull` reads). A floor nobody ever ran is a
73
73
  promise, not a contract.
@@ -362,13 +362,13 @@ public-surface guard checks that last one).
362
362
  | `scripts/run-session-memory-status-test.mjs` | The session **memory-status** read face (S-53): the two judgements three clients would otherwise each get wrong. First, *same status, different code* — this route's 404 carries two unrelated meanings (`not_found.session` = unknown or non-owned session; `not_found.route` = a pre-7.53 server that has no such route at all), so dispatching on the **status** would report "your deployment lacks this surface" as "your session does not exist". The verdict is anchored on `errorCode`, the two 404s are pinned to **different** verdicts, and — the load-bearing negative control — a 404 carrying **no** code falls to `failed` rather than guessing either way, since a wrong guess in either direction is a false statement a user would act on. 501 is allowed a codeless fallback because both of its arms mean the same thing here, and `capability.*` stays split from `feature.*` because those two share a status while their dispositions are opposite. Second, *absence means something different per key*: `optOutSource` and `lastCaptureAt` are legitimately absent on a **healthy** session (a zero-history session really is `{captureOptedOut:false, committedCount:0, foldedCount:0}` with no degradation at all), so reading absence as "off/none/0" asserts something unprovable. Two combined readers are pinned: capture opt-out is read from **both** its keys (a record-store fault yields `indeterminate`, never `active` — the difference between "your conversation is being remembered" and "nobody knows"), and last-capture is a **three-state** read whose discriminator is the *other* key, because `lastCaptureAt`'s absence alone covers both "ledger unreadable" and "genuinely no contributions" and therefore decides nothing; the two shapes are pinned to different verdicts so a single-key read turns red. The thin wrapper is the only IO: it never throws, drops malformed keys to absence rather than trusting them (an unreadable value must answer "don't know", never render as truth), refuses to spend a request on an empty `sessionId`, and passes `signal` through untouched |
363
363
  | `scripts/run-crash-converged-projection-test.mjs` | The `crashConverged` read face on `GET /v1/approvals` (L-38): what the *previous life* of a crashed local engine left behind, projected for every client. Three judgements are pinned. First, **absence is not an empty list** — a missing key (an older server, deps not present, or a carrier that is not an array at all) returns `undefined`, and the client renders nothing; an empty array returns a present zero-count object, which is the server actually saying "none". Folding the first into `{total:0}` would have the client assert "nothing was left behind" on a surface a person uses to decide whether it is safe to re-run something — the worst possible direction for a false statement — so the two cases are pinned to different **return shapes** and a test asserts the two verdicts are unequal. Second, bucketing is a **four-term conjunction**: `orphanState === 'pending'` *and* `resumeSafe === true` *and* both approval-evidence keys (`originalDecision`, `decidedAtMs`) absent. A fifth term rejects any row carrying an **accessor**, and accessors are never invoked at all — reading one means synchronously running someone else's code, and `catch` catches throwing, not *never returning*, so a looping getter would pin the startup thread forever (the row cap does nothing against that shape). The same rule covers the three untrusted reads outside the row as well — the envelope's `crashConverged` key, the carrier's `length`, and every numeric index are read as own property *descriptors* and only data descriptors are used, so accessors and prototype entries read as absent and are never invoked. Such a key is treated as absent: if it was a required field the row is counted as dropped, if it was optional or additive the row survives without it. That also closes the ordering attack, since spreading runs getters in property order and an earlier one could `delete` the approval evidence before it is ever copied (measured before the fix: such a row reached the resume-safe bucket), and the check therefore moves ahead of the read, onto the property descriptors — from which the snapshot is then built directly, because checking descriptors and *then* spreading is two independent observations of the same row, and a non-throwing proxy can make the two `ownKeys` calls disagree (first showing `originalDecision: 'approve'` so the row reads as plain data, then omitting that configurable key so the snapshot loses the evidence; measured before the fix: the dangerous row reached the resume-safe bucket after exactly two enumerations, and after it, one). Keys are written with `Object.defineProperty` rather than plain assignment, because `'__proto__'` is a legal own enumerable key and `o['__proto__'] = x` does not store a value — it calls the prototype setter, letting a row whose own properties are all plain data (so the accessor gate never fires) inject a prototype whose `sessionId` getter deletes the approval evidence from the snapshot during validation; `defineProperty` fires no setter, so the key survives as ordinary additive data and the snapshot keeps `Object.prototype`. A row that simply arrives with a custom prototype is treated the same way, since the snapshot only enumerates own properties: approval evidence sitting on the prototype would never reach it, and a perfectly ordinary object with no proxy and no accessors could otherwise be called safe to re-run — real bodies come from `JSON.parse` and always carry `Object.prototype`, so nothing genuine trips it). Validation itself runs on a **null-prototype** dictionary and the bucketing verdict is carried out of that same pass rather than re-read from the delivered row, because every property lookup on an ordinary `{}` reaches `Object.prototype`: a polluted `sessionId` getter there would delete the approval evidence from the snapshot mid-validation and send the row to the safe bucket (measured before the fix). The row handed to the client is still an ordinary object — the null prototype is an implementation detail of the check, not of the value) — real JSON bodies are all data properties, so only a middle-layer-synthesised payload ever trips it, and it too lands in the human bucket rather than being dropped. The `decided` arm means the human had already approved and side effects may be half-landed, so it always goes to the human bucket, as does `resumeSafe === false` and — the last two terms — any row whose own fields contradict each other, since `pending` claims nothing ran while that evidence says somebody pressed approve. Deciding "not safe" costs one extra question (recoverable); deciding "safe" wrongly has somebody re-run work that already partly happened (not). A 2x2 truth table pins that exactly one cell is resume-safe, so reading either key alone turns red, and the contradictory rows are routed to the human bucket rather than dropped — they are real orphans, and the ones most worth showing. Third, unreadable rows are **dropped and counted**, never thrown and never passed through: the product is declared as `CrashConvergedRow`, so letting a row missing a required field — or carrying one of the wrong type — past would be a lie at the type level, and the closed literal discriminators (`decision` / `cause` / `orphanState`) decide family membership rather than being an open vocabulary. The measuring stick stops at the **type** floor, though: degenerate-but-well-typed values (`ts: NaN`, an empty `toolName`) are kept, because swallowing a real orphan over a decorative field is the worse direction, and the one deliberate exception is `approvalId`, which must be non-empty to be a row identity at all. `dropped` is kept separate from `total` so unreadable rows never inflate "N approvals were affected"; each row is a **one-shot snapshot** — every own enumerable key is read exactly once, and validation, bucketing and the handed-back value all read that same snapshot, so additive upstream keys survive while a **non-idempotent** getter (one that never throws, just answers differently on a second read) can no longer erase the approval evidence between the check and the bucketing (measured before the fix: such a row landed in the resume-safe bucket while its checked value was `"approve"`). Hostile carriers are counted rather than allowed to reject: **every** touch of the carrier is guarded — envelope property reads, `Array.isArray` itself (it throws on a revoked proxy), the `length` read, each indexed read and each row's property reads — and a traversal that dies halfway returns absence rather than a half-counted total. A row that cannot be read never takes the batch with it: its own shape check is inside its own guard, so one revoked-proxy row costs a `dropped` tick rather than collapsing the whole projection to absence — which a client would have read as "this deployment does not offer the surface". Traversal goes by **numeric index, never the carrier's own iterator protocol**, because `for...of` hands the carrier the question of which rows exist: an array carrying an overridden `Symbol.iterator` can yield nothing (measured before the fix: a real orphan became `{total:0}`, which a client reads as "the server said there are none") or swap a dangerous `decided` row for a safe-looking one (measured: `fake-safe` was returned in place of `real-danger`). Row count is capped at 100000 and the cap is checked **before** the walk: requiring only a non-negative integer `length` does not stop a proxy trap reporting a billion, and this surface runs on the startup / `--resume` path, where a synchronous spin freezes the thread (measured before the cap: twenty million rows took 18.3 seconds and twenty million index reads; a billion does not come back). The honest boundary is stated rather than overclaimed — a proxy can still lie in its `length` or index traps, which is the same thing as a host injecting a lying transport — and the widening of `ApprovalsResourceLike.list()` is proven **additive** by really running tsc over a legacy `{ pending }` mock *and* over the real `AgentClient` path — the projector takes `unknown` precisely because a parameter shaped as "an object with an optional `crashConverged`" is a TypeScript weak type that the installed SDK's own `list()` return shape shares no property with, which only a real-client compile would have caught — with a known-red control so a clean run means the checker spoke |
364
364
  | `scripts/run-self-orchestration-denial-test.mjs` | The three judgements behind a **denied self-orchestration request** (server 7.57.0), each of which all three clients would otherwise get wrong on their own. First, whether to retry at all is a **conjunction that may not be loosened**: HTTP 501 *and* an `errorCode` that is **exactly** `capability.self_orchestration_required`. That code shares its shape with every other `capability.*` 501, so dispatching on the prefix would drag "some other capability is not wired up" into the retry arm — those requests do not become acceptable once the two keys are gone, so the client would spend a request and then tell the user the wrong reason. Negative controls cover all four directions: a sibling `capability.*` code, a truncated or suffixed variant of the right one, a codeless 501 (it decides nothing, so it decides nothing — no guessing), and the right code under 500 / 400 / 503 or a string `"501"`. The classifier reads structurally rather than by `instanceof` (a host may inject its own transport; across realms or duplicate SDK instances an understandable error would read as unreadable), so a class instance, a bare `{status, errorCode}` literal and an error carrying those fields on its **prototype** all reach the same verdict — and a hostile proxy or a throwing getter yields `null` instead of throwing, because this classifier runs inside a `catch` block where anything it throws escapes the caller's own guard. Second, removing the intent is a **structural** operation, not wording: `selfOrchestration` sits at the top level while `ultracode` sits under `settings` — two different stamping legs — and a client hand-writing `delete` will miss the second one, which costs the user the same failure twice. The single stripper is pinned to touch exactly those two: other `settings` sub-keys and their values survive byte for byte, `deferTools` is left alone (pulling `Workflow` out would be a behaviour change, not a removal of intent), additive unknown keys survive at both levels, the input object is never mutated, `settings` is only dropped entirely when `ultracode` was really there and nothing else remains (an already-empty one is left as is), a non-object `settings` is not touched at all, an `ultracode` that only exists on the prototype does not count, and the whole thing is idempotent. The end-to-end leg runs a real `buildTaskRequest` product through it and asserts the stripped body still passes the registration gate key by key. Third, on the capabilities body, **absence is not "switched off"**: a pre-7.57 server has no `workflowsGate` key at all, so reading absence as "the engine says no" asserts something the server never said, and the mirror-image disease is folding an **unrecognised** `denial` into `null`, which would have the client render "nothing was denied" when the truth is "denied, for a reason I do not recognise". Five shapes are pinned — caps unreadable, gate absent, closed-set member, unknown value, accessor — with the unknown arm carrying the raw token (or an empty one when the value is not even a string) and never collapsing to `null`. All four untrusted reads go through own **data descriptors** only, and the guard pins the getter invocation count at zero, since `catch` catches throwing but not *never returning*; a descriptor trap that throws and a revoked proxy both yield honest absence rather than an exception — though *what* absence means differs by field, and the guard pins that split rather than a blanket rule: an accessor on `workflows`, `workflowsGate` or `engineCan` reads as absent, while an accessor on `denial` reads as `{unknown:''}`, because a key that is **not there** is the gate saying "nothing was denied" whereas a key that is there but cannot be read is "denied, and I could not read why" — folding the second into the first is exactly the false statement this face exists to prevent. Two further pins came out of an adversarial review. The exported retry list is **frozen at runtime**, not merely `as const`: the verdict hands out that same reference, so any consumer splicing it once would poison every later verdict in the process — the guard asserts `Object.isFrozen`, that four different mutation attempts leave it byte-identical, and that a verdict issued *after* those attempts still carries the original two entries. And the classifier reads `denial` only **after** both criteria have passed, since it is not a criterion but an extra field on the verdict: the guard pins the getter invocation count at zero for any error that does not match and at most one for an error that does. The scope line is drawn explicitly rather than overclaimed — "no getter ever runs" holds for `projectWorkflowsGate`, which reads **wire JSON** where every field is an own data property by definition, but not for the classifier, which reads a **thrown value** that may well be an SDK `APIError` class instance carrying `status` and `errorCode` on its prototype; insisting on own data descriptors there would report a perfectly readable error as unreadable, so that side promises only that it never throws. A final pin covers the **integration document's own worked example** rather than the library: the shipped SDK's `tasks.stream()` is an `async` generator, so calling it issues no request at all — the POST happens inside `streamRaw` on the first iteration, and a `try` wrapped around the `stream(...)` call itself can never catch the 501. A client following a submit-shaped recipe on the streaming leg would never run the classifier, and the whole strip-and-retry path would silently do nothing. The guard drives the **real** `TasksResource` against a fake transport, offline, and pins both halves: the synchronous leg is in flight the moment it is called, the streaming leg has issued zero requests after the call and raises on the first `next()` — and it does so through the **real** error path, with `openStream` returning an actual 501 `Response` that the SDK's own `errorFromResponse` turns into the typed error, pinning the `openStream`→`errorFrom` call order so a transport that stops minting `errorCode` cannot pass. The documented recipe is then **executed** rather than keyword-counted: exactly one retry, a second body that really lost both keys while every other setting survives byte for byte, the caller's own request object left untouched, one disclosure and only one, a second 501 propagating with the request count still at two, and — after the first 501 — an abort leaving the count at one with nothing disclosed. A last leg is type-level: `stripSelfOrchestrationIntent` carries an SDK `TaskRequest` overload, because the wide `Record<string, unknown>` form erases the caller's type and the document's "strip and resubmit" line would not compile without an unsafe cast; a real tsc run over a virtual file proves both the narrow and the wide path, with a known-red control — and it compiles the document's two recipes **verbatim**, extracted from the section itself, because a recipe that does not compile is a recipe that was never given: `{ transientOk: true, signal }` is a TS2379 under `exactOptionalPropertyTypes`, which no amount of prose review had caught. The last thing pinned is the one that would have been quietest of all: the SDK's `stream()` returns only on a `done` or `failed` frame, so a stream truncated mid-run — or yielding nothing at all — ends the `for await` just as normally as a completed one. The documented `runOnce` therefore tracks whether it ever saw a terminal frame and raises when it did not, the guard's success fixture emits a real terminal and asserts the handler received it, and a truncated-stream control asserts that shape is reported as a failure with no retry and nothing disclosed. That terminal-frame rule then needed one more turn of its own: the underlying reader returns *normally* when the signal is aborted, so the check as first written rewrote a user's cancellation into a generic stream fault — a client keying off `AbortError` to suppress the error would instead have shown a failure, or resubmitted. Cancellation is therefore checked first, a real-SDK case aborts from inside the handler and asserts the original `AbortError` survives with no retry and nothing disclosed, and the document is checked for that ordering. The harness runs the documented `handle` and `transcript.note` as real spies rather than pushing frames itself, the drive loop rethrows exactly as the document does, and the disclosure ledger is proven to be the caller's own array by a positive identity assertion — without which the cancellation leg's "nothing disclosed" would have been vacuously true. Each recipe is compiled **on its own**, with a preamble that declares only what a host supplies and injects no library symbol, since compiling them together let the second one borrow the first one's imports, and the preamble's own types are decoupled from what the recipes import so the "remove the imports and it must fail" control fails for the right reason — which is checked by attribution, not merely by redness. Ordering is the last thing to get right: the cancellation check must come before the truncation error but **both** must sit behind the terminal-frame test, because a cancellation that lands after the run already reported `done` would otherwise overwrite a real outcome — one that may have already had effects — with "cancelled", and a person reading that will run it again. Aborting from inside `handle(done)` and `handle(failed)` are both pinned to still report success, and the ordering assertion is anchored inside the streaming `runOnce` body rather than the section, since the section's first `throwIfAborted` belongs to the synchronous recipe and would have made a reversed streaming recipe pass — and that ordering check is now anchored on the TypeScript AST rather than on text, since a comment reproducing the two statements in the right order let a genuinely reversed body pass. One more timing fact had to be written into the recipe: a single SSE read buffers several frames and the SDK yields them back to back, so checking the signal only after the loop lets a cancelled run keep consuming the rest of the chunk — measured, an abort inside `handle(turn_start)` still swallowed the `done` that followed and reported success. The recipe therefore re-checks after every non-terminal frame. Finally, the behavioural matrix is no longer run against a copy of the recipe: both recipes are extracted from the document, transpiled, and **executed** with injected host objects, so the disclosure assertion really exercises the document's own `transcript.note(disclose(...))` line, and the synchronous leg gets the same full matrix the streaming one does |
365
- | `scripts/run-package-hygiene-test.mjs` | Everything `package.json` `files` ships — dist JS/typings and the Markdown docs — is screened line-by-line against a deny-list of strings that must never appear in a published artefact. The guard first proves each pattern still bites on a constructed sample (a screen that cannot fail is worse than none) and honours a per-pattern allow-list for legitimate product vocabulary, so the verdict is "clean surface", not "quiet grep". |
365
+ | `scripts/run-package-hygiene-test.mjs` | Everything `package.json` `files` ships — dist JS/typings and the Markdown docs — is screened line-by-line against a deny-list of strings that must never reach a public tarball (internal hostnames, codenames, person names, collaboration-process words, other repos' ledger ids and repo names; opaque ticket ids `CC-nnn` and post numbers `[nnnn]` are allowed as traceability references). Since 0.77.2 the build strips comments (`removeComments`; enforced by `run-dist-comments-test.mjs`), so what this gate screens in dist is code, string literals and type-level text. Markdown docs are enforced forward-only (CHANGELOG from 0.77.2, the integration doc from §81) because published sections are frozen.
366
366
  | `scripts/run-integration-doc-freshness-test.mjs` | The **integration contract** (`docs/INTEGRATION-CLIENTS.md`) and the **changelog** (`CHANGELOG.md`) checked against the code, because a document with no guard rots — this one had a whole nest of drift found on it within a day of being written. Five directions, each a claim a machine can actually evaluate. (1) *Counting discipline*: the version-anchor row for the guard count may no longer carry a hand-copied number at all — it changes every time a guard is added, and writing it down is planting a timer; the export counts that are still hand-copied (the surface total, the test-hook count, the sentence describing the surface's internal composition, the sum of the sixteen domain rows, and the three sub-counts) are each compared against a value **derived** from `public-export-baseline.json`, which is the drift a human reviewer caught last time. (2) *Coordinates alive*: every `src/` `scripts/` `docs/` path the doc quotes must be on disk **and tracked by git** — on disk is not in the repo, and a doc that points readers at a file living only in its author's working tree sends every clone to nothing. A file landing in the same commit takes a named carve-out that **stops applying** the moment the file is really tracked (it can no longer let anything through, and the guard prints a line asking for it to be deleted) — deliberately not a red, since turning red on the very commit that lands the file would just manufacture a break that only a follow-up commit could clear. (3) *Arm tables*: the `hitl_out_of_slice` row and the `not_in_slice` fenced list must equal, name for name and in **both** directions, the case labels that really fall into those two buckets — read through the **TypeScript AST**, since which bucket an arm lands in is decided by the argument to `nothing(...)` and by nothing a comment says. The extractor is anchored to the one production projector: exactly one function named `eventToSdkMessage`, exactly one `switch (ev.type)` inside it, and no repeated case label — anything else is a broken anchor rather than a verdict, because a second same-shaped switch elsewhere in the file would otherwise overwrite the real one's conclusions and leave the doc agreeing with a switch nobody runs. The list is delimited by a machine-readable fence rather than by section headings, because the same section also names the terminal arms as a counter-example and prose boundaries cannot tell a member from a foil. (4) *Released sections are frozen*: an **append-only ledger** carries every version ever published — its number, the commit it was published from, and the sha256 of its section — and each one is checked, not just the current release, since pinning only the latest would set every earlier version free the moment the next one ships. The ledger cannot vouch for itself either: each recorded hash is **re-derived from that release commit** through git, so editing an old section and its constant together no longer passes — and the commit the row names is in turn checked against the `gitHead` npm recorded at publish time, which is the one value this repository cannot rewrite, so pointing an old version at a freshly written commit does not pass either. The *set* of versions that must be frozen comes from the registry too, so deleting an old row together with its section — which would otherwise remove that version from every set the guard looks at — is red rather than invisible. A failed registry call is classified rather than swallowed, and the classification consults the registry's own status code *before* it considers connection-level symptoms, so an auth refusal whose body happens to mention the network is still red rather than a skip. The version set is compared as full SemVer including prereleases — matching only `x.y.z` would silently drop a published `0.30.0-beta.1` and reopen the very hole this direction closes — and section headings are matched on a whole-version boundary so a stable release cannot bind itself to the release-candidate section sitting above it. Publishing itself is a two-phase protocol rather than a paradox: before a release, exactly one row may be marked pending and must name the current `package.json` version, exempt from the checks whose inputs do not exist yet; once the registry has that version the row must be promoted, so the temporary state cannot survive its own release. And because the pending exemption rests entirely on "this version is not out yet," it is refused outright when the registry cannot be reached to confirm that — an unverifiable premise is not a licence. Three reverse directions close the rest: a section claiming to be released but absent from the ledger, a ledger entry whose section has vanished, and a `package.json` version that was never frozen. Publishing appends a row; it never rewrites one. (6) *Sentinels*: the readers §5a hands hosts for "is this port installed" are checked against what the source actually declares it returns — `hasXxx()` is a `boolean`, the card port / HITL surface / wire target return `T | null`, the `installHost` family returns `T | undefined`. Testing a `null`-returning reader for `!== undefined` is *always true*, and a self-check that passes whether or not the port is installed is worse than none, because hosts retire their own fallback on the strength of it. Both directions are red: an implementation that changes its sentinel without the doc following, and a doc that names the wrong one. The roster covers the zero-argument readers and their `*For` variants alike — a multi-session host reads the variants, so leaving them off would let exactly the surface desktop depends on drift unwatched — and the §5a table and the §8-B checklist line are each checked against the source, because hosts tick the checklist, and a guard that only watches the prose table misses the line people actually follow. (5) *Packaging*: the README ships with the package and opens by pointing hosts at the integration doc, and the checklist names two more files as required reading before an upgrade — all three must really appear in the `npm pack` manifest, or an npm consumer follows a relative link that npmjs rewrites onto a private repository. Missing tooling never takes the whole verdict down with it: when git, npm or the registry is unreachable those legs print the `SKIPPED-SECTION` marker and the rest still judges, while a release commit the ledger names but git cannot resolve is red rather than skipped. The guard says in its own header what it does **not** do: it judges counts, coordinates, arm sets, released bytes and the packing list — whether a sentence is *right* is still for review and for the hosts to report (7) *Retired names*: every name in the per-version `removed` ledger of `scripts/export-liveness.json` may appear in the integration doc only where a retirement note follows the name inside the same clause (or the table row's label cell is itself a retirement label); the scan is by identifier boundary after invisible text (HTML comments, link targets, reference-link labels, tag attributes) has been stripped, so a signature line in a code block, an inline `NAME = 4096`, a hidden note, or a note that belongs to a neighbouring name all count as a bare recommendation and go red. |
367
367
  | `scripts/run-type-superset-ledger-test.mjs` | The type/wire **superset ledger** (`docs/type-superset.json`): positions this package adds on top of a CC-shaped contract, each carrying the evidence for what CC's own type surface does or does not have there. Completeness is deliberately uneven and the ledger says so. The `_sema_*` private-key class is checked in **both** directions (a key in the source that never entered the ledger is red, naming key and file; a ledger row whose key left the source is red) — but only for keys written as literals, which is the convention the ledger mandates. A key assembled by string arithmetic is beyond what any static rule can enumerate, so the guard fails closed on every shape it *can* decide (a bare `_sema_` prefix is red wherever it appears, save one pinned guard site) and leaves the rest as a convention violation for review to catch, rather than claiming a completeness it does not have. The two hand-surveyed classes are only checked for coordinate and evidence integrity, never discovered. Both directions read the source through the **TypeScript AST**, not a text scan, and they read two different sets out of it. A *key site* is an identifier, or a string whose whole value is the key — so `'_sema_decision-v2'` is carried whole rather than truncated at the first non-identifier character into some *other* key that happens to be registered. A *mention* is the key appearing inside a longer string, which is prose, not usage. The staleness direction counts key sites only: a comment or a doc sentence left behind after the last real mint site is deleted must not keep the row alive (mutation-proven — with both the comment and the prose string untouched, removing the one real site turns the guard red). And because a prefix can be concatenated or interpolated into a key no static set will ever see, the bare `_sema_` literal is refused outright rather than traced: every occurrence is red except the single inline `startsWith` guard the sanitizer needs, because the set of expressions a bare prefix can travel through on its way to a concatenation is open-ended and enumerating it is always one form behind. Every row's `host` must still resolve, with the key being a real **member of that declaration** rather than a string occurring somewhere in the same file — `governanceForced`/`delegation` each live on two different shapes in one file, and a member commented out is a member deleted, which a text-shaped check happily reads as still present. And the direction worth the most: each machine-form `ccAbsenceEvidence` is re-derived from the row's own `key` — the ledger's recorded string must match that derivation verbatim, since a row quietly witnessing `\bnever_present\b` is green forever while watching nothing (mutation-proven: the same edit passes the unbound form and is caught by the bound one) — and the check runs against the names the installed `@sema-agent/agent-types` `.d.ts` set actually declares, parsed with the TypeScript AST rather than grepped, so a name CC merely mentions in a comment cannot force the row into the manual escape hatch and thereby retire the very witness that was supposed to fire the day CC declares that name for real. That escape hatch is gated by an allowlist living **in the guard**, not the ledger, so claiming it costs a reviewed diff. Missing material never reads as a pass, and the verdict splits by *why* it is missing: no TypeScript parser skips the suite before it starts; a missing `agent-types` still runs and prints the first three directions, then exits **1** when `package.json` declares the mirror but it is not installed — a broken install must not retire the repository's only "the day CC declares this name" alarm, and reporting it as a skip would leave "never evaluated" and "evaluated, no drift" indistinguishable to the runner — and exits 3 only when nothing declares the mirror at all, which is the one case where the direction genuinely does not apply. Either way a run that evaluated no witness is never counted as one that did. When the mirror *is* present its **installed version** is witnessed too (the two declared floors must agree with each other and the installed copy must meet them), since four preflight probes are satisfied by an arbitrarily stale mirror — they prove the extractor speaks, not that it is current. Every direction carries a positive control — known-present CC symbols, a comment-only sample proving the extractor distinguishes declaration from mention, and synthetic corpora fed through the **same** discriminator function the real verdict uses, so a verdict quietly rewritten to return nothing takes its own control down with it |
368
368
  | `scripts/run-rules-side-test.mjs` | The persisted-permission-rules lane's shared decision half. The two capability bits are checked as **two independent gates** — a worker can honestly advertise the rules lane while predating the revoke routes, and that shape must *hide* the governance surface rather than render a dead entry. Failure classification is by **disposition, not cause**: the two 404s (route missing vs. dead ticket) never share a bucket, a 503 `rule_import_retry` means *the ticket is still alive* (the opposite handling of a dead one), and a stale-cursor 400 drops the cursor and re-lists from the top exactly once — never resuming a stale keyset, never surfacing a partial governance list, and never paging past the hard cap. The persist-ack reader is **merged into** `readToolApprovalRespondAck`: the three-state verdict (`persisted` / `refused` / `unknown`) is derived only from an ack that passed the package's structural narrowing, and a half-shaped object such as `{rulePersisted: true}` with no `delivery` reads as `unknown` — the pre-merge shell read would have said `persisted`, which is precisely the double-ledger drift this file closes, so that case is pinned in reverse. The local-allow-rule skeleton pins all five narrowings (whole-tool, tool-name match, literal anchor with the escaped-star counter-example, bare interpreter prefix consulted only for Bash, and the canonical dangerous-pattern overlay) **with their refusal strings byte-for-byte** — the cli's 128-assertion suite anchors the same strings, so a one-character edit here changes observable behaviour on three clients — and asserts the parse is a pure function of its input, because the same call backs both "render the option" and "resolve the selected value" |
369
369
  | `scripts/run-park-decision-layer-test.mjs` | The decision layer behind the "stuck behind a card" family, shared by every client. A pending row that is **not in the queue** is three states, not one: a bounded, interruptible re-probe loop distinguishes *a decidable row*, *not born yet* (no positive evidence that anything settled — an empty queue proves nothing) and *settled elsewhere*, always probes at least once so a zero budget keeps the pre-fix semantics verbatim, cuts a hung read face off at the window rather than only noticing afterwards, and reports the honest failure when the window is spent instead of inventing a decision. The decision-note reader is likewise three-state: an explicit `noteRecorded: false` outranks an echoed note body, absence renders **no line at all**, and untrusted note text is flattened and bounded before it ever reaches a renderer. Row routing anchors on the deciding quantity — a row carrying `gateKind: "human"` with `toolName: "Write"` is a tool gate, because `human` is the engine's *generic* "someone must decide", not a synonym for a question — and the queue scan refuses to surface a row it cannot positively prove belongs to this session. A chain that fails after the row vanished is split by whether a card was ever presented: decided-elsewhere, or not-its-turn-yet. A row-level single-flight makes "at most one card per pending item" structural rather than incidental. The resume three-way card pins the option **order** (the zero-effect choice sits at index 0, because the frame carries no default-focus field and a stray Enter must not attach or cancel), renders only options the wired verbs can honour, collapses every ambiguous answer to zero action, omits the liveness line entirely when the engine gave no evidence, and — when there is no card lane at all — prints three real routes and exits on a dedicated code rather than reporting success |
370
370
  | `scripts/run-selfheal-reopen-test.mjs` | The 409 active-run self-heal decision chain: `governanceForced` narrows on strict `true` only; triage prefers the wire's `pendingGate.kind` and falls back to the status table (an off-table kind is never guessed into a card arm — hands-off plus the honest wording); a first-sight card makes zero closed/reopened claims and a host presentation receipt of `presented: false` demotes the outcome to reopen-failed; park-row ownership is a fail-closed positive proof (own-run ledger or session id — unprovable is not owned); the three gate-identity key literals live in exactly one mint (`hitl/gateIdentity.ts`, AST string-token scan); the armed-gate presentation ledger is per-session; and the `plan_review` reopen arm shares the arm arm's card body, three-state verdict and delivery pipe, consuming the presentation history once a decision is delivered. The same chain also carries the `running` three-way card: both plan-family gate kinds route to the plan arm and all four ask-family kinds to the ask arm (an off-table kind still never gets guessed into either); the card is offered only for verbs that can actually be honoured and a missing presenter means zero action rather than a silent cancel; a steer is sent **exactly once** with its three delivery outcomes worded apart (a `queued` receipt is the wire correcting the triage input, so the named park word decides which card gets reopened, and an unrecognised park word drives neither arm), and a steer failure is split into *provably not delivered* (4xx) and *delivery unknown*, because telling a user to resend a non-idempotent instruction that may already have landed is how duplicates get made. After a user-chosen cancel, "the session is free" is asserted only from a whitelist of terminal states — park states hold the claim, an unrecognised state word is not a release, a failed read is *unknown* rather than a release, and only a 404 counts as one — and the honest timeout line quotes how long it really waited |
371
- | `scripts/run-terminal-identity-copy-test.mjs` | Terminal-state **identity**, in both lanes where a stop gets a name. A run stopped by this deployment's own governance knobs — the open-set `limits.*` family, `output.invalid`, and the `blocked` contract terminal a ReportBlocked agent produces — is not a provider failure, and labelling it `API Error:` sends the reader to check the network, the key and the quota when the handle is the `--max-turns` they passed themselves. Those terminals now render a neutral row; the reverse direction is guarded just as hard, because asserting "this is *not* an API error" on a code the package does not recognise is the same misfiling pointed the other way — a real `gateway HTTP 502`, a `conflict.session_active_run` and any unknown code all keep the `API Error:` prefix, and the row keeps its `isApiErrorMessage` class flag so brief-mode visibility filtering does not silently drop it. The second half is who the rejected submission belonged to: the self-heal copy told every caller "Your message was NOT sent … send it again", which is three separate untruths for a system injection (a plan-review outcome, a cron wake-up, a task notification) — not the user's message, and not re-sendable, since a host queue marks those non-editable and non-recallable. The injected form says so instead, and the one sentence that promises re-delivery is pinned to the single disposition that earns it: `selfHealSubmissionDisposition` is the same function the host consults before putting the item back on its queue, so the promise and the behaviour cannot drift apart, and the arms where no card could be surfaced state plainly that nothing was delivered and nothing will retry. Since 0.72.6 the same gate pins the **follow intent** after a steer (L-379): a message handed to a live run only pays off if someone tails that run's own event stream, so `steerFollowIntent` decides from the delivery word whether to tail now, after the pending decision, or only after a wake — and the "watch that run" sentence turns into a factual "sema is following that run" **only** when the host declares it attached that tail, so a shell that did not wire it can never claim it did |
371
+ | `scripts/run-terminal-identity-copy-test.mjs` | Terminal-state **identity**, in both lanes where a stop gets a name. A run stopped by this deployment's own governance knobs — the open-set `limits.*` family, `output.invalid`, and the `blocked` contract terminal a ReportBlocked agent produces — is not a provider failure, and labelling it `API Error:` sends the reader to check the network, the key and the quota when the handle is the `--max-turns` they passed themselves. Those terminals now render a neutral row; the reverse direction is guarded just as hard, because asserting "this is *not* an API error" on a code the package does not recognise is the same misfiling pointed the other way — a real `gateway HTTP 502`, a `conflict.session_active_run` and any unknown code all keep the `API Error:` prefix, and the row keeps its `isApiErrorMessage` class flag so brief-mode visibility filtering does not silently drop it. The second half is who the rejected submission belonged to: the self-heal copy told every caller "Your message was NOT sent … send it again", which is three separate untruths for a system injection (a plan-review outcome, a cron wake-up, a task notification) — not the user's message, and not re-sendable, since a host queue marks those non-editable and non-recallable. The injected form says so instead, and the one sentence that promises re-delivery is pinned to the single disposition that earns it: `selfHealSubmissionDisposition` is the same function the host consults before putting the item back on its queue, so the promise and the behaviour cannot drift apart, and the arms where no card could be surfaced state plainly that nothing was delivered and nothing will retry. Since 0.72.6 the same gate pins the **follow intent** after a steer (): a message handed to a live run only pays off if someone tails that run's own event stream, so `steerFollowIntent` decides from the delivery word whether to tail now, after the pending decision, or only after a wake — and the "watch that run" sentence turns into a factual "sema is following that run" **only** when the host declares it attached that tail, so a shell that did not wire it can never claim it did |
372
372
  | `scripts/run-additive-key-passthrough-test.mjs` | The one disease shape behind two legs: a **closed whitelist / flattening arm** dropping a fact that is already on the wire, while both sides of the seam look correct. (1) The `task_progress` projection carries a registered **key ledger** — a frame populated with every key the service really projects is pushed through the shipped `eventToSdkMessage`, and the set of wire keys that survive must equal the registered pass-through list **name for name in both directions**, so quietly forwarding one more key is as red as quietly dropping one. `model` (the child run's model id, minted by core as `prepared.model.id` and projected by the server since 7.52.1) is the key this batch adds, with the same conditional the server itself applies: a non-empty string or no key at all — an empty string is neither a model id nor "unknown". The ledger is also checked against the fenced list in `docs/INTEGRATION-CLIENTS.md` §3d, so a doc that still says seven keys while the code forwards eight is red rather than merely stale. (2) The decide-failure arms carry the server's S-02 `currentPending` pointer key from a 409 `approval_stale` refusal onto the outcome the host reads. The reader is structural rather than `instanceof`, because the client is host-injected and the class identity is not this package's to assume; a half triple never mints (half a pointer cannot relocate anything), an empty string is not presence, and `checkpointToken` never transits. Both the allow and the deny leg are driven end to end through the real durable approval path — as is the accept-session leg, where a refusal carrying the pointer key must now re-raise instead of silently re-sending the human's answer for the **old** card as a plain approve (one decide call, pointer preserved), while a legacy 400 still falls back exactly as before — and all three flattening points must call the one shared reader — the same-shape residue check that makes "fixed one arm and left the twin" red instead of invisible. (3) The same disease growing on the REQUEST side: the `.mcp.json` → server-spec projection rebuilds each server key by key, and the settings schema deliberately leaves some keys parse-transparent — whatever JSON the file carries reaches the engine untouched, because validating them where the whole domain parses all-or-nothing would let one bad declaration take every server down silently. The whitelist had no row for the newest of them, so an operator's per-tool declarations — the ones the write fence reads — were stripped at the package boundary while both sides looked correct. The criterion is not "is that key handled" but the transparent-key table read out of the INSTALLED schema at runtime, reconciled name-for-name against this leg's ledger, so the day upstream adds a third one this turns red and forces an explicit decision. Behaviour is pinned on both transports, by object identity rather than deep equality (a rebuild would be a second judge), and malformed values must transit UNCHANGED rather than be refused here — the engine refuses them loudly and names the server, whereas a package-side judge can only swallow a declared protection quietly. Absence still mints no key, unknown keys still never reach the wire (the fix is the dropped key, not the gate), and the one transparent key this leg deliberately does not forward is a ledger entry with its own exit condition: it belongs to the deployment plane, and the day the request-plane type declares it the entry's premise is gone and the gate says so |
373
373
  | `scripts/run-esc-halt-plan-test.mjs` | The Esc stop decision every client shares: fire the **turn-level** halt first, and escalate to a **run-level** cancel in exactly two cases — the engine itself answered with a 409 from the closed code set (it is saying "there is no in-flight turn here; use cancel for a run-level stop"), or that shot came back with no verdict at all *and* the shell can independently prove a permission card was on screen. Everything else does not escalate. The asymmetry is the whole point and every negative control guards the same direction — deciding *not* to escalate costs the user one more choice on a busy-session card (recoverable), deciding to escalate wrongly tears down a run that was alive and takes every in-flight tool with it (not). So: the closed code set is a **frozen** value, not a `ReadonlySet` — type-level immutability does not stop a consumer's `.add()`, and the guard proves it by really trying to mutate the exported value and then checking the verdict did not drift; the escalation gate is the **conjunction** of that closed set and the 409 status, since honouring the code alone lets a 500 that merely quotes it drive a destructive call; `interrupt.not_held` and `steering.not_running` are deliberately outside the set (the first means *this replica* has no live face — the run may be perfectly alive on another); an unreadable code falls to the no-escalation side; a `parked` flag never overrides a verdict the engine did give, and only strict `true` counts when it did not. The first shot is unconditional by construction — it does not consult `parked`, because the 409 it earns is exactly the verdict the gate wants — and the verdict itself is a closed machine-readable reason word, not display copy. A third escalating case was added once tearing the stream stopped reaping the run: with detach armed, a shot that never lands leaves the run going all the way to the end of the turn, so the Esc the user pressed has no effect at all and nothing on screen says so — the old behaviour had a silent backstop (tearing the stream ended the run) and that backstop is gone. The new fact is held to the same three disciplines as `parked`: it is read only where the engine gave no verdict, it is judged **after** `parked` so an existing host's reason word does not change under it, and only strict `true` counts. Absence is proven to be a no-op rather than asserted — the guard carries its own reference implementation of the previous version's table, runs the full grid through both, requires zero divergence when the new field is omitted, and first shows the comparison really does report a difference on the one cell where the two versions are meant to differ |
374
374
  | `scripts/run-peer-frame-projection-test.mjs` | The three engine-injected lanes design/385 puts on the **one** `task_notification` carrier, which are not the same kind of thing at all: a delegated child's uplink (`agentMessage`), another session's message drained from this session's own box (`crossSessionMessage`), and a receipt about one of *this* session's own outbound messages (`crossSessionNotice`). The engine renders none of them inside a `<task-notification>` shell, so a client that projects them as the generic completion card shows "background task finished" while the model read a colleague's sentence — two faces describing different events. The discriminator is pinned to the **typed carrier being present**, never to the `summary` text: those carriers can only be minted by the engine's injection legs (the external `notify()` input is a strict subset of the payload and can wear none of them), while `summary` is filled by every notification there is — so anchoring on text would let any background task impersonate a colleague's message by writing `<agent-message from="…">` into its own summary, and a positive control asserts exactly that payload still projects as the generic card. Fail-closed has two tiers rather than one: a broken **required** field (empty `from`, a non-string `body`, a notice `kind` outside the closed set) returns absence so the caller falls back to the generic card — an honest downgrade where the user still sees the notification — while a broken **optional** field drops only itself, because losing an attribution note and losing a colleague's whole message are not the same magnitude. The provenance side record is **required and must agree on four points** (`kind` matches the lane; `from`/`taskId`/`seq` are present and equal the carrier/payload — each equality is anchored on a core mint site and pinned by the cli wire-anchor A-K24), so a carrier signed with a trusted name but a disagreeing provenance falls back to the generic card; peer bodies pass the same authority-envelope neutralization core applies (`<task-notification>` etc. are defused) so a colleague's text can never seed the resume dedup ledger. Lane precedence copies the engine renderer's own order, because the model already read the frame in that order and a client ordering of its own would put a card on screen that disagrees with the frame the model saw. Rendering and parsing of the transcript line live in the same module and are round-tripped in both directions, including a body carrying a forged closing tag (a parser fooled there hands half a message to the next row) and a quote inside the sender label (which must not forge a second attribute); the notice lane is deliberately kept **out** of the parser, since recognising it would mean anchoring the `[Cross-session …]` prefix and a user typing that same line would be rendered as engine speech. Hostile carriers are read as own **data** descriptors only and accessors are never invoked at all — `catch` catches throwing, not never returning — proven by a counting getter that must stay at zero calls, alongside a revoked proxy and a prototype-only carrier; and four legacy payload shapes assert the no-carrier path is byte-identical to before, which is the executable form of "zero difference for an older host" |
@@ -391,7 +391,7 @@ public-surface guard checks that last one).
391
391
  | `scripts/run-cost-reconcile-projection-test.mjs` | The **end-of-run cost reconciliation** reaching consumers at all. The engine splits a run's spend on the wire — the task's own cost, which deliberately excludes delegated sub-agents, the delegated total itself, and the within-task compaction subtotal that sits inside the own figure — and states two reconciliation identities for them. The package used to project none of it, so a cost view could only ever see one number and under-reported both delegated and compaction spend. Both structures are now projected onto the result as superset fields in the wire's integer micro-currency unit, read key by key, with unreadable keys dropped individually, an entirely unreadable structure omitted rather than emitted empty, and unknown categories passed through since the vocabulary belongs upstream. The delegated cost stays **absent when it was never priced**, never a fabricated zero. The same reader also feeds a terminal chrome arm carrying the three parts plus the reconciled total, so the two faces can never compute different answers; the reconciled total is minted only when both sides are known, and otherwise a discriminator bit says which side is unknown. **The reference field for total cost keeps its meaning** — it remains the task's own spend and the delegated total is not folded into it — because that is a shape the wider ecosystem reads; the reconciled figure is offered beside it, not in place of it. A frame that carries no stats emits no arm at all, and the existing rule that in-stream per-turn usage is not published for sub-flows is pinned unchanged, since delegated spend arrives once, at the end. The bit that says those figures are a lower bound is **per stream**, not per context: the emit context belongs to the caller and may be reused across streams, so a gap observed on one run is no evidence at all about the next one — the observation is held for the duration of one stream and handed to both projection faces by value, and the guard drives a reused context both sequentially and concurrently to prove neither direction leaks |
392
392
  | `scripts/run-task-progress-terminal-projection-test.mjs` | The one tick that says a delegated child **finished**. The engine fires exactly one final beat carrying a terminal face, and says in the same breath why it exists — so a consumer sees the row finish instead of watching it vanish after the last running beat — but the package's projection whitelist had no seat for that field and its adapter still carried the older premise in a comment, so the terminal beat arrived byte-identical to another running one: the panel row stayed up waiting for a defensive sweep (which only ever settles rows bound to a card still open this turn) or for a separate notification frame. The status now rides through as an **open set** with the vocabulary left upstream, while the question *which words are terminal* is answered by a closed pair on the adapter side — an unrecognised new word takes the running path, because guessing it terminal ends a row that is still working whereas one extra running beat merely renders late. A terminal beat settles the row directly under the lane proof its binding gives it (not the main lane a notification would use, and not by card id, since the engine is naming a child rather than closing a card), freezes the inline group-row twin in the same beat so a later sweep cannot reset the real tool count, clears the session-resident ledger, and fires the stop hook only for a child whose start really fired. It does not mark the row live or emit a second progress beat, and it shares the settled-row ledger with the other two settle legs so a replay or a double-delivery cannot produce a second end. Three things are pinned **unchanged**: a running beat, an absent status (older engines never send the field, and reading absence as terminal would make every child row disappear on its first beat), and the workflow lane gate, which still runs before any of this |
393
393
  | `scripts/run-assistant-arm-identity-test.mjs` | The identity keys on an assistant row, and an explicit account of the two that are **deliberately not** there. What the renderer received was a bare role-and-content object, so a dozen consumer sites downstream were each estimating what the message envelope should have told them. The id is taken from the engine's own event id rather than minted locally, because it has to be **the same value** on the live leg and on a durable replay — a freshly minted one would make a replayed message look new to a host's dedup and to rewind — and when the wire carries none the key is simply absent rather than filled with a random stand-in wearing an identity it does not have; it is also kept distinct from the envelope's own local render key, which is a different identity. The model name comes from what the host pinned when it opened the stream (the request was the host's to build) and is never guessed, since a wrong model name is worse than none once a billing or capability face looks it up. Usage and stop reason are **not** minted on this arm, and the reason is frame order rather than effort: content arms arrive before the turn's closing frame, so at the moment the arm is emitted the engine has not yet said what the round cost — anything put there would be an estimate, which is the very thing this work exists to remove — and synthesising a follow-up assistant update when the real figure lands is also refused, because that shape does not exist upstream and would place a message in the transcript the engine never sent. Their real values leave through the turn's own neutral arm as two superset keys, the usage one reusing the **same single mint point** the footer rollup already folds so the two faces cannot diverge, and the stop reason passed through verbatim as an open set — the machine signal for *was this turn cut short*, previously blind on both the stream and the trace. The existing behaviours beside them are pinned too: no arm at all when usage is wholly absent, and the sub-flow cut-out that keeps a child's turn from driving the leader's face |
394
- | `scripts/run-text-segment-authority-test.mjs` | The **authoritative segment replacement** on `text_end` (L-310, server >=7.75.3). `text_end.content` now goes through the same redactor as `result` and the ledger while `text_delta` stays verbatim, so the two **may differ** — an answer that quoted a credential used to be committed to the local transcript in its unredacted form, because the arm only forwarded the boundary signal. Six timing shapes are pinned, two of which an adversarial review reproduced against the installed engine's real bytes and which the first design got wrong in both directions: a second boundary in the same turn (the per-block case on one provider lane) used to make the first segment's prose vanish, and a boundary that arrives *after* the tool card (the other lane emits it at finalize) used to be read as "this package never handled that segment" and reported nothing at all. Three additive keys, all never-false; the two shapes that look alike are told apart by the second one, because the host's action in them is the opposite. The end-to-end legs drive the real pipeline without hand-inserting a segment commit — doing so is exactly what hid the first defect. A second review round then found two combination timings on top of the first fix — a tool card followed by *more* deltas in the same segment, and a byte count that had been documented as a message count — and both are pinned here too. A third round caught a length that the prose called bytes while the code returned UTF-16 units — harmless in ASCII, and on CJK text enough to leave the credential on screen — plus a backfill ledger that had to be kept in step, so the terminal frame does not re-render the segment a second time — kept in step only where the whole stretch sits in one message, because those ledgers are per-message and a fourth round showed that writing across them charges one message's prose to another. A fifth round settled the whole class into one invariant the guard now checks against the previous release's behaviour: this package only rewrites bytes it is still holding in the current message — once a segment has crossed a package-side boundary it emits the three keys and changes nothing else **0.68.2 (CC-01):** the segment identity is now minted here, not by the host: every committed assistant text row carries a top-level `_sema_segment_id` (stamped once at the `adapt()` exit, so the durable whole-message leg and the streamed-segment leg are covered alike; thinking blocks, tool_use-tailed rows and chrome events are left byte-for-byte), `text_segment_end` carries the same value as `segmentId` before rotating, subagent boundaries never rotate, and a replayed stream yields the same identities. Three mutations (no rotation / no stamping / stamping tool_use rows) each turn the guard red **0.69.0 (CC-02):** the same authority replacement now covers the reasoning face (`reasoning_end`, server >=7.77.0): a thinking block still buffered is swapped whole and its live tail recomputed; one already committed at a boundary (the usual timing, since the first text delta commits it) is left untouched and the host is told the row to replace by its uuid, never re-emitted. Subagent boundaries are ignored and the text-segment identity does not rotate **0.69.1 (CC-09):** the run-stream replay guard still drops a frame whose event id was already seen, but it now reports the drop through the host's dropped-frame sink as `duplicate_seq` instead of vanishing silently (server 7.77.0 reuses the first reasoning delta's id for `reasoning_end`, so that authoritative segment is lost on the print lane until 7.78.1); the interactive adapter has no such guard and keeps receiving it **0.69.1 (CC-10/CC-11):** subagent segment-end frames are fenced on all three identity keys (a frame carrying only `sourceTaskId` no longer masquerades as the leader's), and a reasoning segment that spans tool cards now hands the host every committed row it covers (`committedUuids`) so nothing unredacted is left behind |
394
+ | `scripts/run-text-segment-authority-test.mjs` | The **authoritative segment replacement** on `text_end` (server >=7.75.3). `text_end.content` now goes through the same redactor as `result` and the ledger while `text_delta` stays verbatim, so the two **may differ** — an answer that quoted a credential used to be committed to the local transcript in its unredacted form, because the arm only forwarded the boundary signal. Six timing shapes are pinned, two of which an adversarial review reproduced against the installed engine's real bytes and which the first design got wrong in both directions: a second boundary in the same turn (the per-block case on one provider lane) used to make the first segment's prose vanish, and a boundary that arrives *after* the tool card (the other lane emits it at finalize) used to be read as "this package never handled that segment" and reported nothing at all. Three additive keys, all never-false; the two shapes that look alike are told apart by the second one, because the host's action in them is the opposite. The end-to-end legs drive the real pipeline without hand-inserting a segment commit — doing so is exactly what hid the first defect. A second review round then found two combination timings on top of the first fix — a tool card followed by *more* deltas in the same segment, and a byte count that had been documented as a message count — and both are pinned here too. A third round caught a length that the prose called bytes while the code returned UTF-16 units — harmless in ASCII, and on CJK text enough to leave the credential on screen — plus a backfill ledger that had to be kept in step, so the terminal frame does not re-render the segment a second time — kept in step only where the whole stretch sits in one message, because those ledgers are per-message and a fourth round showed that writing across them charges one message's prose to another. A fifth round settled the whole class into one invariant the guard now checks against the previous release's behaviour: this package only rewrites bytes it is still holding in the current message — once a segment has crossed a package-side boundary it emits the three keys and changes nothing else **0.68.2 (CC-01):** the segment identity is now minted here, not by the host: every committed assistant text row carries a top-level `_sema_segment_id` (stamped once at the `adapt()` exit, so the durable whole-message leg and the streamed-segment leg are covered alike; thinking blocks, tool_use-tailed rows and chrome events are left byte-for-byte), `text_segment_end` carries the same value as `segmentId` before rotating, subagent boundaries never rotate, and a replayed stream yields the same identities. Three mutations (no rotation / no stamping / stamping tool_use rows) each turn the guard red **0.69.0 (CC-02):** the same authority replacement now covers the reasoning face (`reasoning_end`, server >=7.77.0): a thinking block still buffered is swapped whole and its live tail recomputed; one already committed at a boundary (the usual timing, since the first text delta commits it) is left untouched and the host is told the row to replace by its uuid, never re-emitted. Subagent boundaries are ignored and the text-segment identity does not rotate **0.69.1 (CC-09):** the run-stream replay guard still drops a frame whose event id was already seen, but it now reports the drop through the host's dropped-frame sink as `duplicate_seq` instead of vanishing silently (server 7.77.0 reuses the first reasoning delta's id for `reasoning_end`, so that authoritative segment is lost on the print lane until 7.78.1); the interactive adapter has no such guard and keeps receiving it **0.69.1 (CC-10/CC-11):** subagent segment-end frames are fenced on all three identity keys (a frame carrying only `sourceTaskId` no longer masquerades as the leader's), and a reasoning segment that spans tool cards now hands the host every committed row it covers (`committedUuids`) so nothing unredacted is left behind |
395
395
  | `scripts/run-gate-negative-controls-test.mjs` | Whether the registry-shaped guards among the 74 suites above actually turn red when the material they check really breaks — a census had found 16 of them clean enough to rehearse safely (closed sets, mirrors, baselines, floors, a type-shape ratchet) without touching any judgement code. Each is exercised by tampering a disk copy of the real material, spawning the guard's own unmodified script, asserting it exits non-zero and names the disease, then restoring the file byte-for-byte. Seven guards of the same shape and 51 behaviour/projection suites are catalogued rather than rehearsed this round — see `docs/GATE-NEGATIVE-CONTROLS.md` for the full table, the reasons, and a one-minute manual replay recipe for each blind one. The suite cross-checks its own case count against that document's row counts in both directions, so a case quietly dropped from the array without the document following is itself an undeclared blind guard. The backup that makes the restore possible is taken by **exclusive create**: checking for it and then copying are otherwise two steps, and two instances can pass the check together — the later one overwrites the only clean copy with material the earlier one has already tampered, and the rehearsal that promises to leave no trace leaves a permanently corrupted file instead. That interleaving is rehearsed too, in a throwaway directory of its own |
396
396
  | `scripts/run-engine-cap-reader-factory-test.mjs` | The one shared implementation behind every capability reader's four ports (cache, generation gate, probe tee, invalidation), exercised as a table: every reader in the table runs the *same* criteria (the table length is the source of truth, and a roster check fails the gate if any source file calls the factory without having a row) — the four states, a throwing projection treated exactly like an unreadable one (and never escaping the tee), the generation rules (a stale generation is dropped before the projection even runs; a projection that changes the generation mid-flight cannot overwrite the newer value, whether it returns or throws; the caller's `opts` is snapshotted once; and omitting the generation still writes, because that supply is additive and this refactor does not quietly tighten it), the top-level key's getter being read exactly once, a freshly minted "unobserved" reading on every miss (two misses are never the same object, so a consumer that mutates one cannot taint another base URL), one independent table per reader that never take each other down, invalidating one base URL leaving every other base URL's reading untouched, `forget`/tee being no-ops on an empty or non-string base URL, the read anchor being resolved dynamically, and the deliberate split in how presence is judged per reader. |
397
397
  | `scripts/run-mcp-liveness-test.mjs` | The engine's **liveness observation** about each MCP server it hosts (`wiring_manifest.mcp[].liveness`, engine-side from core 7.24.3 / server 7.91.2): one reader, one word list, one leg-level verdict. The cell answers *can this server still be reached* — it is not the connect-time verdict beside it, which the engine deliberately freezes (a server that died mid-run still reads `connected`), and it is not a re-dial's judgement either, so `status: "failed"` next to `liveness.state: "reachable"` is a **real row**: the server answered the handshake and answered with a protocol error — up, and misconfigured. The three words are read as a closed set (an exchange completed / it was lost in transport or the clock / what came back does not answer the question), and a word outside it is malformed rather than rendered, because a word nobody upstream has defined is not a sentence worth putting on screen. **Absence is the fourth reading and is not one of the words**: it means *no liveness record is available*, which on the wire covers a server this leg never reached, a declaration that could not be dialled, an older engine, and a record the projection ahead of us dropped — all indistinguishable, so it is never read as "we looked and could not tell" (a strictly stronger claim), never as healthy and never as off. The failure-class footnote rides the unreachable word only, and one that turns up anywhere else, or that is malformed, loses **just the footnote** while the word and its timestamp stay: the honest reading is then "cannot be reached, reason not given", not "this record is broken". Malformed never becomes healthy: a cell that is present but unreadable marks its row and pulls the leg-level verdict back to *cannot tell*, since letting it sit beside a reachable row would report a leg as reachable on the strength of a record that may well have said the opposite. The verdict takes the worst fact first rather than a majority or the newest reading, carries no server count — so there is no fabricated zero to be read as "no problems" — and its timestamp belongs to **that leg's** observation, not to now: the engine runs no probe and adds no traffic of its own, so this is a per-leg snapshot rather than a heartbeat. The replayed roster on the session panel and the live leg go through the same reader, and the panel's own deployment-side rows carry no liveness position today, so the verdict does not borrow a word from that face. The word list is bitten in both directions where an installed witness exists and the absence of one is itself asserted against the installed engine's version, so the day it ships the comparison starts on its own; the day the wire types declare the cell, the guard turns red and asks for the anchor to move there |
@@ -399,6 +399,7 @@ public-surface guard checks that last one).
399
399
  | `scripts/run-lane-proof-identity-test.mjs` | The **instance identity of a lane proof**: the main-lane proof is minted fresh on every emission. Previously a single module-level constant object was handed both to `laneOf(an unregistered task id)` and to some fifty main-lane emission points, so two unrelated consumers — across adapter instances, across streams, across turns — held the same object: writing a card id onto one of them was readable on the other, and the four opening main-lane events changed together. Nothing in this package writes to a lane proof and the known consumers only read it, so this is an **aliasing hazard on a published output surface** rather than an observed corruption — a consumer that uses the proof as an identity key, for dedup, or as a view-layer identity would conflate two unrelated rows without writing a single byte, which is precisely the half that freezing the object would not solve. The gate therefore anchors on instance identity: two independent adapter instances, two rows inside one instance, the same id read twice, and two arms in one beat are each distinct references; mutating one leaves the others byte-identical; and the subagent lane, which already minted fresh, is the control that proves the criterion discriminates. The main-lane **value** is unchanged — an unregistered id still answers `{lane:"main"}` with exactly one own key and still emits its events, so absence is not turned into a second kind of absence — with ordering pinned three ways (registered-then-read, read-then-registered with no retroactive edit of an already delivered proof, the same id twice) and the id failure classes pinned four ways (unregistered, empty string, absent, non-string, the last two emitting no panel event at all rather than an ownerless proof). Where one row emits **two** events — the terminal-tick and card-close legs, which each yield a lifecycle stop and a panel end — the attribution is decided once (a consumer binding a card between the two yields must not split one row across two lanes) while each event still gets its own proof, so a host consuming them one at a time cannot poison the second before it is even yielded. The run stream leg is covered as the same shape, and a syntax-tree check forbids reintroducing a module-level lane-proof object literal or a module-level `LaneProof`-annotated binding (judged on the type node, not on text, so a compile-time pin tuple that merely mentions the type is not miscaught), backed by a type-checker pass that also catches an un-annotated module-level cache such as `const x = mainLane()` while letting the callable factory itself through, while the module-private three-state sentinels of the untrusted read are frozen instead — only `Object.freeze` counts, never `Object.seal`, which still permits writes to existing keys — their exposure being confined to one module |
400
400
  | `scripts/run-memory-entries-wire-test.mjs` | The two memory-governance capability bits and the three memory-entry response readers. Each bit (`capabilities.memoryCompliance`, for the entry-provenance and erasure endpoints; `capabilities.memoryOrigin`, for the external-origin listing and clearance endpoints) is read the same four-state way as its sibling capability readers: an absent key is reported as not reported (never folded into `false` — an older engine simply does not answer, and the right next step is to try the endpoint and read its 501), `true` is the face being mounted, `false` is a positive "not on this deployment" (the wire does not distinguish a backend without control-plane ownership from an empty operator roster, so the wording never guesses which), any non-boolean value is unreadable and drops the cell instead of being folded into "absent", and a capabilities body that is not an object at all is unreadable rather than "not reported". The two bits deliberately stay **two** readers with two separate per-engine tables, because the engine deliberately keeps them two separate product faces even while they happen to carry the same value today: feeding one an unreadable body, or invalidating one, leaves the other's reading untouched, and a body where one is on and the other off is answered one bit at a time. The entry-export reader narrows each row on its own (an empty array really is zero rows, a non-empty array with nothing readable in it is reported as unreadable rather than as "no rows", and partly bad rows are kept with a dropped count), reads the external-origin marker as three states rather than a boolean (the two structural carriers mark a row; a row whose frontmatter cannot be read, or which carries the third, suspended-form carrier, is undecidable, because the judge for that carrier lives in the engine and this package refuses to mint a second copy of it), and treats an unreadable "is this the whole scope" flag as "not the whole scope". Its verdict port implements — in code, not in a comment — the rule that an empty answer is never a clean store: the caller must state whether the request declared origin-awareness, because this endpoint withholds marked entries by default and the two bodies are shaped identically, so without that statement an empty answer is only ever "unknown"; the affirmative answer is scoped to the one named scope and carries that scope with it, and the type has no store-wide arm at all. The erasure receipt reader keeps three things apart that are easy to collapse: "this call erased nothing" (a real receipt whose erased list is empty and whose not-found list explains why, per id), "a 200 with an empty body", and "a body that could not be read" — at the reading, the counting and the verdict layer alike; it refuses a version envelope it does not recognise instead of reinterpreting it, treats the three closed vocabularies as closed (an unknown word is unreadable, never folded into a known arm), keeps an unreadable binding as unknown instead of claiming "unbound", passes the "history cannot be judged" flag through as four states (set, explicitly unset, absent, and present-but-unreadable — an unreadable flag is kept distinct from an absent one, and the history verdict then answers "unknown" rather than the stronger claim), and answers the replay question as three states so that the degraded lane is never retried automatically. The clearance receipt reader carries the cleared marker through verbatim and says separately whether it was reported at all. Every array in every response is snapshotted once — the length is read exactly once and each index exactly once, rather than iterating the caller's own iterator — because an array that reports one length while being walked and another afterwards could otherwise have a marked row quietly dropped while the "was anything unreadable" check saw nothing, which ends in calling the scope clean; an array that reports an absurd length is reported as unreadable rather than silently truncated to its first rows. All three readers never throw. |
401
401
  | `scripts/run-dist-orphan-test.mjs` | Every `.js` / `.d.ts` under `dist/` must have a same-named source under `src/`, and every source must have its build output — because the compiler only writes and never deletes, so a module removed from the sources keeps shipping from the previous build (the whole `dist/` directory is on the publish whitelist) while the public-surface gate only looks at what the barrel exports and the hygiene gate only looks at forbidden words. Orphans are named one by one; the pre-publish posture is a clean rebuild, and this gate is the check that the posture was actually followed. |
402
+ | `scripts/run-dist-comments-test.mjs` | **dist ships zero comments.** Since 0.77.2 the build strips comments (`removeComments`); this gate walks every shipped `dist/**/*.js` / `*.d.ts` and counts comment trivia with the TypeScript scanner (string literals containing `//` and generator methods are not comments), failing on the first one (`DIST-COMMENT-FAIL`). Source comments are an internal surface; what still ships is code, string literals and type-level text, which the hygiene gate screens. Negative control: one plain comment appended to `dist/index.js` turns it red. |
402
403
  | `scripts/run-task-request-omission-receipt-test.mjs` | Where every key a client hands to the request constructor ends up. The constructor used to answer "not stamped" the same way for four different reasons — value absent, no such row, wrong lane, live gate closed — and a key it had never heard of did not even get that: an unattended run could pass a system prompt, an output schema and a spend cap and receive a body holding the objective and the session id, with nothing anywhere saying what was left out or why. The guard pins the three answers apart. **Seated** keys reach the body verbatim on the unattended lane. Keys the package **knows but did not carry** never throw, never reach the body, and each gets a receipt row with one word from a frozen cause list — every present key is on the body or on the receipt, never both and never neither, checked across both lanes with the live gate open and closed against a key-by-key table written independently of the package's own routing. Keys the package **does not know** are refused loudly and are a separate cell, not a fourth cause: the cause list has no word that could hold them, and the seat reader answers `unknown`, not `none`. The cause list is bitten from both sides (exact, every word producible, nothing produced outside it, the judge table's keys read from source through the TypeScript parser) and no second hand-copied list may exist in `src/`. Upstream claims are read straight off the installed SDK typings: a key seated in this release must be a named request field, a key registered as having no upstream counterpart must not be — the day it appears the guard turns red — and the index signature counts as evidence for nothing. |
403
404
  | `scripts/run-session-policy-wire-test.mjs` | The per-session tool-rule face: the capability bit that says whether an engine keeps such rules at all, and the narrow read plus tightening orchestration built on it. The bit is read the same four-state way as its sibling capability readers — an absent key is not reported (this binary predates the position itself, which says nothing about whether the face exists), `true` is present, `false` is a positive absent (this deployment keeps no per-session rules), any non-boolean value is unreadable and drops the cell rather than being folded into "absent", and a capabilities body that is not an object at all (an array included) is unreadable rather than "not reported". Its single-source verdict answers whether to show the tightening entry: only an engine that says yes is `yes`, both a positive no and a binary too old to answer are `no`, and never having observed a capabilities body is `unknown`. Whether to put a request on the wire is deliberately a **different** question with a different answer for that last state, and lives with the orchestration. The read narrows three ways that must not collapse into each other: a record that really is empty (present, version zero — what an engine answers for a session no rules were ever written for), a record that cannot be read, and a call that failed with a typed disposition. A half-bad record — one rule bucket well-formed and another the wrong shape — counts as unreadable in full, because the write verb replaces the whole record: dropping the bad bucket and writing the rest back would empty it, which relaxes the rules while the caller sees a 200. An unreadable version stamp is never filled in with a zero, a bucket that reports an implausible number of entries is unreadable rather than walked or truncated, each array's length and each of its indices are read exactly once, and a throwing accessor is unreadable rather than propagated. Every load-bearing key is read as an own property — the envelope, the version stamp, each of the five buckets and each array index — because a prototype lookup would let a polluted prototype put a bucket into the reading that the wire never carried, and since the write replaces the whole record the union would then write that invented restriction back as a real one; a guard pollutes the object and array prototypes in place and proves all four shapes stay out. Because the write replaces the whole record, adding a restriction means writing "what is already there, plus the new entries": the union only ever adds, de-duplicates verbatim, keeps a bucket that is present but empty (present-and-empty and absent are opposite meanings, and dropping it would relax the rules), mints no bucket neither side had, copies entry bytes as they came (no trimming, sorting or path rewriting — those judgements belong to the engine), and takes its bucket names from the engine's own type surface rather than a hand-copied list, so a new bucket upstream is a compile error instead of a silently dropped one. Which differences count as relaxing is the engine's judgement and is never re-implemented here: a refusal on those grounds is reported verbatim, never swallowed and never retried. The orchestration is guarded on three axes. Timing: when the record moves between the read and the write, it re-reads and re-writes **exactly once** — two reads and two writes, no more — and the second attempt's union carries the other writer's entries, which is the entire point of re-reading; a second collision is reported rather than retried a third time, and an uncontended write makes exactly one round trip. Concurrency: an explicit barrier holds both orchestrations first reads at the same version before either may write, and the criterion is how many times the store actually rejected a stale version rather than how many writes it saw — the latter is equally true of two serial successes, so it would stop detecting contention the day the interleaving changed. Under real contention the store rejects exactly once, both writers land on strictly different versions, both writers entries survive in the final record, and the round trips are exactly three reads and three writes; the same two orchestrations run serially are asserted to reject zero times in two reads and two writes, which is what proves those numbers are discriminating. On a store where every write loses the race both report a collision having written exactly twice each. Failure classification: a relaxation refusal, a refusal to stamp a version the store cannot establish (the same status code as the relaxation refusal but a different machine code, and folding it into that arm would send the caller off to edit entries that are not the problem), a missing session, a deployment without the face, an ownerless session, a collision code, a bare conflict with no machine code, a rejected body and an unauthorized call each land on their own arm — the collision arm is matched on the machine code verbatim rather than on the status, because two different situations share that status and only one of them is worth retrying. The remaining split is not "which code is this" but "did the engine answer at all": an answered client-side refusal is allowed to say nothing was written, because every such refusal on this endpoint is emitted before the record is touched, while a throw with no answer at all — a dropped connection, a timeout, a response body the transport itself could not decode, a server fault — can only say "unknown", since that throw may well have happened after the record was already saved. Two guards prove that is not theoretical: a write whose receipt cannot be read, and a write that throws after the fixture store has committed, both leave the record changed. Neither is success nor failure: the only honest answer is "unknown", it carries no version, and it is never retried. The direction of the change is likewise never claimed. The engine’s tighten-only rule is an identity gate, not a field gate — for a principal the deployment treats as an operator it does not run at all, so a union that adds a name to an existing allowlist is accepted and really does widen it. This package does not mint a second copy of that rule, so what it reports is the fact it can stand behind: the record now holds what it already had plus the entries sent here. The sentence for a saved write is pinned to contain no claim of tightening, narrowing or restriction, and a guard reproduces the operator case to prove the widening is real while the wording stays honest. Every sentence the module mints is checked pairwise distinct, with the receipt-unreadable one required to keep its "may already be in effect" and the record-unreadable one required to say nothing was written. |
404
405
  | `scripts/run-persisted-rule-write-test.mjs` | The **single-step tightening write** for persisted permission rules — the dual of the revoke surface, and the half where a hopeful reading is expensive. The two behaviours this entry accepts are **derived** from the three-state vocabulary by subtracting the widening one, never hand-copied: the guard bites in both directions (every word in the derived table is really accepted; every constructed outsider — casing variants, trailing whitespace, the widening word itself — is refused before a single round trip), keeps a word-count canary against the parent table, and pins that the source file contains **exactly one** array literal carrying two or more behaviour words, so a second hand-written table shows up as a boundary failure rather than as drift nobody reads. A standing approval is minted by answering a permission question or by importing settings; this entry is not a third route, and the widening word is unspellable in the type. The outcome is a discriminated union whose two failure arms are **not** interchangeable: ten refusal causes each promise the same single thing — not one byte reached the store — while three separate words say the opposite, that the outcome could not be read at all. The service's own "I cannot tell" (a write that could not be confirmed as standing: store wobble, a redemption leg with no decidable ending, or a write that landed and was revoked concurrently before the read-back) stays in the second group, because announcing "nothing was written" invites a clean retry that is not clean, and announcing success misreports a tightening that may already be gone. Anything the shared failure classifier does not recognise defaults to the same place — this is a non-idempotent verb, so "unclassified" must mean "unknown", never "no write": a 500 can happen after the store commits. A 2xx whose body cannot be read is pinned in the same direction and from both sides: it reads as unknown, and the unknown arm carries **neither** the revision nor the written row, so a consumer cannot even spell the shape that would let "unreadable" pass for "written". `persisted` is guarded against the reading everyone reaches for first: it says *this call wrote*, not *a new rule now exists* — an equivalent rule already in the store still mints a fresh causal point, so the lane honestly reports `persisted`, and the material for judging whether the **logical** rule is new (the approval ledger on the returned row) is handed to the caller rather than folded into the discriminant, since the package does not have the one fact that judgement needs. The returned row goes through the **same single narrower** the listing surface uses — proven by running one row corpus through both legs and asserting the two verdicts agree entry for entry (an adversarial pass that forks the listing leg back into an inline copy reds here immediately), plus a source pin that the predicate is defined once and called from exactly the two legs. That sharing is what keeps a row whose behaviour cell is unreadable **visible in both places** rather than hidden by one of them — and the shared narrower is deliberately followed by a second, *different* question only the write leg can ask: is the row that came back **the rule that was just sent**? A rule's identity is a triple, so a receipt missing its behaviour cell, carrying the sibling state, carrying the widening one, or naming another text or another scope is not evidence that the requested tightening is standing; it reads as unknown with its own word, kept distinct from "unreadable" so the two stay tellable apart, while display and derived cells may vary freely. An adversarial pass found both of the gaps this pins: the receipt check that only looked at whether the row was renderable, and a subtler one — pulling the verb off the injected port and calling it bare drops the receiver, so a host that hands over a real resource object (a class instance whose verbs reach the transport through `this`) would see every write throw and be reported as "could not tell", retry after retry, while the package's three other ports call their verbs as methods and work fine. Both are pinned from the failing side: a shorthand-method facade and a class-instance facade must reach the transport and return a real outcome, with the bare-call throw proven to be a real failure mode first. A 405 is split in two, because only the engine's own bare code is evidence about **the engine**: with it, the path exists and this verb does not, so this worker predates the verb; without it — an absent, empty, or foreign code, which is what a proxy or gateway blocking the method typically returns as HTML or an empty body — what was seen is that the verb was refused, while **who** refused it and **at which hop** is unknown, so it lands in the unreadable-outcome arm with its own word rather than sending someone to upgrade a worker that is fine, hiding an entry that is live, or — the part a second adversarial pass insisted on — promising that nothing was written. That promise is what the refusal group means, and a middlebox is free to forward the request and only then answer 405 on its own policy, so a caller who skipped reconciliation on that word would leave behind a standing refusal the user believes never took effect; the guard pins exactly that shape, with a double that writes the rule and *then* answers 405, and with the engine's own bare code still landing in the refusal group beside it. (The same passes caught the naive status-only reading and the asymmetry where an empty-string code fell through to a different bucket.) The documented recovery for an unreadable outcome — retry, then reconcile — is pinned to be **ledger-safe** rather than merely asserted: against a double that models the engine's own "is this identity already standing?" question, re-sending the same identity comes back as a no-op with the approval ledger and the bucket revision both unmoved, however many times it is repeated, while two concurrent writers each landing a causal point are both honestly reported as having written. Finally the four local gates are pinned to be free: a missing write verb on the injected port, an unwritable direction, an unreadable identity pair, and a principal key that is present but cannot name anyone all refuse **without sending anything** — the last of those because silently degrading a blank target into absence would land a tightening aimed at someone else in the caller's own bucket and return a 200 |
@@ -1,29 +1 @@
1
- /**
2
- * abortableSleep.ts — REF-CC-147(域词表-17)+ REF-CC-149(域词表-21)裁定的落地面:P2 裁决
3
- * (docs/refactor/P2-LEDGER-DRAFT.md「I族」)明确 D 族(8 处轮询/重试/看门狗循环)不收编成通用
4
- * poller——循环体裁/终止量/节拍语义/失败后是否继续/abort 归属/谁持有状态六个维度互不兼容,
5
- * 统一后正确性核心会消失在配置参数里。**只抽两个低层原语**,这是其中之一(另一个是
6
- * unrefTimer/TimersPort 接线,见 host.ts):统一「怎么安全地睡一觉」,不统一「该睡多久/睡完
7
- * 干什么」——退避曲线、终止预算这些策略维度仍各自留在调用点。
8
- *
9
- * 原实现 = agentSession/backgroundView.ts 的私有 `sleep()`(D 族六处循环里唯一做对「dispose
10
- * 必须唤醒在飞的等待」这条不变量的一处,P1 lexicon 镜头点名「全仓最佳形」)。workflowClient.ts
11
- * 的 `sleep()` 此前是裸 `new Promise(r => setTimeout(r, ms))`——不感知 abort:dispose() 只置位
12
- * `disposed`/abort 掉 AbortController,但正在等待的这一轮 sleep 不会被唤醒,后果两条:
13
- * ① consume() 循环在 dispose 后最多还会多跑一轮(sleep 到期才重新检查 `!disposed` 退出);
14
- * ② 那个未被清理的 setTimeout 在 Node 上继续维持事件循环存活,把 headless `-p` 车道的进程退出
15
- * 拖慢最多一个退避周期(当前三处退避常量的最大值,见 workflowClient.ts 的 *_BACKOFF_MS)。
16
- */
17
- /**
18
- * abort 感知的 sleep:到点正常 resolve;`signal` 在等待期间被 abort 时立即 resolve 并清掉
19
- * 定时器——不留悬挂 timer 拖住宿主进程。调用方的循环在下一轮入口自会读到 `signal.aborted`
20
- * 并退出,本函数只负责「别让等待本身变成一个新的悬挂句柄」,不负责终止判断。
21
- *
22
- * 入口先查 `signal.aborted`(wave1 回炉修:此前只在等待期间监听 'abort' 事件——若 signal
23
- * 在**调用前**就已 aborted,事件早已派发完毕,`addEventListener('abort', …)` 上的 listener
24
- * 永不触发,函数会退化成裸 `setTimeout` 照睡满整个 `ms` 并留一个未清的句柄,这正是本文件要
25
- * 消灭的那个病,只是窗口从「sleep 期间 abort」挪到了「abort 后才进 sleep」。可达路径见
26
- * workflowClient.ts 的 527→529→530:dispose 掐在一次 GET 在飞时,catch 会原样返回上一轮的
27
- * `degraded=true`,回到 consume() 循环拿到的正是一个已 aborted 的 signal。
28
- */
29
1
  export declare function abortableSleep(ms: number, signal: AbortSignal): Promise<void>;
@@ -1,31 +1,3 @@
1
- /**
2
- * abortableSleep.ts — REF-CC-147(域词表-17)+ REF-CC-149(域词表-21)裁定的落地面:P2 裁决
3
- * (docs/refactor/P2-LEDGER-DRAFT.md「I族」)明确 D 族(8 处轮询/重试/看门狗循环)不收编成通用
4
- * poller——循环体裁/终止量/节拍语义/失败后是否继续/abort 归属/谁持有状态六个维度互不兼容,
5
- * 统一后正确性核心会消失在配置参数里。**只抽两个低层原语**,这是其中之一(另一个是
6
- * unrefTimer/TimersPort 接线,见 host.ts):统一「怎么安全地睡一觉」,不统一「该睡多久/睡完
7
- * 干什么」——退避曲线、终止预算这些策略维度仍各自留在调用点。
8
- *
9
- * 原实现 = agentSession/backgroundView.ts 的私有 `sleep()`(D 族六处循环里唯一做对「dispose
10
- * 必须唤醒在飞的等待」这条不变量的一处,P1 lexicon 镜头点名「全仓最佳形」)。workflowClient.ts
11
- * 的 `sleep()` 此前是裸 `new Promise(r => setTimeout(r, ms))`——不感知 abort:dispose() 只置位
12
- * `disposed`/abort 掉 AbortController,但正在等待的这一轮 sleep 不会被唤醒,后果两条:
13
- * ① consume() 循环在 dispose 后最多还会多跑一轮(sleep 到期才重新检查 `!disposed` 退出);
14
- * ② 那个未被清理的 setTimeout 在 Node 上继续维持事件循环存活,把 headless `-p` 车道的进程退出
15
- * 拖慢最多一个退避周期(当前三处退避常量的最大值,见 workflowClient.ts 的 *_BACKOFF_MS)。
16
- */
17
- /**
18
- * abort 感知的 sleep:到点正常 resolve;`signal` 在等待期间被 abort 时立即 resolve 并清掉
19
- * 定时器——不留悬挂 timer 拖住宿主进程。调用方的循环在下一轮入口自会读到 `signal.aborted`
20
- * 并退出,本函数只负责「别让等待本身变成一个新的悬挂句柄」,不负责终止判断。
21
- *
22
- * 入口先查 `signal.aborted`(wave1 回炉修:此前只在等待期间监听 'abort' 事件——若 signal
23
- * 在**调用前**就已 aborted,事件早已派发完毕,`addEventListener('abort', …)` 上的 listener
24
- * 永不触发,函数会退化成裸 `setTimeout` 照睡满整个 `ms` 并留一个未清的句柄,这正是本文件要
25
- * 消灭的那个病,只是窗口从「sleep 期间 abort」挪到了「abort 后才进 sleep」。可达路径见
26
- * workflowClient.ts 的 527→529→530:dispose 掐在一次 GET 在飞时,catch 会原样返回上一轮的
27
- * `degraded=true`,回到 consume() 循环拿到的正是一个已 aborted 的 signal。
28
- */
29
1
  export function abortableSleep(ms, signal) {
30
2
  if (signal.aborted)
31
3
  return Promise.resolve();
@@ -1,28 +1,3 @@
1
- /**
2
- * adapt/arms.ts — 15 条帧臂的**注册表**(A 族拆分 REF-CC-SPLIT-01 的臂表化半场)。
3
- *
4
- * 拆分前这是驱动壳里一个 600 行的 `switch (m.type)`。改成注册表之后:
5
- * · 每条臂是一个具名 generator,`break` 变 `return`(语义逐条对位,`default:` 空臂 = 表里查不到);
6
- * · 臂拿不到驱动壳的局部变量 —— 它要什么状态,必须写在自己的 deps 类型里。
7
- *
8
- * 🔴 表用 `Map` 而不是普通对象(复审结论 3):对象表被 `{type:'__proto__'}` /
9
- * `{type:'toString'}` 这种帧一命中就是原型污染(wire 帧是 UNTRUSTED 的开集)。
10
- *
11
- * 🔴 **两级 deps 是判别力本身,不是洁癖**(矩阵 §3.5):
12
- * `ProjectionArmFn` 只吃 `{ctx, idOf}` —— 于是「这几条臂碰不到任何适配器状态」这件事从
13
- * 注释里的断言变成**编译期事实**。全量注入(一个大 deps 喂所有臂)会让封闭性声明失去判别力,
14
- * 那正是 A 族 对抗复审里 5/11 那次翻车的根因。
15
- *
16
- * 🔴 **7 组由输入帧顺序耦合的臂对**(复审结论 3 的清单;源码级无顺序耦合 —— 每帧单键分发 +
17
- * return,但**帧到达顺序**会改判别结果)。逐条点名在下面各臂的注释里,标记 `⟨帧序耦合 N/7⟩`:
18
- * 1. assistant(开卡)→ task_progress(FIFO 猜父只认**已开**的卡)
19
- * 2. assistant(Workflow 卡)→ task_progress(判据③ 认 seenWorkflowToolCallIds)
20
- * 3. assistant(开卡)→ tool_end_result(没开过的卡,tool_end 直接丢)
21
- * 4. task_progress(首 tick)→ tool_end_result(关卡 settle 只结**已发布**的行)
22
- * 5. task_progress(首 tick,fire Start)→ task_notification(if-started 门)
23
- * 6. retry_status(挂旗)→ 任一主 lane 内容帧(恢复即清)
24
- * 7. turn_usage(endEmitted)→ result(`!endEmitted` 门决定 end 度量发不发第二遍)
25
- */
26
1
  import type { AdapterContext, AdapterOutput } from '../seam.js';
27
2
  import { type Frame, type IdOf } from './ids.js';
28
3
  import type { AdapterInstanceLedger } from './instanceLedger.js';
@@ -30,21 +5,10 @@ import type { PanelTaskLedger } from './panelTasks.js';
30
5
  import type { TextStream } from './textStream.js';
31
6
  import type { ToolCardLedger } from './toolCards.js';
32
7
  import type { TurnFlags } from './turnFlags.js';
33
- /** 纯投影臂的 deps —— **零适配器状态**(类型即证明)。 */
34
8
  export interface ProjectionArmDeps {
35
9
  readonly ctx: AdapterContext;
36
10
  readonly idOf: IdOf;
37
11
  }
38
- /**
39
- * 有状态臂的 deps —— 矩阵 §3.5 收窄成的六件 **+ `flags`,共七件**,不多给。
40
- *
41
- * ⚠️ ADAPT-F7 事实纠正(2026-08-02):原文写「收窄成的六件,不多给」,而实现一直是七件 ——
42
- * 矩阵 §3.5 那份六件清单把 M6a/M6b 的消费留在 M0 驱动壳的「A7 编排半场」,但落码时
43
- * `result` / `turn_usage` / `retry_status` 三臂被整条搬进臂表,于是必须多注一件 `TurnFlags`。
44
- * 偏离本身有理由(且 §3.3 要求 M6a/M6b 两半同模块,`turnFlags.ts` 满足),但**用一个对不上的
45
- * 数字自证封闭性**正是 [refactor-pipeline-form] 记的「封闭性声明必须机械验证(5/11 案)」的入口:
46
- * 下一棒拿六去数七,只会得出「多注了一件,删掉」的错结论。数目与理由一起写明。
47
- */
48
12
  export interface ArmDeps extends ProjectionArmDeps {
49
13
  readonly text: TextStream;
50
14
  readonly cards: ToolCardLedger;
@@ -52,16 +16,6 @@ export interface ArmDeps extends ProjectionArmDeps {
52
16
  readonly flags: TurnFlags;
53
17
  readonly inst: AdapterInstanceLedger;
54
18
  }
55
- /**
56
- * 只碰 `{ctx, idOf}` 的臂。`ProjectionArmFn` 可赋给 `ArmFn`(入参逆变),所以两级可以放同一张表,
57
- * 而**声明成哪一级**就是那条臂的封闭性声明 —— 想碰状态就得改签名,改签名就在 diff 里显形。
58
- * ⚠️ 判别力边界说清楚:它证明的是「碰不到 M1/M2/M3/M6/inst 五个**适配器状态**模块」,
59
- * 不是「零副作用」—— `workflow_complete` 就是个例子,它写 notifications.ts 的 **module 台账**。
60
- */
61
19
  export type ProjectionArmFn = (m: Frame, deps: ProjectionArmDeps) => Generator<AdapterOutput>;
62
20
  export type ArmFn = (m: Frame, deps: ArmDeps) => Generator<AdapterOutput>;
63
- /**
64
- * 帧类型 → 臂。**Map 而不是对象字面量**:`{type:'__proto__'}` 这种 UNTRUSTED 帧命中对象表
65
- * 就是原型污染(复审结论 3)。表里查不到 = 拆分前 `switch` 的 `default:` 空臂,行为同。
66
- */
67
21
  export declare const ARMS: ReadonlyMap<string, ArmFn>;