Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
c4f42a7
Redesign api
remdui Aug 6, 2026
36c28c9
ci: export api-redesign source for validation
remdui Aug 6, 2026
263b593
ci: expose source snapshot for full redesign validation
remdui Aug 6, 2026
5a801a0
chore: stage reviewed API redesign patch
remdui Aug 6, 2026
bc24279
refactor: complete API redesign review
github-actions[bot] Aug 6, 2026
6aeb6f8
chore: stage API redesign runtime fixes
remdui Aug 6, 2026
fca445e
fix: tighten redesigned API runtime behavior
github-actions[bot] Aug 6, 2026
23a4d56
docs: record exact-head API redesign gates
remdui Aug 6, 2026
348bb0b
test: fix HTTP transport null-parameter coverage
remdui Aug 6, 2026
386b9ee
fix: make configuration list mutations serializer-safe
remdui Aug 6, 2026
e721d02
docs: include toolkit mutation coverage in release gates
remdui Aug 6, 2026
8cbba16
test: cover invalid configuration list mutations
remdui Aug 6, 2026
bef2fe9
chore: stage typed feature test fixture updates
remdui Aug 6, 2026
a6a6894
test: align fixtures with typed feature descriptors
github-actions[bot] Aug 6, 2026
caef4a8
test: assert invalid list mutation diagnostics
remdui Aug 6, 2026
f529420
chore: stage CommandRelay typed config test fix
remdui Aug 6, 2026
13ec1fe
test: use typed CommandRelay whitelist config
github-actions[bot] Aug 6, 2026
d9a87ee
test: verify config mutation failures release write lock
remdui Aug 6, 2026
b5e9327
test: exercise soft and lifecycle reloads in Velocity acceptance
remdui Aug 6, 2026
8233c9e
test: lock acceptance reload semantics
remdui Aug 6, 2026
53b18bf
chore: stage acceptance repository variable fix
remdui Aug 6, 2026
1ff5adb
fix: use configured Maven repository in acceptance
github-actions[bot] Aug 6, 2026
6478dae
test: lock acceptance Maven repository wiring
remdui Aug 6, 2026
a663179
fix: serialize feature lifecycle mutations
remdui Aug 6, 2026
cabc9c4
test: await completed feature reload in acceptance
remdui Aug 6, 2026
ad2296e
fix: make feature reload dependency-transactional
Aug 6, 2026
7248fcf
test: fix reload response import
Aug 6, 2026
4736cc7
test: distinguish replacement and rollback failures
Aug 6, 2026
60aa372
test: align acceptance marker with transactional reload
Aug 6, 2026
308b4ff
fix: make feature disable cleanup consistent
Aug 6, 2026
1b54d9a
test: verify failed disables leave no active features
Aug 6, 2026
84ff902
test: register dependent descriptors for disable failures
Aug 6, 2026
bbcdcd8
fix: stage feature capability publication
Aug 6, 2026
31ffdbe
test: verify staged activation rollback
remdui Aug 6, 2026
504b0ac
fix: lease capability invocations across reloads
Aug 6, 2026
7b6099e
test: verify capability invocation proxy contract
remdui Aug 6, 2026
e9bfa54
fix: reject duplicate capability API classes
remdui Aug 6, 2026
cd832cf
test: prevent capability classloader retention
remdui Aug 6, 2026
f200648
fix: replace active services atomically
Aug 6, 2026
06004de
test: verify atomic internal service replacement
remdui Aug 6, 2026
0888b93
fix: expose configuration persistence failures
remdui Aug 6, 2026
d41817d
fix: propagate YAML persistence failures
remdui Aug 6, 2026
a91ea29
fix: preserve configuration write failures
remdui Aug 6, 2026
d46e89a
test: cover configuration persistence failures
remdui Aug 6, 2026
1b6e81d
fix: preserve last-known-good YAML state
remdui Aug 6, 2026
2960977
fix: retain last-known-good YAML on reload failure
remdui Aug 6, 2026
7986997
fix: block saves after invalid YAML reload
remdui Aug 6, 2026
ef888fa
test: preserve last-good YAML after parse failure
remdui Aug 6, 2026
f40a0df
fix: persist YAML atomically with copy-on-write state
remdui Aug 6, 2026
1503d13
fix: make configuration mutations transactional
remdui Aug 6, 2026
6cfad82
test: adapt config tests to copy-on-write commits
remdui Aug 6, 2026
2c7f433
test: cover atomic YAML write failures
remdui Aug 6, 2026
70339e2
test: verify atomic configuration transactions
remdui Aug 6, 2026
fab39bd
fix: preserve mismatched operator configuration
remdui Aug 6, 2026
bde43a6
fix: reject config type mismatches without deleting data
remdui Aug 6, 2026
d881253
test: preserve operator values on schema mismatch
remdui Aug 6, 2026
daf9e4c
fix: surface required command registration failures
remdui Aug 6, 2026
f6f84ba
fix: centralize feature command ownership
remdui Aug 6, 2026
127c3f8
fix: make feature command registration mandatory and global
remdui Aug 6, 2026
2cca3c5
fix: bind command ownership to feature scopes
remdui Aug 6, 2026
06369fa
fix: share command ownership and report runtime version
remdui Aug 6, 2026
86709ad
fix: bind lifecycle command managers to feature owners
remdui Aug 6, 2026
1ed7173
test: enforce global command ownership failures
remdui Aug 6, 2026
4d03c0b
fix: add explicit feature resource lifecycle state
remdui Aug 6, 2026
e5817cf
fix: quiesce and aggregate listener cleanup
remdui Aug 6, 2026
6c915fc
fix: drain in-flight feature tasks during cleanup
remdui Aug 6, 2026
bc159e4
fix: reject capability registration while quiescing
remdui Aug 6, 2026
3ce4d90
fix: close feature cache manager lifecycle
remdui Aug 6, 2026
753f211
fix: close command manager registration lifecycle
remdui Aug 6, 2026
88015ab
fix: enforce feature resource lifecycle transitions
remdui Aug 6, 2026
93f65bd
test: verify lifecycle quiesce and cleanup aggregation
remdui Aug 6, 2026
2f5ba15
fix: separate feature preparation from ingress activation
remdui Aug 6, 2026
c3f797d
fix: restore Vanish state before opening ingress
remdui Aug 6, 2026
5aeca9b
fix: run feature activation hooks after state restoration
remdui Aug 6, 2026
0b153b3
fix: bind Vanish ingress to post-restore activation
remdui Aug 6, 2026
1737c1b
fix: make global reload stateful and recoverable
remdui Aug 6, 2026
d131467
fix: rebind external data providers after cleanup
remdui Aug 6, 2026
6295e14
fix: quiesce data registrations before cleanup
remdui Aug 6, 2026
3a620b4
ci: validate api redesign branch pushes
remdui Aug 6, 2026
221d51f
ci: lint api redesign branch pushes
remdui Aug 6, 2026
9edbaca
test: cover runtime version and transactional global reload
remdui Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .github/workflows/ci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
push:
branches:
- main
- api-redesign

