From c113bc6b81af22862a497dbf4016c2a8dd474a33 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Tue, 25 Aug 2026 15:17:06 -0700 Subject: [PATCH] docs(binding): document widened capability matrix --- README.md | 18 +-- docs/docs/explanation/security-model.md | 49 +++++--- docs/docs/reference/mcp-tools.md | 149 +++++++++++++++++++--- docs/docs/reference/public-api.md | 156 +++++++++++++++++++----- docs/docs/tutorials/first-server.md | 12 +- 5 files changed, 316 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 2f0efc3..20a64ce 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,17 @@ call `Builder.Build` must do the same from `TestMain` before `m.Run`. Each `execute` request runs Starlark in a fresh worker process created by re-executing the host binary. The submitted program must define a zero-argument `main()`, and only its final converted value is exposed to the caller. Printed text, -globals, and interpreter-local intermediate values are not returned. During -execution, each native-call argument map and each validated native result -crosses the private worker protocol, and each crossing value is independently -subject to `MaxValueDepth` and `MaxValueBytes`. - -Capabilities are bound and canonicalized in the parent before authorization, -and authorization completes before handler dispatch. Trusted subjects come +globals, and interpreter-local intermediate values are not returned. Each +native-call argument map, native result, and final value is independently +subject to `MaxValueDepth` and `MaxValueBytes`. Successful native-result value +bodies also consume the fresh request-scoped `MaxIntermediateValueBytes` +budget. + +The worker binds keyword arguments first. The parent then rebinds them to the +registered Go input, creates a fresh canonical authorization map, authorizes +the call, and dispatches the handler. The parent converts the handler output to +a process-neutral value, and the worker converts that value to Starlark. +Trusted subjects come from typed, host-owned Go context through `mcpserver.InvocationResolver`; tool arguments, Starlark source, and MCP `_meta` are not trusted identity or credential sources. diff --git a/docs/docs/explanation/security-model.md b/docs/docs/explanation/security-model.md index 3ee6b32..f56ded6 100644 --- a/docs/docs/explanation/security-model.md +++ b/docs/docs/explanation/security-model.md @@ -24,13 +24,24 @@ Credentials stay in the host's authentication layer. They are not fields on `aut ## Validation precedes authority -A native call crosses three boundaries in a fixed order: - -1. **Exact binding and canonicalization.** Duplicate keyword syntax is rejected by the Starlark parser as `ErrInvalidProgram` before this step. Positional, missing, unknown, incorrectly typed, and out-of-range arguments reach binding and map to `ErrInvalidArguments`. Successful binding creates the exact registered Go input and a fresh JSON-shaped argument map. -2. **Authorization.** CodeMode passes the trusted subject, stable capability ID, dotted capability name, and canonical arguments to `authz.Authorizer`. -3. **Handler dispatch.** CodeMode calls the typed handler only if authorization returns `nil`. - -This order prevents policy from interpreting malformed Starlark values and prevents a handler from running before policy has evaluated the exact input the handler will receive. The canonical map is separate from the typed handler input, so policy cannot rewrite the handler's arguments by mutating the map. +A native call passes four stages in a fixed order: + +1. **Worker binding.** The worker validates exact keyword arguments against the + registered input shape and sends normalized values to the parent. Duplicate + keyword syntax is rejected by the Starlark parser as `ErrInvalidProgram`. + Positional, missing, unknown, incorrectly typed, and out-of-range arguments + map to `ErrInvalidArguments`. +2. **Parent rebinding and canonicalization.** The parent reconstructs the exact + registered Go input and creates a fresh JSON-shaped authorization map. +3. **Authorization.** CodeMode passes the trusted subject, stable capability ID, + dotted capability name, and canonical arguments to `authz.Authorizer`. +4. **Handler dispatch.** CodeMode calls the typed handler only if authorization + returns `nil`. + +This order prevents policy from interpreting malformed Starlark values and +prevents a handler from running before policy has evaluated the exact input the +handler will receive. The canonical map is separate from the typed handler +input, so policy cannot rewrite the handler's arguments by mutating the map. An authorizer reports a recognized denial with an error that wraps `authz.ErrDenied`. CodeMode classifies that outcome as `permission denied` and does not dispatch the handler. Any other authorizer error, and an authorizer panic recovered at the boundary, becomes `authorization policy failure`. Policy diagnostic text does not cross the MCP boundary. @@ -97,10 +108,12 @@ returned. Unrelated native results are not exposed in that final caller result. No Starlark globals or mutable interpreter state carry into the next execute call. -Capability handlers do not run in the worker. A native call crosses the private -protocol, is rebound to the exact registered input type in the parent, is -authorized there, and is then dispatched to the parent handler. The validated -native result crosses back to the worker so Starlark can continue. +Capability handlers do not run in the worker. A normalized native call crosses +the private protocol, is rebound to the exact registered input type in the +parent, is authorized there, and is then dispatched to the parent handler. The +parent converts the handler output to a bounded process-neutral value. Its +encoded native-result body crosses back to the worker, which converts that +value to Starlark so the program can continue. These rules isolate interpreter state and make Starlark execution killable. They do not confine registered Go code: authorizers, the optional Rego @@ -112,9 +125,17 @@ gave that process. CodeMode derives one execution context from `MaxExecutionTime` and the request context. The elapsed budget starts before waiting for a worker slot and covers spawn, protocol exchange, Starlark execution, and parent dispatch. Killing and -reaping the worker can add operating-system overhead beyond the budget. The -interpreter separately enforces source, bytecode-step, attempted-native-call, -crossing-value depth, and crossing-value size limits. +reaping the worker can add operating-system overhead beyond the budget. Source, +bytecode-step, attempted-native-call, crossing-value depth, and per-crossing +encoded-size limits apply independently. + +`MaxIntermediateValueBytes` additionally bounds the request-scoped sum of +successful parent-to-child native-result value bodies. A result is encoded +before its body length is checked and debited. This cumulative budget is +independent of `MaxValueBytes`, starts fresh for every `Execute`, and includes +successful native results that the program later discards. It does not measure +handler allocations, Starlark object memory, process RSS, or an operating-system +memory quota. When the execution context ends, the parent closes the worker pipes, kills the worker if necessary, and reaps it exactly once. This hard-preempts Starlark, diff --git a/docs/docs/reference/mcp-tools.md b/docs/docs/reference/mcp-tools.md index bc094d8..8f217df 100644 --- a/docs/docs/reference/mcp-tools.md +++ b/docs/docs/reference/mcp-tools.md @@ -134,22 +134,100 @@ For the site-wide sample, the requested name is `records.lookup`. Its stable ID, The required `input` and `output` properties are always arrays. A capability with no input or output fields uses `[]`, not `null`. -`input` and `output` preserve Go field declaration order. Field-shape types have these values: +`input` and `output` preserve Go field declaration order. Each array item keeps +the flat field-shape schema with only `name`, `type`, and `required`; nested +types are encoded in the `type` string rather than in recursive field-shape +objects. + +Input field shapes are: + +| Go field type | `type` | `required` | +| --- | --- | --- | +| `string` | `str` | `true` | +| `int64` | `int` | `true` | +| `bool` | `bool` | `true` | +| `float64` | `float` | `true` | +| `*string` | str | None | `false` | +| `*int64` | int | None | `false` | +| `*bool` | bool | None | `false` | +| `*float64` | float | None | `false` | + +Named input types with these underlying kinds have the same shapes. Required +integers use the signed 64-bit range, and all floats must be finite. An omitted +optional argument and explicit `None` both produce a nil pointer and omit the +field from the canonical authorization map. + +Output `type` strings use this compact notation: + +| Value shape | Notation | +| --- | --- | +| String, integer, Boolean, or finite float | `str`, `int`, `bool`, or `float` | +| Array or slice | `list[T]` | +| String-keyed map | `dict[str, T]` | +| Struct | `{field: T, optional?: U}` | +| Pointer | T | None | -| Position | Go field type | `type` value | `required` | -| --- | --- | --- | --- | -| Input | `string` | `str` | `true` | -| Input | `*int64` | `int | None` | `false` | -| Output | `string` | `str` | `true` | -| Output | `int64` | `int` | `true` | -| Output | `bool` | `bool` | `true` | -| Output | `float64` | `float` | `true` | +Struct fields appear in declaration order. Arrays and slices share `list[T]`. +Pointer layers append ` | None` once. At the root, an `omitempty` pointer uses +`required: false` and the pointed-to type without the outer ` | None`. Inside a +struct, the field name uses `?`, as in `{value: str, detail?: str}`. -The sample capability therefore describes input fields `key` (`str`, required) and `limit` (`int | None`, optional), and output fields `key` (`str`, required) and `count` (`int`, required). +For example, one flat `output` array can contain: -`signature` is the same invocation-only keyword signature returned by `search_api`. +```json +[ + { + "name": "items", + "type": "list[{title: str, score: float}]", + "required": true + }, + { + "name": "alias", + "type": "dict[str, bool]", + "required": true + }, + { + "name": "nested", + "type": "{value: str, detail?: str}", + "required": true + }, + { + "name": "values", + "type": "list[str | None] | None", + "required": true + }, + { + "name": "extra", + "type": "str", + "required": false + } +] +``` -`output` is the stable result contract. Models and clients must use these field shapes. Clients that parsed a trailing ` -> ` suffix on `signature` must stop parsing it and use `describe_api.output` instead. There is no compatibility suffix, alias, or alternate signature field. +The output universe includes nested structs, arrays and slices, string-keyed +maps, pointers, named scalars, all signed and unsigned integer kinds, finite +`float32` and `float64` values, and byte slices or arrays as integer lists from +0 through 255. Integers are projected through signed 64-bit values, so a +returned unsigned value above `math.MaxInt64` is invalid. Byte sequences are +not base64 encoded. + +A nil pointer without `omitempty` remains present as `None`. A nil pointer with +`omitempty` is omitted. Nil slices, maps, and byte slices also become `None` +even though discovery shows `list[T]` or `dict[str, T]`; non-nil empty +containers remain empty. A nil pointer inside a list or map becomes `None`. + +The host rejects unsupported reflected shapes when it registers a capability, +before the MCP service can expose it. Rejected output graphs include +interfaces, `json.RawMessage`, types with value or pointer methods that +implement JSON or text marshaling, non-string map keys, cyclic type graphs, +unsupported scalar kinds, embedded or unexported fields, invalid tags, and +`omitempty` on a non-pointer field. + +`signature` is the same invocation-only keyword signature returned by +`search_api`. `output` is the stable result contract. Models and clients must +use these field shapes. Clients that parsed a trailing ` -> ` suffix +on `signature` must stop parsing it and use `describe_api.output` instead. +There is no compatibility suffix, alias, or alternate signature field. ## `execute` @@ -174,7 +252,29 @@ The source must define `def main():` as a function with zero arguments. Top-leve Capabilities are available by dotted name. The sample native call is `records.lookup(key="alpha", limit=2)`. Native calls accept keyword arguments only. Duplicate keyword syntax is rejected by the Starlark parser as `invalid program` before authorization or handler dispatch. Positional, unknown, missing, incorrectly typed, and out-of-range arguments reach binding and map to `invalid capability arguments`. For the sample, `key` is required and `limit` can be omitted, `None`, or an integer in the signed 64-bit range. -Each `execute` call gets a fresh interpreter and fresh source, step, elapsed-time, native-call, conversion-depth, and result-size budgets. There is no interpreter state shared between calls. +For example, a capability described with the signature +`records.search(*, count: int, active: bool, score: float, label: str | None)` +and output type `list[{id: str, active: bool, score: float}]` can be composed +inside one program: + +```python +def main(): + response = records.search(count=3, active=True, score=1.5) + ids = [] + total = 0.0 + count = 0 + for item in response["items"]: + if item["active"]: + ids.append(item["id"]) + total += item["score"] + count += 1 + return {"count": count, "score": total, "ids": ids} +``` + +Each `execute` call gets a fresh interpreter and fresh source, step, +elapsed-time, native-call, conversion-depth, per-value size, and aggregate +intermediate-value budgets. There is no interpreter or aggregate accounting +shared between calls. ### Successful structured output @@ -199,7 +299,19 @@ The `result` property is the final converted return value from `main`. Its runti - an array containing supported values - an object with string keys and supported values -`MaxValueDepth` is inclusive. A scalar or `None` is depth 1. Each tuple, list, or dictionary wrapper adds one. A scalar with limit 1 succeeds, a one-level container with limit 2 succeeds, and one more wrapper with limit 2 fails. Native arguments, native results, and the final value are independently subject to `MaxValueDepth` and `MaxValueBytes`. Starlark tuples and lists become arrays. `None` becomes `null`. Dictionaries must have string keys. +`MaxValueDepth` is inclusive. A scalar or `None` is depth 1. Each tuple, list, +or dictionary wrapper adds one. A scalar with limit 1 succeeds, a one-level +container with limit 2 succeeds, and one more wrapper with limit 2 fails. +Native arguments, native results, and the final value are independently subject +to `MaxValueDepth` and `MaxValueBytes`. + +`MaxIntermediateValueBytes` separately bounds the request-scoped sum of +successful parent-to-child native-result value bodies. After each successful +result is encoded, its encoded body length is checked and debited. The sum +excludes frame envelopes, native-call arguments, failed handlers, and the final +program value. Results consume this aggregate budget even when `main` does not +return them. A new `execute` call starts fresh. Starlark tuples and lists become +arrays. `None` becomes `null`. Dictionaries must have string keys. Only the final converted value from the worker process is exposed in the successful MCP result. `print` output is discarded. Globals, values created during top-level source loading, intermediate expressions, and native results that are not included in the final return value are not added to structured output. The successful envelope contains only `result`. @@ -215,7 +327,10 @@ The listed descriptions above are the model-facing contract. Recovery uses the s - Call only names confirmed through `search_api` and `describe_api`, and call them only inside `main`. - Return the final value from `main`. - Use `describe_api.output` as the result contract. Do not parse a type name from `signature`. -- After `resource limit exceeded`, reduce the applicable bounded quantity: query or source bytes, execution steps or time, native calls, or crossing-value depth or encoded size. Then retry the call. +- After `resource limit exceeded`, reduce the applicable bounded quantity: + query or source bytes, execution steps or time, native calls, crossing-value + depth or per-value encoded size, or the cumulative encoded size of successful + native results. Then retry the call. - After `permission denied` or `authorization policy failure`, contact the host if the access was expected. One allowed or denied input cannot establish whether the policy is default-open, default-deny, complete, or incomplete. ## Errors @@ -230,8 +345,8 @@ After a well-formed call reaches the adapter, a resolver or service failure beco | `invalid capability arguments` | A native call failed binding: positional, unknown, missing, incorrectly typed, or out-of-range arguments. | | `permission denied` | Policy returned a recognized denial. | | `authorization policy failure` | Policy evaluation failed. | -| `resource limit exceeded` | A discovery, execution, or conversion budget was exceeded. | -| `capability failed` | A handler failed or returned an invalid output. | +| `resource limit exceeded` | A discovery, execution, depth, per-value, or aggregate intermediate-value budget was exceeded. | +| `capability failed` | A handler failed or returned an invalid value, including a non-finite float or an unsigned integer above `math.MaxInt64`. | | `context canceled` | The request context was canceled. | | `context deadline exceeded` | A service returned a bare deadline error. Root CodeMode execution deadlines are normally projected as `resource limit exceeded`. | | `internal failure` | Any unknown service error or recovered adapter failure. | diff --git a/docs/docs/reference/public-api.md b/docs/docs/reference/public-api.md index 7f9810a..453b464 100644 --- a/docs/docs/reference/public-api.md +++ b/docs/docs/reference/public-api.md @@ -81,29 +81,77 @@ The site examples use one capability contract throughout: #### Supported input and output types -Both generic type arguments must be non-pointer structs. Fields must be exported, direct fields; embedded fields are not supported. A field name defaults to its Go name. A single `json` tag can replace it with a valid Starlark identifier. Other struct tags, ignored fields, and unsupported JSON tag options are rejected. +Both generic type arguments must be non-pointer structs. Input fields must be +direct exported fields. Output structs can be nested, but every struct in the +output graph must contain only direct exported fields. Embedded fields are not +supported. -The shipped input matrix is: +A field name defaults to its Go name. One `json` tag can replace it with a +valid Starlark identifier. Other struct tags, ignored fields, duplicate names, +and unsupported JSON tag options are rejected. -| Go field type | Starlark input | Required | JSON tag rule | -| --- | --- | --- | --- | -| `string` | `str` | Yes | `omitempty` is rejected. | -| `*int64` | `int` or `None` | No | `omitempty` is accepted but is not required for optional binding. | - -Inputs accept keyword arguments only. Duplicate keyword syntax is rejected by the Starlark parser as `ErrInvalidProgram` before authorization or handler dispatch. Positional, unknown, missing required, incorrectly typed, and out-of-range arguments reach binding and map to `ErrInvalidArguments`. An omitted optional integer and an explicit `None` both produce a nil pointer. +Inputs support these scalar fields and named types with the same underlying +kind: -The shipped output matrix is: - -| Go field type | Starlark value | Final JSON value | +| Go field type | Starlark input | Required | `omitempty` | +| --- | --- | --- | --- | +| `string` | `str` | Yes | Rejected | +| `int64` | `int` | Yes | Rejected | +| `bool` | `bool` | Yes | Rejected | +| `float64` | `float` | Yes | Rejected | +| `*string` | `str` or `None` | No | Accepted but not required | +| `*int64` | `int` or `None` | No | Accepted but not required | +| `*bool` | `bool` or `None` | No | Accepted but not required | +| `*float64` | `float` or `None` | No | Accepted but not required | + +Inputs accept keyword arguments only. Integers must fit the signed 64-bit +range, and floats must be finite. CodeMode does not coerce an integer to a +float or a Boolean to an integer. An omitted optional input and an explicit +`None` both produce a nil pointer and omit that key from the fresh canonical +authorization map. Duplicate keyword syntax is rejected by the Starlark parser +as `ErrInvalidProgram` before authorization or handler dispatch. Positional, +unknown, missing required, incorrectly typed, and out-of-range arguments reach +binding and map to `ErrInvalidArguments`. + +An output can recursively contain these Go types: + +| Go type | Starlark value | Rules | | --- | --- | --- | -| `string` | `str` | string | -| `int64` | `int` | signed 64-bit integer | -| `bool` | `bool` | boolean | -| `float64` | `float` | number | - -Every output field is required. `omitempty` is rejected on output fields. A `float64` output must be finite. Nested structs, slices, maps, other pointer fields, and other scalar field types are outside the shipped registration matrix. - -`Register(builder, capability)` compiles and validates the generic binding contract and retains the capability. It rejects a nil or closed builder, a nil handler, invalid metadata, unsupported types, and duplicate IDs or names. +| `string`, `bool`, and named types with those underlying kinds | `str`, `bool` | Values are preserved. | +| `int`, `int8`, `int16`, `int32`, `int64`, and named types with those underlying kinds | `int` | Values are normalized to signed 64-bit integers. | +| `uint`, `uint8`, `uint16`, `uint32`, `uint64`, and named types with those underlying kinds | `int` | Values are normalized to signed 64-bit integers. A value above `math.MaxInt64` is invalid. | +| `float32`, `float64`, and named types with those underlying kinds | `float` | Values are normalized to finite `float64` values. NaN and infinity are invalid. | +| Pointer to a supported type | The referenced value or `None` | Pointers can appear in fields and inside containers. | +| Struct | Dictionary-like object | Fields follow the same export and tag rules at every nesting level. | +| Array or slice of a supported type | List | Arrays and slices have the same model-facing type notation. | +| Map with a string or named-string key and a supported value type | Dictionary | Other map key types are rejected. | +| Slice or array whose element has underlying type `uint8` | List of integers from 0 through 255 | Byte sequences are not base64 encoded. | + +A nil pointer field without `omitempty` remains present with `None`; its +discovery type is `T | None`. A nil pointer field with `omitempty` is absent; +at the root its field shape has `required: false`, and inside a struct its name +uses `?`, as in `{detail?: str}`. The omitted field's type does not gain +`| None` from that pointer. `omitempty` is rejected on every non-pointer output +field. + +Nil slices, maps, and byte slices become `None`. This remains true even though +their discovery types are `list[T]` or `dict[str, T]`. Non-nil empty slices and +maps become empty lists and dictionaries. A nil pointer inside a list or map +becomes `None`; `omitempty` omits only struct fields. + +`Register(builder, capability)` compiles and validates the complete reflected +type graph before retaining the capability. Registration rejects unsupported +input types; pointer roots; interfaces including `any`; `json.RawMessage`; +types whose value or pointer method set implements `json.Marshaler` or +`encoding.TextMarshaler`; functions, channels, complex values, +`unsafe.Pointer`, and `uintptr`; non-string map keys; cyclic type graphs; and +invalid fields or tags. + +These are registration-time failures and map to `ErrInvalidRegistration`. +Returned values are checked at call time. An unsigned result above +`math.MaxInt64` or a non-finite float maps to `ErrCapabilityFailure`. +Output-depth, per-value byte, and aggregate intermediate-byte exhaustion map to +`ErrResourceLimit`. ### Options and builder lifecycle @@ -159,6 +207,7 @@ The filter is deployment configuration, not a per-request policy. Use an `authz. | `MaxNativeCalls uint64` | 100 | Attempted native calls in one execution. | | `MaxValueDepth int` | 32 | Inclusive nesting depth of any value crossing the worker boundary. | | `MaxValueBytes int` | 1,048,576 bytes (1 MiB) | Type-preserving encoding of any value crossing the worker boundary. | +| `MaxIntermediateValueBytes int` | 8,388,608 bytes (8 MiB) | Cumulative encoded successful parent-to-child native-result value bodies in one `Execute` call. | | `MaxSearchQueryBytes int` | 256 bytes | Raw search query before trimming or case normalization. Whitespace padding counts. | | `MaxSearchResults int` | 20 | Search results returned. | | `MaxConcurrentExecutions int` | 8 | Concurrent spawn attempts and live worker processes. | @@ -178,6 +227,20 @@ complete protocol frame. The worker frame cap adds the fixed envelope and, for native calls, the longest enabled encoded capability ID. Build rejects a catalog and limit combination whose largest legal frame cannot be represented. +`MaxIntermediateValueBytes` is independent of `MaxValueBytes`. Each +`Execute` call starts with a fresh aggregate budget. After a successful native +result is encoded, CodeMode checks and debits its value-body byte length from +that request's remaining budget. The sum excludes frame envelopes, native-call +arguments, failed handlers, and the final program value. It also does not +measure handler-owned memory, Starlark object memory, process RSS, or a +high-water mark. + +A native result must satisfy both limits: `MaxValueBytes` bounds that one +crossing, while `MaxIntermediateValueBytes` bounds the sum of successful +native-result bodies. A sequence of individually valid results can therefore +exhaust the aggregate limit, and a single result can exceed `MaxValueBytes` +while aggregate capacity remains. + `MaxExecutionTime` starts before waiting for a worker slot and covers spawn, protocol exchange, Starlark execution, and parent dispatch. Killing and reaping the worker can add operating-system overhead beyond the budget. @@ -233,7 +296,27 @@ Describe(name CapabilityName) (Description, error) | `input` | array of field shapes | Ordered input fields. | | `output` | array of field shapes | Stable result contract. Models and clients must use these ordered field shapes. | -Each field shape has `name` (string), `type` (string), and `required` (boolean). Input shapes use `str` and `int | None`. Output shapes use `str`, `int`, `bool`, and `float`. Output fields always report `required: true`. +Each field shape remains the flat object `{name, type, required}`. Input +`type` values are `str`, `int`, `bool`, and `float`; optional pointer inputs +append ` | None` and set `required` to `false`. + +Output `type` strings use deterministic compact notation: + +| Go shape | Notation | +| --- | --- | +| String, integer, Boolean, or float | `str`, `int`, `bool`, or `float` | +| Array or slice | `list[T]` | +| String-keyed map | `dict[str, T]` | +| Struct | `{field: T, optional?: U}` | +| Pointer without an enclosing `omitempty` field | T | None | + +Fields remain in Go declaration order, including fields inside a struct +notation. Arrays and slices share `list[T]` because they produce the same +Starlark value. Repeated pointer layers append ` | None` once. Root output +optionality stays in `required`; nested output optionality uses `?`. Examples +include `list[{title: str, score: float}]`, `dict[str, bool]`, +`{value: str, detail?: str}`, and the nested nullable +`list[str | None] | None`. `Description.Output` is the stable result contract. Clients that parsed a trailing ` -> ` suffix on `signature` must stop parsing it and use `output` instead. There is no compatibility suffix, alias, or alternate signature field. @@ -249,14 +332,26 @@ Every call creates a fresh interpreter and fresh budgets. Module loading is disa For each native call, CodeMode performs these operations in order: -1. Bind exact keyword arguments to the registered Go input and create a fresh canonical JSON-shaped argument map. -2. Call the authorizer with that canonical input. -3. Dispatch the handler only when authorization succeeds. -4. Convert the handler's exact registered output to Starlark. - -After `main` returns, CodeMode converts only its final value. Supported final values are `None`, booleans, strings, signed 64-bit integers, finite floats, tuples, lists, and dictionaries with string keys, recursively within configured limits. `None` becomes JSON `null`, and tuples and lists become JSON arrays. - -Only that converted return value is exposed to the caller. Printed text, globals, and interpreter-local intermediate values are not returned. During execution, each native-call argument map and each validated native result crosses the private worker protocol, and each crossing value is independently subject to `MaxValueDepth` and `MaxValueBytes`. +1. The worker binds exact keyword arguments against the registered input shape + and sends normalized values to the parent. +2. The parent rebinds those values to the exact registered Go input and creates + a fresh canonical authorization map. +3. The parent calls the authorizer with that canonical input. +4. The parent dispatches the handler only when authorization succeeds. +5. The parent converts the exact registered handler output to a bounded + process-neutral value. The worker then converts that value to Starlark. + +After `main` returns, CodeMode converts only its final value. Supported final +values are `None`, booleans, strings, signed 64-bit integers, finite floats, +tuples, lists, and dictionaries with string keys, recursively within configured +limits. `None` becomes JSON `null`, and tuples and lists become JSON arrays. + +Only that converted return value is exposed to the caller. Printed text, +globals, and interpreter-local intermediate values are not returned. Each +native-call argument map, native result, and final value is independently +subject to `MaxValueDepth` and `MaxValueBytes`. Successful native-result value +bodies also consume the request-scoped `MaxIntermediateValueBytes` budget, +including results that `main` does not return. ### Error classifications @@ -298,7 +393,10 @@ Credentials do not belong in `Subject`. The host keeps credentials in its authen | `CapabilityName string` | Dotted model-facing name. | | `Arguments map[string]any` | Fresh canonical projection of validated keyword arguments. | -Canonical arguments contain JSON-shaped values from the shipped input matrix. A supplied string is a string and a supplied optional integer is an `int64`. An omitted optional integer or explicit `None` is absent from the map. The canonical map is separate from the typed input passed to the handler. +Canonical arguments contain JSON-shaped scalar values from the supported input +matrix: strings, `int64` integers, Booleans, and finite `float64` values. An +omitted optional value or explicit `None` is absent from the map. The canonical +map is separate from the typed input passed to the handler. ### Authorizers diff --git a/docs/docs/tutorials/first-server.md b/docs/docs/tutorials/first-server.md index bbfd8e2..6b1663d 100644 --- a/docs/docs/tutorials/first-server.md +++ b/docs/docs/tutorials/first-server.md @@ -207,7 +207,17 @@ The program prints structured results for all three tools. The final line contai {"result":{"count":2,"key":"alpha"}} ``` -`execute` first binds and canonicalizes the keyword arguments in the parent, then authorizes the native call, and then dispatches `lookupRecords` in the parent. Each `execute` call runs a fresh bounded interpreter in a re-executed worker process. Only the final converted value returned by a zero-argument `main()` is exposed in the successful MCP result; printed text, globals, and interpreter-local intermediate values are not returned. During execution, each native-call argument map and each validated native result crosses the private worker protocol, and each crossing value is independently subject to `MaxValueDepth` and `MaxValueBytes`. +`execute` first binds the keyword arguments in the worker. The parent rebinds +them to `lookupInput`, creates a fresh canonical authorization map, authorizes +the native call, and then dispatches `lookupRecords`. The parent converts the +handler output to a process-neutral value, and the worker converts it to +Starlark. Each `execute` call runs a fresh bounded interpreter in a re-executed +worker process. Only the final converted value returned by a zero-argument +`main()` is exposed in the successful MCP result; printed text, globals, and +interpreter-local intermediate values are not returned. Each native-call +argument map, native result, and final value is independently subject to +`MaxValueDepth` and `MaxValueBytes`. Successful native-result value bodies also +consume the fresh request-scoped `MaxIntermediateValueBytes` budget. `ServeWorkerAndExit` must be the first statement in `main`, before flag parsing, credential loading, client construction, or other application setup. It returns immediately in the ordinary host. In a re-executed worker, it serves one private probe or execution request and terminates the process.