nexus-ai-pro 2.12.0 → 2.14.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 (172) hide show
  1. package/API_STABILITY.md +55 -0
  2. package/CHANGELOG.md +112 -1
  3. package/README.md +16 -6
  4. package/dist/batch/anthropic.js +3 -1
  5. package/dist/batch/manager.d.ts +5 -0
  6. package/dist/batch/manager.js +25 -9
  7. package/dist/batch/openai-client.js +3 -1
  8. package/dist/cli/policy.d.ts +10 -0
  9. package/dist/cli/policy.js +116 -0
  10. package/dist/cli.js +15 -7
  11. package/dist/core/lifecycle.d.ts +25 -2
  12. package/dist/core/lifecycle.js +172 -6
  13. package/dist/core/nexus.d.ts +25 -15
  14. package/dist/core/nexus.js +72 -26
  15. package/dist/credentials/index.d.ts +179 -0
  16. package/dist/credentials/index.js +206 -0
  17. package/dist/embeddings/adapters.d.ts +3 -3
  18. package/dist/embeddings/adapters.js +16 -10
  19. package/dist/embeddings/batch-google.js +7 -1
  20. package/dist/embeddings/batch.d.ts +7 -0
  21. package/dist/embeddings/batch.js +32 -9
  22. package/dist/embeddings/engine.js +7 -2
  23. package/dist/embeddings/errors.d.ts +5 -1
  24. package/dist/embeddings/errors.js +3 -1
  25. package/dist/evaluate/emulate.d.ts +143 -0
  26. package/dist/evaluate/emulate.js +214 -0
  27. package/dist/images/google.js +6 -1
  28. package/dist/images/manager.js +3 -0
  29. package/dist/images/openai.js +4 -1
  30. package/dist/images/operation.js +1 -0
  31. package/dist/lifecycle/continue.d.ts +12 -0
  32. package/dist/lifecycle/continue.js +14 -0
  33. package/dist/media/index.d.ts +14 -1
  34. package/dist/media/index.js +16 -6
  35. package/dist/media/mistral.js +3 -1
  36. package/dist/policy/index.d.ts +184 -0
  37. package/dist/policy/index.js +490 -0
  38. package/dist/providers/anthropic.js +11 -5
  39. package/dist/providers/azure-openai.js +1 -0
  40. package/dist/providers/cohere.d.ts +3 -2
  41. package/dist/providers/cohere.js +6 -4
  42. package/dist/providers/google.d.ts +3 -2
  43. package/dist/providers/google.js +9 -7
  44. package/dist/providers/openai.js +17 -7
  45. package/dist/realtime/gemini-live.transport.js +3 -0
  46. package/dist/realtime/openai-websocket.transport.d.ts +1 -0
  47. package/dist/realtime/openai-websocket.transport.js +12 -7
  48. package/dist/realtime/session.js +8 -2
  49. package/dist/realtime/types.d.ts +8 -1
  50. package/dist/residency/errors.d.ts +18 -0
  51. package/dist/residency/errors.js +18 -0
  52. package/dist/residency/index.d.ts +93 -0
  53. package/dist/residency/index.js +225 -0
  54. package/dist/router/failover.d.ts +13 -0
  55. package/dist/router/failover.js +26 -4
  56. package/dist/server/http.js +3 -0
  57. package/dist/server/regions.d.ts +88 -0
  58. package/dist/server/regions.js +167 -0
  59. package/dist/server/runs.d.ts +18 -0
  60. package/dist/server/runs.js +21 -2
  61. package/dist/server/server.js +3 -2
  62. package/dist/telephony/manager.js +21 -15
  63. package/dist/telephony/providers/telnyx.d.ts +14 -13
  64. package/dist/telephony/providers/telnyx.js +36 -20
  65. package/dist/telephony/providers/twilio.d.ts +18 -13
  66. package/dist/telephony/providers/twilio.js +48 -24
  67. package/dist/types/batch.d.ts +5 -0
  68. package/dist/types/config.d.ts +8 -1
  69. package/dist/types/embeddings.d.ts +5 -0
  70. package/dist/types/images.d.ts +5 -0
  71. package/dist/types/lifecycle.d.ts +111 -0
  72. package/dist/types/residency.d.ts +78 -0
  73. package/dist/types/residency.js +1 -0
  74. package/dist/types/server.d.ts +6 -0
  75. package/dist/types/telephony.d.ts +26 -0
  76. package/dist/types/voice.d.ts +4 -0
  77. package/dist/utils/scrub.d.ts +12 -0
  78. package/dist/utils/scrub.js +49 -0
  79. package/dist/version.d.ts +1 -1
  80. package/dist/version.js +1 -1
  81. package/dist/video/google.js +11 -2
  82. package/dist/video/index.d.ts +10 -0
  83. package/dist/video/index.js +39 -14
  84. package/dist/video/runway.js +4 -1
  85. package/dist/voice/manager.js +14 -2
  86. package/dist/voice/providers/openai.d.ts +3 -2
  87. package/dist/voice/providers/openai.js +9 -10
  88. package/dist-cjs/batch/anthropic.js +3 -1
  89. package/dist-cjs/batch/manager.d.ts +5 -0
  90. package/dist-cjs/batch/manager.js +25 -9
  91. package/dist-cjs/batch/openai-client.js +3 -1
  92. package/dist-cjs/cli/policy.d.ts +10 -0
  93. package/dist-cjs/cli/policy.js +122 -0
  94. package/dist-cjs/core/lifecycle.d.ts +25 -2
  95. package/dist-cjs/core/lifecycle.js +174 -6
  96. package/dist-cjs/core/nexus.d.ts +25 -15
  97. package/dist-cjs/core/nexus.js +80 -34
  98. package/dist-cjs/credentials/index.d.ts +179 -0
  99. package/dist-cjs/credentials/index.js +219 -0
  100. package/dist-cjs/embeddings/adapters.d.ts +3 -3
  101. package/dist-cjs/embeddings/adapters.js +16 -10
  102. package/dist-cjs/embeddings/batch-google.js +7 -1
  103. package/dist-cjs/embeddings/batch.d.ts +7 -0
  104. package/dist-cjs/embeddings/batch.js +32 -9
  105. package/dist-cjs/embeddings/engine.js +7 -2
  106. package/dist-cjs/embeddings/errors.d.ts +5 -1
  107. package/dist-cjs/embeddings/errors.js +3 -1
  108. package/dist-cjs/evaluate/emulate.d.ts +143 -0
  109. package/dist-cjs/evaluate/emulate.js +221 -0
  110. package/dist-cjs/images/google.js +6 -1
  111. package/dist-cjs/images/manager.js +3 -0
  112. package/dist-cjs/images/openai.js +4 -1
  113. package/dist-cjs/images/operation.js +1 -0
  114. package/dist-cjs/lifecycle/continue.d.ts +12 -0
  115. package/dist-cjs/lifecycle/continue.js +17 -0
  116. package/dist-cjs/media/index.d.ts +14 -1
  117. package/dist-cjs/media/index.js +16 -6
  118. package/dist-cjs/media/mistral.js +3 -1
  119. package/dist-cjs/policy/index.d.ts +184 -0
  120. package/dist-cjs/policy/index.js +500 -0
  121. package/dist-cjs/providers/anthropic.js +11 -5
  122. package/dist-cjs/providers/azure-openai.js +1 -0
  123. package/dist-cjs/providers/cohere.d.ts +3 -2
  124. package/dist-cjs/providers/cohere.js +6 -4
  125. package/dist-cjs/providers/google.d.ts +3 -2
  126. package/dist-cjs/providers/google.js +9 -7
  127. package/dist-cjs/providers/openai.js +17 -7
  128. package/dist-cjs/realtime/gemini-live.transport.js +3 -0
  129. package/dist-cjs/realtime/openai-websocket.transport.d.ts +1 -0
  130. package/dist-cjs/realtime/openai-websocket.transport.js +12 -7
  131. package/dist-cjs/realtime/session.js +8 -2
  132. package/dist-cjs/realtime/types.d.ts +8 -1
  133. package/dist-cjs/residency/errors.d.ts +18 -0
  134. package/dist-cjs/residency/errors.js +22 -0
  135. package/dist-cjs/residency/index.d.ts +93 -0
  136. package/dist-cjs/residency/index.js +232 -0
  137. package/dist-cjs/router/failover.d.ts +13 -0
  138. package/dist-cjs/router/failover.js +26 -4
  139. package/dist-cjs/server/http.js +3 -0
  140. package/dist-cjs/server/regions.d.ts +88 -0
  141. package/dist-cjs/server/regions.js +170 -0
  142. package/dist-cjs/server/runs.d.ts +18 -0
  143. package/dist-cjs/server/runs.js +21 -2
  144. package/dist-cjs/server/server.js +3 -2
  145. package/dist-cjs/telephony/manager.js +21 -15
  146. package/dist-cjs/telephony/providers/telnyx.d.ts +14 -13
  147. package/dist-cjs/telephony/providers/telnyx.js +36 -20
  148. package/dist-cjs/telephony/providers/twilio.d.ts +18 -13
  149. package/dist-cjs/telephony/providers/twilio.js +48 -24
  150. package/dist-cjs/types/batch.d.ts +5 -0
  151. package/dist-cjs/types/config.d.ts +8 -1
  152. package/dist-cjs/types/embeddings.d.ts +5 -0
  153. package/dist-cjs/types/images.d.ts +5 -0
  154. package/dist-cjs/types/lifecycle.d.ts +111 -0
  155. package/dist-cjs/types/residency.d.ts +78 -0
  156. package/dist-cjs/types/residency.js +2 -0
  157. package/dist-cjs/types/server.d.ts +6 -0
  158. package/dist-cjs/types/telephony.d.ts +26 -0
  159. package/dist-cjs/types/voice.d.ts +4 -0
  160. package/dist-cjs/utils/scrub.d.ts +12 -0
  161. package/dist-cjs/utils/scrub.js +53 -0
  162. package/dist-cjs/version.d.ts +1 -1
  163. package/dist-cjs/version.js +1 -1
  164. package/dist-cjs/video/google.js +11 -2
  165. package/dist-cjs/video/index.d.ts +10 -0
  166. package/dist-cjs/video/index.js +39 -14
  167. package/dist-cjs/video/runway.js +4 -1
  168. package/dist-cjs/voice/manager.js +14 -2
  169. package/dist-cjs/voice/providers/openai.d.ts +3 -2
  170. package/dist-cjs/voice/providers/openai.js +9 -10
  171. package/llms.txt +4 -2
  172. package/package.json +66 -1
