Skip to content

chore(surface): fold to agentbase-mixin-flatten branch (comp-attr enrich + allowlist cleanup)#57

Open
mjerris wants to merge 7 commits into
mainfrom
chore/allowlist-fold-rust
Open

chore(surface): fold to agentbase-mixin-flatten branch (comp-attr enrich + allowlist cleanup)#57
mjerris wants to merge 7 commits into
mainfrom
chore/allowlist-fold-rust

Conversation

@mjerris

@mjerris mjerris commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Brings the Rust port to green against porting-sdk fix/agentbase-mixin-flatten-fold
(diff-side Class A mixin-flatten + B1 composition-attribute folds + the newer python oracle).

Emission (scripts/enumerate_surface.py)

  • Composition-attribute enrich mirroring the oracle's _enrich_composition_attributes:
    import self-only class-ref-returning members from the reference signature oracle onto the
    matching port class, gated on the port's OWN port_signatures.json recording that member
    (keeps the two oracles consistent by construction; never invents surface). Surfaces the
    swml_verbs_generated / post_prompt_generated struct-field members Rust records
    method-less — retires 207 phantom omissions and stops those classes folding to the
    gen-type pseudo-module while the reference (now carrying members) does not.
  • Promote PromptObjectModel.sections / Section.subsections public struct fields (the
    pub fn parser misses bare fields); un-drop PomBuilder.pom (oracle now records it).
  • Drop the 5 composition-delegate re-exposures Rust flattens onto AgentBase
    (render_swml/get_contexts/get_raw_prompt/create_tool_token/get_global_data) — the
    delegate class already carries the reference-matching copy, so the AgentBase copy read as a
    phantom addition after the family fold.

