This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: kmp-wasmgc-ffi-heap-boundary-guardrail

KMP WasmGC FFI Cross-Heap Lifecycle and Finalization Invariant

Enforces strict object lifespans and deterministic disposal contracts across Kotlin Multiplatform WasmGC managed heaps and JavaScript V8/JavaScriptCore host memory. Unchecked bi-directional referencing across the WasmGC/JS boundary leads to opaque circular cross-heap references that evade both engine garbage collectors. All cross-boundary exports and imports must leverage FinalizationRegistry, WeakRef, and explicit AutoCloseable/Disposable handles.

Prohibited Anti-Pattern: Retaining raw JS references in Kotlin Wasm or leaking Wasm exported references across JS FFI without explicit dereferencing or finalization registries.

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: KMP WasmGC FFI Cross-Heap Lifecycle and Finalization Invariant - Enforces strict object lifespans and deterministic disposal contracts across Kotlin Multiplatform WasmGC managed heaps and JavaScript V8/JavaScriptCore host memory. Unchecked bi-directional referencing across the WasmGC/JS boundary leads to opaque circular cross-heap references that evade both engine garbage collectors. All cross-boundary exports and imports must leverage FinalizationRegistry, WeakRef, and explicit AutoCloseable/Disposable handles.
globs: **/wasmJsMain/**/*.kt, **/wasmJsTest/**/*.kt, **/*Wasm*.kt, **/webMain/**/*.ts, **/webMain/**/*.js
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/kmp-wasmgc-ffi-heap-boundary-guardrail"
---

# KMP WasmGC FFI Cross-Heap Lifecycle and Finalization Invariant

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Retaining raw JS references in Kotlin Wasm or leaking Wasm exported references across JS FFI without explicit dereferencing or finalization registries.

## System Constraints
You are a Principal Mobile and WebAssembly Systems Engineer enforcing zero-leak memory invariants between Kotlin Multiplatform (WasmGC) and JavaScript runtime engines. WasmGC garbage collection operates within engine-level GC routines, but JS host references passed as external references (anyref/externref) or Wasm objects exported to JS retain strong roots across heap boundaries unless explicitly tracked and severed. You must reject any FFI boundary design that relies on implicit cross-heap GC or fails to provide explicit lifecycle disposal.

## Strict Guidelines
- Engineers MUST ALWAYS implement AutoCloseable or a dedicated dispose() interface on any Kotlin Wasm object that holds references to external JS objects (`JsAny`).
- Engineers MUST NOT store long-lived WasmGC exported references inside global JS closures, maps, or DOM nodes without registering them into a JavaScript `FinalizationRegistry` or holding them through a `WeakRef`.
- Engineers MUST use explicit integer handle tables or token IDs when transmitting reference handles across the JS-to-Wasm boundary to prevent opaque GC cycles.
- JavaScript wrappers around KMP Wasm exported objects MUST expose an explicit `dispose()` or `Symbol.dispose` method that invokes the corresponding Wasm free/teardown routine.
- Callbacks passing from JavaScript into Kotlin Wasm MUST NEVER capture enclosing JS host contexts unless the callback handle is guaranteed to be deregistered upon completion.
- Code SHOULD verify in test suites that cross-heap allocations do not increase retained heap size monotonically using V8 memory profiler snapshots (`performance.measureUserAgentSpecificMemory()`).

## Bad vs Good Implementations
### Preventing Uncollectible Cross-Heap Cycles in JS-to-WasmGC Bridge

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: The JavaScript class holds a strong reference to the WasmGC session, while passing an event closure holding a reference back to the JS instance into Wasm. This forms an uncollectible cross-heap cycle between the V8 JS heap and WasmGC heap that neither garbage collector can traverse and collect.
```typescript
class WasmBridgeSession {
  private wasmSession: any;
  private onEventCallback: (event: any) => void;

  constructor(wasmModule: any) {
    // Stores a direct WasmGC reference
    this.wasmSession = wasmModule.createSession();
    // Closure captures 'this', and passing it into WasmGC creates a hard circular root
    this.onEventCallback = (ev: any) => this.handleEvent(ev);
    this.wasmSession.registerListener(this.onEventCallback);
  }

  private handleEvent(event: any): void {
    console.log('Event received', event);
  }
  // No teardown method to unbind listener or dispose wasmSession reference
}
```

#### Production Standard (Correct)
> **Standard**: Uses an indirect numeric handle (token) rather than holding direct opaque managed references, registers with FinalizationRegistry for leak detection, and implements explicit disposal conforming to standard disposal patterns (Symbol.dispose) to cleanly release resources across the boundary.
```typescript
export class WasmBridgeSession implements Disposable {
  private wasmToken: number | null;
  private wasmModule: any;
  private static finalizer = new FinalizationRegistry((token: number) => {
    // Host-level safety net if explicit disposal was skipped
    console.warn(`WasmBridgeSession token ${token} collected without explicit dispose()`);
  });

  constructor(wasmModule: any) {
    this.wasmModule = wasmModule;
    this.wasmToken = wasmModule.allocateSessionToken();
    WasmBridgeSession.finalizer.register(this, this.wasmToken, this);
  }

  public [Symbol.dispose](): void {
    this.dispose();
  }

  public dispose(): void {
    if (this.wasmToken !== null) {
      WasmBridgeSession.finalizer.unregister(this);
      this.wasmModule.releaseSessionToken(this.wasmToken);
      this.wasmToken = null;
      this.wasmModule = null;
    }
  }
}
```

