neotoma 0.7.0 → 0.8.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 (263) hide show
  1. package/dist/actions.d.ts +74 -0
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +324 -20
  4. package/dist/actions.js.map +1 -1
  5. package/dist/cli/aauth_tbs_attestation.d.ts +156 -0
  6. package/dist/cli/aauth_tbs_attestation.d.ts.map +1 -0
  7. package/dist/cli/aauth_tbs_attestation.js +188 -0
  8. package/dist/cli/aauth_tbs_attestation.js.map +1 -0
  9. package/dist/cli/aauth_tpm2_attestation.d.ts +144 -0
  10. package/dist/cli/aauth_tpm2_attestation.d.ts.map +1 -0
  11. package/dist/cli/aauth_tpm2_attestation.js +182 -0
  12. package/dist/cli/aauth_tpm2_attestation.js.map +1 -0
  13. package/dist/cli/aauth_yubikey_attestation.d.ts +194 -0
  14. package/dist/cli/aauth_yubikey_attestation.d.ts.map +1 -0
  15. package/dist/cli/aauth_yubikey_attestation.js +217 -0
  16. package/dist/cli/aauth_yubikey_attestation.js.map +1 -0
  17. package/dist/cli/agents_grants_import.d.ts +58 -0
  18. package/dist/cli/agents_grants_import.d.ts.map +1 -0
  19. package/dist/cli/agents_grants_import.js +283 -0
  20. package/dist/cli/agents_grants_import.js.map +1 -0
  21. package/dist/cli/index.d.ts.map +1 -1
  22. package/dist/cli/index.js +35 -0
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/cli/packs.d.ts +15 -0
  25. package/dist/cli/packs.d.ts.map +1 -0
  26. package/dist/cli/packs.js +156 -0
  27. package/dist/cli/packs.js.map +1 -0
  28. package/dist/crypto/agent_identity.d.ts +66 -9
  29. package/dist/crypto/agent_identity.d.ts.map +1 -1
  30. package/dist/crypto/agent_identity.js +22 -8
  31. package/dist/crypto/agent_identity.js.map +1 -1
  32. package/dist/middleware/aauth_admission.d.ts +30 -0
  33. package/dist/middleware/aauth_admission.d.ts.map +1 -0
  34. package/dist/middleware/aauth_admission.js +116 -0
  35. package/dist/middleware/aauth_admission.js.map +1 -0
  36. package/dist/middleware/aauth_verify.d.ts.map +1 -1
  37. package/dist/middleware/aauth_verify.js +92 -8
  38. package/dist/middleware/aauth_verify.js.map +1 -1
  39. package/dist/server.d.ts.map +1 -1
  40. package/dist/server.js +16 -1
  41. package/dist/server.js.map +1 -1
  42. package/dist/services/aauth_admission.d.ts +49 -0
  43. package/dist/services/aauth_admission.d.ts.map +1 -0
  44. package/dist/services/aauth_admission.js +110 -0
  45. package/dist/services/aauth_admission.js.map +1 -0
  46. package/dist/services/aauth_attestation_apple_se.d.ts +36 -0
  47. package/dist/services/aauth_attestation_apple_se.d.ts.map +1 -0
  48. package/dist/services/aauth_attestation_apple_se.js +208 -0
  49. package/dist/services/aauth_attestation_apple_se.js.map +1 -0
  50. package/dist/services/aauth_attestation_revocation.d.ts +160 -0
  51. package/dist/services/aauth_attestation_revocation.d.ts.map +1 -0
  52. package/dist/services/aauth_attestation_revocation.js +770 -0
  53. package/dist/services/aauth_attestation_revocation.js.map +1 -0
  54. package/dist/services/aauth_attestation_tpm2.d.ts +61 -0
  55. package/dist/services/aauth_attestation_tpm2.d.ts.map +1 -0
  56. package/dist/services/aauth_attestation_tpm2.js +373 -0
  57. package/dist/services/aauth_attestation_tpm2.js.map +1 -0
  58. package/dist/services/aauth_attestation_trust_config.d.ts +42 -0
  59. package/dist/services/aauth_attestation_trust_config.d.ts.map +1 -0
  60. package/dist/services/aauth_attestation_trust_config.js +242 -0
  61. package/dist/services/aauth_attestation_trust_config.js.map +1 -0
  62. package/dist/services/aauth_attestation_verifier.d.ts +134 -0
  63. package/dist/services/aauth_attestation_verifier.d.ts.map +1 -0
  64. package/dist/services/aauth_attestation_verifier.js +167 -0
  65. package/dist/services/aauth_attestation_verifier.js.map +1 -0
  66. package/dist/services/aauth_attestation_webauthn_packed.d.ts +58 -0
  67. package/dist/services/aauth_attestation_webauthn_packed.d.ts.map +1 -0
  68. package/dist/services/aauth_attestation_webauthn_packed.js +442 -0
  69. package/dist/services/aauth_attestation_webauthn_packed.js.map +1 -0
  70. package/dist/services/aauth_operator_allowlist.d.ts +40 -0
  71. package/dist/services/aauth_operator_allowlist.d.ts.map +1 -0
  72. package/dist/services/aauth_operator_allowlist.js +69 -0
  73. package/dist/services/aauth_operator_allowlist.js.map +1 -0
  74. package/dist/services/aauth_tpm_structures.d.ts +103 -0
  75. package/dist/services/aauth_tpm_structures.d.ts.map +1 -0
  76. package/dist/services/aauth_tpm_structures.js +310 -0
  77. package/dist/services/aauth_tpm_structures.js.map +1 -0
  78. package/dist/services/activation/stage_zero_five.d.ts +62 -0
  79. package/dist/services/activation/stage_zero_five.d.ts.map +1 -0
  80. package/dist/services/activation/stage_zero_five.js +126 -0
  81. package/dist/services/activation/stage_zero_five.js.map +1 -0
  82. package/dist/services/activation/user_preference.d.ts +110 -0
  83. package/dist/services/activation/user_preference.d.ts.map +1 -0
  84. package/dist/services/activation/user_preference.js +98 -0
  85. package/dist/services/activation/user_preference.js.map +1 -0
  86. package/dist/services/activation/vertical_detection.d.ts +78 -0
  87. package/dist/services/activation/vertical_detection.d.ts.map +1 -0
  88. package/dist/services/activation/vertical_detection.js +219 -0
  89. package/dist/services/activation/vertical_detection.js.map +1 -0
  90. package/dist/services/agent_capabilities.d.ts +80 -90
  91. package/dist/services/agent_capabilities.d.ts.map +1 -1
  92. package/dist/services/agent_capabilities.js +161 -275
  93. package/dist/services/agent_capabilities.js.map +1 -1
  94. package/dist/services/agent_grants.d.ts +146 -0
  95. package/dist/services/agent_grants.d.ts.map +1 -0
  96. package/dist/services/agent_grants.js +580 -0
  97. package/dist/services/agent_grants.js.map +1 -0
  98. package/dist/services/attribution_policy.d.ts.map +1 -1
  99. package/dist/services/attribution_policy.js +2 -1
  100. package/dist/services/attribution_policy.js.map +1 -1
  101. package/dist/services/bundled_pages/html_shell.d.ts +118 -0
  102. package/dist/services/bundled_pages/html_shell.d.ts.map +1 -0
  103. package/dist/services/bundled_pages/html_shell.js +242 -0
  104. package/dist/services/bundled_pages/html_shell.js.map +1 -0
  105. package/dist/services/bundled_pages/tokens.d.ts +496 -0
  106. package/dist/services/bundled_pages/tokens.d.ts.map +1 -0
  107. package/dist/services/bundled_pages/tokens.js +261 -0
  108. package/dist/services/bundled_pages/tokens.js.map +1 -0
  109. package/dist/services/compliance/alerting.d.ts +123 -0
  110. package/dist/services/compliance/alerting.d.ts.map +1 -0
  111. package/dist/services/compliance/alerting.js +169 -0
  112. package/dist/services/compliance/alerting.js.map +1 -0
  113. package/dist/services/compliance/historical_backfill.d.ts +74 -0
  114. package/dist/services/compliance/historical_backfill.d.ts.map +1 -0
  115. package/dist/services/compliance/historical_backfill.js +244 -0
  116. package/dist/services/compliance/historical_backfill.js.map +1 -0
  117. package/dist/services/compliance/renderer.d.ts +21 -0
  118. package/dist/services/compliance/renderer.d.ts.map +1 -0
  119. package/dist/services/compliance/renderer.js +208 -0
  120. package/dist/services/compliance/renderer.js.map +1 -0
  121. package/dist/services/compliance/routes.d.ts +55 -0
  122. package/dist/services/compliance/routes.d.ts.map +1 -0
  123. package/dist/services/compliance/routes.js +212 -0
  124. package/dist/services/compliance/routes.js.map +1 -0
  125. package/dist/services/compliance/scorecard.d.ts +98 -0
  126. package/dist/services/compliance/scorecard.d.ts.map +1 -0
  127. package/dist/services/compliance/scorecard.js +318 -0
  128. package/dist/services/compliance/scorecard.js.map +1 -0
  129. package/dist/services/conversation_turn.d.ts +85 -0
  130. package/dist/services/conversation_turn.d.ts.map +1 -0
  131. package/dist/services/conversation_turn.js +457 -0
  132. package/dist/services/conversation_turn.js.map +1 -0
  133. package/dist/services/correction.d.ts.map +1 -1
  134. package/dist/services/correction.js +8 -1
  135. package/dist/services/correction.js.map +1 -1
  136. package/dist/services/docs_bundle/frontmatter.d.ts +26 -0
  137. package/dist/services/docs_bundle/frontmatter.d.ts.map +1 -0
  138. package/dist/services/docs_bundle/frontmatter.js +50 -0
  139. package/dist/services/docs_bundle/frontmatter.js.map +1 -0
  140. package/dist/services/docs_bundle/loader.d.ts +28 -0
  141. package/dist/services/docs_bundle/loader.d.ts.map +1 -0
  142. package/dist/services/docs_bundle/loader.js +91 -0
  143. package/dist/services/docs_bundle/loader.js.map +1 -0
  144. package/dist/services/docs_bundle/render_html.d.ts +15 -0
  145. package/dist/services/docs_bundle/render_html.d.ts.map +1 -0
  146. package/dist/services/docs_bundle/render_html.js +48 -0
  147. package/dist/services/docs_bundle/render_html.js.map +1 -0
  148. package/dist/services/docs_bundle/types.d.ts +73 -0
  149. package/dist/services/docs_bundle/types.d.ts.map +1 -0
  150. package/dist/services/docs_bundle/types.js +50 -0
  151. package/dist/services/docs_bundle/types.js.map +1 -0
  152. package/dist/services/docs_install/loader.d.ts +25 -0
  153. package/dist/services/docs_install/loader.d.ts.map +1 -0
  154. package/dist/services/docs_install/loader.js +63 -0
  155. package/dist/services/docs_install/loader.js.map +1 -0
  156. package/dist/services/docs_install/renderer.d.ts +40 -0
  157. package/dist/services/docs_install/renderer.d.ts.map +1 -0
  158. package/dist/services/docs_install/renderer.js +323 -0
  159. package/dist/services/docs_install/renderer.js.map +1 -0
  160. package/dist/services/docs_install/routes.d.ts +20 -0
  161. package/dist/services/docs_install/routes.d.ts.map +1 -0
  162. package/dist/services/docs_install/routes.js +117 -0
  163. package/dist/services/docs_install/routes.js.map +1 -0
  164. package/dist/services/feedback/mirror_local_to_entity.d.ts +75 -0
  165. package/dist/services/feedback/mirror_local_to_entity.d.ts.map +1 -0
  166. package/dist/services/feedback/mirror_local_to_entity.js +101 -0
  167. package/dist/services/feedback/mirror_local_to_entity.js.map +1 -0
  168. package/dist/services/feedback/neotoma_payload.d.ts +69 -0
  169. package/dist/services/feedback/neotoma_payload.d.ts.map +1 -0
  170. package/dist/services/feedback/neotoma_payload.js +181 -0
  171. package/dist/services/feedback/neotoma_payload.js.map +1 -0
  172. package/dist/services/inspector_mount.d.ts +90 -0
  173. package/dist/services/inspector_mount.d.ts.map +1 -0
  174. package/dist/services/inspector_mount.js +219 -0
  175. package/dist/services/inspector_mount.js.map +1 -0
  176. package/dist/services/local_auth.d.ts +13 -0
  177. package/dist/services/local_auth.d.ts.map +1 -1
  178. package/dist/services/local_auth.js +40 -2
  179. package/dist/services/local_auth.js.map +1 -1
  180. package/dist/services/oauth_pages/render.d.ts +25 -0
  181. package/dist/services/oauth_pages/render.d.ts.map +1 -0
  182. package/dist/services/oauth_pages/render.js +235 -0
  183. package/dist/services/oauth_pages/render.js.map +1 -0
  184. package/dist/services/observation_storage.d.ts.map +1 -1
  185. package/dist/services/observation_storage.js +8 -1
  186. package/dist/services/observation_storage.js.map +1 -1
  187. package/dist/services/protected_entity_types.d.ts +85 -0
  188. package/dist/services/protected_entity_types.d.ts.map +1 -0
  189. package/dist/services/protected_entity_types.js +120 -0
  190. package/dist/services/protected_entity_types.js.map +1 -0
  191. package/dist/services/request_context.d.ts +16 -0
  192. package/dist/services/request_context.d.ts.map +1 -1
  193. package/dist/services/request_context.js +8 -0
  194. package/dist/services/request_context.js.map +1 -1
  195. package/dist/services/root_landing/auth_overview.d.ts +60 -0
  196. package/dist/services/root_landing/auth_overview.d.ts.map +1 -0
  197. package/dist/services/root_landing/auth_overview.js +86 -0
  198. package/dist/services/root_landing/auth_overview.js.map +1 -0
  199. package/dist/services/root_landing/cli_overview.d.ts +34 -0
  200. package/dist/services/root_landing/cli_overview.d.ts.map +1 -0
  201. package/dist/services/root_landing/cli_overview.js +123 -0
  202. package/dist/services/root_landing/cli_overview.js.map +1 -0
  203. package/dist/services/root_landing/html_template.d.ts.map +1 -1
  204. package/dist/services/root_landing/html_template.js +2 -2
  205. package/dist/services/root_landing/html_template.js.map +1 -1
  206. package/dist/services/root_landing/http_api_overview.d.ts +34 -0
  207. package/dist/services/root_landing/http_api_overview.d.ts.map +1 -0
  208. package/dist/services/root_landing/http_api_overview.js +110 -0
  209. package/dist/services/root_landing/http_api_overview.js.map +1 -0
  210. package/dist/services/root_landing/mcp_overview.d.ts +34 -0
  211. package/dist/services/root_landing/mcp_overview.d.ts.map +1 -0
  212. package/dist/services/root_landing/mcp_overview.js +133 -0
  213. package/dist/services/root_landing/mcp_overview.js.map +1 -0
  214. package/dist/services/root_landing/site_nav.d.ts.map +1 -1
  215. package/dist/services/root_landing/site_nav.js +42 -1
  216. package/dist/services/root_landing/site_nav.js.map +1 -1
  217. package/dist/services/sandbox/inspector_redirect.d.ts +41 -0
  218. package/dist/services/sandbox/inspector_redirect.d.ts.map +1 -0
  219. package/dist/services/sandbox/inspector_redirect.js +59 -0
  220. package/dist/services/sandbox/inspector_redirect.js.map +1 -0
  221. package/dist/services/sandbox/pack_registry.d.ts +57 -0
  222. package/dist/services/sandbox/pack_registry.d.ts.map +1 -0
  223. package/dist/services/sandbox/pack_registry.js +101 -0
  224. package/dist/services/sandbox/pack_registry.js.map +1 -0
  225. package/dist/services/sandbox/seeder.d.ts +53 -0
  226. package/dist/services/sandbox/seeder.d.ts.map +1 -0
  227. package/dist/services/sandbox/seeder.js +98 -0
  228. package/dist/services/sandbox/seeder.js.map +1 -0
  229. package/dist/services/sandbox/sessions.d.ts +131 -0
  230. package/dist/services/sandbox/sessions.d.ts.map +1 -0
  231. package/dist/services/sandbox/sessions.js +326 -0
  232. package/dist/services/sandbox/sessions.js.map +1 -0
  233. package/dist/services/schema_definitions.d.ts.map +1 -1
  234. package/dist/services/schema_definitions.js +47 -0
  235. package/dist/services/schema_definitions.js.map +1 -1
  236. package/dist/services/session_info.d.ts +75 -0
  237. package/dist/services/session_info.d.ts.map +1 -1
  238. package/dist/services/session_info.js +48 -3
  239. package/dist/services/session_info.js.map +1 -1
  240. package/dist/services/verticals/baseline_metadata.d.ts +39 -0
  241. package/dist/services/verticals/baseline_metadata.d.ts.map +1 -0
  242. package/dist/services/verticals/baseline_metadata.js +394 -0
  243. package/dist/services/verticals/baseline_metadata.js.map +1 -0
  244. package/dist/services/verticals/entity_type_registry.d.ts +29 -0
  245. package/dist/services/verticals/entity_type_registry.d.ts.map +1 -0
  246. package/dist/services/verticals/entity_type_registry.js +169 -0
  247. package/dist/services/verticals/entity_type_registry.js.map +1 -0
  248. package/dist/services/verticals/install.d.ts +64 -0
  249. package/dist/services/verticals/install.d.ts.map +1 -0
  250. package/dist/services/verticals/install.js +262 -0
  251. package/dist/services/verticals/install.js.map +1 -0
  252. package/dist/services/verticals/registry.d.ts +131 -0
  253. package/dist/services/verticals/registry.d.ts.map +1 -0
  254. package/dist/services/verticals/registry.js +457 -0
  255. package/dist/services/verticals/registry.js.map +1 -0
  256. package/dist/shared/action_schemas.d.ts +10 -10
  257. package/dist/shared/contract_mappings.d.ts.map +1 -1
  258. package/dist/shared/contract_mappings.js +56 -0
  259. package/dist/shared/contract_mappings.js.map +1 -1
  260. package/dist/shared/openapi_types.d.ts +443 -7
  261. package/dist/shared/openapi_types.d.ts.map +1 -1
  262. package/openapi.yaml +423 -4
  263. package/package.json +1 -1
package/dist/actions.d.ts CHANGED
@@ -14,6 +14,80 @@ export declare const app: import("express-serve-static-core").Express;
14
14
  * X-Forwarded-For header — any caller can claim to be loopback.
15
15
  */
16
16
  export declare function isLocalRequest(req: express.Request): boolean;
17
+ /**
18
+ * Derive the canonical `@authority` the AAuth profile requires (AAuth §10.3.1).
19
+ * We trust explicit configuration first, then fall back to the host the
20
+ * process is actually listening on. We must NOT trust `Host:` headers from
21
+ * the request path — an attacker could smuggle signatures authored against
22
+ * a different authority. See src/middleware/aauth_verify.ts.
23
+ */
24
+ export declare function canonicalAauthAuthority(): string;
25
+ export declare function storeStructuredForApi(params: {
26
+ userId: string;
27
+ entities: Record<string, unknown>[];
28
+ sourcePriority: number;
29
+ observationSource?: import("./shared/action_schemas.js").ObservationSource;
30
+ idempotencyKey: string;
31
+ originalFilename?: string;
32
+ relationships?: Array<{
33
+ relationship_type: string;
34
+ source_index: number;
35
+ target_index: number;
36
+ }>;
37
+ commit?: boolean;
38
+ strict?: boolean;
39
+ }): Promise<{
40
+ success: boolean;
41
+ replayed: boolean;
42
+ source_id: any;
43
+ entities_created: number;
44
+ observations_created: number;
45
+ entities: {
46
+ entity_id: string;
47
+ entity_type: string;
48
+ observation_id: string;
49
+ }[];
50
+ } | {
51
+ warnings?: {
52
+ code: string;
53
+ policy: string;
54
+ observation_index: number;
55
+ entity_type: string;
56
+ entity_id: string;
57
+ identity_basis: string;
58
+ identity_rule: string;
59
+ }[] | undefined;
60
+ success: boolean;
61
+ replayed: boolean;
62
+ commit: boolean;
63
+ source_id: string | null;
64
+ entities_created: number;
65
+ observations_created: number;
66
+ entities: {
67
+ entity_id: string;
68
+ entity_type: string;
69
+ observation_id: string | null;
70
+ observation_index: number;
71
+ action: "created" | "matched_existing" | "would_create" | "would_match_existing" | "extended";
72
+ canonical_name: string;
73
+ resolver_path: string[];
74
+ identity_basis: string;
75
+ identity_rule: string;
76
+ warnings?: {
77
+ code: string;
78
+ policy: string;
79
+ entity_type: string;
80
+ identity_basis: string;
81
+ identity_rule: string;
82
+ }[] | undefined;
83
+ entity_snapshot_after: Record<string, unknown> | null;
84
+ }[];
85
+ relationships_created: {
86
+ relationship_type: string;
87
+ source_entity_id: string;
88
+ target_entity_id: string;
89
+ }[];
90
+ }>;
17
91
  export declare function startHTTPServer(): Promise<{
18
92
  server: import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
19
93
  port: number;
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AA0I9B,eAAO,MAAM,GAAG,6CAAY,CAAC;AAgY7B;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAU5D;AA2hMD,wBAAsB,eAAe;;;eAkDpC"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAiJ9B,eAAO,MAAM,GAAG,6CAAY,CAAC;AAwZ7B;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAU5D;AA4PD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAmBhD;AA2kHD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,4BAA4B,EAAE,iBAAiB,CAAC;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,KAAK,CAAC;QACpB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;;;;;;;;;;;;;cAkfS,MAAM;gBACJ,MAAM;2BACK,MAAM;qBACZ,MAAM;mBACR,MAAM;wBACD,MAAM;uBACP,MAAM;;;;;;;;;mBA9IV,MAAM;qBACJ,MAAM;wBACH,MAAM,GAAG,IAAI;2BACV,MAAM;;wBAET,MAAM;uBACP,MAAM,EAAE;wBACP,MAAM;uBACP,MAAM;;kBA9NX,MAAM;oBACJ,MAAM;yBACD,MAAM;4BACH,MAAM;2BACP,MAAM;;+BA4NF,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;;;2BAkFlC,MAAM;0BACP,MAAM;0BACN,MAAM;;GA8E3B;AA29DD,wBAAsB,eAAe;;;eA6EpC"}
package/dist/actions.js CHANGED
@@ -16,11 +16,13 @@ import { encryptResponseMiddleware } from "./middleware/encrypt_response.js";
16
16
  import { unknownFieldsGuard } from "./middleware/unknown_fields_guard.js";
