@neuralis/brain-core 0.3.2 → 0.4.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 (311) hide show
  1. package/README.md +79 -1409
  2. package/app/README.md +1 -1
  3. package/app/cards/FilesystemCardShell.tsx +42 -4
  4. package/app/cards/FilesystemChangeCard.tsx +34 -11
  5. package/app/cards/README.md +6 -5
  6. package/app/cards/diffUtils.ts +99 -1
  7. package/app/filesystem/FilesystemHost.tsx +17 -2
  8. package/app/filesystem/api/README.md +3 -3
  9. package/app/filesystem/api/index.ts +35 -11
  10. package/app/filesystem/api/projectPackages.ts +13 -102
  11. package/app/filesystem/domain/types.ts +30 -10
  12. package/app/filesystem/hooks/README.md +3 -3
  13. package/app/filesystem/hooks/useDiscoverableSources.ts +19 -30
  14. package/app/filesystem/hooks/useFilesystemSSE.ts +1 -1
  15. package/app/filesystem/host/README.md +5 -3
  16. package/app/filesystem/host/buildFilesystemHostValue.ts +19 -0
  17. package/app/filesystem/host/installFilesystemHostComponents.tsx +1 -1
  18. package/app/filesystem/lib/sourceKindForm.ts +118 -39
  19. package/app/filesystem/lib/treeVisualState.tsx +98 -4
  20. package/app/filesystem/store/README.md +1 -0
  21. package/app/filesystem/store/filesystemStore.ts +86 -5
  22. package/app/filesystem/store/gitSlice.ts +5 -0
  23. package/app/filesystem/store/helpers.ts +17 -0
  24. package/app/filesystem/store/listSlice.ts +8 -0
  25. package/app/filesystem/ui/CodeEditor.tsx +18 -3
  26. package/app/filesystem/ui/FileEditor.tsx +15 -10
  27. package/app/filesystem/ui/FileTree.tsx +41 -16
  28. package/app/filesystem/ui/FolderPicker.tsx +251 -0
  29. package/app/filesystem/ui/GitControlPanel.tsx +5 -0
  30. package/app/filesystem/ui/PackagesPanel.tsx +52 -374
  31. package/app/filesystem/ui/PolicyEditor.tsx +109 -24
  32. package/app/filesystem/ui/README.md +17 -11
  33. package/app/filesystem/ui/RawMediaPreview.tsx +58 -0
  34. package/app/filesystem/ui/SourceBranchHeader.tsx +4 -4
  35. package/app/filesystem/ui/SourcesPanel.tsx +420 -360
  36. package/app/filesystem/ui/Toolbar.tsx +1 -2
  37. package/app/shared/filesystem.png +0 -0
  38. package/dist/app/host.js +57 -49
  39. package/dist/scripts/sourceRootPairV1.d.ts +47 -0
  40. package/dist/scripts/sourceRootPairV1.d.ts.map +1 -0
  41. package/dist/scripts/sourceRootPairV1.js +147 -0
  42. package/dist/scripts/sourceRootPairV1.js.map +1 -0
  43. package/dist/scripts/sourceToFolderV1.d.ts +56 -0
  44. package/dist/scripts/sourceToFolderV1.d.ts.map +1 -0
  45. package/dist/scripts/sourceToFolderV1.js +152 -0
  46. package/dist/scripts/sourceToFolderV1.js.map +1 -0
  47. package/dist/scripts/sourceToHostV1.d.ts +56 -0
  48. package/dist/scripts/sourceToHostV1.d.ts.map +1 -0
  49. package/dist/scripts/sourceToHostV1.js +132 -0
  50. package/dist/scripts/sourceToHostV1.js.map +1 -0
  51. package/dist/src/connectors/AppSourceConfigStore.d.ts +17 -3
  52. package/dist/src/connectors/AppSourceConfigStore.d.ts.map +1 -1
  53. package/dist/src/connectors/AppSourceConfigStore.js +46 -6
  54. package/dist/src/connectors/AppSourceConfigStore.js.map +1 -1
  55. package/dist/src/connectors/HostConnector.d.ts +201 -0
  56. package/dist/src/connectors/HostConnector.d.ts.map +1 -0
  57. package/dist/src/connectors/HostConnector.js +903 -0
  58. package/dist/src/connectors/HostConnector.js.map +1 -0
  59. package/dist/src/connectors/inProcessTransport.d.ts +131 -0
  60. package/dist/src/connectors/inProcessTransport.d.ts.map +1 -0
  61. package/dist/src/connectors/inProcessTransport.js +447 -0
  62. package/dist/src/connectors/inProcessTransport.js.map +1 -0
  63. package/dist/src/domain/access/callerPermissions.d.ts +29 -0
  64. package/dist/src/domain/access/callerPermissions.d.ts.map +1 -1
  65. package/dist/src/domain/access/callerPermissions.js +85 -5
  66. package/dist/src/domain/access/callerPermissions.js.map +1 -1
  67. package/dist/src/domain/access/nativeLocalFence.d.ts +65 -0
  68. package/dist/src/domain/access/nativeLocalFence.d.ts.map +1 -0
  69. package/dist/src/domain/access/nativeLocalFence.js +171 -0
  70. package/dist/src/domain/access/nativeLocalFence.js.map +1 -0
  71. package/dist/src/domain/access/uriPermission.js +3 -3
  72. package/dist/src/domain/access/uriPermission.js.map +1 -1
  73. package/dist/src/domain/byteRange.d.ts +15 -0
  74. package/dist/src/domain/byteRange.d.ts.map +1 -0
  75. package/dist/src/domain/byteRange.js +30 -0
  76. package/dist/src/domain/byteRange.js.map +1 -0
  77. package/dist/src/domain/defaultPermissions.d.ts.map +1 -1
  78. package/dist/src/domain/defaultPermissions.js +29 -0
  79. package/dist/src/domain/defaultPermissions.js.map +1 -1
  80. package/dist/src/domain/mime.d.ts +8 -16
  81. package/dist/src/domain/mime.d.ts.map +1 -1
  82. package/dist/src/domain/mime.js +10 -99
  83. package/dist/src/domain/mime.js.map +1 -1
  84. package/dist/src/domain/paths.d.ts.map +1 -1
  85. package/dist/src/domain/paths.js +5 -3
  86. package/dist/src/domain/paths.js.map +1 -1
  87. package/dist/src/domain/sourceConfig.d.ts +52 -19
  88. package/dist/src/domain/sourceConfig.d.ts.map +1 -1
  89. package/dist/src/domain/sourceConfig.js +103 -19
  90. package/dist/src/domain/sourceConfig.js.map +1 -1
  91. package/dist/src/domain/sourcePackages.d.ts +8 -8
  92. package/dist/src/domain/sourcePackages.d.ts.map +1 -1
  93. package/dist/src/domain/sourceRootPair.d.ts +88 -0
  94. package/dist/src/domain/sourceRootPair.d.ts.map +1 -0
  95. package/dist/src/domain/sourceRootPair.js +108 -0
  96. package/dist/src/domain/sourceRootPair.js.map +1 -0
  97. package/dist/src/domain/syncTypes.d.ts +8 -1
  98. package/dist/src/domain/syncTypes.d.ts.map +1 -1
  99. package/dist/src/domain/syncTypes.js +1 -0
  100. package/dist/src/domain/syncTypes.js.map +1 -1
  101. package/dist/src/domain/types.d.ts +24 -1
  102. package/dist/src/domain/types.d.ts.map +1 -1
  103. package/dist/src/domain/uri.d.ts.map +1 -1
  104. package/dist/src/domain/uri.js +3 -2
  105. package/dist/src/domain/uri.js.map +1 -1
  106. package/dist/src/embeddings/clients/GeminiEmbedder.d.ts.map +1 -1
  107. package/dist/src/embeddings/clients/GeminiEmbedder.js +2 -5
  108. package/dist/src/embeddings/clients/GeminiEmbedder.js.map +1 -1
  109. package/dist/src/embeddings/clients/OpenAIEmbedder.d.ts.map +1 -1
  110. package/dist/src/embeddings/clients/OpenAIEmbedder.js +2 -3
  111. package/dist/src/embeddings/clients/OpenAIEmbedder.js.map +1 -1
  112. package/dist/src/embeddings/clients/QwenEmbedder.d.ts +2 -0
  113. package/dist/src/embeddings/clients/QwenEmbedder.d.ts.map +1 -1
  114. package/dist/src/embeddings/clients/QwenEmbedder.js +10 -2
  115. package/dist/src/embeddings/clients/QwenEmbedder.js.map +1 -1
  116. package/dist/src/embeddings/clients/VoyageEmbedder.d.ts +2 -0
  117. package/dist/src/embeddings/clients/VoyageEmbedder.d.ts.map +1 -1
  118. package/dist/src/embeddings/clients/VoyageEmbedder.js +8 -1
  119. package/dist/src/embeddings/clients/VoyageEmbedder.js.map +1 -1
  120. package/dist/src/embeddings/factory.d.ts +5 -3
  121. package/dist/src/embeddings/factory.d.ts.map +1 -1
  122. package/dist/src/embeddings/factory.js +7 -3
  123. package/dist/src/embeddings/factory.js.map +1 -1
  124. package/dist/src/embeddings/models.d.ts +1 -1
  125. package/dist/src/embeddings/models.d.ts.map +1 -1
  126. package/dist/src/embeddings/retry.js +1 -1
  127. package/dist/src/embeddings/retry.js.map +1 -1
  128. package/dist/src/embeddings/types.d.ts +6 -0
  129. package/dist/src/embeddings/types.d.ts.map +1 -1
  130. package/dist/src/embeddings/types.js +11 -0
  131. package/dist/src/embeddings/types.js.map +1 -1
  132. package/dist/src/git/GitRunner.d.ts +110 -30
  133. package/dist/src/git/GitRunner.d.ts.map +1 -1
  134. package/dist/src/git/GitRunner.js +511 -199
  135. package/dist/src/git/GitRunner.js.map +1 -1
  136. package/dist/src/git/GitWatchRegistry.d.ts +41 -35
  137. package/dist/src/git/GitWatchRegistry.d.ts.map +1 -1
  138. package/dist/src/git/GitWatchRegistry.js +61 -79
  139. package/dist/src/git/GitWatchRegistry.js.map +1 -1
  140. package/dist/src/git/gitPlace.d.ts +61 -0
  141. package/dist/src/git/gitPlace.d.ts.map +1 -0
  142. package/dist/src/git/gitPlace.js +74 -0
  143. package/dist/src/git/gitPlace.js.map +1 -0
  144. package/dist/src/git/sourceGitControl.d.ts +31 -19
  145. package/dist/src/git/sourceGitControl.d.ts.map +1 -1
  146. package/dist/src/git/sourceGitControl.js +208 -92
  147. package/dist/src/git/sourceGitControl.js.map +1 -1
  148. package/dist/src/git/sourceGitStatus.js +1 -1
  149. package/dist/src/git/sourceGitStatus.js.map +1 -1
  150. package/dist/src/git/untrackedStats.js +1 -1
  151. package/dist/src/git/untrackedStats.js.map +1 -1
  152. package/dist/src/index.d.ts +2 -1
  153. package/dist/src/index.d.ts.map +1 -1
  154. package/dist/src/index.js +2 -1
  155. package/dist/src/index.js.map +1 -1
  156. package/dist/src/lifecycle.d.ts +8 -11
  157. package/dist/src/lifecycle.d.ts.map +1 -1
  158. package/dist/src/lifecycle.js +2 -1
  159. package/dist/src/lifecycle.js.map +1 -1
  160. package/dist/src/routes/git.d.ts.map +1 -1
  161. package/dist/src/routes/git.js +183 -54
  162. package/dist/src/routes/git.js.map +1 -1
  163. package/dist/src/routes/helpers/uriGuard.d.ts +1 -1
  164. package/dist/src/routes/helpers/uriGuard.d.ts.map +1 -1
  165. package/dist/src/routes/helpers/uriGuard.js +3 -2
  166. package/dist/src/routes/helpers/uriGuard.js.map +1 -1
  167. package/dist/src/routes/helpers.d.ts +10 -3
  168. package/dist/src/routes/helpers.d.ts.map +1 -1
  169. package/dist/src/routes/helpers.js +12 -5
  170. package/dist/src/routes/helpers.js.map +1 -1
  171. package/dist/src/routes/list.d.ts.map +1 -1
  172. package/dist/src/routes/list.js +2 -3
  173. package/dist/src/routes/list.js.map +1 -1
  174. package/dist/src/routes/policies.d.ts +6 -4
  175. package/dist/src/routes/policies.d.ts.map +1 -1
  176. package/dist/src/routes/policies.js +22 -16
  177. package/dist/src/routes/policies.js.map +1 -1
  178. package/dist/src/routes/raw.d.ts.map +1 -1
  179. package/dist/src/routes/raw.js +65 -11
  180. package/dist/src/routes/raw.js.map +1 -1
  181. package/dist/src/routes/sources.d.ts +60 -18
  182. package/dist/src/routes/sources.d.ts.map +1 -1
  183. package/dist/src/routes/sources.js +411 -299
  184. package/dist/src/routes/sources.js.map +1 -1
  185. package/dist/src/services/BridgeService.d.ts +15 -35
  186. package/dist/src/services/BridgeService.d.ts.map +1 -1
  187. package/dist/src/services/BridgeService.js +30 -88
  188. package/dist/src/services/BridgeService.js.map +1 -1
  189. package/dist/src/services/FilesystemService.d.ts +3 -2
  190. package/dist/src/services/FilesystemService.d.ts.map +1 -1
  191. package/dist/src/services/FilesystemService.js +110 -32
  192. package/dist/src/services/FilesystemService.js.map +1 -1
  193. package/dist/src/services/FilesystemUiService.d.ts +4 -1
  194. package/dist/src/services/FilesystemUiService.d.ts.map +1 -1
  195. package/dist/src/services/FilesystemUiService.js +66 -25
  196. package/dist/src/services/FilesystemUiService.js.map +1 -1
  197. package/dist/src/services/SyncScheduler.d.ts +12 -4
  198. package/dist/src/services/SyncScheduler.d.ts.map +1 -1
  199. package/dist/src/services/SyncScheduler.js +37 -8
  200. package/dist/src/services/SyncScheduler.js.map +1 -1
  201. package/dist/src/services/baselineRepair.d.ts +13 -3
  202. package/dist/src/services/baselineRepair.d.ts.map +1 -1
  203. package/dist/src/services/baselineRepair.js +13 -3
  204. package/dist/src/services/baselineRepair.js.map +1 -1
  205. package/dist/src/services/declaredSources.d.ts +125 -42
  206. package/dist/src/services/declaredSources.d.ts.map +1 -1
  207. package/dist/src/services/declaredSources.js +117 -45
  208. package/dist/src/services/declaredSources.js.map +1 -1
  209. package/dist/src/store/dataFormats.d.ts +17 -2
  210. package/dist/src/store/dataFormats.d.ts.map +1 -1
  211. package/dist/src/store/dataFormats.js +18 -3
  212. package/dist/src/store/dataFormats.js.map +1 -1
  213. package/dist/src/sync/DeltaSyncRunner.d.ts.map +1 -1
  214. package/dist/src/sync/DeltaSyncRunner.js +144 -4
  215. package/dist/src/sync/DeltaSyncRunner.js.map +1 -1
  216. package/dist/src/sync/SyncJobQueue.d.ts +7 -2
  217. package/dist/src/sync/SyncJobQueue.d.ts.map +1 -1
  218. package/dist/src/sync/SyncJobQueue.js +16 -10
  219. package/dist/src/sync/SyncJobQueue.js.map +1 -1
  220. package/dist/src/sync/SyncWatchService.d.ts +21 -4
  221. package/dist/src/sync/SyncWatchService.d.ts.map +1 -1
  222. package/dist/src/sync/SyncWatchService.js +115 -10
  223. package/dist/src/sync/SyncWatchService.js.map +1 -1
  224. package/dist/src/sync/categoryInference.d.ts +1 -1
  225. package/dist/src/sync/categoryInference.js +1 -1
  226. package/dist/src/usecases/admitSourceRoot.d.ts +65 -0
  227. package/dist/src/usecases/admitSourceRoot.d.ts.map +1 -0
  228. package/dist/src/usecases/admitSourceRoot.js +85 -0
  229. package/dist/src/usecases/admitSourceRoot.js.map +1 -0
  230. package/dist/src/usecases/browseSourceFolders.d.ts +85 -0
  231. package/dist/src/usecases/browseSourceFolders.d.ts.map +1 -0
  232. package/dist/src/usecases/browseSourceFolders.js +210 -0
  233. package/dist/src/usecases/browseSourceFolders.js.map +1 -0
  234. package/dist/src/usecases/createDeclaredSources.d.ts +13 -2
  235. package/dist/src/usecases/createDeclaredSources.d.ts.map +1 -1
  236. package/dist/src/usecases/createDeclaredSources.js +39 -0
  237. package/dist/src/usecases/createDeclaredSources.js.map +1 -1
  238. package/dist/src/usecases/list.d.ts.map +1 -1
  239. package/dist/src/usecases/list.js +83 -26
  240. package/dist/src/usecases/list.js.map +1 -1
  241. package/dist/src/usecases/listAttachableDevices.d.ts +13 -0
  242. package/dist/src/usecases/listAttachableDevices.d.ts.map +1 -0
  243. package/dist/src/usecases/listAttachableDevices.js +10 -0
  244. package/dist/src/usecases/listAttachableDevices.js.map +1 -0
  245. package/dist/src/usecases/listSourcePackages.d.ts.map +1 -1
  246. package/dist/src/usecases/listSourcePackages.js +2 -5
  247. package/dist/src/usecases/listSourcePackages.js.map +1 -1
  248. package/dist/src/usecases/purgeExcludedPoints.d.ts.map +1 -1
  249. package/dist/src/usecases/purgeExcludedPoints.js +3 -2
  250. package/dist/src/usecases/purgeExcludedPoints.js.map +1 -1
  251. package/dist/src/usecases/readFiles.d.ts.map +1 -1
  252. package/dist/src/usecases/readFiles.js +15 -0
  253. package/dist/src/usecases/readFiles.js.map +1 -1
  254. package/dist/src/usecases/reconcileSourcePackageRoots.d.ts +1 -1
  255. package/dist/src/usecases/reconcileSourcePackageRoots.d.ts.map +1 -1
  256. package/dist/src/usecases/reconcileSourcePackageRoots.js +2 -1
  257. package/dist/src/usecases/reconcileSourcePackageRoots.js.map +1 -1
  258. package/dist/src/usecases/revokePrincipal.d.ts +7 -5
  259. package/dist/src/usecases/revokePrincipal.d.ts.map +1 -1
  260. package/dist/src/usecases/revokePrincipal.js +29 -7
  261. package/dist/src/usecases/revokePrincipal.js.map +1 -1
  262. package/dist/src/usecases/scheduleSourceSync.d.ts +7 -0
  263. package/dist/src/usecases/scheduleSourceSync.d.ts.map +1 -1
  264. package/dist/src/usecases/scheduleSourceSync.js +6 -5
  265. package/dist/src/usecases/scheduleSourceSync.js.map +1 -1
  266. package/dist/src/usecases/triggerSourceSync.d.ts +46 -0
  267. package/dist/src/usecases/triggerSourceSync.d.ts.map +1 -0
  268. package/dist/src/usecases/triggerSourceSync.js +75 -0
  269. package/dist/src/usecases/triggerSourceSync.js.map +1 -0
  270. package/dist/src/usecases/uploadBinary.d.ts +2 -1
  271. package/dist/src/usecases/uploadBinary.d.ts.map +1 -1
  272. package/dist/src/usecases/uploadBinary.js +2 -2
  273. package/dist/src/usecases/uploadBinary.js.map +1 -1
  274. package/dist/src/usecases/vector/endpointGuard.d.ts +1 -1
  275. package/dist/src/usecases/vector/endpointGuard.js +1 -1
  276. package/dist/src/vector/qdrantClient.d.ts +1 -1
  277. package/dist/src/vector/qdrantClient.js +1 -1
  278. package/dist/src/wiring.d.ts +3 -0
  279. package/dist/src/wiring.d.ts.map +1 -1
  280. package/dist/src/wiring.js +72 -6
  281. package/dist/src/wiring.js.map +1 -1
  282. package/package.json +145 -54
  283. package/skills/connect-business-data/SKILL.md +2 -2
  284. package/skills/create-package/SKILL.md +18 -18
  285. package/skills/create-package/references/config-and-credentials.md +16 -13
  286. package/skills/create-package/references/connectors-and-uri-policies.md +36 -27
  287. package/skills/create-package/references/manifest-and-discovery.md +12 -12
  288. package/skills/create-package/references/runtime-and-build.md +18 -18
  289. package/skills/create-package/references/skills-and-context.md +35 -19
  290. package/skills/create-package/references/surfaces-and-design.md +16 -16
  291. package/skills/create-package/references/team-and-workflows.md +2 -2
  292. package/skills/create-package/references/test-and-ship.md +17 -17
  293. package/skills/create-package/references/tools-and-schemas.md +5 -5
  294. package/skills/create-package/scripts/{check-package.sh → check-package.mjs} +78 -64
  295. package/skills/create-package/scripts/{verify-loaded.sh → verify-loaded.mjs} +156 -162
  296. package/skills/git/SKILL.md +31 -29
  297. package/skills/manage-files/SKILL.md +11 -14
  298. package/skills/manage-sources/SKILL.md +85 -45
  299. package/skills/manage-uri-policy/SKILL.md +9 -6
  300. package/skills/sync/SKILL.md +1 -1
  301. package/dist/src/connectors/LocalDiskConnector.d.ts +0 -171
  302. package/dist/src/connectors/LocalDiskConnector.d.ts.map +0 -1
  303. package/dist/src/connectors/LocalDiskConnector.js +0 -1233
  304. package/dist/src/connectors/LocalDiskConnector.js.map +0 -1
  305. package/skills/manage-files/scripts/copy.sh +0 -7
  306. package/skills/manage-files/scripts/create.sh +0 -7
  307. package/skills/manage-files/scripts/mkdir.sh +0 -7
  308. package/skills/manage-files/scripts/modify.sh +0 -7
  309. package/skills/manage-files/scripts/upload.sh +0 -16
  310. package/skills/manage-files/scripts/write.sh +0 -29
  311. package/skills/manage-sources/scripts/reembed.sh +0 -23
