@voltro/plugin-inventory 0.0.0-stage → 0.87.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/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,442 @@
1
- # Temporary Holding Version
1
+ # @voltro/plugin-inventory
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ## Contracts and content
4
+
5
+ `defineItem` captures an item's stable ID, kind, schema version, stack/expiry/equipment rules, and Effect schemas. `itemContent` validates and detaches both static attributes and instance defaults; catalog JSON never contains a Schema object. The example values below illustrate a test item, not deployment balancing.
6
+
7
+ ```ts
8
+ import { Schema } from 'effect'
9
+ import { defineItem, inventoryPlugin, itemContent } from '@voltro/plugin-inventory'
10
+
11
+ const potion = defineItem({
12
+ id: 'potion.small', kind: 'consumable', schemaVersion: 1,
13
+ stackable: true, maxStack: 10, tradable: false,
14
+ rarity: 'common', tags: ['potion'],
15
+ attributes: Schema.Struct({ potency: Schema.Number }),
16
+ instanceAttributes: Schema.Struct({}),
17
+ display: { nameKey: 'items.potion.small.name' },
18
+ })
19
+ const inventory = inventoryPlugin({ items: [potion], channel: 'live', locale: 'en' })
20
+ const content = itemContent(potion, { potency: 1 }, {})
21
+ // Add inventory to the API app's plugins array.
22
+ // After normal store binding, a trusted server job imports [content]
23
+ // through inventory.inventory.importCatalog(subject, scopedImport).
24
+ ```
25
+
26
+ The `./items`, `./catalog`, `./strategies`, `./errors`, `./rpc` and `./web` entries are designed as browser-safe source. SQL lives in `./server`; `./cms` is server-only.
27
+
28
+ ## Catalog sources
29
+
30
+ `CatalogSource` is a read-only Effect port: `read({ tenantId, channel, locale })` returns one immutable `{ revision, items }` snapshot. The default `dataStoreCatalog` adapter borrows the app's DataStore. Imports need a system Subject or an explicitly allowed service, capture the complete input, serialize through a tenant fence and publish one channel/locale head in the same transaction as its snapshot. IDs and versioned gameplay bindings remain in SQL after removal. A kind cannot be reused under the same ID, and an accepted version cannot silently change its gameplay contract. A new revision may change validated content values. Old snapshots remain available in SQL; replay acknowledges the original import and never moves the active head backwards.
31
+
32
+ `cmsCatalogSource` in `./cms` reads only the `item` content type's **published** rows, explicitly filtered by tenant, site, environment and locale. Its `environments` option maps logical channels to existing CMS environments; it guesses no deployment names. Register `inventoryItemContentTables` and the normal CMS revision/reference tables, and use the existing CMS publish/release pipeline. `definitionJson` contains the serialized validated `CatalogItem`; `itemId` and `schemaVersion` must match it. The adapter refuses a malformed or oversized whole snapshot. Direct CMS reads currently validate against installed contracts; durable ID reuse admission, a typed catalog editor, release-channel promotion and CDC invalidation for replacement sources remain integration work. Use SQL imports for the written durable ID/version binding contract.
33
+
34
+ The proposed `voltro inventory import` command is not implemented. The actual server import method is `inventory.inventory.importCatalog(subject, { tenantId, channel, locale, revision, items })`. No background cache or backend fallback is installed. Catalog I/O belongs at join/workflow/request boundaries, never inside a room tick.
35
+
36
+ ## Trusted operations and player intentions
37
+
38
+ `inventory.inventory.service()` and the request `InventoryPort` expose the same implementation. `grant` and `consume` require an explicit owner, tenant, request ID, reason and reference. Only system/allowed-service Subjects can grant or consume. A grant validates instance attributes, fills compatible unlocked stacks and creates bounded overflow. `stackPolicy` output must conserve quantity and respect each cap; `expiryPolicy` resolves a grant-time deadline. Repeated accepted intentions return the original operation receipt; a changed intention under the same identity refuses. A pending admission is not accepted value.
39
+
40
+ Players and guests get `inventory.mine`, `inventory.loadout` and the app-configured `inventory.catalog`. The sole player mutation is `inventory.equip`: slot set, slot, instance ID or null, and request ID. The service derives tenant/owner from the verified Subject, checks possession, lock, deadline and the declared equipment slot, and never accepts quantities, attributes or client state. Declare `slotSets` to admit equipment. `useInventory`, `useLoadout` and `useCatalog` use the normal client subscription surface.
41
+
42
+ `expire` is a trusted operation with an explicit cutoff and request ID. Reads hide overdue instances without writing; the job removes them and stale loadouts with `expired` ledger rows. Locked overdue instances refuse the operation. Scheduling that job and trade escrow integration remain open.
43
+
44
+ The `inventory.equip` intention uses an action descriptor because the borrowed InventoryPort owns its complete SQL transaction. The dispatch spine adds no outer mutation transaction. The name and input remain the same; compiled dispatch and wire proof are pending.
45
+
46
+ ## SQL truth, rebuild and privacy
47
+
48
+ The five owned roles are subject fences, item instances, loadouts, movement ledger and operation receipts. Each accepted operation allocates a durable owner sequence, so equal timestamps cannot scramble replay. Movement rows preserve validated after-images. `auditInventory` and `rebuildInventory`, exposed on the installed SQL facade and standalone adapter, replay complete accepted receipts/movements to compare or repair possession materialization; they refuse broken sequence, receipt, quantity or attribute evidence. Repair writes no new movement and never grants extra value. Loadouts remain separate SQL truth; the possession rebuild removes only dangling references and does not reconstruct equipment history.
49
+
50
+ Default whole-work limits are 4,096 instances, 10,000 ledger/request rows, 10,000 catalog items and 10,000 persisted catalog versions. Limits are configurable within admitted ranges and overflow refuses; they are **not capacity measurements**. One attribute object is bounded to 4,096 nodes, depth 16 and a conservative 64-KiB string budget. Canonical hashing of whole work additionally refuses over 3,000,000 nodes, depth 32 or 64 MiB. No nightly evidence or production capacity follows from these budgets.
51
+
52
+ The privacy declaration covers every table registered by the plugin, including shared catalog roles. Subject hooks retain shared tenant catalog content used by other players. User/guest export is tenant-scoped and contains only the subject's possessions, loadouts and movements. Erase fences later grants, removes possessions/loadouts, tombstones all owner-keyed request bodies including zero-change requests, and redacts retained ledger owner/instance/snapshot/reference/actor fields. Retained deterministic keys are pseudonymous. Guild-member privacy, arbitrary actor/reference erasure, account merge and a legal retention decision remain open.
53
+
54
+ ## Extension and inspect surfaces
55
+
56
+ Exported `defaultStackPolicy` and `defaultExpiryPolicy` are the actual defaults. `./testing` supplies fixtures that call the actual item/content/strategy implementations. `catalogSource` and `service` replace the source and eight-method InventoryPort implementation; the replacement must retain authority, receipts and export/erase behavior. Full replacement-table privacy wiring, events, mixins, `craftResolver` and `systemContractConformance` remain open.
57
+
58
+ The `/status` inspect endpoint and shared/local/cloud panels show process binding, installed source/stack/expiry slots, item contracts, slot sets and configured bounds. They contain no player inventory or ledger data. Search and page controls bound contract rendering. The four layers are written; their builds, transport and browser evidence are pending. Player lookup, ledger/reference search, channel diff and authorized grant/revoke/reset actions are not yet built.
59
+
60
+ ## Optional immediate item crafting
61
+
62
+ `crafting` installs the four-method `CraftingPort` (`craft`, `recipes`, `exportSubject`, `eraseSubject`) alongside the existing eight-method InventoryPort. With the option absent, no recipe role or Crafting Layer is registered. Craft is trusted system/explicit-service only; the player API retains read/equip intentions. The following quantities are illustrative app recipe rules, not pricing or measured throughput. `potion` is the app's admitted item contract.
63
+
64
+ ```ts
65
+ import { inventoryPlugin, defineRecipe } from '@voltro/plugin-inventory'
66
+ import { systemSubject } from '@voltro/protocol'
67
+
68
+ const distill = defineRecipe({
69
+ id: 'distill', version: 1,
70
+ inputs: [{ itemId: potion.id, qty: 2 }],
71
+ outputs: [{ itemId: potion.id, qty: 1 }],
72
+ maxBatches: 10,
73
+ })
74
+ const inventory = inventoryPlugin({
75
+ items: [potion], crafting: { recipes: [distill] },
76
+ })
77
+ // Invoke from trusted server code after normal app/store/catalog binding:
78
+ const receipt = inventory.inventory.crafting().craft(systemSubject('inventory.craft'), {
79
+ tenantId, owner: { ownerKind: 'player', ownerId },
80
+ recipeId: distill.id, batches: 1, requestId,
81
+ })
82
+ ```
83
+
84
+ `defineRecipe` captures immutable ID/version, unique item inputs/outputs, optional JSON output attributes and a bounded batch count. The adapter is immediate: currency `cost` requires the exact-store prepared participant described below; positive `timeMs` or nonnull level/quest `requires` refuse admission. No charge, waiting timer or eligibility result is silently skipped. Each declared amount times the configured batch budget must fit the exact item quantity limit. Timed and prerequisite crafting still need actual Progression/workflow integration.
85
+
86
+ `defaultCraftResolver` selects oldest eligible acquisitions, with deterministic ID ties. Equipped, locked and expired instances are excluded. Custom `craftResolver` receives detached frozen actual inventory observations and can select eligible instances or schema-valid output attributes; it cannot change declared input/output totals or add a gift. Custom craft/stack/expiry policies require an explicit nondefault `crafting.version`. Defaults and `craftFixture` are exported through `./crafting` and `./testing`. The existing Item contracts validate produced attributes and stack capacity; previous contract versions must remain while possessions reference them.
87
+
88
+ The actual SQL adapter captures catalog content, craft selection, stack/expiry results and complete materialization basis outside replayable SQL callbacks. Immutable tenant/version recipe declarations live in `_voltro_inv_recipes`; changed declarations/rule versions refuse. Under the existing permanent Inventory owner fence, a changed sequence/item/loadout basis refuses the prepared plan. Accepted consumption, output creation/stack updates, append-only movements, operation receipt and captured craft intent commit together. A rejected plan cannot partially consume items. A retry of the same accepted request returns before catalog/clock/custom callbacks; changed intent under the same request conflicts. Distinct requests may craft independently. There is no room tick I/O or another state store.
89
+
90
+ `auditInventory`/`rebuildInventory` validates recipe identity, captured request/digest, accepted input selection, exact output conservation and every movement/after-image. It uses accepted SQL truth without recalling historical craft/catalog/expiry/stack callbacks. Retained recipe declarations are shared configuration. Export includes owned craft intention/selection and the complete Inventory movement scope; erase clears owner/request/craft/receipt/link bodies, removes possessions/loadouts and preserves permanent pseudonymous fences. A delayed craft cannot revive an erased owner. Guild-owner lifecycle integration remains part of the outstanding complete Inventory contract.
91
+
92
+ `crafting.service` replaces all four methods. A replaced InventoryPort requires a full Crafting service replacement; default SQL craft does not borrow custom storage. Replacement methods preserve their receiver and the plugin forwards complete export/erase. App-owned replacement tables still need explicit schema/CDC/privacy registration. The configuration-only inspect endpoint, Shared EN/DE semantic panel and ordinary Local/Cloud providers show actual optional backend/resolver/recipe budgets and report configured game-currency costs; duration workflow, prerequisites, player craft and trade/escrow remain absent. Captured configuration is not execution evidence.
93
+
94
+ A six-dialect native case uses actual schema, independent borrowed pools, same-request concurrency, terminal replay, rebuild and erase. Factory/privacy, strict host decoder/provider and bilingual DOM regressions are written.
95
+
96
+ ## Prepared SQL inventory units
97
+
98
+ `InventorySqlService.prepareGrant` and `prepareCraft` on the explicit
99
+ `dataStoreInventory` server adapter prepare trusted intentions for
100
+ `executeSqlMutations` from `@voltro/database/sql`. The units are bound to the exact
101
+ borrowed store; InventoryPort and CraftingPort retain their ordinary methods.
102
+ Grant/catalog/attribute/expiry and custom stack planning occur before acceptance.
103
+ Default stack arithmetic runs under the owner fence; captured custom stock or
104
+ craft basis changes refuse. Ordinary writes use the same single movement writer.
105
+
106
+ Compose a debit and grant/craft outside an active transaction: a later failure
107
+ rolls back all accepted movements, stock, wallet values and receipts together.
108
+ Pending admissions are not accepted value. Recreate units from original SQL
109
+ identities, never serialized capabilities. Terminal replay precedes clock,
110
+ catalog and custom callbacks. Currency-cost recipes use the actual atomic
111
+ participant described below; optional Shop binding is explicit in Economy, and
112
+ does not borrow SQL for replaced ports. See the Transactions page in the
113
+ bilingual public docs.
114
+
115
+
116
+ The installed default SQL facade also exposes `inventory.inventory.prepareGrant`
117
+ / `prepareCraft`. These use the actual bound adapter, capture before laziness
118
+ and revoke on deactivation. Replaced Inventory or crafting refuses SQL
119
+ participation; no fallback. For Shop configuration pass `inventory.inventory` as
120
+ the explicit `shop.inventory` participant. Independent exact-store admission
121
+ protects against cross-app units.
122
+
123
+
124
+ ## Atomic game-currency crafting
125
+
126
+ Immediate recipes may now declare `cost: { currency, amount }`, with a positive
127
+ canonical decimal-string amount per batch. Full configured batch multiplication
128
+ must fit signed SQL BIGINT. Free recipes use `cost:null`. Currency-cost recipes
129
+ require an explicit exact-store `crafting.economy.prepareTransfer` participant
130
+ unless the entire Crafting service is replaced. Missing participants refuse
131
+ configuration; costs are never skipped. Positive duration or level/quest
132
+ prerequisites still refuse. Amounts below are illustrative game rules, not Owner
133
+ prices or measured budgets.
134
+
135
+ ```ts
136
+ import { economyPlugin } from '@voltro/plugin-economy'
137
+ import { defineItem, defineRecipe, inventoryPlugin } from '@voltro/plugin-inventory'
138
+ import { Schema } from 'effect'
139
+
140
+ const potion = defineItem({ id: 'potion', kind: 'consumable', stackable: true,
141
+ maxStack: 10, tradable: false, rarity: 'common', tags: [],
142
+ attributes: Schema.Struct({}), instanceAttributes: Schema.Struct({}),
143
+ display: { nameKey: 'items.potion' } })
144
+ export const economy = economyPlugin({
145
+ currencies: [{ id: 'gold', kind: 'soft', cap: '1000' }], realMoney: false,
146
+ })
147
+ export const inventory = inventoryPlugin({ items: [potion], crafting: {
148
+ economy: economy.economy,
149
+ recipes: [defineRecipe({ id: 'distill', inputs: [{ itemId: potion.id, qty: 2 }],
150
+ outputs: [{ itemId: potion.id, qty: 1 }],
151
+ cost: { currency: 'gold', amount: '7' }, maxBatches: 10 })],
152
+ } })
153
+ // Add both instances to one API app; publish actual matching item content.
154
+ // Only trusted server code invokes inventory.inventory.crafting().craft.
155
+ ```
156
+
157
+ The installed default `economy.economy.prepareTransfer` facade borrows its actual
158
+ SQL writer, captures inputs before laziness and refuses before binding, after
159
+ deactivation or with a complete wallet replacement. It supplies no default SQL
160
+ fallback for custom wallets. An explicit `EconomySqlService` also satisfies the
161
+ structural participant. Inventory imports no Economy implementation at runtime.
162
+ Full Crafting replacement owns its payment/privacy behavior; it cannot combine
163
+ with a default currency participant or system-account option.
164
+
165
+ The Inventory writer prepares the actual movement and wallet units outside SQL
166
+ callbacks. One exact-store group accepts the wallet debit, item consumption,
167
+ outputs, balanced financial postings, inventory movements and both actual
168
+ receipts together. The sink defaults to `crafting` and can be named through
169
+ `crafting.systemAccount`. The payment request derives from tenant/owner/original
170
+ craft request, independent of mutable content. Ordinary and prepared craft use
171
+ the same group and original writers, without nested service transactions. A
172
+ foreign physical store is refused. Insufficient funds or a later movement/basis
173
+ conflict rolls back accepted debit and outputs; pending admission rows are not
174
+ paid or crafted value. Retrying uses the original request identity.
175
+
176
+ Accepted craft SQL history retains the captured recipe/cost and actual payment
177
+ receipt. Terminal replay validates it before current clock/catalog/resolver or
178
+ payment preparation. Inventory audit/rebuild admits exact paid amount and unique
179
+ payment identity while reconstructing items without another debit or historical
180
+ callback. This is item history validation; Economy owns its complete financial
181
+ ledger audit/rebuild. Erasing Inventory clears owned paid craft bodies; Economy
182
+ must run its own privacy hooks to clear the wallet links. Shared recipe rules
183
+ remain, and permanent owner fences refuse recreation. Receipt times describe
184
+ captured admissions; no measured execution-duration claim follows.
185
+
186
+ All four configuration inspect layers now include exact declared per-batch costs
187
+ and whether currency fulfillment is configured. They expose no balance, payer,
188
+ financial receipt or outcome. Time/prerequisite/player-craft/trade features
189
+ remain absent.
190
+
191
+ ## Player inventory components
192
+
193
+ The separate browser `./components` entry contains `InventoryGrid`, `ItemCard`,
194
+ `LoadoutEditor` and `CraftingPanel`; React is required only for this entry/web
195
+ hooks. Import the existing UI kit theme and include this package's source
196
+ classes in the app's Tailwind sources.
197
+
198
+ `InventoryGrid` accepts own instance/catalog observations and a caller-owned
199
+ `at` timestamp. One detached pure admission joins content by item ID/schema
200
+ version and refuses duplicate/malformed/getter-backed data. Removed catalog
201
+ content retains visible possession IDs and quantities; unavailable metadata never
202
+ permits equipment selection. Search and tags filter the actual content. Fixed
203
+ rows/overscan keep DOM bounded; focused rows remain mounted, and Show all items
204
+ provides complete traversal. Counts describe the current filtered data, not a
205
+ measured capacity. A failed observation hides stale choices. Dates update only
206
+ when the app supplies a new timestamp; no background polling or tick I/O.
207
+
208
+ `LoadoutEditor` uses the live accepted loadout, declared slot set/slots and actual
209
+ own catalog/items. It emits only `{ slotSet, slot, instanceId, requestId }`, with
210
+ an app-owned identity and no owner/quantity/attribute data. The native select
211
+ stays controlled by the accepted server observation; no optimistic item or
212
+ loadout patch. Locked/expired/unknown or wrong-slot items cannot be selected.
213
+ `useInventoryEquip` calls the existing action; failures belong to the parent's
214
+ error handling. Subscription freshness/account-boundary ownership remains the
215
+ application's responsibility.
216
+
217
+ ```tsx
218
+ import { InventoryGrid, LoadoutEditor } from '@voltro/plugin-inventory/components'
219
+ import { useInventory, useCatalog, useLoadout, useInventoryEquip } from '@voltro/plugin-inventory/web'
220
+
221
+ export const Equipment = (props: { at: number; title: (key: string) => string; onError: (error: unknown) => void }) => {
222
+ const source = useInventory(), catalog = useCatalog(), loadout = useLoadout('arena'), equip = useInventoryEquip()
223
+ return <>
224
+ <InventoryGrid source={source} catalog={catalog} at={props.at} title={props.title} />
225
+ <LoadoutEditor source={source} catalog={catalog} loadout={loadout}
226
+ slotSet="arena" slots={['head']} at={props.at} title={props.title}
227
+ busy={equip.pending} requestId={() => crypto.randomUUID()}
228
+ onEquip={input => { void equip.run(input, { onError: props.onError }) }} />
229
+ </>
230
+ }
231
+ ```
232
+
233
+ `ItemCard` inherits the UI kit and accepts title/image slots, without inventing
234
+ item art or resolving storage URLs. EN/DE copy and a complete copy override are
235
+ available. `CraftingPanel` is read-only by default: local batch selection previews
236
+ conserved ingredients through the actual exported craft resolver, excludes
237
+ locked/expired/equipped material, and displays exact decimal currency costs.
238
+ Materials available does not mean wallet affordability or reservation. An app
239
+ may provide `renderAction` for its own authorized intention boundary; the package
240
+ adds no player Craft/Grant RPC. Recipe costs/duration/prerequisites obey the
241
+ actual partial server contract. Native SQL ownership remains on the server.
242
+
243
+ DOM seams do not prove native layout, focus/accessibility, cross-account
244
+ subscriptions, browser transport or actual large-list performance. Full
245
+ player-template/Rooms integration remains open.
246
+
247
+ ## System contract port and strategy fixtures
248
+
249
+ `./testing` now exports `inventoryPortFixture(options)`, `inventoryFakeLayer`,
250
+ `craftingFakeLayer`, `inventoryPortSystemFixture`, `craftingPortSystemFixture`
251
+ and the distinguishing `stackPolicySystemFixture`/`expiryPolicySystemFixture`/
252
+ `craftResolverSystemFixture`. Their defaultImplementation fields refer to the
253
+ actual exported defaults. Probes call the real admission/resolver functions; no
254
+ copied stack/expiry/craft rule. They match the existing testing system suite's
255
+ structural fixture contract, not a completed conformance run.
256
+
257
+ The explicit test-only Layer acquires the real installed Inventory service and
258
+ free Crafting on one new private InMemoryDataStore per scope, publishes the supplied
259
+ actual content and provides the ordinary Tags together. Release is registered
260
+ before binding/content preparation, and revokes the installed facade after scope
261
+ exit, including failed setup. Item contracts/content/options are captured before
262
+ Layer acquisition. No paid recipe is accepted by this fixture; actual paid native
263
+ fixtures live with the real cross-system Economy participant. No independent
264
+ wallet, fake SQL result, runtime backend or production isolation proof.
265
+
266
+ `./testing` is a server/testing entry because these Layers load the actual
267
+ adapters; never import it from a browser descriptor. Existing declaration
268
+ fixture exports remain the same public names. Native SQL/durability/normal boot
269
+ are not established by private memory stores. Full per-table privacy,
270
+ events/hooks, inspect, table mixins/removal/upgrade and
271
+ `systemContractConformance` registration/execution remain open; exported fixture
272
+ presence is not a Plan21 pass.
273
+
274
+ ## Actual installed Catalog fixture
275
+
276
+ The test-only `inventoryPortFixture` now builds the plugin's actual service Layer, including `CatalogSource`. Without recipes it exposes only Inventory/Catalog; an explicit empty recipe list refuses, and nonempty free recipes also expose Crafting. No absent Crafting service is synthesized. `catalogFakeLayer` and `catalogSourceSystemFixture` exercise the actual published scoped catalog. Catalog and mutation facades refuse after release.
277
+
278
+ ## Trusted Inventory reservation participant
279
+
280
+ The default server adapter and installed facade now expose `prepareReserve`,
281
+ `prepareRelease` and read-only `reservation`. `InventorySqlService` types and
282
+ `InventoryReserveInput`, `InventoryReleaseInput`, `InventoryReservationView` are
283
+ exported from `@voltro/plugin-inventory/server`; the public eight-method
284
+ InventoryPort and sole player equip mutation remain the same. The facade refuses
285
+ borrowing default SQL from a replaced Inventory service and refuses after normal
286
+ deactivation. It captures input descriptors without invoking getters.
287
+
288
+ A trusted system/allowed-service operation supplies tenant, actual owner,
289
+ permanent reservation ID and unique instance quantities. The existing owner
290
+ fence and movement writer validate actual inventory, catalog version/tradability,
291
+ quantity, deadline and absence of another lock or loadout. Catalog/clock reads
292
+ happen before replayable SQL. Every captured actual before-image is compared
293
+ again under the transaction fence. Partial quantities conservatively lock the
294
+ whole stack; the accepted reservation records the exact proposed quantity.
295
+ Consume, equip, expiry and crafting cannot use that locked stack. No quantities,
296
+ attributes, instance identity, acquisition or original expiry are regenerated.
297
+
298
+ Canonical reserve/release operation identities derive from tenant/owner and
299
+ reservation ID, independently of a caller nonce. The nullable server-only
300
+ `reservation` field in the existing operation role holds admitted terms,
301
+ versioned catalog content and before-images. Lock changes, zero-quantity movement
302
+ snapshots, sequence and accepted receipt commit together through the actual
303
+ `PreparedSqlMutation`/`executeSqlMutations` path. A group failure rolls back all
304
+ accepted locks, snapshots, counts and receipts; pending rows can remain.
305
+
306
+ Release requires this exact accepted reservation and actual unchanged locked
307
+ instances. It unlocks the entire reserved stack even after the original expiry,
308
+ so the normal expiry job can remove it. Reserve/release retries return originals
309
+ before current clock/catalog; changed reserve terms under the same ID conflict.
310
+ Replaying a released reservation's original receipt never locks the stack again.
311
+ `reservation` reports accepted SQL custody snapshots (`not-observed`, `reserved`,
312
+ `released`), not a fresh availability or balance claim. It performs only reads,
313
+ without seeding, clock/catalog callbacks or materialization repair.
314
+
315
+ Audit/rebuild validates the complete existing owner sequence, bound intention,
316
+ exact canonical operation/digest, versioned tradable content, original before-
317
+ images, zero deltas, lock transitions, matching release and movement references.
318
+ Missing/mismatched custody refuses; repair uses accepted history without recalling
319
+ historical callbacks. Export includes owned custody; erase clears reservation
320
+ bodies, accepted snapshots and references alongside the existing owner privacy
321
+ and keeps the permanent erased fence. Other owners and shared catalog remain.
322
+ Guild lifecycle and arbitrary actor/reference erasure remain separate open scope.
323
+
324
+ They cover original retries, grouped rollback, expired release, rebuild,
325
+ privacy, accessor refusal, replacement borrowing and revocation. This
326
+ participant does not install player trading: the four existing inspect layers
327
+ keep `tradingInstalled:false`. Trade integration of the instance-preserving
328
+ owner transfer/splitting participant below, Trade binding of the Wallet Escrow
329
+ participant, Trade service/routes, expiry scheduling, replacement
330
+ participant/conformance, cross-system hooks and templates remain open.
331
+
332
+ ## Original instance and partial reservation transfer
333
+
334
+ `InventorySqlService.prepareReservationTransfer` and
335
+ `inventory.inventory.prepareReservationTransfer` take trusted tenant, source
336
+ owner, recipient owner and the existing permanent reservation ID. Types
337
+ `InventoryReservationTransferInput` and `InventoryReservationTransferReceipt`
338
+ are exported from `./server`. No new player write or InventoryPort method is
339
+ introduced. The default-only facade captures the owners before laziness, refuses
340
+ input getters/unknown state, replacement SQL borrowing and deactivated access.
341
+
342
+ The actual owning writer derives quantities/content from the accepted reservation,
343
+ never caller values. A whole reserved instance moves with its original ID. A
344
+ partial transfer gives the recipient a deterministic split ID, leaves the source
345
+ remainder under the original ID and unlocks it. Both retain exactly the existing
346
+ schema, attributes, acquisition time, original expiry and provenance. No new
347
+ grant, stack-policy call, catalog/expiry callback or asset creation authority is
348
+ used. Expired assets refuse transfer; the matching release remains available.
349
+
350
+ The prepared exact-store group fences both owners in deterministic key order,
351
+ executes outgoing and incoming operations through the same movement writer and
352
+ checks their actual reciprocal receipts before commit. Source delta plus target
353
+ delta is zero per item. Recipient capacity, unchanged actual reserved stock,
354
+ loadouts, quantity, deadline and canonical operation/target/split identity remain
355
+ mandatory. Failure rolls back both possessions, locks, sequences, movements and
356
+ accepted receipts; pending admissions may remain. Changed recipients conflict
357
+ with the source's permanent transfer identity. Accepted retries use original
358
+ reservation and both receipts before current clock/catalog, without recrediting.
359
+ An erased participant refuses replay; no re-creation bypass is provided.
360
+
361
+ The existing operation role adds nullable server-only `transfer` custody. Each
362
+ owner's admitted intention contains only that owner's raw ID and the opaque
363
+ counterpart fence/operation keys, plus original asset snapshots. These keys are
364
+ pseudonymous, not anonymization. The shared owner movement/receipt sequence now
365
+ recognizes `transfer-out` and `transfer-in`. Audit/rebuild validates original
366
+ reservation/lock transition, exact before/after snapshots and both immutable
367
+ movement IDs/deltas. Incoming history preserves original acquisition rather than
368
+ reassigning it to the transfer time. Missing peer rows, movements, mismatched
369
+ quantities or snapshots refuse. Owner erasure clears its own transfer bodies,
370
+ instance/reference/actor fields and after-images; retained counterpart operation
371
+ sequence and item deltas prove conservation for the surviving owner's rebuild.
372
+ An erased owner's materialization cannot be recreated. Arbitrary actor/reference,
373
+ app-specific attribute privacy, guild lifecycle and legal retention remain open.
374
+
375
+ Read-only `reservation` adds `transferred`, with an empty current-custody list,
376
+ when both accepted journal sides establish transfer. It never reports the
377
+ original locked snapshot as freshly available after transfer. It still performs
378
+ only reads. The four inspect layers retain `tradingInstalled:false`: Trade
379
+ binding of the Wallet Escrow participant, Trade service and player accept/cancel
380
+ routes, sanctions/source integration, replacement transfer participant, hooks,
381
+ expiry jobs and templates remain open.
382
+
383
+ ## Atomic fresh reservation and transfer
384
+
385
+ `InventorySqlService.prepareReserveTransfer` and the default-only lifecycle
386
+ facade capture `InventoryReserveTransferInput` (tenant/from/to/reservation/items).
387
+ `InventoryReservedTransferReceipt` contains actual `reserve` and `transfer`
388
+ receipts. They use the same reservation/paired-transfer/movement writers, one
389
+ exact-store SQL group and two deterministic owner fences. Fresh counterparty
390
+ items need no earlier accepted reservation transaction. Whole original IDs or
391
+ exact split quantities preserve attributes, acquisition, provenance and original
392
+ expiry. Catalog/before-images are captured outside SQL and rechecked under fences.
393
+ A changed/raced basis refuses; no callback or new catalog source is called in
394
+ replayable SQL. Originals precede current clock/catalog; no renewed expiry.
395
+
396
+ Failure rolls back fresh locks, both movement sides, sequences and accepted
397
+ custody/receipts together. Pending admissions can remain. Existing cap, capacity,
398
+ expiry, ownership and privacy rules apply; no final-position capacity netting is
399
+ inferred. Inputs are trusted system/allowed-service only, descriptor-captured and
400
+ revocable; no InventoryPort/player method is added. Missing accepted custody is
401
+ refused, not repaired. The higher-level Trade writer must bind immutable terms
402
+ and actual sanctions/admission to the same group.
403
+
404
+ No new table or installed Trade service; `tradingInstalled:false` remains
405
+ accurate until full durable Trade binding/routes/panels are installed.
406
+
407
+ ## Read-only original reservation evidence
408
+
409
+ Trusted `InventorySqlService.reservationEvidence` and the default-only revocable
410
+ facade return `InventoryReservationEvidence` through the same actual
411
+ reservation/ peer validation as `reservation`. It includes original selected
412
+ items, actual reserve/release and transfer-from/to receipts. Unlike current
413
+ availability, its `items` remain original custody snapshots after
414
+ release/transfer. They are not spendable stock. A redacted peer receipt is null;
415
+ retained peer conservation is still checked. No synthetic receipt is recreated.
416
+
417
+ Observation invokes no seed, current clock/catalog, repair or writer. Existing
418
+ availability `reservation` shape stays unchanged and shows empty items after
419
+ close. Native/root Trade source scenarios reuse this real read.
420
+
421
+ ### Prepared consumption for cross-system financial groups
422
+
423
+ The server SQL adapter exposes `InventorySqlService.prepareConsume(subject,
424
+ input)` alongside `prepareGrant`. It captures the same trusted consume intention
425
+ and returns an actual-store-bound participant in the existing movement writer.
426
+ Ordinary `consume` executes this same writer. The ordinary eight-method
427
+ `InventoryPort` and player equip intention stay unchanged. A group failure rolls
428
+ back accepted consumption and movement receipts; pending admissions may remain.
429
+ The Economy IAP asset adapter uses this participant for explicit item-refund
430
+ disposition.
431
+
432
+
433
+ ## Production license admission
434
+
435
+ 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 `inventory`. 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.
436
+
437
+ ## Responsive filters
438
+
439
+ InventoryGrid stacks its labeled search, tag and traversal controls on narrow
440
+ screens and uses a flexible three-column row from the small-screen breakpoint.
441
+ The minimum control height stays 44 px; filtering and server-controlled inventory
442
+ behavior are unchanged.
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.