@north-light/cli 0.3.421 → 0.3.423

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 (37) hide show
  1. package/dist/api/contract.d.ts +45 -0
  2. package/dist/api/contract.js +1 -0
  3. package/dist/api/error-codes.d.ts +10 -0
  4. package/dist/api/error-codes.js +1 -1
  5. package/dist/api/index.d.ts +2 -0
  6. package/dist/api/index.js +1 -1
  7. package/dist/builtin-memory/crouter-sdk/errors.md +1 -1
  8. package/dist/builtin-memory/crouter-sdk/getting-started.md +2 -0
  9. package/dist/builtin-memory/crouter-sdk/release-notes.md +10 -0
  10. package/dist/clients/attach/viewer.js +480 -480
  11. package/dist/commands/sys/join.js +1 -1
  12. package/dist/core/directory/join.d.ts +4 -2
  13. package/dist/core/directory/join.js +1 -1
  14. package/dist/core/grants/sync.d.ts +6 -0
  15. package/dist/core/grants/sync.js +1 -1
  16. package/dist/core/providers/errors.js +1 -1
  17. package/dist/core/providers/mint.js +1 -1
  18. package/dist/core/runtime/host.js +3 -3
  19. package/dist/core/runtime/nodes.js +1 -1
  20. package/dist/core/runtime/revive.js +2 -2
  21. package/dist/core/runtime/run-command.js +1 -1
  22. package/dist/core/runtime/unsandboxed.d.ts +3 -0
  23. package/dist/core/runtime/unsandboxed.js +1 -0
  24. package/dist/core/spaces/process.js +1 -1
  25. package/dist/daemon/api/app-listener.js +13 -13
  26. package/dist/daemon/api/server.js +1 -1
  27. package/dist/daemon/cron/authority.d.ts +1 -1
  28. package/dist/daemon/cron/authority.js +1 -1
  29. package/dist/daemon/crtrd.js +7 -7
  30. package/dist/types.d.ts +3 -1
  31. package/docs/release-local.md +4 -3
  32. package/docs/sdk/errors.md +1 -1
  33. package/docs/sdk/getting-started.md +2 -0
  34. package/docs/sdk/release-notes.md +10 -0
  35. package/package.json +5 -2
  36. package/packages/crouter-identity/package.json +1 -1
  37. package/runtime.lock.json +12 -12
@@ -0,0 +1,45 @@
1
+ /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
2
+ export declare const CONTRACT_LEVEL = "1.0";
3
+ /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
4
+ export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
5
+ /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
6
+ export declare const CONTRACT_HEADER = "Crouter-Contract";
7
+ /** A parsed `MAJOR.MINOR` level. */
8
+ export interface ContractLevel {
9
+ major: number;
10
+ minor: number;
11
+ }
12
+ /** Parses one `MAJOR.MINOR` level; `null` when it is anything else. */
13
+ export declare function parseContractLevel(value: string): ContractLevel | null;
14
+ /** Parses a `Crouter-Contract` response value into its well-formed levels, dropping anything malformed. */
15
+ export declare function parseServedContracts(header: string | null | undefined): ContractLevel[];
16
+ /** True when `required` (one `MAJOR.MINOR`) is satisfied by `served` (a `Crouter-Contract` value or its parsed levels):
17
+ * some served level has the same major and a minor at or above the required one. A malformed `required` is never
18
+ * satisfied. */
19
+ export declare function contractSatisfies(required: string, served: string | null | undefined | readonly ContractLevel[]): boolean;
20
+ /** The refusal a server answers instead of handling a request, or `null` when the request may be handled.
21
+ * - `required` absent: handled.
22
+ * - `required` not `MAJOR.MINOR`: 400 `invalid_request` (`param` is the header).
23
+ * - `required` not satisfied by `served`: 426 `runtime_version_unsupported`, details
24
+ * `{sdk_version, runtime_version, required_contract, runtime_contract}` (the SDK's `RuntimeVersionDetails`).
25
+ * `sdk_version` is the legacy minimum-runtime header value the caller sent (empty when it sent none). */
26
+ export type ContractRefusal = {
27
+ status: 400;
28
+ code: 'invalid_request';
29
+ message: string;
30
+ param: string;
31
+ } | {
32
+ status: 426;
33
+ code: 'runtime_version_unsupported';
34
+ message: string;
35
+ details: {
36
+ sdk_version: string;
37
+ runtime_version: string | null;
38
+ required_contract: string;
39
+ runtime_contract: string | null;
40
+ };
41
+ };
42
+ export declare function contractRefusal(required: string | undefined, served: string | null, context?: {
43
+ legacyMinimum?: string | undefined;
44
+ runtimeVersion?: string | null | undefined;
45
+ }): ContractRefusal | null;
@@ -0,0 +1 @@
1
+ var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.0",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
@@ -4,6 +4,11 @@ export declare const errorCodes: {
4
4
  readonly status: 401;
5
5
  readonly retryable: false;
6
6
  };
