Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
68b98ce
fix(server): read Resources budgets only through view functions
pgherveou Sep 3, 2026
91c512a
fix(server): require V16 metadata instead of falling back to state_ge…
pgherveou Sep 3, 2026
6df956f
fix(host-cli): drop the three superseded on-disk state layouts
pgherveou Sep 3, 2026
6cac8db
fix(codegen): parse only the async_trait future return shape
pgherveou Sep 3, 2026
2dfbbb0
fix(server): announce the boot auth state after the initial session r…
johnthecat Sep 2, 2026
a926670
chore(ios): sync UniFFI bindings for the HostCallbacks auth_state_cha…
johnthecat Sep 2, 2026
61cf8f4
refactor(server): make the boot announcement explicit in the session …
johnthecat Sep 2, 2026
3d66805
fix(server): keep the session store sync task on a weak host reference
johnthecat Sep 2, 2026
26fa293
fix: use runtime statement-slot context
pgherveou Sep 3, 2026
cd342e4
fix: adopt current proof contexts
pgherveou Sep 3, 2026
ae538a1
Merge remote-tracking branch 'origin/fix/current-proof-contexts' into…
pgherveou Sep 3, 2026
148de5c
fix: use runtime statement-slot context
pgherveou Sep 3, 2026
01a9321
fix: adopt current proof contexts
pgherveou Sep 3, 2026
a6f8c4a
fix(server): harden the ring-root generation read and its test scripts
pgherveou Sep 3, 2026
6cdf8d7
Merge remote-tracking branch 'origin/fix/current-proof-contexts' into…
pgherveou Sep 3, 2026
08c8011
Merge remote-tracking branch 'origin/main' into drop-legacy-fallbacks
pgherveou Sep 4, 2026
21aade3
Revert "fix(server): require V16 metadata instead of falling back to …
pgherveou Sep 4, 2026
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
107 changes: 17 additions & 90 deletions rust/crates/truapi-codegen/src/rustdoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,6 @@ fn extract_method(item_id: &str, item: &Item, names: &NameContext) -> Result<Opt
raw_output
} else {
unwrap_future_output(raw_output)
.with_context(|| format!("Method `{name}` has an invalid Future return type"))?
};

let (kind, return_type) = if is_result_subscription_return(output) {
Expand Down Expand Up @@ -929,48 +928,13 @@ fn is_subscription_return(output: &serde_json::Value) -> bool {
.unwrap_or(false)
}

/// Resolve the `Output = T` binding from a Send future method return.
/// Resolve the `Output = T` binding from a Send future method return, or the
/// return itself when it is not one.
///
/// `async_trait` represents `async fn` as
/// `Pin<Box<dyn Future<Output = T> + Send + 'async_trait>>` in rustdoc JSON.
/// Explicit `impl Future<Output = T> + Send` returns are also accepted so the
/// parser remains compatible with older TrUAPI trait snapshots.
fn unwrap_future_output(output: &serde_json::Value) -> Result<&serde_json::Value> {
if let Some(future_output) = extract_async_trait_future_output(output) {
return Ok(future_output);
}
let Some(bounds) = output
.get("impl_trait")
.and_then(serde_json::Value::as_array)
else {
return Ok(output);
};
let future = bounds
.iter()
.filter_map(|bound| bound.get("trait_bound"))
.filter_map(|bound| bound.get("trait"))
.find(|bound| {
bound
.get("path")
.and_then(serde_json::Value::as_str)
.is_some_and(|path| path_suffix(path) == "Future")
})
.context("impl Trait return is missing its Future bound")?;
let constraints = future
.get("args")
.and_then(|args| args.get("angle_bracketed"))
.and_then(|args| args.get("constraints"))
.and_then(serde_json::Value::as_array)
.context("Future bound is missing its associated-type constraints")?;
constraints
.iter()
.find(|constraint| {
constraint.get("name").and_then(serde_json::Value::as_str) == Some("Output")
})
.and_then(|constraint| constraint.get("binding"))
.and_then(|binding| binding.get("equality"))
.and_then(|equality| equality.get("type"))
.context("Future bound is missing its Output equality")
fn unwrap_future_output(output: &serde_json::Value) -> &serde_json::Value {
extract_async_trait_future_output(output).unwrap_or(output)
}

fn extract_async_trait_future_output(output: &serde_json::Value) -> Option<&serde_json::Value> {
Expand Down Expand Up @@ -1646,55 +1610,6 @@ mod tests {
);
}

#[test]
fn unwraps_send_future_output() {
let output = serde_json::json!({
"impl_trait": [
{
"trait_bound": {
"trait": {
"path": "core::future::Future",
"args": {
"angle_bracketed": {
"args": [],
"constraints": [
{
"name": "Output",
"binding": {
"equality": {
"type": {
"resolved_path": {
"path": "Result",
"id": 1,
"args": null
}
}
}
}
}
]
}
}
}
}
},
{
"trait_bound": {
"trait": {
"path": "Send",
"id": 2,
"args": null
}
}
}
]
});

let unwrapped = unwrap_future_output(&output).expect("future output");

assert_eq!(get_resolved_name(unwrapped).as_deref(), Some("Result"));
}

#[test]
fn unwraps_async_trait_send_future_output() {
let output = serde_json::json!({
Expand Down Expand Up @@ -1759,8 +1674,20 @@ mod tests {
}
});

let unwrapped = unwrap_future_output(&output).expect("async-trait future output");
let unwrapped = unwrap_future_output(&output);

assert_eq!(get_resolved_name(unwrapped).as_deref(), Some("Result"));
}

/// A return that is not an `async_trait` future is the method's own type, so
/// it has to pass through untouched. Rejecting it here would turn every
/// non-async method into a parse failure instead of a plain return type.
#[test]
fn a_return_that_is_not_a_future_passes_through() {
let output = serde_json::json!({
"resolved_path": { "path": "Result", "id": 1, "args": null }
});

assert_eq!(unwrap_future_output(&output), &output);
}
}
18 changes: 8 additions & 10 deletions rust/crates/truapi-host-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,9 @@ settings containing arguments, such as `EDITOR='code --wait'`, are supported.
Managed sessions isolate signer accounts, product/core storage, and permissions.
Once a signer identity is known, its public session name is the Lite username
and its files live under
`<base-path>/<network>/<username>_signing_host`. Provisional and legacy named
sessions are promoted to that user-owned root, so an old name such as `pgtest`
does not remain the durable namespace. The selected username is remembered per
`<base-path>/<network>/<username>_signing_host`. Provisional named sessions
are promoted to that user-owned root, so an old name such as `pgtest` does not
remain the durable namespace. The selected username is remembered per
network but is not repeated in the status bar as a separate session field.
`default` remains only as a compatibility/bootstrap location until a username
is resolved. It is hidden from session completion and listing and cannot be
Expand Down Expand Up @@ -390,8 +390,8 @@ other saved pairings and the signing identity are unchanged.
`/session --clear <name>` permanently deletes that session's local signer
keys, scripts, core/product storage, and permissions. `/session --clear-all`
does the same for every signing-host session on the current network, including
legacy bootstrap state, while preserving other networks and pairing-host
state. Neither command deregisters an on-chain username. The interactive UI
the network's signing-host bootstrap state, while preserving other networks and
pairing-host state. Neither command deregisters an on-chain username. The interactive UI
asks for `[y/N]` confirmation. `exec` treats the explicit one-shot command as
confirmation and runs it immediately. Clearing an inactive named session keeps
the host running; clearing the active session or all sessions stops the signing
Expand Down Expand Up @@ -483,16 +483,14 @@ the selected id, so the newly selected product sees its own state. The next
Pairing-host state follows the same identity rule under
`<base-path>/<network>/<username>_pairing_host`. Before the first identity is
known it uses the small `<network>/pairing-host` bootstrap; connecting moves
legacy bootstrap data to the first resolved user. After `/logout`, connecting
that bootstrap data to the first resolved user. After `/logout`, connecting
as a different user swaps to that user's KV/core namespace instead of carrying
the previous user's product data forward.

Product-local KV is persisted independently under each identity root as
`storage/<safe-product-slug>--<hash>.json`. Each document records its normalized
product id and raw product keys. On first use, the older combined
`product-storage.json` in that profile is split into those files and retained
as `product-storage.v1.json.migrated`. Product and core JSON writes use a
flushed temporary file and atomic rename.
product id and raw product keys. Product and core JSON writes use a flushed
temporary file and atomic rename.

Six scripts ship under `js/scripts/`:

Expand Down
27 changes: 8 additions & 19 deletions rust/crates/truapi-host-cli/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1168,11 +1168,7 @@ user-selectable and is omitted from session completion and listing.
When a managed session has no connected user, startup and bare `/session` add
an actionable transcript notice directing the user to `/session <name>`.

`/session --list` includes:

- legacy directories under `signing-host/sessions/`; and
- network directories ending in `_signing_host`.

`/session --list` includes the network directories ending in `_signing_host`.
The active session is marked with `*`.

`/session <name>` provisions the target before replacing the current runtime:
Expand Down Expand Up @@ -1222,12 +1218,11 @@ phrase is not written locally until the replacement runtime activates
successfully.

`/session --clear <name>` removes the durable name shown by `/session --list`,
its identity or legacy session directory, any separate legacy product storage,
and the matching network account cached in the compatibility account store.
`/session --clear-all` removes every such session, the network's signing-host
bootstrap state, and every compatibility account record for that network. It
does not remove pairing-host state, another network's records, externally
referenced scripts, or on-chain usernames.
its identity directory, and the matching network account cached in the
compatibility account store. `/session --clear-all` removes every such session,
the network's signing-host bootstrap state, and every compatibility account
record for that network. It does not remove pairing-host state, another
network's records, externally referenced scripts, or on-chain usernames.

The interactive UI describes the data loss and uses the existing `[y/N]`
approval. `exec` executes these explicit one-shot commands without another
Expand Down Expand Up @@ -1260,15 +1255,14 @@ The layout may contain compatibility paths as well as identity-owned paths:
storage/
default/
<product-file>.json
sessions/ # accepted legacy session layout
<legacy-name>/

pairing-host/
current-user
session.json # bootstrap script metadata, when used
core-storage.json # bootstrap auth/core state
scripts/
storage/ # or legacy storage/default/
storage/
<product-file>.json

<username>_signing_host/
accounts.json
Expand Down Expand Up @@ -1388,11 +1382,6 @@ The version `1` JSON document is:
The core has already removed its product namespace before the CLI stores the
raw key. Identity and host role are isolated by the parent directory.

Legacy combined `product-storage.json` keys are decoded with
`ProductStorageKey` and split into per-product files. A fully safe migration is
retained as `product-storage.v1.json.migrated`. An undecodable legacy key or
document prevents the backup rename.

Noncanonical product filenames, unsupported versions, invalid ids, and invalid
hex values are ignored with warnings.

Expand Down
Loading