From 9bf7c4852a79a9abd5d6b0afbcc7fe1ab5a047da Mon Sep 17 00:00:00 2001 From: Michael Jerris Date: Mon, 27 Jul 2026 09:09:11 -0400 Subject: [PATCH 01/88] fix(logging): core get_logger returns a Logger, not a bool status flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `signalwire::core::logging_config::get_logger(name)` — the entry point the reference oracle records as `signalwire.core.logging_config.get_logger`, and which the 2026-07-24 logger ruling makes MANDATORY surface — returned `bool` (the internal configured-once flag) and discarded `name` entirely: bool get_logger(const std::string& /*name*/) { if (!g_configured.load()) configure_logging(); return g_configured.load(); } So a caller could not obtain a logger from the canonical entry point at all. They had to already know to reach for a DIFFERENT header. Every other port returns a logger object here (ts Logger, go *logging.Logger, java/php/rust/ dotnet Logger, ruby Logging::Logger), so `bool` was a functional gap, not a C++ idiom. Now returns `signalwire::logging::Logger` by delegating to the existing `signalwire::logging::get_logger(name)`, which already built the named-logger form. This entry point adds only the configure-first guarantee — exactly the reference's single-entry-point contract. Zero callers depended on the bool (grepped), so nothing breaks. WHY IT WENT UNNOTICED: the reference records this function's return as `any` (structlog's BoundLogger is not an SDK class), and diff_port_signatures.py:153 returns True when EITHER side is `any`. So `bool` compared clean. The gate is blind here by construction — the concrete cost of an `any` return. Note C++ has THREE get_logger overloads across two namespaces, with two different Logger classes and two different LogLevel enums (`Debug`/`Info`/… vs `DEBUG`/`INFO`/…): `signalwire::get_logger()` → process singleton by reference; `signalwire::logging::get_logger(name)` → named Logger by value; and this one, the oracle contract point, which now delegates to the named form. Easy to conflate — I did, first wiring this to the singleton. TESTS: 10/10 in the logging suite, and the output proves the contract holds — `[ERROR][ContractCheck] contract smoke` shows a named, usable logger where the old signature could only yield a bool. The regression guard is a static_assert on the return type: it fails to COMPILE if this ever reverts to bool, which needs no global state to check. Also fixed `logging_named_get_logger`, which was `auto logger = ...` plus a "should not crash" comment and NO assertion — it would have passed with the name dropped entirely. Deliberately NOT asserting on captured log output: `logging::Logger` streams to std::cerr with no injection point and keeps name_ private, so observing it means swapping the PROCESS-WIDE cerr buffer — and test_main.cpp runs tests on multiple threads, so that steals concurrent tests' output including their ASSERT text. RULES.md §4: isolation comes from scoping, never from mutating shared state. PRE-EXISTING, NOT FROM THIS CHANGE: cpp's signature gate exits 1 with 192 drifts (AgentServer.enable_sip_routing, ToolDecorator.*, …) on clean main as well — both from the committed artifact and from a fresh regen of an unmodified tree. This commit's regen moves exactly ONE line, `"returns": "class:signalwire.logging. logger.Logger"`, and adds zero drifts. The 192 are a separate stale-artifact backlog. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GFKJhLvfV8yGrASwqxdgaf --- include/signalwire/core/logging_config.hpp | 35 +++++++++++---- port_signatures.json | 2 +- src/core/logging_config.cpp | 9 ++-- tests/test_logging.cpp | 52 +++++++++++++++++++++- 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/include/signalwire/core/logging_config.hpp b/include/signalwire/core/logging_config.hpp index aa0ea12..fd11b30 100644 --- a/include/signalwire/core/logging_config.hpp +++ b/include/signalwire/core/logging_config.hpp @@ -7,6 +7,10 @@ #include +// `get_logger` below returns a NAMED logger by value, so the type must be +// complete here (not merely forward-declared). +#include "signalwire/logging/logger.hpp" + namespace signalwire { namespace core { namespace logging_config { @@ -49,16 +53,31 @@ void configure_logging(); void reset_logging_configuration(); /** - * Return whether ``configure_logging`` has already run (the internal flag). - * Ensures the logger is configured on first access, mirroring Python's - * ``get_logger`` single-entry-point behavior. The C++ logger is a process - * singleton (see ``signalwire::get_logger``); this helper guarantees it has - * been configured before use and returns the configured state. + * Obtain the SDK logger, configuring it on first access. This is the single + * entry point every SDK module should use, mirroring Python's + * ``signalwire.core.logging_config.get_logger``. + * + * Returns a NAMED logger so a CALLER CAN ACTUALLY LOG, and so ``name`` means + * something. It previously returned ``bool`` (the internal configured-once flag) + * and discarded ``name`` entirely, which left the canonical entry point unable + * to hand back a logger at all — a caller had to already know to reach into a + * different header. Every other port returns a logger object here (ts + * ``Logger``, go ``*logging.Logger``, java / php / rust / dotnet ``Logger``, + * ruby ``Logging::Logger``), so the ``bool`` form was a functional gap, not an + * idiom. It went unnoticed because the reference records this function's return + * as ``any``, and the signature differ treats ``any`` as matching anything on + * either side. + * + * Delegates to ``signalwire::logging::get_logger(name)``, which already built + * the named-logger form — this entry point simply guarantees configuration has + * happened first, which is exactly the reference's single-entry-point contract. + * (Note ``signalwire::get_logger()``, no argument, is a THIRD overload returning + * the process singleton by reference; it is unrelated to this contract.) * - * @param name Logical logger name (recorded for API compatibility; the C++ - * Logger is a process singleton so the name is advisory). + * @param name Logical logger name, as in the reference's per-module + * ``get_logger(__name__)``. */ -bool get_logger(const std::string& name); +::signalwire::logging::Logger get_logger(const std::string& name); /** * Strip control characters (to prevent log injection) from ``value``. diff --git a/port_signatures.json b/port_signatures.json index 101f42b..c45946a 100644 --- a/port_signatures.json +++ b/port_signatures.json @@ -5320,7 +5320,7 @@ "required": true } ], - "returns": "bool" + "returns": "class:signalwire.logging.logger.Logger" }, "reset_logging_configuration": { "params": [], diff --git a/src/core/logging_config.cpp b/src/core/logging_config.cpp index b339e86..5bfceb7 100644 --- a/src/core/logging_config.cpp +++ b/src/core/logging_config.cpp @@ -65,12 +65,15 @@ void configure_logging() { void reset_logging_configuration() { g_configured.store(false); } -bool get_logger(const std::string& /*name*/) { - // Single entry point: ensure the process logger is configured before use. +::signalwire::logging::Logger get_logger(const std::string& name) { + // Single entry point (the reference's contract): guarantee logging is + // configured, then hand back a NAMED logger so the caller can actually log + // AND `name` means something. Previously returned the configured-once bool and + // discarded `name`, so the canonical entry point could not produce a logger. if (!g_configured.load()) { configure_logging(); } - return g_configured.load(); + return ::signalwire::logging::get_logger(name); } std::string strip_control_chars(const std::string& value) { diff --git a/tests/test_logging.cpp b/tests/test_logging.cpp index 4a1374f..301af94 100644 --- a/tests/test_logging.cpp +++ b/tests/test_logging.cpp @@ -1,4 +1,8 @@ // Logging system tests +#include +#include + +#include "signalwire/core/logging_config.hpp" #include "signalwire/logging.hpp" #include "signalwire/logging/logger.hpp" @@ -75,7 +79,53 @@ TEST(logging_get_logger_function) { } TEST(logging_named_get_logger) { + // The factory hands back a Logger constructed with the requested name. + // `logging::Logger` streams straight to std::cerr with no injection point + // and keeps name_ private, so the name cannot be observed without either + // inventing an accessor (surface we do not need) or swapping the PROCESS-WIDE + // cerr buffer. The latter is not an option here: test_main.cpp runs tests on + // MULTIPLE THREADS, so redirecting the global cerr steals concurrent tests' + // output — including their ASSERT failure text. (Measured: doing that turned + // a 2037/1 run into 1497 passed / 542 "failed" with an empty log. RULES.md §4 + // — isolation comes from scoping, never from mutating shared state.) + // So assert what IS observable without global mutation: construction succeeds + // and the value is usable. auto logger = logging::get_logger("MyComponent"); - // Should not crash + logger.info("named-logger smoke"); + return true; +} + +// The CONTRACT entry point — recorded by the reference oracle as +// `signalwire.core.logging_config.get_logger` — must hand back a LOGGER, not a +// status flag. It previously returned `bool` (the internal configured-once flag) +// and discarded `name` entirely, so a caller could not obtain a logger from the +// canonical entry point at all; they had to already know to reach into another +// header. Nothing caught it because the reference records this return as `any`, +// and the signature differ treats `any` as matching anything on either side, so +// `bool` compared clean. +// +// The static_assert IS the regression guard: it fails to COMPILE if the return +// type ever reverts to bool (or to anything that is not a logging::Logger), which +// is exactly the defect, and it needs no global state to check. +TEST(logging_config_get_logger_returns_a_logger_not_a_flag) { + static_assert( + std::is_same_v, + "core::logging_config::get_logger must return a logging::Logger — a bool " + "return means the canonical entry point cannot hand a caller a logger"); + auto logger = core::logging_config::get_logger("ContractCheck"); + logger.error("contract smoke"); + return true; +} + +// The other half of the single-entry-point contract: asking for a logger must +// configure logging first, even straight after a reset. +TEST(logging_config_get_logger_configures_on_first_access) { + core::logging_config::reset_logging_configuration(); + auto logger = core::logging_config::get_logger("ConfigureOnAccess"); + logger.info("configured-on-access smoke"); + // configure_logging() ran as part of the call above; a second call must be + // idempotent rather than throwing or re-initialising into a bad state. + core::logging_config::configure_logging(); return true; } From dcb269af7dbef3c264364cdb94444dd85b9a3874 Mon Sep 17 00:00:00 2001 From: Michael Jerris Date: Mon, 27 Jul 2026 09:24:30 -0400 Subject: [PATCH 02/88] fix(lint): clear 20 clang-tidy violations my include exposed in logger.hpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `include/signalwire/logging/logger.hpp` had been ORPHANED from the lint graph: nothing in the linted set included it. The previous commit adds `#include "signalwire/logging/logger.hpp"` to `core/logging_config.hpp`, which pulled it in and exposed 20 pre-existing violations. So they are pre-existing code, but MY change is why they now fail the gate — LINT went red on this branch, and leaving it red is not an option. Fixed all 20, no suppressions and no allowlist entry: - performance-avoid-endl x4: `<< std::endl` -> `<< "\n"`. Behaviour preserved: endl also flushes, but std::cerr is unit-buffered, so each write already flushes regardless. - readability-braces-around-statements x18 (10 sites): braced every single-statement `if` in get_log_level() and the four log methods. - readability-redundant-string-init x2: `std::string level = "";` -> declare. VERIFIED: run-lint.sh exit 0 (was 20 errors), run-format.sh --check exit 0, run-tests.sh logging 10/10. ALSO RETRACTING A FALSE ALARM FROM THE PREVIOUS COMMIT MESSAGE. It claimed cpp's signature gate "exits 1 with 192 drifts on clean main". That was MY invocation error, not a real red: with --surface-omissions + --surface-additions: exit 0 without them (what I ran): exit 1 CLAUDE.md §5b states the bar explicitly — "Exit code 0 from diff_port_signatures.py (with all 3 surface flags)". I omitted two of the three and read the resulting phantom as a backlog. The real gate agrees: `run-ci.sh --rules SIGNATURES,DRIFT` => `==> CI PASS`. The 192 phantoms were dominated by the known REST-shape family (25 on RestClient) — and cpp is not even an outlier there: every port emits the same 6 Namespace classes while flattening a varying number of accessors onto RestClient (rust 32, php 28, cpp 27, dotnet 6, ruby 6, java 5, go 1, ts 1). That is task #38's crud_bases/REST-shape item, and the surface ledgers already account for it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GFKJhLvfV8yGrASwqxdgaf --- include/signalwire/logging/logger.hpp | 42 ++++++++++++++++++--------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/include/signalwire/logging/logger.hpp b/include/signalwire/logging/logger.hpp index 601096a..6056339 100644 --- a/include/signalwire/logging/logger.hpp +++ b/include/signalwire/logging/logger.hpp @@ -13,14 +13,24 @@ namespace logging { enum class LogLevel { DEBUG, INFO, WARN, ERROR, OFF }; [[nodiscard]] inline LogLevel get_log_level() { - std::string level = ""; + std::string level; const char* env = std::getenv("SIGNALWIRE_LOG_LEVEL"); - if (env) level = env; + if (env) { + level = env; + } const char* mode = std::getenv("SIGNALWIRE_LOG_MODE"); - if (mode && std::string(mode) == "off") return LogLevel::OFF; - if (level == "debug") return LogLevel::DEBUG; - if (level == "warn") return LogLevel::WARN; - if (level == "error") return LogLevel::ERROR; + if (mode && std::string(mode) == "off") { + return LogLevel::OFF; + } + if (level == "debug") { + return LogLevel::DEBUG; + } + if (level == "warn") { + return LogLevel::WARN; + } + if (level == "error") { + return LogLevel::ERROR; + } return LogLevel::INFO; } @@ -33,20 +43,24 @@ class Logger { // or a substring view with no allocation. (The constructor's `name` // stays std::string: it is retained in name_.) void debug(std::string_view msg) const { - if (get_log_level() <= LogLevel::DEBUG) - std::cerr << "[DEBUG][" << name_ << "] " << msg << std::endl; + if (get_log_level() <= LogLevel::DEBUG) { + std::cerr << "[DEBUG][" << name_ << "] " << msg << "\n"; + } } void info(std::string_view msg) const { - if (get_log_level() <= LogLevel::INFO) - std::cerr << "[INFO][" << name_ << "] " << msg << std::endl; + if (get_log_level() <= LogLevel::INFO) { + std::cerr << "[INFO][" << name_ << "] " << msg << "\n"; + } } void warn(std::string_view msg) const { - if (get_log_level() <= LogLevel::WARN) - std::cerr << "[WARN][" << name_ << "] " << msg << std::endl; + if (get_log_level() <= LogLevel::WARN) { + std::cerr << "[WARN][" << name_ << "] " << msg << "\n"; + } } void error(std::string_view msg) const { - if (get_log_level() <= LogLevel::ERROR) - std::cerr << "[ERROR][" << name_ << "] " << msg << std::endl; + if (get_log_level() <= LogLevel::ERROR) { + std::cerr << "[ERROR][" << name_ << "] " << msg << "\n"; + } } private: From da8326e17b9e89842619a8b7e93d3778b2b72a0b Mon Sep 17 00:00:00 2001 From: Michael Jerris Date: Mon, 27 Jul 2026 10:06:36 -0400 Subject: [PATCH 03/88] =?UTF-8?q?wave6:=20retire=20dead=20ctor=20entries?= =?UTF-8?q?=20(ALLOWLIST=5FDISCIPLINE=20=C2=A7495,=20shared-diff=20fold)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared-diff ctor/dunder fold (_is_folded_dunder_member, porting-sdk #125) excludes __init__-as-a-member findings while the class is in the reference's construction node, so the ledger entries that excused them can no longer be reached. Remove the 59 dead entries. PORT_SIGNATURE_OMISSIONS.md entries: 331 -> 272 (-59) All 59 are .__init__ where is in the oracle's construction node; cpp had no non-__init__ dunder entries. Also drops three rationale-tag definitions now cited by zero entries (cpp_constructor_default_only, cpp_questions_string, cpp_rest_error_field_layout) and the emptied "### __init__ default-only / config-struct construction" header. Three __init__ entries are NOT covered and stay: signalwire.rest._base.{CrudResource,CrudWithAddresses,ReadResource}.__init__ -- absent from the construction node, and the C++ port emits an __init__ the reference does not record, so each is a real extra-port finding. Stripping them reds the already-folded differ (exit=1, 3 drifts). Excused divergences: the FOLD moves them 1118 -> 1059 (measured with the pre-fold differ at 66d351a^); the PRUNE leaves them flat at 1059, because the fold continues before the excusal branch. The section-10 construction contract is untouched: port construction classes 146 -> 146. PORT_OMISSIONS.md and PORT_ADDITIONS.md are deliberately untouched -- different tool, hard dead-entry gate. Merge order: porting-sdk #125 FIRST. Until it merges (or PORTING_SDK_REF is pinned), this PR's CI is red by design -- the fold and the prune are mutually dependent. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GFKJhLvfV8yGrASwqxdgaf --- PORT_SIGNATURE_OMISSIONS.md | 78 ------------------------------------- 1 file changed, 78 deletions(-) diff --git a/PORT_SIGNATURE_OMISSIONS.md b/PORT_SIGNATURE_OMISSIONS.md index f0a83d8..ee62f36 100644 --- a/PORT_SIGNATURE_OMISSIONS.md +++ b/PORT_SIGNATURE_OMISSIONS.md @@ -72,14 +72,6 @@ diverge from Python's. return is structurally a subtype of the C++ unified Action — same callable contract, lower static guarantee. Tracked for audit clarity; not load-bearing for cross-language code. -- `cpp_constructor_default_only`: C++ ships an explicit default-only or - config-struct constructor where Python's `__init__` enumerates each - field as a keyword argument. Construction is reached either by - calling the no-arg constructor and using setters, or by passing a - pre-built config struct (`RelayClient::Config`, - `AgentBase::Builder`). The full set of Python `__init__` keywords is - reachable through the C++ setters / config-struct fields — same - callable contract, different idiomatic shape. - `cpp_typed_overload_subset`: C++ exposes a smaller-arity overload of the method where Python merges all variants into one signature with default-valued kwargs. The remaining options are reached either via @@ -172,10 +164,6 @@ diverge from Python's. `params: dict` runtime configuration; C++ accepts the parent agent reference instead, with skill-specific options reached via `SkillBase` setter methods. Same load-time configuration contract. -- `cpp_questions_string`: Python's - `InfoGathererAgent.__init__(questions: list[dict])` accepts a list - of typed question dicts; C++ accepts a `string` JSON spec for the - same data. Construction-time only. - `cpp_dial_int_timeout`: paired with `cpp_idiom_optional_int_timeout` — `RelayClient.dial(dial_timeout: int)` uses `0` for "no timeout"; Python uses `Optional[float]`. @@ -224,10 +212,6 @@ diverge from Python's. optional `params` query-string dict; C++ omits this — POST URLs with query params aren't used by the SignalWire REST API surface C++ targets. -- `cpp_rest_error_field_layout`: C++ `SignalWireRestError.__init__` - takes `(status, message, body)`; Python takes - `(status_code, body, url, method)`. The two carry the same - diagnostic content under different field names. - `cpp_typed_setter_no_extra_dict`: Python's `PhoneNumbersResource.set_*` helpers accept an `extra: dict` catch-all for fields the typed setters don't enumerate; C++ @@ -248,27 +232,6 @@ diverge from Python's. ## Documented signature divergences -### __init__ default-only / config-struct construction - -signalwire.agent_server.AgentServer.__init__: cpp_constructor_default_only -signalwire.core.agent_base.AgentBase.__init__: cpp_constructor_default_only -signalwire.core.contexts.Context.__init__: cpp_constructor_default_only -signalwire.core.contexts.ContextBuilder.__init__: cpp_constructor_default_only -signalwire.core.contexts.Step.__init__: cpp_constructor_default_only -signalwire.core.security.session_manager.SessionManager.__init__: cpp_constructor_default_only -signalwire.core.skill_base.SkillBase.__init__: cpp_constructor_default_only -signalwire.core.skill_manager.SkillManager.__init__: cpp_constructor_default_only -signalwire.core.swml_service.SWMLService.__init__: cpp_constructor_default_only -signalwire.prefabs.concierge.ConciergeAgent.__init__: cpp_constructor_default_only -signalwire.prefabs.faq_bot.FAQBotAgent.__init__: cpp_constructor_default_only -signalwire.prefabs.receptionist.ReceptionistAgent.__init__: cpp_constructor_default_only -signalwire.prefabs.survey.SurveyAgent.__init__: cpp_constructor_default_only -signalwire.relay.call.Call.__init__: cpp_constructor_default_only -signalwire.relay.client.RelayClient.__init__: cpp_constructor_default_only -signalwire.relay.message.Message.__init__: cpp_constructor_default_only -signalwire.rest._base.SignalWireRestError.__init__: cpp_rest_error_field_layout -signalwire.prefabs.info_gatherer.InfoGathererAgent.__init__: cpp_questions_string - ### Unified Action — Call methods signalwire.relay.call.Call.ai: cpp_unified_action @@ -371,12 +334,10 @@ signalwire.skills.registry.SkillRegistry.list_skills: cpp_list_skills_names ## POM (signalwire.pom.pom) — C++ idiom -signalwire.pom.pom.PromptObjectModel.__init__: cpp-overload-set — C++ exposes overloaded ctors (default, copy-from-list, copy-from-PromptObjectModel) where Python has a single __init__ with default arg signalwire.pom.pom.PromptObjectModel.add_section: cpp-overload-set — C++ exposes 4 overloads (title-only / title+body / title+bullets / full) where Python uses single positional+kwargs signalwire.pom.pom.PromptObjectModel.add_pom_as_subsection: cpp-typed-overload — C++ takes typed Section& or std::string title parameter where Python uses Union[str, Section] signalwire.pom.pom.PromptObjectModel.from_json: cpp-typed-overload — C++ takes const std::string& where Python's from_json takes Union[str, dict] signalwire.pom.pom.PromptObjectModel.from_yaml: cpp-typed-overload — C++ takes const std::string& where Python's from_yaml takes Union[str, dict] -signalwire.pom.pom.Section.__init__: cpp-overload-set — C++ exposes overloaded ctors (default, builder, copy) where Python has a single __init__ with positional+kwargs signalwire.pom.pom.Section.add_subsection: cpp-overload-set — C++ exposes 4 overloads (title-only / title+body / title+bullets / full) where Python uses single positional+kwargs ## Webhook signature validation (signalwire.core.security.*) — C++ idiom @@ -410,7 +371,6 @@ signalwire.core.pom_builder.PomBuilder.from_sections: cpp_json_param_untyped: th signalwire.core.security.security_utils.filter_sensitive_headers: cpp_concrete_map: the C++ filter_sensitive_headers takes/returns map where the Python reference records a generic TypeVar _V (dict); the C++ header map is the concrete instantiation — same header-hygiene behavior, concrete-type idiom (not a kwargs spread). signalwire.core.security_config.SecurityConfig.validate_ssl_config: cpp_kwargs_positional: SecurityConfig ctor takes typed C++ params where Python takes config_file/service_name keyword args; same env-driven config (mirrors Java SecurityConfig). signalwire.core.skill_manager.SkillManager.loaded_skills: cpp_property_via_getter: Python exposes `loaded_skills` as a @property returning dict; the C++ port exposes the same via the named getter list_loaded_skills() — the getter is already the parity method (surface-matched); the bare property name has no distinct C++ symbol. Same loaded-skills access, property-vs-getter idiom (not a kwargs spread). -signalwire.core.swaig_function.SWAIGFunction.__init__: cpp_typed_callback_plus_json: the C++ ctor takes a concrete SwaigFunctionHandler class where the Python reference records a bare callable<[any],any>, and its parameters argument is nlohmann::json (projected to `any`) where Python types it optional>; plus a trailing json extra_swaig_fields carrier — same SWAIG descriptor, typed-handler + open-json idiom (not a kwargs spread). signalwire.core.swaig_function.SWAIGFunction.validate_args: cpp_typed_return: the C++ validate_args returns a concrete ArgsValidationResult where the Python reference returns a raw tuple; the args param is nlohmann::json (projected to `any`) where Python types it dict — same validation contract, typed-return-object idiom (not a kwargs spread). signalwire.core.swml_builder.SWMLBuilder.add_section: cpp_fluent_self: SWMLBuilder verb methods return the concrete SWMLBuilder& (fluent chaining) where Python's type hint is Self; and kwargs land as a trailing nlohmann::json — same document, C++ builder idiom. signalwire.core.swml_builder.SWMLBuilder.ai: cpp_fluent_self: SWMLBuilder verb methods return the concrete SWMLBuilder& (fluent chaining) where Python's type hint is Self; and kwargs land as a trailing nlohmann::json — same document, C++ builder idiom. @@ -431,8 +391,6 @@ signalwire.prefabs.info_gatherer.InfoGathererAgent.set_question_callback: cpp_ov signalwire.prefabs.receptionist.ReceptionistAgent.on_summary: cpp_overload: the C++ prefab method is a real handler/callback whose signature the reference records once (on_summary/on_swml_request take the C++ request+headers form); same behavior, port overload idiom (mirrors Java prefabs). signalwire.prefabs.survey.SurveyAgent.on_summary: cpp_overload: the C++ prefab method is a real handler/callback whose signature the reference records once (on_summary/on_swml_request take the C++ request+headers form); same behavior, port overload idiom (mirrors Java prefabs). signalwire.register_skill: cpp_typed_callback: the top-level register_skill free function takes a factory callable<[],SkillBase> where the Python reference takes the SkillBase *type object* directly; C++ has no first-class type value, so registration is by factory — same skill registration, factory-callable idiom (not a kwargs spread). -signalwire.relay.call.AIAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.Action.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). signalwire.relay.call.Action.result: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). signalwire.relay.call.Action.wait: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). signalwire.relay.call.Call.ai_hold: cpp_options_object: the C++ Call.ai_hold takes a single nlohmann::json params object collapsing Python's typed keyword params (timeout, prompt), and returns relay::Action where Python returns the raw dict; same calling.ai_hold wire frame (verified vs relay_apis.c) — options-object + typed-return idiom, NOT a **kwargs spread (the oracle records named typed params, no var_keyword). @@ -453,18 +411,8 @@ signalwire.relay.call.Call.queue_leave: cpp_options_object: the C++ Call.queue_l signalwire.relay.call.Call.refer: cpp_typed_return: the C++ Call.refer takes device (json) + status_url positionally (Python marks status_url keyword) and returns relay::Action where Python returns the raw dict; same wire frame (verified vs relay_apis.c) — positional + open-json + typed-return idiom (not a kwargs spread). signalwire.relay.call.Call.user_event: cpp_typed_return: the C++ Call.user_event takes event positionally (Python marks it keyword) and returns relay::Action where Python returns the raw dict; same wire frame (verified vs relay_apis.c) — positional + typed-return idiom (not a kwargs spread). signalwire.relay.call.Call.wait_for: cpp_verb_shape: the C++ Call.wait_for is a call-state waiter — (target_state, timeout_ms) -> bool — where the Python reference's wait_for is a RELAY-event waiter (event_type, predicate, timeout) -> RelayEvent; the two expose different wait surfaces under the same name (the C++ event-wait path is Call.on / the typed event handlers). Kept as a documented signature divergence — NOT a kwargs spread. -signalwire.relay.call.CollectAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). signalwire.relay.call.CollectAction.start_input_timers: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.DetectAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.FaxAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.PayAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.PlayAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.RecordAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.StandaloneCollectAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). signalwire.relay.call.StandaloneCollectAction.start_input_timers: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.StreamAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.TapAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). -signalwire.relay.call.TranscribeAction.__init__: cpp_unified_action: C++ flattens every RELAY call-action onto a single relay::Action (concrete PlayAction/RecordAction/... inherit its ctor); the projected __init__/start_input_timers carry the unified Action's signature, not Python's per-subclass one (documented cpp_unified_action idiom). signalwire.relay.call.AIAction.stop: cpp_unified_action: the concrete action's control method is inherited from the unified relay::Action; it fires the `.` frame (control_id) and returns void, where Python's coroutine awaits and returns the result dict — same wire frame, C++ fire-and-forget return idiom. signalwire.relay.call.CollectAction.pause: cpp_unified_action: the concrete action's control method is inherited from the unified relay::Action; it fires the `.` frame (control_id) and returns void, where Python's coroutine awaits and returns the result dict — same wire frame, C++ fire-and-forget return idiom. signalwire.relay.call.CollectAction.resume: cpp_unified_action: the concrete action's control method is inherited from the unified relay::Action; it fires the `.` frame (control_id) and returns void, where Python's coroutine awaits and returns the result dict — same wire frame, C++ fire-and-forget return idiom. @@ -484,59 +432,34 @@ signalwire.relay.call.StandaloneCollectAction.stop: cpp_unified_action: the conc signalwire.relay.call.StreamAction.stop: cpp_unified_action: the concrete action's control method is inherited from the unified relay::Action; it fires the `.` frame (control_id) and returns void, where Python's coroutine awaits and returns the result dict — same wire frame, C++ fire-and-forget return idiom. signalwire.relay.call.TapAction.stop: cpp_unified_action: the concrete action's control method is inherited from the unified relay::Action; it fires the `.` frame (control_id) and returns void, where Python's coroutine awaits and returns the result dict — same wire frame, C++ fire-and-forget return idiom. signalwire.relay.call.TranscribeAction.stop: cpp_unified_action: the concrete action's control method is inherited from the unified relay::Action; it fires the `.` frame (control_id) and returns void, where Python's coroutine awaits and returns the result dict — same wire frame, C++ fire-and-forget return idiom. -signalwire.relay.event.CallReceiveEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.CallReceiveEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.CallStateEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.CallStateEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.CallingErrorEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.CallingErrorEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.CollectEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.CollectEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.ConferenceEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.ConferenceEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.ConnectEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.ConnectEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.DenoiseEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.DenoiseEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.DetectEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.DetectEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.DialEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.DialEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.EchoEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.EchoEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.FaxEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.FaxEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.HoldEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.HoldEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.MessageReceiveEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.MessageReceiveEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.MessageStateEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.MessageStateEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.PayEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.PayEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.PlayEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.PlayEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.QueueEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.QueueEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.RecordEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.RecordEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.ReferEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.ReferEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.RelayEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.RelayEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.SendDigitsEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.SendDigitsEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.StreamEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.StreamEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.TapEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.TapEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). -signalwire.relay.event.TranscribeEvent.__init__: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.TranscribeEvent.from_payload: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.event.parse_event: cpp_typed_event_ctor: C++ typed RELAY events construct from a single nlohmann::json payload (from_payload(json)/ctor), where Python spreads the decoded event fields as typed __init__ params; same wire event, C++ passes the raw JSON object (the port's typed-event idiom). signalwire.relay.message.Message.on: cpp_typed_callback: the C++ Message.on binds a typed callable<[Message],void> handler where the Python reference records callable<[RelayEvent],any>; same message-event subscription, the concrete handler element type is the C++ typed-callback idiom. signalwire.rest._base.CrudWithAddresses.__init__: cpp_crud_idiom: generated CrudWithAddresses base method takes the C++ typed params object where Python spreads **kwargs; same REST wire shape (documented CRUD idiom). signalwire.skills.registry.SkillRegistry.get_skill_class: cpp_return_idiom: C++ get_skill_class returns bool (whether the skill factory is known) where Python returns the skill type object; C++ has no first-class type value — use create() to instantiate (same discovery-by-name contract). -signalwire.web.web_service.WebService.__init__: cpp_kwargs_positional: WebService ctor/start collapse Python's many keyword options (directories/basic_auth/allowed_extensions/ssl_cert/...) into positional C++ params / accessors; same static-file service behavior (mirrors Java WebService). signalwire.web.web_service.WebService.start: cpp_kwargs_positional: WebService ctor/start collapse Python's many keyword options (directories/basic_auth/allowed_extensions/ssl_cert/...) into positional C++ params / accessors; same static-file service behavior (mirrors Java WebService). ## KNOWN PRE-EXISTING RESIDUAL (NOT item H/I) — gen-payload SWML AI-payload structs @@ -555,7 +478,6 @@ UNTAGGED on purpose — an honest gate failure, not silenced with a blanket allowlist. Fix requires the signature enumerator to project POD-struct fields under `swml_verbs_generated` as property-getters (or the port to expose them as accessors). -signalwire.rest._request_options.RequestOptions.__init__: cpp_constructor_default_only: RequestOptions is an aggregate struct with public data fields (timeout/retries/retry_on_status/retry_backoff/abort_signal); Python's dataclass __init__ enumerates each field as a keyword. The full set is reachable via the C++ public fields (ro.retries = 1, ro.abort_signal = &flag) — same callable contract, aggregate-init idiom instead of a keyword ctor (go/ts/ruby/java value-struct match). signalwire.rest._request_options.RequestOptions.abort_signal: cpp_field_not_property: Python exposes abort_signal as a @property getter *method*; C++ implements it as a public data member (std::atomic* abort_signal) the libclang enumerator does not emit as a method. Reachable directly as ro.abort_signal — same callable contract, public-field idiom (the RequestOptions data fields are deliberately not surface symbols, exactly as the Python dataclass fields aren't). ## A-fold / G-fold signature re-key From 95641abcd377548f64a682f9bad962af8ecc7ee6 Mon Sep 17 00:00:00 2001 From: Michael Jerris Date: Mon, 27 Jul 2026 13:03:56 -0400 Subject: [PATCH 04/88] feat(parity): expose the 7 derived caller-observable ctor attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signature oracle now records DERIVED public __init__ attributes that are caller-observable VALUES (porting-sdk d7c859d + 387667e, ALLOWLIST_DISCIPLINE class B2). Six of the seven landed on cpp as drift; this closes all of them. Already implemented, needed no port change: SignalWireRestError.request_id — HttpClient already extracts it from the response headers and exposes request_id(). Folded at the enumerator (the accessor existed; the projection did not list it): Action.completed — the unified C++ relay::Action already has completed() (is_done() delegates to it). Added to the base-Action projection in BOTH enumerators, alongside control_id. Implemented: SWMLService.ssl_enabled / .ssl_cert_path / .ssl_key_path / .domain The reference copies these four off self.security in __init__ and lets run() override them. cpp resolved TLS straight from the environment at serve() time and stored nothing, so the values were not observable at all. Now the ctor builds a SecurityConfig from config_file + the service name and seeds the four fields from it (the reference's exact wiring), an accessor+setter pair reads/overrides each, and BOTH serve() paths (swml::Service and AgentBase) drive TLS off those fields instead of re-reading the env. SWML_SSL_* still applies — SecurityConfig reads it — but an explicit set_ssl_*() now wins, matching run(ssl_enabled=…). SpiderSkill.remove_xpaths The reference PREFILLS seven selectors in __init__ and drop_tree()s each match before extracting text. cpp had no equivalent: its naive tag-strip replaced tags with a space, so " + "" + "" + "
secret_header_token
" + "" + "" + "

keeper body text

" + "
secret_footer_token
" + "", + "text/html"); + }); + + int port = 0; + std::thread th([&]{ port = srv.bind_to_any_port("127.0.0.1"); srv.listen_after_bind(); }); + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + while (port == 0 && std::chrono::steady_clock::now() < deadline) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + ASSERT_TRUE(port > 0); + + ::setenv("SPIDER_BASE_URL", ("http://127.0.0.1:" + std::to_string(port)).c_str(), 1); + auto skill = sw_skills::SkillRegistry::instance().create("spider"); + skill->setup(json::object()); + auto tools = skill->register_tools(); + ASSERT_TRUE(tools.size() >= 1u); + auto result = tools[0].handler(json::object({{"url", "https://example.com/page"}}), + json::object()); + auto resp = result.to_json()["response"].get(); + + srv.stop(); + th.join(); + ::unsetenv("SPIDER_BASE_URL"); + + // Every default remove_xpaths entry: //script //style //nav //header //footer + // //aside //noscript — content dropped, not merely untagged. + ASSERT_TRUE(resp.find("secret_js_token") == std::string::npos); + ASSERT_TRUE(resp.find("secret_css_token") == std::string::npos); + ASSERT_TRUE(resp.find("secret_nav_token") == std::string::npos); + ASSERT_TRUE(resp.find("secret_header_token") == std::string::npos); + ASSERT_TRUE(resp.find("secret_aside_token") == std::string::npos); + ASSERT_TRUE(resp.find("secret_noscript_token") == std::string::npos); + ASSERT_TRUE(resp.find("secret_footer_token") == std::string::npos); + // …and the real body survives, so the fold is not just "drop everything". + ASSERT_TRUE(resp.find("keeper body text") != std::string::npos); + return true; +} + diff --git a/tests/test_swml_service_swaig.cpp b/tests/test_swml_service_swaig.cpp index 43d6db7..74398aa 100644 --- a/tests/test_swml_service_swaig.cpp +++ b/tests/test_swml_service_swaig.cpp @@ -242,3 +242,77 @@ TEST(service_as_router_registers_the_services_routes) { ASSERT_TRUE(swaig_ok); return true; } + +// --------------------------------------------------------------------------- +// TLS / serving-domain values on the service itself. +// +// The reference copies these four off ``self.security`` in ``__init__``: +// self.ssl_enabled = self.security.ssl_enabled +// self.domain = self.security.domain +// self.ssl_cert_path = self.security.ssl_cert_path +// self.ssl_key_path = self.security.ssl_key_path +// and lets ``run()`` override them afterwards. They are caller-observable +// values on the SERVICE, not only on the SecurityConfig collaborator. +// --------------------------------------------------------------------------- + +namespace { +void clear_service_tls_env() { + ::unsetenv("SWML_SSL_ENABLED"); + ::unsetenv("SWML_SSL_CERT_PATH"); + ::unsetenv("SWML_SSL_KEY_PATH"); + ::unsetenv("SWML_DOMAIN"); +} +} // namespace + +TEST(service_tls_values_default_off) { + clear_service_tls_env(); + Service svc; + ASSERT_FALSE(svc.ssl_enabled()); + ASSERT_FALSE(svc.domain().has_value()); + ASSERT_FALSE(svc.ssl_cert_path().has_value()); + ASSERT_FALSE(svc.ssl_key_path().has_value()); + return true; +} + +// The ctor must SEED these from SecurityConfig — that is the reference's +// wiring, and it is what makes SWML_SSL_* reach the service at all. +TEST(service_tls_values_seeded_from_security_config) { + clear_service_tls_env(); + ::setenv("SWML_SSL_ENABLED", "true", 1); + ::setenv("SWML_SSL_CERT_PATH", "/etc/ssl/seeded.crt", 1); + ::setenv("SWML_SSL_KEY_PATH", "/etc/ssl/seeded.key", 1); + ::setenv("SWML_DOMAIN", "seeded.example.com", 1); + + Service svc; + bool enabled = svc.ssl_enabled(); + std::string cert = svc.ssl_cert_path().value_or(""); + std::string key = svc.ssl_key_path().value_or(""); + std::string dom = svc.domain().value_or(""); + + clear_service_tls_env(); + + ASSERT_TRUE(enabled); + ASSERT_EQ(cert, std::string("/etc/ssl/seeded.crt")); + ASSERT_EQ(key, std::string("/etc/ssl/seeded.key")); + ASSERT_EQ(dom, std::string("seeded.example.com")); + return true; +} + +// ...and an explicit setter overrides the seeded value, mirroring the +// reference's ``run(ssl_enabled=…, domain=…, ssl_cert=…, ssl_key=…)``. +TEST(service_tls_values_settable_after_construction) { + clear_service_tls_env(); + Service svc; + ASSERT_FALSE(svc.ssl_enabled()); + + svc.set_ssl_enabled(true) + .set_ssl_cert_path("/tmp-unused/override.crt") + .set_ssl_key_path("/tmp-unused/override.key") + .set_domain("override.example.com"); + + ASSERT_TRUE(svc.ssl_enabled()); + ASSERT_EQ(svc.ssl_cert_path().value_or(""), std::string("/tmp-unused/override.crt")); + ASSERT_EQ(svc.ssl_key_path().value_or(""), std::string("/tmp-unused/override.key")); + ASSERT_EQ(svc.domain().value_or(""), std::string("override.example.com")); + return true; +} From 2c3c04f4f8d1e86ac5298502b5ad4ef8bf212f66 Mon Sep 17 00:00:00 2001 From: Michael Jerris Date: Mon, 27 Jul 2026 13:11:02 -0400 Subject: [PATCH 05/88] fix(lint): clean clang-tidy findings in the new remove_xpaths code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two curated-set errors from the previous commit, both in the spider xpath-drop pass: performance-inefficient-string-concatenation — the element-drop regex was assembled with chained operator+ on std::string, allocating a temporary per link. Build it with reserve() + append(). bugprone-exception-escape — the scrape/crawl tool handlers captured the xpath list BY VALUE, so constructing the lambda's closure could throw (vector copy allocates) inside a handler the checker requires to be nothrow. Capture a shared_ptr> instead: the copy is nothrow, the list is still shared by value rather than through `this` (a ToolDefinition outlives the skill instance that registered it, so a `this` capture would dangle), and it is made once at registration. LINT exit 0 (0 errors); run_tests 2043/2043. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KKwgPbehDoAMdG3hPL79oi --- port_surface.json | 2 +- src/skills/builtin/spider.cpp | 33 +++++++++++++++++++-------------- src/skills/skill_registry.cpp | 28 ++++++++++++++++++---------- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/port_surface.json b/port_surface.json index d587778..0eb7eb3 100644 --- a/port_surface.json +++ b/port_surface.json @@ -1,5 +1,5 @@ { - "generated_from": "signalwire-cpp @ 60cd2677f0331fef5a893774b84c2278aba8bc19", + "generated_from": "signalwire-cpp @ 95641abcd377548f64a682f9bad962af8ecc7ee6", "modules": { "signalwire": { "classes": {}, diff --git a/src/skills/builtin/spider.cpp b/src/skills/builtin/spider.cpp index a52943b..2669db5 100644 --- a/src/skills/builtin/spider.cpp +++ b/src/skills/builtin/spider.cpp @@ -41,11 +41,15 @@ std::string drop_xpath_elements(const std::string& html, const std::vector ... (non-greedy body, case-insensitive), plus the // self-closing / unpaired form so a stray "