7
+ readonly machine_revoked: {
8
+ readonly type: "authentication_error";
9
+ readonly status: 401;
10
+ readonly retryable: false;
11
+ };
7
12
  readonly grant_revoked: {
8
13
  readonly type: "authentication_error";
9
14
  readonly status: 401;
@@ -79,6 +84,11 @@ export declare const errorCodes: {
79
84
  readonly status: 403;
80
85
  readonly retryable: false;
81
86
  };
87
+ readonly unsandboxed_machine: {
88
+ readonly type: "permission_error";
89
+ readonly status: 403;
90
+ readonly retryable: false;
91
+ };
82
92
  readonly isolated_run: {
83
93
  readonly type: "permission_error";
84
94
  readonly status: 403;
@@ -1 +1 @@
1
- const e={unauthorized:{type:"authentication_error",status:401,retryable:!1},grant_revoked:{type:"authentication_error",status:401,retryable:!1},grant_removed:{type:"authentication_error",status:401,retryable:!1},grant_suspended:{type:"authentication_error",status:401,retryable:!1},refresh_token_expired:{type:"authentication_error",status:401,retryable:!1},refresh_token_reused:{type:"authentication_error",status:401,retryable:!1},scope_missing:{type:"permission_error",status:403,retryable:!1},owner_only:{type:"permission_error",status:403,retryable:!1},other_app_run:{type:"permission_error",status:403,retryable:!1},payer_not_app:{type:"permission_error",status:403,retryable:!1},approval_pending:{type:"permission_error",status:403,retryable:!1},user_denied:{type:"permission_error",status:403,retryable:!1},provider_refused:{type:"permission_error",status:403,retryable:!1},provider_not_connected:{type:"permission_error",status:403,retryable:!1},provider_setup_pending:{type:"permission_error",status:403,retryable:!1},question_pages_off:{type:"permission_error",status:403,retryable:!1},isolated_run:{type:"permission_error",status:403,retryable:!1},provider_version_retired:{type:"permission_error",status:403,retryable:!1},invalid_scope:{type:"invalid_request_error",status:400,retryable:!1},invalid_request:{type:"invalid_request_error",status:400,retryable:!1},range_not_satisfiable:{type:"invalid_request_error",status:416,retryable:!1},conflict:{type:"invalid_request_error",status:409,retryable:!1},not_found:{type:"invalid_request_error",status:404,retryable:!1},model_unavailable:{type:"invalid_request_error",status:404,retryable:!1},upstream_model_unavailable:{type:"server_error",status:503,retryable:!0},substitution_refused:{type:"invalid_request_error",status:400,retryable:!1},idempotency_conflict:{type:"invalid_request_error",status:409,retryable:!1},run_settled:{type:"invalid_request_error",status:409,retryable:!1},cap_exhausted:{type:"rate_limit_error",status:402,retryable:!1},concurrency_limit:{type:"rate_limit_error",status:429,retryable:!0},all_routes_exhausted:{type:"rate_limit_error",status:429,retryable:!0},provider_rate_limited:{type:"rate_limit_error",status:429,retryable:!0},rate_limited:{type:"rate_limit_error",status:429,retryable:!0},runtime_unreachable:{type:"server_error",status:503,retryable:!0},isolated_unavailable:{type:"server_error",status:503,retryable:!0},runtime_starting:{type:"server_error",status:503,retryable:!0},runtime_provisioning:{type:"server_error",status:503,retryable:!0},provider_unavailable:{type:"server_error",status:503,retryable:!0},upstream_refused:{type:"server_error",status:502,retryable:!1},provider_bad_response:{type:"server_error",status:502,retryable:!1},service_unavailable:{type:"server_error",status:503,retryable:!0},not_implemented:{type:"server_error",status:501,retryable:!1},permission_error:{type:"permission_error",status:403,retryable:!1},storage_unavailable:{type:"server_error",status:503,retryable:!0},server_error:{type:"server_error",status:500,retryable:!0},server_failure:{type:"server_error",status:500,retryable:!1},wake_timeout:{type:"server_error",status:504,retryable:!0}};export{e as errorCodes};
1
+ const e={unauthorized:{type:"authentication_error",status:401,retryable:!1},machine_revoked:{type:"authentication_error",status:401,retryable:!1},grant_revoked:{type:"authentication_error",status:401,retryable:!1},grant_removed:{type:"authentication_error",status:401,retryable:!1},grant_suspended:{type:"authentication_error",status:401,retryable:!1},refresh_token_expired:{type:"authentication_error",status:401,retryable:!1},refresh_token_reused:{type:"authentication_error",status:401,retryable:!1},scope_missing:{type:"permission_error",status:403,retryable:!1},owner_only:{type:"permission_error",status:403,retryable:!1},other_app_run:{type:"permission_error",status:403,retryable:!1},payer_not_app:{type:"permission_error",status:403,retryable:!1},approval_pending:{type:"permission_error",status:403,retryable:!1},user_denied:{type:"permission_error",status:403,retryable:!1},provider_refused:{type:"permission_error",status:403,retryable:!1},provider_not_connected:{type:"permission_error",status:403,retryable:!1},provider_setup_pending:{type:"permission_error",status:403,retryable:!1},question_pages_off:{type:"permission_error",status:403,retryable:!1},unsandboxed_machine:{type:"permission_error",status:403,retryable:!1},isolated_run:{type:"permission_error",status:403,retryable:!1},provider_version_retired:{type:"permission_error",status:403,retryable:!1},invalid_scope:{type:"invalid_request_error",status:400,retryable:!1},invalid_request:{type:"invalid_request_error",status:400,retryable:!1},range_not_satisfiable:{type:"invalid_request_error",status:416,retryable:!1},conflict:{type:"invalid_request_error",status:409,retryable:!1},not_found:{type:"invalid_request_error",status:404,retryable:!1},model_unavailable:{type:"invalid_request_error",status:404,retryable:!1},upstream_model_unavailable:{type:"server_error",status:503,retryable:!0},substitution_refused:{type:"invalid_request_error",status:400,retryable:!1},idempotency_conflict:{type:"invalid_request_error",status:409,retryable:!1},run_settled:{type:"invalid_request_error",status:409,retryable:!1},cap_exhausted:{type:"rate_limit_error",status:402,retryable:!1},concurrency_limit:{type:"rate_limit_error",status:429,retryable:!0},all_routes_exhausted:{type:"rate_limit_error",status:429,retryable:!0},provider_rate_limited:{type:"rate_limit_error",status:429,retryable:!0},rate_limited:{type:"rate_limit_error",status:429,retryable:!0},runtime_unreachable:{type:"server_error",status:503,retryable:!0},isolated_unavailable:{type:"server_error",status:503,retryable:!0},runtime_starting:{type:"server_error",status:503,retryable:!0},runtime_provisioning:{type:"server_error",status:503,retryable:!0},provider_unavailable:{type:"server_error",status:503,retryable:!0},upstream_refused:{type:"server_error",status:502,retryable:!1},provider_bad_response:{type:"server_error",status:502,retryable:!1},service_unavailable:{type:"server_error",status:503,retryable:!0},not_implemented:{type:"server_error",status:501,retryable:!1},permission_error:{type:"permission_error",status:403,retryable:!1},storage_unavailable:{type:"server_error",status:503,retryable:!0},server_error:{type:"server_error",status:500,retryable:!0},server_failure:{type:"server_error",status:500,retryable:!1},wake_timeout:{type:"server_error",status:504,retryable:!0}};export{e as errorCodes};
@@ -7,6 +7,8 @@ export type { ErrorCode, ErrorEnvelope, ErrorOrigin, ErrorType } from './error-c
7
7
  export { API_VERSION, routes } from './routes.js';
8
8
  export { RUNTIME_VERSION_HEADER, MINIMUM_RUNTIME_VERSION_HEADER, compareReleaseVersions, minimumRuntimeRefusal } from './runtime-version.js';
9
9
  export type { MinimumRuntimeRefusal } from './runtime-version.js';
10
+ export { CONTRACT_HEADER, CONTRACT_LEVEL, REQUIRED_CONTRACT_HEADER, contractRefusal, contractSatisfies, parseContractLevel, parseServedContracts } from './contract.js';
11
+ export type { ContractLevel, ContractRefusal } from './contract.js';
10
12
  export { LONG_POLL_MAX_WAIT_SECONDS, LONG_POLL_ROUTES, longPollHoldSeconds, longPollRoute, requestedWait } from './long-poll.js';
11
13
  export type { LongPollName, LongPollRoute } from './long-poll.js';
12
14
  export * from '../shared/generated-context.js';
package/dist/api/index.js CHANGED
@@ -1 +1 @@
1
- import{CrtrClient as e,deadlineExceeded as t,MAX_TIMER_MS as m,sleepWithin as p,waitForDaemonAvailability as f}from"./client.js";import{APIError as E,ApiError as i,isErrorBody as l}from"./errors.js";import{errorCodes as R}from"./error-codes.js";import{API_VERSION as n,routes as s}from"./routes.js";import{RUNTIME_VERSION_HEADER as d,MINIMUM_RUNTIME_VERSION_HEADER as A,compareReleaseVersions as M,minimumRuntimeRefusal as N}from"./runtime-version.js";import{LONG_POLL_MAX_WAIT_SECONDS as S,LONG_POLL_ROUTES as u,longPollHoldSeconds as L,longPollRoute as P,requestedWait as T}from"./long-poll.js";export*from"../shared/generated-context.js";export*from"./dto/common.js";export*from"./dto/health.js";export*from"./dto/limits.js";export*from"./dto/grants.js";export*from"./dto/usage.js";export*from"./dto/nodes.js";export*from"./dto/node-outcomes.js";export*from"./dto/node-events.js";export*from"./dto/run-events.js";export*from"./dto/broker-signals.js";export*from"./dto/bash-jobs.js";export*from"./dto/messages.js";export*from"./dto/reports.js";export*from"./dto/structured-requests.js";export*from"./dto/lifecycle.js";export*from"./dto/subscriptions.js";export*from"./dto/focus.js";export*from"./dto/crons.js";export*from"./dto/config.js";export*from"./dto/attach.js";export*from"./dto/broker.js";export*from"./dto/profiles.js";export*from"./dto/modelauth.js";export*from"./dto/model-config.js";export*from"./dto/canvas.js";export*from"./dto/worktree.js";export*from"./dto/human.js";export*from"./dto/files.js";export*from"./dto/bash.js";export*from"./dto/inbox.js";export*from"./dto/human-requests.js";export*from"./dto/reviews.js";export*from"./dto/review-comments.js";export*from"./dto/chat-inventory.js";export*from"./dto/analytics.js";export*from"./dto/custom-objects.js";export{E as APIError,n as API_VERSION,i as ApiError,e as CrtrClient,S as LONG_POLL_MAX_WAIT_SECONDS,u as LONG_POLL_ROUTES,m as MAX_TIMER_MS,A as MINIMUM_RUNTIME_VERSION_HEADER,d as RUNTIME_VERSION_HEADER,M as compareReleaseVersions,t as deadlineExceeded,R as errorCodes,l as isErrorBody,L as longPollHoldSeconds,P as longPollRoute,N as minimumRuntimeRefusal,T as requestedWait,s as routes,p as sleepWithin,f as waitForDaemonAvailability};
1
+ import{CrtrClient as e,deadlineExceeded as t,MAX_TIMER_MS as m,sleepWithin as p,waitForDaemonAvailability as f}from"./client.js";import{APIError as E,ApiError as R,isErrorBody as _}from"./errors.js";import{errorCodes as i}from"./error-codes.js";import{API_VERSION as s,routes as n}from"./routes.js";import{RUNTIME_VERSION_HEADER as C,MINIMUM_RUNTIME_VERSION_HEADER as I,compareReleaseVersions as N,minimumRuntimeRefusal as O}from"./runtime-version.js";import{CONTRACT_HEADER as d,CONTRACT_LEVEL as S,REQUIRED_CONTRACT_HEADER as c,contractRefusal as L,contractSatisfies as M,parseContractLevel as u,parseServedContracts as D}from"./contract.js";import{LONG_POLL_MAX_WAIT_SECONDS as H,LONG_POLL_ROUTES as U,longPollHoldSeconds as V,longPollRoute as v,requestedWait as W}from"./long-poll.js";export*from"../shared/generated-context.js";export*from"./dto/common.js";export*from"./dto/health.js";export*from"./dto/limits.js";export*from"./dto/grants.js";export*from"./dto/usage.js";export*from"./dto/nodes.js";export*from"./dto/node-outcomes.js";export*from"./dto/node-events.js";export*from"./dto/run-events.js";export*from"./dto/broker-signals.js";export*from"./dto/bash-jobs.js";export*from"./dto/messages.js";export*from"./dto/reports.js";export*from"./dto/structured-requests.js";export*from"./dto/lifecycle.js";export*from"./dto/subscriptions.js";export*from"./dto/focus.js";export*from"./dto/crons.js";export*from"./dto/config.js";export*from"./dto/attach.js";export*from"./dto/broker.js";export*from"./dto/profiles.js";export*from"./dto/modelauth.js";export*from"./dto/model-config.js";export*from"./dto/canvas.js";export*from"./dto/worktree.js";export*from"./dto/human.js";export*from"./dto/files.js";export*from"./dto/bash.js";export*from"./dto/inbox.js";export*from"./dto/human-requests.js";export*from"./dto/reviews.js";export*from"./dto/review-comments.js";export*from"./dto/chat-inventory.js";export*from"./dto/analytics.js";export*from"./dto/custom-objects.js";export{E as APIError,s as API_VERSION,R as ApiError,d as CONTRACT_HEADER,S as CONTRACT_LEVEL,e as CrtrClient,H as LONG_POLL_MAX_WAIT_SECONDS,U as LONG_POLL_ROUTES,m as MAX_TIMER_MS,I as MINIMUM_RUNTIME_VERSION_HEADER,c as REQUIRED_CONTRACT_HEADER,C as RUNTIME_VERSION_HEADER,N as compareReleaseVersions,L as contractRefusal,M as contractSatisfies,t as deadlineExceeded,i as errorCodes,_ as isErrorBody,V as longPollHoldSeconds,v as longPollRoute,O as minimumRuntimeRefusal,u as parseContractLevel,D as parseServedContracts,W as requestedWait,n as routes,p as sleepWithin,f as waitForDaemonAvailability};
@@ -26,7 +26,7 @@ Every class below extends `APIError`, which carries `status`, `code`, `message`,
26
26
  | Condition | Class |
27
27
  |---|---|
28
28
  | SDK configuration error | `CrouterError` with `status: 0`, `code: 'crouter_error'` |
29
- | The runtime is older than the SDK, or names no version. The hosting router or the runtime can refuse this *before* handling (the SDK sends `Crouter-Minimum-Runtime-Version`): nothing was accepted and no idempotency key was recorded. A hosted runtime is always refused first once the hosting router has this check. Only a runtime that is not behind such a router and is older than the check release handles the request first, and then the SDK raises after | `RuntimeVersionError` with `status: 426`, `code: 'runtime_version_unsupported'`; `sdkVersion` and `runtimeVersion` (or `null`) name both, and it is never retryable. See [[crouter-sdk/release-notes]] |
29
+ | The runtime does not serve the contract level the SDK requires, or names none. The hosting router or the runtime refuses this *before* handling (the SDK sends `Crouter-Required-Contract` and a frozen `Crouter-Minimum-Runtime-Version`): nothing was accepted and no idempotency key was recorded. A runtime that predates the contract level handles the request first, and the SDK then raises after | `RuntimeVersionError` with `status: 426`, `code: 'runtime_version_unsupported'`; `requiredContract` and `runtimeContract` (or `null`) name both levels, `sdkVersion` and `runtimeVersion` stay for logging, and it is never retryable. A level is `MAJOR.MINOR`; the runtime satisfies it when it serves the same major and a minor at or above. See [[crouter-sdk/release-notes]] |
30
30
  | HTTP 400 | `BadRequestError` |
31
31
  | HTTP 401 | `AuthenticationError` |
32
32
  | HTTP 403 | `PermissionDeniedError` |
@@ -27,6 +27,8 @@ npm i @north-light/sdk
27
27
 
28
28
  One dependency. It re-exports every data type you need, so you do not also install `@north-light/api`.
29
29
 
30
+ `@latest` always works against production: a release reaches `latest` only once production serves its contract level. The `next` tag carries every release the moment it is published and may need a newer runtime than production runs (see [[crouter-sdk/errors]] for `RuntimeVersionError`). Pin an exact version in your app.
31
+
30
32
  ## 3. Node, on the owner's own daemon
31
33
 
32
34
  Pass nothing. The client resolves the same socket path the `crtr` CLI does, and starts the daemon if it is not already up.
@@ -9,6 +9,16 @@ when-and-why-to-read: When you need Release notes, this knowledge should be read
9
9
 
10
10
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
11
11
 
12
+ ## 0.3.423
13
+
14
+ ### Compatibility is a contract level, not the package version (additive)
15
+
16
+ The SDK no longer refuses a runtime because its release number is lower than the SDK's. It sends `Crouter-Required-Contract: <CONTRACT_LEVEL>` and refuses a runtime that does not serve that level (`MAJOR.MINOR`, same major, minor at or above). The level changes only when the `/v1` surface changes, so an ordinary release no longer makes production answer 426. `RuntimeVersionError` keeps `status` 426 and code `runtime_version_unsupported`, and gains `requiredContract` and `runtimeContract`; `sdkVersion` and `runtimeVersion` stay. Apps that catch the error need no change.
17
+
18
+ - Every SDK release is published to the `next` dist-tag; `latest` moves only once production serves its level. `@latest` therefore always works against production.
19
+ - The SDK now tolerates values it does not know: an unknown run event `type` is passed to your listener as-is, and an unknown run `status` ends `runs.wait` and the run watcher, reporting the raw status (`RunEnded.ended` can be `'unknown'`). Handle a status or event type you do not recognize rather than assuming the union is closed.
20
+ - `FakeCloud` takes `contract` and `routedContract` options and refuses an incompatible call at ingress, before any interceptor runs.
21
+
12
22
  ## 0.3.419
13
23
 
14
24
  ### Testing mode: `app_in_testing` (additive)