package/API_STABILITY.md CHANGED
@@ -41,6 +41,61 @@ Both are covered by this policy:
41
41
  Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
42
42
  one of its explicit subpaths.
43
43
 
44
+ ## 2.14 stage (2.14.0)
45
+
46
+ Everything 2.14 adds is stable from the start and additive, unless this section says otherwise.
47
+
48
+ New and stable:
49
+ - `nexus-ai-pro/policy`: `parsePolicy()`, `applyPolicy()`, `comparePolicies()`, `checkPolicyChange()`,
50
+ `providerGate()`, `policyHash()`, `PolicyError`, `Policy`, `PolicyDocument`, `PolicyAccess`,
51
+ `PolicyRouting`, `PolicyBudgets`, `PolicyResidency`, `PolicyCredentials`, `PolicyIssue`,
52
+ `ApplyPolicyOptions`, `PolicyChange`, `PolicyChangeKind`, and `PolicyCheck`;
53
+ - `LifecycleConfig.providers` and `policy`, `ProviderGate`, `PolicyReference`,
54
+ `OperationDescriptor.policy`, `AuditLogEvent.policy`, and `OperationLifecycle.access()`;
55
+ - the `nexus policy check` command, its flags, its exit codes, and its `--json` output;
56
+ - `nexus-ai-pro/evaluate/emulate`: `emulatedTools()`, `EmulatedToolsOptions`, `EmulatorClient`,
57
+ `EmulatedToolCall`, `EmulatedResult`, `EmulationStore`, `MemoryEmulationStore`, `EmulationError`, and
58
+ `emulationKey()`. What the emulator is told may be reworded in a minor release; a store's keys never
59
+ change within a major;
60
+ - `CallResidency.refusal()`, optional, and the optional `model` its `allows()`, `place()`, and
61
+ `check()` take;
62
+ - `OperationLifecycle.callContext()`, the optional `OperationLifecycleLike.callContext`, and
63
+ `ContinuedCallContext`;
64
+ - `tenantId` on `BatchSubmitRequest`, `EmbeddingBatchSubmitRequest`, `MediaReadRequest`,
65
+ `MediaDescribeRequest`, `MediaAnswerRequest`, `TranscriptionRequest`, `SpeechRequest`, and every
66
+ telephony request that takes a `signal`; `MediaCallContext.tenantId` and `credential`;
67
+ `RealtimeSessionConfig.credential`; the optional context the OpenAI voice adapter and the Twilio and
68
+ Telnyx adapters take on each method that calls their API.
69
+
70
+ The kinds `comparePolicies()` gives a change may be refined in a minor release: a change found to widen
71
+ what a tenant may reach is reported as `widened` from then on, so a check may start to ask for an
72
+ acknowledgment it did not ask for before. A check never stops asking for one it asked for.
73
+
74
+ ## 2.13 stage (2.13.0)
75
+
76
+ Everything 2.13 adds is stable from the start and additive, unless this section says otherwise.
77
+
78
+ New and stable:
79
+ - `nexus-ai-pro/server/regions`: `regionalGateway()`, `RegionalGateway`, `RegionalGatewayOptions`,
80
+ `RegionTarget`, `RegionHealth`, and `RegionHealthOptions`;
81
+ - `RunManagerOptions.region` and `residency`, as `AgentServerOptions` carries them; `ThreadRecord.region`,
82
+ `RunRecord.region`, and `ReplicaReport.region`; a regional server's ids start with its region;
83
+ - `nexus-ai-pro/residency`: `residency()`, `Residency`, `ResidencyOptions`, `RegionRule`,
84
+ `RegionDefinition`, `ResidencyPolicy`, `ResidencyDecision`, `ResidencyTarget`, `ResidencyPlacement`,
85
+ `CallResidency`, `ResidencyError`, `regionalTraceStore()`, `RegionalTraceStore`,
86
+ `RegionalTraceStoreOptions`, `regionalAssetStore()`, and `RegionalAssetStoreOptions`;
87
+ - `LifecycleConfig.residency`, `ProviderCallContext.residency`, and `ImageRequestBase.tenantId`;
88
+ - `nexus-ai-pro/credentials`: `tenantCredentials()`, `TenantCredentials`, `TenantCredentialsOptions`,
89
+ `TenantCredentialStats`, `CredentialError`, `parseSecret()`, `credentialRequest()`, `SecretSource`,
90
+ `awsSecrets()`, `gcpSecrets()`, `azureSecrets()`, `vaultSecrets()`, `envSecrets()`,
91
+ `memorySecrets()`, `MemorySecrets`, `AwsSecretsClient`, `GcpSecretsClient`, `AzureSecretsClient`,
92
+ and `VaultSecretsOptions`;
93
+ - `LifecycleConfig.credentials`, `CredentialResolver`, `CredentialRequest`, and `ProviderCredential`;
94
+ `ProviderCallContext.tenantId` and `credential`; `OperationTicket.refreshCredential()` and
95
+ `scrub()`;
96
+ - `OpenAIProviderConfig.credentialHeader`, `EmbeddingRequest.tenantId`, and
97
+ `EmbeddingProviderError.attempts`.
98
+
44
99
  ## 2.12 stage (2.12.0)
