Skip to content

Commit 4dd76a7

Browse files
mjerrisclaude
andauthored
chore(allowlist): fold cleanup + spec-driven REST + §1 header (java) (#63)
* chore(allowlist): wave-2 fold-branch reconciliation (crud_bases + B1 + composition + §1) Reconcile the Java surface against porting-sdk@fix/agentbase-mixin-flatten-fold (the fold branch carrying the AgentBase-mixin, B1 composition-attr, typed SWML-verb, and spec-driven REST crud_bases folds). Emission (scripts/enumerate_surface.py): - crud_bases map: emit a top-level `crud_bases` map (25 resources) keyed by the reference `<module>.<Class>` path, read from the generated rest_signatures.json sidecar (base + typed bind). diff_port_surface._fold_crud_methods now folds each resource's inherited CRUD ops structurally via the ref∪port map — no per-resource allow-list, no per-op rename. - composition-delegate strip (§4c.1): drop the flattened pass-through copies of render_swml/get_contexts/get_raw_prompt/create_tool_token/extract_sip_username from AgentBase (guarded on the method already being emitted on its canonical helper class — SwmlRenderer/PromptManager/SessionManager/SWMLService). - B1 composition-attr getter folds (rename-not-omission): strip the get_ prefix so Java getters fold onto the reference attribute name — getSkillManager→skill_manager (global), and scoped per-(module,class): PromptObjectModel.getSections→sections, Section.getSubsections→subsections, Action/Message.getResult→result, WebService.getSecurity→security. Allow-lists (dual-keyed: folded `agentbase-family.*` for SURFACE-DIFF + unfolded `agent_base.AgentBase.*` twins kept for the DRIFT/SIGNATURE gate): - PORT_ADDITIONS: 25 genuine port-only AgentBase-family extensions re-expressed with crisp per-method WHY (builder/clone/MCP/SIP-routing/getter/query idioms; no ref twin). - PORT_OMISSIONS: 5 folded family omission keys; 7 B1 composition-attr omissions (logger×4 module-level-no-instance-field, AgentServer.agents, SWMLService.security, SwaigRequest.argument — genuine non-name absence per rename-not-omission audit); 173 generated-model field-accessor omissions (DEFERRED generator-parity item — FLAGGED FOR OWNER SIGN-OFF; go/rust/cpp/ts/php emit typed field members, java/ruby/ dotnet emit method-less DTOs). - §1 allow-list-discipline header added to all four Tier-A files. 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 * chore(allowlist): sweep dead entries (symbol excluded/removed from oracle) Deleted stale allow-list entries whose symbol no longer exists in the reference oracle (python-only subsystems already excluded: RAG/search backend, mcp_gateway daemon, CLI, livewire, historical skill variants). The new SURFACE-DIFF dead-entry gate enforces this. Coordinated-With: porting-sdk@fix/agentbase-mixin-flatten-fold Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * java: fold accessors + exclude ctor/dunder + emit oracle-gated DTO fields Idiom-fold pass (idiom_reaudit_brief): allow-list entries are for genuine capability differences only, never idiom. Fold idiom at the enumerator so the diff sees the same surface. Surface + signature enumerators (lockstep): - DTO-field emission: generated read-side payload classes (swml_verbs_generated / post_prompt_generated / swaig_request_generated) now emit the oracle-recorded typed composition FIELDS, oracle-GATED per class (never the full field list — scalar wire fields the reference doesn't record are not over-emitted). Reserved-word field escapes resolve via @SerializedName. Retires 174 surface omissions by emission (RULES.md §2). Aborts loud if the oracle wants a field the DTO doesn't declare. - accessor->member fold: getX/setX/isX/hasX/withX collapse onto the reference member X they re-express (read+write of a public reference field), when the reference records X on the same (module, class) and X != the accessor name. - ctor/dunder exclusion: __init__/__repr__/etc. dropped from emission when they would be a port-only ADDITION (reference records no such dunder on that class). Result: surface omissions 218 -> 44; surface additions 741 -> 695; signature drift folds down to genuine port-only typed enum/handler/functional-interface surface + convenience-on-different-class + the WebService.app omission (JDK HttpServer, no ASGI-app handle). Both diff_port_surface.py and diff_port_signatures.py exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NqhUoqrbptHNS3cypq9s6t * java: delete 46 redundant matched addition entries (accessor-fold wins) After the accessor->member fold, 46 PORT_ADDITIONS.md entries name a symbol that now matches the reference by name (getX folded to the reference member X that the oracle records — FabricNamespace.addresses, RequestOptions.timeout, POM.sections, WebService.security, etc.). The parity holds without the allow-list line, so the entry is redundant cruft — delete it (RULES.md §2: idiom folded, not excused). Surface additions 695 -> 649. diff_port_surface.py + diff_port_signatures.py both exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NqhUoqrbptHNS3cypq9s6t * java: restore load-bearing Section.subsections addition line One of the 46 deleted "redundant" addition lines (Section.subsections) was also excusing the SIGNATURE gate's missing-port: the surface fold matches it (getSubsections->subsections), but the JAR-reflected Section signature carries no zero-arg subsections method, so the signature oracle's field-accessor member is missing on the signature side. Restore the line with a precise surface- matched/signature-absent rationale. Both diffs exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NqhUoqrbptHNS3cypq9s6t * feat(surface): emit the construction contract; drop 4 dead logger omissions FIRST PORT TO ADOPT the construction contract (porting-sdk ALLOWLIST_DISCIPLINE.md §10, emitter + diff landed in porting-sdk cf05021/37dcb2a). java is the prover. WHY THIS EXISTS 489 `__init__` signature-omissions fleet-wide — 62 of them java's, 17% of its signature ledger. Each one excuses a WHOLE constructor from comparison, because `compare_param` matches params by POSITION and ignores names, which is meaningless for a 22-param kwargs constructor vs a Java builder. So one line hid every parameter at once: a port could silently drop `record_stereo` or `signing_key` forever and no gate would notice. WHAT THIS EMITS A `construction` node: a NAME-KEYED set of configurable params per class, sourced from (1) the class's own `__init__` where it has a public ctor, else (2) its BUILDER's setters — because in Java the builder setters ARE the construction parameter set. `_BUILDER_CONSTRUCTS` binds AgentBaseBuilder→AgentBase and RelayClientBuilder→RelayClient. The setter names were ALREADY canonical snake_case (translate_method_name), so only the binding was missing — no name mapping needed. 123 classes emitted. Purely additive: 1,470 new lines, zero existing signatures changed. WHAT IT IMMEDIATELY REVEALED (369 findings, previously invisible) construction-missing-param 240 construction-extra-param 66 construction-required-flip 45 construction-missing-class 16 construction-type-mismatch 2 On AgentBase alone — 11 reference construction params a Java developer CANNOT set today: agent_id, check_for_input_override, config_file, default_webhook_url, enable_post_prompt_override, native_functions, schema_path, schema_validation, suppress_logs, token_expiry_secs, use_pom. Plus basic_auth, which is the §7 typed-split row (reference `optional<tuple<string,string>>` == java authUser + authPassword; Java cannot idiomatically express a 2-tuple — verified in AgentBase.java:146,238 — so that one folds rather than being a gap). These are REPORTED, not written to any ledger: per ALLOWLIST_DISCIPLINE §0a an agent may not create an omission or an addition. They are implement-work and go to the owner as findings. `required` is deliberately compared: a Java builder setter is optional by construction, so where the reference marks a param required (e.g. AgentBase.name) the diff raises `construction-required-flip` rather than silently accepting an under-specified construction. That is contract per the owner ruling and must not vary between ports. ALSO — 4 dead logger omissions deleted AgentServer/SkillBase/SkillManager/SkillRegistry `.logger`, dead since the owner ruling that logging is a MODULE-LEVEL capability (porting-sdk 7f2a67f surface + 37dcb2a signature). The other 8 ports did this in their fold lanes; java's was held back because this branch was doing the construction work. VERIFIED (real gates, sibling-adjacent checkout) diff_port_surface.py ✓ 2718 symbols; 40 excused omissions, 650 excused additions diff_port_signatures.py ✓ 1411 reference symbols, 7031 port symbols, 6098 excused Both exit 0. No JAR rebuild needed — this changes the enumerator only, no Java source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y1fEcuaivoN1poPGS75jAy * fix(agent): wire the missing AgentBase/SWMLService construction params The reference AgentBase.__init__ FORWARDS several params to collaborators rather than storing them on self: schema_path / config_file / schema_validation -> super().__init__ (SWMLService) [agent_base.py:205-207] token_expiry_secs -> SessionManager [agent_base.py:247] Java implemented AgentBase's own state but never wired that forwarding chain, so a Java developer could not point an agent at a config file or disable schema validation even though the SDK already shipped ConfigLoader (used by SecurityConfig + WebService) — the capability existed but was unreachable from AgentBase. Wired through, per Java's builder idiom: AgentBase.Builder — agentId, usePom, nativeFunctions, defaultWebhookUrl, suppressLogs, enablePostPromptOverride, checkForInputOverride, tokenExpirySecs (-> new SessionManager(secs)), and the three forwarded to Service: schemaPath, configFile, schemaValidation. AgentBase.build() now loads the config file's `service` section (name/route/host/port), with explicit builder values taking precedence, matching the reference's _load_service_config precedence. Service — new 9-arg constructor carrying schemaPath/configFile/ schemaValidation; schemaPath now actually reaches SchemaUtils (it was hardcoded null), and configFile reaches a real SecurityConfig, the same collaborators the reference forwards them to. SchemaUtils.getSchemaPath() — the reference reads self.schema_utils.schema_path at agent_base.py:210. Because Service now exposes the `security` SecurityConfig member the reference records, the PORT_OMISSIONS line claiming Java's SWML service has no such member is stale — deleted (implementing made it unnecessary). Enumerator folds (idiom at the emitter, RULES.md §2 / ALLOWLIST §0 — no new allow-list entries): - surface: construction-param read/write accessors strip to the `construction` contract that already compares them by name; getSecurity() folds onto the reference's `security` attribute like WebService's. - signatures: SWMLService.__init__ joins PREFER_FULL_OVERLOAD so the 1-arg convenience ctor stops hiding every param; Java's convenience-overload expression of "defaulted param" is re-applied as required:false. Tests: AgentConstructionParamsTest — 16 tests ported from the reference's TestAgentBaseInitialization / TestSWMLServiceInitialization, driving real behavior (token expiry decoded off the minted token, native_functions and default_webhook_url asserted on the rendered wire, config-file precedence, schema path reaching SchemaUtils, config file reaching SecurityConfig). Construction findings: AgentBase 17 -> 6, SWMLService 7 -> 3. All residual are the §7 basic_auth typed-split row (authUser/authPassword) plus the pre-existing env_provider/max_duration extras. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y1fEcuaivoN1poPGS75jAy * fix(surface): class B2 — fold or implement all 109 construction read-backs CONSTRUCTION-READBACK [java]: 109 -> 0 (all 217 caller-supplied params are readable back). Zero new ledger entries; 71 DEAD PORT_ADDITIONS deleted. ENUMERATOR FOLDS (60 of the 109). The B2 oracle now records a public __init__ attribute that is also a ctor param as a real class member, which invalidated two stale premises in the enumerator: - `_CONSTRUCTION_PARAM_ACCESSORS` stripped construction-param getters on the reasoning that "the oracle does not enumerate scalar state, so there is no member to fold onto". True before B2, false now. The strip is ORACLE-GATED: an entry applies only when the oracle does NOT record the underlying field, so it degrades automatically as the oracle grows. 3 entries went inert. - `_AI_CHAT_MEMBER_OVERRIDES` pinned AIChatClient/AIChatError to member lists that excluded url/code/message — all three are B2 members now. Plus per-class renames the generic same-name fold cannot reach: getSpace->host, getName->function_name (DataMap), isNumberedBullets->numberedBullets (the reference declares that attribute in camelCase verbatim; it is the wire key), getServerMessage->message, SkillBase.bind->__init__. The signature enumerator never applied `_SURFACE_METHOD_ALIASES` at all, so the same symbols were green on surface and `missing-port` on signatures (friction warning #1). Wired it in, gated on the NARROWER signature oracle so rows whose target only the surface records (__call__, __getattr__) do not fire. REAL DEFECTS FOUND AND FIXED: 1. SessionManager HMAC key disagreed with the reference — the same defect cpp hit. The reference keys with the secret_key STRING's bytes (self.secret_key.encode(), session_manager.py:79,152) and defaults it to a 64-char hex string; java stored 32 RAW bytes, so tokens were not interoperable. The byte[] overload's own docstring admitted the mismatch. secret_key is now a String; the byte[] overload decodes as UTF-8. 2. RelayError discarded the raw server message — it passed only the decorated "RELAY error {code}: {message}" to super(), while the reference preserves the undecorated value as self.message. 3. Call never carried project_id/context/segment_id. The event frames already carried all three; the port read none of them. Populated at all three construction sites. 4. AgentServer had no log_level param at all. 5. Prefabs discarded caller config: ConciergeAgent dropped services / hours_of_operation / special_instructions, FAQBotAgent dropped persona / suggest_related, SurveyAgent dropped survey_name / brand_name / max_retries / introduction. Each is now stored AND rendered into the prompt, so the value has effect rather than being dead state. 6. SkillBase never received the agent or retained its params (the reference passes both to __init__). Added bind(agent, params), called by SkillManager immediately before setup() — the reference's construct-then-setup ordering. 7. SWMLService host/name/port/route were PROTECTED fields with no reader. Also fixed two pre-existing gate failures found on the way: - pom.Section was absent from `signalwire.pom.pom` in the SIGNATURE dict: the name-keyed class->module map resolved the colliding simple name `Section` to a generated DTO module. The surface enumerator already had the FQN pin; the signature table did not. 5 signature drifts cleared. - The accessor fold ate `set_native_functions`: the reference files native_functions on AgentBase but set_native_functions on AIConfigMixin, and the "accessor is itself a reference member" guard was same-class only. It now consults the whole agentbase-family (friction warning #5). VERIFICATION (all six, before commit) diff_port_surface.py exit 0 — 2758 symbols diff_port_signatures.py exit 0 — 6399 excused divergences suites/doc_truth.py exit 0 — all 8 rules PASS suites/behavioral.py ENVELOPE + SWAIG-HTTP-INVOKE fail, both identical at HEAD (parked 4.2 request-options; no swaigHttpDump gradle target). WAIT-LIVENESS was red at HEAD and is green now. construction_readback.py exit 0 — 217/217 scripts/run-tests.sh 2219/2219 pass The native-name sidecar needed only regeneration — java already ships the producer, so the fold->doc drift resolved with no doc rewriting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y1fEcuaivoN1poPGS75jAy * ci(security): enforce SECRET-SCRUB-LIVE nightly — the credential-hygiene leg was never run java already shipped the SECRET-SCRUB-LIVE fixture (SecretScrubDump.java + the secretScrubDump Gradle task), but no gate ever invoked it. run-ci wired only SECRET-SCRUB — the STATIC leg, which greps the relay source for the raw-frame-log SHAPE. Those are two different rules: the static grep cannot see what the client actually WRITES at debug level, so a credential reaching the log by any path the grep does not model (a separate handler line, an interpolated field, a wrapper that re-serializes the frame) was unobserved. Adding SECRET-SCRUB-LIVE to BEHAVIORAL-NIGHTLY makes the RUNTIME property gated: the fixture drives the real RelayClient through a live WebSocket connect plus an inbound signalwire.authorization.state re-auth frame at SIGNALWIRE_LOG_LEVEL= debug with the corpus sentinels (project=PJ-TESTLEAK, token=PT-TESTLEAK, authorization_state=AENC-TESTLEAK), captures the process's own stdout+stderr, and asserts no sentinel appears verbatim. What is now proven that was not before: java's debug log provably contains no live credential, measured from the captured output rather than inferred from the source. Nightly is the rule's declared tier (it needs a live debug-level relay drive), so it joins the existing BEHAVIORAL-NIGHTLY line next to WAIT-LIVENESS/ RELAY-LIVENESS rather than the per-PR set. The gate is GREEN on java as shipped — no port fix was needed. java stores the re-auth blob without logging it (RelayClient.java:1203) and scrubs both frame log sites, matching the python reference. NON-VACUITY PROVEN, not assumed. Reintroducing the exact defect the rule exists to catch — a debug line interpolating the sentinel token into the log inside the fixture's drive window — turned the gate RED with `token oracle: {"leaked": false} java: {"leaked": true}`, then removing it returned it to green. A fixture that reported leaked=false without driving anything would not have moved. VERIFICATION python3 porting-sdk/scripts/suites/behavioral.py --port java --repo . \ --rules SECRET-SCRUB-LIVE exit 0, PASS ./gradlew -q secretScrubDump {"project":{"leaked":false},"token":{"leaked":false}, "authorization_state":{"leaked":false}} env SW_CI_TIER=nightly bash scripts/run-ci.sh [BEHAVIORAL-NIGHTLY] behavioral suite, nightly rules (WAIT-LIVENESS/RELAY-LIVENESS/SECRET-SCRUB-LIVE) ... PASS (plus SURFACE/DOC-TRUTH/TEST/GEN/BEHAVIORAL/PACKAGE-NIGHTLY/FMT/LINT PASS) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFKJhLvfV8yGrASwqxdgaf * ci: wire TYPE-EROSION at the current count, so the number can only shrink A port emitting `any` silently satisfies every type the reference declares — compare_param treats `any` on EITHER side as matching anything, and says so in its own docstring. That is a fair accommodation for a dynamic language and an unlimited opt-out, and until now nothing counted how much of it there was. It is not academic. ConciergeAgent.__init__ declares hours_of_operation: optional<dict<string,string>> in BOTH its method signature and its section-10 construction node. go shipped a single STRING — per-label hours unreachable, and global_data carrying a string where every other port carries a map — with no gate red anywhere. The oracle stated the type correctly and nothing enforced it. Wired per port at its measured count: php 176, cpp 122, ruby 97, rust 55, typescript 45, dotnet 19, go 16, java 13, perl 13. Total 556 slots where a reference declaration is currently unenforceable. RATCHET, DELIBERATELY, NOT A HARD GATE. Failing nine ports at once would produce nine allow-list entries, which is the exact opposite of the goal. Banking the current number means the count can be driven down on purpose while it can never grow by accident. Placed immediately after each port's SURFACE gate, because it reads the port_signatures.json that enumeration writes. cpp needed a hand edit: it runs gates SERIALLY via `run_gate "NAME" "desc" cmd` rather than the `sched_gate` DAG the other eight use, so a scripted insert anchored on sched_gate correctly skipped it rather than producing a silently dead line. Verified all nine: bash -n clean, and the gate PASSES at its banked ratchet. Non-vacuity proven rather than assumed — lowering a ratchet by one makes go, java and dotnet each exit 1 naming the regression, so the gate fires instead of decorating. * ci: install the porting-sdk scripts' declared dependencies before running them Completes the fix for dotnet's "311 Python symbol(s) missing from port" — a CI-only red that exited 0 locally against byte-identical inputs, against a port that was correct. Root cause: enumerate_surface.py reaches the reference oracle through generate_rest.py, which needs PyYAML. The CI interpreter had none, the import raised ModuleNotFoundError, a bare `except Exception: return None` swallowed it, the oracle loaded EMPTY, and 266 oracle-gated members silently failed to emit. Proven by md5-ing every diff input (only the REGENERATED port_surface.json differed) and then directly: 3.13 emits 1530 members, a yaml-less interpreter 1264, and the 266 lost are exactly the gated ones. porting-sdk 3eee516 added scripts/requirements.txt as the single declaration plus check_script_deps.py to keep it honest. This is the consumer half: every workflow that runs those scripts now installs from that manifest. 17 workflows across 9 ports — each port's surface-audit and doc-audit, plus go's multi-os. Every one previously ran the gate scripts and installed NOTHING, which is why the failure was one forgotten dependency away in nine repos simultaneously. perl/live-smoke.yml was deliberately SKIPPED: it has no setup-python step to anchor to, so an inserted pip line could not be guaranteed to have an interpreter. Flagged rather than forced. Placement verified programmatically, not by eye, because I got it wrong twice: the first attempt split the setup-python step in half (install landed between `- name:` and its `uses:`), and the second placed it AFTER the enumerate step in 7 workflows and BEFORE setup-python in 4. Both passed `yaml.safe_load` — structurally valid and semantically useless — which is exactly why the check asserts ORDER: install must come after any setup-python step and before the job's first run step. All 17 now satisfy that, with no duplicates and valid YAML across all 63 workflow files. * fix(gate): TYPE-EROSION raced the surface suite — I broke four ports' CI MY REGRESSION, from wiring the gate without testing it in CI's scheduling order. go, perl, rust and cpp went red with [TYPE-EROSION] ... FAIL: exit 1 json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) against a port_signatures.json that is valid and committed. The surface suite REGENERATES that file in place, and I declared the new gate with no scheduler resource — so it ran CONCURRENTLY with SURFACE (which holds res=surface) and read the file mid-write. Two fixes, because either alone is insufficient: 1. SCHEDULER (the real remedy): TYPE-EROSION now takes the same resource as each port's SURFACE gate — go/php/perl/dotnet res=surface, java res=gradle — so they cannot overlap. typescript, ruby and rust declare NO resource on SURFACE, so serialisation is not available there, which is why (2) is required. 2. THE GATE tolerates it. On an unparseable snapshot it now SKIPS with the byte count, the decode error, and the instruction to give it SURFACE's resource — rather than dying. Deliberately a SKIP and not a pass: 0 erosions measured from a half-written file is a FALSE GREEN, which is worse than the red it replaces. Verified all four paths rather than assuming: a normal run still exits 0; an empty snapshot skips with exit 0 and an explanatory message; the ratchet still FIRES (exit 1) when the count exceeds it, so the skip did not blunt real detection; the fleet total is unchanged at 556; and bash -n passes on all nine run-ci scripts. Worth naming the pattern, since it is the third instance today: a gate that reads a regenerated artifact must either hold that artifact's lock or refuse to guess. The other two were the empty-oracle fallbacks that produced phantom missing-symbol counts. Same shape — read a file someone else is writing, then report the result as if it were the truth. * feat(swml): adopt the 169-def schema — ai_sidecar verb + typed RingbackConfig Coordinated pass against porting-sdk 3435180 (169 $defs = the prior 167 + RingbackConfig + AiSidecar). Two upstream changes land together on purpose: doing them as two separate 9-repo passes would burn the fan-out twice. 1. RUNTIME schema — this SDK SHIPS schema.json for its own validation, so a stale copy means the shipped library validates against a schema with no ai_sidecar and no RingbackConfig. Re-vendored byte-for-byte from canonical (copied, not re-serialized: the x-sdk-* markup is load-bearing and a re-serialize would also churn formatting). 2. GENERATED types — regenerated from CANONICAL, which is what the generators read (NOT the vendored copy). That is why GEN-FRESH-SWML went red the moment canonical moved: it compares generated code against canonical. New surface: the ai_sidecar verb (live_transcribe plus an LLM/SWAIG/MCP loop) with AiSidecarConfig carrying all 12 fields — real direction/customer_role enums and SWAIG resolved through its $ref. RingbackConfig replaces what was collapsing to an untyped map: connect's ringback is now `array | RingbackConfig` with all 9 properties typed. VERIFIED, by running the gates rather than reading the diff: * GEN suite: all rules PASS (this port, and 9/9 across the fleet) * SURFACE-DIFF: exit 0 — the 6 drifts the oracle correctly raised (AiSidecarConfig.SWAIG + 5 ringback slots) are now implemented, not excused. No omission entry was added: a real missing capability is never idiom. * vendored copy asserted equal to canonical, 169 defs, x-sdk-* count 5 * behaviorally exercised against the vendored schema (go, ValidateVerb): ai_sidecar{prompt,lang,direction,customer_role} -> valid; missing `prompt` -> correctly rejected; customer_role "nobody" -> correctly rejected; ringback object form -> valid. port_signatures.json is regenerated and committed — SURFACE-FRESH fails otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFKJhLvfV8yGrASwqxdgaf * chore(surface): regenerate port_surface.json for the ai_sidecar/RingbackConfig surface Follow-up to the 169-def schema adoption in this PR. There are TWO per-port audit artifacts and the schema commit only refreshed one: port_signatures.json was regenerated, port_surface.json was not — so surface-audit went red naming the same 4 symbols the signatures gate had already accepted: signalwire.core.swml_verbs_generated.AiSidecar signalwire.core.swml_verbs_generated.AiSidecarConfig signalwire.core.swml_verbs_generated.AiSidecarConfig.SWAIG signalwire.core.swml_verbs_generated.RingbackConfig Regenerated with the same invocation CI uses. Verified: diff_port_surface.py exits 0 against the regenerated porting-sdk/python_surface.json (porting-sdk c08cb73), for this port and all nine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFKJhLvfV8yGrASwqxdgaf * test(swml): stop asserting a frozen verb HEADCOUNT — assert the schema loaded instead ai_sidecar makes 39 verbs, so every `== 38` assertion in the fleet broke at once. The fix is not 38 -> 39: that just moves the same trap one verb further out, and the next upstream verb breaks it again. WHY THESE ASSERTIONS SHOULDN'T EXIST AS EXACT COUNTS: the python reference has NO equivalent assertion. swml_service.py only LOGS the count (`log.debug("found_verbs_in_schema", count=len(verb_names))`). So an exact-count check is not parity with the reference — it is port-invented brittleness that fires on every correct upstream change and has never caught a real defect. Notably php/perl/dotnet/cpp already used `>= 38` and were unaffected, which is the shape that survives. What is actually worth pinning, and what these now assert: * the schema is not TRUNCATED (>= 38 verbs — catches a failed/partial load, the real bug) * the count agrees with the names actually extracted (catches a count/list mismatch) * ts: names are unique; and the pre-existing loop that every schema verb has a builder method — that loop was always the real test, the headcount was noise. DELIBERATELY LEFT EXACT: SwmlTest.java:358 `assertEquals(38, ...getVerbs().size())`. That counts the 38 verbs the test itself explicitly calls, so it is a genuine invariant of the test body, not a schema headcount. Changing it would weaken a real check. Verified per port by running the tests, not by reading: go pkg/swml ok ruby 64 runs, 117 assertions, 0 failures ts 2 files, 84 tests passed java SchemaUtilsTest + SwmlTest, 0 failures Scope note: all of go's THREE red checks (test, doc-audit, windows) were this same pair of tests — doc-audit and windows both run `go test ./...`. Likewise ruby's REST-COVERAGE red was the same single test failure surfacing again, because that gate runs the whole suite. Four ports, three gates, one root cause. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFKJhLvfV8yGrASwqxdgaf --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 70e13af commit 4dd76a7

43 files changed

Lines changed: 7779 additions & 2072 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/doc-audit.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ jobs:
5252
uses: actions/setup-python@v6
5353
with:
5454
python-version: '3.12'
55+
# Install the porting-sdk gate scripts' declared dependencies. NOT optional: the
56+
# enumerator reaches the reference oracle through generate_rest.py, which needs
57+
# PyYAML, and a bare `except Exception: return None` turned its absence into an EMPTY
58+
# oracle — 266 oracle-gated members silently failed to emit and the diff reported
59+
# "311 Python symbol(s) missing from port", blaming a port that was correct. A dev box
60+
# has PyYAML, so the failure is CI-only by construction. One manifest, shared by all
61+
# ten repos; check_script_deps.py fails if a script imports something it omits.
62+
- name: Install porting-sdk script dependencies
63+
run: python3 -m pip install --quiet -r ${{ github.workspace }}/porting-sdk/scripts/requirements.txt
64+
5565

5666
- name: Set up JDK 21
5767
uses: actions/setup-java@v5

.github/workflows/surface-audit.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ jobs:
4545
uses: actions/setup-python@v6
4646
with:
4747
python-version: '3.12'
48+
# Install the porting-sdk gate scripts' declared dependencies. NOT optional: the
49+
# enumerator reaches the reference oracle through generate_rest.py, which needs
50+
# PyYAML, and a bare `except Exception: return None` turned its absence into an EMPTY
51+
# oracle — 266 oracle-gated members silently failed to emit and the diff reported
52+
# "311 Python symbol(s) missing from port", blaming a port that was correct. A dev box
53+
# has PyYAML, so the failure is CI-only by construction. One manifest, shared by all
54+
# ten repos; check_script_deps.py fails if a script imports something it omits.
55+
- name: Install porting-sdk script dependencies
56+
run: python3 -m pip install --quiet -r ${{ github.workspace }}/porting-sdk/scripts/requirements.txt
57+
4858

4959
- name: Enumerate Java SDK surface
5060
working-directory: signalwire-java

GEN_TYPE_DEGENERACY_ALLOW.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,50 @@
1+
<!-- ══════════════════════════════════════════════════════════════════════════
2+
BEFORE YOU ADD AN ENTRY TO THIS FILE — READ THIS.
3+
4+
Every entry here is a place the parity checker STOPS comparing. That is a real cost:
5+
a divergence you list is a divergence no gate will ever catch again. So entries must
6+
be RARE, and each one must earn its place. Default to skepticism: assume the entry is
7+
NOT needed and make the case that it is.
8+
9+
The order of preference, always:
10+
1. FIX THE PORT so it matches the reference (add the missing member; make the
11+
signature match).
12+
2. FIX THE EMISSION so idiom folds onto the reference shape — the enumerator/emitter
13+
canonicalizes your language's spelling onto the oracle's (builder → __init__,
14+
getters → attributes, Result<T,E> → the plain return, CamelCase → the reference
15+
name, options-object/kwargs → the expanded param list, RAII/dispose → close).
16+
MOST divergences are idiom and belong here, not in this file.
17+
3. FIX THE REFERENCE if the oracle itself is wrong or stale (a Python-only symbol
18+
that leaked into the contract, a param the reference added and the oracle never
19+
re-enumerated). Fix Python / the oracle, then re-drift — do not paper over a
20+
broken reference with a per-port entry.
21+
4. Only when 1–3 genuinely cannot apply does an entry here become justified.
22+
23+
An entry is JUSTIFIED ONLY IF it is irreducible after correct emission — i.e. the
24+
divergence survives because the two languages genuinely cannot express the same thing,
25+
not because the emitter hasn't folded the idiom yet. If emission COULD fold it, the
26+
entry is a bug in this file; go fix the emitter.
27+
28+
Each entry MUST state WHY, concretely, in one of these forms:
29+
• ADDITION — this symbol exists in the port but not the reference. Answer: is it
30+
genuine port-only surface with NO reference twin (say what it is and why the
31+
reference has no equivalent), or is it IDIOM the emitter should have folded (then
32+
it does not belong here — fold it)? A convenience/alias/back-compat wrapper is NOT
33+
a justification.
34+
• OMISSION — this reference symbol has no port member. Answer: WHY can it not exist
35+
here — what specific language feature is absent (e.g. no async-context-manager
36+
protocol, no __init__ method protocol)? "impossible:" means the construct cannot
37+
be expressed at all; if it merely LOOKS different, that's idiom → fold it, don't
38+
omit it. Cite a precedent when one exists (e.g. RelayClient omits the same dunder).
39+
• SIGNATURE — the symbol matches by name but its parameters differ. Answer: is the
40+
difference a foldable idiom collapse (options-object, leading context/self,
41+
builder) — then EXPAND it in the signature emitter so names+count match, don't list
42+
it — or a genuine reference-only parameter with no cross-language analogue?
43+
44+
If you cannot write a crisp, specific WHY that survives the "could emission fold this?"
45+
test, the entry is not ready. Prove it's needed before you add it.
46+
═══════════════════════════════════════════════════════════════════════════════ -->
47+
148
# GEN-TYPE-DEGENERACY allowlist (java)
249

350
Each entry excuses one generated `path:kind:name` finding with a genuine reason.

0 commit comments

Comments
 (0)