@@ -48,13 +48,13 @@ Effective value cascade, exactly three steps: **platform file override → `envF
48
48
  - **`envFallback` is ignored for `type:"json"`** (validation warning; the administrator sets the env var and nothing changes), and **`type:"json"` rows are excluded from the flat settings list** — the key still reads through the cascade, it is just not on the list.
49
49
  - **`category: "vector"` is filtered out of the flat Platform Settings list** — a dedicated Vector section owns it, so any non-embedding key you file there is invisible in the admin UI.
50
50
  - **Secret-shaped names are warned, not blocked.** A `key` or `envFallback` matching `secret|token|passw|credential|apikey|api_key|private` produces a manifest warning — and the value still renders as plaintext. Declare a credential instead.
51
- - **Do not invent an `envFallback`.** It projects an arbitrary environment variable's VALUE into administrator-visible state — which is exactly why `configSettings[]` is honoured for **first-party** packages only.
51
+ - **Do not invent an `envFallback`.** It projects an arbitrary environment variable's VALUE into administrator-visible state — which is exactly why `configSettings[]` is honoured for **native** packages only.
52
52
 
53
53
  **Declaration is not a grant.** A row registers a schema entry on the feature-gated admin Config surface. It gives your package no write authority over anything.
54
54
 
55
55
  ## Task — read a config value in your code
56
56
 
57
- `ctx.config.get(key)` reads a declared platform setting live. Every first-party key is registered before `init`; an undeclared key throws. Reads below host-assigned first-party trust are denied.
57
+ `ctx.config.get(key)` reads a declared platform setting live. Every native key is registered before `init`; an undeclared key throws. Reads below host-assigned native trust are denied.
58
58
 
59
59
  ```ts