17
17
  import { aauthVerify, getAAuthContextFromRequest, getAttributionDecisionFromRequest, } from "./middleware/aauth_verify.js";
18
18
  import { attributionContext } from "./middleware/attribution_context.js";
19
+ import { aauthAdmission, getAAuthAdmissionFromRequest, } from "./middleware/aauth_admission.js";
19
20
  import { buildSessionInfo } from "./services/session_info.js";
20
21
  import { AttributionPolicyError } from "./services/attribution_policy.js";
21
22
  import { registerFeedbackAdminProxyRoutes } from "./services/feedback/admin_proxy.js";
22
23
  import { AgentCapabilityError, contextFromAgentIdentity, enforceAgentCapability, } from "./services/agent_capabilities.js";
23
- import { getCurrentAgentIdentity, runWithRequestContext, } from "./services/request_context.js";
24
+ import { getCurrentAAuthAdmission, getCurrentAgentIdentity, runWithRequestContext, } from "./services/request_context.js";
25
+ import { assertCanWriteProtectedBatch } from "./services/protected_entity_types.js";
24
26
  import { createAgentIdentity as buildAgentIdentity, getAgentIdentityFromRequest, normaliseClientName, } from "./crypto/agent_identity.js";
25
27
  import { initServerKeys } from "./services/encryption_service.js";
