Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2504,6 +2504,10 @@ inventories and validates their ownership; its convenience fields are unchanged.
Portable artifacts remain bounded to 4 MiB. Version 1 artifacts must be
regenerated with the matching CLI; they are not accepted as version 2.

Modeled projection contracts [share repeated operations and selector schemas](docs/compact-projection-contracts.md)
when smaller, preserving every current and retained event selector within the
same manifest budget. Runtime program identities and execution are unchanged.

For a full application, run `distributed build` or `distributed dev` from its
Cargo workspace root. The CLI discovers the typed application, runtime binary,
conventional `ui/` SvelteKit app, and `@hops-ops/distributed` dependency. A
Expand Down
36 changes: 36 additions & 0 deletions docs/compact-projection-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Shared projection material in application manifests

Several exact event selectors can apply the same mutation. This is common when
retained state events and current events update the same read model. The
`projection!` authoring form already accepts multiple events per mutation;
application manifests now share identical operation lists and selector body
schemas when that reduces their encoded size. No authoring change is required.

The modeled projection's `program` value uses the explicit
`shared_projection_program_v1` encoding when smaller than the expanded form.
Its `program` retains all original fields and every exact arm, replacing only
`operations` with `operations_ref` and selector `body_schema` with
`body_schema_ref`. These zero-based references address the wrapper's canonical
`operation_sets` and `body_schemas` tables. Operations are interned by their
complete canonical JSON, including ordering, IDs, expressions and effects;
schemas are interned by exact string equality. No historical selector is
removed, merged, treated as current, or exempted from replay validation.

The runtime projection IR, program and binding IDs, server execution and client
projection-program exports are unchanged. The application Surface's artifact
fingerprints change deterministically when shared storage is used. Rebuild the
service and generated clients together. Existing expanded manifests remain accepted
and retain their existing encoding and fingerprints when decoded and re-encoded.

Tools reading the opaque modeled `program` JSON can use
`distributed::application::expand_projection_program_contract(&value)` to read
either representation. Expansion returns exactly the original program JSON.
Unsupported encodings, invalid references, inline/reference ambiguity, duplicate
or unused table entries, and noncanonical table order fail validation.

The complete encoded application manifest remains bounded at 4 MiB. Each opaque
modeled value remains bounded at 1 MiB; an expanded program also must fit the
existing 1 MiB budget. The decoder checks repeated byte costs before copying
shared values and validates programs individually, without retaining an expanded
copy of the whole application. Sharing is storage normalization, not an increase
in wire limits or a replacement for retained-history replay.
5 changes: 5 additions & 0 deletions docs/unsigned-command-inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ Code that manually constructs `CommandTypeField` or `SurfaceTypeField` must
set `unsigned_integer` to the matching `CommandUnsignedInteger` variant, or
`None` for unrefined fields. Prefer derives so this metadata follows the Rust
type. A refinement on a non-`BigInt` field is rejected.

Typed application manifests preserve `unsigned_integer` in canonical Surface
command input and output contracts, including nested types. Decoding and
re-encoding retains the same contract bytes and fingerprints. Unrefined fields
omit this metadata, preserving their existing canonical representation.
13 changes: 11 additions & 2 deletions src/application/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,11 @@ fn validate_projection(
let fields = modeled.as_object().ok_or_else(|| {
ApplicationError::InvalidSpec("modeled projection must be an object".into())
})?;
if let Some(program) = fields.get("program") {
// Validate each shared value independently without retaining the
// expanded copies in the manifest or increasing the wire budget.
super::expand_projection_program_contract(program)?;
}
let program_id = fields
.get("program_id")
.and_then(serde_json::Value::as_str)
Expand Down Expand Up @@ -1852,14 +1857,18 @@ fn surface_command_type_value(
serde_json::json!({
"name": definition.name,
"fields": definition.fields.iter().map(|field| {
serde_json::json!({
let mut value = serde_json::json!({
"name": field.name,
"type_name": field.type_name,
"nullable": field.nullable,
"list": field.list,
"item_nullable": field.item_nullable,
"nested": field.nested.as_deref().map(|nested| surface_command_type_value(Some(nested))),
})
});
if let Some(unsigned) = field.unsigned_integer {
value["unsigned_integer"] = serde_json::json!(unsigned);
}
value
}).collect::<Vec<_>>(),
})
}
Expand Down
3 changes: 3 additions & 0 deletions src/application/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod manifest;
mod module;
mod mount;
mod plan;
mod projection_contract;
mod registration;
mod runtime;
mod runtime_host;
Expand Down Expand Up @@ -50,6 +51,8 @@ pub use plan::{
compile_deployment_plan, DeploymentPlan, PlanFingerprint, ProcessIntent, ProcessPlan,
DEPLOYMENT_PLAN_SCHEMA_VERSION, MAX_DEPLOYMENT_PLAN_BYTES,
};
pub(crate) use projection_contract::compact_projection_program_contract;
pub use projection_contract::expand_projection_program_contract;
pub use registration::{Application, ApplicationBuilder, ContractCompiler};
pub use runtime::{Runtime, RuntimeDialect};
pub use runtime_host::{bind_single_process, CapabilityProviders, RuntimeHost};
Expand Down
Loading
Loading