60
60
  import type { PackageInitContext } from '@neuralis/package-system/contracts';
@@ -80,8 +80,8 @@ The shape is exactly `{ id, label, category?, oauth? }` — `id` and `label` req
80
80
 
81
81
  - **`id` must pass `^[A-Za-z_][A-Za-z0-9_.-]*$`, at most 128 chars.** The id becomes an `<id>.enc.json` path segment, so a slash, whitespace or a leading dot is rejected — an id can never escape its scope directory. Duplicate ids in one manifest, a missing `id`/`label`, or a non-object entry all fail validation with a named error.
82
82
  - **Declare only what something RESOLVES.** A catalog row an owner fills in that no code reads is worse than no row — the operator believes the key is configured while the feature stays dark. A drift guard asserts declarations and consumers match **in both directions**, so a dead declaration fails the build. Boot-critical infrastructure read from the environment (a vector-DB key wired before any credential can be decrypted) belongs in `.env`, not here.
83
- - **Reserved namespaces are first-party-only.** The git (`git.<host>.pat`, `git.<host>.oauth.*`), channel, and MCP (`mcp.<server>.*`) id classes have host-owned resolution semantics — a resolver reads them at a member's OWN scope on that member's behalf — so a non-first-party manifest declaring one is a **validation error, not a warning**: unlike a retired key, it fails the manifest and the package does not load. Squatting `git.github.com.pat` would otherwise put a token an owner filled in at global scope into the cascade for every member without their own. Declare ids in your own namespace (`acme.apiKey`).
84
- - **The catalog is dynamic**: the union of package `credentials[]`, every installed skill's `credentials:` frontmatter, and ids already stored by hand. Entries carry only `hasValue`, never the value. There is **no visibility trust gate** — your package's own ids appear at any trust tier, or an owner could never set them. Trust governs ATTRIBUTION instead: a first-party declaration wins the label/category, a later declarer only joins the `declaredBy` list, and the UI renders the trust tier next to a non-first-party declarer.
83
+ - **Reserved namespaces are native-only.** The git (`git.<host>.pat`, `git.<host>.oauth.*`), channel, and MCP (`mcp.<server>.*`) id classes have host-owned resolution semantics — a resolver reads them at a member's OWN scope on that member's behalf — so a non-native manifest declaring one is a **validation error, not a warning**: unlike a retired key, it fails the manifest and the package does not load. Squatting `git.github.com.pat` would otherwise put a token an owner filled in at global scope into the cascade for every member without their own. Declare ids in your own namespace (`acme.apiKey`).
84
+ - **The catalog is dynamic**: the union of package `credentials[]`, every installed skill's `credentials:` frontmatter, and ids already stored by hand. Entries carry only `hasValue`, never the value. There is **no visibility trust gate** — your package's own ids appear at any trust tier, or an owner could never set them. Trust governs ATTRIBUTION instead: a native declaration wins the label/category, a later declarer only joins the `declaredBy` list, and the UI renders the trust tier next to a non-native declarer.
85
85
  - **Declaration is not a grant here either.** A row is catalog metadata; it confers no write authority, and consumption still resolves against the caller's real scope.
86
86
  - **`oauth: true` is a DISPLAY hint only** — it changes admin chrome to Connect/Disconnect. It gives you no OAuth flow, and on an unwired provider the row is a dead button.
87
87
 
@@ -112,19 +112,22 @@ description: Fetch a city forecast via the Weather API.
112
112
  credentials:
113
113
  - WEATHER_API_KEY
114
114
  ---
115
- Run: `bash ${SKILL_DIR}/scripts/forecast.sh "$CITY"`
115
+ Run: `node "${SKILL_DIR}/scripts/forecast.mjs" "$CITY"`
116
116
  ```
117
117
 
118
- ```bash
119
- # skills/forecast/scripts/forecast.sh
120
- set -euo pipefail
121
- : "${WEATHER_API_KEY:?not configured — set it under Credentials}"
122
- curl --fail-with-body -sS -H "Authorization: Bearer $WEATHER_API_KEY" "https://api.weather.example/v1/forecast?q=$1"
118
+ ```js
119
+ // skills/forecast/scripts/forecast.mjs
120
+ const key = process.env.WEATHER_API_KEY;
121
+ if (!key) { process.stderr.write('WEATHER_API_KEY is not configured — set it under Credentials\n'); process.exit(2); }
122
+ const res = await fetch(`https://api.weather.example/v1/forecast?q=${encodeURIComponent(process.argv[2] ?? '')}`, { headers: { Authorization: `Bearer ${key}` } });
123
+ const body = await res.text();
124
+ if (!res.ok) { process.stderr.write(`HTTP ${res.status}\n${body}\n`); process.exit(1); } // keep the error body
125
+ process.stdout.write(body);
123
126
  ```
124
127
 
125
128
  On activation the host resolves each declared id at the caller's real `(userId, projectId, agentId)` scope, within the caller's tier ceiling: own user key always; agent and project keys with `project.credentials.write`; global with `platform.scope` too. A tier above it reads as missing, so a member's run needs the member's own key (`credentials.self`). **The model never sees a resolved value.**
126
129
 
127
- - **`credentials:` must be a list of plain strings.** A scalar or a list of objects is dropped silently by the whitelist frontmatter parser: the skill activates normally, the catalog shows no ids, and your script exits on the `${VAR:?…}` guard with no parse error anywhere.
130
+ - **`credentials:` must be a list of plain strings.** A scalar or a list of objects is dropped silently by the whitelist frontmatter parser: the skill activates normally, the catalog shows no ids, and your script exits on its missing-key guard with no parse error anywhere.
128
131
  - **A missing value is non-fatal.** The id lands on a `missing` list, the rendered body carries a `(missing: <id>)` marker, and no variable is set — which is why scripts should fail with a message pointing the user at the Credentials surface.
129
132
  - **Sanitise exactly once.** Pass `env` and the allow-list straight through to the shell runner. A second defensive sanitise pass has no allow-list and strips exactly the credential keys that were just projected — and the symptom, "the credential is configured but the script says it is missing", sends you debugging the wrong layer.
130
133
  - **The allow-list applies to the OVERRIDES side only.** It never un-strips a same-named variable already present in the host process environment. That is deliberate: a leaked host secret must not piggyback on an allow-listed name.
@@ -153,10 +156,10 @@ Never inline a secret in a manifest or a source config file, and never default-a
153
156
  ## What works on which reach path
154
157
 
155
158
  - **Installed package (in-process):** the only path where both halves work fully. Trust comes from being installed, so keys register at boot, the Config tab shows your package as declarer, `ctx.credentials` is available, and skill `credentials:` projects into script environments.
156
- - **Project `_packages/` drop (sandboxed, untrusted):** `configSettings[]` is **silently dropped** — the only trace is a `config.declaration_skipped` warning, nothing fails at install time, and the first `get()` throws `Config key not registered`. The sandbox bridge exposes no config and no credential host function at all, so declared `credentials[]` still show in the catalog but sandboxed code cannot read a value. Take tunables as tool inputs or from your own data files, and do not plan on secret access. **An admin can elevate a dropped package to `trusted`, never to first-party** — the drop path pins `untrusted` and the elevation channel has no first-party value — so neither array ever comes back on this path. If you need declared config or credential access, ship the package on the installed path instead.
159
+ - **Project `_packages/` drop (sandboxed, untrusted):** `configSettings[]` is **silently dropped** — the only trace is a `config.declaration_skipped` warning, nothing fails at install time, and the first `get()` throws `Config key not registered`. The sandbox bridge exposes no config and no credential host function at all, so declared `credentials[]` still show in the catalog but sandboxed code cannot read a value. Take tunables as tool inputs or from your own data files, and do not plan on secret access. **An admin can elevate a dropped package to `trusted`, never to native** — the drop path pins `untrusted` and the elevation channel has no native value — so neither array ever comes back on this path. If you need declared config or credential access, ship the package on the installed path instead.
157
160
  - **Source markdown package (no install step):** no manifest, so neither array exists on this path. Per-skill `credentials:` frontmatter still works exactly as above — a source-package skill can shell out with a secret in its environment; it just cannot declare a tunable or a catalog row of its own.
158
161
 
159
162
  ## Going deeper
160
163
 
161
164
  - Configuration tiers and the cascade: <https://docs.neuralisapp.com/enterprise/configuration>
162
- - Credential scopes, the dynamic catalog and the encrypted store: <https://docs.neuralisapp.com/enterprise/credentials>
165
+ - Credential scopes, the dynamic catalog and the encrypted store: <https://docs.neuralisapp.com/package-system/credentials>
@@ -28,6 +28,9 @@ The dataset lives only behind the configured source/connector. An arbitrary writ
28
28
  { "id": "acme-mcp", "type": "mcp", "transport": "stdio", "command": "npx",
29
29
  "args": ["-y", "@acme/mcp-server"],
30
30
  "auth": { "type": "env-vars", "envVarName": "ACME_TOKEN" } },
31
+ { "id": "acme-remote", "type": "mcp", "transport": "streamable-http",
32
+ "url": "https://mcp.acme.test/mcp", "name": "Acme", "description": "Orders and invoices",
33
+ "auth": { "type": "oauth2", "scopes": ["read"] } },
31
34
  { "id": "acme-api", "type": "openapi",
32
35
  "openApiSpec": "https://api.acme.test/openapi.json",
33
36
  "openApiOperationFilter": { "include": ["listOrders", "getOrder"] },
@@ -41,18 +44,19 @@ The dataset lives only behind the configured source/connector. An arbitrary writ
41
44
  - `auth.type` is closed: `none | api-key | bearer-token | basic | oauth2 | oauth2-pkce | env-vars`. `env-vars` is the one for a stdio MCP subprocess — decrypted values go into the child process env.
42
45
  - **Secrets are collected through `auth.setupFields[]`** (shape in `references/config-and-credentials.md`). There is no `credentialRef` auth field; writing one collects nothing and the connector starts unauthenticated.
43
46
  - A `type: "source"` entry is handled only by the source pipeline; the outbound lifecycle skips it.
47
+ - **A remote `mcp` row is offered to the user, never auto-connected.** It needs `transport: "streamable-http"` and a `url` (a package fails validation otherwise; `stdio` + `command` is the trusted sidecar form above), and it is never registered as a connector. Every agent whose owner may see the package — its base-access feature, and the package switched on for that agent — gets it under Connections → MCP servers as "Offered by <package>" (and as an `offered` row of `connect {list:{kind:"mcp"}}`); Add, or `connect {add:{kind:"mcp", offer:"<packageId>#<connectorId>"}}`, adopts it as that agent's own MCP server and signs in through the server's OAuth (discovery, dynamic client registration, PKCE). Only `url`, `name`, `description`, `icon` and `auth.scopes` are read; `auth.type` is `oauth2`, `oauth2-pkce` or `none`, and any other type is not offered. No MCP request reaches the server before the user signs in (the sign-in reads its OAuth metadata and registers a client), no Neuralis identity header is ever sent, and an adopted server stays the user's when the package is later switched off.
44
48
 
45
49
  ### Which tenant an outbound connector registers into
46
50
 
47
51
  A `connectors[]` entry is **not** registered once for the whole deployment. It lands in exactly one **partition**, and which one is decided by how the package reached the host — never by anything you can write in the manifest:
48
52
 
49
- - **Installed package, first-party** → the **platform lane**. One registration, visible in every project on the deployment. It is the only lane a package with no owning project may reach.
53
+ - **Installed package, native** → the **platform lane**. One registration, visible in every project on the deployment. It is the only lane a package with no owning project may reach.
50
54
  - **Project `_packages/` drop** → **that project's own partition**. The connector exists for that project and nowhere else; two projects dropping the same package get two independent connectors with independent config, status and health.
51
- - **No resolvable owning project and not first-party → NOTHING is registered.** Fail-closed on purpose: the only partition left would be the platform lane, which every project can see. **This is the first thing to check when a connector "disappeared"** — the package loads clean, `check-package.sh` is happy, the manifest is valid, and `GET /connectors` simply never lists it. The only trace is the server line `Connector registration refused: unresolved owning project`. Nothing in the manifest will fix it; find why the package has no owning project.
55
+ - **No resolvable owning project and not native → NOTHING is registered.** Fail-closed on purpose: the only partition left would be the platform lane, which every project can see. **This is the first thing to check when a connector "disappeared"** — the package loads clean, `check-package.mjs` is happy, the manifest is valid, and `GET /connectors` simply never lists it. The only trace is the server line `Connector registration refused: unresolved owning project`. Nothing in the manifest will fix it; find why the package has no owning project.
52
56
 
53
57
  Reads follow the same partition. `GET /connectors` answers with the caller's own visibility — the platform lane UNION their project — and an id-addressed call resolves own-project first, then platform. An id belonging to **another** project answers the same generic **`404 Not found`** as an unknown id or a malformed one: the three are deliberately indistinguishable, so no caller can enumerate another project's connector ids out of the error text. Do not build a retry, a fallback or a "does it exist elsewhere" probe on that 404 — it will never become a more specific code.
54
58
 
55
- Tools follow it too: a connected remote-MCP connector's tools are merged into the sessions of its own tenant — the platform lane's into every project, a project's into that project alone.
59
+ A remote MCP row never takes this path: it is an offer each agent adopts (above), so its tools reach exactly the agents that added it.
56
60
 
57
61
  **Reading is not writing.** Listing connectors, reading one's health and reading its resolved tools ride the module's `core.observe`. Every MUTATING arm — create, delete, configure, connect, disconnect, enable, disable — additionally requires the **`core.connectors`** feature — granted to `admin` by default, reached by an owner-strength role through its `'*'` wildcard, and held by no one else. A caller who can SEE a connector row and gets `403 Forbidden` on Connect is looking at that gate, not at a broken connector: name the feature and stop. Never retry it, and never reach for another identity.
58
62
 
@@ -93,21 +97,26 @@ None of this touches a `type: "source"` entry. A filesystem kind is registered a
93
97
  1. **Omitting `"additionalProperties": false` is PERMISSIVE, not rejecting.** Arbitrary unvalidated keys land in your factory's `config`; the rejection you assumed was happening never happens.
94
98
  2. **It is a hand-rolled draft-07 SUBSET.** Supported: top-level `type: "object"`, `required`, `additionalProperties: false`, and per-property `type`, `enum`, `const`, `minLength`, `maxLength`, `pattern`, `minimum`, `maximum`, `items`. Everything else — `$ref`, `oneOf`/`anyOf`/`allOf`, `format`, `dependencies`, nested object schemas — is **silently ignored**: no error, no warning, invalid config reaching your factory as a type crash deep inside the connector. Keep the form flat.
95
99
 
96
- 3. **A `root` property must say WHICH PLANE it is on.** The Add-Source form renders every field
97
- from its declared `x-neuralis.ui`, and `root` is where the chrome differs:
98
- `"control": "container-path"` means the path lives in the CONTAINER filesystem, so the form
99
- offers the `/mounts` volume picker, the `neuralis mount add` callout and the Browse button;
100
- `"control": "host-path"` means it lives on a paired device — a machine outside the app (reached
101
- through that device's broker), so it gets none of them — Browse least of all, because `GET /sources/browse` readdirs
102
- in the SERVER process, which is the wrong machine for a host root. The bare legacy
103
- `"control": "path"` still works and is treated as container-plane. Declare `label` and
104
- `placeholder` too: the form uses yours, and a kind that declares neither renders a bare `Path`
105
- with an empty placeholder.
100
+ 3. **A `root` property declares `"control": "container-path"`** (the bare legacy `"path"` still
101
+ works the same): the path lives on the app's own machine — in a Docker install the container,
102
+ where the form suggests the `/mounts` binds and the `neuralis mount add` callout; on a native
103
+ install only the project's own folder, any other folder being attached on the user's paired
104
+ device. `"browse": true` offers the folder picker. Declare `label` and `placeholder` too: the
105
+ form uses yours, and a kind that declares neither renders a bare `Path` with an empty
106
+ placeholder. One kind may serve both planes: a field marked `"transport": true` (the `host`
107
+ kind's `device`) renders BEFORE the root and, while it holds a value, turns the root into a path
108
+ on that device — your `deviceLabel`/`devicePlaceholder` (or a `devicePlaceholders` entry per
109
+ the chosen row's `platform`: `win32` · `darwin` · `linux`), browsed through that device's broker —
110
+ and a schema-level `"not": {"required": ["device", "hostRoot"]}` both refuses the pair and hides
111
+ the excluded field. `"advanced": true` folds a field under "Advanced" and `"help"` is its short
112
+ line; the schema-level `"x-neuralis": {"ui": {"summary": "…"}}` is the kind's one-line form text
113
+ (your `description` stays the agent's).
106
114
  4. **Any other field is a text input, or a `select` filled by YOUR route.** `"control": "select",