### Deterministic Kotlin WasmGC Foreign Object Lifecycle

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Kotlin Wasm keeps an unmonitored strong reference to `jsCallback`. If the JS side maintains a reference to `NativeBridgeSubscriber`, a foreign cross-boundary leak is created with no release mechanism.
```kotlin
import kotlin.js.JsAny
import kotlin.js.JsExport

@JsExport
class NativeBridgeSubscriber(val jsCallback: JsAny) {
    // Stores JS object directly on Kotlin WasmGC heap indefinitely
    fun notify(payload: JsAny) {
        invokeJsCallback(jsCallback, payload)
    }
}

private fun invokeJsCallback(target: JsAny, payload: JsAny): Unit = js("target(payload)")
```

#### Production Standard (Correct)
> **Standard**: Implements `AutoCloseable` to provide a deterministic teardown path that nullifies the `JsAny` reference, severing the WasmGC-to-JS edge and permitting immediate collection by host engine GC.
```kotlin
import kotlin.js.JsAny
import kotlin.js.JsExport

@JsExport
class NativeBridgeSubscriber(jsCallback: JsAny) : AutoCloseable {
    private var callbackRef: JsAny? = jsCallback

    fun notify(payload: JsAny) {
        val callback = checkNotNull(callbackRef) { "Subscriber has already been disposed" }
        invokeJsCallback(callback, payload)
    }

    override fun close() {
        // Explicitly nullify JS foreign reference from WasmGC heap
        callbackRef = null
    }
}

private fun invokeJsCallback(target: JsAny, payload: JsAny): Unit = js("target(payload)")
```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Recommended Agent Skills (askills.dev)

Ready-to-use executable skill functions tailored to this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: KMP WasmGC FFI Cross-Heap Lifecycle and Finalization Invariant

KMP WasmGC FFI Cross-Heap Lifecycle and Finalization Invariant defines an industrial-grade behavioral standard for mobile engineering. Enforces strict object lifespans and deterministic disposal contracts across Kotlin Multiplatform WasmGC managed heaps and JavaScript V8/JavaScriptCore host memory. Unchecked bi-directional referencing across the WasmGC/JS boundary leads to opaque circular cross-heap references that evade both engine garbage collectors. All cross-boundary exports and imports must leverage FinalizationRegistry, WeakRef, and explicit AutoCloseable/Disposable handles. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Retaining raw JS references in Kotlin Wasm or leaking Wasm exported references across JS FFI without explicit dereferencing or finalization registries. Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftRetaining raw JS references in Kotlin Wasm or leaking Wasm exported references across JS FFI without explicit dereferencing or finalization registries.Critical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS implement AutoCloseable or a dedicated dispose() interface on any Kotlin Wasm object that holds references to external JS objects (`JsAny`).

Engineers MUST ALWAYS implement AutoCloseable or a dedicated dispose() interface on any Kotlin Wasm object that holds references to external JS objects (`JsAny`).

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST NOT store long-lived WasmGC exported references inside global JS closures, maps, or DOM nodes without registering them into a JavaScript `FinalizationRegistry` or holding them through a `WeakRef`.

Engineers MUST NOT store long-lived WasmGC exported references inside global JS closures, maps, or DOM nodes without registering them into a JavaScript `FinalizationRegistry` or holding them through a `WeakRef`.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Engineers MUST use explicit integer handle tables or token IDs when transmitting reference handles across the JS-to-Wasm boundary to prevent opaque GC cycles.

Engineers MUST use explicit integer handle tables or token IDs when transmitting reference handles across the JS-to-Wasm boundary to prevent opaque GC cycles.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

JavaScript wrappers around KMP Wasm exported objects MUST expose an explicit `dispose()` or `Symbol.dispose` method that invokes the corresponding Wasm free/teardown routine.

JavaScript wrappers around KMP Wasm exported objects MUST expose an explicit `dispose()` or `Symbol.dispose` method that invokes the corresponding Wasm free/teardown routine.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Callbacks passing from JavaScript into Kotlin Wasm MUST NEVER capture enclosing JS host contexts unless the callback handle is guaranteed to be deregistered upon completion.

Callbacks passing from JavaScript into Kotlin Wasm MUST NEVER capture enclosing JS host contexts unless the callback handle is guaranteed to be deregistered upon completion.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
6

Code SHOULD verify in test suites that cross-heap allocations do not increase retained heap size monotonically using V8 memory profiler snapshots (`performance.measureUserAgentSpecificMemory()`).

Code SHOULD verify in test suites that cross-heap allocations do not increase retained heap size monotonically using V8 memory profiler snapshots (`performance.measureUserAgentSpecificMemory()`).

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is KMP WasmGC FFI Cross-Heap Lifecycle and Finalization Invariant critical for AI coding assistants?

Enforces strict object lifespans and deterministic disposal contracts across Kotlin Multiplatform WasmGC managed heaps and JavaScript V8/JavaScriptCore host memory. Unchecked bi-directional referencing across the WasmGC/JS boundary leads to opaque circular cross-heap references that evade both engine garbage collectors. All cross-boundary exports and imports must leverage FinalizationRegistry, WeakRef, and explicit AutoCloseable/Disposable handles. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Retaining raw JS references in Kotlin Wasm or leaking Wasm exported references across JS FFI without explicit dereferencing or finalization registries.) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

Related MOBILE Rules

View all rules →
1 Rule in Stack

KMP