Skip to content

Creator surface: the reads, runtime items, use handlers, a judged bar, benches, eye rows - #89

Merged
dop42 merged 6 commits into
mainfrom
feat/api-power
Oct 4, 2026
Merged

dop42 merged 6 commits into
mainfrom
feat/api-power

Conversation

@dop42

@dop42 dop42 commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Owner's ask: make sure the APIs give creators their full power, and add what is logically missing. This compares the creator surface with ESX / QBCore / ox_core / ox_inventory / ox_lib / ox_target and fills the gaps that can be filled safely.

Server exports (all behind the READ / WRITERS allowlists, writes audited event=export.<Name>)

  • GetPlayers(filter?), GetJobs(), GetGangs(): the roster and the configured groups (grades as a list)
  • GetItem, GetItems, GetInventory(target), CanCarryItem(target, item, count?, meta?)
  • RegisterItem(name, def): an item added on both halves at runtime. Validated by the one Catalog.Register both halves run, sent to late joiners on hello, never a weapon, never a taken name, capped per caller (SERVER.EXPORTS.ITEMS.MAX_PER_CALLER), sent eight per event so the client takes them inside one resume
  • RegisterUsableItem(name, export) / UnregisterUsableItem: the caller's export decides a use ({ ok, consume? }), never taken from another owner
  • OpenStash(src, name, options?): configured stashes or the caller's own namespace only, under the create cap (rule also enforced in the contract)
  • GetVehicle, GetOwnedVehicles, AddVehicle, HasKeys, GiveKeys
  • Notify(src, msg, kind?, ms?)
  • StartProgress(src, spec) / StopProgress(src, id?): a bar the server starts and judges by its own clock (modules/progress/server/main.lua): finished early is rejected + security journal, no report is no_answer, leaving is left; verdict on opx:on:progress:finished
  • RegisterCraftingBench(key, def) / UnregisterCraftingBench(key): stored as <caller>:<key>, gate as a job list
  • GetMetadata / SetMetadata take a citizen id: one row key (JSON_SET / JSON_REMOVE on a quoted path) under the offline ledger, bounds checked at the write
  • a caller that stops takes its use handlers, benches and server bars with it

Client exports

  • AddTarget / UpdateTarget / RemoveTarget / ClearTargets: rows owned by the caller's resource name, callbacks held to its own exports, swept on stop/reload by the eye
  • ShowPrompt / UpdatePrompt / HidePrompt / HideAllPrompts
  • OpenPanel / UpdatePanel / AppendPanel / ClosePanel (opx:on:panel:action)
  • OpenCraftingBench(key); reads GetPlayerData, GetItemCount, HasItem, GetItem, IsDown, GetNeeds
  • a caller named like a module is refused the eye and the strip (export.ownerTaken): it would share that module's rows

Public events

opx:on:inventory:items (bag deltas by item name, from a per-bag tally), opx:on:character:created / deleted, opx:on:vehicles:registered / state (impound), opx:on:crafting:ordered / collected, opx:on:progress:finished.

Skipped, and why

  • Hooks from another resource: a veto runs inside the action, synchronously; a cross-resource call there cannot be bounded
  • Impound / garage screen for a player, job-gated garages: need UI and garage-module work
  • Shops / gunsmith contracts: shops is the clothing shop; gunsmith is a crafting consumer, covered by creator benches
  • Runtime weapons: need a TweakDB record and a class
  • A server↔client callback helper: already Lib.Callback in opx_lib over Open77.net

Checks

  • OPX_LIB_PATH=../opx_lib lua tests/run.lua: 4546 checks, 0 failed (≈170 new: allowlist refusal, validation, audit, events firing, caller-stop cleanup, client budget for target rows and runtime-item parts)
  • luac -p on every changed file, manifest paths ≤ 47, python tools/check-sandbox-globals.py ok; no UI change
  • Merged origin/main

Docs: opx77-framework/opx77_doc#10

🤖 Generated with Claude Code