107
115
  "optionsRoute": "things?scope=mine"` makes the form read that relative route of your own package and
108
- offer the first array in the JSON answer as rows `{id, name?, status?}` (value = `id`; a
116
+ offer the first array in the JSON answer as rows `{id, name?, platform?, status?}` (value = `id`; a
109
117
  `revoked` row is not offered). An absolute route, a scheme or a `..` segment is refused and the
110
- field falls back to text with the reason. The list is UX only: re-check the value in your connector.
118
+ field falls back to text with the reason. With `"optional": true` the select also offers the
119
+ empty value as a real choice, labelled `emptyLabel`. The list is UX only: re-check the value in your connector.
111
120
 
112
121
  **Never put a secret in `configSchema`.** There is no `credentialFields[]` key that splits schema properties into the credential store, and the factory context carries no `credentials` map — a config property IS plain config, persisted in `sourceConfig.json`. Declare the secret in `neuralis.credentials[]` and read it through the `CredentialResolver` the loader injects into your package's `init(ctx)` — **that** `ctx`, not the factory's — and let the factory close over the resolver. Rules in `references/config-and-credentials.md`.
113
122
 
@@ -132,7 +141,7 @@ export function createNotesConnector(ctx: ConnectorFactoryContext): ConnectorPor
132
141
  }
133
142
  ```
134
143
 
135
- - **Declared capabilities must EXACTLY equal the runtime set**, both directions. Closed vocabulary: `list`, `read`, `stat`, `write`, `delete`, `move`, `mkdir`, `rmdir`, `exec`, `execDetached`, `scanDelta`, `walk`, `count`, `peekCount`, `scanContent`, `subscribeEvents`, `resolveOsUri`; `list` and `read` are mandatory, each of the rest gates one optional method. Symptom: `connector.source.capability-mismatch` with declared-vs-actual sets, thrown at **per-source instantiation** — "installed fine, breaks the first time someone creates a source of this kind." First-party ⇒ boot-fatal; otherwise the kind goes unavailable and callers get `ConnectorCapabilityMissing`.
144
+ - **Declared capabilities must EXACTLY equal the runtime set**, both directions. Closed vocabulary: `list`, `read`, `stat`, `write`, `delete`, `move`, `mkdir`, `rmdir`, `exec`, `execDetached`, `scanDelta`, `walk`, `count`, `peekCount`, `scanContent`, `subscribeEvents`, `resolveOsUri`; `list` and `read` are mandatory, each of the rest gates one optional method. Symptom: `connector.source.capability-mismatch` with declared-vs-actual sets, thrown at **per-source instantiation** — "installed fine, breaks the first time someone creates a source of this kind." Native ⇒ boot-fatal; otherwise the kind goes unavailable and callers get `ConnectorCapabilityMissing`.
136
145
  - **Pin the implemented ABI: `4 satisfies ConnectorPortVersion`.** A package shipped SEPARATELY from the kernel pins the number it was built against — importing the host's runtime constant makes an old binary relabel itself as current on the next platform upgrade, which is the one failure the version check exists to catch. (A connector that ships IN LOCKSTEP with the kernel may import the constant instead; yours does not.) Older exec signatures are rejected without adaptation; version equality alone does not prove external code enforces authorization.
137
146
  - **Exec is authenticated, and you do NOT write the entry check.** `exec({ command, cwd, timeout?, env?, background? }, { session, signal? })` receives the real `SessionContext` and exact source URI. Declaring the `exec` capability REQUIRES declaring `"execRequires": ["<feature.id>", …]` beside it — the feature id(s) that open a shell on your kind. A missing or EMPTY list is a manifest error, because "every id in an empty list is held" is a door that is always open. The platform stamps a frozen copy of those ids on the instance and wraps `exec` so each one is checked before your code runs, and the calling tool then evaluates the SOURCE's own uri-policy `exec` bit on the cwd. So enforce your lifecycle and supported options — not the entry feature; a second copy in your provider would be free to drift from your manifest. Return the existing result fields plus optional bounded `text`. Exec bindings require an absolute factory `projectRoot`. The loader snapshots provider/source/project/scope before factory execution and stamps the checked instance; callers verify `getConnectorBindingProvenance` against current authority. Never reuse one instance across bindings.
138
147
  - **A run that OUTLIVES its call is `execDetached` + the `detached` facet.** Declare the capability (it also requires `exec`) and implement all FIVE verbs — `start`, `status`, `output`, `kill`, `release` — or the loader refuses the binding: a declared capability with a missing verb is a run the platform can start and never stop. `start(request, context)` is a second exec ENTRY, so the platform wraps it with your `execRequires` exactly like `exec`; the other four take the run id you returned and NOTHING else, because a caller who has since lost the entry feature must still be able to read and stop their own run. Ownership is the platform's stored record, never a claim in your arguments: the optional `actor` on `kill` is for your audit row only. Report a kill as an ACKNOWLEDGEMENT and the real end through `status`; answer `null` for a run you no longer know, and keep `output` rows monotonic by `seq` so the platform can prove it copied the whole log before it reports the run finished.
@@ -156,12 +165,12 @@ export function createNotesConnector(ctx: ConnectorFactoryContext): ConnectorPor
156
165
  ]
157
166
  ```
158
167
 
159
- Required: `source` (unique slug), `kind`, `scope` ∈ `project|user|agent`, a non-empty `description`, `seed` ∈ `auto|discoverable`. Optional: `root`, `label`, `sync`, `permissions`, `recognizesPackages`, `visibility.seeRequires`.
168
+ Required: `source` (unique slug), `kind`, `scope` ∈ `project|user|agent`, a non-empty `description`, `seed` ∈ `auto|discoverable`. Optional: `root`, `label`, `sync`, `permissions`, `packageFolders`, `visibility.seeRequires`.
160
169
 
161
- - `seed: "auto"` seeds into every project with no human in the loop → requires **trusted or better**, else `seed="auto" requires trust >= 'trusted'` and the package fails to load. A root naming a platform token (`${appRoot}`) requires **first-party**. Otherwise roots must be project-relative: no leading `/`, no `${…}` token, no `.` / `..` segments.
170
+ - `seed: "auto"` seeds into every project with no human in the loop → requires **trusted or better**, else `seed="auto" requires trust >= 'trusted'` and the package fails to load. A root naming a platform token (`${appRoot}`) requires **native**. Otherwise roots must be project-relative: no leading `/`, no `${…}` token, no `.` / `..` segments.
162
171
  - An untrusted drop gets `seed: "discoverable"` with a project-relative root and nothing more — its declarations never enter the trusted attribution map, so the source appears unattributed, with no package-supplied label or description.
163
172
  - **`visibility.seeRequires` is a UX default, not the gate.** The authoritative see-filter and the add-feature check live server-side. Ship a source believing `seeRequires` hides it and you have shipped an unhidden source; setting it grants nothing either.
164
- - **`recognizesPackages` does NOT switch markdown discovery on**, despite the name. Default unset (⇒ false); allowed `true` only on a `local`-kind source (`recognizesPackages may only be true for local-kind sources.`), and settable only by an owner/admin-effective caller (`Setting recognizesPackages requires an owner/admin-effective role.`). Its real job is the *code-LOAD* axis: the host scans a flagged, enabled, local source's directory for **installable** packages, the way it always scans the project `packages` drop-zone (which needs no flag). Its one effect on markdown is a dedup nuance — under a flagged source the package slug is read as the FIRST path segment, so a subtree duplicating an already-loaded package stops double-injecting. **Symptom when unset: none for markdown.** A plain synced folder still contributes skills, instructions, rules, agents and docs by folder layout alone — recognition criteria are the reach-path table in `SKILL.md` (column (c)) and the discovery rules in `references/manifest-and-discovery.md`. If a source package is invisible, this flag is not the cause; work the diagnosis table below.
173
+ - **`packageFolders` does NOT switch markdown discovery on.** Default unset (⇒ none); `[""]` makes the whole declared `host` source one package folder, `["a/b"]` one folder under its root — the same field the created source stores (`packageFolders may only be set when connection.kind is "host".`), settable only by an owner/admin-effective caller (`Setting packageFolders requires an owner/admin-effective role.`). Its real job is the *code-LOAD* axis: the host scans each package folder of an enabled folder it can open for **installable** packages, the way it always scans the project `packages` drop-zone (which needs no flag); a package folder on a paired device loads over the device link while the device is online, and while it is offline the package stays listed as not available. A loaded package's source twin is not injected twice — the dedup is by exact origin. **Symptom when unset: none for markdown.** A plain synced folder still contributes skills, instructions, rules, agents and docs by folder layout alone — recognition criteria are the reach-path table in `SKILL.md` (column (c)) and the discovery rules in `references/manifest-and-discovery.md`. If a source package is invisible, this flag is not the cause; work the diagnosis table below.
165
174
 
166
175
  ## Set path permissions (`uriPolicies`)
167
176
 
@@ -175,24 +184,24 @@ An **object keyed by URI pattern** — not an array, and no `access` enum. The p
175
184
  { "pattern": "data://<self>/private/**", "permissions": { "read": false } },
176
185
  { "pattern": "data://<self>/private/$self/**", "permissions": { "read": true }, "override": true },
177
186
  { "pattern": "data://<self>/private/**", "permissions": { "read": true },
178
- "roles": ["owner", "admin"], "override": true } ] } }
187
+ "priority": 2, "override": true } ] } }
179
188
  ```
180
189
 
181
190
  1. **Every key must start with `packages://<self>/` or `data://<self>/`** (or equal that prefix minus the trailing slash). A foreign namespace — another package's id, `app://`, `neuralis://`, a bare OS path — **rejects the WHOLE manifest**: no partial registration, the package does not load at all. Inner `paths[].pattern` entries containing `://` are checked identically; relative inner patterns pass.
182
191
  2. **`<self>` is a load-time placeholder** substituted with your package slug at boot. Never hardcode your own id in a key.
183
192
  3. **`$self`, `$creator`, `$source` are a DIFFERENT family** — runtime tokens substituted inside `paths[].pattern` with the caller's agent id, the source creator's user id, and the source slug. Confusing them is the classic failure: `<self>` in a path position becomes a literal, never-matching segment (the rule appears to do nothing); `$self` in a *key* is not substituted at load, fails the prefix check, and rejects the manifest. This pair is also how self-maintaining identity works: `data://<self>/$self/**` writable while `data://<self>/*/**` is not means an agent rewrites its own identity files and no one else's — a uri-policy grant, not a special case.
184
193
  4. **Every value needs `default: { read, write, exec }`, all three booleans present**, or the manifest fails with `missing required default { read, write, exec } booleans`. **Every `paths[]` row needs its capabilities nested under a `permissions` object**, not at the row's top level — the top-level spelling used to normalize to `permissions: {}` and the rule became SILENTLY INERT (it read like a real deny in the manifest and in every persisted copy of it). It is now a load-time rejection, so an inert row cannot ship.
185
- 5. **Patterns match the FULL URI** (leading slash stripped): `?` = one non-separator char, `*` = a run of non-`/`, `**` = anything including `/`, a leading `**/` may match zero segments, anchored both ends. Two symptoms follow: a source-relative pattern (`memory/**` instead of `data://<self>/memory/**`) **silently never matches**, so the intended deny is absent and access falls back to `default`; and `data://<self>/**` does **not** cover the bare root `data://<self>`, so directory-level denies leak on the directory node itself. Spelling is NOT one of the symptoms: slash runs, `.` and `..` are canonicalized on the pattern AND on the URI under test before matching (see the floor paragraph below), so you cannot miss a rule by writing it non-canonically and no caller reaches a file by rewriting its URI.
194
+ 5. **Patterns match the FULL URI** (leading slash stripped): `?` = one non-separator char, `*` = a run of non-`/`, `**` = anything including `/`, a leading `**/` may match zero segments, anchored both ends. Two symptoms follow: a source-relative pattern (`memory/**` instead of `data://<self>/memory/**`) **silently never matches**, so the intended deny is absent and access falls back to `default`; and `data://<self>/**` does **not** cover the bare root `data://<self>`, so directory-level denies leak on the directory node itself. Spelling is NOT one of the symptoms: slash runs, `.` and `..` are canonicalized on the pattern AND on the URI under test before matching (see the floor paragraph below), so you cannot miss a rule by writing it non-canonically and no caller reaches a file by rewriting its URI. Letter case is handled too: a rule's `false` bits match every case of its pattern (`CONVERSATIONS/` hits a `conversations/**` deny on every OS), its `true` bits only the exact spelling — write patterns in the case your package creates the files.
186
195
 
187
- **Evaluation order:** `default` → `byRole` → `byUser` → `byAgent` → `paths`. Path rules are **deny-wins**: a `read: true` rule applies only when the result is not already `false`. To re-open something a broader rule denied, set `override: true` — otherwise the carve-out looks correct in the manifest and the caller still gets 403. Rules narrow with `roles[]` and/or `agents[]`.
196
+ **Evaluation order:** `default` → `byRole` → `byUser` → `byAgent` → `paths`. Path rules are **deny-wins**: a `read: true` rule applies only when the result is not already `false`. To re-open something a broader rule denied, set `override: true` — otherwise the carve-out looks correct in the manifest and the caller still gets 403. Rules narrow with `priority`, `users[]` and/or `agents[]`; every condition present on one rule must hold, so "A or B" is two rules.
188
197
 
189
- **`byRole` is keyed by name but resolved by STRENGTH; `roles[]` is still a literal name match.** Do not treat them as one mechanism. A `byRole` entry applies to the caller whose role NAME matches, else to the caller at that entry's exact declared `priority` (so a custom role at the owner anchor gets the `owner` entry), else — for every entry belonging to a role at least as strong as the caller — its explicit `false` bits only, never a grant. A role weaker than `viewer` therefore INHERITS viewer's denials instead of escaping them, and an `owner` never inherits a viewer restriction. A `paths[].roles[]` array has none of that: it matches the role name exactly, so a rule scoped to `["owner","admin"]` does not reach a custom owner-strength role until that role's name is added to the array. If your policy needs a custom role to reach a carve-out, name it in `roles[]`.
198
+ **`byRole` is keyed by name but resolved by STRENGTH; a path rule names its subject by the caller's role priority with `priority`.** Do not treat them as one mechanism. A `byRole` entry applies to the caller whose role NAME matches, else to the caller at that entry's exact declared `priority` (so a custom role at the owner anchor gets the `owner` entry), else — for every entry belonging to a role at least as strong as the caller — its explicit `false` bits only, never a grant. A role weaker than `viewer` therefore INHERITS viewer's denials instead of escaping them, and an `owner` never inherits a viewer restriction. On a `paths[]` rule, `"priority": 2` matches every caller whose role priority is n or stronger (`<= 2`; lower = stronger: owner 1, admin 2, manager 10, member 20, viewer 30), a custom role at that strength included — it is the caller's role strength, not the rule's order. It fails closed: a value that is not an integer from 1 to 99, or a caller without a priority, never matches, and the manifest is rejected. `priority` is grant-only (`override: true`, every declared bit `true`), never on a deny or `floor: true` row. `paths[].roles[]` is rejected in a manifest (`roles is not allowed in a package manifest`): it matches a role NAME exactly, and only a project's own stored rules may use it.
190
199
 
191
200
  **Uncovered paths deny by default.** A filesystem-touching package with no policy baseline is not "open" — it is denied everywhere and its writes fail quietly. Missing path policy is a bug to flag, never a passing state.
192
201
 
193
202
  **`exec` is the dimension the sandboxed shell reads.** Keep it `false` on a data zone. The other direction's symptom: `execute action=shell` denies on the cwd with `capability: 'exec'` and `Source "<slug>" denies shell execution (exec) to this caller`, though reads and writes there succeed.
194
203
 
195
- **Your baseline's `default` applies to YOUR PATTERN, not to the source.** The key you declare (`data://<self>/**`) scopes the whole entry: at seeding, that `default` block becomes a path RULE on that pattern, and the source keeps its own source-level default. This is why a `default: { exec: false }` in your manifest does not — and must not — turn off `exec` for the entire shared `data` source that every other package also declares against. One consequence worth knowing: because the shell and terminal read SOURCE-level policy for `exec`, a manifest `exec:false` is enforced on the brain-core git remote-op gate but does not restrict code execution under your subtree. Use a `floor: true` row for anything that must actually be unreachable.
204
+ **Your baseline's `default` applies to YOUR PATTERN, not to the source.** The key you declare (`data://<self>/**`) scopes the whole entry: at seeding, that `default` block becomes a path RULE on that pattern, and the source keeps its own source-level default. This is why a `default: { exec: false }` in your manifest does not — and must not — turn off `exec` for the entire shared `data` source that every other package also declares against. One consequence worth knowing: the shell and terminal judge `exec` on the working directory, so your manifest `exec:false` stops a shell from STARTING anywhere under your subtree (and binds the brain-core git remote-op gate) — but a command started elsewhere can still run a script it can read from there. Use a `floor: true` row for anything that must actually be unreachable.
196
205
 
197
206
  **Two limits on where your baselines land — and the one exception that is not limited.** Manifest `uriPolicies` baselines are seeded **only at first source attach, and only for source slugs literally named `data` or `packages`** — declare them expecting them to apply to a custom slug (`repo`, `machine`) and nothing happens; the persisted config is used verbatim. And **a new non-floor pattern in an updated package version does not reach existing installs**: only brand-new sources get it, pre-existing projects keep the old permissions indefinitely, and reaching them takes a version-stamped baseline repair pass. Plan for that whenever you tighten a shipped policy.
198
207
 