jobs:
lint:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci-tests-and-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
push:
branches:
- main
- api-redesign

jobs:
tests:
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Java 25](https://img.shields.io/badge/Java-25-007396?logo=openjdk&logoColor=white)](https://adoptium.net/)
[![License](https://img.shields.io/github/license/HauntedMC/ProxyFeatures)](LICENSE)

A modular feature framework and reusable API for your entire Velocity network.
A modular Velocity feature runtime with a reload-safe, dependency-free integration API.

## Quick Start

Expand Down Expand Up @@ -58,8 +58,9 @@ public runtime integration and clean shutdown. Set `PLATFORM_ACCEPTANCE_KEEP_WOR

## Published Modules

- `proxyfeatures-api`: reusable commands, configuration, cache, localization, packet, and text contracts.
- `proxyfeatures-contracts`: small cross-platform persistence and wire-message contracts shared with ServerFeatures.
- `proxyfeatures-api`: dependency-free root API, capability contracts, immutable DTOs, feature catalog, and extensions.
- `proxyfeatures-toolkit`: shared runtime implementation support for config, cache, localization, HTTP, and text.
- `proxyfeatures-contracts`: seven cross-platform wire-message contracts shared with ServerFeatures.
- `proxyfeatures`: the installable Velocity plugin; its jar keeps the historical `ProxyFeatures.jar` name.

The testkit is reactor-internal and is not part of the supported production API. Maven consumers should depend on the
Expand All @@ -69,15 +70,16 @@ smallest public artifact they need, with `provided` scope when the Velocity plug
<dependency>
<groupId>nl.hauntedmc.proxyfeatures</groupId>
<artifactId>proxyfeatures-api</artifactId>
<version>3.0.0</version>
<version>3.3.0</version>
<scope>provided</scope>
</dependency>
```

## Repository Layout

- `proxyfeatures-api`: public, reusable integration surface.
- `proxyfeatures-contracts`: shared sanction, player persistence, and cross-platform messaging model.
- `proxyfeatures-toolkit`: reusable implementation support used by the Velocity runtime.
- `proxyfeatures-contracts`: cross-platform wire messages only.
- `proxyfeatures-testkit`: common test infrastructure.
- `proxyfeatures-platform-velocity`: feature framework, feature implementations, and distributable jar.
- `proxyfeatures-platform-acceptance`: API-only consumer and real Velocity boot gate.
Expand All @@ -87,10 +89,11 @@ smallest public artifact they need, with `provided` scope when the Velocity plug
- [Configuration Guide](docs/CONFIGURATION.md)
- [Documentation Index](docs/README.md)
- [Architecture](docs/ARCHITECTURE.md)
- [Public API](docs/API.md)
- [Development Notes](docs/DEVELOPMENT.md)
- [Testing and Quality](docs/TESTING.md)
- [Release Process](docs/RELEASE.md)
- [Migrating to 3.0](docs/MIGRATING-3.0.md)
- [Migrating to 3.3](docs/MIGRATING-3.3.md)
- [Contributing](CONTRIBUTING.md)

## Community
Expand Down
13 changes: 13 additions & 0 deletions docs/API-REDESIGN-VALIDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# API redesign validation gates

ProxyFeatures 3.3 is intentionally breaking. The redesigned API is release-ready only when all of these gates pass on the exact release head:

- `./mvnw -U -B -ntp verify`
- architecture boundary and public-surface tests
- API, toolkit and contracts JAR-content tests
- toolkit configuration round-trip and mutation tests
- external-consumer compilation against published API artifacts only
- real Velocity platform acceptance, including capability availability and reload transitions
- no cross-feature implementation imports, reflective feature discovery, legacy service registries, compatibility adapters or runtime migration readers

A passing check from an earlier commit is not sufficient after a rebase or review fix. Release versions are updated only after the exact-head checks are green.
109 changes: 109 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# ProxyFeatures 3.3 Public API

The 3.3 API is a small dependency-free artifact. It exposes runtime discovery, feature lifecycle observations,
reload-safe capabilities, immutable domain snapshots, and lifecycle-safe extension registration. It does not expose
Velocity adapters, persistence entities, configuration implementation, feature classes, or internal coordination.

## Dependency

```xml
<dependency>
<groupId>nl.hauntedmc.proxyfeatures</groupId>
<artifactId>proxyfeatures-api</artifactId>
<version>3.3.0</version>
<scope>provided</scope>
</dependency>
```

Add an optional or required Velocity plugin dependency on `proxyfeatures` according to your integration policy.

## Discover the root API

The Velocity plugin instance implements `ProxyFeaturesApi`:

```java
ProxyFeaturesApi proxyFeatures = proxy.getPluginManager()
.getPlugin("proxyfeatures")
.flatMap(container -> container.getInstance())
.filter(ProxyFeaturesApi.class::isInstance)
.map(ProxyFeaturesApi.class::cast)
.orElseThrow(() -> new IllegalStateException("ProxyFeatures API is unavailable"));
```

`proxyFeatures.version()` reports both API and runtime versions. `features()` lists every known built-in feature and
its current `DISABLED`, `STARTING`, `ACTIVE`, `STOPPING`, or `FAILED` state.

## Resolve capabilities safely

Keep the reference, not the provider:

```java
CapabilityRef<PresenceApi> presence =
proxyFeatures.capabilities().reference(PresenceApi.class);

boolean hidden = presence.get()
.map(api -> api.isHidden(playerId))
.orElse(false);
```

`CapabilityRef` is stable for the lifetime of the plugin. Its `get()` and `require()` methods resolve the currently
active provider. A feature reload can remove one implementation and install another, so callers must resolve the
reference again for each operation. `require()` throws `CapabilityUnavailableException` when no provider is active.

## Capability inventory

| Contract | Owner | Purpose |
|---|---|---|
| `AdmissionApi` | Capacity | Atomic backend admission decisions and expiring leases |
| `QueueApi` | Queue | Asynchronous queue join/leave, lookup, enablement, and snapshots |
| `PresenceApi` | Vanish | Authoritative online visibility state and immutable snapshots |
| `FriendshipApi` | Friends | Asynchronous friendship decisions independent of ORM entities |
| `NetworkLocationApi` | AntiVPN | Session-scoped country-code lookup without triggering remote calls |
| `PlayerLanguageApi` | PlayerLanguage | Cached preference/resolved locale and asynchronous mutation |
| `PlayerCountApi` | PlayerCount | Vanish-aware network/backend count snapshots |
| `MaintenanceApi` | Maintenance | Global/backend state, bypass decisions, and snapshots |
| `RestartApi` | Restart | Drain state and expected return checks |
| `TwoFactorApi` | TwoFactor | Authentication lock and authentication-server checks |
| `VersionApi` | VersionCheck | Minimum supported protocol and version name |
| `SanctionsApi` | Sanctions | Asynchronous persistence-independent sanction history snapshots |
| `MotdExtensions` | Core | Ordered, lifecycle-safe MOTD contribution registration |

Features not listed as capability providers remain operator-facing or implementation-only. Their concrete classes are
not supported integration contracts.

## Admission and queue

`AdmissionRequest` is intentionally untrusted: callers provide player identity, previous/target server, and intent;
the runtime derives permissions, maintenance, restart, two-factor, capacity, and reservation policy. An allowed
`AdmissionDecision` always contains exactly one `AdmissionLease`. Commit it after the exact connection succeeds or
release/close it when the attempt ends. Lease operations are idempotent.

Queue operations return `CompletionStage` and typed status values. `QueueJoinRequest` cannot supply priority,
permission, capacity, or bypass values; those remain runtime-owned. All snapshots and contained collections are
immutable point-in-time views.

## MOTD extensions

External plugins can register an ordered contributor through the core capability:

```java
CapabilityRef<MotdExtensions> extensions =
proxyFeatures.capabilities().reference(MotdExtensions.class);

ExtensionRegistration registration = extensions.require().register(
"my-plugin",
100,
context -> Optional.of(MotdContribution.secondLine("Scheduled event tonight"))
);
```

Close the returned registration during plugin shutdown or before replacing it. Registration close is idempotent.
Blank contributions are ignored and contributor failures are isolated by the runtime registry.

## Threading and data rules

- Treat DTOs as immutable snapshots; obtain a new snapshot when freshness matters.
- Do not block Velocity event threads on `CompletionStage` results.
- Do not rely on concrete provider classes or cast API contracts to runtime implementations.
- Treat absence as normal for optional features and during reload transitions.
- API events and DTOs use Java types only; platform-specific events remain in the Velocity runtime.
159 changes: 116 additions & 43 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,132 @@
# Architecture Overview
# ProxyFeatures 3.3 Architecture

ProxyFeatures has two kinds of modularity: Maven modules enforce compile-time ownership, while runtime feature
classes provide independently configurable network behavior.
ProxyFeatures 3.3 separates public integration contracts, reusable implementation support, wire contracts, and the
Velocity runtime. Feature implementations collaborate through typed capabilities; they do not import one another.

## Build Modules
## Module boundaries

```text
proxyfeatures-testkit ──(test only)──▶ api / contracts / velocity
proxyfeatures-api ───────────────────▶ velocity plugin
proxyfeatures-contracts ─────────────▶ velocity plugin and ServerFeatures
velocity plugin ─────────────────────▶ packaged ProxyFeatures.jar
proxyfeatures-api ───────────────────────────────▶ external plugins
└───────────────────────────────────────▶ Velocity runtime
proxyfeatures-toolkit ──────────────────────────▶ Velocity runtime
proxyfeatures-contracts ─▶ Velocity runtime / ServerFeatures
proxyfeatures-testkit ────(test scope only)─────▶ project tests
Velocity runtime + the three production modules ▶ ProxyFeatures.jar
```

- `proxyfeatures-api` contains public reusable framework contracts. It knows about Velocity APIs but not the
ProxyFeatures plugin bootstrap or feature implementations.
- `proxyfeatures-contracts` owns the deliberately small persistence and cross-platform messaging schemas shared with
ServerFeatures. ServerFeatures therefore does not depend on the full proxy plugin, and both runtimes serialize the
same wire types.
- `proxyfeatures-testkit` contains test infrastructure and is never a production dependency.
- `proxyfeatures-platform-velocity` owns bootstrapping, lifecycle implementation, and concrete network features.
- `proxyfeatures-platform-acceptance` is activated by a Maven profile. Its API-only consumer proves the API is
available from the packaged plugin, and its final module boots a pinned Velocity runtime.
- `proxyfeatures-api` is dependency-free Java. It owns the root API, feature catalog, capability contracts, immutable
DTOs, identifiers, lifecycle-safe references, and public extension points.
- `proxyfeatures-toolkit` owns reusable config, cache, localization, formatting, parsing, and HTTP implementation
support. It is an implementation library, not a plugin integration API.
- `proxyfeatures-contracts` owns only seven cross-process wire messages. Persistence entities and runtime services do
not belong here.
- `proxyfeatures-platform-velocity` owns the plugin bootstrap, Velocity adapters, persistence, lifecycle framework,
internal collaboration ports, and all concrete features.
- `proxyfeatures-platform-acceptance` is an optional real-runtime gate whose consumer compiles against only public
APIs and obtains `ProxyFeaturesApi` from the Velocity plugin instance.

Dependencies point toward stable contracts. Public modules must not import plugin bootstrap, framework-internal, or
feature-implementation packages.
Dependencies point inward toward stable contracts. The API never imports Velocity, DataRegistry, DataProvider,
toolkit, contracts, runtime framework, or feature implementation packages.

## Design Goals
## Runtime ownership

- Keep features isolated so one module can be changed without destabilizing others.
- Centralize common lifecycle concerns such as command/listener/task registration.
- Let operators adopt features gradually, not all at once.
The `ProxyFeatures` plugin instance is the single authoritative `ProxyFeaturesApi` implementation and owns three
long-lived registries:

## Runtime Model
1. `DefaultCapabilityRegistry` publishes public API interfaces.
2. `DefaultFeatureCatalog` projects the explicit built-in manifest and lifecycle state.
3. `InternalServiceRegistry` connects implementation-only ports such as queue/admission coordination.

At startup, the plugin loads shared configuration, discovers available features, validates dependencies, and starts only the features that are enabled.
A feature registers services through its `FeatureApiManager`. Every registration is tagged with the owning feature
and gets an idempotent removal handle. Cleanup removes all services before the feature object is discarded. A
`CapabilityRef<T>` remains stable across disable, enable, and reload, but resolves the current provider for each
operation. Consumers must not retain a resolved implementation across reloads.

During runtime, each feature owns its own behavior while using shared framework services for common tasks (config access, lifecycle management, logging, and integration points).
Feature lifecycle is externally observable as:

On reload/shutdown, features are asked to clean up resources so stale listeners, tasks, and cached state do not leak into the next run.

The plugin entry point implements `ProxyFeaturesContext`, which gives reusable configuration/resource services the
minimal host capabilities they need without coupling the API module back to the concrete plugin.

## Configuration and Data
```text
DISABLED → STARTING → ACTIVE → STOPPING → DISABLED
└──────────────────────→ FAILED
```

- `config.yml` stores shared/global settings only.
- Each feature owns `features/<FeatureName>/config.yml`.
Startup includes context creation, defaults, configuration/localization reload, feature initialization, and optional
state restoration. Failure in any of those stages moves the catalog to `FAILED` and triggers cleanup.

## Feature discovery and isolation

`BuiltInFeatures` explicitly lists all 33 shipped features. Runtime classpath scanning is not used. The manifest is
the source of truth for feature identity, implementation constructor, startup phase, dependency declarations,
classification, reload/failure policy, and published capability names. Each feature owns only its configuration and
localization defaults plus runtime behavior.

Production code inside one feature package may not import another feature package. Cross-feature behavior uses:

- a public capability when external plugins can reasonably use the contract;
- a runtime-only collaboration port when the interaction is an implementation detail;
- a shared framework or toolkit utility when the behavior is genuinely generic;
- a wire contract when the boundary crosses processes.

Automated architecture tests enforce these rules, the dependency-free API, the wire-only contracts module, the
explicit 33-feature manifest, and the absence of the removed discovery/service systems.

## Feature inventory

“Consumes” lists public capabilities. Internal ports are deliberately unavailable to external plugins.

| Feature | Role | Provides | Consumes / internal collaboration |
|---|---|---|---|
| Announcer | Internal | — | shared lifecycle/toolkit |
| AntiBot | Internal | — | shared `IpAddressUtil` |
| AntiVPN | Provider | `NetworkLocationApi` | shared `IpAddressUtil` |
| Broadcast | Internal | — | — |
| Capacity | Provider | `AdmissionApi` | `MaintenanceApi`, `RestartApi`, `TwoFactorApi`; internal `QueueAdmissionPort` |
| ClientInfo | Internal | — | — |
| CommandHider | Internal | — | — |
| CommandLogger | Internal | — | — |
| CommandRelay | Internal | — | wire `CommandRelayMessage` |
| ConnectionInfo | Internal | — | — |
| Friends | Provider | `FriendshipApi` | `PresenceApi` |
| HLink | Internal | — | — |
| Hub | Internal | — | — |
| Maintenance | Provider | `MaintenanceApi` | core `MotdExtensions` |
| Messenger | Consumer | — | `FriendshipApi`, `PresenceApi` |
| Motd | Consumer | — | `PresenceApi`, `VersionApi`, core `MotdExtensions` |
| PlayerCount | Provider | `PlayerCountApi` | `PresenceApi`; wire `PlayerCountSnapshotMessage` |
| PlayerInfo | Consumer | — | `PlayerLanguageApi`, `SanctionsApi` |
| PlayerLanguage | Provider | `PlayerLanguageApi` | `NetworkLocationApi` |
| PlayerList | Consumer | — | `PresenceApi` |
| ProxyInfo | Internal | — | — |
| Queue | Provider | `QueueApi` | required `AdmissionApi` |
| ResourcePack | Internal | — | — |
| Restart | Provider | `RestartApi` | internal capacity coordination; wire `RestartLifecycleMessage` |
| Sanctions | Provider | `SanctionsApi` | — |
| ServerLinks | Internal | — | — |
| SlashServer | Internal | — | — |
| StaffChat | Internal | — | wire `StaffChatMessage` |
| TextCommands | Internal | — | — |
| TwoFactor | Provider | `TwoFactorApi` | — |
| Vanish | Provider | `PresenceApi` | wire `VanishStateMessage` |
| VersionCheck | Provider | `VersionApi` | — |
| Votifier | Internal | — | wire `VoteMessage` |

## Shared implementation services

Generic implementation code lives outside feature packages:

- one cached `PlayerReferenceResolver` per plugin runtime serves persistence-aware features;
- `HttpTransport` is the shared HTTP transport, including Discord webhook JSON posts;
- `IpAddressUtil` is shared by AntiBot and AntiVPN;
- `DefaultMotdExtensions` owns ordered, lifecycle-safe MOTD contributions;
- toolkit config/cache writes use centralized path handling, validation, and atomic file replacement.

## Configuration and data

- `config.yml` stores shared feature enablement and global settings.
- Each feature owns `features/<FeatureName>/config.yml` and localized message files.
- Framework messages live in `lang/messages*.yml`.
- Each feature owns `features/<FeatureName>/messages*.yml`.
- Some features may use additional local files for structured data.
- Shared database entities and Redis wire messages live in `proxyfeatures-contracts`; publishing and subscription
behavior remains platform-owned.

## Why This Matters

For operators, this architecture means safer rollout and easier troubleshooting.
- Persistence entities stay with their owning runtime feature or framework adapter.
- Redis messages stay in `proxyfeatures-contracts`; publishers/subscribers remain runtime-owned.

For contributors, it means clearer boundaries: implement behavior inside a feature, keep shared behavior in the framework, and avoid tight coupling between unrelated modules.
See [Public API](API.md) for plugin integration and [Migrating to 3.3](MIGRATING-3.3.md) for the intentionally
breaking transition.
Loading