From d33d5e80baf9bfe5e8db7e988abfdffc6dc7fb79 Mon Sep 17 00:00:00 2001 From: udo Date: Tue, 14 Jul 2026 21:32:29 +0000 Subject: [PATCH] fix canonical component artifact identity --- docs/wasm-runtime-architecture.md | 4 +++- site/tests/components.uce | 4 ++++ site/tests/relative-child.uce | 6 ++++++ src/wasm/worker.cpp | 9 +++++++-- 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 site/tests/relative-child.uce diff --git a/docs/wasm-runtime-architecture.md b/docs/wasm-runtime-architecture.md index 532761c..8bd1722 100644 --- a/docs/wasm-runtime-architecture.md +++ b/docs/wasm-runtime-architecture.md @@ -396,7 +396,9 @@ header free-functions are `inline`. The wasm backend exposes only declarations child and again with that child activated. This guards the component-slot cache invariant that a cache hit must restore both the function-table slot and the resolved unit path; otherwise nested relative targets resolve from - the caller after a repeated parent invocation. + the caller after a repeated parent invocation. Resolved component paths are + canonical absolute paths: equivalent spellings containing `.` or `..` must + share one source, compile, artifact, and module-cache identity. - **WebSocket end-to-end**: a headless client performs a raw WS handshake to `:HTTP_PORT` with path `/site/tests/websockets.ws.uce` (self-resolving diff --git a/site/tests/components.uce b/site/tests/components.uce index 444d40f..090f0c4 100644 --- a/site/tests/components.uce +++ b/site/tests/components.uce @@ -27,6 +27,8 @@ RENDER(Request& context) String lazy_first = component("components/lazy-parent", lazy_props, context); lazy_props["show_child"].set_bool(true); String lazy_second = component("components/lazy-parent", lazy_props, context); + String relative_resolved = component_resolve("components/../relative-child"); + String relative_markup = component("components/../relative-child", props, context); String alias_markup = component("components/props_alias", props, context); // Named handler through the string-returning component("unit:NAME") form // (distinct path from component_render below): resolves __uce_component_HEADER @@ -47,6 +49,8 @@ RENDER(Request& context) check("component_resolve()", resolved != "", resolved); check("component()", panel_markup.find("Component Output") != String::npos && panel_markup.find("This body comes from component()") != String::npos, panel_markup); check("cached component retains path for a newly activated relative child", lazy_first.find("lazy-child") == String::npos && lazy_second.find("lazy-child") != String::npos, lazy_second); + check("relative component path has canonical identity", relative_resolved.find("/../") == String::npos && str_ends_with(relative_resolved, "/site/tests/relative-child.uce"), relative_resolved); + check("relative component path renders its canonical artifact", relative_markup.find("relative-child") != String::npos, relative_markup); check("COMPONENT(Request& props) alias", alias_markup.find("alias=Component Output") != String::npos && alias_markup.find("body=This body comes from component()") != String::npos, alias_markup); check("component_render() named handler", footer_markup.find("Named footer render") != String::npos, footer_markup); check("component(\"unit:NAME\") named handler", header_markup.find("Component Output") != String::npos && header_markup.find("This body comes from component()") == String::npos, header_markup); diff --git a/site/tests/relative-child.uce b/site/tests/relative-child.uce new file mode 100644 index 0000000..a27d27d --- /dev/null +++ b/site/tests/relative-child.uce @@ -0,0 +1,6 @@ +#include "testlib.h" + +COMPONENT(Request& context) +{ + ?>relative-child