dop42 and others added 6 commits October 5, 2026 00:36
…, benches, eye rows

What a separate resource could not do and every framework it comes from can:

Server exports
- GetPlayers(filter?), GetJobs(), GetGangs(): the roster and the configured groups
- GetItem, GetItems, GetInventory, CanCarryItem: the catalogue and a whole bag
- RegisterItem(name, def): an item added on both halves at runtime, validated by
  the one Catalog.Register both halves run, sent to late joiners on hello; never a
  weapon, never a taken name, capped per caller (SERVER.EXPORTS.ITEMS)
- RegisterUsableItem / UnregisterUsableItem: the caller's export decides a use
- OpenStash: configured or the caller's own namespace only, under the create cap
- GetVehicle, GetOwnedVehicles, AddVehicle, HasKeys, GiveKeys
- Notify; StartProgress / StopProgress: a bar the server starts and judges by
  its own clock (modules/progress/server/main.lua), rejected when finished early
- RegisterCraftingBench / UnregisterCraftingBench, with a job-list gate
- GetMetadata / SetMetadata take a citizen id: one row key under the ledger
- everything a caller handed over goes when it stops

Client exports
- AddTarget / UpdateTarget / RemoveTarget / ClearTargets: eye rows owned by the
  caller, callbacks held to its own exports
- ShowPrompt / UpdatePrompt / HidePrompt / HideAllPrompts
- OpenPanel / UpdatePanel / AppendPanel / ClosePanel (opx:on:panel:action)
- OpenCraftingBench; GetPlayerData, GetItemCount, HasItem, GetItem, IsDown, GetNeeds
- a caller named like a module is refused the eye and the strip

Public events
- opx:on:inventory:items (what a bag gained and lost, by item name)
- opx:on:character:created / deleted, opx:on:vehicles:registered / state,
  opx:on:crafting:ordered / collected, opx:on:progress:finished

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every client re-validates a runtime item with Catalog.Register inside the
resume that received the `opx:net:inventory:catalog` event, and the platform
kills a resume that spends more than ~10,000 instructions. Two things let a
valid RegisterItem call overrun it, silently leaving the item on the server
and missing on that client (and on every late joiner, whose hello resends the
same parts):

- `use.status` was unbounded, and is walked twice per item on the client; a
  part of items with a few hundred needs measured 82,000 instructions. It is
  now held to 8 needs, refused `bad_definition:use.status` past that.
- eight items per event was too many even inside the bounds: eight items at
  every bound the validator allows measured 12,600. The server now sends four
  per event and the client refuses a longer part whole (it was 16).

Tests measure a refused heavy part and a full part at the bounds with the
exact callCost helper, and check a late joiner is sent every item in parts
of at most four.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stash's size is written into its row when it is created and read back
from the row for ever after. The new OpenStash export admits a configured
stash (config/inventory STASHES) for any writer, but OpenStash sized it from
the caller's options or the 50-slot / 100 kg default -- so a creator opening
`ncpd_lockers` before anybody walked to its marker created it at its own
size and replaced the operator's for good. `withStash` (AddToStash) and the
stash marker already use the configured size; OpenStash now does too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new cleanup on `onResourceStop` drops every use handler, crafting bench
and server bar a caller registered, by name. Another resource's stop reaches
this VM queued, on a later tick, and the devkit says outright that a
stop/start can occur before then (server-api, "Resource lifecycle events":
check GetResourceState). On `restart my_shop` the new instance can register
its handlers and benches before the old instance's stop is drained here, and
the late cleanup then wiped the new registrations: every use of its items
refused, its benches gone, until the next restart. The server bus is also
host-wide, so any resource could raise `onResourceStop` naming a running
caller and wipe it the same way.

The handler now leaves a caller GetResourceState reports running or
starting; its own re-registrations replace the old ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dop42
dop42 merged commit f361c1b into main Oct 4, 2026
1 check passed
@dop42
dop42 deleted the feat/api-power branch October 4, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant