Project signature owned-result wrapping v1

September 10, 2026 ยท View on GitHub

Status: implemented bounded profile; HOSTED GREEN under the v0.4.0 release baseline. Historical local, authoring-time, ignored, or separately provisioned observations below retain their narrower scope; public promotion and broader product completion remain separately gated.

Audience: compiler contributors, SDK integrators, and candidate reviewers.

The ordered parameters form of change_function_signature may carry one additional closed member:

{
  "kind": "change_function_signature",
  "target": "provider.function.id",
  "parameters": [{"from": "input"}],
  "wrap_return": {
    "record": "wrapper.record.id",
    "field": "wrapper.record.field.id"
  }
}

The target must be an explicit monomorphic function whose complete result is an owning Bytes or string. It may have no requires or ensures clause and must not be either Project entrypoint or a manifest web export. The selected wrapper must be an existing, visible, explicit, monomorphic record with exactly one explicit field. Retained checked HIR must prove that field has the exact original result type and that the record is sized, resource-free, non-Copy, and cleanup-owning. Record and field display spellings come from authenticated source bindings; request bytes select stable identities only.

Admission authenticates the provider source and checked signature, the whole record and field shape, and every stable-ID-bound local call inventory before mutation. Every provider parameter must match retained HIR elementwise in name, span, ownership, and recursively resolved type. Bare source string normalizes only to checked owning String; all legacy scalars/views and ordered nominal owners keep their exact ordinary ownership and type identity. Calls from contracts are rejected. At least one local body call is required, and the authenticated count must equal the rewritten count.

The provider wraps its former body exactly once in the selected record constructor at the existing result position. Each local caller keeps its existing left-to-right argument staging and call commit, then immediately projects and moves the sole field. The call is evaluated once. The provider record owns the result until publication; after projection, ordinary caller cleanup owns the moved Bytes or string and cleans the empty record according to the compiler's existing structural plan. Full Project reconstruction rechecks HIR, ownership, cleanup, interpreter admission, native emission, Wasm emission, manifests, and target admission before the candidate is observable.

The lane rejects borrowed results, projected provider results, generic or resource-bearing wrappers, multi-field or mismatched wrappers, implicit identities, contract occurrences, exported providers, zero-local-caller changes, unknown mapping fields, and caller-inventory drift. It does not search or rewrite external source, generated SDKs, packages, deployed consumers, reflection, network providers, or runtime data. Package-consumer analysis and replay are separate explicit APIs; this lane neither invokes them nor claims a package conflict gate, compatibility result, or automatic consumer migration.

SPX-G494 owns unsupported wrapper shape and scope, SPX-G495 owns retained source/HIR authentication failures, and SPX-G496 owns caller inventory and migration failures. The closed intent schema and candidate catalogue expose the same two stable selectors and exclusions.

Implemented regressions in tests/project_candidate/signature_ownership.rs cover Bytes and String wrappers, legacy borrowed parameters, bare owning String parameter normalization, provider construction, caller projection, exact candidate replay, cleanup-bearing Project admission, wrong-type and multi-field rejection, and unchanged candidate state after rejection. Full Project replay is the authored interpreter/native/Wasm admission oracle; no test target, backend artifact, benchmark, or local runtime was executed for this change.