26
28
  import { storeRawContent, downloadRawContent, resolveLocalSourceFilePath, SourceFileNotFoundError, } from "./services/raw_storage.js";
@@ -30,7 +32,7 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
30
32
  import { NeotomaServer } from "./server.js";
31
33
  import { logger } from "./utils/logger.js";
32
34
  import { OAuthError } from "./services/mcp_oauth_errors.js";
33
- import { ensureLocalDevUser, ensureSandboxPublicUser, LOCAL_DEV_USER_ID, SANDBOX_PUBLIC_USER_ID, } from "./services/local_auth.js";
35
+ import { ensureLocalDevUser, ensureSandboxAauthUser, ensureSandboxPublicUser, LOCAL_DEV_USER_ID, SANDBOX_PUBLIC_USER_ID, } from "./services/local_auth.js";
34
36
  import { isSandboxMode, sandboxDestructiveGuard, sandboxHeaderMiddleware, } from "./services/sandbox_mode.js";
35
37
  import { buildLandingContext, buildRootLandingHtml, buildRootLandingJson, buildRootLandingMarkdown, buildRobotsTxt, wantsHtml as acceptWantsHtml, wantsMarkdown as acceptWantsMarkdown, } from "./services/root_landing/index.js";
36
38
  import { getSandboxTermsResponse } from "./services/sandbox/terms.js";
@@ -363,6 +365,28 @@ app.get("/.well-known/oauth-protected-resource", async (req, res) => {
363
365
  authorization_servers: [base],
364
366
  });
365
367
  });
368
+ // AAuth resource server metadata. Exposed publicly so AAuth client libraries
369
+ // can auto-configure issuer / supported algs / signature window without an
370
+ // out-of-band conversation. Neotoma is verifier-only: it never hosts agent
371
+ // JWKs, so jwks_uri is null and agents convey their public keys per-request
372
+ // via the Signature-Key header (with thumbprint binding via the jkt claim).
373
+ // See docs/proposals/agent-trust-framework.md and src/middleware/aauth_verify.ts.
374
+ app.get("/.well-known/aauth-resource.json", (_req, res) => {
375
+ const authorityHost = canonicalAauthAuthority();
376
+ const issuer = authorityHost.startsWith("http")
377
+ ? authorityHost
378
+ : `https://${authorityHost}`;
379
+ res.setHeader("Content-Type", "application/json");
380
+ res.json({
381
+ issuer,
382
+ client_name: "Neotoma",
383
+ signature_window: 60,
384
+ supported_algs: ["ES256", "EdDSA"],
385
+ supported_typ: ["aa-agent+jwt"],
386
+ jwks_uri: null,
387
+ jwks_uri_reason: "Neotoma is verifier-only; agent JWKs are conveyed per-request via the Signature-Key header (with thumbprint binding via the jkt claim).",
388
+ });
389
+ });
366
390
  // Server info endpoint (no-auth) - exposes server port for MCP configuration
