@voltro/plugin-matchmaking 0.0.0-stage → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24492 -0
- package/LICENSE +61 -0
- package/README.md +272 -2
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +8333 -0
- package/dist/admission.entity.js +17 -0
- package/dist/assignment.js +125 -0
- package/dist/avoidPairs.js +4 -0
- package/dist/contract.js +105 -0
- package/dist/director-app/matchmaking.director.workflow.server.tsx +5 -0
- package/dist/director-app/matchmaking.director.workflow.tsx +13 -0
- package/dist/director.js +72 -0
- package/dist/directorContract.js +15 -0
- package/dist/directorWorkflow.js +5 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +20 -0
- package/dist/index.d.ts +523 -0
- package/dist/index.js +12 -0
- package/dist/inspect.js +40 -0
- package/dist/packing.js +71 -0
- package/dist/parties.js +316 -0
- package/dist/party.entity.js +33 -0
- package/dist/partyComponents.js +176 -0
- package/dist/partyContract.js +24 -0
- package/dist/plugin.js +257 -0
- package/dist/port.d.ts +116 -0
- package/dist/port.js +4 -0
- package/dist/queue.d.ts +91 -0
- package/dist/queue.js +79 -0
- package/dist/queueDeclarations.js +27 -0
- package/dist/roomsAllocator.d.ts +67 -0
- package/dist/roomsAllocator.js +75 -0
- package/dist/rpc.d.ts +247 -0
- package/dist/rpc.js +201 -0
- package/dist/server.d.ts +337 -0
- package/dist/server.js +8 -0
- package/dist/service.js +369 -0
- package/dist/strategies.d.ts +97 -0
- package/dist/strategies.js +140 -0
- package/dist/testing.d.ts +296 -0
- package/dist/testing.js +151 -0
- package/dist/ticketContract.d.ts +52 -0
- package/dist/ticketContract.js +30 -0
- package/dist/tickets.entity.js +49 -0
- package/dist/web.d.ts +279 -0
- package/dist/web.js +7 -0
- package/package.json +104 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Voltro — Proprietary Software License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Voltro UG. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and all associated source code, documentation, and other
|
|
6
|
+
materials (the "Software") are the proprietary property of Voltro UG
|
|
7
|
+
("Voltro"). This is NOT open-source software.
|
|
8
|
+
|
|
9
|
+
Your access to and use of the Software is governed exclusively by the Voltro
|
|
10
|
+
Terms of Service at:
|
|
11
|
+
|
|
12
|
+
https://voltro.cloud/legal/terms
|
|
13
|
+
|
|
14
|
+
By downloading, installing, or using the Software you agree to those Terms.
|
|
15
|
+
Subject to and conditioned on your continued compliance with them, Voltro
|
|
16
|
+
grants you a limited, non-exclusive, non-transferable, non-sublicensable,
|
|
17
|
+
revocable license to use the Software. The license covers use by your
|
|
18
|
+
authorised named users only, up to the number of seats (or other billing metric)
|
|
19
|
+
your plan or order covers; each named user must be an identified individual
|
|
20
|
+
using their own personal account, and accounts, access credentials and seats
|
|
21
|
+
must not be shared. All rights not expressly granted in the
|
|
22
|
+
Terms are reserved by Voltro. If you do not agree to the Terms, you have no
|
|
23
|
+
license and may not use the Software.
|
|
24
|
+
|
|
25
|
+
The Terms of Service — including the scope of permitted use, plan
|
|
26
|
+
entitlements, fees, and pricing — may change from time to time. The version in
|
|
27
|
+
force is the one published at the URL above. Commercial terms and pricing are
|
|
28
|
+
NOT part of this notice and are not fixed by, or granted in perpetuity by, the
|
|
29
|
+
version of the Software in which this file appears.
|
|
30
|
+
|
|
31
|
+
Permitted use — your own software. Voltro is a development framework. You may
|
|
32
|
+
use the Software to build, run, and commercially distribute your own
|
|
33
|
+
applications and services, and to charge your own customers for them, without
|
|
34
|
+
owing Voltro any share of that revenue or any further permission beyond the
|
|
35
|
+
Terms. Applications you build with the Software are yours.
|
|
36
|
+
|
|
37
|
+
Prohibited use — reselling Voltro. You may NOT redistribute, resell,
|
|
38
|
+
sublicense, publish, or otherwise provide the Software itself — in whole or in
|
|
39
|
+
part, in source or compiled form — to third parties as a library, framework,
|
|
40
|
+
SDK, template, starter, or development tool for them to build upon, whether
|
|
41
|
+
standalone or repackaged. Shipping the Software as a non-separable, bundled
|
|
42
|
+
dependency inside an application you deliver (for example, compiled into your
|
|
43
|
+
deployed app) is permitted; extracting or re-exposing it as a reusable
|
|
44
|
+
component for others is not.
|
|
45
|
+
|
|
46
|
+
Except as expressly permitted above or by the Terms, you may not copy, modify,
|
|
47
|
+
merge, publish, sublicense, sell, distribute, or create derivative works of the
|
|
48
|
+
Software, in whole or in part.
|
|
49
|
+
|
|
50
|
+
Third-party open-source components distributed alongside the Software are the
|
|
51
|
+
property of their respective owners and are licensed under their own terms; see
|
|
52
|
+
THIRD-PARTY-NOTICES.md where provided. Nothing in this notice limits your
|
|
53
|
+
rights, or extends Voltro's, in those components.
|
|
54
|
+
|
|
55
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
56
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
57
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
58
|
+
VOLTRO BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM,
|
|
59
|
+
OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
|
|
60
|
+
|
|
61
|
+
For licensing inquiries, contact Voltro UG.
|
package/README.md
CHANGED
|
@@ -1,3 +1,273 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Matchmaking foundations
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This implementation is **built**. It contains queue descriptions, pure matching
|
|
4
|
+
policies and an installable SQL solo-ticket admission adapter.
|
|
5
|
+
|
|
6
|
+
## Take
|
|
7
|
+
|
|
8
|
+
`@voltro/plugin-matchmaking/queue` exports `defineQueue` and `runQueueMatch`;
|
|
9
|
+
`./strategies` exports `fifo`, `skillWindow`, `regionLatency`, `composite` and
|
|
10
|
+
`validateMatchOutput`. The root also exports these functions and `packTeams`.
|
|
11
|
+
The server root also exports `matchmakingPlugin(options)` and `MatchmakingPort`.
|
|
12
|
+
The factory installs three admission tables and the enqueue/cancel/status RPCs;
|
|
13
|
+
it requires explicit `queues`, trusted `prepareTicket` and transactional `admit`
|
|
14
|
+
callbacks. No default skill/policy provider, Director or Room allocation exists
|
|
15
|
+
yet. A queue descriptor alone does not activate matching. Browser descriptors
|
|
16
|
+
import `./rpc`, errors from `./errors`; the root now includes server-only code.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { Schema } from 'effect'
|
|
20
|
+
import { defineQueue } from '@voltro/plugin-matchmaking/queue'
|
|
21
|
+
import { skillWindow } from '@voltro/plugin-matchmaking/strategies'
|
|
22
|
+
|
|
23
|
+
const ticket = Schema.Struct({
|
|
24
|
+
skill: Schema.Number,
|
|
25
|
+
region: Schema.String,
|
|
26
|
+
latencies: Schema.Record(Schema.String, Schema.Number),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const queue = defineQueue({
|
|
30
|
+
name: 'ranked-2v2', room: 'arena', ticket,
|
|
31
|
+
teams: { count: 2, size: 2 },
|
|
32
|
+
pools: { byRegion: attributes => attributes.region },
|
|
33
|
+
matchFunction: skillWindow({ initialWindow: 100, growPerSecond: 25,
|
|
34
|
+
maxWindow: 600, latencyMaxMs: 120 }),
|
|
35
|
+
minQuality: { initial: 0.8, after: [[30000, 0.5], [90000, 0]] },
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`runQueueMatch(queue, { tickets, now, avoid })` takes server-owned ticket evidence
|
|
40
|
+
and an explicit clock. Tickets contain `id`, `tenantId`, `queue`, `members`,
|
|
41
|
+
`createdAt`, `attributes`; this is a pure Director input, not a player RPC state
|
|
42
|
+
payload. The function schema-checks attributes without silently coercing data,
|
|
43
|
+
partitions through the captured pool callback, executes the actual matching slot
|
|
44
|
+
and validates its output. Ticket tenant mixing, duplicate ticket/subject IDs,
|
|
45
|
+
future creation times, sparse arrays, non-JSON attributes and budgets refuse.
|
|
46
|
+
Object key order does not change JSON identity.
|
|
47
|
+
|
|
48
|
+
## Parties
|
|
49
|
+
|
|
50
|
+
`matchmakingPlugin({ …, parties: { maxPartySize?, partyInviteTtlMs? } })` installs
|
|
51
|
+
ephemeral SQL parties (default size 8, max 256; invites live 10 minutes by default).
|
|
52
|
+
Routes under `matchmaking.parties.*`: `create` (idempotent per `requestId`, the caller
|
|
53
|
+
becomes leader; `approval: 'leader' | 'open'`), `invite` (leader only), `join`
|
|
54
|
+
(needs a live invite unless the party is open; `party_full` at capacity), `leave`,
|
|
55
|
+
`kick` and `transferLeader` (leader only), `setReady`, plus the live queries
|
|
56
|
+
`mine` (the caller's party or `null`) and `invites`. Hooks in `./web`: `useParty`,
|
|
57
|
+
`usePartyInvites`, `useCreateParty`, `useInviteToParty`, `useJoinParty`,
|
|
58
|
+
`useLeaveParty`, `useKickFromParty`, `useSetPartyReady`, `useTransferPartyLeader`, and the
|
|
59
|
+
presentational `PartyPanel` (en/de `partyLabels`).
|
|
60
|
+
|
|
61
|
+
A subject belongs to at most one party per tenant (`already_in_party`). When the
|
|
62
|
+
leader leaves, the longest-standing member takes over; the last member leaving
|
|
63
|
+
dissolves the party and its invites. `PartyMembershipPort` answers current
|
|
64
|
+
membership for other plugins. Export adds `parties` (own membership, sent and
|
|
65
|
+
received invites); erase leaves the party first, then deletes invites, then runs
|
|
66
|
+
the ticket hook.
|
|
67
|
+
|
|
68
|
+
Party search: the leader calls `matchmaking.parties.search({ partyId, queue,
|
|
69
|
+
requestId, preferences })` (`useSearchAsParty`) once every member is ready
|
|
70
|
+
(`party_not_ready` otherwise). One ticket lists all members and every member's claim
|
|
71
|
+
points at it, so nobody can search alone meanwhile (`already_searching`). Members read
|
|
72
|
+
it with `matchmaking.status`; `mine` reports it as `searchTicketId`. Any member stops
|
|
73
|
+
it with `matchmaking.parties.cancelSearch` (`useCancelPartySearch`); the solo
|
|
74
|
+
`matchmaking.cancel` refuses party tickets. Join, leave, kick, leader transfer and
|
|
75
|
+
withdrawn readiness cancel an open search in the same transaction. The leader's
|
|
76
|
+
`prepareTicket` result stands for the party. Locks go party → member claims (sorted)
|
|
77
|
+
→ queue. The `party.invited` event and presence remain open.
|
|
78
|
+
|
|
79
|
+
### Server roster composition
|
|
80
|
+
|
|
81
|
+
Resolve `PartyRosterPort` from `./port` through the scoped Effect context for
|
|
82
|
+
trusted server composition. `read(tenantId, partyId)` returns `null` for a missing
|
|
83
|
+
party in that tenant, otherwise a detached, frozen
|
|
84
|
+
`{ tenantId, partyId, leaderId, memberIds }` snapshot (leader first, remaining IDs
|
|
85
|
+
in stable order). The default reader locks the party before its members, matching
|
|
86
|
+
the writers' order without writing admission counters, tickets or timestamps.
|
|
87
|
+
Current leader transfer, privacy erasure and final-member dissolution are observed.
|
|
88
|
+
Malformed/corrupt membership refuses instead of returning a partial roster.
|
|
89
|
+
|
|
90
|
+
This adds no player RPC. The caller must authorize its operation and resolve
|
|
91
|
+
actual member Subjects and current World locations. IDs do not imply Subject
|
|
92
|
+
kinds or movement authority; a snapshot is not a lease. A durable World command
|
|
93
|
+
must retain its original captured members on replay. Reads created before binding,
|
|
94
|
+
queued across rebinding/deactivation, or completed after a binding change refuse
|
|
95
|
+
with `unavailable`. Full replacements supply this separate port in their scoped
|
|
96
|
+
Layer; `fakePartyRosterLayer(read)` in `./testing` accepts the complete reader and
|
|
97
|
+
fails closed when omitted. The existing membership port is unchanged.
|
|
98
|
+
|
|
99
|
+
## Configure
|
|
100
|
+
|
|
101
|
+
| Option | Default | Authoring budget |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `teams` | required | At most 64 teams, 256 players/team, 4,096 total slots |
|
|
104
|
+
| `maxTickets` | 10,000 | Maximum supplied tickets; not accepted Director capacity |
|
|
105
|
+
| `maxSearchNodes` | 100,000 | Branch inspections shared across policies and partitions |
|
|
106
|
+
| `maxAvoidPairs` | 10,000 | Maximum supplied symmetric exclusion pairs |
|
|
107
|
+
| `maxAttributeBytes` | 16,384 | UTF-8 JSON bytes per ticket; JSON depth at most 32 |
|
|
108
|
+
| `minQuality` | initial 0, no steps | Non-increasing quality floor with strictly increasing wait times |
|
|
109
|
+
| `cycleMs` | 1,000 | Descriptor only; no scheduler is installed |
|
|
110
|
+
| `ticketTtlMs` | 300,000 | SQL ticket lifetime; owner-triggered expiry, no sweep job yet |
|
|
111
|
+
| `maxPreferencesBytes` | 4,096 | Bounded JSON preference intent; at most 65,536 bytes |
|
|
112
|
+
| `maxExportRows` | 10,000 | Refuses an incomplete privacy export/erase; at most 100,000 rows |
|
|
113
|
+
|
|
114
|
+
Rules, schedules and callback selection are captured at description time.
|
|
115
|
+
A matching call copies/fixes its ticket data; the solver owns no clock, timer,
|
|
116
|
+
connection, store or per-process authoritative ticket registry. Parties are
|
|
117
|
+
indivisible. Parties larger than a team stay in leftovers. The iterative solver
|
|
118
|
+
backtracks when first-fit packing fails and stops at the configured branch budget,
|
|
119
|
+
returning `exhausted`, `searchNodes` and every unassigned ticket. These counters
|
|
120
|
+
are algorithm observations, not measured CPU duration or throughput.
|
|
121
|
+
|
|
122
|
+
## Hook
|
|
123
|
+
|
|
124
|
+
`matchmaking.enqueue` and `matchmaking.cancel` are Actions: the adapter owns
|
|
125
|
+
their SQL transactions. `matchmaking.status` is a reactive Query on the ticket
|
|
126
|
+
table; the executor checks the verified caller and exact tenant/owner. The wire
|
|
127
|
+
accepts only `{ queue, requestId, preferences }`, never authoritative attributes,
|
|
128
|
+
party members, state or assignments. Required `prepareTicket` resolves trusted
|
|
129
|
+
attributes before SQL; required `admit` rechecks durable eligibility inside the
|
|
130
|
+
borrowed transaction. Actual default Social/Sanctions integration is still open.
|
|
131
|
+
|
|
132
|
+
Subject and queue admission rows serialize one live solo search across queues
|
|
133
|
+
and the live queue cap. Exact request receipts return the committed ticket before
|
|
134
|
+
calling either provider again, through the subject erase fence; changed intent
|
|
135
|
+
refuses. Cancel is repeatable. Starting a new intention expires the owner’s old
|
|
136
|
+
expired search atomically. Status is read-only and may show `open` after its
|
|
137
|
+
`expiresAt` until a new intention or future sweep persists expiry.
|
|
138
|
+
|
|
139
|
+
`./web` provides `useEnqueueMatchmaking`, `useCancelMatchmaking` and
|
|
140
|
+
`useMatchmakingStatus(ticketId, apiName)`. Actions are explicit; absent ticket
|
|
141
|
+
selection keeps the subscription idle. No hidden polling/retry/auto-enqueue is
|
|
142
|
+
installed. Assignment events, listener revocation, workflow triggers, ordinary
|
|
143
|
+
queue discovery and the combined `useMatchmaking` lobby remain unfinished.
|
|
144
|
+
A pure match result grants no seat/session/token. SQL is ticket truth; no hot
|
|
145
|
+
index or additional authoritative per-process ticket registry is introduced.
|
|
146
|
+
|
|
147
|
+
## Override
|
|
148
|
+
|
|
149
|
+
`fifo()` prefers canonical oldest-ticket/ID order and returns quality 1.
|
|
150
|
+
`skillWindow()` grows the permitted skill spread using the oldest candidate's
|
|
151
|
+
waiting time, up to `maxWindow`; quality is `1 - spread / maxWindow` (or 1 for
|
|
152
|
+
zero spread/zero maximum). With `latencyMaxMs`, all candidates need the same
|
|
153
|
+
region and bounded latency evidence for that region. `regionLatency()` applies
|
|
154
|
+
that region rule and uses `1 - worstLatency / latencyMaxMs`, with quality 1 at
|
|
155
|
+
zero latency/zero maximum. These caller-supplied values are not verified network
|
|
156
|
+
measurements. Skill and latency providers remain author/server authority.
|
|
157
|
+
|
|
158
|
+
`composite([first, second])` applies policies to leftovers in order, sharing the
|
|
159
|
+
original search budget. Every custom result is checked for full team size,
|
|
160
|
+
whole original tickets, disjoint assignment, explicit complete leftovers,
|
|
161
|
+
configured minimum quality and exclusion pairs. Missing/forged/foreign tickets
|
|
162
|
+
refuse; no automatic fallback overwrites a failed slot. An `avoid` pair excludes
|
|
163
|
+
the two subjects from the entire match, including opposite teams and the same
|
|
164
|
+
party. Actual `SocialBlockPort` acquisition remains future work; this pure input
|
|
165
|
+
alone does not establish integration with Social or Sanctions.
|
|
166
|
+
|
|
167
|
+
## Extend or replace
|
|
168
|
+
|
|
169
|
+
`./port` exports the three-method `MatchmakingPort`; routes resolve that Tag
|
|
170
|
+
from their Effect context, so a replacement Layer uses the same RPC contract.
|
|
171
|
+
`./testing` exports `fakeMatchmakingService`/`fakeMatchmakingLayer`, with missing
|
|
172
|
+
methods failing closed. The default service clears on plugin deactivation and
|
|
173
|
+
one plugin instance refuses binding another app store.
|
|
174
|
+
|
|
175
|
+
`./server` exports `dataStoreMatchmakingTickets`, `TicketAdmissionOptions` and
|
|
176
|
+
the ticket/subject-admission/queue-admission table handles. This standalone
|
|
177
|
+
adapter borrows the supplied DataStore and exposes the same service plus
|
|
178
|
+
export/erase. Tables are `_voltro_mm_tickets`, `_voltro_mm_subject_admission`
|
|
179
|
+
and `_voltro_mm_queue_admission`. Export projects only the target’s ticket DTOs;
|
|
180
|
+
erase deletes those tickets and retains a redacted hashed subject fence with no
|
|
181
|
+
plain subject ID or active ticket pointer. Peers and queue budgets remain. The
|
|
182
|
+
fence prevents recreation after adapter reopening; this is not a compliance
|
|
183
|
+
claim. All privacy hooks refuse bounded incomplete work rather than silently
|
|
184
|
+
truncate.
|
|
185
|
+
|
|
186
|
+
## Panel and GM
|
|
187
|
+
|
|
188
|
+
The read-only process `/status` inspect endpoint reports captured
|
|
189
|
+
queue/admission budgets, required author slots and the default adapter binding.
|
|
190
|
+
It does not read SQL or enumerate tickets/players/attributes.
|
|
191
|
+
`directorInstalled:false` expressly means matching/allocation is unfinished. The
|
|
192
|
+
shared bilingual Matchmaking page is wired into both Local and Cloud dashboards,
|
|
193
|
+
their current instance/replica selection and the shared AppInspector feature
|
|
194
|
+
catalog. Cloud uses authorized app-scoped plugin-inventory validation; Local
|
|
195
|
+
uses the existing read credential transport. Unknown/malformed snapshots fail
|
|
196
|
+
instead of becoming empty data. Reload remains focusable while duplicate refresh
|
|
197
|
+
admission is guarded. No live queue-depth/match counters, GM writes or capacity
|
|
198
|
+
claim exists yet; these remain open alongside templates/manifests and
|
|
199
|
+
allocation-aware panel additions.
|
|
200
|
+
|
|
201
|
+
## Tests
|
|
202
|
+
|
|
203
|
+
`./testing` exports `matchmakingInputFixture` and `matchFunctionFixture`, using
|
|
204
|
+
actual pure policies. Adapter/plugin tests additionally author actual
|
|
205
|
+
transactional rollback, intent/identity capture, port replacement, caps and
|
|
206
|
+
erase. Six native fixtures (PostgreSQL, MySQL, MariaDB, MSSQL, SQLite, Turso)
|
|
207
|
+
each author SQL roundtrip, two-pool owner/queue races, rollback, successor
|
|
208
|
+
expiry and privacy; missing fixture credentials fail rather than become
|
|
209
|
+
acceptance skips. No native fixture has run.
|
|
210
|
+
|
|
211
|
+
## Trusted SQL Director planning
|
|
212
|
+
|
|
213
|
+
The installed `plugin.matchmaking.plan(subject, { tenantId, queue })` facade and
|
|
214
|
+
`dataStoreMatchmakingTickets(...).director.plan(...)` now implement the read/
|
|
215
|
+
planning stage over the same borrowed SQL ticket store. `./server` additionally
|
|
216
|
+
exports `dataStoreMatchmakingPlanner(store, { queues, avoidPairs, now })` for an
|
|
217
|
+
explicit server-owned composition. Queue/callback selection is captured with the
|
|
218
|
+
same single declaration-capture implementation used by admission. The stage
|
|
219
|
+
requires a trusted system Subject; human, anonymous and external service callers
|
|
220
|
+
refuse. No player planning route or new port authority is added.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { Effect, Schema } from 'effect'
|
|
224
|
+
import { systemSubject } from '@voltro/protocol'
|
|
225
|
+
import { matchmakingPlugin } from '@voltro/plugin-matchmaking'
|
|
226
|
+
import { defineQueue } from '@voltro/plugin-matchmaking/queue'
|
|
227
|
+
import { fifo } from '@voltro/plugin-matchmaking/strategies'
|
|
228
|
+
|
|
229
|
+
export const matchmaking = matchmakingPlugin({
|
|
230
|
+
queues: [defineQueue({ name: 'duels', room: 'arena', ticket: Schema.Struct({ skill: Schema.Number }),
|
|
231
|
+
teams: { count: 2, size: 1 }, matchFunction: fifo<{ readonly skill: number }>() })],
|
|
232
|
+
prepareTicket: () => Effect.succeed({ skill: 100 }),
|
|
233
|
+
admit: () => Effect.void,
|
|
234
|
+
})
|
|
235
|
+
// In trusted server code, after this same plugin has bound its SQL adapter:
|
|
236
|
+
const proposal = matchmaking.matchmaking.plan(systemSubject('queue-planning'), { tenantId: 'game', queue: 'duels' })
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
This thin example's skill/admission rules are intentionally author supplied;
|
|
240
|
+
production default Players/Sanctions bindings remain unfinished. Planning reads
|
|
241
|
+
only current tenant/queue open tickets with expiry strictly after the captured
|
|
242
|
+
clock. A complete scan over `maxTickets` refuses instead of returning a truncated
|
|
243
|
+
queue. It validates actual SQL roster/scope/time and detached attribute budgets,
|
|
244
|
+
then runs the real queue partition/match/output admission. It returns proposals,
|
|
245
|
+
complete leftovers, search-node count/exhaustion and observation scope/time.
|
|
246
|
+
These are read observations, not ticket claims, assignments or measured capacity.
|
|
247
|
+
Repeated reads can propose the same tickets. A later writer must atomically
|
|
248
|
+
recheck/claim all original tickets and admit an idempotent Room allocation.
|
|
249
|
+
No singleton workflow, debounce/timer, SQL claim/assignment, allocation/retry,
|
|
250
|
+
backfill or event settlement is activated by this facade.
|
|
251
|
+
|
|
252
|
+
`avoidPairs(context)` is a captured Effect slot with immutable scoped candidate
|
|
253
|
+
records, explicit `maxPairs` and the actual DataStore read query capability; it
|
|
254
|
+
exposes no mutators. Root, `./server` and browser-safe `./strategies` export the
|
|
255
|
+
single `defaultMatchmakingAvoidPairs`, whose empty policy is for an app without a
|
|
256
|
+
block provider. `./testing` supplies `matchmakingAvoidPairsFixture(query)` using
|
|
257
|
+
the actual supplied read capability. A custom provider's pairs pass the same
|
|
258
|
+
bounded pure matcher validation; its typed refusal retains the error code.
|
|
259
|
+
Automatic optional Social block binding/shadow-pool enforcement remains open;
|
|
260
|
+
empty defaults are not evidence that an installed Social plugin is integrated.
|
|
261
|
+
Both dashboards report actual default/custom provider selection while keeping
|
|
262
|
+
`directorInstalled: false`. No queue depth/player data is in inspection.
|
|
263
|
+
|
|
264
|
+
The plan contains owner/attribute data for trusted processing only; do not put
|
|
265
|
+
it in a workflow payload/result or expose it to clients. A durable Director
|
|
266
|
+
needs opaque persisted identities and privacy-aware settlement. Plugin
|
|
267
|
+
deactivation clears the owned facade; ordinary client routing still resolves
|
|
268
|
+
MatchmakingPort and keeps its existing replacement semantics.
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
## Production license admission
|
|
272
|
+
|
|
273
|
+
Declare the actual `licensingPlugin()` before this plugin in the API app. Production and staging activation require its verified active or grace deployment-wide license to grant `matchmaking`. Explicit development/test mode stays free without recurring per-instance notices; CLI owns the actual template first-boot receipt. Tenant-scoped licenses refuse until actual tenant-bound authority exists. This server-only gate preserves normal schema, store binding and cleanup; it runs before the original activation hook, not before schema/store binding.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
We take the security of Voltro and the applications built on it seriously. This
|
|
4
|
+
document explains how to report a vulnerability and what to expect in return.
|
|
5
|
+
|
|
6
|
+
## Reporting a vulnerability
|
|
7
|
+
|
|
8
|
+
**Please do not open public issues or discuss suspected vulnerabilities in
|
|
9
|
+
public channels.** Report them privately by email:
|
|
10
|
+
|
|
11
|
+
**security contact: [support@voltro.dev](mailto:support@voltro.dev)**
|
|
12
|
+
(subject line: `SECURITY`).
|
|
13
|
+
|
|
14
|
+
Include, where you can:
|
|
15
|
+
|
|
16
|
+
- the affected package(s) and version(s) (e.g. `@voltro/runtime@x.y.z`);
|
|
17
|
+
- a description of the issue and its impact;
|
|
18
|
+
- a minimal reproduction or proof of concept;
|
|
19
|
+
- any suggested remediation.
|
|
20
|
+
|
|
21
|
+
If you'd like to encrypt your report, ask in a first plain email and we'll
|
|
22
|
+
arrange a key.
|
|
23
|
+
|
|
24
|
+
## What to expect
|
|
25
|
+
|
|
26
|
+
- **Acknowledgement** of your report within **3 business days**.
|
|
27
|
+
- An initial assessment (severity, affected surface) and a remediation plan as
|
|
28
|
+
soon as we've reproduced the issue.
|
|
29
|
+
- **Coordinated disclosure:** we'll agree a disclosure timeline with you and
|
|
30
|
+
credit you in the release notes if you wish. Please give us reasonable time to
|
|
31
|
+
ship a fix before any public disclosure.
|
|
32
|
+
|
|
33
|
+
We do not currently run a paid bug-bounty program, but we genuinely appreciate
|
|
34
|
+
responsible disclosure and will credit reporters.
|
|
35
|
+
|
|
36
|
+
## Software Bill of Materials (SBOM)
|
|
37
|
+
|
|
38
|
+
A CycloneDX SBOM of the third-party runtime dependency graph is generated for the
|
|
39
|
+
published packages (`pnpm gen:sbom` → `sbom.cdx.json`). If your procurement or
|
|
40
|
+
security team needs it, request it at
|
|
41
|
+
[support@voltro.dev](mailto:support@voltro.dev).
|
|
42
|
+
|
|
43
|
+
## Supported versions
|
|
44
|
+
|
|
45
|
+
Voltro is pre-1.0 and ships in lockstep: **only the latest published minor of
|
|
46
|
+
each `@voltro/*` package receives security fixes.** Please upgrade to the latest
|
|
47
|
+
release before reporting, and pin exact versions in production.
|
|
48
|
+
|
|
49
|
+
## Scope
|
|
50
|
+
|
|
51
|
+
In scope: the published `@voltro/*` packages and the framework's own code.
|
|
52
|
+
|
|
53
|
+
Out of scope: vulnerabilities in third-party dependencies that are already
|
|
54
|
+
tracked upstream (we monitor these via a production-dependency audit gate in
|
|
55
|
+
CI), and issues that require a non-default, explicitly-unsafe configuration. If
|
|
56
|
+
you're unsure, report it anyway — we'd rather hear about it.
|