@@ -211,7 +220,7 @@ Denial surfaces as `uri_policy_denied` (HTTP 403, structured body with `uri` and
211
220
  | Refused, source slug unknown here | `source_not_configured` (`nextStep: check_configured_sources`, HTTP 404) | no persisted SourceConfig for that slug in this project — or one scoped to someone else, which answers identically. NB the uri gate runs FIRST and denies an unconfigured source as `uri_policy_denied`, so this code surfaces on the routes with no pre-guard (`list`) and for scheme-less uris — a scheme-bearing guess usually reads as the 403 row above | read the reply's `availableSources` (the caller's own visible set) and address one of those; retrying the same slug never helps |
212
221
  | Kind absent from the picker | *(log only)* | manifest entry incomplete, factory unresolvable, or trust rejected | read the server log line |
213
222
  | A mutating connector call refused while listing works | `403 Forbidden` on a `/connectors` write arm | the caller holds `core.observe` but not `core.connectors` | an admin performs it, or the feature is granted — never retry |
214
- | An outbound connector is listed nowhere, package `loaded` | *(log only:* `Connector registration refused: unresolved owning project`*)* | the package is not first-party and has no owning project, so it was registered into NO partition | resolve the owning project; the manifest is not the problem |
223
+ | An outbound connector is listed nowhere, package `loaded` | *(log only:* `Connector registration refused: unresolved owning project`*)* | the package is not native and has no owning project, so it was registered into NO partition | resolve the owning project; the manifest is not the problem |
215
224
  | Readable but invisible to semantic search | *(none)* | sync-excluded path — on disk, never indexed | do not conclude the file is missing |
216
225
 
217
226
  **Policy and enabled are orthogonal; the enabled check runs right after visibility** (a source you cannot see answers as absent, before any enabled or policy answer). Policy answers *which URIs you may touch*; the enable toggle (`true | false | 'auto'`, where `'auto'` derives from the connector runtime probe) answers *whether the source is live at all*. Conflate them and users flip the wrong switch.