Allow-list cleanup

  • Delete 292 dead PORT_OMISSIONS + 40 dead PORT_ADDITIONS non-REST entries the
    folds retired; keep entries live for either the folded SURFACE diff or the unfolded
    SIGNATURE diff (incl. class-level coverage — diff_port_signatures.py does not apply the
    family fold, so it still needs the unfolded AgentBase.<m> keys).
  • 5 new impossible: omissions (private-field idioms: agents / logger / security /
    loaded_skills / skill_manager — Rust holds these private + exposes renamed accessors) and
    18 new agentbase-family additions (genuine port-only granular setters/accessors).
  • FROZEN: all signalwire.rest.* *Resource/*Namespace entries left as-is pending the
    deferred spec-driven REST base+subclass parity pass (per coordinator direction — do not
    delete/rename).
  • Add the ALLOWLIST_DISCIPLINE §1 header to PORT_OMISSIONS.md + PORT_ADDITIONS.md.

Verification

scripts/suites/surface.py --port rustall 9 SURFACE rules PASS (SIGNATURES, DRIFT,
SURFACE-FRESH, SURFACE-DIFF, RELAY-VERB-RESULT-LOCK, GEN-TYPE-DEGENERACY, ROUTE-COLLISION,
GEN-IDIOM, SEMVER-DIFF). Regeneration verified deterministic. No Rust source touched, so the
cargo TEST/LINT/BEHAVIORAL gates are unaffected by this change.

Rust has no SWML keyword-escape verbs (only 1 SWML verb) and no functional-options, so those
parts of the shared brief don't apply. No D1 REST *Resource class-rename was needed.

Coordinated-With: porting-sdk@fix/agentbase-mixin-flatten-fold

🤖 Generated with Claude Code

https://claude.ai/code/session_01NqhUoqrbptHNS3cypq9s6t

…rich + allowlist cleanup

Bring the rust port to green against porting-sdk fix/agentbase-mixin-flatten-fold
(diff-side Class A mixin-flatten + B1 composition-attr folds + newer oracle).

Emission (enumerate_surface.py):
- Add a composition-attribute enrich mirroring the oracle's
  _enrich_composition_attributes: import self-only class-ref-returning members
  from the reference signature oracle onto the matching port class, gated on the
  port's OWN port_signatures.json recording that member (keeps the two oracles
  consistent by construction). Surfaces the swml_verbs_generated / post_prompt_generated
  struct-field members Rust records method-less — retires 207 phantom omissions and
  stops those classes folding to the gen-type pseudo-module.
- Promote PromptObjectModel.sections / Section.subsections public struct fields
  (the pub-fn parser misses fields); un-drop PomBuilder.pom (oracle now records it).
- Drop the 5 composition-delegate re-exposures Rust flattens onto AgentBase
  (render_swml/get_contexts/get_raw_prompt/create_tool_token/get_global_data) — the
  delegate class already carries the reference-matching copy, so the AgentBase copy
  was a phantom addition after the family fold.

Allowlist cleanup:
- Delete 292 dead PORT_OMISSIONS + 40 dead PORT_ADDITIONS non-REST entries the folds
  retired; keep entries live for either the folded SURFACE diff or the unfolded
  SIGNATURE diff (incl. class-level coverage).
- 5 new impossible: omissions (private-field idioms: agents/logger/security/
  loaded_skills/skill_manager) + 18 new agentbase-family additions (genuine port-only
  granular setters/accessors).
- Freeze all signalwire.rest.* *Resource/*Namespace entries pending the deferred
  spec-driven REST base+subclass parity pass (do not delete/rename).
- Add the ALLOWLIST_DISCIPLINE §1 header to PORT_OMISSIONS.md + PORT_ADDITIONS.md.

Coordinated-With: porting-sdk@fix/agentbase-mixin-flatten-fold

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NqhUoqrbptHNS3cypq9s6t
@mjerris mjerris added the coordinated-pass PR coordinated with a non-main porting-sdk pin label Jul 24, 2026
mjerris and others added 3 commits July 24, 2026 13:37
… in surface

The composition-attribute enrich surfaced `Section.main` (a POM `list<SWMLMethod>`
field the reference oracle records), so the bare name `main` now exists in
port_surface.json. The DOC_AUDIT_IGNORE.md `main:` entry — excluding the doc-local
`fn main` entry point in getting-started code fragments — claimed "not a port symbol",
which IGNORE-LEDGER-VERIFY correctly flagged as a now-false non-existence rationale.

Reword the rationale to describe the doc-local `fn main` code-fragment token
positively (a Rust program entry point written inline in the example), distinct from
the `Section.main` POM field, without any absence claim. The doc-audit exclusion of
the `fn main` fragment token stays; only the stale rationale is corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NqhUoqrbptHNS3cypq9s6t
… dead entries

Wave-3 cleanup on the allowlist-fold branch, now that the spec-driven REST
crud_bases mechanism is in the oracle.

REST crud_bases emission:
- enumerate_surface.py now emits the port's OWN top-level `crud_bases` map in
  port_surface.json (49 resources), sourced from the generator sidecar
  (rest_signatures.json), never hand-written. The surface diff folds a CRUD
  method (list/create/get/update/delete/paginate) on any class either the
  reference or the port declares a crud_base for, so rust's resources beyond the
  reference's 25 (Messages, PubSub, ImportedNumbers, …) fold without per-op
  allow-list entries. Symbol-neutral: the class shape is unchanged, only the
  new top-level map is added.

Dead-entry retirement:
- PORT_ADDITIONS.md: deleted 274 frozen `signalwire.rest.*` CRUD additions now
  folded by the crud_bases map. Kept 70 genuine port-infra additions (RestClient
  namespace accessors, _base CrudResource/HttpClient/SignalWireRestError helpers,
  _request_options builders, http_client transports, *_with_options overloads).
- PORT_OMISSIONS.md: deleted 213 dead `signalwire.rest.*` omissions (symbols no
  longer in the oracle / folded by crud_bases).

Accessor omissions (24 composition-attr entries): all KEPT as genuine non-name
omissions — rust exposes the OPERATIONS (add_skill, get_basic_auth_credentials,
register_verb_handler) but not the composed OBJECT under any name; agents/
loaded_skills have a matching method already (a rename would orphan it); logger/
security/skill_manager/verb_registry have no per-instance member at all. Zero
renames were warranted.

DUAL-GATE: AgentBase.skill_manager and AgentServer.app are surface-oracle-EXCLUDED
(so the surface fold covers them) but the SIGNATURE oracle still records them, so
they stay in PORT_OMISSIONS with an impossible: reason — the DRIFT gate keys
unfolded and needs the AgentBase/AgentServer-scoped copies.

SURFACE-DIFF + signature DRIFT both PASS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NqhUoqrbptHNS3cypq9s6t
Emit the @DataClass public fields the reference oracle now records into the
Rust surface + signature oracles:

- relay.event.* typed-event accessor fields (already added; committed here).
- AI-Chat DTO public struct fields (CLASS B field-emit via the enumerator):
  ChatResponse.{text,conversation_id,user_event}, ChatLog.{messages,call_timeline},
  ConversationInfo.{id,status,initial_message}. Rust exposes these as bare `pub`
  struct fields (a field is not a `pub fn`, so the parser misses them); promote
  them onto the surface via PUBLIC_FIELD_MEMBERS and synthesize the reference-shaped
  0-param field-read accessors in build_ai_chat_signatures().
- CollectEvent: the reference field is `final`; the Rust accessor is spelled
  `is_final` (reserved-word clash) and folded to `final` via the METHOD_RENAMES
  table. Delete the now-dead `CollectEvent.is_final` PORT_ADDITIONS.md line
  (the surface emits `final`).
- RequestOptions.{timeout,retries,retry_on_status,retry_backoff}: the reference
  records these as 0-param dataclass field accessors; Rust exposes each as a
  chained builder setter over the same `pub` field. Excuse as builder-setter
  idiom in PORT_SIGNATURE_OMISSIONS.md, matching the existing `abort_signal`
  entry (same shape, same field).

Surface diff + signature diff both exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NqhUoqrbptHNS3cypq9s6t
@mjerris

mjerris commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Coordinated set root: signalwire/porting-sdk#120 (branch fix/agentbase-mixin-flatten-fold, now open for review).

That PR carries the shared oracle/diff folds this branch is pinned to (PORTING_SDK_REF is set on this repo) plus the allow-list doctrine governing the burn-down — ALLOWLIST_DISCIPLINE.md §0/§0a/§0b/§7/§8/§9. Short version: idiom is standardized by the code that does the emission and is never an addition or omission; an AI may not create either — fold, implement, delete, or propose-and-stop.

Merges as a set, porting-sdk first. No merge before approval.

…questOptions setter additions)

Allow-list fold campaign, rust lane.

PORT_OMISSIONS.md — 4 dead `logger` omissions deleted. The surface oracle no
longer records the per-instance `logger` attribute (owner ruling 2026-07-24:
logging is a MODULE-LEVEL capability ports may reach however their language
does; contract = the 5 free functions in `signalwire.core.logging_config`).
The entries excused symbols that are no longer in the reference, so
diff_port_surface flagged them DEAD:
  AgentServer.logger, SkillBase.logger, SkillManager.logger, SkillRegistry.logger

PORT_ADDITIONS.md — 4 dead `RequestOptions` builder-setter additions deleted:
  RequestOptions.{retries,retry_backoff,retry_on_status,timeout}
These carried the canonical banned rationale ("builder setter idiom for the
same field", ALLOWLIST_DISCIPLINE §7). Rust has BOTH `pub timeout: Option<f64>`
(the field) and `pub fn timeout(self, s) -> Self` (the setter over it), spelled
identically — so the enumerator already emits ONE member per field matching the
oracle's `RequestOptions.{abort_signal,retries,retry_backoff,retry_on_status,
timeout}`. The lines excused nothing and were pure paperwork.

Verification (fast diffs, no build):
  diff_port_surface.py --reference python_surface.json --port-surface
    port_surface.json --omissions PORT_OMISSIONS.md --additions PORT_ADDITIONS.md
  -> exit 0: "port matches Python reference (2550 symbols; 33 excused
     omissions, 528 excused additions)"
  audit_docs.py --root . --surface port_surface.json --ignore DOC_AUDIT_IGNORE.md
  -> exit 0: "docs/examples reference only known symbols (1758 resolved / 3705
     total)" — zero unresolved symbols, no accessor-fold doc drift on rust.

KNOWN RED, not fixable in this lane: diff_port_signatures.py now reports 3
missing-port drifts (AgentServer.logger / SkillManager.logger /
SkillRegistry.logger). The owner-ruled logger exclusion landed in
porting-sdk `scripts/enumerate_python.py` (surface oracle) but NOT in
`scripts/enumerate_python_signatures.py`, so python_signatures.json still
records all 4 members keyed on the same
`class:signalwire.core.logging_config.get_logger` return the surface
enumerator excludes on. The signature diff consumed PORT_OMISSIONS.md via
--surface-omissions, so deleting the dead surface lines unmasked it. The fix
is the one-line mirror in the signature enumerator (porting-sdk, orchestrator-
owned); creating a signature omission here is forbidden (ALLOWLIST_DISCIPLINE
§0a).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y1fEcuaivoN1poPGS75jAy
@mjerris

mjerris commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

Re-running CI against porting-sdk acfae9e (the coordinated pin now carries the signature-oracle lockstep fix 37dcb2a, which several of these reds predate).

mjerris and others added 2 commits July 24, 2026 22:44
Rust expresses a wide many-optional-kwarg constructor as an OPTIONS STRUCT:
the reference's `AgentBase.__init__(name, route, host, port, ...)` becomes
`AgentBase::new(AgentOptions::new(name).route(..).host(..))`. Those struct
FIELDS are the construction parameter set, so they satisfy the §10 contract
rather than being N port-only additions plus one blanket `__init__` omission.

`build_construction` emits a NAME-KEYED top-level `construction` node from two
sources, in precedence order: the class's own `__init__` (Rust `new`) params,
then its options struct's public fields. The fluent `with_*`/setter methods
fold onto the field they set — they add no capability the field does not
already carry, the same field+setter precedent RequestOptions established.

`required` is derived from the options struct's OWN constructor rather than
from `Option<T>`-ness: `AgentOptions::new(name)` takes `name` and defaults the
other nine, and a `Default`-only struct demands nothing — so a defaulted scalar
(`auto_answer: bool`) is correctly optional despite its non-Option type.

`basic_auth_user`/`basic_auth_password` canonicalize to the reference's single
`basic_auth` tuple param via the adapter rename table (ADAPTER_CONTRACT rule 3);
Rust splits the pair because struct literals have no anonymous-tuple idiom.

124 classes / 241 params. Purely additive — `modules` is byte-identical, so no
existing signature comparison changes. DRIFT and SURFACE-DIFF both exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y1fEcuaivoN1poPGS75jAy
…fab options idiom

AgentBase implemented its own state but never wired the params the reference
FORWARDS to collaborators, so 13 documented constructor params were unreachable
from construction even though the underlying capability already existed in the
port (17 config-file references, 30 schema-validation references, all dead).

Wired, each to the same collaborator the reference uses:
  * schema_path / schema_validation -> Service -> SchemaUtils
    (agent_base.py:205-207, swml_service.py:186). Service now holds an
    Arc<SchemaUtils> override, built ONLY when the caller asks for a non-default
    schema; default construction keeps borrowing the process-wide shared
    default, so the parse-once hot path is unchanged.
  * config_file -> Service -> SecurityConfig (swml_service.py:139) and ->
    AgentBase's own _load_service_config (agent_base.py:359-382). Added
    SecurityConfig::with_config_file, porting _load_config_file key for key
    including the nested auth.basic.{user,password} path and the reference's
    defaults -> env -> config-file priority order.
  * token_expiry_secs -> SessionManager (agent_base.py:247).
  * agent_id, native_functions, record_format, record_stereo,
    default_webhook_url, suppress_logs, enable_post_prompt_override,
    check_for_input_override, trust_proxy_for_signature stored on the agent.

Two wire/behavior defects found and fixed along the way:

  * record_format defaulted to "wav" and record_stereo to false, where the
    reference defaults "mp4"/true. The identical reference program emitted a
    different recording on the wire.
  * webhook signature validation unconditionally honored X-Forwarded-Proto /
    X-Forwarded-Host. Those headers are set by whoever reaches the agent, so an
    attacker able to reach it directly could sign a URL for a hostname of their
    choosing, inject it, and have the signature validate. The reference defaults
    trust_proxy_for_signature=False for exactly this reason. Signature URL
    reconstruction now goes through resolve_signature_base, which honors
    operator config (SWML_PROXY_URL_BASE / manual_set_proxy_url) always but the
    forwarded headers only on explicit opt-in. Advertised web_hook_url
    construction is unaffected.

AgentBase::clone also rebuilt its Service from ServiceOptions, silently dropping
the schema-utils override and resolved SecurityConfig; it now clones the Service
directly.

Prefabs: FAQBot/Receptionist/Survey/Concierge/InfoGatherer took flat positionals
that made every param required, so valid reference programs did not port
(FAQBotAgent(faqs) needed five arguments here). Each now takes an options struct
whose `new` takes exactly the reference's required positionals and defaults the
rest. Concierge's venue_info map is flattened to the reference's real params
(from_venue_info retained for callers holding a JSON blob), and survey's
brand_name / conclusion / max_retries are now wired into the prompt and global
data as the reference wires them rather than accepted and dropped.

Enumerators: the options-struct family folds at emit (EMISSION COVERS IDIOM)
rather than being recorded as surface additions — an options struct IS its
target class's reference __init__ kwargs, and the construction contract in
port_signatures.json is what proves the parameter set matches. Same fold for the
seven new field-read getters, which stand in for reference instance attributes
that Python records as no surface at all. That made 18 PORT_ADDITIONS entries
dead; deleted them. No new omission or addition entries.

Construction drift, this brief's scope: AgentBase 13 missing params -> 0,
SWMLService 3 -> 0, prefabs 21 required-flips + 14 missing/extra params -> 0.
Fleet-wide 210 -> 166.

Verification: 2045 tests pass (18 new construction-contract tests), FMT clean,
LINT clean, diff_port_signatures.py and diff_port_surface.py both exit 0,
enumerator regen byte-stable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y1fEcuaivoN1poPGS75jAy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

coordinated-pass PR coordinated with a non-main porting-sdk pin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant