Project Signature Evolution 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: agent builders, compiler contributors, and reviewers.
This additive intention shape extends Project Candidates and Semantic Change
IR v1. Canonical source remains authoritative; an
intention constructs candidate ASTs and cannot publish source, supply trusted
HIR, or bypass the complete Project verifier. Existing append_parameters
requests, canonical bytes, argument treatment, and limits remain unchanged.
Ordered parameter mapping
change_function_signature now additionally admits an object with exactly
kind, target, and parameters. The target retains the existing requirement:
an explicitly identified, monomorphic, top-level function other than main.
The array order becomes the candidate declaration's parameter order.
{
"kind": "change_function_signature",
"target": "calculator.add",
"parameters": [
{"from": "right"},
{"from": "left"},
{"name": "offset", "type": "i64", "argument": {"kind": "i64", "value": 0}}
]
}
Each element has exactly one of these shapes:
| Shape | Meaning |
|---|---|
{"from":"old_name"} | Retain one original parameter with its exact existing name, type, and mode at this position. |
{"from":"old_name","name":"new_name"} | Retain its exact type and mode and rename the original lexical parameter binding. |
{"name":"new_name","borrow_slice_from_owner":"old_name"} | Replace one of at most eight distinct exact original own Bytes parameters with borrow Slice<u8> under the closed owner-view admission below. |
{"name":"new_name","borrow_str_from_owner":"old_name"} | Replace one of the same bounded set of exact original owning string parameters with borrow str. |
{"name":"new_name","type":"scalar","argument":literal} | Add a fresh by-value scalar parameter and supply the explicit matching scalar literal at every migrated call. |
{"name":"new_name","type":type_selector,"argument_expression":expression} | Compute a new scalar or checked Copy nominal argument from the original staged parameters after all original arguments, then fully revalidate each migrated caller. See Argument Expressions v1. |
A retained parameter can appear only once. Original parameters omitted from the array are removed from the declaration, but their caller argument expressions are still evaluated. An empty array therefore removes every Copy parameter while preserving argument evaluation at existing calls. Every owning or borrowed parameter must be retained exactly once; it cannot be removed or copied.
The sole exception is the explicit borrow_slice_from_owner or
borrow_str_from_owner replacement. One change admits one through eight
distinct exact original own Bytes or bare owning string parameters,
including a closed mix of both kinds.
Retained checked HIR must independently prove that the provider body uses each
selected owner exactly once, as the unprojected root of its matching
compiler-owned core.bytes.as-slice or core.string.as-str operation. The
provider declaration changes each selected parameter to exact borrow Slice<u8>
or borrow str, and each authenticated view expression becomes its
corresponding new parameter reference. Any selected
owner root in requires or ensures, move, return, projection, nested owner,
duplicate or cross-owner alias, alternate operation, ninth conversion, or
concurrent from mapping rejects. Every new binding must be unique and absent
from the complete candidate lexical inventory so no rewrite can capture another
binding.
New parameter names must be distinct from all original names, including names
of removed parameters. They cannot reinterpret an existing body binding.
New scalar types and literal kinds are the complete i64, i32, char, u8,
usize, f32, f64, and bool vocabulary. Characters and floats use the
exact transport encodings and signed-float canonical lowering defined by
Scalar Literal Constructors v1.
Unknown fields, combined
parameters/append_parameters, inferred defaults, nonliteral argument
values, duplicate mappings, and unknown original names reject. Computed values
require the separate explicit argument_expression form above.
Only that computed form additionally admits stable-ID nominal type objects;
provider and caller bindings are resolved independently and rebuilt checked
signature facts must prove exact-identity Sized Copy admission.
Every original parameter must have value mode and a built-in Copy type
(i64, i32, char, u8, usize, [u8; N], f32, f64, or bool),
have value mode and an admitted concrete Copy record/variant type, be
exactly own Bytes, be an exact borrow str or borrow Slice<u8> view, or
belong to the checked owning extension below.
Named Copy admission uses the retained checked HIR parameter identity and
compiler TypeFacts: copy and sized must be true, while
contains_resource and needs_drop must be false. The borrowed-view extension
retains only exact borrow str and borrow Slice<u8> parameters. The owning
extension admits
ordinary string parameters and own record/variant parameters whose checked
facts have copy: false, sized: true, contains_resource: false, and
needs_drop: true. Source string parameters use the ordinary bare type
spelling; the checked HIR supplies their owning mode. They are owners for the
exactly-once retention rule even though source does not spell own string.
Display names and source field shapes do not establish those properties. The
compiler retains exact nominal parameter and return facts for admitted modules
even when a function is absent from the entry/test closure. Concrete generic instances, including admitted
compiler-owned variants, use their complete ordered type-argument identity;
generic target functions remain excluded.
The mapping keeps the original source type spelling, type arguments and mode.
Retained mappings do not convert a record to another record,
alter fields, widen a target profile or add an owning parameter. Classes,
resources, resource-containing aggregates, borrowed nominal/storage types,
borrow Bytes, and shared modes remain excluded. Admitted borrowed views may
be reordered and renamed while preserving their exact source type and mode.
Migration evaluates each view expression once, left to right, into an immutable
borrowed local before the final call. Ordinary loan and provenance verification
must admit those locals and their roots; this operation creates no view, extends
no root, and grants no ownership. Full candidate admission still rejects a
removed parameter referenced by the body or contracts, and any caller that
cannot be legally staged.
For eligible named parameters, change/catalog adds type_identity and
type_provenance, including the nominal declaration identity, ordered argument
identities and exact checked ownership/storage facts. The owning extension
also identifies the primitive String type separately from nominal declarations:
type_identity is string, provenance declaration is null, and arguments
is empty. Its surface mode remains value, while provenance ownership is
own. Owning nominal descriptors keep their declaration and argument identities
and surface mode: own. These descriptors add
checked_owning_parameters_retained_exactly_once to the mapping constraints.
An eligible borrowed-view signature instead adds
borrowed_views_retained_exactly_once; it fabricates no nominal provenance.
Both application and discovery use the same retained-HIR eligibility routine.
Existing scalar parameter descriptors and the intention's {from[,name]} shape
remain unchanged.
Evaluation and lexical hygiene
Reordering call argument expressions directly would reorder effects and checked failures; removing one would silently skip its evaluation. This route instead transforms every existing direct call into a block. For example:
choose(left(), right())
with the mapping [from right, from left] becomes the structural equivalent
of:
{
let spx_sig_stage_0 = left();
let spx_sig_stage_1 = right();
choose(spx_sig_stage_1, spx_sig_stage_0)
}
The real implementation constructs AST nodes, moves the original argument subtrees into the initializers, and delegates source projection to the canonical formatter. It never rewrites text. Every original argument executes at most once, in its original left-to-right position; the first failing argument still prevents evaluation of later arguments. A removed argument still executes when its original position is reached. Newly supplied literals are pure and cannot introduce an additional effect or checked arithmetic failure. The original call's enclosing lazy operand, branch, loop, or contract retains the generated block in place; staging is not hoisted out of that scope.
Fresh staging names avoid a conservative inventory of names across all
candidate modules: function names, parameters, import aliases, local bindings,
assignment targets, variable/call references, and nested match binders.
Previously generated staging names are reserved as well. This prevents an
introduced let from capturing references in a later original argument,
including references to deliberately adversarial staging-like source names.
Display renames are simultaneous: swapping two parameter names is supported.
The substitution follows the original admitted AST's lexical scopes across
requires/ensures, sequential let initializers, assignments, blocks, loop bodies,
match binders and guards, and nested record patterns. A conflicting local or
pattern binder receives a fresh spx_sig_bind_N name, and its references follow
that binding. Field labels, call names, types, and persistent declaration IDs
are not renamed. The postcondition result binding cannot become a parameter
rename destination. A reference to a removed parameter cannot silently become
a reference to a retained parameter renamed to that spelling. Unknown future
binding-bearing or-pattern forms reject until their binding rules are supported.
For each admitted owning parameter, each original expression result is staged in an owning local in original argument order. The final ordinary call receives every retained owner once in mapped order; the real verifier and cleanup-plan builder own transfer and atomic CallCommit semantics. This changes the placement of moves: a later original argument borrowing an owner already moved into an earlier staging local may fail ordinary verification. Such candidates reject; this operation does not bypass loans or promise admission of every previously valid call shape. Resource-bearing types remain unsupported because observable resource finalizers and general settlement order need additional treatment. Admission of resource-free String or nominal owners is not permission to drop or duplicate one: every original owner must reach the final ordinary call exactly once. String place reads retain the ordinary compiler's clone semantics. Staging can therefore add String copies or allocations; this route does not establish unchanged physical move counts, allocation failures, or finalization traces. No custom cleanup, physical finalization authority, or hidden settlement-model action is introduced here.
For one or more owner-view mappings, every original caller
argument is staged exactly once in its original left-to-right order, including
removed or reordered arguments. Only after all original staging completes do
fresh immutable locals evaluate the matching bytes_as_slice(staged_owner) or
string_as_str(staged_owner) in
mapped-parameter order. The
final call receives those views; it never receives or transfers the owners. The
staging block keeps every owner caller-owned, and ordinary loan, failure, and
cleanup replay remain the only authority for their lifetimes and exact cleanup. This changes the borrow
boundary: the caller now creates the loan before the final call and that loan
spans the callee, whereas the original provider created its view inside the
body after preconditions. Ordinary full Project/HIR/loan/cleanup/target replay
must prove that changed lifetime is legal at every caller. The route claims no
equivalent lifetime, failure, cleanup trace, or external-consumer behavior.
The interpreter represents a String-derived borrowed view with copied immutable
UTF-8 bytes plus the exact logical owner ValueId; that implementation detail
is not observable language storage and does not support a physical allocation,
performance, or economics equivalence claim. Native and Wasm lowering retain
their admitted runtime carrier without transferring the owner.
Stable-ID provider bindings determine which direct calls migrate. Existing import aliases stay unchanged, and provider module identity is checked. Traversal includes local calls, imported calls, generic caller bodies, class method bodies, contracts, match guards, unsafe blocks, loops, and nested expressions. It does not rely on the six-family cross-file graph, which omits local calls, and it does not find external consumers.
Admission, bounds, and diagnostics
After the mapping, ordinary candidate application canonically formats and reparses all sources, independently rebuilds the full Project, checks the existing identity/effect/contract/profile requirements, and preserves admitted core target lanes. Removing a parameter still used in a body or contract fails real verification. A caller may first submit a separately admitted body change that removes that use; the signature change must then bind that candidate's new revision. External API compatibility and behavioral equivalence are not implied by preserved exported stable IDs.
The mapping permits at most 4,096 original and resulting parameters before ordinary profile admission imposes its existing narrower export limits. Expression traversal is bounded to depth 256 and 1,048,576 expression nodes, including generated expression growth; lexical pattern traversal has the same bounds. Staging-name allocation is bounded to 1,048,576 attempts. The enclosing Semantic Change node/depth/byte limits and Project source/output limits also remain active. These are deterministic structural bounds, not a total heap memory limit or a performance guarantee.
Retained nominal facts additionally allow at most 4,096 distinct concrete parameter/return and checked body-value type identities per module, under the existing builder-byte budget. Extraction shares this inventory with signature admission. This counts concrete instances, not only source declarations. Removing that extra cap was rejected by automatic security review as weakening a resource boundary; it remains enforced and can reject a larger otherwise admitted type inventory. This limit is not evidence of general unbounded signature support.
SPX-G225 rejects unsupported mappings, unsupported type/mode subjects, unknown or
duplicate parameter selections, inconsistent provider bindings, and name/type
reinterpretation. SPX-G226 rejects existing structural capacity excess.
SPX-G259 rejects unsafe binding substitutions, including contract-result
capture and a still-referenced removed parameter during renaming. SPX-G260
rejects omitted owners or borrowed views. SPX-G261 bounds the additional substitution traversal
and fresh-name allocation with the same depth/node ceilings. SPX-G469
rejects invalid owner-to-view subjects, provider uses, contract references,
additive owner mappings, capture-prone bindings, and rebuilt borrowed-parameter
mismatches. SPX-G477 rejects a duplicate selected owner, SPX-G478 rejects a
ninth owner conversion, and SPX-G479 rejects a shared replacement binding
across owners. Real Project
verification and candidate stale/replay checks retain their existing
language and SPX-G222โSPX-G224 diagnostics. Failed candidate construction
never mutates a previously returned candidate or live source files.
Evidence and non-claims
The unit evidence in
src/project/candidate/signature.rs
is implemented under the v0.4.0 release baseline. It covers reordered Copy results,
retained first-failure selection after dropping or reordering arguments,
parameter/local name capture attempts, imported and declared-effect call
ordering, canonical source round trips, removal of a still-used parameter,
borrowed-view reordering/renaming, and rejection of type changes, omitted
owners/views, and unsupported borrowed modes.
Additional authored regressions cover simultaneous display renames, contract
references, local mutation, match guard capture avoidance, and removed-binding
capture. tests/project_candidate/signature_ownership.rs
authors full Project candidate/replay checks for reordered and renamed owned
byte arguments, one and multiple bounded Bytes/String owner-to-view replacements, exact original
evaluation order followed by mapped-order view derivation, provider
transfer/duplicate/contract/additive/wrong-kind/over-cap rejection, exact replay, and unchanged
live source files. These owner-to-view cases have hosted-green release evidence. The
closed intention schema and change/catalog expose the exact
borrow_slice_from_owner and borrow_str_from_owner fields and exclusions. Authored catalogue checks pin
the lack of external package source rewrite. Authored package-conflict coverage
requires the ordinary parameters facet and the existing
no_automatic_consumer_migration_or_candidate_era_consumer_acceptance
non-claim; it does not migrate or validate an external consumer.
tests/project/signature_owned_values.rs
adds authored cases for bare String and resource-free owned record/variant
parameters, two local callers per target, preserved argument subtrees and order,
renaming, exact replay/recovery, stale rejection, omitted-owner diagnostics,
exact borrowed-view admission, and borrowed nominal exclusion. Its five
regressions pass locally on macOS/Rust 1.98.
Catalog assertions distinguish implicit String ownership from source mode and
preserve legacy Bytes/scalar descriptors. String declarations are retained and
checked outside this fixture's executable closure; this is not a claim that
the mixed owned-variant Project Wasm lane executes internal String literals.
Explicit nongeneric resource-free record and variant type imports may contain
owned Bytes storage and retain their stable nominal identity across modules.
Importing a function whose signature exposes owned nominal arguments retains
SPX-G172 rejection.
Asymmetric conditional variant-owner roots retain SPX-H006 rejection; the
positive variant target consumes its first owner and returns its second through
an admitted straight-line body. These tests do not establish runtime or
physical cleanup behavior, and the admitted release regression corpus is HOSTED GREEN.
The pure reference-interpreter probes are included in the implemented release corpus.
Declared-effect ordering is a structural regression, not hosted effect-runtime
evidence.
This lane does not admit String, borrow Bytes, a projected or nested owner,
more than eight conversions, duplicate or cross-owner aliases, an additive owner alias, borrowed results, parallel
reads, external source rewriting, target-profile widening, runtime support,
provider or network behavior, ABI or deployment compatibility, or consumer
acceptance. Library compilation and static formatting/diff checks passed; the
new cfg and integration regressions are included in the v0.4.0 hosted-green regression corpus.
Additional staging changes expression identities, local storage, generated code, and interpreter fuel consumption. This is not exact operational-cost equivalence, external consumer migration, a full semantic merge, type/return conversion, arbitrary ownership-sensitive migration, or physical owned-call settlement support. Direct byte-owner staging can also change cleanup storage and internal trace labels; it does not claim identical runtime traces or costs. Those wider cases remain open in the graph-operational roadmap. The same storage, trace-label and cost limits apply to String and nominal owner staging.
tests/project/signature_named_copy.rs and
tests/project/signature_catalog.rs author named aggregate staging,
retention/removal, alias/identity, catalogue and independent candidate replay
evidence. Rebase signature fingerprints additionally bind retained nominal
type identities: unchanged source spelling cannot conceal a different record
or variant identity on a concurrent base. The regression in
tests/project_candidate/rebase.rs authors that conflict and unchanged-source
failure behavior. The implemented cases have hosted-green release evidence. neither runtime equivalence nor the
full signature-evolution objective is promoted.