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.
- package/dist/actions.d.ts +74 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +324 -20
- package/dist/actions.js.map +1 -1
- package/dist/cli/aauth_tbs_attestation.d.ts +156 -0
- package/dist/cli/aauth_tbs_attestation.d.ts.map +1 -0
- package/dist/cli/aauth_tbs_attestation.js +188 -0
- package/dist/cli/aauth_tbs_attestation.js.map +1 -0
- package/dist/cli/aauth_tpm2_attestation.d.ts +144 -0
- package/dist/cli/aauth_tpm2_attestation.d.ts.map +1 -0
- package/dist/cli/aauth_tpm2_attestation.js +182 -0
- package/dist/cli/aauth_tpm2_attestation.js.map +1 -0
- package/dist/cli/aauth_yubikey_attestation.d.ts +194 -0
- package/dist/cli/aauth_yubikey_attestation.d.ts.map +1 -0
- package/dist/cli/aauth_yubikey_attestation.js +217 -0
- package/dist/cli/aauth_yubikey_attestation.js.map +1 -0
- package/dist/cli/agents_grants_import.d.ts +58 -0
- package/dist/cli/agents_grants_import.d.ts.map +1 -0
- package/dist/cli/agents_grants_import.js +283 -0
- package/dist/cli/agents_grants_import.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +35 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/packs.d.ts +15 -0
- package/dist/cli/packs.d.ts.map +1 -0
- package/dist/cli/packs.js +156 -0
- package/dist/cli/packs.js.map +1 -0
- package/dist/crypto/agent_identity.d.ts +66 -9
- package/dist/crypto/agent_identity.d.ts.map +1 -1
- package/dist/crypto/agent_identity.js +22 -8
- package/dist/crypto/agent_identity.js.map +1 -1
- package/dist/middleware/aauth_admission.d.ts +30 -0
- package/dist/middleware/aauth_admission.d.ts.map +1 -0
- package/dist/middleware/aauth_admission.js +116 -0
- package/dist/middleware/aauth_admission.js.map +1 -0
- package/dist/middleware/aauth_verify.d.ts.map +1 -1
- package/dist/middleware/aauth_verify.js +92 -8
- package/dist/middleware/aauth_verify.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +16 -1
- package/dist/server.js.map +1 -1
- package/dist/services/aauth_admission.d.ts +49 -0
- package/dist/services/aauth_admission.d.ts.map +1 -0
- package/dist/services/aauth_admission.js +110 -0
- package/dist/services/aauth_admission.js.map +1 -0
- package/dist/services/aauth_attestation_apple_se.d.ts +36 -0
- package/dist/services/aauth_attestation_apple_se.d.ts.map +1 -0
- package/dist/services/aauth_attestation_apple_se.js +208 -0
- package/dist/services/aauth_attestation_apple_se.js.map +1 -0
- package/dist/services/aauth_attestation_revocation.d.ts +160 -0
- package/dist/services/aauth_attestation_revocation.d.ts.map +1 -0
- package/dist/services/aauth_attestation_revocation.js +770 -0
- package/dist/services/aauth_attestation_revocation.js.map +1 -0
- package/dist/services/aauth_attestation_tpm2.d.ts +61 -0
- package/dist/services/aauth_attestation_tpm2.d.ts.map +1 -0
- package/dist/services/aauth_attestation_tpm2.js +373 -0
- package/dist/services/aauth_attestation_tpm2.js.map +1 -0
- package/dist/services/aauth_attestation_trust_config.d.ts +42 -0
- package/dist/services/aauth_attestation_trust_config.d.ts.map +1 -0
- package/dist/services/aauth_attestation_trust_config.js +242 -0
- package/dist/services/aauth_attestation_trust_config.js.map +1 -0
- package/dist/services/aauth_attestation_verifier.d.ts +134 -0
- package/dist/services/aauth_attestation_verifier.d.ts.map +1 -0
- package/dist/services/aauth_attestation_verifier.js +167 -0
- package/dist/services/aauth_attestation_verifier.js.map +1 -0
- package/dist/services/aauth_attestation_webauthn_packed.d.ts +58 -0
- package/dist/services/aauth_attestation_webauthn_packed.d.ts.map +1 -0
- package/dist/services/aauth_attestation_webauthn_packed.js +442 -0
- package/dist/services/aauth_attestation_webauthn_packed.js.map +1 -0
- package/dist/services/aauth_operator_allowlist.d.ts +40 -0
- package/dist/services/aauth_operator_allowlist.d.ts.map +1 -0
- package/dist/services/aauth_operator_allowlist.js +69 -0
- package/dist/services/aauth_operator_allowlist.js.map +1 -0
- package/dist/services/aauth_tpm_structures.d.ts +103 -0
- package/dist/services/aauth_tpm_structures.d.ts.map +1 -0
- package/dist/services/aauth_tpm_structures.js +310 -0
- package/dist/services/aauth_tpm_structures.js.map +1 -0
- package/dist/services/activation/stage_zero_five.d.ts +62 -0
- package/dist/services/activation/stage_zero_five.d.ts.map +1 -0
- package/dist/services/activation/stage_zero_five.js +126 -0
- package/dist/services/activation/stage_zero_five.js.map +1 -0
- package/dist/services/activation/user_preference.d.ts +110 -0
- package/dist/services/activation/user_preference.d.ts.map +1 -0
- package/dist/services/activation/user_preference.js +98 -0
- package/dist/services/activation/user_preference.js.map +1 -0
- package/dist/services/activation/vertical_detection.d.ts +78 -0
- package/dist/services/activation/vertical_detection.d.ts.map +1 -0
- package/dist/services/activation/vertical_detection.js +219 -0
- package/dist/services/activation/vertical_detection.js.map +1 -0
- package/dist/services/agent_capabilities.d.ts +80 -90
- package/dist/services/agent_capabilities.d.ts.map +1 -1
- package/dist/services/agent_capabilities.js +161 -275
- package/dist/services/agent_capabilities.js.map +1 -1
- package/dist/services/agent_grants.d.ts +146 -0
- package/dist/services/agent_grants.d.ts.map +1 -0
- package/dist/services/agent_grants.js +580 -0
- package/dist/services/agent_grants.js.map +1 -0
- package/dist/services/attribution_policy.d.ts.map +1 -1
- package/dist/services/attribution_policy.js +2 -1
- package/dist/services/attribution_policy.js.map +1 -1
- package/dist/services/bundled_pages/html_shell.d.ts +118 -0
- package/dist/services/bundled_pages/html_shell.d.ts.map +1 -0
- package/dist/services/bundled_pages/html_shell.js +242 -0
- package/dist/services/bundled_pages/html_shell.js.map +1 -0
- package/dist/services/bundled_pages/tokens.d.ts +496 -0
- package/dist/services/bundled_pages/tokens.d.ts.map +1 -0
- package/dist/services/bundled_pages/tokens.js +261 -0
- package/dist/services/bundled_pages/tokens.js.map +1 -0
- package/dist/services/compliance/alerting.d.ts +123 -0
- package/dist/services/compliance/alerting.d.ts.map +1 -0
- package/dist/services/compliance/alerting.js +169 -0
- package/dist/services/compliance/alerting.js.map +1 -0
- package/dist/services/compliance/historical_backfill.d.ts +74 -0
- package/dist/services/compliance/historical_backfill.d.ts.map +1 -0
- package/dist/services/compliance/historical_backfill.js +244 -0
- package/dist/services/compliance/historical_backfill.js.map +1 -0
- package/dist/services/compliance/renderer.d.ts +21 -0
- package/dist/services/compliance/renderer.d.ts.map +1 -0
- package/dist/services/compliance/renderer.js +208 -0
- package/dist/services/compliance/renderer.js.map +1 -0
- package/dist/services/compliance/routes.d.ts +55 -0
- package/dist/services/compliance/routes.d.ts.map +1 -0
- package/dist/services/compliance/routes.js +212 -0
- package/dist/services/compliance/routes.js.map +1 -0
- package/dist/services/compliance/scorecard.d.ts +98 -0
- package/dist/services/compliance/scorecard.d.ts.map +1 -0
- package/dist/services/compliance/scorecard.js +318 -0
- package/dist/services/compliance/scorecard.js.map +1 -0
- package/dist/services/conversation_turn.d.ts +85 -0
- package/dist/services/conversation_turn.d.ts.map +1 -0
- package/dist/services/conversation_turn.js +457 -0
- package/dist/services/conversation_turn.js.map +1 -0
- package/dist/services/correction.d.ts.map +1 -1
- package/dist/services/correction.js +8 -1
- package/dist/services/correction.js.map +1 -1
- package/dist/services/docs_bundle/frontmatter.d.ts +26 -0
- package/dist/services/docs_bundle/frontmatter.d.ts.map +1 -0
- package/dist/services/docs_bundle/frontmatter.js +50 -0
- package/dist/services/docs_bundle/frontmatter.js.map +1 -0
- package/dist/services/docs_bundle/loader.d.ts +28 -0
- package/dist/services/docs_bundle/loader.d.ts.map +1 -0
- package/dist/services/docs_bundle/loader.js +91 -0
- package/dist/services/docs_bundle/loader.js.map +1 -0
- package/dist/services/docs_bundle/render_html.d.ts +15 -0
- package/dist/services/docs_bundle/render_html.d.ts.map +1 -0
- package/dist/services/docs_bundle/render_html.js +48 -0
- package/dist/services/docs_bundle/render_html.js.map +1 -0
- package/dist/services/docs_bundle/types.d.ts +73 -0
- package/dist/services/docs_bundle/types.d.ts.map +1 -0
- package/dist/services/docs_bundle/types.js +50 -0
- package/dist/services/docs_bundle/types.js.map +1 -0
- package/dist/services/docs_install/loader.d.ts +25 -0
- package/dist/services/docs_install/loader.d.ts.map +1 -0
- package/dist/services/docs_install/loader.js +63 -0
- package/dist/services/docs_install/loader.js.map +1 -0
- package/dist/services/docs_install/renderer.d.ts +40 -0
- package/dist/services/docs_install/renderer.d.ts.map +1 -0
- package/dist/services/docs_install/renderer.js +323 -0
- package/dist/services/docs_install/renderer.js.map +1 -0
- package/dist/services/docs_install/routes.d.ts +20 -0
- package/dist/services/docs_install/routes.d.ts.map +1 -0
- package/dist/services/docs_install/routes.js +117 -0
- package/dist/services/docs_install/routes.js.map +1 -0
- package/dist/services/feedback/mirror_local_to_entity.d.ts +75 -0
- package/dist/services/feedback/mirror_local_to_entity.d.ts.map +1 -0
- package/dist/services/feedback/mirror_local_to_entity.js +101 -0
- package/dist/services/feedback/mirror_local_to_entity.js.map +1 -0
- package/dist/services/feedback/neotoma_payload.d.ts +69 -0
- package/dist/services/feedback/neotoma_payload.d.ts.map +1 -0
- package/dist/services/feedback/neotoma_payload.js +181 -0
- package/dist/services/feedback/neotoma_payload.js.map +1 -0
- package/dist/services/inspector_mount.d.ts +90 -0
- package/dist/services/inspector_mount.d.ts.map +1 -0
- package/dist/services/inspector_mount.js +219 -0
- package/dist/services/inspector_mount.js.map +1 -0
- package/dist/services/local_auth.d.ts +13 -0
- package/dist/services/local_auth.d.ts.map +1 -1
- package/dist/services/local_auth.js +40 -2
- package/dist/services/local_auth.js.map +1 -1
- package/dist/services/oauth_pages/render.d.ts +25 -0
- package/dist/services/oauth_pages/render.d.ts.map +1 -0
- package/dist/services/oauth_pages/render.js +235 -0
- package/dist/services/oauth_pages/render.js.map +1 -0
- package/dist/services/observation_storage.d.ts.map +1 -1
- package/dist/services/observation_storage.js +8 -1
- package/dist/services/observation_storage.js.map +1 -1
- package/dist/services/protected_entity_types.d.ts +85 -0
- package/dist/services/protected_entity_types.d.ts.map +1 -0
- package/dist/services/protected_entity_types.js +120 -0
- package/dist/services/protected_entity_types.js.map +1 -0
- package/dist/services/request_context.d.ts +16 -0
- package/dist/services/request_context.d.ts.map +1 -1
- package/dist/services/request_context.js +8 -0
- package/dist/services/request_context.js.map +1 -1
- package/dist/services/root_landing/auth_overview.d.ts +60 -0
- package/dist/services/root_landing/auth_overview.d.ts.map +1 -0
- package/dist/services/root_landing/auth_overview.js +86 -0
- package/dist/services/root_landing/auth_overview.js.map +1 -0
- package/dist/services/root_landing/cli_overview.d.ts +34 -0
- package/dist/services/root_landing/cli_overview.d.ts.map +1 -0
- package/dist/services/root_landing/cli_overview.js +123 -0
- package/dist/services/root_landing/cli_overview.js.map +1 -0
- package/dist/services/root_landing/html_template.d.ts.map +1 -1
- package/dist/services/root_landing/html_template.js +2 -2
- package/dist/services/root_landing/html_template.js.map +1 -1
- package/dist/services/root_landing/http_api_overview.d.ts +34 -0
- package/dist/services/root_landing/http_api_overview.d.ts.map +1 -0
- package/dist/services/root_landing/http_api_overview.js +110 -0
- package/dist/services/root_landing/http_api_overview.js.map +1 -0
- package/dist/services/root_landing/mcp_overview.d.ts +34 -0
- package/dist/services/root_landing/mcp_overview.d.ts.map +1 -0
- package/dist/services/root_landing/mcp_overview.js +133 -0
- package/dist/services/root_landing/mcp_overview.js.map +1 -0
- package/dist/services/root_landing/site_nav.d.ts.map +1 -1
- package/dist/services/root_landing/site_nav.js +42 -1
- package/dist/services/root_landing/site_nav.js.map +1 -1
- package/dist/services/sandbox/inspector_redirect.d.ts +41 -0
- package/dist/services/sandbox/inspector_redirect.d.ts.map +1 -0
- package/dist/services/sandbox/inspector_redirect.js +59 -0
- package/dist/services/sandbox/inspector_redirect.js.map +1 -0
- package/dist/services/sandbox/pack_registry.d.ts +57 -0
- package/dist/services/sandbox/pack_registry.d.ts.map +1 -0
- package/dist/services/sandbox/pack_registry.js +101 -0
- package/dist/services/sandbox/pack_registry.js.map +1 -0
- package/dist/services/sandbox/seeder.d.ts +53 -0
- package/dist/services/sandbox/seeder.d.ts.map +1 -0
- package/dist/services/sandbox/seeder.js +98 -0
- package/dist/services/sandbox/seeder.js.map +1 -0
- package/dist/services/sandbox/sessions.d.ts +131 -0
- package/dist/services/sandbox/sessions.d.ts.map +1 -0
- package/dist/services/sandbox/sessions.js +326 -0
- package/dist/services/sandbox/sessions.js.map +1 -0
- package/dist/services/schema_definitions.d.ts.map +1 -1
- package/dist/services/schema_definitions.js +47 -0
- package/dist/services/schema_definitions.js.map +1 -1
- package/dist/services/session_info.d.ts +75 -0
- package/dist/services/session_info.d.ts.map +1 -1
- package/dist/services/session_info.js +48 -3
- package/dist/services/session_info.js.map +1 -1
- package/dist/services/verticals/baseline_metadata.d.ts +39 -0
- package/dist/services/verticals/baseline_metadata.d.ts.map +1 -0
- package/dist/services/verticals/baseline_metadata.js +394 -0
- package/dist/services/verticals/baseline_metadata.js.map +1 -0
- package/dist/services/verticals/entity_type_registry.d.ts +29 -0
- package/dist/services/verticals/entity_type_registry.d.ts.map +1 -0
- package/dist/services/verticals/entity_type_registry.js +169 -0
- package/dist/services/verticals/entity_type_registry.js.map +1 -0
- package/dist/services/verticals/install.d.ts +64 -0
- package/dist/services/verticals/install.d.ts.map +1 -0
- package/dist/services/verticals/install.js +262 -0
- package/dist/services/verticals/install.js.map +1 -0
- package/dist/services/verticals/registry.d.ts +131 -0
- package/dist/services/verticals/registry.d.ts.map +1 -0
- package/dist/services/verticals/registry.js +457 -0
- package/dist/services/verticals/registry.js.map +1 -0
- package/dist/shared/action_schemas.d.ts +10 -10
- package/dist/shared/contract_mappings.d.ts.map +1 -1
- package/dist/shared/contract_mappings.js +56 -0
- package/dist/shared/contract_mappings.js.map +1 -1
- package/dist/shared/openapi_types.d.ts +443 -7
- package/dist/shared/openapi_types.d.ts.map +1 -1
- package/openapi.yaml +423 -4
- 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;
|
package/dist/actions.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;
|
|
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
|
-
|
|
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 (
|
|
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
|
-
//
|
|
3896
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 :${
|
|
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 <
|
|
5707
|
+
if (code === "EADDRINUSE" && offset < triesLimit - 1) {
|
|
5404
5708
|
continue;
|
|
5405
5709
|
}
|
|
5406
5710
|
throw err;
|