45
100
 
46
101
  Everything 2.12 adds is stable from the start and additive, unless this section says otherwise.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,115 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.14.0] - 2026-10-19
8
+
9
+ Policy you can review, evaluation without side effects, and credentials per tenant in every family.
10
+
11
+ ### Added
12
+
13
+ - **Policy you can review.** `nexus-ai-pro/policy` reads guardrails, routing, budgets, residency, and
14
+ credentials from one versioned file. `parsePolicy()` validates it as the client starts, naming every
15
+ issue; `applyPolicy()` turns it into the client's configuration; and every operation and audit event
16
+ records the policy's name, version, and content hash. `nexus policy check` compares a version with
17
+ the one before it, a file or `git:<ref>`, and fails the build on a change that widens what a tenant
18
+ may reach (a region, a provider, a model, a budget, lowered guardrails, a shared key) until it is
19
+ acknowledged in the file or with `--accept`, and on content changed without a new version.
20
+ - **Per-tenant routing rules.** `LifecycleConfig.providers` takes a `ProviderGate` that admits or
21
+ refuses each provider and model an operation would reach, checked wherever residency is: a refused
22
+ provider is refused with an `OperationDeniedError` naming the rule before anything is sent, a
23
+ completion's refused attempt is dropped from its route, and an embedding never falls back to a
24
+ refused model. `providerGate()` builds one from a policy's routing rules, with `*` wildcards.
25
+ - **Evaluation without side effects.** `emulatedTools()`, on the new `nexus-ai-pro/evaluate/emulate`,
26
+ returns an agent's tools answered by a model playing each one, told the tool, its result schema, a
27
+ scenario, real calls, and the run's earlier calls, and never run, so an evaluation of an agent that
28
+ charges cards or writes to a database sends nothing. Answers are kept by tool, arguments, scenario,
29
+ and call, and replayed, so a suite runs the same answers in CI without a model; tools that change
30
+ nothing can run for real.
31
+ - **Credentials per tenant in every family.** Video, media understanding, completion and embedding
32
+ batches, voice, telephony, and realtime sessions send the tenant's key, resolved by
33
+ `LifecycleConfig.credentials`, in place of the configured one, as completions, embeddings, and images
34
+ have since 2.13. Each takes `tenantId` on its requests. A video job's or a batch's ref records its
35
+ tenant, so `status()`, `resume()`, `cancel()`, and `download()` use the tenant's key and regions from
36
+ any process, through the new `OperationLifecycle.callContext()`. A Twilio tenant's calls go out on its
37
+ subaccount; an OpenAI SIP call keeps its project's key.
38
+ - `ai.lifecycle.access()` returns the `CallResidency` an operation would get, and
39
+ `CallResidency.refusal()` says why a provider would be refused once placed on its twin; `allows()`,
40
+ `place()`, and `check()` take the model as well.
41
+
42
+ ### Fixed
43
+
44
+ - `VideoManager.start()` and the calls on a video ref, and `EmbeddingBatchManager.start()` and the
45
+ calls on its refs, ran outside the client's residency policy; they are held to the tenant's regions
46
+ now. A durable video generation ran its operation without its tenant, so a tenant's budget and rate
47
+ limit did not apply to it.
48
+
49
+ ## [2.13.0] - 2026-10-19
50
+
51
+ Regions and residency, and credentials per tenant.
52
+
53
+ ### Added
54
+
55
+ - **Agent servers across regions.** `regionalGateway()`, on the new `nexus-ai-pro/server/regions`, sits
56
+ in front of one agent server per region. A thread or run goes to the region its id names and waits
57
+ there while that region is down, since its data is there and nowhere else; new work goes to the
58
+ tenant's home region and fails over only to another region the tenant may reach; a tenant that may
59
+ reach no region served is refused with a 403 naming the rule. Regions are checked through their
60
+ `/health`, marked down by failed requests, and taken out by an operator with `markDown()`. An agent
61
+ server given `region` stamps it on its threads, runs, ids, replica report, and health, and, given
62
+ `residency`, refuses tenants that may not reach it.
63
+ - **Residency: each tenant's requests and data kept in its regions.** `residency()`, on the new
64
+ `nexus-ai-pro/residency`, says which regions each tenant may reach (a map, or a function over the
65
+ tenant's record), which providers run in each region, and each provider's twin inside a region.
66
+ `LifecycleConfig.residency` applies it to every family: a completion or stream, its fallbacks and
67
+ its context summary included, is sent to the provider routing chose when that runs inside the
68
+ tenant's regions and to its twin there when not; an embedding never falls back outside them; and a
69
+ provider anything would reach outside them is refused with a `ResidencyError` naming the rule,
70
+ before anything is sent. `regionalTraceStore()` and `regionalAssetStore()` write each tenant's
71
+ traces, every run of a trace together, and assets in its home region, and read them only from its
72
+ regions.
73
+ - **Credentials per tenant.** `LifecycleConfig.credentials` resolves each provider's key per tenant,
74
+ when a call is made, in every family that runs through the lifecycle: completions, streams,
75
+ embeddings, and images send it in place of the configured key. `tenantCredentials()`, on the new
76
+ `nexus-ai-pro/credentials`, reads it from a secret source, cached for `cacheMs`, shared by
77
+ concurrent calls, carried by the last key while the source is down for up to `staleMs`, and
78
+ refusing a tenant with no key unless told to fall back. Sources for AWS Secrets Manager, Google
79
+ Secret Manager, Azure Key Vault, HashiCorp Vault, the environment, and memory, each over the
80
+ vendor's own client with no dependency.
81
+ - A completion or stream whose provider refuses its credential is resolved again and made once more
82
+ on the new key; in any other family, the next operation reads the key again. Every key and header
83
+ value an operation resolves is removed from the errors it raises, before an audit event, a log,
84
+ a trace, or the health report sees them.
85
+ - `ProviderCallContext.residency`, the operation's `CallResidency`; `ImageRequestBase.tenantId`; and
86
+ an agent server's trace root records its run's tenant as `metadata.nexus.tenant`.
87
+ - `ProviderCallContext.tenantId` and `credential()`; `OperationTicket.refreshCredential()` and
88
+ `scrub()`; `OpenAIProviderConfig.credentialHeader`; `EmbeddingRequest.tenantId`; and
89
+ `EmbeddingProviderError.attempts`, each attempt's error.
90
+
91
+ ### Proof
92
+
93
+ - `tests/regions.test.ts`: the proof that closes 2.13. Three regions, each an agent server with its own
94
+ state, traces, and provider, behind a gateway, with every tenant's key from one secret manager. A run
95
+ of four model calls has its key rotated and revoked after the second; the third and fourth are
96
+ answered on the new key. Then the EU tenant's home region goes down: its new work fails over to the
97
+ other EU region, its thread waits for its own, a tenant held to that region waits too, and nothing
98
+ of either reaches the US: no thread, run, trace, or provider call. Without region ids, the server's
99
+ check, a thread's region kept, new work held to the tenant's regions, failover, checks, the
100
+ operator's mark, or the gateway's own refusal, a test fails.
101
+ - `tests/residency.test.ts`: against a provider server per region, a tenant pinned to the EU is
102
+ answered by the EU twin of the provider routing chose, completions and streams alike, and nothing
103
+ of it reaches the US; a provider with no twin there is refused with the rule, nothing sent; an
104
+ embedding whose EU provider fails never falls back to the US; an image provider outside the regions
105
+ is refused; and each tenant's traces and assets land in its home region and are read only from its
106
+ regions. Without placement, twins, the decision, the routed check, the fallback filter, the image
107
+ check, traces kept together, the tenant view, the asset home, or the rule checks, a test fails.
108
+ - `tests/tenant-credentials.test.ts`: against a provider of the test's own that takes only live keys,
109
+ each tenant's calls carry its own key through completions, streams, and embeddings; in a run of five
110
+ calls whose key is rotated and revoked after the second, the third is answered on the new key and
111
+ every call after uses it; a key the provider quotes in its refusal reaches no error, stack, cause,
112
+ audit event, log, trace, or health report. Without the call's credential, the refresh, its limit,
113
+ either scrub, an adapter's use of the key, the cache's expiry, its single read, its stale key, or
114
+ its refusal, a test fails.
115
+
7
116
  ## [2.12.0] - 2026-10-18
8
117
 
9
118
  Observability that loses nothing, and media you can evaluate.
@@ -2879,7 +2988,9 @@ shared policy vocabulary is in place for a future release that revisits this.
2879
2988
  - URL fetching rejects unsafe private-network targets and limits response reads.
2880
2989
  - CLI and audit findings no longer disclose detected secret values.
2881
2990
 
2882
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.12.0...HEAD
2991
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.14.0...HEAD
2992
+ [2.14.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.13.0...v2.14.0
2993
+ [2.13.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.12.0...v2.13.0
2883
2994
  [2.12.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.11.0...v2.12.0
2884
2995
  [2.11.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.10.0...v2.11.0
2885
2996
  [2.10.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.9.0...v2.10.0
package/README.md CHANGED
@@ -141,7 +141,7 @@ from the doc comments. The guides live in the repository, so these links go to G
141
141
  | [MCP](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/mcp.md) | Borrowing tools from MCP servers, a registry of many, and serving your own |
142
142
  | [Agent protocols](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/protocols.md) | Serving an agent to user interfaces over AG-UI, to other agents over A2A, and to code editors over ACP, and calling remote agents as tools |
143
143
  | [Traces](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/tracing.md) | Run trees, queries, feedback, comparison, and alerts |
144
- | [Evaluation](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/evaluation.md) | Datasets, evaluators, experiments, comparisons with a verdict, review queues, LLM judges |
144
+ | [Evaluation](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/evaluation.md) | Datasets, evaluators, experiments, comparisons with a verdict, review queues, LLM judges, and tools emulated by a model so an evaluation sends nothing |
145
145
  | [Context hub](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/context-hub.md) | Prompts, instructions, tools, and skills versioned together, promoted through gates, and moved between projects |
146
146
  | [Insights](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/insights.md) | Failing and slow runs clustered, regressions between time windows, and evaluated fix proposals |
147
147
  | [Prompts](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/prompts.md) | Typed templates, content versions, gated promotion, rollback, A/B splits, serving through outages |
@@ -167,6 +167,8 @@ from the doc comments. The guides live in the repository, so these links go to G
167
167
  | [Agent server](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/server.md) | Self-hosted HTTP server for assistants: threads, durable runs, resumable streams, cron, a worker queue, and metrics |
168
168
  | [Deployments](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/deployments.md) | Revisions and canaries with automatic rollback, autoscaling on Kubernetes and Helm, and per-tenant limits |
169
169
  | [Tenancy](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/tenancy.md) | One tenant model: a tenant's view of every store, tenant graph runs, and the isolation suite |
170
+ | [Policy](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/policy.md) | Guardrails, routing, budgets, residency, and credentials as one versioned file, validated as the client starts, checked by `nexus policy check` in CI, and recorded on every operation |
171
+ | [Regions and credentials](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/regions.md) | Agent servers across regions behind a gateway, residency rules that keep each tenant's requests, traces, and assets in its regions, and each tenant's provider keys from a secret manager |
170
172
  | [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
171
173
  | [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
172
174
  | [Packaging and install weight](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) | Every entry point and what it costs |
@@ -307,14 +309,22 @@ Shipped so far:
307
309
  - 2.12 observability that loses nothing, and media you can evaluate: spans spooled through a collector
308
310
  outage, OTLP over gRPC, deployment evaluation that reads each run's whole trace, evaluators that
309
311
  read and judge images, documents, audio, and video, a catalog of every stored asset, and automation
310
- rules that roll a canary back when a score falls.
312
+ rules that roll a canary back when a score falls;
313
+ - 2.13 regions and residency, and credentials per tenant: agent servers across regions behind a
314
+ gateway that fails over only within a tenant's regions, residency rules that keep each tenant's
315
+ provider calls, traces, and assets in its regions, and each tenant's provider keys from a secret
316
+ manager, rotated without a restart and kept out of every error;
317
+ - 2.14 policy you can review, and evaluation without side effects: guardrails, routing, budgets,
318
+ residency, and credentials as one versioned file that `nexus policy check` compares with the version
319
+ before it in CI and every operation records, tools emulated by a model so an evaluation sends
320
+ nothing, and each tenant's key in every family.
311
321
 
312
322
  Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
313
323
  [MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
314
- Next, 2.13 runs agents across regions, keeps each tenant's requests and data in the region they
315
- require, and resolves provider keys per tenant from a secret manager. Pinecone, images, the Gemini
316
- embedding batch tier, media understanding, video, Gemini Live, SIP, and both phone providers graduate
317
- once the live workflow passes against each. The
324
+ Next, 2.15 publishes a compatibility record for community adapters: the contract suite each passed,
325
+ against which version, with its benchmark, on one page. Pinecone, images, the Gemini embedding batch
326
+ tier, media understanding, video, Gemini Live, SIP, and both phone providers graduate once the live
327
+ workflow passes against each. The
318
328
  [roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists what comes after, in the order
319
329
  applications ask for it.
320
330
 
@@ -127,13 +127,15 @@ export class AnthropicBatchProvider {
127
127
  async send(path, init, context) {
128
128
  const fetchImpl = this.options.fetch ?? fetch;
129
129
  const baseUrl = (this.options.baseUrl || 'https://api.anthropic.com/v1').replace(/\/$/, '');
130
+ const credential = await context.credential?.(this.info.name);
130
131
  const response = await fetchImpl(`${baseUrl}${path}`, {
131
132
  ...init,
132
133
  headers: {
133
- 'x-api-key': this.options.apiKey,
134
+ 'x-api-key': credential?.apiKey ?? this.options.apiKey,
134
135
  'anthropic-version': this.options.version ?? '2023-06-01',
135
136
  'content-type': 'application/json',
136
137
  ...this.options.headers,
138
+ ...credential?.headers,
137
139
  ...(init.headers ?? {}),
138
140
  },
139
141
  signal: context.signal,
@@ -60,6 +60,11 @@ export declare class BatchManager {
60
60
  status(ref: BatchJobRef): Promise<BatchJobState>;
61
61
  /** Cancels a batch. Throws when the provider cannot cancel. */
62
62
  cancel(ref: BatchJobRef): Promise<BatchJobState>;
63
+ /**
64
+ * A call on a batch outside its operation, as the tenant its ref records, or `tenantId` for a ref
65
+ * made before refs recorded one: with the tenant's key, inside its regions.
66
+ */
67
+ private continuing;
63
68
  private collect;
64
69
  /**
65
70
  * Prices a finished batch at the provider's discounted rate.
@@ -2,6 +2,7 @@ import { OperationRunner } from '../operations/runner.js';
2
2
  import { estimateCost } from '../optimizer/cost.js';
3
3
  import { DEFAULT_CURRENCY } from '../optimizer/cost.js';
4
4
  import { generateRequestId } from '../utils/ids.js';
5
+ import { continuing } from '../lifecycle/continue.js';
5
6
  import { withLifecycle } from '../lifecycle/run.js';
6
7
  import { BatchCapabilityError, BatchProviderNotFoundError, BatchValidationError } from './errors.js';
7
8
  import { pollUntilSettled } from './poll.js';
@@ -46,6 +47,7 @@ export class BatchManager {
46
47
  operation: 'batch.submit',
47
48
  requestId,
48
49
  provider: providerName,
50
+ ...(request.tenantId !== undefined ? { tenantId: request.tenantId } : {}),
49
51
  metadata: { items: request.items.length },
50
52
  }, {
51
53
  signal: operation.signal,
@@ -60,7 +62,7 @@ export class BatchManager {
60
62
  };
61
63
  let ref = operation.previousHeartbeat?.batchRef;
62
64
  if (!ref) {
63
- ref = await provider.submit(request, context);
65
+ ref = withTenant(await provider.submit(request, context), request.tenantId);
64
66
  await operation.heartbeat({ batchRef: ref });
65
67
  }
66
68
  operation.report({ completed: 0, total: request.items.length, message: `submitted as ${ref.id}` });
@@ -77,34 +79,41 @@ export class BatchManager {
77
79
  const provider = this.providers.get(ref.provider);
78
80
  if (!provider)
79
81
  throw new BatchProviderNotFoundError(ref.provider);
80
- const context = {
82
+ return this.continuing('batch.collect', ref, options.tenantId, (continued) => this.collect(provider, ref, options, undefined, {
83
+ ...continued,
81
84
  requestId: generateRequestId(),
82
85
  signal: options.signal ?? new AbortController().signal,
83
86
  attempt: 1,
84
- };
85
- return this.collect(provider, ref, options, undefined, context);
87
+ }));
86
88
  }
87
89
  async status(ref) {
88
90
  const provider = this.providers.get(ref.provider);
89
91
  if (!provider)
90
92
  throw new BatchProviderNotFoundError(ref.provider);
91
- return provider.poll(ref, {
93
+ return this.continuing('batch.status', ref, undefined, (continued) => provider.poll(ref, {
94
+ ...continued,
92
95
  requestId: generateRequestId(),
93
96
  signal: new AbortController().signal,
94
97
  attempt: 1,
95
- });
98
+ }));
96
99
  }
97
100
  async cancel(ref) {
98
101
  const provider = this.providers.get(ref.provider);
99
102
  if (!provider)
100
103
  throw new BatchProviderNotFoundError(ref.provider);
101
- if (!provider.cancel)
104
+ const cancel = provider.cancel;
105
+ if (!cancel)
102
106
  throw new BatchCapabilityError(ref.provider, 'cancel');
103
- return provider.cancel(ref, {
107
+ return this.continuing('batch.cancel', ref, undefined, (continued) => cancel.call(provider, ref, {
108
+ ...continued,
104
109
  requestId: generateRequestId(),
105
110
  signal: new AbortController().signal,
106
111
  attempt: 1,
107
- });
112
+ }));
113
+ }
114
+ continuing(operation, ref, tenantId, call) {
115
+ const tenant = tenantOf(ref) ?? tenantId;
116
+ return continuing(this.runtime.lifecycle, { family: 'batch', operation, provider: ref.provider, ...(tenant !== undefined ? { tenantId: tenant } : {}) }, call);
108
117
  }
109
118
  async collect(provider, ref, request, operation, context) {
110
119
  const state = await pollUntilSettled(() => provider.poll(ref, context), {
@@ -230,3 +239,10 @@ function sumUsage(items) {
230
239
  }
231
240
  return { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens };
232
241
  }
242
+ function withTenant(ref, tenantId) {
243
+ return tenantId === undefined ? ref : { ...ref, metadata: { ...ref.metadata, batch: { tenantId } } };
244
+ }
245
+ function tenantOf(ref) {
246
+ const notes = ref.metadata?.batch;
247
+ return typeof notes?.tenantId === 'string' ? notes.tenantId : undefined;
248
+ }
@@ -84,10 +84,12 @@ export class OpenAIBatchClient {
84
84
  async send(path, init, context) {
85
85
  const fetchImpl = this.options.fetch ?? fetch;
86
86
  const baseUrl = (this.options.baseUrl || 'https://api.openai.com/v1').replace(/\/$/, '');
87
+ const credential = await context.credential?.('openai');
87
88
  const headers = {
88
- authorization: `Bearer ${this.options.apiKey}`,
89
+ authorization: `Bearer ${credential?.apiKey ?? this.options.apiKey}`,
89
90
  ...(this.options.organization ? { 'openai-organization': this.options.organization } : {}),
90
91
  ...this.options.headers,
92
+ ...credential?.headers,
91
93
  ...(init.headers ?? {}),
92
94
  };
93
95
  const response = await fetchImpl(`${baseUrl}${path}`, { ...init, headers, signal: context.signal });
@@ -0,0 +1,10 @@
1
+ import { type ParsedArgs } from './args.js';
2
+ /**
3
+ * `nexus policy check <policy.json> [--base <file>|git:<ref>] [--accept path,path] [--json]`.
4
+ *
5
+ * Validates the policy, and, given the version it replaces, lists every change: each that widens what
6
+ * a tenant may reach, or that needs a reviewer's eye, must be acknowledged in the file's
7
+ * `acknowledged` or with `--accept`, and content that changed must come with a new version. Exits 1
8
+ * when the policy is not valid or a change is not acknowledged.
9
+ */
10
+ export declare function runPolicyCommand(subcommand: string | undefined, args: ParsedArgs): Promise<void>;
@@ -0,0 +1,116 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { checkPolicyChange, PolicyError, parsePolicy } from '../policy/index.js';
6
+ import { CliUsageError, flagBool, listFlag, printTable, stringFlag, writeJson } from './args.js';
7
+ const run = promisify(execFile);
8
+ export async function runPolicyCommand(subcommand, args) {
9
+ if (subcommand !== 'check')
10
+ throw new CliUsageError(`Unknown policy command "${subcommand ?? ''}". Use check.`);
11
+ const [file] = args.positionals;
12
+ if (!file)
13
+ throw new CliUsageError('nexus policy check needs a policy file: nexus policy check policy.json [--base git:main].');
14
+ const json = flagBool(args.flags, 'json');
15
+ const base = stringFlag(args.flags, 'base');
16
+ const after = parse(await readFile(file, 'utf8'), file, json);
17
+ if (!after)
18
+ return;
19
+ if (base === undefined) {
20
+ report(json, { policy: after.reference, valid: true }, `${describe(after)} is valid.`);
21
+ return;
22
+ }
23
+ const text = await readBase(base, file);
24
+ if (text === undefined) {
25
+ report(json, { policy: after.reference, valid: true, base: null }, `${describe(after)} is valid. ${base} has no policy at ${file}: nothing to compare.`);
26
+ return;
27
+ }
28
+ const before = parse(text, base, json);
29
+ if (!before)
30
+ return;
31
+ const check = checkPolicyChange(before.document, after.document, { accept: listFlag(args.flags, 'accept') ?? [] });
32
+ if (!check.ok)
33
+ process.exitCode = 1;
34
+ if (json) {
35
+ writeJson({ policy: after.reference, valid: true, base: before.reference, ...check });
36
+ return;
37
+ }
38
+ console.log(`${describe(after)} is valid. Compared with ${before.name}@${before.version} from ${base}:`);
39
+ if (check.changes.length === 0)
40
+ console.log('No changes.');
41
+ else {
42
+ const pending = new Set(check.unacknowledged);
43
+ printTable(check.changes.map((change) => ({
44
+ kind: change.kind,
45
+ path: change.path,
46
+ review: change.kind === 'narrowed' ? '' : pending.has(change) ? 'NOT ACKNOWLEDGED' : 'acknowledged',
47
+ summary: change.summary,
48
+ })), ['kind', 'path', 'review', 'summary']);
49
+ }
50
+ if (check.versionUnchanged) {
51
+ console.log('');
52
+ console.log(`The content changed, but the version is still ${after.version}: raise it.`);
53
+ }
54
+ if (check.unacknowledged.length > 0) {
55
+ const paths = check.unacknowledged.map((change) => change.path);
56
+ console.log('');
57
+ console.log(`${paths.length} change${paths.length === 1 ? '' : 's'} need${paths.length === 1 ? 's' : ''} acknowledging: add ${paths.map((item) => `"${item}"`).join(', ')} to "acknowledged" in ${file}, or pass --accept ${paths.join(',')}.`);
58
+ }
59
+ }
60
+ function parse(text, source, json) {
61
+ try {
62
+ return parsePolicy(text);
63
+ }
64
+ catch (error) {
65
+ if (!(error instanceof PolicyError))
66
+ throw error;
67
+ process.exitCode = 1;
68
+ if (json)
69
+ writeJson({ source, valid: false, issues: error.issues });
70
+ else {
71
+ console.log(`${source} is not a valid policy:`);
72
+ for (const issue of error.issues)
73
+ console.log(` ${issue.path || '(document)'}: ${issue.message}`);
74
+ }
75
+ return undefined;
76
+ }
77
+ }
78
+ async function readBase(base, file) {
79
+ if (!base.startsWith('git:'))
80
+ return readFile(base, 'utf8');
81
+ const ref = base.slice(4);
82
+ if (!ref)
83
+ throw new CliUsageError('--base git: needs a ref, such as git:main or git:HEAD~1.');
84
+ const git = async (args) => {
85
+ try {
86
+ return (await run('git', args, { cwd: path.dirname(path.resolve(file)), maxBuffer: 16 * 1024 * 1024 })).stdout;
87
+ }
88
+ catch (error) {
89
+ const stderr = String(error.stderr ?? '').trim();
90
+ throw new Error(stderr || error.message);
91
+ }
92
+ };
93
+ try {
94
+ await git(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
95
+ }
96
+ catch (error) {
97
+ throw new Error(`git has no commit ${ref} where ${file} is: ${error.message || 'unknown ref'}`);
98
+ }
99
+ const object = `${ref}:./${path.basename(file)}`;
100
+ try {
101
+ await git(['cat-file', '-e', object]);
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ return git(['show', object]);
107
+ }
108
+ function describe(policy) {
109
+ return `Policy ${policy.name}@${policy.version} (sha256 ${policy.hash.slice(0, 12)})`;
110
+ }
111
+ function report(json, value, text) {
112
+ if (json)
113
+ writeJson(value);
114
+ else
115
+ console.log(text);
116
+ }
package/dist/cli.js CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import process from 'node:process';
2
+ import { readdir, readFile, stat } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
+ import process from 'node:process';
4
5
  import { pathToFileURL } from 'node:url';
5
- import { readdir, readFile, stat } from 'node:fs/promises';
6
- import { EvalRunner } from './evals/runner.js';
6
+ import { CliUsageError, flagBool, isRecord, numberFlag, parseArgs, printTable, stringFlag, writeJson, } from './cli/args.js';
7
7
  import { NexusAI } from './core/nexus.js';
8
- import { listKnownModels, listModelsForProvider, getModelCapabilities } from './models/registry.js';
8
+ import { EvalRunner } from './evals/runner.js';
9
+ import { getModelCapabilities, listKnownModels, listModelsForProvider } from './models/registry.js';
10
+ import { TokenOptimizer } from './optimizer/index.js';
9
11
  import { SecurityPipeline } from './security/index.js';
10
12
  import { redactSensitiveText } from './security/output-guard.js';
11
13
  import { UploadScanner } from './security/upload-scanner.js';
12
- import { TokenOptimizer } from './optimizer/index.js';
13
- import { CliUsageError, flagBool, isRecord, numberFlag, parseArgs, printTable, stringFlag, writeJson, } from './cli/args.js';
14
14
  const DEFAULT_SCAN_MAX_BYTES = 1_000_000;
15
15
  const IGNORED_DIRS = new Set(['.git', 'node_modules', 'dist', 'coverage', '.next', '.turbo']);
16
16
  async function main() {
@@ -78,6 +78,12 @@ async function main() {
78
78
  await runDeployCommand(subcommand, { positionals: args, flags: parsed.flags });
79
79
  return;
80
80
  }
81
+ case 'policy': {
82
+ const [subcommand, ...args] = parsed.positionals;
83
+ const { runPolicyCommand } = await import('./cli/policy.js');
84
+ await runPolicyCommand(subcommand, { positionals: args, flags: parsed.flags });
85
+ return;
86
+ }
81
87
  case 'optimize':
82
88
  await runOptimize(parsed);
83
89
  return;
@@ -431,6 +437,7 @@ Usage:
431
437
  nexus deploy canary <assistant> <revision> <percent> --url <server> [--reason r] [--version n]
432
438
  nexus deploy promote <assistant> <revision> --url <server> [--reason r] [--version n]
433
439
  nexus deploy rollback <assistant> [--to revision] --url <server> [--reason r] [--version n]
440
+ nexus policy check <policy.json> [--base <policy.json>|git:<ref>] [--accept path,path] [--json]
434
441
  nexus optimize [request.json|prompt.txt] [--model gpt-5-mini] [--max-input-tokens 4000] [--densify] [--json]
435
442
 
436
443
  Eval files can export { cases, client } or { cases, config }. JSON cases may use "expected", "contains", and "match": "includes" | "exact" | "regex".
@@ -442,7 +449,8 @@ Database modules for "db status" and "db migrate" export { client }, and optiona
442
449
  "doctor" exits 1 when a check fails, or with --strict when one warns. Its module exports any of { database, client, redis, operations, ai, deployment, close }.
443
450
  "deploy" reads NEXUS_SERVER_URL and NEXUS_SERVER_TOKEN when --url and --token are not given.
444
451
  "migrate" moves 1.x root imports to their 2.0 subpaths and reports removed options; --check exits 1 while any remain.
445
- "graph lint" reads a compiled graph's shape and exits 1 on an error finding, or on a warning with --strict.`);
452
+ "graph lint" reads a compiled graph's shape and exits 1 on an error finding, or on a warning with --strict.
453
+ "policy check" exits 1 when the policy is not valid, or, against --base, when a change that widens what a tenant may reach is not acknowledged.`);
446
454
  }
447
455
  main().catch((error) => {
448
456
  const message = error instanceof Error ? error.message : String(error);
@@ -1,8 +1,9 @@
1
- import type { RateLimitConfig } from '../types/config.js';
2
- import type { ProviderHealthOutcome, LifecycleConfig, LifecycleStage, OperationDescriptor, OperationLifecycleLike, OperationResultInfo, OperationStartOptions, OperationTicket, ProviderCallContext } from '../types/lifecycle.js';
3
1
  import type { AuditLogger } from '../ops/audit-logger.js';
4
2
  import type { MetricsCollector } from '../ops/metrics.js';
5
3
  import { type RateLimiter } from '../ops/rate-limiter.js';
4
+ import type { RateLimitConfig } from '../types/config.js';
5
+ import type { ContinuedCallContext, LifecycleConfig, LifecycleStage, OperationDescriptor, OperationLifecycleLike, OperationResultInfo, OperationStartOptions, OperationTicket, ProviderCallContext, ProviderGate, ProviderHealthOutcome } from '../types/lifecycle.js';
6
+ import type { CallResidency, ResidencyDecision, ResidencyPolicy } from '../types/residency.js';
6
7
  /** Every lifecycle stage, in the order an operation passes through them. */
7
8
  export declare const LIFECYCLE_STAGES: readonly LifecycleStage[];
8
9
  /** Raised when `lifecycle.authorize` refuses an operation, before any provider is called. */
@@ -104,6 +105,21 @@ export declare class OperationLifecycle implements OperationLifecycleLike {
104
105
  }, plan: OperationPlan<T>, options?: OperationStartOptions & {
105
106
  audit?: Record<string, unknown>;
106
107
  }): Promise<T>;
108
+ /**
109
+ * What a provider call that continues an operation admitted earlier is given, such as a video job
110
+ * polled after a restart or a batch collected by another worker: its tenant's credentials, resolved
111
+ * as `start()` resolves them, its residency, and a `scrub()` that keeps them out of its errors.
112
+ * Nothing is authorized, budgeted, or audited: the operation it continues was.
113
+ */
114
+ callContext(input: Omit<OperationDescriptor, 'requestId'> & {
115
+ requestId?: string;
116
+ }): Promise<ContinuedCallContext>;
117
+ /**
118
+ * Which providers and models an operation may reach, under the residency policy and the provider
119
+ * gate, or `undefined` when there is neither: what `start()` gives each operation's context, for
120
+ * code that decides outside one.
121
+ */
122
+ access(descriptor: Pick<OperationDescriptor, 'tenantId'> & Partial<OperationDescriptor>): Promise<CallResidency | undefined>;
107
123
  /**
108
124
  * Reports how a provider answered a call a family made itself, such as a realtime connection, to the
109
125
  * client's provider health and circuit breaker.
@@ -114,3 +130,10 @@ export declare class OperationLifecycle implements OperationLifecycleLike {
114
130
  private checkRateLimit;
115
131
  private authorize;
116
132
  }
133
+ /** An operation's residency, as the families that call providers check it. */
134
+ export declare function callResidency(policy: ResidencyPolicy, decision: CallResidency['decision']): CallResidency;
135
+ /**
136
+ * What an operation may reach: the providers its residency policy allows, and among them those the
137
+ * provider gate admits, model by model.
138
+ */
139
+ export declare function callAccess(descriptor: OperationDescriptor | undefined, policy: ResidencyPolicy | undefined, decision: ResidencyDecision | undefined, gate: ProviderGate | undefined): CallResidency;