@@ -220,7 +229,7 @@ Denial surfaces as `uri_policy_denied` (HTTP 403, structured body with `uri` and
220
229
 
221
230
  Live and enforced before any handler runs, where a **deny is absolute**. Three kinds compile:
222
231
 
223
- - **`safety`** — `config.blockedTools: string[]` is a tool blocklist and `config.timeoutMs` **plus** `config.target` is a per-tool timeout (`target` = a tool name, `*`, or a `fs_*` prefix; smallest matching timeout wins). Either half alone compiles to nothing. Both are **global** — a blocked name is denied for every caller and every package, not only yours — and that is exactly why they are **first-party-only**: from an installed or dropped package both are dropped at load, with a warning naming the kind. Do not reach for `safety` to restrict your own tools; `permission` does that at every trust level.
232
+ - **`safety`** — `config.blockedTools: string[]` is a tool blocklist and `config.timeoutMs` **plus** `config.target` is a per-tool timeout (`target` = a tool name, `*`, or a `fs_*` prefix; smallest matching timeout wins). Either half alone compiles to nothing. Both are **global** — a blocked name is denied for every caller and every package, not only yours — and that is exactly why they are **native-only**: from an installed or dropped package both are dropped at load, with a warning naming the kind. Do not reach for `safety` to restrict your own tools; `permission` does that at every trust level.
224
233
  - **`permission`** — `config.allowedTools: string[]`, an allowlist scoped to the declaring package. It also filters the visible tool catalog, so a denied tool is hidden, not merely refused.
225
234
  - **`trust`** — `config.enforceInputValidation: boolean`.
226
235
 
@@ -230,4 +239,4 @@ Two rules you never write are synthesized for every package: a **trust-tier ceil
230
239
 
231
240
  ---
232
241
 
233
- Public reference: [connectors](https://docs.neuralisapp.com/package-system/connectors) · [URI policies](https://docs.neuralisapp.com/package-system/uri-policies) · [sources and connectors in the brain](https://docs.neuralisapp.com/brain-core/sources-and-connectors)
242
+ Public reference: [connectors](https://docs.neuralisapp.com/package-system/connectors) · [URI policies](https://docs.neuralisapp.com/package-system/uri-policies) · [sources and connectors in the brain](https://docs.neuralisapp.com/brain-core/connectors)
@@ -91,7 +91,7 @@ Four skill layouts are recognised, not one — `skills/<name>/SKILL.md`, `<root-
91
91
 
92
92
  ## Trust and runtime
93
93
 
94
- **Trust is assigned by where the package came from, never declared.** `access.trust` is `first-party | trusted | untrusted`. A package installed as a dependency of the host is made `first-party` by the host after its manifest is read; a project-dropped package is pinned `untrusted` on its seed *before* the manifest merge, and the seed wins. Wherever the host assigns nothing, an **absent value means `untrusted`** — so `"access": {"trust": "first-party"}` in a package the host did not install as a dependency changes nothing.
94
+ **Trust is assigned by where the package came from, never declared.** `access.trust` is `native | trusted | untrusted`. A package installed as a dependency of the host is made `native` by the host after its manifest is read; a project-dropped package is pinned `untrusted` on its seed *before* the manifest merge, and the seed wins. Wherever the host assigns nothing, an **absent value means `untrusted`** — so `"access": {"trust": "native"}` in a package the host did not install as a dependency changes nothing.
95
95
 
96
96
  Untrusted implies, all at once: rules physically moved into the `docs` array (so they never inject into the system prompt), `activation.target: prompt-context` rewritten to `workspace`, `overwrite` forced false, `skillScripts` restricted to `"none"`, no `permissions.exec`, no `net: "full"`, no `fs: "full"`, no `seed: "auto"` sources.
97
97
 
@@ -101,15 +101,15 @@ Untrusted implies, all at once: rules physically moved into the `docs` array (so
101
101
 
102
102
  The three differ on two axes — LANGUAGE and CONFINEMENT — and only one of them gives a dropped package the second:
103
103
 
104
- - **`node`** — in-process, **first-party only**.
105
- - **`wasm`** — JS/TS compiled into an Extism sandbox. **The only code runtime available below first-party**, and what a dropped package declares to run code.
106
- - **`mcp`** — any language, as an stdio subprocess. Also **first-party only**: the child runs as the host user, with the host's filesystem and network reach, from the `entry.command` and `entry.args` your manifest names. It is a LANGUAGE escape hatch, never a confinement one. Pair it with `entry.command` or you get a warning.
104
+ - **`node`** — in-process, **native only**.
105
+ - **`wasm`** — JS/TS compiled into an Extism sandbox. **The only code runtime available below native**, and what a dropped package declares to run code.
106
+ - **`mcp`** — any language, as an stdio subprocess. Also **native only**: the child runs as the host user, with the host's filesystem and network reach, from the `entry.command` and `entry.args` your manifest names. It is a LANGUAGE escape hatch, never a confinement one. Pair it with `entry.command` or you get a warning.
107
107
 
108
- **Neither `node` nor `mcp` is rejected below first-party — both are DEMOTED.** The package warns and loads `partial`, reason `untrusted-node` or `untrusted-mcp`: declarative contributions register normally, no subprocess is spawned, and every tool and route handler returns a `code-runtime-unavailable` error. So if you are writing a project drop and you need to run code, the answer is `wasm` — declaring `mcp` to get a non-JS language will load the package and silently run none of it.
108
+ **Neither `node` nor `mcp` is rejected below native — both are DEMOTED.** The package warns and loads `partial`, reason `untrusted-node` or `untrusted-mcp`: declarative contributions register normally, no subprocess is spawned, and every tool and route handler returns a `code-runtime-unavailable` error. So if you are writing a project drop and you need to run code, the answer is `wasm` — declaring `mcp` to get a non-JS language will load the package and silently run none of it.
109
109
 
110
- **If you need an existing MCP server, do not reach for this runtime at all — but check WHERE that server runs before you plan around a connector either.** Declare an `{ "type": "mcp" }` **connector** (`connectors[]` — a different field and a different discriminator) when the server is reachable **over HTTP**: give it a `url`, and once connected it is dialed into the agent's MCP session with zero code and zero schema of yours. **A local stdio server (`command`) is not dialed in-host at any tier** — the transport factory refuses stdio and the session factory skips the assignment with a warning, so only `url` servers are reached. Two consequences worth stating plainly: a `"type": "mcp"` connector that omits `transport` **defaults to `stdio`**, i.e. to the refused one, so name `transport` explicitly for an HTTP server; and a **local** non-JS server has no working route below first-party today, by this path or by `runtime.type: "mcp"`. The intended shape for that case is a managed sidecar, which does not exist yet.
110
+ **If you need an existing MCP server, do not reach for this runtime at all — but check WHERE that server runs before you plan around a connector either.** Declare an `{ "type": "mcp" }` **connector** (`connectors[]` — a different field and a different discriminator) when the server is reachable **over HTTP**: give it a `url`, and once connected it is dialed into the agent's MCP session with zero code and zero schema of yours. **A local stdio server (`command`) is not dialed in-host at any tier** — the transport factory refuses stdio and the session factory skips the assignment with a warning, so only `url` servers are reached. Two consequences worth stating plainly: a `"type": "mcp"` connector that omits `transport` **defaults to `stdio`**, i.e. to the refused one, so name `transport` explicitly for an HTTP server; and a **local** non-JS server has no working route below native today, by this path or by `runtime.type: "mcp"`. The intended shape for that case is a managed sidecar, which does not exist yet.
111
111
 
112
- `runtime.binding: "embedded"` is the one true hard reject — a non-first-party package declaring it is marked `error` with `embedded binding requires first-party trust`; nothing registers.
112
+ `runtime.binding: "embedded"` is the one true hard reject — a non-native package declaring it is marked `error` with `embedded binding requires native trust`; nothing registers.
113
113
 
114
114
  **`runtime.skillScripts`** is `host | wasm-only | none`. An untrusted manifest declaring anything other than `"none"` fails validation (omitting it is also fine). Beyond that declaration-time check the value is **advertisement**: rendered as a tag in the package overview, enforcing nothing at runtime — do not plan around it blocking a spawn. Separately, `${SKILL_DIR}` resolves to a sentinel string only when a skill has **no host-side directory at all** (a source-contributed skill whose connector gives no container path); installed packages and project drops both resolve a real directory.
115
115
 
@@ -150,7 +150,7 @@ The three differ on two axes — LANGUAGE and CONFINEMENT — and only one of th
150
150
 
151
151
  **`meta.families`** groups tools for UI and feature gating. **`meta.distribution`** has exactly four keys — `installRoot`, `gitRepository`, `gitRef`, `npm`; no `homepage`, `repository` or `bugs` key exists there.
152
152
 
153
- **`source.kind`** records provenance — thirteen values: `neuralis`, `local-dir`, `mounted-dir`, `git`, `npm`, `archive`, `mcp-server`, plus the imported-ecosystem kinds `claude-skill`, `cursor-skill`, `gemini-extension`, `openclaw-plugin`, `openai-app`, `m365-agent`. Two rules bite: `git` **requires** `meta.distribution.gitRepository` or validation fails; `neuralis` is the marker the loader reads to treat a package as a host-owned builtin and select full in-process discovery — a project drop must never claim it.
153
+ **`source.kind`** records provenance — thirteen values: `neuralis`, `local-dir`, `mounted-dir`, `git`, `npm`, `archive`, `mcp-server`, plus the imported-ecosystem kinds `claude-skill`, `cursor-skill`, `gemini-extension`, `openclaw-plugin`, `openai-app`, `m365-agent`. Two rules bite: `git` **requires** `meta.distribution.gitRepository` or validation fails; `neuralis` is never authored: the host stamps it — with `native` trust — on every package of its install slots (its own dependencies and the packages installed while it runs), and only that stamped pair lets a package's code be imported; a manifest that declares it is ignored (the validator warns), and a project drop can never reach it.
154
154
 
155
155
  **Not in this contract — do not invent them:** no `category` enum, no curated keyword vocabulary beyond free-form `tags`, no `engines`/compatibility field, no manifest-level deprecation or yank key (`version` is metadata, not an update trigger — pulling a published version is a registry-side operation over the signed revocation feed, never a field you declare here).
156
156
 
@@ -169,13 +169,13 @@ Each of `tools`, `prompts`, `resources` takes `none | selected | all`; `selected
169
169
 
170
170
  ### A tool name is global: a collision is SHADOWED, not renamed
171
171
 
172
- A tool name is one key for the whole deployment, so two packages — or a package and one of the platform's own tools, or an MCP server — can claim the same one. **Nothing is renamed.** Each agent answers the name with ONE claimant, chosen by rule, never by load order: the **first-party** claimant wins if there is one (the platform's own tools included), otherwise the lexically-smallest `packageId`, and any package before an MCP server. A claimant the caller cannot use — its package switched off for that agent, out of scope, or its tool feature-gated for the caller — drops out BEFORE the rule, so it never takes the name from anyone. The losing package keeps every one of its OTHER tools.
172
+ A tool name is one key for the whole deployment, so two packages — or a package and one of the platform's own tools, or an MCP server — can claim the same one. **Nothing is renamed.** Each agent answers the name with ONE claimant, chosen by rule, never by load order: the **native** claimant wins if there is one (the platform's own tools included), otherwise the lexically-smallest `packageId`, and any package before an MCP server. A claimant the caller cannot use — its package switched off for that agent, out of scope, or its tool feature-gated for the caller — drops out BEFORE the rule, so it never takes the name from anyone. The losing package keeps every one of its OTHER tools.
173
173
 
174
174
  > **Symptom:** a call to your tool answers from another package. The package is healthy; the NAME is contested. An owner can pick your package for that name per agent in Tool Access, but by default the rule decides — rename your tool so nobody has to choose.
175
175
 
176
176
  Where to look: the boot log carries a `Namespace collisions detected` line naming the owner and the shadowed package ids, and `GET tools/catalog` returns a `contestedTools` row (`{name, owner, claimants, preferred?}`) for each name contested for that agent.
177
177
 
178
- So **pick a name nobody else will pick**. Prefix by domain, not by convenience — `acme_release_status`, not `status`. The rule is the ONLY protection, and it applies to every install class alike: the platform's own packages, an admin's `neuralis pkg add`'ed dependency and a project `_packages/` drop all fight for the same key. Trust decides who WINS the fight, never whether there is one — a host dependency is stamped `first-party` by the host regardless of its scope, so `@acme/*` next to `@neuralis/*` is a lexical tie-break, not a rescue. Nothing anywhere refuses to start over a collision: a taken name shadows, it never takes the deployment down.
178
+ So **pick a name nobody else will pick**. Prefix by domain, not by convenience — `acme_release_status`, not `status`. The rule is the ONLY protection, and it applies to every install class alike: the platform's own packages, an admin's `neuralis pkg add`'ed dependency and a project `_packages/` drop all fight for the same key. Trust decides who WINS the fight, never whether there is one — a host dependency is stamped `native` by the host regardless of its scope, so `@acme/*` next to `@neuralis/*` is a lexical tie-break, not a rescue. Nothing anywhere refuses to start over a collision: a taken name shadows, it never takes the deployment down.
179
179
 
180
180
  ## `dataLayout` — say where you keep your files
181
181
 
@@ -187,9 +187,9 @@ If your package writes under its own `data://` zone, declare the first-level dir
187
187
 
188
188
  `"machineWritten": [{ "path": "*/stats/*.json", "class": "metrics" }]` (inside `dataLayout`) names the files your package writes on its own — never rendered into the prompt. `path` is a glob relative to your zone (`*` one segment, `**` any depth; a leading `/`, an empty or `..` segment, or any character outside `[A-Za-z0-9._*-]` rejects the manifest); `class` is `metrics` (numbers only), `log` or `runtime-text` (runtime state a person may search for). A NEW project's `data` source leaves `metrics` files out of its index from the start; existing projects keep their settings. Declarations from untrusted packages are ignored.
189
189
 
190
- **The rest of the block is owned elsewhere:** `sources[]` / `connectors[]` / `uriPolicies` → `connectors-and-uri-policies.md` (a `uriPolicies` pattern may name only `packages://<self>/…` and `data://<self>/…`; a foreign namespace rejects the whole manifest — and a `connectors[]` entry registers into exactly ONE project partition, or into none at all when the package is neither first-party nor owned by a resolvable project), `credentials[]` / `configSettings[]` → `config-and-credentials.md`, `app.surfaces[]` → `surfaces-and-design.md`, `team[]` / workflows → `team-and-workflows.md` (note that `team[]` is VALIDATED fail-closed at load: a bad slug or a template path that leaves the package rejects the whole manifest).
190
+ **The rest of the block is owned elsewhere:** `sources[]` / `connectors[]` / `uriPolicies` → `connectors-and-uri-policies.md` (a `uriPolicies` pattern may name only `packages://<self>/…` and `data://<self>/…`; a foreign namespace rejects the whole manifest — and a `connectors[]` entry registers into exactly ONE project partition, or into none at all when the package is neither native nor owned by a resolvable project), `credentials[]` / `configSettings[]` → `config-and-credentials.md`, `app.surfaces[]` → `surfaces-and-design.md`, `team[]` / workflows → `team-and-workflows.md` (note that `team[]` is VALIDATED fail-closed at load: a bad slug or a template path that leaves the package rejects the whole manifest).
191
191
 
192
- ## `events[]` — notifications from your package (first-party only)
192
+ ## `events[]` — notifications from your package (native only)
193
193
 
194
194
  A package the host installs as a dependency can declare the moments a person should hear about — a run finished, a sync failed. The platform delivers them: a badge on your dock entry, a row in the user's inbox, per-person mute and follow. You build none of that UI. Any other install class has the field dropped at load with a warning.
195
195
 
@@ -59,16 +59,16 @@ export function api(state: NotesState) {
59
59
 
60
60
  - **The lifecycle FILE is optional; `init` inside it is not.** A package with no routes and no tool handlers (markdown, schemas, connectors) ships no `src/lifecycle.ts` at all. A lifecycle module without `init` is discarded with a warning and `start`/`drain`/`stop`/`api`/`onBootstrapComplete`/`provisionProject`/`deprovisionProject`/`onPrincipalRevoked`/`onCredentialChanged`/`health` are never wired.
61
61
  - **`init` returning nothing** leaves the dispatch state `undefined` — symptom: every request returns 503 `Package not initialized — lifecycle failed to load`, though the route files loaded and nothing logged an error.
62
- - **Peer lookups belong in `onBootstrapComplete(state, ctx)`** — it fires once after every package's `init` and `start` have resolved. Elsewhere load order is not guaranteed: `getPackageApi()` returns `undefined` intermittently, fine on one boot, silently degraded on the next. From a non-first-party package `undefined` may also be a trust **denial** (audited `denied:untrusted` / `denied:trust-mismatch`), not a missing package.
62
+ - **Peer lookups belong in `onBootstrapComplete(state, ctx)`** — it fires once after every package's `init` and `start` have resolved. Elsewhere load order is not guaranteed: `getPackageApi()` returns `undefined` intermittently, fine on one boot, silently degraded on the next. From a non-native package `undefined` may also be a trust **denial** (audited `denied:untrusted` / `denied:trust-mismatch`), not a missing package.
63
63
  - **`onPrincipalRevoked(state, event)` closes; it never decides.** The host calls it when a user is disabled, deleted or has their sessions reset (no `event.projectId`), or loses one project (`membership_removed` / `project_archived`, with `projectId`). Stop what you hold open for `event.userId` — sockets, running jobs, pollers — through your existing stop, and return promptly: the call is bounded by a timeout, and the user is already refused everywhere else, so never re-check access here. Only a host-installed package's lifecycle runs at all; a `_packages/` drop never receives it.
64
64
 
65
- **`init` receives** `selfId`, `selfTrust`, `data { dataDir, globalDir?, fileStore<T>, jsonlAppender<T>, dataPath, logger }`, `host { id }`, `packages { isLoaded, getPackageApi<T> }`, `credentials?`, `sessions? { createProjectFactory }`, `config { get }`, lazy `platform` facts, first-party `hostPorts`, `logger`. Platform facts throw before publication and are read where used; bootstrap completion receives the same `platform`, rather than copied aggregates. `dataDir` is **per project**: `<projectsRoot>/<projectId>/data/<zone>/`, mode `0700`, with `logs/`. First-party packages (host-installed, `@neuralis/*` and the deploy's own deps) additionally receive `data.globalDir` — a project-independent platform home under the app zone, where their process-global state and their `logs/` live; a project-installed package like yours has NO `globalDir` (its state is project data by nature), and `ctx.data.globalDir` is `undefined` for it.
65
+ **`init` receives** `selfId`, `selfTrust`, `data { dataDir, globalDir?, fileStore<T>, jsonlAppender<T>, dataPath, logger }`, `host { id }`, `packages { isLoaded, getPackageApi<T> }`, `credentials?`, `sessions? { createProjectFactory }`, `config { get }`, lazy `platform` facts, native `hostPorts`, `logger`. Platform facts throw before publication and are read where used; bootstrap completion receives the same `platform`, rather than copied aggregates. `dataDir` is **per project**: `<projectsRoot>/<projectId>/data/<zone>/`, mode `0700`, with `logs/`. Native packages (host-installed, `@neuralis/*` and the deploy's own deps) additionally receive `data.globalDir` — a project-independent platform home under the app zone, where their process-global state and their `logs/` live; a project-installed package like yours has NO `globalDir` (its state is project data by nature), and `ctx.data.globalDir` is `undefined` for it.
66
66
 
67
67
  **Write through `dataPath`, and hold ONE appender.** `jsonlAppender()` takes a RESOLVED path, not a string — `ctx.data.dataPath('events.jsonl')` produces one, and it refuses anything that would leave your own data dir (an absolute path, or a relative one that climbs out), so you cannot write into a sibling package's data by accident. The APPENDER creates any missing intermediate directories when it writes; `dataPath` itself only resolves, so if you hand its result to your own `fs` call the directories are yours to create. Keep ONE appender for the life of the package: its write ordering is per-instance, so a fresh one per call gives up the guarantee that entries written together stay together. (The WASM guest plane's `ctx.data.*` also takes paths relative to the package data dir, but REPORTS failure as `{ ok: false, error }` instead of throwing — its answers cross into guest memory and carry a fixed code, never a filesystem path. Do not merge the two contracts in your head.)
68
68
 
69
- **The logger is levellable and append-only.** `ctx.data.logger` is a `PackageLogger` already pointed at `<dataDir>/logs/` (for a first-party package: `<globalDir>/logs/`); it resolves its level at every CALL — your package's entry in the operator's `packageLogLevelOverrides` (keyed on the `pkg` field of your log lines: your zone path, `runtime/packages/<dir>` for a drop, the bare name for `@neuralis/*`) → the global `packageLogLevel` → `debug`. Both are platform keys; your manifest declares nothing for them. Never cache `logger.level` in a constructor: the level is live, so a captured value stays pinned after an administrator edit. Every line is appended (O(1)), so the file is OLDEST-first: read "the newest N" with `JsonlAppender.readNewest(path, n)` (`@neuralis/package-system/data`), which takes the tail and then the rotated `.1`, `.2` … generations — never the first lines.
69
+ **The logger is levellable and append-only.** `ctx.data.logger` is a `PackageLogger` already pointed at `<dataDir>/logs/` (for a native package: `<globalDir>/logs/`); it resolves its level at every CALL — your package's entry in the operator's `packageLogLevelOverrides` (keyed on the `pkg` field of your log lines: your zone path, `runtime/packages/<dir>` for a drop, the bare name for `@neuralis/*`) → the global `packageLogLevel` → `debug`. Both are platform keys; your manifest declares nothing for them. Never cache `logger.level` in a constructor: the level is live, so a captured value stays pinned after an administrator edit. Every line is appended (O(1)), so the file is OLDEST-first: read "the newest N" with `JsonlAppender.readNewest(path, n)` (`@neuralis/package-system/data`), which takes the tail and then the rotated `.1`, `.2` … generations — never the first lines.
70
70
 
71
- `onCredentialChanged(state, event)` receives `{credentialId, scope, change}` after a write or delete; re-resolve a changed secret through `ctx.credentials`, never through the event. `drain(state)` finishes or checkpoints work before `stop`, and `health(state)` returns structured checks with closed status/reason values. Runtime fan-outs are bounded. A first-party package exposes its declared `provides` implementations on the state returned by standalone `init(ctx)`. The discovered adapter retains `state.services` as `PackageRuntimeApi.services`, separately from the public `api(state)`; the host resolves them by contract id.
71
+ `onCredentialChanged(state, event)` receives `{credentialId, scope, change}` after a write or delete; re-resolve a changed secret through `ctx.credentials`, never through the event. `drain(state)` finishes or checkpoints work before `stop`, and `health(state)` returns structured checks with closed status/reason values. Runtime fan-outs are bounded. A native package exposes its declared `provides` implementations on the state returned by standalone `init(ctx)`. The discovered adapter retains `state.services` as `PackageRuntimeApi.services`, separately from the public `api(state)`; the host resolves them by contract id.
72
72
 
73
73
  Use those contracts instead of module globals — `createProjectFactory` for per-project services, `ctx.credentials.*` for secrets. Cross-package calls carry the caller (`getPackageApi<T>(id, { userId, projectId, callerId, callerTrust })`) and are trust-gated and audited. Never call another package over MCP.
74
74
 
@@ -101,7 +101,7 @@ export const POST: RouteHandler = async (req, state) => {
101
101
 
102
102
  **The feature gate runs before your handler**; never re-check the route's own `feature` inside it.
103
103
 
104
- > **A route with no `feature` is reachable by any authenticated project member.** It does not "require first-party trust". Symptom: no error anywhere — a viewer-role user or an agent's skill script successfully calls an endpoint you assumed was admin-only, and you find out in an audit, not in a test. Declare a `feature` on every route not deliberately open to all members.
104
+ > **A route with no `feature` is reachable by any authenticated project member.** It does not "require native trust". Symptom: no error anywhere — a viewer-role user or an agent's skill script successfully calls an endpoint you assumed was admin-only, and you find out in an audit, not in a test. Declare a `feature` on every route not deliberately open to all members.
105
105
 
106
106
  **Identity.** `req.session` (`userId`, `projectId`, `agentId`, `role`, `grantedFeatures`, `priority`, `agentAccess`, `limits`) is the only identity — never re-state it in a body or query, never trust a caller-supplied `projectId`; best case the gateway rejects with 403 `project_mismatch`, worst case you built a confused deputy. `req.interactive` is a transport marker (browser session vs machine ticket), **never identity**, and is absent on sandboxed dispatch — read it as strict `=== true` or you hand sandboxed callers the privileged human branch.
107
107
 
@@ -115,17 +115,17 @@ Enforced automatically by the dispatchers; your package writes no code for it. L
115
115
 
116
116
  | | tool calls / min | route calls / min | tool timeout |
117
117
  |---|---|---|---|
118
- | first-party | unlimited | unlimited | none effectively enforced |
118
+ | native | unlimited | unlimited | none effectively enforced |
119
119
  | trusted | 100 | 300 | 120 s |
120
120
  | untrusted | 10 | 30 | 30 s |
121
121
 
122
- Only the **trusted** tier's tool rate limit and timeout are administrator-tunable; the rest are hardcoded security floors. Exceeding a route limit returns **429**, and every non-first-party route access is audited. Trust is assigned by where the package came from — declaring `access.trust` yourself changes none of it.
122
+ Only the **trusted** tier's tool rate limit and timeout are administrator-tunable; the rest are hardcoded security floors. Exceeding a route limit returns **429**, and every non-native route access is audited. Trust is assigned by where the package came from — declaring `access.trust` yourself changes none of it.
123
123
 
124
124
  ---
125
125
 
126
126
  ## 6. The sandbox — what a dropped package can do
127
127
 
128
- A `_packages/` drop runs in a WASM sandbox, untrusted by default; an administrator may elevate it to trusted.
128
+ A `_packages/` drop runs in a WASM sandbox, untrusted by default; an administrator may elevate it to trusted (Admin → Packages, or `neuralis package trust <package> trusted`).
129
129
 
130
130
  **WASM here is the TOOL and ROUTE runtime — it never renders UI.** There is no WASM UI engine and none can be declared: the `wasm-ui` renderer was removed from the surface contract and a manifest still declaring it fails validation. A sandboxed WASM tool feeds a card exactly like any other tool — by returning `structuredContent` — and the UI itself ships as a self-contained `iframe` asset (`references/surfaces-and-design.md`).
131
131
 
@@ -166,10 +166,10 @@ wrappers over brain-core's `read`/`list`/`search`/`write` routes
166
166
  (`create`/`overwrite`/`edit`/`insert`/`lines`/`fields`/`move`/`copy`/`mkdir`/`revert`/`delete`). The route CATALOG —
167
167
  which routes exist and which feature each needs — lives on the per-package
168
168
  "API &amp; usage" docs pages:
169
- <https://docs.neuralisapp.com/agent-core/api-usage> ·
170
- <https://docs.neuralisapp.com/brain-core/api-usage> ·
171
- <https://docs.neuralisapp.com/admin/features-and-routes> ·
172
- <https://docs.neuralisapp.com/machine-core/api-usage> — mechanism:
169
+ <https://docs.neuralisapp.com/agent-core/routes> ·
170
+ <https://docs.neuralisapp.com/brain-core/routes> ·
171
+ <https://docs.neuralisapp.com/admin/routes> ·
172
+ <https://docs.neuralisapp.com/machine-core/routes> — mechanism:
173
173
  <https://docs.neuralisapp.com/package-system/routes>.
174
174
 
175
175
  `callRoute` denials return `{ ok: false, error: '…' }` rather than throwing — a
@@ -241,13 +241,13 @@ Only a package with `src/tools/*.ts`, `src/routes/*.ts` or `src/lifecycle.ts` ne
241
241
 
242
242
  **Fail-closed:** `src/routes/*` present with no valid `dist/routes.json` ⇒ the package wires **no** route at all and loads `partial` (`build-missing`), logging `declares route sources but has no valid dist/routes.json — failing closed`. Tools may still work, which makes it look like a routing bug. Rebuild.
243
243
 
244
- **Three triggers:** the **Build** button in the filesystem widget's Packages panel (build + reload + audit — the recommended path); **automatic on install** when `runtime.type` is `wasm` and source is present, where failure is **non-fatal** (the installer warns, the package installs `partial` — this is why "it installed fine but my tool 503s" happens); or the CLI **by absolute path**:
244
+ **Three triggers:** **Build** in Admin → Packages or `neuralis package build <package>` (a package manager's action: build + reload + audit — the recommended path); **automatic on install** when `runtime.type` is `wasm` and source is present, where failure is **non-fatal** (the installer warns, the package installs `partial` — this is why "it installed fine but my tool 503s" happens); or the CLI **by absolute path**:
245
245
 
246
246
  ```bash
247
247
  node <install-root>/node_modules/.bin/neuralis-build <absolute package root>
248
248
  ```
249
249
 
250
- A first-party package's UI uses the same CLI in `ui` mode — `neuralis-build ui <absolute package root>` bundles the `app.module` entry to `dist/app/` and fails the build if React or another host-shared module would be bundled into it (`BUNDLED_SHARED_MODULE` — two Reacts break every hook), if it imports a module of another UI package that package's `app.module.provides` does not list (`UNDECLARED_SHARED_MODULE`), or if it reads React with no resolvable `react` (`NO_REACT`). Beside the bundle it writes `shared-imports.json` (host API version, React major, shared ids read); the host refuses a module whose record no longer matches it, so rebuild after every platform upgrade.
250
+ A native package's UI uses the same CLI in `ui` mode — `neuralis-build ui <absolute package root>` bundles the `app.module` entry to `dist/app/` and fails the build if React or another host-shared module would be bundled into it (`BUNDLED_SHARED_MODULE` — two Reacts break every hook), if it imports a module of another UI package that package's `app.module.provides` does not list (`UNDECLARED_SHARED_MODULE`), or if it reads React with no resolvable `react` (`NO_REACT`). Beside the bundle it writes `shared-imports.json` (host API version, React major, shared ids read); the host refuses a module whose record no longer matches it, so rebuild after every platform upgrade.
251
251
 
252
252
  `npx neuralis-build` from a project data folder **does not work** — no module tree above it, so npx goes to the public registry and dies with `E404 'neuralis-build@*' is not in this registry`.
253
253
 
@@ -255,9 +255,9 @@ A first-party package's UI uses the same CLI in `ui` mode — `neuralis-build ui
255
255
 
256
256
  ---
257
257
 
258
- ## 7b. Developing a first-party package from ITS OWN repository
258
+ ## 7b. Developing a native package from ITS OWN repository
259
259
 
260
- A column-(a) package does not have to live inside the platform's tree, and for a real first-party
260
+ A column-(a) package does not have to live inside the platform's tree, and for a real native
261
261
  package it should not. The dependency line is the trust act, and a dependency can point at any
262
262
  directory on the deploying machine — so the package gets its own repository, its own git history and
263
263
  its own release cadence, and the deployment's image build installs it from that directory (or from a
@@ -311,7 +311,7 @@ report a suite as green in an environment you did not run it in.
311
311
 
312
312
  1. **git remote** — source backup and review. Changes nothing about the deployment.
313
313
  2. **dependency registration + image rebuild** — the operator's trust act, then the build that
314
- installs it. THIS is what makes the package discovered and loaded in-process with first-party
314
+ installs it. THIS is what makes the package discovered and loaded in-process with native
315
315
  trust.
316
316
  3. **mount** — binds the directory over the installed copy for live editing (build in the folder,
317
317
  restart). Plumbing only; without (2) the package is still invisible.
@@ -346,7 +346,7 @@ every agent that can see it.
346
346
  | **503 `code-runtime-unavailable`** | the package loaded `partial`; read the reason |
347
347
  | `pkg add` or the image build refuses the package, or it is absent and Admin health lists it under `builtin-packages` | it failed the loader's admission check (named per file — a tool in `tools/` missing `x-neuralis.annotations.category` is the usual one: the schema validator alone passes it, the native-tool check does not), or it collides with another package over a service, an OAuth prefix or a config key; only that package is left out |
348
348
 
349
- Partial reasons: `build-missing` (no sandbox artifact — rebuild), `runtime-unavailable` (the code runtime crashed, or an MCP subprocess failed to spawn), `untrusted-node` (a non-first-party package declared or defaulted to the in-process `node` runtime), `untrusted-mcp` (a non-first-party package declared the `mcp` subprocess runtime — nothing was spawned). **Both trust reasons have the same fix: declare `wasm`, the only code runtime available below first-party.** A `partial` package still registers every declarative contribution, which is why it looks half-working rather than broken.
349
+ Partial reasons: `build-missing` (no sandbox artifact — rebuild), `runtime-unavailable` (the code runtime crashed, or an MCP subprocess failed to spawn), `untrusted-node` (a non-native package declared or defaulted to the in-process `node` runtime), `untrusted-mcp` (a non-native package declared the `mcp` subprocess runtime — nothing was spawned). **Both trust reasons have the same fix: declare `wasm`, the only code runtime available below native.** A `partial` package still registers every declarative contribution, which is why it looks half-working rather than broken.
350
350
 
351
351
  ---
352
352