Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ __pycache__/
*.egg-info/
build/
dist/

# Device-test fixtures, plans, generated Android outputs, and raw diagnostics
# stay local. Curated timestamped records are added explicitly after review.
plugin-testing/*
10 changes: 5 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ copyability, stream separation, machine output, keyboard safety, or recovery.
- Integration tests cover CLI lifecycle behavior against temporary plugin
roots, parent wiring, source preservation, rollback, and documentation
artifacts.
- Android fixture tests must compile the single plugin-level V2 runtime with
- Android fixture tests must compile the single plugin-level V3 runtime with
mixed C/C++ and Kotlin/Java feature input. Do not describe Python-only tests
or generated-text checks as Android compilation proof.
- Device tests are relevant only when qualifying generated runtime integration,
Expand Down Expand Up @@ -110,11 +110,11 @@ validation tier completed.
- `README.md` is a short product entry point; the separate GitHub Wiki contains
generator-specific user guidance.
- `CONTRIBUTING.md` is contributor documentation.
- `docs/V1-TO-V2-ARCHITECTURE.md` records contributor-facing architectural
history without defining a supported migration workflow.
- `docs/V3-ARCHITECTURE.md` records the contributor-facing V3 model without
defining a supported V2 migration workflow.
- `maintainers/` contains release/operation procedures.
- The immutable `v1-final` tag and Git history preserve the implementation
baseline. They create no V1 maintenance or compatibility contract.
- Historical tags and Git history preserve earlier implementation baselines.
They create no V2 maintenance or compatibility contract.

The main repository must not contain a second copy of Wiki user guides. GitHub
stores Wiki pages in `supernote-module-generator.wiki.git`; update and review
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ recursive-include docs *.md
recursive-include maintainers *.md
recursive-include architecture *.md
recursive-include tests *.py
recursive-include tests/fixtures *
recursive-include src/supernote_module_generator/templates *
global-exclude __pycache__ *.py[cod]
121 changes: 89 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ existing Supernote plugin. It generates the JSI, JNI, Kotlin Symbol Processing,
TypeScript, build, and lifecycle code that connects those implementations to
JavaScript.

V2 models one user-facing feature, regardless of where its implementation
V3 models one user-facing feature, regardless of where its implementation
lives. One feature may contain C++, C helper files, Kotlin, and Java together.
JSI is the only JavaScript frontend, and the plugin compiles one generated V2
JSI is the only JavaScript frontend, and the plugin compiles one generated V3
runtime/build component shared by all features.

V2 is the current stable architecture. Version `2.0.4` aligns the CLI help with
the actual Add, Update, Remove, feature-version, and Doctor behavior. It also
includes the cross-platform generator and generated-build improvements from
`2.0.3`, including Windows command discovery, Android toolchain diagnostics,
short coordinated runtime build paths, hardened failure handling, and safer
generated runtime teardown. Actual feature calls still require the plugin
runtime to be ready.
The initial V2 release series deliberately keeps advanced value/object features
and caller-controlled cancellation out of scope; the supported foundation is
described below.
Version `3.0.0.dev0` is the development line for first-class native objects and
declared copied value types. JavaScript keeps references to original C++,
Kotlin, and Java object instances, while declared value objects are validated
and copied. Arrays, nullable values, string enums, live object fields,
returned-only objects, explicit constructors/factories, and async object retention use
one language-neutral JavaScript and TypeScript model.

There are no V2 users or migration requirements. V3 deliberately has no V2
manifest reader, converter, compatibility mode, or migration tool.

## Install

Expand Down Expand Up @@ -118,31 +117,59 @@ declaration to JavaScript or TypeScript. `SupernotePluginAsync` is always explic
Kotlin `suspend`, C++ future-like types, or blocking implementation code never
silently change the public API.

An exported class publishes the object type. Its single eligible public
constructor becomes the normal `create(...)` factory, while every other method
still needs its own marker:
`SupernotePluginObject` declares reference semantics;
`SupernotePluginValue` declares copied structural semantics. Neither marker
publishes members or construction by itself. Every JavaScript-visible function,
method, field, and constructor requires its own explicit marker:

```cpp
// @SupernotePluginExport
class Document {
// @SupernotePluginValue
struct Point {
// @SupernotePluginExport
double x;
// @SupernotePluginExport
double y;
};

// @SupernotePluginObject
class Stroke {
public:
explicit Document(std::string path);
// @SupernoteConstructor
explicit Stroke(std::vector<Point> points);

// @SupernotePluginExport
bool intersects(const std::shared_ptr<Stroke> &other) const;

// @SupernotePluginExport
std::shared_ptr<Stroke> transformed(Point offset) const;

// @SupernotePluginExport
std::int32_t pageCount() const;
std::string label;

void resetInternalCache(); // ignored
void resetInternalCache(); // ignored
};

// @SupernotePluginExport
std::shared_ptr<Stroke> loadStroke(std::string path);
```

Initial V2 also supports the same narrow per-JavaScript-object model for
deliberately marked Kotlin/Java classes. Object parameters/results,
returned-only objects, inheritance, properties, custom factories, and general
object graphs are deferred.
JavaScript receives stable runtime-local identity: if the same live native
instance is exposed again in one active runtime generation, the same JavaScript
object is returned. C++ objects use generated shared ownership; JVM objects use
managed global references and `IsSameObject`. Returned-only objects omit a
constructor but retain the same methods, argument/result behavior, lifetime,
and identity. Marked native-object fields are live properties; source
mutability determines whether they are writable.

Kotlin data classes and supported Java records/final classes can declare copied
values. Kotlin/Java object classes use `@SupernotePluginObject`, and an eligible
constructor uses `@SupernoteConstructor`. Static/top-level functions returning
an object are ordinary explicitly marked factories; no separate factory marker
is needed.

## Initial value types
## V3 types and copied values

The initial semantic types and JavaScript/TypeScript mappings are:
The closed V3 semantic types and JavaScript/TypeScript mappings are:

| Supernote value | JavaScript/TypeScript |
| --- | --- |
Expand All @@ -153,11 +180,37 @@ The initial semantic types and JavaScript/TypeScript mappings are:
| `float32`, `float64` | `number` |
| `string` | `string` |
| `bytes` | `Uint8Array` |
| string enum | string-literal union |
| declared value object | typed plain object |
| native reference object | nominally branded generated interface |
| homogeneous array of `T` | `T[]` |
| nullable `T` | `T \| null` |

Strings use UTF-8 when crossing native/JNI boundaries. Byte values use
copy-based snapshot semantics and pass only the visible `Uint8Array` view.
Nullability, generic collections, maps, value structs, enums, unsigned values,
and zero-copy buffers are not part of the initial foundation.
Declared value fields are required and strictly validated. Extra JavaScript
fields are ignored without being read. Values and array containers are copied;
native-object leaves retain references and identity. Arrays must be dense and
homogeneous. `null` is accepted only where declared, while omitted values and
`undefined` remain invalid.

V3 intentionally does not accept arbitrary JavaScript objects, dynamic/JSON
trees, callbacks, maps, sets, tuples, general unions, recursive value objects,
raw pointers, numeric native handles, unsigned/platform-dependent C++ integer
types, or unmarked structural lookalikes.

## Language-family routing

The public API does not expose implementation-family details. Current V3 passes
C++ native objects only to C++ routes and Kotlin/Java native objects within the
shared JVM family. Complete copied values may cross generated C++/JVM internal
routes when both families declare the same logical schema.

Current V3 does not generate C++/JVM native-object proxies. A direct or nested
cross-family object reference is rejected during generation with a source-
located diagnostic. Object type IDs and public TypeScript shapes remain
language-neutral so a later proxy implementation does not require a public API
redesign.

## Async, errors, and lifetime

Expand Down Expand Up @@ -192,6 +245,11 @@ compilation for that environment, not that a particular Supernote firmware,
PluginHost, linker namespace, or SELinux policy will load and execute the code.
Target-device behavior must be validated on the intended device.

Same-process native runtime replacement is generation-checked and bounded. A
PluginHost process accepts at most 32 generated native generations for one
plugin component; restart PluginHost before another replacement if that limit
is reached.

The generator does not create the surrounding Supernote plugin. Plugin creation,
installation, and device debugging are covered by the
[official Supernote plugin documentation](https://docs.supernote.com/).
Expand All @@ -200,10 +258,9 @@ installation, and device debugging are covered by the

See [CONTRIBUTING.md](https://github.com/Ziv-Ink/supernote-module-generator/blob/main/CONTRIBUTING.md)
for development and validation rules and
[V1 to V2 architecture](https://github.com/Ziv-Ink/supernote-module-generator/blob/main/docs/V1-TO-V2-ARCHITECTURE.md)
for contributor-facing
architectural history. That history is not a project migration guide or a
compatibility promise.
[V3 architecture](https://github.com/Ziv-Ink/supernote-module-generator/blob/main/docs/V3-ARCHITECTURE.md)
for the contributor-facing runtime and type model. It is not a V2 migration
guide or compatibility promise.

## License

Expand Down
82 changes: 51 additions & 31 deletions docs/V1-TO-V2-ARCHITECTURE.md → docs/V3-ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
# V1 to V2 architecture
# V3 architecture

This document is architectural history for contributors. It explains why V2
code does not preserve several V1 shapes. It is not a converter guide, migration
analyzer, compatibility promise, or supported V1 maintenance policy.
This document summarizes the V3 architecture for contributors. It is not a V2
converter guide, migration analyzer, compatibility promise, or supported V2
maintenance policy.

## Same product, deliberate architecture break
## Deliberate architecture break

V2 remains in the same repository, Python distribution
V3 remains in the same repository, Python distribution
(`supernote-module-generator`), and CLI command (`supernote-module`). The
immutable `v1-final` tag preserves the exact final V1 development baseline;
`v1.0.0` remains the earlier historical release tag. Mainline V2 development
reuses proven V1 machinery where its behavior still matches the V2 contract.
historical tags preserve earlier development baselines. Mainline V3 development
reuses proven machinery only where its behavior still matches the V3 contract.

There are no external V1 projects requiring migration support. Experimental
V1 projects can be updated manually. Do not add an automatic converter,
There are no V2 users requiring migration support. Experimental projects can be
updated manually. Do not add an automatic converter,
read-only analyzer, legacy mode, source rewriter, or hidden compatibility
branch unless a real future user need produces a new explicit decision.

## Logical features replace backend-specific modules

V1 asked developers to create Native, Native JNI, or JSI module types. V2 asks
Earlier generators asked developers to create backend-specific module types. V3 asks
which starter source families to scaffold:

```text
C/C++ (native)
Kotlin/Java (JVM)
```

That selection creates example files only. A logical feature remains
That selection creates starter files only. A logical feature remains
language-neutral and may contain either or both families. Marked source and KSP
manifests determine its actual build and routing requirements.

Expand All @@ -37,7 +36,7 @@ second React Native bridge frontend.

## Source facts, API meaning, and routes are separate

The V2 pipeline is:
The V3 pipeline is:

```text
language source model
Expand All @@ -51,23 +50,26 @@ adapters where compiler knowledge is required. The common model contains only
facts with common Supernote meaning; it is not a collection of optional JNI,
C++, or Kotlin backend fields.

## Explicit intent replaces inference
## First-class objects and explicit intent

V1 object exports exposed supported public methods automatically. V2 ignores
ordinary code regardless of language visibility. `SupernotePluginExport` publishes a
declaration to JavaScript, `SupernotePluginInternal` creates hidden generated routing,
V3 represents declared native instances as nominal, runtime-local JavaScript
objects with stable identity, automatic lifetime management, and live marked
fields. Declared value types are validated copied data. Arbitrary JavaScript
object graphs are not accepted.

V3 ignores ordinary code regardless of language visibility.
`SupernotePluginExport` publishes a declaration to JavaScript,
`SupernotePluginInternal` creates hidden generated routing,
`SupernotePluginAsync` selects async Supernote semantics, and
`SupernoteConstructor` resolves an otherwise ambiguous construction path.

An exported class publishes its type and automatically uses its one eligible
public constructor as `create(...)`. Every regular method, property-like API,
static API, or special factory still requires explicit intent. There is no V1
automatic-member compatibility mode.
A marked object publishes its nominal type. Construction, every method, field,
static API, and factory still require explicit intent. Returned-only object
types are valid. There is no automatic-member compatibility mode.

## One compiled runtime per plugin
## One generated runtime per plugin

V1 generated a local React Native/Android package for each module. V2 generates
one plugin-level native build component containing shared runtime services and
V3 generates one plugin-level native build component containing shared runtime services and
all generated feature bindings. Logical features remain independent ownership
units, but they do not compile separate worker pools, JVM services, or runtime
singletons.
Expand All @@ -78,6 +80,15 @@ each feature gets a child FeatureSession. Background work never stores a
thread and receives valid runtime access only if the originating generation is
still alive.

Plugin replacement loads a uniquely named copy of the generated bindings and
performs an explicit native/JVM generation-identity handshake before JNI
registration. A stale or mismatched publication fails closed. Dependency lookup
uses one process-global SoLoader source per generated plugin component; native
generations retained by SoLoader are capped at 32 per PluginHost process. The
33rd load fails with a restart instruction instead of growing process state
without a bound. This leaves room for the required 25-cycle reload stress while
making the operational limit explicit.

## Async and teardown

Async is explicit API intent, not a Kotlin/C++ implementation inference.
Expand All @@ -86,6 +97,12 @@ supported Kotlin `suspend` implementations use the generated coroutine route.
Once accepted, both use the same pending-operation, exactly-once completion,
error, cancellation, and teardown lifecycle.

Worker and deferred-destruction services start lazily. When the final session
for one generated runtime generation is invalidated, that generation explicitly
stops and joins its workers, clears pending JVM completions, and drains cleanup.
This cleanup does not depend on the native library's static destructor because
PluginHost may retain loaded generations in one process.

Feature-only teardown rejects pending Promises while the runtime is healthy.
Runtime teardown performs no JSI work and drops later completions. Physical work
is cooperatively cancelled, never forcibly terminated, and teardown never waits
Expand All @@ -112,10 +129,13 @@ context. Contributors must preserve all parts of that contract:
- final component shutdown cannot unload code while queued or late cleanup can
still execute.

## What V1 still contributes
## Language-family boundary

Current native-object routes remain within one implementation family: C++
objects go to C++ and Kotlin/Java objects stay on the JVM. Declared copied
values may cross generated internal C++/JVM routes. Cross-family object proxies
are deferred without changing the public JavaScript or TypeScript model.

V1 remains useful for its parsers, code generation, JSI HostFunction/HostObject
patterns, shared ownership, transactions, diagnostics, build knowledge, KSP/JNI
machinery, tests, and regression history. Reuse those pieces when they satisfy
V2 decisions. Replace behavior that V2 deliberately changed instead of wrapping
it in a compatibility branch.
Earlier parsers, code generation, JSI HostFunction/HostObject patterns, shared
ownership, transactions, diagnostics, build knowledge, KSP/JNI machinery,
tests, and regression history remain useful only when they satisfy V3 decisions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Final generator and artifact verification

Source suite:

- `python3 -m pytest -q`: 571 passed in 14.32 seconds.

Exact isolated artifacts:

| Artifact | SHA-256 | Offline installed suite |
| --- | --- | --- |
| `supernote_module_generator-3.0.0.dev0-py3-none-any.whl` | `2681ba5afe39ef3b6d6e33077e1e04455624a3d223fd16881252d7d21254220b` | 571 passed in 14.68 seconds |
| `supernote-module-generator-3.0.0.dev0.tar.gz` | `3854e7c86caaafa8ef932dacf60731bbd5132a9a8bb1ce75ed5995c9089bb669` | 571 passed in 14.91 seconds |

Both artifacts were built into an initially empty isolated directory using the
project's setuptools PEP 517 backend. `twine check` accepted both. Each was
installed without network access or dependencies, reported `supernote-module
3.0.0.dev0`, and imported from its own virtual environment's `site-packages`.
The sdist environment used the locally installed `wheel` build dependency via
system site packages because a plain new Python 3.9 venv omits that standard
build dependency; the generated package itself was installed from the exact
sdist and took precedence over the host's unrelated V2 installation.

Archive checks confirmed the V3 runtime/object generator sources, object/value
annotation templates, JSI module template, console-script entry point, and
runtime regression tests are present in the relevant artifacts.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Retained deterministic fuzz and ASan/UBSan campaign:
command: python3 -m pytest -q -rs tests/test_v3_phase2_frontends.py tests/test_jvm_manifest_projection.py tests/test_v3_semantic_model.py tests/test_v3_phase4_conversion.py tests/test_v3_phase4_generated_kernels.py tests/test_v3_phase5_cpp_object_runtime.py
result: 155 passed in 4.95s
skips: 0

ThreadSanitizer runtime teardown/cancellation campaign:
command: env SUPERNOTE_V3_TSAN=1 python3 -m pytest -q -rs tests/test_plugin_runtime_codegen.py::test_generated_runtime_enforces_session_cancellation_and_cleanup_contracts
result: 1 passed in 1.91s
skips: 0
Loading