367
391
  // When NEOTOMA_MCP_PROXY_URL or MCP_PROXY_URL is set (e.g. ngrok tunnel), mcpUrl uses it so "Add to Cursor" uses the proxy.
368
392
  app.get("/server-info", (_req, res) => {
@@ -661,10 +685,20 @@ function setOAuthKeySessionCookie(req, res) {
661
685
  * the request path — an attacker could smuggle signatures authored against
662
686
  * a different authority. See src/middleware/aauth_verify.ts.
663
687
  */
664
- function canonicalAauthAuthority() {
688
+ export function canonicalAauthAuthority() {
665
689
  const explicit = process.env.NEOTOMA_AAUTH_AUTHORITY?.trim();
666
- if (explicit)
690
+ if (explicit) {
691
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(explicit)) {
692
+ try {
693
+ const url = new URL(explicit);
694
+ return url.host;
695
+ }
696
+ catch {
697
+ return explicit;
698
+ }
699
+ }
667
700
  return explicit;
701
+ }
668
702
  try {
669
703
  const url = new URL(config.apiBase);
670
704
  return url.host;
@@ -693,6 +727,13 @@ app.use(aauthVerify({
693
727
  // may nest a more specific `runWithRequestContext` — nested scopes
694
728
  // shadow outer ones exactly as intended.
695
729
  app.use(attributionContext());
730
+ // Stronger AAuth Admission plan: resolve verified AAuth identities to
731
+ // `agent_grant` entities and stamp `req.aauthAdmission` /
732
+ // `req.authenticatedUserId`. Runs after attributionContext so the
733
+ // admission middleware can nest a richer request context (admission +
734
+ // identity) for downstream guards. Cheap public discovery routes are
735
+ // short-circuited inside the middleware.
736
+ app.use(aauthAdmission());
696
737
  // MCP StreamableHTTP endpoint (GET, POST, DELETE)
697
738
  // This endpoint enables Cursor's "Connect" button for OAuth authentication
698
739
  app.all("/mcp", async (req, res) => {
@@ -742,7 +783,12 @@ app.all("/mcp", async (req, res) => {
742
783
  }
743
784
  }
744
785
  }
745
- const hasAuth = !!(authHeader?.startsWith("Bearer ") || connectionIdHeader);
786
+ // Stronger AAuth Admission: a verified AAuth identity that resolves
787
+ // to an active `agent_grant` for some user is a valid remote auth
788
+ // path. Bearer / OAuth / X-Connection-Id remain fully supported.
789
+ const aauthAdmissionForRequest = getAAuthAdmissionFromRequest(req);
790
+ const aauthAdmitted = !!(aauthAdmissionForRequest && aauthAdmissionForRequest.admitted);
791
+ const hasAuth = !!(authHeader?.startsWith("Bearer ") || connectionIdHeader || aauthAdmitted);
746
792
  // When encryption is on, only the key-derived token is accepted
747
793
  if (config.encryption.enabled && connectionIdHeader !== "dev-local") {
748
794
  const wwwAuthHeader = `Bearer resource_metadata="${base}/.well-known/oauth-protected-resource"`;
@@ -1763,6 +1809,20 @@ app.use(async (req, res, next) => {
1763
1809
  // full AAuth roundtrip get their hardware/software tier. Destructive admin
1764
1810
  // routes are separately gated by sandboxDestructiveGuard.
1765
1811
  if (isSandboxMode() && !headerAuth.startsWith("Bearer ")) {
1812
+ // α (sandbox attribution partitioning): when the request carries a verified
1813
+ // AAuth signature, attribute writes/reads to a deterministic per-thumbprint
1814
+ // user instead of the shared SANDBOX_PUBLIC_USER_ID. Same key on two
1815
+ // requests resolves to the same user_id; different keys → different
1816
+ // user_ids. Unsigned requests keep the public-user fallback unchanged.
1817
+ // Read partitioning falls out automatically because every read path scopes
1818
+ // queries by req.authenticatedUserId.
1819
+ const aauthCtx = req.aauth;
1820
+ if (aauthCtx?.verified === true && typeof aauthCtx.thumbprint === "string") {
1821
+ const aauthUser = ensureSandboxAauthUser(aauthCtx.thumbprint);
1822
+ req.authenticatedUserId = aauthUser.id;
1823
+ logger.info(`[Auth] ${req.method} ${req.path} auth_method=sandbox_aauth user_id=${aauthUser.id} thumbprint=${aauthCtx.thumbprint}`);
1824
+ return next();
1825
+ }
1766
1826
  const sandboxUser = ensureSandboxPublicUser();
1767
1827
  req.authenticatedUserId = sandboxUser.id;
1768
1828
  logger.info(`[Auth] ${req.method} ${req.path} auth_method=sandbox_public user_id=${sandboxUser.id}`);
@@ -1800,10 +1860,27 @@ app.use(async (req, res, next) => {
1800
1860
  }
1801
1861
  }
1802
1862
  }
1863
+ // Stronger AAuth Admission: a verified AAuth identity that resolved
1864
+ // to an active `agent_grant` is a valid remote auth path. The
1865
+ // admission middleware has already populated `req.aauthAdmission`
1866
+ // and `req.authenticatedUserId`. We accept it here even when no
1867
+ // Bearer is present so agents holding an active grant can reach
1868
+ // direct write routes (`/store`, `/observations/create`,
1869
+ // `/create_relationship`, `/correct`) without OAuth/Bearer.
1870
+ const admissionForRequest = getAAuthAdmissionFromRequest(req);
1871
+ if (admissionForRequest?.admitted &&
1872
+ admissionForRequest.user_id &&
1873
+ req.authenticatedUserId === admissionForRequest.user_id) {
1874
+ logger.info(`[Auth] ${req.method} ${req.path} auth_method=aauth_admitted user_id=${admissionForRequest.user_id} grant_id=${admissionForRequest.grant_id ?? "?"}`);
1875
+ return next();
1876
+ }
1803
1877
  // Bearer required for remaining paths (Ed25519, OAuth)
1804
1878
  if (!headerAuth.startsWith("Bearer ")) {
1805
1879
  logWarn("AuthMissingBearer", req);
1806
- return sendError(res, 401, "AUTH_REQUIRED", "Missing Bearer token");
1880
+ return sendError(res, 401, "AUTH_REQUIRED", "Missing Bearer token", {
1881
+ hint: "AAuth-signed agents can authenticate without Bearer once an active agent_grant matches their identity. " +
1882
+ "Create a grant via Inspector → Agents → Grants.",
1883
+ });
1807
1884
  }
1808
1885
  const bearerToken = headerAuth.slice("Bearer ".length).trim();
1809
1886
  // Try to validate as Ed25519 bearer token first
@@ -1883,18 +1960,33 @@ app.get("/me", async (req, res) => {
1883
1960
  }
1884
1961
  });
1885
1962
  /**
1886
- * Helper to extract authenticated user_id from request
1887
- * Supports: middleware-set user (e.g. dev-local when encryption off), session token, Ed25519 bearer
1963
+ * Helper to extract authenticated user_id from request.
1964
+ * Supports: middleware-set user (Bearer / OAuth / dev-local / AAuth admission),
1965
+ * session token, Ed25519 bearer.
1966
+ *
1967
+ * Stronger AAuth Admission plan: admitted AAuth callers carry
1968
+ * `req.authenticatedUserId` resolved from the matching `agent_grant`'s
1969
+ * owner. This helper applies the same `user_id` mismatch rules to them
1970
+ * as to OAuth/Bearer — payload-driven user pivots are rejected. The
1971
+ * local-dev override remains available only for the local dev user;
1972
+ * AAuth admission can never resolve to that id, so admitted agents are
1973
+ * structurally locked to their grant owner.
1888
1974
  * @param req - Express request object
1889
1975
  * @param providedUserId - Optional user_id from request body/query
1890
1976
  * @returns Authenticated user_id
1891
1977
  * @throws Error if not authenticated or user_id mismatch
1892
1978
  */
1893
1979
  async function getAuthenticatedUserId(req, providedUserId) {
1894
- // Use user already set by auth middleware (e.g. dev-local when encryption off and no Bearer)
1895
1980
  const authenticatedUserId = req.authenticatedUserId;
1981
+ const aauthAdmissionForRequest = getAAuthAdmissionFromRequest(req);
1896
1982
  if (authenticatedUserId) {
1897
1983
  if (providedUserId && providedUserId !== authenticatedUserId) {
1984
+ // AAuth admission: never honour payload user_id overrides — the
1985
+ // grant owner is the only valid scope for an admitted agent.
1986
+ // Bearer/OAuth flows fall through to the existing rules below.
1987
+ if (aauthAdmissionForRequest?.admitted) {
1988
+ throw new Error(`user_id parameter (${providedUserId}) does not match admitted AAuth user (${authenticatedUserId}); grants only resolve to their owner`);
1989
+ }
1898
1990
  // When authenticated as local dev user, allow body/query user_id override for CLI tests and dev flows.
1899
1991
  // The sandbox public user is intentionally excluded so public sandbox
1900
1992
  // callers cannot pivot into other users' data by spoofing user_id.
@@ -2912,6 +3004,136 @@ app.get("/agents", async (req, res) => {
2912
3004
  return handleApiError(req, res, error, "Failed to list agents", "DB_QUERY_FAILED", "APIError:agents_list");
2913
3005
  }
2914
3006
  });
3007
+ // ============================================================================
3008
+ // /agents/grants — Stronger AAuth Admission management surface
3009
+ //
3010
+ // Ergonomic wrappers over the agent_grant entity store for the Inspector
3011
+ // grants page and CLI tooling. The structured-store routes
3012
+ // (`store_structured`, `correct`) remain available for agents that hold
3013
+ // the bootstrap capability and want to manage grants programmatically.
3014
+ // All routes require an authenticated user and operate strictly within
3015
+ // that user's scope. Cross-user grant access is rejected at the route
3016
+ // layer (not just the UI). See docs/subsystems/agent_attribution_integration.md.
3017
+ //
3018
+ // REGISTERED BEFORE `/agents/:key` so Express's first-match ordering does
3019
+ // not route `/agents/grants` into the observed-agents handler.
3020
+ // ============================================================================
3021
+ const grantsCommonHandlers = {
3022
+ errorEnvelopeFromGrantError: (err) => {
3023
+ const e = err;
3024
+ if (e && typeof e === "object" && typeof e.code === "string" && typeof e.statusCode === "number") {
3025
+ return {
3026
+ statusCode: e.statusCode,
3027
+ envelope: buildErrorEnvelope(e.code.toUpperCase(), e.message ?? "Grant error", {
3028
+ code: e.code,
3029
+ }),
3030
+ };
3031
+ }
3032
+ return null;
3033
+ },
3034
+ };
3035
+ app.get("/agents/grants", async (req, res) => {
3036
+ try {
3037
+ const userId = await getAuthenticatedUserId(req, req.query.user_id);
3038
+ const { listGrantsForUser } = await import("./services/agent_grants.js");
3039
+ const status = typeof req.query.status === "string" ? req.query.status : undefined;
3040
+ const query = typeof req.query.q === "string" ? req.query.q : undefined;
3041
+ const grants = await listGrantsForUser(userId, {
3042
+ status: status || "all",
3043
+ query,
3044
+ });
3045
+ return res.json({ grants });
3046
+ }
3047
+ catch (error) {
3048
+ return handleApiError(req, res, error, "Failed to list agent grants", "DB_QUERY_FAILED", "APIError:agents_grants_list");
3049
+ }
3050
+ });
3051
+ app.get("/agents/grants/:id", async (req, res) => {
3052
+ try {
3053
+ const userId = await getAuthenticatedUserId(req, req.query.user_id);
3054
+ const { getGrant } = await import("./services/agent_grants.js");
3055
+ const grant = await getGrant(userId, req.params.id);
3056
+ if (!grant) {
3057
+ return sendError(res, 404, "RESOURCE_NOT_FOUND", `Agent grant not found: ${req.params.id}`);
3058
+ }
3059
+ return res.json({ grant });
3060
+ }
3061
+ catch (error) {
3062
+ return handleApiError(req, res, error, "Failed to fetch agent grant", "DB_QUERY_FAILED", "APIError:agents_grants_detail");
3063
+ }
3064
+ });
3065
+ app.post("/agents/grants", async (req, res) => {
3066
+ try {
3067
+ const userId = await getAuthenticatedUserId(req, req.body?.user_id ?? undefined);
3068
+ const body = (req.body ?? {});
3069
+ const { createGrant } = await import("./services/agent_grants.js");
3070
+ const grant = await createGrant(userId, {
3071
+ label: typeof body.label === "string" ? body.label : "",
3072
+ capabilities: Array.isArray(body.capabilities) ? body.capabilities : [],
3073
+ status: body.status ?? "active",
3074
+ match_sub: typeof body.match_sub === "string" ? body.match_sub : null,
3075
+ match_iss: typeof body.match_iss === "string" ? body.match_iss : null,
3076
+ match_thumbprint: typeof body.match_thumbprint === "string" ? body.match_thumbprint : null,
3077
+ notes: typeof body.notes === "string" ? body.notes : null,
3078
+ });
3079
+ return res.status(201).json({ grant });
3080
+ }
3081
+ catch (error) {
3082
+ const mapped = grantsCommonHandlers.errorEnvelopeFromGrantError(error);
3083
+ if (mapped) {
3084
+ return res.status(mapped.statusCode).json(mapped.envelope);
3085
+ }
3086
+ return handleApiError(req, res, error, "Failed to create agent grant", "DB_QUERY_FAILED", "APIError:agents_grants_create");
3087
+ }
3088
+ });
3089
+ app.patch("/agents/grants/:id", async (req, res) => {
3090
+ try {
3091
+ const userId = await getAuthenticatedUserId(req, req.body?.user_id ?? undefined);
3092
+ const body = (req.body ?? {});
3093
+ const { updateGrantFields } = await import("./services/agent_grants.js");
3094
+ const updates = {};
3095
+ if (typeof body.label === "string")
3096
+ updates.label = body.label;
3097
+ if (Array.isArray(body.capabilities))
3098
+ updates.capabilities = body.capabilities;
3099
+ if (body.notes === null || typeof body.notes === "string")
3100
+ updates.notes = body.notes;
3101
+ if (body.match_sub === null || typeof body.match_sub === "string")
3102
+ updates.match_sub = body.match_sub;
3103
+ if (body.match_iss === null || typeof body.match_iss === "string")
3104
+ updates.match_iss = body.match_iss;
3105
+ if (body.match_thumbprint === null || typeof body.match_thumbprint === "string") {
3106
+ updates.match_thumbprint = body.match_thumbprint;
3107
+ }
3108
+ const grant = await updateGrantFields(userId, req.params.id, updates);
3109
+ return res.json({ grant });
3110
+ }
3111
+ catch (error) {
3112
+ const mapped = grantsCommonHandlers.errorEnvelopeFromGrantError(error);
3113
+ if (mapped) {
3114
+ return res.status(mapped.statusCode).json(mapped.envelope);
3115
+ }
3116
+ return handleApiError(req, res, error, "Failed to update agent grant", "DB_QUERY_FAILED", "APIError:agents_grants_update");
3117
+ }
3118
+ });
3119
+ async function setGrantStatusRoute(req, res, next) {
3120
+ try {
3121
+ const userId = await getAuthenticatedUserId(req, req.body?.user_id ?? undefined);
3122
+ const { setStatus } = await import("./services/agent_grants.js");
3123
+ const grant = await setStatus(userId, req.params.id, next);
3124
+ return res.json({ grant });
3125
+ }
3126
+ catch (error) {
3127
+ const mapped = grantsCommonHandlers.errorEnvelopeFromGrantError(error);
3128
+ if (mapped) {
3129
+ return res.status(mapped.statusCode).json(mapped.envelope);
3130
+ }
3131
+ return handleApiError(req, res, error, `Failed to ${next === "active" ? "restore" : next} agent grant`, "DB_QUERY_FAILED", `APIError:agents_grants_${next}`);
3132
+ }
3133
+ }
3134
+ app.post("/agents/grants/:id/suspend", (req, res) => setGrantStatusRoute(req, res, "suspended"));
3135
+ app.post("/agents/grants/:id/revoke", (req, res) => setGrantStatusRoute(req, res, "revoked"));
3136
+ app.post("/agents/grants/:id/restore", (req, res) => setGrantStatusRoute(req, res, "active"));
2915
3137
  // GET /api/agents/:key — One agent's identity + rollup counts
2916
3138
  // REQUIRES AUTHENTICATION
2917
3139
  app.get("/agents/:key", async (req, res) => {
@@ -3482,7 +3704,7 @@ app.post("/observations/create", async (req, res) => {
3482
3704
  return res.status(500).json(buildErrorEnvelope("DB_QUERY_FAILED", message));
3483
3705
  }
3484
3706
  });
3485
- async function storeStructuredForApi(params) {
3707
+ export async function storeStructuredForApi(params) {
3486
3708
  const { userId, entities, sourcePriority, observationSource, idempotencyKey, originalFilename, relationships, commit: commitInput, strict: strictInput, } = params;
3487
3709
  const commit = commitInput !== false;
3488
3710
  const strict = strictInput === true;
@@ -3501,6 +3723,21 @@ async function storeStructuredForApi(params) {
3501
3723
  enforceAgentCapability("create_relationship", entityTypes, capabilityCtx);
3502
3724
  }
3503
3725
  }
3726
+ // Protected-entity-types guard: governance state (`agent_grant`, etc.)
3727
+ // is gated by an explicit capability on the admitted grant. Mirrors
3728
+ // the same check made deep in `createObservation` so callers see a
3729
+ // structured `capability_denied` envelope before any writes.
3730
+ {
3731
+ const entityTypes = entities
3732
+ .map((entity) => entity?.entity_type)
3733
+ .filter((t) => typeof t === "string" && t.length > 0);
3734
+ assertCanWriteProtectedBatch({
3735
+ entity_types: entityTypes,
3736
+ op: "store_structured",
3737
+ identity: getCurrentAgentIdentity(),
3738
+ admission: getCurrentAAuthAdmission(),
3739
+ });
3740
+ }
3504
3741
  const { resolveEntityWithTrace, CanonicalNameUnresolvedError, MergeRefusedError } = await import("./services/entity_resolution.js");
3505
3742
  const { detectFlatPackedRows, FlatPackedRowsError } = await import("./services/flat_packed_detection.js");
3506
3743
  // R4: lazy import so the structured-store handler can attach required
@@ -3892,8 +4129,13 @@ async function storeUnstructuredForApi(params) {
3892
4129
  observations_created: 0,
3893
4130
  };
3894
4131
  }
3895
- // POST /api/store - Unified store for structured, unstructured, or combined payloads
3896
- app.post("/store", writeRateLimit, async (req, res) => {
4132
+ // Shared handler body for the unified /store endpoint. Extracted so that the
4133
+ // sandbox-only POST /sandbox/aauth-only/store route below can reuse the exact
4134
+ // same write semantics (validation, auth, structured + unstructured flows,
4135
+ // error envelopes) without duplicating logic. Both routes funnel through
4136
+ // getAuthenticatedUserId, which honors the α attribution partitioning applied
4137
+ // in the sandbox bypass above.
4138
+ async function handleStorePost(req, res) {
3897
4139
  const parsed = StoreRequestSchema.safeParse(req.body);
3898
4140
  if (!parsed.success) {
3899
4141
  logWarn("ValidationError:store", req, {
@@ -4009,7 +4251,29 @@ app.post("/store", writeRateLimit, async (req, res) => {
4009
4251
  const message = error instanceof Error ? error.message : "Failed to store payload";
4010
4252
  return sendError(res, 500, "DB_QUERY_FAILED", message);
4011
4253
  }
4012
- });
4254
+ }
4255
+ // POST /api/store - Unified store for structured, unstructured, or combined payloads
4256
+ app.post("/store", writeRateLimit, handleStorePost);
4257
+ // γ-write: sandbox-only route that EXPLICITLY requires a verified AAuth
4258
+ // signature before delegating to the same handleStorePost handler. Unlike the
4259
+ // global /store endpoint (which accepts bearer auth, sandbox public-user
4260
+ // fallback, OR AAuth-derived attribution), this route makes AAuth admission
4261
+ // the gate: unsigned requests get 401 here. The route is only registered when
4262
+ // isSandboxMode() is true, so it does not exist in production. Demo value:
4263
+ // shows identity-bound provenance — a write performed by an AAuth-signed
4264
+ // agent lands under the per-thumbprint user from α and is round-trippable via
4265
+ // /entities/query, /entities/:id, and /stats scoped to that same identity.
4266
+ if (isSandboxMode()) {
4267
+ const aauthRequired = (req, res, next) => {
4268
+ const aauthCtx = req.aauth;
4269
+ if (aauthCtx?.verified === true && typeof aauthCtx.thumbprint === "string") {
4270
+ return next();
4271
+ }
4272
+ return sendError(res, 401, "AAUTH_REQUIRED", "POST /sandbox/aauth-only/store requires a verified AAuth signature (RFC 9421 + aa-agent+jwt).");
4273
+ };
4274
+ app.post("/sandbox/aauth-only/store", writeRateLimit, aauthRequired, handleStorePost);
4275
+ logger.info("[Sandbox] AAuth-required write route enabled at POST /sandbox/aauth-only/store");
4276
+ }
4013
4277
  // POST /api/store/unstructured - Store raw file (base64), optional AI interpretation
4014
4278
  app.post("/store/unstructured", async (req, res) => {
4015
4279
  const parsed = StoreUnstructuredRequestSchema.safeParse(req.body);
@@ -5002,6 +5266,12 @@ app.post("/correct", async (req, res) => {
5002
5266
  if (correctCtx) {
5003
5267
  enforceAgentCapability("correct", [entity_type], correctCtx);
5004
5268
  }
5269
+ assertCanWriteProtectedBatch({
5270
+ entity_types: [entity_type],
5271
+ op: "correct",
5272
+ identity: getCurrentAgentIdentity(),
5273
+ admission: getCurrentAAuthAdmission(),
5274
+ });
5005
5275
  const { createCorrection } = await import("./services/correction.js");
5006
5276
  const result = await createCorrection({
5007
5277
  entity_id,
@@ -5067,6 +5337,7 @@ app.get("/session", async (req, res) => {
5067
5337
  identity,
5068
5338
  middlewareDecision: getAttributionDecisionFromRequest(req),
5069
5339
  rawClientInfoName: rawClientName,
5340
+ admission: getAAuthAdmissionFromRequest(req),
5070
5341
  });
5071
5342
  return res.json(session);
5072
5343
  }
@@ -5353,7 +5624,16 @@ app.get("/openapi_actions.yaml", (req, res) => {
5353
5624
  function tryListen(port) {
5354
5625
  return new Promise((resolve, reject) => {
5355
5626
  const server = app.listen(port, () => {
5356
- resolve({ server, port });
5627
+ // When `port === 0` the OS assigns an ephemeral port. We must report
5628
+ // the actually-bound port back to callers (the eval harness's
5629
+ // isolated server fixture writes it to NEOTOMA_SESSION_PORT_FILE so
5630
+ // the test harness can hit /health). Without this, a port file would
5631
+ // contain the literal "0" and the health probe would never connect.
5632
+ const addr = server.address();
5633
+ const boundPort = addr && typeof addr === "object" && typeof addr.port === "number"
5634
+ ? addr.port
5635
+ : port;
5636
+ resolve({ server, port: boundPort });
5357
5637
  });
5358
5638
  server.once("error", (err) => {
5359
5639
  server.close();
@@ -5363,6 +5643,26 @@ function tryListen(port) {
5363
5643
  }
5364
5644
  // Export function to start HTTP server (called explicitly, not on import)
5365
5645
  export async function startHTTPServer() {
5646
+ // Stronger AAuth Admission plan: capabilities now live on agent_grant
5647
+ // entities. If the legacy NEOTOMA_AGENT_CAPABILITIES_* env vars are
5648
+ // still set, fail fast with a structured pointer to the migration
5649
+ // command. See docs/subsystems/agent_attribution_integration.md.
5650
+ const { assertNoLegacyCapabilityEnv, LegacyAgentCapabilityEnvError } = await import("./services/agent_capabilities.js");
5651
+ try {
5652
+ assertNoLegacyCapabilityEnv();
5653
+ }
5654
+ catch (err) {
5655
+ if (err instanceof LegacyAgentCapabilityEnvError) {
5656
+ logger.error(JSON.stringify({
5657
+ event: "boot_failed_legacy_agent_capabilities_env",
5658
+ variables: err.variables,
5659
+ migration_command: err.migrationCommand,
5660
+ message: err.message,
5661
+ }));
5662
+ throw err;
5663
+ }
5664
+ throw err;
5665
+ }
5366
5666
  // Initialize encryption service
5367
5667
  await initServerKeys();
5368
5668
  // Sandbox mode: ensure the `sandbox_abuse_report` entity type is registered
@@ -5383,24 +5683,28 @@ export async function startHTTPServer() {
5383
5683
  const basePort = httpPortEnv ? parseInt(httpPortEnv, 10) : config.httpPort || 3080;
5384
5684
  const portFile = process.env.NEOTOMA_SESSION_PORT_FILE;
5385
5685
  const maxTries = 20;
5386
- for (let offset = 0; offset < maxTries; offset++) {
5686
+ // basePort === 0 means OS-assigned ephemeral port; retrying on EADDRINUSE
5687
+ // makes no sense (and would try port 1, 2, ...). The eval harness uses
5688
+ // ephemeral ports to avoid colliding with the operator's dev server.
5689
+ const triesLimit = basePort === 0 ? 1 : maxTries;
5690
+ for (let offset = 0; offset < triesLimit; offset++) {
5387
5691
  const port = basePort + offset;
5388
5692
  try {
5389
- const { server } = await tryListen(port);
5693
+ const { server, port: boundPort } = await tryListen(port);
5390
5694
  if (portFile) {
5391
- fs.writeFileSync(portFile, String(port), "utf-8");
5695
+ fs.writeFileSync(portFile, String(boundPort), "utf-8");
5392
5696
  }
5393
5697
  // eslint-disable-next-line no-console
5394
- console.log(`HTTP Actions listening on :${port}`);
5698
+ console.log(`HTTP Actions listening on :${boundPort}`);
5395
5699
  // Start background OAuth state cleanup job
5396
5700
  import("./services/mcp_oauth.js").then((oauth) => {
5397
5701
  oauth.startStateCleanupJob();
5398
5702
  });
5399
- return { server, port };
5703
+ return { server, port: boundPort };
5400
5704
  }
5401
5705
  catch (err) {
5402
5706
  const code = err?.code;
5403
- if (code === "EADDRINUSE" && offset < maxTries - 1) {
5707
+ if (code === "EADDRINUSE" && offset < triesLimit - 1) {
5404
5708
  continue;
5405
5709
  }
5406
5710
  throw err;