diff --git a/.agents/skills/mex-inbox/.mex-managed.json b/.agents/skills/mex-inbox/.mex-managed.json deleted file mode 100644 index 64a7433856..0000000000 --- a/.agents/skills/mex-inbox/.mex-managed.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "schemaVersion": 1, - "owner": "mex-agent", - "skill": "mex-inbox", - "packageVersion": "0.8.2", - "files": { - "SKILL.md": "39cf595ac48203bf49ea2e273ea1c1c25e7ebbf4616c68596cd8e1c08eaf2738", - "agents/openai.yaml": "12a2827fe257c303d926122e4bb590910abd660e6451550a1db68a48037b4402", - "references/cli-workflows.md": "6c91905cc69d8ea47080630ee1551bf1a61ec33605fff01463d4a2ad71f497c6" - } -} diff --git a/.agents/skills/mex-inbox/SKILL.md b/.agents/skills/mex-inbox/SKILL.md deleted file mode 100644 index 7ddb707cf2..0000000000 --- a/.agents/skills/mex-inbox/SKILL.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: mex-inbox -description: Draft and review contributions to existing MEX project knowledge. Use when the user asks to capture a discussion or decision in project knowledge, propose an addition or correction for team review, or explicitly invokes /mex-inbox or $mex-inbox. Also supports existing Spec proposals. Do not activate for brainstorming alone, routine GROW upkeep, email inboxes, session logs, or handoffs. ---- - -# MEX Inbox - -Turn an explicit request to retain project knowledge into a focused contribution for review. Inbox proposals are review artifacts; accepted knowledge belongs in the existing Wiki Markdown. Ordinary GROW upkeep can continue directly. - -## Keep the scope honest - -- Create one `knowledge.create` or `knowledge.update` change per draft, for an existing kind: `architecture`, `component`, `convention`, `decision`, `pattern`, or `guide`. -- Existing Spec workflows also support `spec.create` and `spec.update` for `spec`, `requirement`, `constraint`, and `acceptance_criterion`. Choose these only for actual Spec-family intent. -- Capture durable conclusions, their rationale, and useful evidence. Do not dump the conversation or present unresolved ideas as agreed facts. -- Do not route session logs, Relays, or routine GROW edits through Inbox merely because they contain context. - -## Prepare a draft - -1. Distill what future agents or teammates need to know from the user's request and discussion. -2. Search existing knowledge first. Prefer correcting or extending the relevant record or section when it already covers the subject; create a new entry when the claim has no suitable home. -3. Read the exact target and current revisions before drafting a correction. Preserve unrelated knowledge when replacing its body. Never guess an ID or revision. -4. Preserve useful files, code, commits, entities, and external links as evidence only when actually available. Never invent provenance, authors, dates, events, or history. -5. Build one request against the action-scoped runtime contract. -6. Run the exact preview flow and summarize the human effect before technical details. -7. When the user already asked to create, save, or draft it, apply that exact successful checkout-local draft preview without asking again. -8. Return `/inbox?view=drafts&draft=` and state that the draft is checkout-local, nothing was published or shared, and review or publication happens in Hub. - -Read [references/cli-workflows.md](references/cli-workflows.md) before executing any Inbox mutation. Load only the operation being performed. - -## Apply the approval policy - -- Proceed with read-only list, show, and target resolution. -- Treat an explicit create/save/draft request as authorization only for the local draft preview and exact apply. -- Preview a local draft deletion, explain that the checkout-local draft will be removed, and obtain fresh confirmation before applying. -- For publish, approve, reject, withdraw, mark stale, or repair: create a semantic preview, explain the local-versus-Git-tracked effect, and wait for fresh explicit confirmation. Apply the exact preview unchanged only after confirmation. -- Keep Git commit, push, pull, and staging separate from every MEX authorization. -- Never approve or publish merely because this skill activated. - -## Report effects precisely - -- Lead with what the proposal means to a person. Do not expose envelopes, hashes, revision machinery, or raw diffs unless diagnosis requires them. -- After a local draft write, say exactly what changed and that it remains checkout-only. -- After a canonical write, say which Git-tracked MEX artifacts were written to the working tree and that commit/push is still required to share them. Never claim that MEX committed or pushed. -- When MEX context materially helps your work, mention MEX and the relevant finding naturally in your explanation. Tie the mention to what it helped you understand, decide, or verify. Avoid fixed phrases, standalone acknowledgements, repeated mentions, or narrating routine context loading. This replaces older MEX instructions requiring a fixed acknowledgement or context-loading narration. diff --git a/.agents/skills/mex-inbox/agents/openai.yaml b/.agents/skills/mex-inbox/agents/openai.yaml deleted file mode 100644 index de507237cc..0000000000 --- a/.agents/skills/mex-inbox/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "MEX Inbox" - short_description: "Propose additions to project knowledge" - default_prompt: "Use $mex-inbox to capture what we decided as a contribution to this project's knowledge." diff --git a/.agents/skills/mex-inbox/references/cli-workflows.md b/.agents/skills/mex-inbox/references/cli-workflows.md deleted file mode 100644 index c0f9215d84..0000000000 --- a/.agents/skills/mex-inbox/references/cli-workflows.md +++ /dev/null @@ -1,86 +0,0 @@ -# Inbox CLI workflows - -Use JSON mode for deterministic agent work. Keep request and preview files temporary and avoid displaying them unless troubleshooting. - -## Resolve only the needed contract - -Run: - -```text -mex inbox contract --action --json -``` - -Use one of these command IDs: - -- `inbox.draft.save` -- `inbox.draft.delete` -- `inbox.publish` -- `inbox.proposal.approve` -- `inbox.proposal.reject` -- `inbox.proposal.withdraw` -- `inbox.proposal.mark-stale` -- `inbox.proposal.repair` - -Treat this bounded action result as the exact runtime source for the request shape, available examples, constraints, preview command, and apply command. Do not dump `mex capabilities --json` or the full Inbox contract during ordinary execution. Write request and preview JSON only to ordinary regular files inside the checkout or an approved temporary directory; do not use symlinks. - -## Find existing knowledge and resolve a correction - -1. Use `mex wiki query --limit 10 --json` or `mex wiki list --type --limit 25 --json` to find related records before creating another. -2. Read the best candidate with `mex inbox target --json`. This bounded read returns `target`, `version`, `sourcePath`, and the existing body from the current Wiki index, without initializing or repairing it. -3. For `knowledge.update`, copy `target` and use `version.contentHash` as the target revision and `version.semanticRevision` as the semantic revision. Select a section entity when only that section should change. The patch body replaces the target's body; preserve its relevant existing content. A target can fit the read response but exceed the 16 KiB update-body limit; choose a smaller section or a title/summary-only patch when appropriate, never truncate existing knowledge to fit. -4. If no suitable record exists, use `knowledge.create` with the appropriate existing kind. MEX chooses a path in `context/` or `patterns/`; do not invent an Inbox knowledge category or supply an arbitrary destination path. -5. If several targets remain plausible after reading them, ask which one the user intends. If a read reports a stale or unavailable index, address its explicit maintenance requirement before relying on it; never fabricate revisions or silently refresh an index as part of a read. - -For create-time topics, find them with Wiki reads, then resolve each with `mex inbox target --json` for current revisions. Topics can be dependencies but are not Inbox correction targets. Omit topics when none are needed; normal knowledge creates have no relation editor. - -## Resolve a legacy Spec update - -1. Use `mex spec list --json` to identify candidates. -2. Use `mex spec show --json` for the exact candidate. -3. Match the requested durable claim to one exact entity and kind. -4. Copy `version.contentHash` as the entity revision and `version.semanticRevision` as the semantic revision exactly as the selected contract requires. For nested requirements, constraints, or acceptance criteria, take both from the enclosing `mex spec show` projection. -5. Stop and ask for target clarification when multiple candidates remain plausible. - -Do not invent target IDs, relation endpoints, topic IDs, or revisions. For a create request with relations or topics, resolve every referenced entity and its current revisions first. - -## Save a checkout-local draft - -1. Resolve `inbox.draft.save`. -2. Create a unique operation ID and a request containing one `knowledge.create` or `knowledge.update` draft (or a Spec change for actual Spec-family intent). -3. For a new draft, provide no unrelated expectations. For an existing draft update, read it with `mex inbox draft show --json` and use its exact current local revision. -4. Preview with `mex inbox draft save --json` and capture the complete successful JSON wrapper unchanged. Require `ok: true`, `mode: "preview"`, and `data.preview.valid: true`. -5. Summarize the proposed local effect. If the user asked to create/save/draft, apply with `mex inbox draft save --apply --json` without another confirmation. -6. Read the returned draft ID and respond with `/inbox?view=drafts&draft=`. - -The apply writes only checkout-local draft state. It does not create a canonical proposal, Activity record, commit, push, or notification. Apply before the preview expires; if anything changes or the preview becomes stale, preview again instead of reconstructing it. - -## Delete a local draft - -1. Read the exact draft and current local revision. -2. Resolve and preview `inbox.draft.delete`. -3. Explain that the checkout-local draft will be deleted and wait for fresh confirmation. -4. Apply the captured preview unchanged with `mex inbox draft delete --apply --json`. - -## Publish a draft - -1. Read the exact draft and resolve `inbox.publish`. -2. Build the request with the exact local draft revision and preview using `mex inbox publish --json`. -3. Explain that applying writes a canonical proposal and Activity artifact into the working tree while leaving Git commit/push separate. -4. Wait for fresh explicit confirmation. -5. Apply the exact preview with `mex inbox publish --apply --json`. -6. Return `/inbox?view=review&proposal=`. - -Publishing removes the exact local draft after creating the pending proposal. It does not approve the proposal or change accepted knowledge. The proposal is Markdown in the working tree; teammates receive it through Git. - -## Review canonical proposals - -Use `mex inbox proposal list --json` and `mex inbox proposal show --json` for read-only review. For approve, reject, withdraw, mark-stale, or repair: - -1. Resolve the matching command ID. -2. Use the proposal's exact current artifact revision and any action-specific rationale or replacement draft. -3. Preview with the corresponding `mex inbox proposal --json` command. -4. Explain the semantic outcome and which canonical Git-tracked MEX records would change. -5. Wait for fresh explicit confirmation. -6. Apply the exact preview with the same command plus `--apply --json`. - -Approval writes the proposed knowledge change, proposal decision, Wiki ledger, and Activity records to the working tree. The proposal remains review history. Reject and withdraw make a terminal proposal decision without changing knowledge. Mark stale changes a pending proposal to stale only when MEX proves dependency drift. Repair replaces stale intent, clears prior review, and returns the proposal to pending without changing knowledge. These canonical transitions write Activity records; none commits, pushes, pulls, stages, or notifies teammates. diff --git a/.agents/skills/mex-relay/.mex-managed.json b/.agents/skills/mex-relay/.mex-managed.json deleted file mode 100644 index 20ae02b7ea..0000000000 --- a/.agents/skills/mex-relay/.mex-managed.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "schemaVersion": 1, - "owner": "mex-agent", - "skill": "mex-relay", - "packageVersion": "0.8.2", - "files": { - "SKILL.md": "186d1d19c4b513f4b98ee27bf0259744d1ef79ffc758676484596cfe498aa502", - "agents/openai.yaml": "9c2e9c2e34d60ec0f4d07089b07d9e49a6c8210c2a3e277593c15a2635f03076", - "references/cli-workflows.md": "56b6e74129cdc6ddb5121480b03e01fd2f1f65b7b8c0b7899552a4a217d48cf5" - } -} diff --git a/.agents/skills/mex-relay/SKILL.md b/.agents/skills/mex-relay/SKILL.md deleted file mode 100644 index 15935610aa..0000000000 --- a/.agents/skills/mex-relay/SKILL.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: mex-relay -description: Prepare and manage durable MEX team handoffs. Use when the user asks to hand work to a person or team, prepare an end-of-session handoff, save what the next engineer needs, create a MEX Relay, take or close a Relay, or explicitly invokes /mex-relay or $mex-relay. Treat Relay as a memory and context baton, not chat, notifications, task assignment, issue tracking, or a Jira replacement; do not activate for ordinary status messages that are not durable handoffs. ---- - -# MEX Relay - -Prepare durable team handoffs that another engineer can continue from. Never represent a Relay as a sent message or notification. - -## Prepare a Relay draft - -1. Infer the useful session state: a concise summary, current position, completed work, in-progress work, blockers, unresolved questions, next actions, and relevant decisions, files, code, commits, or external links. -2. For “whoever picks this up,” choose open-to-team. It includes future active project Members; no Member lookup is needed to save this local draft. If the user names people, retain named-recipient intent and resolve exact IDs only when needed. An unresolved named draft may stay recipient-free locally; do not silently publish it to everyone. -3. Default to a standalone Relay. Use the local-save shortcut for a new draft; keep the structured preview/apply path for exact updates and canonical actions. -4. Include an existing relevant Workstream only as typed evidence. Never invent one or turn saving a handoff into Workstream creation. -5. Add optional typed context references only when the referenced IDs, paths, commits, or URLs are known. Never invent provenance. -6. Resolve the action-scoped runtime contract. For a new draft, `mex relay draft save --from --json` performs the exact local preview/apply internally. For an update, build and preview the structured request. -7. An explicit create/save/draft request authorizes that local write without another confirmation. Publication remains separate. -8. Return `/relays?view=drafts&draft=` and state that the draft is checkout-local and nothing has been delivered or shared. - -Read [references/cli-workflows.md](references/cli-workflows.md) before executing any Relay mutation. Load only the operation being performed. - -## Apply the approval policy - -- Proceed with read-only member, Workstream, draft, and Relay resolution. -- Treat an explicit create/save/draft request as authorization only for the local draft preview and exact apply. -- Preview a local draft deletion, explain its consequence, and obtain fresh confirmation before applying. -- For publish, take, or close: generate a semantic preview, explain the local-versus-Git-tracked effect, and wait for fresh explicit confirmation. Apply the exact preview unchanged only after confirmation. -- Keep Git commit, push, pull, and staging separate from every MEX authorization. -- Never publish, take, or close merely because this skill activated. - -## Preserve lifecycle meaning - -- Taking records one claimant: a named active recipient, or any active project Member for an open-to-team Relay. Future Members qualify after joining and receiving the artifact through Git. Separate offline claims can still require Git conflict resolution. -- An inactive Member can be reactivated through the existing Team identity workflow, preserving their ID and older handoffs. Preview and confirm this canonical change separately; never change identity merely to bypass Relay eligibility. -- Treat closing as “this handoff no longer needs attention.” Do not claim it completes a linked task, issue, pull request, or Workstream. -- After publication, say that Git-tracked Relay and Activity records were written to the working tree. Explain that teammates receive them only after commit/push and their own pull or refresh. -- Never claim that MEX sent a notification, committed, pushed, pulled, staged, assigned work, or completed another system's object. -- When MEX context materially helps your work, mention MEX and the relevant finding naturally in your explanation. Tie the mention to what it helped you understand, decide, or verify. Avoid fixed phrases, standalone acknowledgements, repeated mentions, or narrating routine context loading. This replaces older MEX instructions requiring a fixed acknowledgement or context-loading narration. diff --git a/.agents/skills/mex-relay/agents/openai.yaml b/.agents/skills/mex-relay/agents/openai.yaml deleted file mode 100644 index 9b0e781d0c..0000000000 --- a/.agents/skills/mex-relay/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "MEX Relay" - short_description: "Prepare durable MEX team handoffs" - default_prompt: "Use $mex-relay to prepare a durable handoff for the next engineer." diff --git a/.agents/skills/mex-relay/references/cli-workflows.md b/.agents/skills/mex-relay/references/cli-workflows.md deleted file mode 100644 index 89f8122762..0000000000 --- a/.agents/skills/mex-relay/references/cli-workflows.md +++ /dev/null @@ -1,88 +0,0 @@ -# Relay CLI workflows - -Use JSON mode for deterministic agent work. Keep request and preview files temporary and avoid displaying them unless troubleshooting. - -## Resolve only the needed contract - -Run: - -```text -mex relay contract --action --json -``` - -Use one of these command IDs: - -- `relay.draft.save` -- `relay.draft.delete` -- `relay.publish` -- `relay.acknowledge` -- `relay.close` - -Treat this bounded action result as the exact runtime source for the request shape, available examples, constraints, preview command, and apply command. Do not dump `mex capabilities --json` or the full Relay contract during ordinary execution. Write request and preview JSON only to ordinary regular files inside the checkout or an approved temporary directory; do not use symlinks. - -## Choose an audience and optional context - -1. For an open handoff or “save for whoever picks it up,” use `audience: "team"` and `recipients: []`. Do not enumerate today's Members; active Members who join later must remain eligible. This setting does not share the local draft. -2. For named people, use `audience: "members"`. Resolve names with `mex member list --active --limit 100 --json` and bounded cursors. Local drafts may retain an empty recipient array while names are unresolved; record the intended name in `unresolvedQuestions` and resolve it before publication. -3. Use `mex member show --json` for disambiguation or exact publication revisions. Ask when named recipients remain ambiguous; never reinterpret a named subset such as “the backend team” as every project Member. -4. Default to no Workstream lookup. If the user named a Workstream or the handoff clearly belongs to an existing one, run `mex workstream list --json` and `mex workstream show --json` to resolve it exactly. -5. In Relay v3, preserve a relevant Workstream as typed entity evidence when the selected contract supports it; do not author the legacy top-level Workstream field. - -Never fabricate recipient IDs, entity IDs, revisions, commits, code fingerprints, paths, URLs, or provenance. - -## Save a checkout-local draft - -1. Resolve `relay.draft.save`. -2. Write one temporary JSON object containing the draft content. Include a concise summary, the intended audience, and only useful non-empty context sections. Prefer accurate omissions over invented completeness. -3. For a new draft, run `mex relay draft save --from --operation-id --json`. This validates the content, obtains a signed local preview, and applies that exact preview internally. It never publishes and needs no Member resolution. Do not combine `--from` with a request file or `--apply`. -4. Require `ok: true`, `mode: "apply"`, and `data.applied: true`; read the draft ID from `data.localChanges` and return `/relays?view=drafts&draft=`. The shortcut retains the original preview under `.mex/local/relay-previews/` before applying and removes it after success. An interrupted save can resume with the same operation ID and unchanged content, or the exact `problem.recovery` command. Do not replace the receipt or create another operation to work around an interrupted/conflicting result; if recovery is refused, report it and inspect the existing draft state. - -For example, a new open handoff may contain: - -```json -{ - "audience": "team", - "recipients": [], - "summary": "Continue the parser correction.", - "completed": ["Reproduced the failure with the nested-import fixture."], - "nextActions": ["Check whether the same failure affects exported aliases."] -} -``` - -For an existing draft update, read it with `mex relay draft show --json`, preserve its content, and use its exact local revision in the structured request. Preview with `mex relay draft save --json`; require `ok: true`, `mode: "preview"`, and `data.preview.valid: true`. Apply the complete captured envelope unchanged with `mex relay draft save --apply --json`. An explicit save/update request needs no additional confirmation for this checkout-local write. - -The apply writes only checkout-local draft state in `.mex/local/team.db`. It does not create a canonical Relay or Activity record, deliver a handoff, commit, push, or notify anyone. Apply before the preview expires; if anything changes or the preview becomes stale, preview again instead of reconstructing it. - -## Delete a local draft - -1. Read the exact draft and current local revision. -2. Resolve and preview `relay.draft.delete`. -3. Explain that the checkout-local draft will be deleted and wait for fresh confirmation. -4. Apply the captured preview unchanged with `mex relay draft delete --apply --json`. - -## Publish a Relay - -1. Read the exact draft. For an open-to-team audience, no recipient Member list is needed. For named recipients, require at least one and read each current active Member; an empty named draft must be completed before publication. -2. Resolve `relay.publish`. Team publication expects only the exact local draft revision; named publication also expects every recipient Member revision. Preview with `mex relay publish --json`. The service verifies the current active sender in both cases. -3. Explain that applying replaces the private local draft with canonical Git-tracked Relay and Activity records in the working tree, records the service-observed branch/HEAD/dirty repository state without copying dirty source contents, and does not deliver through a notification service or share before Git commit/push and teammate pull/refresh. -4. Wait for fresh explicit confirmation. -5. Apply the exact preview with `mex relay publish --apply --json`. -6. Return `/relays?view=sent&state=open&relay=`. - -## Take a Relay - -1. Resolve the exact published Relay with `mex relay show --json`. A team audience allows the current active project Member; a named audience requires the current Member to be listed. An omitted audience on an older Relay means named recipients. -2. Resolve `relay.acknowledge` and preview with `mex relay acknowledge --json`. -3. Explain that applying records the current Member as claimant and writes canonical Relay/Activity state in the working tree. A synchronized claim prevents another take; separate offline claims still require Git conflict resolution. There is no unclaim or reassignment action, and it does not assign or start a task elsewhere. -4. Wait for fresh explicit confirmation, then apply the exact preview with `mex relay acknowledge --apply --json`. -5. Return `/relays?view=mine&state=open&relay=`. - -## Close a Relay - -1. Resolve the exact acknowledged Relay and its current revision. -2. Resolve `relay.close` and preview with `mex relay close --json`. -3. Explain that applying irreversibly marks only the handoff as no longer needing attention and writes canonical Relay/Activity state in the working tree. -4. Wait for fresh explicit confirmation, then apply the exact preview with `mex relay close --apply --json`. -5. Return `/relays?view=all&state=closed&relay=`. - -Closing does not complete a linked task, issue, pull request, or Workstream. No lifecycle command stages, commits, pushes, pulls, or sends a notification. diff --git a/.github/skills/code-review/tests/architecture-map.sh b/.github/skills/code-review/tests/architecture-map.sh index 3fa69034db..fdc26f3627 100755 --- a/.github/skills/code-review/tests/architecture-map.sh +++ b/.github/skills/code-review/tests/architecture-map.sh @@ -2,9 +2,11 @@ set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" -guide="$repo_root/AGENTS.md" +api="$repo_root/AdvancedCore/src/main/java/com/bencodez/advancedcore/api" -grep -Fq '`api/inventory/` contains GUI/editor behavior' "$guide" -grep -Fq '`api/item/` owns item construction and compatibility-sensitive item serialization' "$guide" +test -f "$api/inventory/BInventory.java" +test -d "$api/inventory/editgui" +test -f "$api/item/ItemBuilder.java" +test -f "$api/item/FullInventoryHandler.java" -echo "architecture map guidance: ok" +echo "inventory and item architecture: ok" diff --git a/.mex/.gitignore b/.mex/.gitignore deleted file mode 100644 index a2ec937c67..0000000000 --- a/.mex/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -graph.db* -wiki.db* -local/ diff --git a/.mex/AGENTS.md b/.mex/AGENTS.md deleted file mode 100644 index a61c9af5c3..0000000000 --- a/.mex/AGENTS.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: agents -description: AdvancedCore MEX authority and retrieval boundary. -last_updated: 2026-09-20 ---- - -# AdvancedCore memory - -Root `AGENTS.md` contains the source hierarchy and MEX use guidance. This Wiki keeps context about user/cache, storage, reward, and compatibility boundaries. Read only relevant pages from `ROUTER.md`. Verify every claim against current Java/tests; MEX 0.8.2 has no Java structural graph. - - -## MEX context policy -- When MEX context materially helps your work, mention MEX and the relevant finding naturally in your explanation. Tie the mention to what it helped you understand, decide, or verify. Avoid fixed phrases, standalone acknowledgements, repeated mentions, or narrating routine context loading. This replaces older MEX instructions requiring a fixed acknowledgement or context-loading narration. -- Do not claim an author, date, or historical event unless the retrieved data actually provides it. -- After a MEX write, say exactly what changed and its sharing boundary: a local draft is checkout-only and nothing is shared; a canonical artifact is written to the working tree and requires commit/push to share. -- Skill activation is not approval for canonical actions. - diff --git a/.mex/ROUTER.md b/.mex/ROUTER.md deleted file mode 100644 index 0212104059..0000000000 --- a/.mex/ROUTER.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: router -description: AdvancedCore durable knowledge routes. -edges: - - target: patterns/user-storage-change.md - condition: when changing user persistence - - target: patterns/reward-queue-change.md - condition: when changing queued reward resolution -last_updated: 2026-09-20 ---- - -# AdvancedCore memory routes - -| Task | Read | -| --- | --- | -| Shared API/platform ownership | `context/architecture.md`, `context/stack.md` | -| User/cache/SQL persistence | `context/storage.md`, `patterns/user-storage-change.md` | -| Rewards or queue restoration | `context/rewards.md`, `patterns/reward-queue-change.md` | -| Why current limitations matter | `context/decisions.md` | -| Build or MEX environment | `context/setup.md` | - -Root `AGENTS.md` owns authority and use guidance. Inspect current Java/tests after relevant MEX retrieval. diff --git a/.mex/SETUP.md b/.mex/SETUP.md deleted file mode 100644 index 6ff9d4f626..0000000000 --- a/.mex/SETUP.md +++ /dev/null @@ -1,3 +0,0 @@ -# AdvancedCore MEX setup - -Codex uses the official MEX Inbox and Relay skills. This Java repository has no structural Java Code Graph in MEX 0.8.2. Use Wiki context, then current Java source and tests. diff --git a/.mex/SYNC.md b/.mex/SYNC.md deleted file mode 100644 index dd8371c273..0000000000 --- a/.mex/SYNC.md +++ /dev/null @@ -1,3 +0,0 @@ -# Keep AdvancedCore memory current - -Follow root `AGENTS.md` authority rules. Check remembered storage and reward claims against source/tests before use. Correct stale entries through reviewed MEX edits or Inbox; rebuild the Wiki index after changes. diff --git a/.mex/config.json b/.mex/config.json deleted file mode 100644 index 0fe65b9f6c..0000000000 --- a/.mex/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "setupMode": "code-repo", - "aiTools": [ - "codex" - ], - "scaffold_id": "b2369e6e-a7ad-49d5-84af-a2a04252d236", - "scaffold_name": "AdvancedCore", - "origin": null, - "upstream": null -} diff --git a/.mex/context/architecture.md b/.mex/context/architecture.md deleted file mode 100644 index 292543b359..0000000000 --- a/.mex/context/architecture.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: architecture -description: AdvancedCore library ownership boundaries. -triggers: [architecture, API, user, reward] -last_updated: 2026-09-20 -mex: - id: mx_01M30783X7XRSR3B5HENZVSXAJ - type: architecture - status: promoted - revision: 1 - title: architecture ---- - -# Library boundaries - -AdvancedCore provides user data/cache, reward construction and dispatch, compatibility helpers, and platform integrations consumed by plugins including VotingPlugin. Legacy/public surfaces remain in `api/user` and `api/rewards`; newer shared contracts live under `core/user` and `core/reward`. Those shared contracts coexist with Bukkit adapters rather than replacing every legacy path. Source: `AdvancedCore/src/main/java/com/bencodez/advancedcore/api/user/`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/core/user/`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/core/reward/`. - -Bukkit-specific reward dispatch and scheduling still exist. The shared orchestrator sequences configured work through injected platform and durability adapters; inspect the actual adapter before assuming a stronger delivery guarantee. Source: `AdvancedCore/src/main/java/com/bencodez/advancedcore/api/rewards/RewardExecutor.java`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/core/reward/SharedRewardOrchestrator.java`. diff --git a/.mex/context/conventions.md b/.mex/context/conventions.md deleted file mode 100644 index dcf63df2fe..0000000000 --- a/.mex/context/conventions.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: conventions -description: Where current rules and contracts reside. -triggers: [convention, testing] -last_updated: 2026-09-20 -mex: - id: mx_01M30783YGZSBKFZQFH186Q7BN - type: convention - status: promoted - revision: 1 - title: conventions ---- - -# Rule ownership - -Repository guidance is in root `AGENTS.md`. `README.md`, `AdvancedCore/pom.xml`, CI, and tests describe current build and public behavior. MEX stores unusual constraints, not generic Java/Maven or code-review checklists. diff --git a/.mex/context/decisions.md b/.mex/context/decisions.md deleted file mode 100644 index 2a85b17d13..0000000000 --- a/.mex/context/decisions.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: decisions -description: Compatibility constraints for downstream consumers. -triggers: [decision, compatibility, migration] -last_updated: 2026-09-20 -mex: - id: mx_01M307CW4VNVXE8YM59FVJJCNG - type: decision - status: promoted - revision: 1 - title: AdvancedCore compatibility decisions ---- - -# Compatibility decisions - -AdvancedCore is shaded/consumed by VotingPlugin. Public user/reward changes affect its vote path even when this repository's own tests pass; check the current consumer source and dependency version before changing a contract. Source: `AdvancedCore/pom.xml`, root `AGENTS.md`. - -New queued reward references encode normal versus generated provenance. Legacy bare names have no marker: resolver precedence favors a registered/direct reward before generated fallback when names collide. This compatibility behavior does not establish exactly-once execution. Source: `AdvancedCore/src/main/java/com/bencodez/advancedcore/api/rewards/RewardExecutor.java`, `AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/rewards/QueuedGeneratedRewardDispatchTest.java`. diff --git a/.mex/context/rewards.md b/.mex/context/rewards.md deleted file mode 100644 index 882e8d9d88..0000000000 --- a/.mex/context/rewards.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: rewards -description: Reward execution and persisted queue provenance. -triggers: [reward, queue, generated, snapshot] -last_updated: 2026-09-20 -mex: - id: mx_01M307CW4VJTMDNFXVDRE3SS1A - type: component - status: promoted - revision: 1 - title: Reward queue boundary ---- - -# Reward boundaries - -`RewardExecutor` initializes online state, schedules reward execution off the Bukkit primary thread when needed, and resolves persisted queue references at delivery time. Explicit queue references distinguish generated files from normal named rewards; older entries first prefer a registered reward or direct handle, then try generated resolution. This preserves legacy queue provenance and current YAML-defined reward lookup. Source: `AdvancedCore/src/main/java/com/bencodez/advancedcore/api/rewards/RewardExecutor.java`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/api/user/PersistedQueueReference.java`. - -`SharedRewardOrchestrator` sequences a plan through requirements, durable decision/progress, deferral, native action, and checkpoint callbacks. The caller supplies `SharedRewardDurability` and platform actions. The action completes before the progress checkpoint; without an idempotent native action/adapter, a crash in that window can replay it. Do not infer exactly-once delivery from the orchestration interface. The legacy offline queue now claims occurrences, serializes per-user replay, and retains/restores failed entries; inspect its checkpoint and acknowledgement paths separately. Source: `AdvancedCore/src/main/java/com/bencodez/advancedcore/core/reward/SharedRewardOrchestrator.java`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/core/reward/SharedRewardDurability.java`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/api/user/AdvancedCoreUser.java`. diff --git a/.mex/context/setup.md b/.mex/context/setup.md deleted file mode 100644 index 2d27de2443..0000000000 --- a/.mex/context/setup.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: setup -description: MEX use for AdvancedCore. -triggers: [setup, MEX, graph] -last_updated: 2026-09-20 -mex: - id: mx_01M30783YVMXD07R9PNSMFCZ0F - type: guide - status: promoted - revision: 1 - title: setup ---- - -# MEX use - -Use Wiki for architecture and history, Java source/tests for implementation truth. Run `mex wiki rebuild-index` after memory changes and `mex check` for scaffold health. The local CLI does not need a Hub. diff --git a/.mex/context/stack.md b/.mex/context/stack.md deleted file mode 100644 index 397e4f366d..0000000000 --- a/.mex/context/stack.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: stack -description: Current AdvancedCore build/platform boundary. -triggers: [Java, Maven, Bukkit, proxy] -last_updated: 2026-09-20 ---- - -# Build/runtime boundary - -This checkout compiles for Java 21 from `AdvancedCore/pom.xml`; CI runs its Maven package goal. The POM includes Bukkit/Spigot, BungeeCord, and Velocity APIs. Dependency and version details should be read from the current POM, not frozen in MEX. MEX 0.8.2 indexes no Java symbols here. Source: `AdvancedCore/pom.xml`, `.github/workflows/maven.yml`. diff --git a/.mex/context/storage.md b/.mex/context/storage.md deleted file mode 100644 index d45f1d4502..0000000000 --- a/.mex/context/storage.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: storage -description: Current user cache and persistence semantics and limits. -triggers: [storage, cache, SQL, persistence] -last_updated: 2026-09-20 -mex: - id: mx_01M307CW4V8D4NK2BM9GH4VY2R - type: component - status: promoted - revision: 1 - title: User and storage boundary ---- - -# User and storage boundary - -Ordinary cached writes and the explicit atomic transaction are distinct paths. `UserDataCache.addChange` can publish before its queued persistence; a suppressed storage exception must not be mistaken for a durable acknowledgement. For caller-owned SQL records, `UserDataManager.withAtomicUserTransaction` enters the shared runtime, flushes queued cache work, and reconciles the committed snapshot. Initial values are row prerequisites; operation-specific mutations belong inside the callback. Source: `AdvancedCore/src/main/java/com/bencodez/advancedcore/api/user/usercache/UserDataCache.java`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/api/user/usercache/UserDataManager.java`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/core/user/runtime/SharedUserDataRuntime.java`. - -`SqlUserStorage.transaction` exposes a bounded callback with a locked user row and one backend-owned JDBC connection. The scope can read/write user values and access caller-owned SQL tables within the same commit; callers must not commit, close, change auto-commit, or retain the connection. This boundary does not supply a unique operation key, retry policy, or reward acknowledgement by itself. Blocking use belongs on a storage worker. Source: `AdvancedCore/src/main/java/com/bencodez/advancedcore/core/user/storage/SqlUserStorage.java`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcSqlUserStorage.java`. diff --git a/.mex/events/operations.jsonl b/.mex/events/operations.jsonl deleted file mode 100644 index fdec76623e..0000000000 --- a/.mex/events/operations.jsonl +++ /dev/null @@ -1,10 +0,0 @@ -{"v":1,"phase":"intent","opId":"mig_04bdfe68d2f7f9f793ced12eadc3a4027c0b5a64","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783X7XRSR3B5HENZVSXAJ"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["context/architecture.md"],"payloadHash":"60e9cb3b8cf14fc598ca64e965b4e9a2e20ef743ac37abc02e2c86cc48103ff5","revisions":[{"entityId":"mx_01M30783X7XRSR3B5HENZVSXAJ","before":0,"after":1}]} -{"v":1,"phase":"complete","opId":"mig_04bdfe68d2f7f9f793ced12eadc3a4027c0b5a64","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783X7XRSR3B5HENZVSXAJ"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["context/architecture.md"],"payloadHash":"60e9cb3b8cf14fc598ca64e965b4e9a2e20ef743ac37abc02e2c86cc48103ff5","revisions":[{"entityId":"mx_01M30783X7XRSR3B5HENZVSXAJ","before":0,"after":1}]} -{"v":1,"phase":"intent","opId":"mig_bba7e40030721976170be1e0e46673406f16555f","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783YGZSBKFZQFH186Q7BN"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["context/conventions.md"],"payloadHash":"dea0802d2c6208b2f103f615a1a322ba63f9a35dbddbe82d49988f8ed41f86ae","revisions":[{"entityId":"mx_01M30783YGZSBKFZQFH186Q7BN","before":0,"after":1}]} -{"v":1,"phase":"complete","opId":"mig_bba7e40030721976170be1e0e46673406f16555f","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783YGZSBKFZQFH186Q7BN"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["context/conventions.md"],"payloadHash":"dea0802d2c6208b2f103f615a1a322ba63f9a35dbddbe82d49988f8ed41f86ae","revisions":[{"entityId":"mx_01M30783YGZSBKFZQFH186Q7BN","before":0,"after":1}]} -{"v":1,"phase":"intent","opId":"mig_8dba03310a5302a413a7df38e88f6c2de16bc1e0","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783YVMXD07R9PNSMFCZ0F"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["context/setup.md"],"payloadHash":"cf355dba2ffc569444242ee5b2fce6dd91e95d47beb012e590e6b8893c2314a4","revisions":[{"entityId":"mx_01M30783YVMXD07R9PNSMFCZ0F","before":0,"after":1}]} -{"v":1,"phase":"complete","opId":"mig_8dba03310a5302a413a7df38e88f6c2de16bc1e0","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783YVMXD07R9PNSMFCZ0F"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["context/setup.md"],"payloadHash":"cf355dba2ffc569444242ee5b2fce6dd91e95d47beb012e590e6b8893c2314a4","revisions":[{"entityId":"mx_01M30783YVMXD07R9PNSMFCZ0F","before":0,"after":1}]} -{"v":1,"phase":"intent","opId":"mig_cc04f1fee47c054c0c16e513ddfd6f0cc8eee0d8","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783Z5PYF08WAWW4ANAERJ"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["patterns/reward-queue-change.md"],"payloadHash":"1072a9121ee524d2455683ec981fcd93140be76ed20022a550a39cfeeb80551e","revisions":[{"entityId":"mx_01M30783Z5PYF08WAWW4ANAERJ","before":0,"after":1}]} -{"v":1,"phase":"complete","opId":"mig_cc04f1fee47c054c0c16e513ddfd6f0cc8eee0d8","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783Z5PYF08WAWW4ANAERJ"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["patterns/reward-queue-change.md"],"payloadHash":"1072a9121ee524d2455683ec981fcd93140be76ed20022a550a39cfeeb80551e","revisions":[{"entityId":"mx_01M30783Z5PYF08WAWW4ANAERJ","before":0,"after":1}]} -{"v":1,"phase":"intent","opId":"mig_f55a8ecd21474000a175ad7778297c2f28674334","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783ZG1EDMVDQEA2XMH875"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["patterns/user-storage-change.md"],"payloadHash":"e44262574c07d9269b4c926c807a6bad692b5983ba739f6fe24abea618c717c1","revisions":[{"entityId":"mx_01M30783ZG1EDMVDQEA2XMH875","before":0,"after":1}]} -{"v":1,"phase":"complete","opId":"mig_f55a8ecd21474000a175ad7778297c2f28674334","type":"create-entry","entityIds":[],"createdIds":["mx_01M30783ZG1EDMVDQEA2XMH875"],"actor":{"kind":"system","id":"mex-migration"},"timestamp":"2026-01-01T00:00:00.000Z","files":["patterns/user-storage-change.md"],"payloadHash":"e44262574c07d9269b4c926c807a6bad692b5983ba739f6fe24abea618c717c1","revisions":[{"entityId":"mx_01M30783ZG1EDMVDQEA2XMH875","before":0,"after":1}]} diff --git a/.mex/patterns/INDEX.md b/.mex/patterns/INDEX.md deleted file mode 100644 index 62c3117959..0000000000 --- a/.mex/patterns/INDEX.md +++ /dev/null @@ -1,6 +0,0 @@ -# AdvancedCore patterns - -| Pattern | When useful | -| --- | --- | -| [user-storage-change.md](user-storage-change.md) | Changing cache, SQL, or user lifecycle behavior | -| [reward-queue-change.md](reward-queue-change.md) | Changing queued reward resolution or replay | diff --git a/.mex/patterns/README.md b/.mex/patterns/README.md deleted file mode 100644 index c585805c23..0000000000 --- a/.mex/patterns/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# AdvancedCore patterns - -These pages preserve non-obvious failure paths. Verify them against current code/tests. Formal rules stay in the repository and workspace instructions. diff --git a/.mex/patterns/reward-queue-change.md b/.mex/patterns/reward-queue-change.md deleted file mode 100644 index cd987c048b..0000000000 --- a/.mex/patterns/reward-queue-change.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: reward-queue-change -description: Preserve normal/generated reward provenance on queue delivery. -triggers: [reward, queue, generated] -last_updated: 2026-09-20 -mex: - id: mx_01M30783Z5PYF08WAWW4ANAERJ - type: pattern - status: promoted - revision: 1 - title: reward-queue-change ---- - -# Queued reward change - -Trace how the queue reference is written, restored, resolved, and dispatched. Test normal named, generated-file, legacy bare-name, and registered/generated name-collision cases. Include occurrence claims, checkpoint persistence, and failure recovery when changing replay. Keep the delivery claim narrower than the actual action and checkpoint contract. See `context/rewards.md` and `AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/rewards/QueuedGeneratedRewardDispatchTest.java`. diff --git a/.mex/patterns/user-storage-change.md b/.mex/patterns/user-storage-change.md deleted file mode 100644 index dad684b0b4..0000000000 --- a/.mex/patterns/user-storage-change.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: user-storage-change -description: Check cache publication against persistence behavior. -triggers: [storage, cache, SQL] -last_updated: 2026-09-20 -mex: - id: mx_01M30783ZG1EDMVDQEA2XMH875 - type: pattern - status: promoted - revision: 1 - title: user-storage-change ---- - -# User storage change - -Trace ordinary cache mutation/flush separately from `withAtomicUserTransaction`, including SQL failure, retry, cache reconciliation, and shutdown. Caller-owned records can share the explicit JDBC transaction, but uniqueness and retry remain caller responsibilities. See `context/storage.md` and `AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/user/`. diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index c5fe636e6e..0000000000 --- a/AGENTS.md +++ /dev/null @@ -1,107 +0,0 @@ -# Maintainer and AI-agent guide - -AdvancedCore is a shared Bukkit/Paper/Folia library embedded by plugins such as VotingPlugin, MCPerks, and GraveStonesPlus. Changes can affect downstream plugins even when this repository builds successfully, so preserve public behavior and packaged compatibility unless a breaking change is explicitly authorized. - -## Security threat model - -For security reviews, vulnerability triage, and security-sensitive changes, read docs/security-threat-model.md before classifying or fixing findings. Treat it as the repository-specific attacker/trust-boundary model; verify every conclusion against current code and tests. Do not promote compatibility, trusted-operator behavior, or generic correctness bugs into security findings unless the documented boundary is actually crossed. - -## Build and verification - -Requirements: JDK 21+ and Maven. The Maven project is in the `AdvancedCore/` subdirectory. - -```shell -mvn -B -f AdvancedCore/pom.xml test -mvn -B -f AdvancedCore/pom.xml package -``` - -All test classes and test-only helpers must live under `com.bencodez.advancedcore.tests` or its subpackages, with matching directories under `AdvancedCore/src/test/java`. Keep tests out of production packages. Preserve production visibility when relocating tests; use narrowly scoped test-only reflective helpers when package-private access is required. Preserve test discovery and the package-phase artifact tests. - -Confirm the current workflow and POM before relying on these commands. Do not use deployment, install, or developer-copy profiles for routine validation. Verify that the package invocation produced a fresh JAR, record discovered test counts, and run `git diff --check`. - -## Architecture and compatibility boundaries - -- `AdvancedCorePlugin` is the shared plugin entry point; `lifecycle/`, `core/runtime/`, and `bukkit/runtime/` coordinate its shared executor group plus shutdown and platform-cleanup sequencing. Component-specific executors remain with their owning handlers and managers. -- `api/command/` contains command parsing, permissions, player/console dispatch, aliases, and tab completion. -- `api/rewards/` parses and executes configured rewards, including conditions, chances, delayed work, and nested definitions. -- `api/user/` and storage packages own identity, cached user state, persistence, migrations, and shutdown flushing. -- `api/javascript/` and placeholder/message packages perform placeholder and script evaluation. -- `api/inventory/` contains GUI/editor behavior. -- `api/item/` owns item construction and compatibility-sensitive item serialization. -- SimpleAPI is a lower-level dependency. Verify its pinned contract instead of assuming an unreleased or unmerged API. - -Treat public classes, constructors, methods, return values, callback threading, configuration shapes, serialized data, and shaded packages as compatibility surfaces. Before changing one, search downstream usage in VotingPlugin and other affected repositories when authorized. - -## Compatibility-first change policy - -Compatibility is the default acceptance criterion for every new feature, refactor, fix, storage change, and dependency change. Unless the task explicitly authorizes a breaking change: - -- Existing plugins embedding or calling AdvancedCore must continue to compile and run without source, configuration, dependency, or deployment changes. -- Preserve public and de-facto APIs, constructors, overloads, callback/threading behavior, configuration keys/defaults, serialized forms, database semantics, shaded packages, and reflection-visible names used by downstream plugins. -- New configuration must be additive: existing files remain valid, missing new keys preserve the previous behavior, and no manual config regeneration or edits are required. -- Existing persisted user data and database state must remain readable. Any required migration must happen automatically, be restart-safe and idempotent, and not require administrators to delete/recreate data. -- New integrations or platform support must remain optional and must not force unrelated consumers to add dependencies or coordinate upgrades. -- Verify compatibility against VotingPlugin and other affected downstream usage when a change touches a shared contract. -- When a compatibility-preserving implementation is not practical, stop and surface the compatibility impact before implementing a breaking path unless the request explicitly permits it. - -For compatibility-sensitive changes, keep regression coverage for established behavior in addition to tests for the new behavior. - -## Runtime invariants - -1. Never block the Bukkit/Paper/Folia server or region thread on database, network, or long-running filesystem work. -2. Do not access Bukkit entities, inventories, worlds, or other thread-confined state from arbitrary async callbacks. -3. Reward execution must not be lost or duplicated across delay, disconnect, reload, retry, or partial failure. Preserve the component's documented delivery contract. -4. Read-only lookups must not create users, mutate caches, or trigger persistence unless that behavior is part of the established API. -5. Keep user identity and UUID resolution consistent across online/offline paths. Coordinate cache updates, database writes, resets, and shutdown flushing. Final shared-runtime retirement fences queued notifications before marking storage retiring; callbacks already holding lifecycle admission may finish and re-enter storage. The optional pre-storage shutdown stage must finish before native storage retirement. Platform cleanup and the completion hook may run earlier on deferred shutdown; consumers must retain their own persistence owner until their stage settles. -6. Reload and disable must cancel or retire tasks, listeners, executors, connections, and callbacks without allowing stale work to mutate new state. Native ServerData admission precedes early enable hooks and reads and is shared across shaded classloaders through bounded opaque JVM Properties tokens. Release only after successful publication/preparation and actual captured native executor termination; failed or cancelled stages retain admission until JVM restart because settlement alone cannot prove drain. Retain the originating Properties object for release. Repeated disable must not submit stale final writes after release. Final whole-file publications settle in order even for asynchronous owners. A visible native final replacement gets one bounded settlement retry; if unconfirmed, fence that owner against subsequent writes/reload/setup so another save cannot commit its unacknowledged values. -7. GUI/editor actions must recheck permissions and ownership and must not duplicate items, lose configuration, or save to the wrong path or user. -8. Optional integrations must be guarded against absent classes and incompatible versions. One platform must not load another platform's API accidentally. - -## Placeholders and JavaScript - -Placeholder parsing has compatibility-sensitive edge cases: - -- preserve literal modulo/percent text and whitespace-containing PlaceholderAPI tokens; -- preserve escape parity rather than consuming or duplicating backslashes; -- isolate script bindings per evaluation so player/provider state cannot leak between requests; -- continue resolving non-script placeholders when JavaScript evaluation is disabled; -- do not introduce a second evaluation pass that turns substituted player/provider data into executable script; -- distinguish trusted administrator-authored script from untrusted replacement data. - -Add focused regression tests whenever parsing order, escaping, tokenization, bindings, or disabled-engine behavior changes. - -## Persistence, configuration, and resources - -- Preserve existing YAML keys, defaults, casing behavior, migrations, and serialized forms unless a migration is included and tested. -- Make multi-step persistence transitions atomic or recoverable. Test failure before write, after write, before acknowledgement, during reload, and on restart when applicable. -- Bound queues, caches, input sizes, retries, and diagnostics. Handle executor rejection, cancellation, null/closed connections, and interruption explicitly. -- Do not hide failures with broad catches, ignored futures, or success results after partial failure. -- Never log credentials, database URLs, tokens, full configuration, or player data unnecessarily. - -## Change and PR workflow - -Keep changes focused and avoid formatting churn. Before any commit, push, PR update, review reply, or other remote change: - -1. run relevant focused tests; -2. run the full Maven package build; -3. verify the fresh artifact and test discovery; -4. run `git diff --check`; -5. inspect the complete base-to-HEAD diff for compatibility, concurrency, persistence, lifecycle, security, and packaging regressions. - -For substantive work, obtain a fresh source-read-only review. The implementation agent verifies and fixes accepted findings, reruns validation, and obtains a new review of the updated snapshot. Do not reuse an earlier clean verdict after changes, and do not merge without explicit authorization. - -## MEX project memory - -For substantial tasks where architecture, compatibility, or prior failures matter, retrieve relevant MEX context, then verify it against current Java/tests and formal contracts. Use current code/tests first, this guide and formal docs second, reviewed MEX knowledge third, and historical Relays last. Correct stale memory; skip MEX for trivial edits. MEX 0.8.2 does not index Java here. Use `$mex-inbox` for durable findings and `$mex-relay` for substantial unfinished handoffs. - - -## MEX agent skills -- At the start of every session, read `.mex/AGENTS.md` and `.mex/ROUTER.md` before project work; follow `ROUTER.md` to load only the relevant context. -- Read `mex logging --json` at session start and before optional logging. Its checkout-local advisory mode is `significant` (quiet default: material decisions, risks, blockers, or durable discoveries), `checkpoints` (batch useful notes at task/session boundaries), or `manual` (no unsolicited notes). Skip routine tool calls, edits, repeated status, and empty summaries. Honor explicit user log requests in every mode; never suppress mandatory workflow Activity or recovery audit records. Report a policy read failure instead of guessing or changing the preference. -- When earlier work may inform the task, retrieve bounded relevant notes with `mex timeline --query "subject phrase" --file src/example.ts --limit 10 --json`, using the known subject or exact recorded file path, or both. Treat matches as historical evidence, not accepted current knowledge; verify conclusions before reuse or explicit promotion with their source retained. -- Use `$mex-inbox` for explicit contributions to project knowledge and `$mex-relay` for durable team handoffs. Invoke them automatically when intent clearly matches; ordinary GROW upkeep remains available without Inbox. -- When MEX context materially helps your work, mention MEX and the relevant finding naturally in your explanation. Tie the mention to what it helped you understand, decide, or verify. Avoid fixed phrases, standalone acknowledgements, repeated mentions, or narrating routine context loading. This replaces older MEX instructions requiring a fixed acknowledgement or context-loading narration. -- Do not claim an author, date, or historical event unless the retrieved data actually provides it. -- After a MEX write, say exactly what changed and its sharing boundary: a local draft is checkout-only and nothing is shared; a canonical artifact is written to the working tree and requires commit/push to share. -- Skill activation is not approval for canonical actions. - diff --git a/docs/checked-player-presence.md b/docs/checked-player-presence.md deleted file mode 100644 index 9832038171..0000000000 --- a/docs/checked-player-presence.md +++ /dev/null @@ -1,84 +0,0 @@ -# Checked server player presence - -`AdvancedCorePlugin.checkPlayerPresenceAsync(UUID)` reads server join-history evidence for an already resolved UUID. -It returns `CompletionStage` with `JOINED`, `NEVER_JOINED` or `UNKNOWN`. It does not resolve a name, -create a user, write history or use plugin-row existence as join proof. Existing boolean identity-validation APIs -remain unchanged. Resolve the correct current server UUID on an appropriate worker before calling this API; -an unresolved or ambiguous migrated identity must remain unknown rather than being guessed from a name. - -`checkPlayerPresenceIdentitiesAsync(Collection)` accepts up to eight verified current/historical UUIDs -belonging to the same person. After input validation, positive evidence from any supplied identity is sufficient. Negative evidence -requires every supplied identity to be confirmed negative; uncertain, empty, missing or oversized input stays -unknown. The API does not discover aliases or validate their ownership. Supply a null member to represent an -unresolved part of a migration, rather than silently omitting it: this makes the complete request unknown before -any read is queued. Duplicate UUIDs are read only once. - -The requesting thread performs no history, filesystem or UUID lookup. The existing AdvancedCore storage executor -owns the read, with at most 128 admitted entries per service. An alias request atomically reserves one entry -for its public aggregate plus one for each distinct child. Insufficient capacity rejects the complete request -as unknown without scheduling partial alias reads. Missing identity, saturation, executor rejection, -provider failure and retired requests return `UNKNOWN`. Completions may run on a worker, requesting or retiring -thread: use an asynchronous continuation or the correct platform scheduler for client work. Never attach -blocking storage or player effects directly. A result is an inspection snapshot, not permission to mutate a -player or proof that a later menu/configuration generation is still current. - -The runtime captures server-wide player-storage directory metadata and in-memory player-saving policy during -enable on the platform owner. Disabled, malformed or unavailable saving policy disables negative answers -for that generation while retained records and observed joins can still establish positives. The absent -`players.disable-saving` key follows the platform false default. Workers never load platform configuration. -Recognized CraftBukkit implementations provide either the level-directory API or the player's storage-directory -metadata; a world/dimension folder is never assumed to own player data. Unsupported layouts remain `UNKNOWN`. -The worker reads UUID-named history directly without calling the server's offline-player loader, profile lookup, -data fixer or migration logic. Complete GZIP/NBT records supply `JOINED`; malformed or incomplete records remain -`UNKNOWN`. Current, recovery and retained migration files are read without creating, moving or rewriting them. -Input is bounded to 1 MiB compressed, 8 MiB expanded, depth 64 and 100,000 structural nodes; oversized history is -unknown. Negative evidence requires a readable unchanged directory with no record or recovery file for that UUID. -Native interrupted-save files (`UUID-random.dat`) and corruption backups prevent negative answers even when no -canonical record survives; their contents are never read as positive proof or changed. A worker-owned directory -index preserves retained recovery evidence across checks. Every absent result requires a fresh bounded -enumeration: directory identity and timestamps alone cannot certify unchanged entries. -For the built-in provider, requests register at their own position on the existing storage worker, -then one bounded cohort shares a fresh scan. Registration turns cannot move a later request ahead -of previously queued persistence. Each cohort includes at most the existing 128 admitted entries; -custom providers retain their original individual scheduling. Point-record checks run before the -common enumeration, and a canonical/recovery record discovered there prevents a false negative. -Subsequent cohorts always scan afresh, including on keyless filesystems with colliding timestamps; -no cached negative result certifies a later request. -Scanning is bounded to 1,000,000 entries and 65,536 recovery identities; incomplete, failed or unstable scans -remain unknown. Closing the lookup clears the index without waiting for an in-flight scan. -File keys strengthen stability checks when the provider supplies them. Providers without file keys use -unchanged creation timestamps, modification times and record sizes; unavailable creation metadata remains -unknown. A stable valid record provides positive evidence without inspecting later siblings. A failed -candidate read can prevent negative evidence but cannot erase a readable valid sibling. Directory readability -is rechecked before using an absence index. A missing directory, symbolic link, failed scope read or changed -scope remains unknown. No raw history or paths are -returned or logged. - -The additive `checkPlayerPresenceIdentitiesAsync(identities, true)` operation also checks whether the -actual history scope contains unmapped offline/non-online UUID records or observed joins. Current-name -cache mappings cannot enumerate all former offline names. Such retained evidence prevents a negative -online-family result unless a checked alias already supplies positive history. A stable readable scope -containing only online UUID records can still supply `NEVER_JOINED`; malformed offline records and -interrupted/corruption backups keep the result unknown. This uses the same bounded worker-owned index -and admission limits, with fresh bounded enumeration before each wider negative result because directory -timestamps cannot certify unchanged contents. Online UUIDs require version 4 and variant 2; unsupported -aliases retain positives but cannot certify wider absence. No profile lookup or history mutation is used. Existing UUID-only methods retain -their behavior. Providers that implement only the original lookup method preserve positives but -cannot certify the wider negative scope. - -A generation-owned join listener and initial UUID-only online snapshot supply positive evidence for players whose -first history file has not yet been saved. The requesting/worker threads never inspect player entity state. -This evidence retains at most 65,536 joined UUIDs; failed snapshot capture or capacity exhaustion disables new -negative answers while retained positive evidence remains usable. Retired listeners cannot repopulate the old -generation. Alias requests share the same admission/retirement boundary as individual reads: an unfinished -aggregate becomes `UNKNOWN` at retirement even if one alias already completed positive. - -These are checks against the server's retained history for the resolved identity. They cannot reconstruct -deleted history or unrelated UUIDs after an identity-mode change. Keep uncertain migrated identities visible. -Shutdown retires requests before existing cleanup shuts down the shared worker. Re-enable retires the previous -service; delayed reads do not publish into its replacement. The service creates no executor or alternate user -storage and does not replace existing lifecycle cleanup. - -An optional display filter must keep both `JOINED` and `UNKNOWN`, and exclude only confirmed `NEVER_JOINED`. -Filtering must not change award eligibility. Exact consumer integration and live platform validation are -separate acceptance checks; passing the library's focused tests does not prove either. diff --git a/docs/jar-packaging.md b/docs/jar-packaging.md deleted file mode 100644 index f79c865962..0000000000 --- a/docs/jar-packaging.md +++ /dev/null @@ -1,23 +0,0 @@ -# JAR packaging contract - -AdvancedCore consumes SimpleAPI's normal self-contained artifact for SNAPSHOT -compatibility and preserves the established transitive dependency contract. -It does not add an AdvancedCore-specific payload filter or change dependency -publication semantics for existing consumers. - -AdvancedCore does not use SimpleAPI's HTTP, Redis, or MQTT implementations. -Its SimpleAPI dependency therefore excludes Bouncy Castle, Jedis, and Paho. -Hikari, FoliaLib, Configurate, and UniDialog remain available because the -AdvancedCore API and implementation use them. Hikari and FoliaLib retain their -existing `com.bencodez.simpleapi.*` relocated package names. - -Gson is declared as `provided`: AdvancedCore compiles against API signatures -that contain Gson types, while the supported server/proxy platforms supply it. -Rhino remains bundled and relocated for the JavaScript feature. - -The package phase runs `PackagedArtifactTest` after shading. It verifies the -actual minimized JAR contains its required classes and relocations. VotingPlugin -applies its own narrow artifact filters when producing its smaller user-facing -distribution. -Deployment profiles use the same Shade configuration; only -publication/final-name behavior differs. diff --git a/docs/mysql-startup-failure.md b/docs/mysql-startup-failure.md deleted file mode 100644 index 40046ccaae..0000000000 --- a/docs/mysql-startup-failure.md +++ /dev/null @@ -1,36 +0,0 @@ -# MySQL startup failures - -User storage remains fail-closed: a rejected MySQL connection aborts startup. It -never selects SQLite as a fallback. Correct the configured database credentials -or server permissions before restarting; existing tables and user data remain -unchanged. - -This fix requires the accompanying SimpleAPI SQL initialization fix. Its table -constructor retains the original connection failure and avoids invoking subclass -logging hooks before AdvancedCore assigns its plugin reference. AdvancedCore -continues to initialize its table, caches and UUID schema after that assignment. -If subclass initialization subsequently throws, it closes the owned SQL pool and -workers and preserves the primary failure, attaching cleanup failures as suppressed -exceptions. The platform-neutral MySQL backend already uses the same deferred -initialization boundary and owns equivalent failure cleanup. - -No configuration or schema migration is required. Pool initialization remains the -existing startup operation; this change does not move startup to a background -thread or change successful storage or reward processing. - -The proxy/global-data SQL constructor had the same rejected-connection continuation -pattern. It now aborts before invoking its subclass diagnostics or issuing queries, -and closes the pool if initial database selection fails. - - -## Building against the published dependency - -The POM pins published SimpleAPI `1.0.2-20261011.161601-40`, which includes the -merged SQL lifecycle fix from PR #111 (`fdbf26eec885ec3f1c1dbd937a243a0acfaa0a5e`). -A normal clean checkout can run `mvn -B -f AdvancedCore/pom.xml clean package` -without checking out or installing SimpleAPI manually. CI uses the same dependency -resolution path. The immutable timestamp avoids older rolling snapshot cache entries. - -Normal server installation uses the shaded AdvancedCore JAR; no separate runtime -SimpleAPI installation, configuration change, schema migration or storage fallback -is added. The dependency must be available from the configured Maven repository. diff --git a/docs/reward-gamemode-eligibility.md b/docs/reward-gamemode-eligibility.md deleted file mode 100644 index 9b0aa28d1d..0000000000 --- a/docs/reward-gamemode-eligibility.md +++ /dev/null @@ -1,166 +0,0 @@ -# Reward gamemode eligibility - -Rewards may opt into the live player's Bukkit gamemode with `GameModes` and -`BlackListedGameModes`. Both values must be YAML lists containing at most four valid Bukkit -`GameMode` enum names (case is ignored). - -An allowlist requires the current mode to be present. A denylist rejects the -current mode, and denial wins when a mode appears in both lists. Omitting both -keys preserves existing reward behavior when no global default is configured. -Invalid types or names fail closed. - -The embedding plugin can set `DefaultRewardGameModes` in its primary configuration -(the same configuration that supplies `DefaultRewardWorlds`). For example: - -```yaml -DefaultRewardGameModes: - - SURVIVAL -``` - -The default is an empty list, so existing configurations keep their behavior. -A reward's explicit `GameModes` list overrides the global allowlist, including -`GameModes: []` to opt out. `BlackListedGameModes` still rejects matching modes. -The global list has the same four-entry limit and valid names as a reward list; -a malformed default denies rewards that inherit it, while an explicit valid -reward allowlist remains independent. Saved changes take effect through the -embedding plugin's normal configuration reload. AdvancedCore does not regenerate -or rewrite existing reward definitions to apply the default. - -```yaml -GameModes: - - SURVIVAL - - ADVENTURE -BlackListedGameModes: - - ADVENTURE -Commands: - - "give %player% iron_ingot 1" -``` - -The current mode is read from the online player on that player's owning server -scheduler. Rewards deferred by an unmet reattemptable requirement are retried -through the existing offline/timed queue after a completed gamemode transition; -the queue remains the source of truth. The transition wakeup uses a one-second -player-owned delay so it observes the committed mode, and reads it again before -remaining effects. Completed effects keep their existing replay checkpoints; this -does not promise atomicity between external commands and storage after a crash. -An already-passed chance is retained with the original deferred occurrence even -when eligibility changes before its first effect. Replay does not roll it again, -including after handler replacement or removal of a global default. - -Native transition signals recheck due timed rewards as well as the offline queue. -A timed occurrence deferred specifically by player eligibility retains its original -due time and replay identity while the bounded eligibility fallback owns its automatic -wake-up. Repeated deferrals do not create additional per-occurrence timer chains. -If that fallback cannot accept a hint, the occurrence keeps the ordinary future -retry deadline and timer instead. A transition does not accelerate unrelated future -rewards or ordinary failures with a future retry deadline. Expected eligibility deferrals -use debug logging; actual delivery failures retain their warning. - -The bounded fallback begins after ten seconds and backs off repeated attempts per -player up to five minutes, using elapsed monotonic time. Explicit eligibility -signals retry immediately and reset that fallback delay. Repeatedly retaining the -same unfinished occurrence does not reset the backoff or discard its wake-up hint. -Quit releases the cached native player reference but retains the bounded logical -hint. It does not poll that player's storage while waiting for login. The existing -resolved AdvancedCore login event, after configured authentication delays, rebinds -and wakes that known work after validating the exact live player session on its -owning scheduler. Cancelled events and stale events for a disconnected or replaced -session do not wake it. A quit also invalidates a queued eligibility check before -its worker starts. If replay already started, its ordinary per-action eligibility -checks continue honoring that login wait, retaining unfinished work for the accepted -replacement session. An accepted login also records a plugin-owned Boolean receipt -on that exact native player, even when there is no pending hint yet. A late retirement -can therefore recover work after login without treating a merely connected replacement -as authenticated. Unknown hints resolve and check this receipt on the player's owner -before reading reward queues. Other plugins' metadata and non-Boolean values are not -accepted. Quit removes only this plugin's receipt; it is session metadata, not persisted -player data or a new reward queue. Plugin startup continues reconstructing timed callbacks -from the existing durable queue. - -When the plugin is freshly enabled while players are already connected, it also -initializes receipts for those existing native sessions after its integrations -are ready. It uses the configured authentication providers, resolves the existing -user asynchronously by name (including offline-mode identity rules), and rechecks -the exact session on its owning scheduler. Newly joining sessions still use the -normal accepted-login path; this bootstrap does not emit another generic login -event or create another reward occurrence. Checks are sequential. A timed-out -provider query cannot accumulate more queries while its underlying operation is -still running. Provider failures fail closed and produce a bounded debug notice; -a later normal authenticated login remains the recovery path for a session whose -startup check could not complete. Disconnect, handler replacement and disable -invalidate pending receipt publication. - -Consumers that explicitly disable AdvancedCore user-data loading do not register -its normal login listener. For those consumers, native join and startup accept -the connected session directly on its owning scheduler, without loading a user, -querying authentication integrations, or firing an AdvancedCore login event. -Reconnect wakes only already-known deferred work and retains its original replay -identity. Consumers with user-data loading enabled keep the ordinary authenticated -login requirement described above. - -The retained-hint table is limited to 4,096 logical users. If it fills, ordinary -offline work is still discovered automatically: the existing fallback takes a -snapshot of currently online players and visits at most 128 per pass, using each -player's owning scheduler and asynchronous name-based user resolution. One -snapshot is finished before taking another, so a changing online population cannot -repeatedly displace an unvisited member of that cycle. Discovery backs off from ten -seconds to at most five minutes between passes and remains available until disable. -It scans online sessions, not the stored player database. Mode/world/login signals -also try immediate discovery. At most 128 overflow resolutions and 128 temporary -replay admissions may be outstanding; saturated admissions remain discoverable in -a later pass. An uncompleted storage or replay callback still occupies its slot, -rather than allowing timed-out requests to accumulate. Temporary admissions retain -the session gate while replay claims run; after saturation, configured gamemode -actions also require the plugin's accepted-login receipt. An offline or merely -connected replacement cannot acquire rewards through overflow recovery. The existing -persistent queue, occurrence identities and completed-effect checkpoints remain -authoritative; this is neither a second reward queue nor another delivery path. - -The existing `GiveOffline` policy still controls whether a denied ordinary reward -is retained. Forced or ignored requirements keep their established bypass behavior. -No temporary-inventory detection or integration is included in this change. - -Command lists also recheck before each pending command. A command that changes -mode is a completed effect; later commands wait for eligibility rather than -repeating that command. On Folia, console commands and economy mutations run on the global scheduler, -so a player-owned eligibility check and subsequent global handoff cannot be -atomic with a simultaneous gamemode change. Already-started effects are never -rolled back. - -For opted-in rewards on Folia, entity-scheduler retirement before an action -starts retains the unfinished work under the existing `GiveOffline` policy. -Immediate retirement and retirement after scheduling use the same action claim; -a stale callback cannot later execute that retained attempt. Scheduler rejection, -unknown timeouts, shutdown and already-committed inventory overflow retain their -existing failure and ownership behavior. Ordinary Spigot scheduling is unchanged. - -## Requirement execution context - -Async reward delivery and `canGiveRewardAsync` retain ordered requirement evaluation. -Scripts, custom requirements and preprocessing placeholder providers run on the reward -worker. Only native world, location and gamemode predicates request the player's owner; -world-requirement file preparation happens on the worker before that handoff. Custom -`RequirementInject` implementations may override `requiresPlayerThread()` for a short, -nonblocking native predicate and `prepareAsyncRequest(...)` for its worker preparation. -The synchronous `canGiveReward` method retains its existing caller-owned execution contract; -callers that need scheduling should use the asynchronous method. This does not make arbitrary -Bukkit calls inside administrator scripts safe on a worker. - -A freshly deferred hint never authenticates a player. Without this plugin's accepted-login -receipt it waits for the normal accepted-login signal before either offline or timed queue -replay. A native pending-login receipt also denies configured gamemode actions, even when -the current mode otherwise matches. Existing queue payloads and replay identities are retained. - -Asynchronous reward requirements keep online `Permission` and `VaultGroup` checks -on the resolved player's owning scheduler. Offline permission and group providers -remain on the reward worker, including `VaultGroup` when `GiveOffline` is enabled. -Permission negation, default permission names and requirement enablement keep their -existing meanings. The synchronous requirement API retains its caller-owned threading -contract. Custom requirements may override `onRequirementRequestAsync` to compose -native reads through `evaluatePlayerRequirement` with provider work through -`evaluateAsyncRequirement`; the default delegates to the existing synchronous callback. -These phase helpers await the result and do not create another reward occurrence. - -For rewards opting into either gamemode key, the native eligibility observation runs on the player owner, then injection callbacks (including `Javascripts`) return to the existing asynchronous reward scheduler. Collected player actions still return to their owner and recheck eligibility before mutation. Unconfigured awaited rewards retain their established callback boundary. Custom callbacks and scripts must continue to use supported scheduled APIs for direct Bukkit work; arbitrary script code is not made thread-safe automatically. - -For rewards using gamemode eligibility, the native player session resolved before an injection is retained across the worker callback. Collected actions and mixed command sections revalidate that session on its owning scheduler; a reconnect does not silently transfer an admitted action to a replacement session. Deferred work still follows the existing retry policy. diff --git a/docs/security-threat-model.md b/docs/security-threat-model.md deleted file mode 100644 index 44e4d90fdb..0000000000 --- a/docs/security-threat-model.md +++ /dev/null @@ -1,254 +0,0 @@ -# AdvancedCore security threat model - -This document defines the repository-specific threat model for security review and Codex Security scans. Read it with current source, tests, and AGENTS.md. AdvancedCore is a shared foundation, so a helper that mishandles lower-trust input can become a vulnerability in several downstream plugins even when AdvancedCore itself has no direct Internet listener. - -Current code is the source of truth. Older security findings are useful attack ideas, but do not assume an old implementation still exists. - -## Security objectives - -AdvancedCore runs inside the Minecraft server JVM with the privileges of the consuming plugin and provides shared user persistence, SQL, identity, rewards, configuration, commands, GUIs, timers, permissions, backups, cross-server helpers, placeholders, and optional JavaScript. - -The highest-value properties are: - -1. Lower-trust values passed through a public API must remain data and not become SQL syntax, file paths, reward selectors, console commands, scripts, placeholders, or other executable syntax unexpectedly. -2. User-data reads/writes, caches, transactions, and callbacks must not allow duplicate rewards, lost debits, stale authorization, or cross-user state leakage under concurrency. -3. Permission and identity decisions must be correct at the final side-effect boundary, including offline-mode, Bedrock, proxy, and delayed/offline reward paths. -4. Reward execution must not be duplicated or redirected across retries, delayed work, world changes, reconnects, reload, or failure recovery. -5. Bukkit/Paper/Folia thread ownership must be preserved so ordinary interaction spam cannot become races, duplicated state, or server instability. -6. Generic filesystem, YAML, backup, SQL, and script helpers must not silently widen a lower-trust caller's authority. -7. Optional JavaScript is privileged operator code, but attacker-derived replacement data must not become executable script or leak another player's evaluation context. -8. Queues, caches, scheduled work, scans, recursion, and diagnostics influenced by players or persisted tainted data must remain bounded. - -## Trust boundaries - -### Lower-trust data - -Treat as attacker-controlled or tainted unless the caller proves otherwise: - -- player names and UUID-like strings; -- command arguments, GUI clicks, dialog/text values, sign/chat/placeholder results; -- values read from user databases or caches when they may have originated from votes, proxies, remote services, compromised backends, or previous unsafe writes; -- reward names/keys chosen indirectly by players or remote data; -- plugin/proxy/global-message payloads; -- values downstream plugins receive from Votifier, Redis, MQTT, sockets, plugin messaging, or shared databases; -- offline-mode and Bedrock identity strings; -- public API parameters when a benign downstream plugin may pass player- or vote-controlled values. - -Authentication or persistence does not make a string safe for a different interpreter. - -### Trusted operator input - -Plugin YAML, reward files, configured console commands, database credentials/table prefixes, backup/report destinations chosen directly by an administrator, admin-only raw SQL, and explicitly enabled JavaScript are intentionally privileged. - -Do not report an administrator configuring a dangerous command, script, or query as a vulnerability by itself. Report it when a lower-trust actor can influence that privileged input or reach the sink without the intended authorization. - -### Other installed plugins - -A fully malicious installed plugin already shares the JVM and is not meaningfully sandboxed. Public API footguns still matter because ordinary downstream plugins may pass lower-trust data into AdvancedCore assuming documented validation or escaping. - -## Current controls to preserve - -Current master already contains important fixes and architecture that older scan results may predate: - -- the SQLite user path has prepared-statement regression coverage for values (`AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/user/UserTablePreparedStatementTest.java`); -- selected user-storage paths quote dynamic SQL identifiers (`AdvancedCore/src/main/java/com/bencodez/advancedcore/api/user/userstorage/sql/UserTable.java`); -- the normal registry and queued generated-reward resolver paths normalize names and validate safe reward filenames before constructing reward objects (`AdvancedCore/src/main/java/com/bencodez/advancedcore/api/rewards/RewardRegistry.java`, `AdvancedCore/src/main/java/com/bencodez/advancedcore/api/rewards/RewardLoader.java`); -- shared user/cache runtime uses explicit admission gates and concurrent structures (`AdvancedCore/src/main/java/com/bencodez/advancedcore/core/user/runtime/SharedUserDataRuntime.java` and its regression tests); -- JavaScript evaluation synchronizes access to the shared engine (`AdvancedCore/src/main/java/com/bencodez/advancedcore/api/javascript/JavascriptEngine.java`); -- inventory click execution has explicit scheduler behavior and regression tests (`AdvancedCore/src/main/java/com/bencodez/advancedcore/api/inventory/BInventoryListener.java`); -- compatibility-sensitive Folia/player scheduling has regression coverage in the command/reward scheduler tests. - -These are **path-specific controls**, not blanket guarantees for every sibling API. Security review should verify that alternate paths preserve the same boundary instead of assuming one protected path makes the whole subsystem safe. - -Security review should look for bypasses, inconsistent sibling paths, and new cross-feature races rather than simply restating the old vulnerability that motivated a control. - -## SQL and persistent user state - -High-risk areas include user storage, global SQL state, migrations, dynamic columns, queued writes, and public raw-query helpers. - -### Values vs identifiers - -Lower-trust values should be bound with prepared statements. - -Table names, column names, type definitions, ORDER BY fragments, defaults, and DDL cannot be protected by value binding. They require strict construction, quoting, or allowlists appropriate to the database. - -Search for: - -- one SQLite, MySQL, or PostgreSQL sibling path that concatenates a value while others bind it; -- identifier quoting bypassed by type/default expressions or raw query fragments; -- a lower-trust user-data key becoming a column/schema identifier without validation; -- raw SQL helpers reachable through player or admin-lite input; -- asynchronous ALTER/migration steps racing reads and writes; -- partially applied migrations leaving authorization-relevant fields at defaults; -- error handling that converts a failed balance/permission read into an allow decision; -- stale caches overwriting newer shared-database values; -- transaction boundaries that allow reward effects without durable debits or limits. - -Admin-only raw SQL is intentional. Its existence is not a remote SQL-injection finding. - -### User cache and concurrency - -Trace one mutation through admission, cache read, validation, storage write, cache update, callbacks, and flush/recovery. - -Look for: - -- lost updates between async writers; -- stale reads used for purchase, reward, or permission decisions; -- lock-order inversions or callbacks while holding shared locks; -- shutdown dropping accepted queued mutations; -- duplicate change/listener callbacks for one logical mutation; -- callbacks delivered on an unexpected thread where downstream plugins may grant rewards or call Bukkit APIs; -- cache eviction or reload allowing stale data to become authoritative again; -- one UUID/name mapping mutating another user's cache. - -## Filesystem, YAML and backup boundaries - -Generic file helpers are powerful because downstream code may supply names and paths. - -The normal registry lookup and queued generated-reward replay paths validate safe reward filenames, but that guarantee is not universal. In current master, the public directly-defined path `RewardLoader.getRewardDirectlyDefined` still constructs `new File(directFolder, reward + ".yml")` after only replacing spaces and does not call `RewardRegistry.isSafeRewardFileName`. Treat that sibling path as an outstanding review target: determine whether any lower-trust caller can supply the reward name and whether later save/edit operations can escape the intended `DirectlyDefined` directory. - -Test all reward-loading, creation, and editing paths for the same containment invariant, including directly defined rewards, folders, migrations, GUI editors, and public APIs. - -Search for: - -- parent traversal, absolute paths, separator variants, Unicode normalization, Windows drive/UNC syntax, or symlink tricks bypassing containment; -- check-then-use races between validation and file open/move; -- a validated child path later converted back to an untrusted string and re-resolved; -- generic file writers receiving player/vote values through a benign caller; -- backup/report creation following symlinks into secrets or unrelated plugin data; -- archives/logs including credentials, database URLs, tokens, or excessive player data; -- YAML keys or section paths derived from lower-trust input altering a different subtree than intended. - -Creating a file at an administrator-selected path is trusted behavior. Path traversal becomes security-relevant when the name/path crosses from a lower-trust boundary. - -## Rewards and command execution - -Configured reward commands are intentionally privileged. - -The dangerous boundary is selection and substitution: can lower-trust data choose a reward/command, change its arguments, or become executable syntax? - -Review string reward shortcuts, nested/random/choice rewards, directly defined rewards, offline/retry rewards, world-branch rewards, delayed rewards, reverse/negated requirements, per-player and bulk rewards, and placeholders inserted into console/player commands. - -Search for: - -- player/vote data selecting an arbitrary reward name; -- a lower-trust string being reinterpreted as a console-command shortcut; -- multi-pass placeholder expansion where substituted data becomes a second placeholder/token; -- requirement failure incorrectly queuing a reward for later claim; -- branch rewards being saved for worlds or conditions not selected at trigger time; -- duplicate execution after reconnect, retry, callback duplication, or scheduler rejection; -- reward effects occurring before a limit/debit is durable. - -Do not flag operator-authored console commands merely because they are powerful. - -## Commands, GUIs and permissions - -Command framework registration is not enough; authorization must still be correct where side effects happen. - -Review admin/editor commands, GiveAll/bulk operations, SetData, RunSQLQuery, JavaScript, reward editing, user-targeting commands, and GUI callbacks. - -High-value cases include: - -- self-vs-other-player permission confusion; -- special values such as all widening scope without explicit bulk permission; -- console/player sender assumptions; -- a GUI rendered with permission but clicked after permission/config changes; -- stale GUI state applying to the wrong player, path, or reward after reload; -- overlapping click callbacks duplicating effects; -- hidden/admin-only buttons whose callback lacks an independent permission check; -- command aliases reaching a less-protected handler. - -Current inventory behavior has explicit scheduler semantics. Do not report async callbacks generically. Show the concrete Bukkit/Folia API or stateful operation that becomes unsafe, the reachable click path, and the integrity or availability effect. - -## Identity, permissions and offline players - -Minecraft online usernames are normally constrained, but AdvancedCore is also used in offline, proxy, and Bedrock environments. - -Review exact-name/UUID lookup, Bedrock prefixes, case normalization, cache mappings, offline permission checks, pre-authentication integrations, and permission requirements on delayed/offline rewards. - -Search for: - -- Java and Bedrock identities sharing a normalized base name; -- case collisions in offline UUID generation; -- fallback lookup selecting the wrong stored account; -- untrusted prefix-only Bedrock names treated as authenticated identities; -- reverse/negated permissions behaving incorrectly for offline users; -- pre-login events or rewards firing before an authentication plugin confirms ownership; -- stale permission snapshots granting rewards after permissions change. - -Require a concrete account, reward, or permission effect. - -## Placeholder and JavaScript boundary - -Placeholder parsing must preserve literal data and avoid unintended second evaluation. - -Search for: - -- player/provider output inserted and then reparsed as PlaceholderAPI or script; -- escaping/parity bugs turning inert percent/backslash text into executable tokens; -- attacker-selected placeholder identifiers causing unbounded cache/state or expensive work; -- script bindings leaking between users/evaluations; -- persistent globals in a shared script engine changing later evaluation behavior; -- concurrency around a shared engine or binding cleanup; -- JavaScript error/log paths exposing sensitive objects or data. - -JavaScript is administrator-authored privileged code and is not a sandbox. If enabled and supplied with Bukkit, AdvancedCore, or console objects, broad power is intentional. The security issue is an authorization/input crossing into script execution, not the script's power itself. - -## Cross-server and messaging - -AdvancedCore can sit underneath systems that receive plugin messages, Redis/MQTT/socket traffic, or shared database state. - -Do not assume a message is trusted merely because it arrived through an internal helper. When AdvancedCore itself authenticates or encrypts a transport, test the actual guarantee. When authentication belongs to the consuming plugin, avoid inventing a missing AdvancedCore security contract. - -Look for source/origin confusion, replay/duplicate callbacks, one server overwriting another's state without version/ownership checks, shared global state allowing a compromised backend to trigger privileged changes, encryption treated as authorization, and malformed payloads crossing into generic config/command helpers. - -## Resource exhaustion and lifecycle - -Tainted persisted data can be as dangerous as live player input. - -Prioritize: - -- one scheduled task per database/reward row without a useful bound; -- all-user scans/sorts on the server or region thread; -- large YAML/list/map recursion; -- unbounded GUI button creation; -- cache cardinality controlled by player-selected keys; -- retry loops after persistent database failure; -- executor queues with no admission bound; -- cancellation/reload leaving stale callbacks alive; -- repeated malformed data causing log amplification. - -Reload/disable must retire old executors, listeners, timers, caches, and callbacks so stale work cannot mutate replacement state. - -## Logging, secrets and supply chain - -Do not log database passwords, credential-bearing URLs, tokens, private keys, full sensitive configuration, or raw lower-trust control characters. - -CI findings are security-relevant when untrusted PR code receives write-capable credentials or can influence trusted release artifacts. Mutable action tags and dependency policy should be calibrated to actual privileges and the release path rather than reported as runtime RCE. - -## High-value attack stories - -1. A downstream plugin stores player-controlled data keys and values across SQLite, MySQL/MariaDB, and PostgreSQL; compare all syntax/value boundaries. -2. Two async operations mutate the same user balance while reload or cache eviction occurs. -3. A lower-trust reward name reaches every reward load/create/edit path using traversal, separator, and Unicode variants. -4. A reward with multiple conditional branches fails one requirement; verify failed branches cannot be claimed later unless explicitly designed. -5. Two GUI clicks overlap around a debit, reward, or config write and test duplicate effects. -6. An offline or Bedrock identity collides with another stored user and triggers a permission/reward decision. -7. Player/provider placeholder output contains another placeholder or script token and crosses every formatting layer. -8. Two users execute JavaScript-backed evaluation concurrently and sequentially; verify bindings/globals do not cross unexpectedly. -9. A large tainted DB/YAML dataset triggers timers, GUI rendering, or scans; verify work is bounded and off-thread. -10. Reload/disable occurs with queued user writes, delayed rewards, inventory callbacks, and scheduler tasks in flight. - -## Scan calibration and severity - -Critical: ordinary-player or unauthenticated input reaches arbitrary console command execution, JavaScript/JVM execution, arbitrary host/plugin file write, or SQL syntax capable of modifying unrelated data. - -High: permission bypass to privileged/bulk operations; lower-trust path traversal with meaningful file access; cross-server spoofing causing privileged effects; repeatable reward/economy duplication; identity confusion causing significant reward/permission theft; persistent resource exhaustion with practical server outage. - -Medium: prerequisite-heavy reward/identity integrity failures; bounded scheduler/DB DoS; meaningful secret leakage to limited readers; placeholder/script injection with configuration-dependent effects; threading races causing occasional duplicate/lost effects. - -Low: trusted-admin footguns, malformed trusted config, generic public API misuse requiring a malicious installed plugin, minor log injection, or build hardening without a privileged token/artifact path. - -Usually not security by itself: API/ABI compatibility regressions, operator-authored SQL/commands/scripts, ordinary malformed YAML exceptions, cosmetic GUI issues, or a thread-policy concern without a concrete reachable side effect. - -For every finding identify the lower-trust source, public/helper path, final privileged sink, current mitigation that failed, and whether current master still exposes the issue. diff --git a/docs/shared-platform-services.md b/docs/shared-platform-services.md deleted file mode 100644 index cecd86e5c4..0000000000 --- a/docs/shared-platform-services.md +++ /dev/null @@ -1,92 +0,0 @@ -# Shared platform services - -The `core.platform` package now has minimal `PlatformServices`, `PlatformPlayer`, -and `PlatformScheduler` contracts. `BukkitPlatformServices` adapts the existing -plugin-owned SimpleAPI Bukkit/Folia scheduler; it does not create another runtime, -executor, service registry, persistent user, or player cache. - -## Native consumers - -Shared code receives `PlatformServices` through its constructor. The Bukkit entry -point supplies `new BukkitPlatformServices(() -> plugin)`; a future loader supplies -its own implementation. Construction does not resolve the plugin or touch Bukkit. -There are no Fabric, Forge, or NeoForge implementations or new artifacts yet. - -```java -platform.scheduler().runPlayer(playerId, player -> { - if (player.hasPermission("example.message")) { - player.sendMessage("Already-rendered message"); - } -}); -platform.scheduler().runServer(() -> platform.dispatchConsoleCommand("say hello")); -``` - -`findOnlinePlayer(UUID)` is a read-only online-session lookup, not offline UUID/name -resolution or an `AdvancedCoreUser` lookup. It does not invoke Vault, offline -LuckPerms checks, SQL, or FLAT storage. The player permission method checks one -literal native node and does not add prefixes, split alternatives, or grant OP. -Existing AdvancedCore permission helpers and their provider/fallback rules remain -unchanged. - -Player operations must run on that player's owning game thread. A synchronous -lookup does not grant thread ownership; asynchronous consumers should use -`runPlayer`. Submission only locates the entity by UUID; it does not read player -state such as `isOnline()` on the caller's thread. Online/session checks run inside -the captured entity's scheduler callback, not on the global or location scheduler. -If that login disconnects, is replaced, or the plugin is disabled before the -callback, it will not execute the action. A stale handle -returns false for permission checks and rejects messages/commands; it does not -look up a replacement login and redirect the operation. - -Server-wide callbacks must not access region-confined worlds or players. Console -command dispatch belongs in that context, but called commands are still responsible -for their own Folia safety. Raw message/command methods do not expand placeholders, -color codes, JSON, or JavaScript. `sendMessage` is the basic native text transport, -not a replacement for AdvancedCore's rich-message renderer. - -## Scheduling contract - -`runServerLater` uses **seconds**, matching SimpleAPI's existing `runTaskLater` -overload. It is not a tick count and does not guarantee a wall-clock deadline. -Negative delays are rejected. `runServer` uses the existing `runTask` handoff. -`runPlayer` never supplies a null entity to SimpleAPI (which would fall back to -global execution). The plugin's existing scheduler owns shutdown cancellation; -no parallel lifecycle or background work is introduced here. - -A false `runPlayer` result means UUID lookup found no player to schedule. A true -result means submitted, **not completed** or confirmed online. Disconnect/retirement -or shutdown can prevent callbacks from running. Rejection and callback exceptions -are not swallowed. These methods provide no durable completion acknowledgement, -retry policy, persistence, timeout, or exactly-once guarantee; do not use their -return values to checkpoint rewards. Ordered asynchronous reward execution and -its completion boundaries remain separate work in PR #317. - -## Existing production integration - -Only the private legacy `MiscUtils.runConsoleCommand` implementation delegates to -`ConsoleCommandDispatcher`, using a lazily resolved Bukkit adapter. Existing public -command-list helpers exercise the new shared scheduler and command service now. - -The caller's placeholder/script parsing, logging, list iteration, and public -signatures are unchanged. The shared dispatcher removes exactly one leading slash -and preserves the old stagger branch: a positive delay uses the delayed scheduler -only when staggering is enabled. All other cases use `runTask`. It does not filter -null/empty results, re-render text, or turn scheduled submission into completion. -Single-command overloads, the separate slash helper, and async reward methods are -not changed. PR #317 also touches `MiscUtils`; preserve its async additions when -combining branches, and rerun both suites. - -## Validation - -The existing command remains `mvn -B -f AdvancedCore/pom.xml package` on Java 21. -Tests cover core command policy in a JDK-only fixture and isolated classloader, -Bukkit UUID lookup/native dispatch, entity-versus-global routing, stale sessions, -disable, failures, lazy ownership, worker-thread submission with no early player -state reads, already-disconnected submissions, and the existing command-list facade. -No new workflow, module, dependency pin, or test skip is introduced. SimpleAPI remains -`1.0.2-SNAPSHOT`; record the actual resolved snapshot when validating downstream. - -Before release, build SimpleAPI, AdvancedCore, and VotingPlugin against the actual -candidate artifacts and test the packaged plugin on Bukkit/Paper and Folia. -Mocked scheduler routing and core classloader tests are not live-server or -packaged-native-mod validation. diff --git a/docs/shared-reward-configuration.md b/docs/shared-reward-configuration.md deleted file mode 100644 index 7dd738ded3..0000000000 --- a/docs/shared-reward-configuration.md +++ /dev/null @@ -1,71 +0,0 @@ -# Shared reward configuration reads - -`core.rewards.RewardConfigReader` interprets reward settings through SimpleAPI's -`StructuredConfigView`. It does not open files, create users, schedule work, or -execute rewards. The supplier is evaluated on each read so a replaced root can -be observed without rebuilding the reader; the caller owns synchronization. - -## Existing Bukkit callers - -`RewardFileData` retains its constructors, public method signatures, native -section getters, file handling, setters, and generated-snapshot behavior. Its -portable setting getters delegate to `BukkitRewardConfigReader`, which reads -through the existing virtual `getConfigData()` method. Nothing is resolved during -construction. Subclasses, `setConfigData`, and file reloads keep their current -behavior, including the existing distinction between constructor and reload -case handling. - -The Bukkit adapter preserves implicit default-tree getters and raw list identity. -The existing `ArrayList` casts remain, including their historical failure -for other list implementations. This extraction does not silently filter native -objects, coerce command entries, or change list mutability. Native display/item -sections stay in the existing Bukkit API. Reward-derived permission defaults also -remain in that facade; native readers can supply a reward name explicitly. - -## Native readers - -```java -StructuredConfigView view = new ConfigurateStructuredConfigView(node); -RewardConfigReader settings = new RewardConfigReader(() -> view); -List consoleCommands = settings.getCommandsConsole(); -boolean delayed = settings.getDelayedEnabled(); -StructuredConfigView nested = settings.definitionAt("Choices", "daily.bonus", "Rewards"); -``` - -Use imports from `com.bencodez.advancedcore.core.rewards` and -`com.bencodez.simpleapi.core.config`. The supplied view controls casing, paths, -and typed conversion rules; use the appropriate SimpleAPI case-insensitive view -when required. `definitionAt` uses literal keys, so names containing dots are not -split. The named choice/item helpers retain the existing dotted-path convention. - -Plain-data lists from the neutral reader are detached and unmodifiable, retain -entry order and value types, and use SimpleAPI's bounded exports. Unsupported -native objects are rejected by that export contract. This is intentionally not -an emulation of Bukkit's native object/list identity. No command is executed by -reading it. Empty/malformed non-list settings return an empty list. - -## Dependencies and validation - -This source requires SimpleAPI's merged structured configuration APIs (SimpleAPI -PR #78 and its follow-ups): `StructuredConfigView`, `BukkitStructuredConfigView`, -and `ConfigurateStructuredConfigView`. The dependency uses `1.0.2-SNAPSHOT` so -builds can consume newer published snapshots from Nexus. The existing repository -configuration enables snapshots with `updatePolicy` set to `always`. - -The SimpleAPI timestamp-pinning test has been removed at the maintainer's request. -The separate Javadoc workflow security test remains unchanged. When validating, -record the timestamped SimpleAPI artifact Maven actually resolves; a successful -build with one snapshot does not validate later snapshots automatically. - -Tests cover portable setting/nested-definition behavior, an isolated runtime -that rejects Bukkit and JUnit, native Bukkit defaults and list semantics, -replacement/reload/subclass compatibility, and real Bukkit/Configurate parity. -Use the repository's normal Maven package command and verify these tests actually -run; a successful invocation with zero discovered JUnit tests is not validation. -A downstream VotingPlugin build must resolve the exact candidate AdvancedCore -and SimpleAPI artifacts, not unrelated cached snapshots. - -This is one Maven project with `core` and `bukkit` packages. It does not add a -shared AdvancedCore artifact, a loader implementation, or a new workflow. -Storage/FLAT removal, user extraction, reward execution (including PR #317), and -native mod packaging remain separate work. PR #314 is not reopened. diff --git a/docs/shared-runtime-foundation.md b/docs/shared-runtime-foundation.md deleted file mode 100644 index b864ef6136..0000000000 --- a/docs/shared-runtime-foundation.md +++ /dev/null @@ -1,200 +0,0 @@ -# Shared runtime foundation - -AdvancedCore remains one Maven project with one existing POM. New shared runtime -mechanics live under `core/runtime` and their platform contract under -`core/platform`; Bukkit cleanup hooks live under `bukkit/runtime`. No modules, -workflow files, dependency versions, publishing changes or game-loader stubs are -introduced. - -The existing `AdvancedCoreLifecycle` facade now delegates its executor creation -and shutdown sequence to `core.runtime.AdvancedCoreRuntime` through -`BukkitRuntimePlatform`. AdvancedCorePlugin's current enable/disable calls already -use that facade, so Bukkit uses the extracted code immediately. There is no -parallel runtime with different caches or a second set of background tasks. - -## Preserved contracts - -- The facade constructor, nested RuntimeExecutors constructor and getters retain - their signatures and return the same executor/scheduler objects. -- General, login and inventory work retain separate single-thread executors. -- BukkitScheduler/Folia behavior is unchanged; game/entity/region scheduling is - not redirected to these background executors. -- JavaScript cleanup still happens before executor shutdown. Native providers - close only after their storage worker and admitted preparation settle. -- Executor grace periods remain at most 2/2/2/1 seconds, followed by reward shutdown, - forced executor shutdown and the existing unload/cache/inventory/integration - cleanup order. The time-checker timer is captured at the same point. -- Individual component failures retain the warning/debug callback behavior and - do not skip later components. Interrupted waits restore the interrupt flag. -- Lifecycle calls remain serialized by the owning platform. Consumer hooks and - game/integration cleanup retain their lifecycle-thread execution. - -## Shared shutdown deadline - -All owned shutdown waits share one monotonic 15-second deadline captured at entry -into `AdvancedCorePlugin.onDisable()`. Pre-storage preparation, executor grace, -delayed rewards, forced-worker grace, and deferred watchdogs consume the same -remaining budget; a late preparation completion cannot start another grace -period. The public `RewardHandler.shutdown()` keeps its ten-second maximum for -standalone callers, while lifecycle shutdown supplies only its remaining budget. - -Final inventory, server timestamp, and timed-permission mutations are admitted in -order on one bounded daemon persistence worker. Each publication stage settles -before its successor snapshot starts, including asynchronous adapters. Handler -admission is fenced immediately, and the worker remains available for ordered -successors until all final stages settle. `ServerData.mutateFinalData` -returns the actual file owner's publication stage. Native and completion-aware -final mutations are staged in a private YAML candidate. Native live YAML retains -a replacement that has become visible even if its directory sync then fails; -visibility alone does not acknowledge its reward receipt. -Native ServerData forces that candidate on the shutdown worker; an ordered -adapter serializes under its own YAML monitor and confirms its existing publisher. -Later unrelated saves cannot publish a failed final overflow mutation. Ordered -snapshots carry only successfully published final changes forward and preserve -newer explicit edits, including an edit that writes the same previous value. -No caller waits for publication while holding the YAML owner's monitor. Legacy -live read/mutate/save transactions use `ServerData.publicationMonitor()`, whose -native default is the file object itself. A facade must return its backing -owner's stable monitor and delegate YAML access, mutations and publication to -that same owner; overriding the monitor alone does not reroute inherited native -methods. Overflow-section replacement and recovered time-transition validation -use this contract so ordinary or final whole-file snapshots cannot interleave -with those compound live operations. This lock does not turn a queued or void -save into a durable acknowledgement and must never be held while awaiting an -asynchronous publication stage. - -Legacy -void `saveData` overrides retain the established live-YAML mutation followed by -one save callback, but cannot claim confirmed publication without overriding the -additive completion contract; they report an unsupported confirmation. A legacy -callback failure remains observable, with no second mutation or save invocation. -Its live changes retain the old non-transactional behavior; candidate isolation -remains specific to native and completion-aware owners. For a known visible -native replacement, one immediate retry may settle only its retained directory -sync and cleanup, without repeating the mutation or move. Confirmation settles -the original publication successfully. If that retry fails, its stage fails and -the owner rejects subsequent saves, final mutations, reload and setup; later -cleanup cannot silently commit unacknowledged overflow. This fence does not -remove the separate file/receipt crash window or promise replay safety after an -uncertain restart. Failures before replacement retain their existing recovery -behavior. Inventory persistence gates user-storage -retirement and cannot acknowledge reserved replay overflow before its actual -save succeeds. The public synchronous handler shutdown methods remain available. -The shutdown-completion hook follows submission of final file saves; it is not a -durability acknowledgement. Its additive `onShutdownComplete(finalFilesStage)` -overload receives completion of all final file publications and invokes the -existing zero-argument hook on the same lifecycle thread by default. A consumer -retains its file owner until both pre-storage preparation and final-file stages -settle, including failure, then closes that owner. Worker termination must not be -part of either stage, which would create a completion/termination cycle. - -Owned background executors and shutdown continuations use daemon threads so an -interrupt-ignoring JDBC call or filesystem operation cannot retain the JVM. A -timeout is reported while the actual stage stays pending. The native storage -owner remains open until its executing storage worker ends; accepted final file -saves are not discarded to meet the deadline. Preparation that settles after -or just before the deadline still admits its final checkpoint and retains the -daemon worker until that accepted retirement actually settles, without renewing -any caller wait. The runtime distinguishes an accepted native retirement from a -pending time transition or consumer hook; its watchdog cannot discard that -retirement's queued final task. -Durable completion can outlive plugin disable. While admitted shutdown work is -pending, a JVM hook registered with native file admission during startup gives -its existing owners the remainder of the same 15-second deadline during orderly -process exit, including disable inside an already-running host shutdown hook. -The hook performs no file writes or Bukkit calls and does not renew the deadline. -It waits at most 15 seconds from process exit as well, even if the host has not yet -started plugin disable. Successful ownership release or the disable deadline -removes it during a reload; -failed publication still retains the original failure and native admission fence. -An exit after the deadline, a hard process kill, or failure to register the hook -can still lose unfinished work. A timeout never means persistence succeeded. - -The 15-second limit bounds AdvancedCore's waits. Synchronous consumer methods -(`onBeforeStorageShutdown`, `onUnLoad`, `onShutdownComplete`) and third-party -Bukkit/integration cleanup must return promptly: Java cannot safely preempt them -on the lifecycle thread. The temporary process hook waits only for already-owned -shutdown stages and never scans threads or calls other plugins. AdvancedCore -cannot guarantee a total server-stop time when another plugin or one of these -callbacks blocks. - -Consumer `onBeforeStorageShutdown()` stages settle before shared user storage -retirement begins. A thrown exception, exceptional completion, or cancellation -still starts shared-cache retirement and waits for that retirement to settle. -The preparation failure remains the reported cause; a storage failure is retained -as a suppressed failure. Successful default hooks preserve the existing retirement -stage identity. A failing hook does not bypass accepted user-data flushing. - -Delayed-reward admission closes before storage preparation. The additive -`RewardHandler.beginShutdown()` stage reports actual delayed-worker termination; -the existing bounded shutdown methods still cancel queued timers and interrupt -the worker when their wait ends. An interruption request does not release native -storage while an admitted check continues running. After that worker exits, a -manager-worker barrier includes checks it deferred under Spigot/Folia thread -classification and snapshots the existing per-user persisted replay tails. -Those tails include claim/checkpoint acknowledgement or restore callbacks that -can outlive timer execution. Final storage retirement is queued only after those -callbacks finish, so callback-held storage admission can unwind before the final -writer runs. Failed or cancelled preparation and time retirement retain this -ordering and still report their failure. This uses the existing replay owner; -it adds no reward engine or universal delivery guarantee. - -## Native ServerData successor admission - -Enable acquires native `ServerData.yml` admission before `onPreLoad()` and the -native load hook. Calling `loadHook()` directly acquires the same admission before -constructing the file owner. A successor using the same normalized absolute -platform data path fails immediately while its predecessor owns admission; it -does not wait for the predecessor or canonicalize/read the filesystem on that -admission path. Retrying enable after successful predecessor retirement is safe. -Repeated disable calls cannot submit new final writes after admission is released, -and a completed shutdown allows the same instance to acquire a fresh generation -where its existing consumer lifecycle supports re-enable. - -Shaded copies share a bounded JVM Properties namespace with at most 256 opaque -String tokens. No plugin, classloader, future or callback is stored there. Each -owner releases from the exact Properties object into which it acquired its token. -The key uses a digest of the lexically normalized platform path, without file -contents. Installed plugins must preserve the JVM Properties ownership entries -and use the same platform data path; alias paths through symlinks and replacing -the JVM Properties map are not an interprocess or filesystem-lock protocol. -Separate JVMs are outside this admission guarantee. - -Successful release requires confirmed final inventory/timestamp/permission -publication, consumer preparation, user-storage retirement, persistence-worker -termination and successful return from the unload/completion hooks. It also -requires actual termination of the native executor identities captured before unload can replace -their fields. A daemon continuation observes that termination without extending -the caller's 15-second budget or blocking the server thread. Partial startup with -no admitted native writer releases after successful cleanup and retirement of -its created executors. - -An exceptional or cancelled preparation/publication stage is different from an -owner-drain acknowledgement: it can settle while older admitted native writes -still run. Existing adapters cannot prove complete drain after that outcome, so -the opaque token is conservatively retained until JVM restart, even if another -final save subsequently succeeds or the observed executors later exit. The -successor continues to fail closed. A pending successful save or preparation may -release when it genuinely completes; a timeout warning alone is not failure or -permission to discard the token. No manual token removal is a supported recovery -path while the JVM may still contain predecessor writers. Completion-stage -adapters must include all earlier admitted writes in their successful publication -boundary and may not report mere task submission as completed publication. - -## What this does not port yet - -This is the executor/lifecycle slice of the future shared runtime, not a complete -Bukkit-free AdvancedCore service graph. Users, settings, storage, rewards, game -operations and integration providers are extracted in subsequent changes. Native -packaging must only include dependency-clean classes once that graph is ready; -this PR does not tell a Fabric server to load the full existing AdvancedCore JAR. - -## Validation - -Use the existing `mvn -B -f AdvancedCore/pom.xml package` command. Existing Bukkit -lifecycle tests remain in place. New tests compare the cleanup/executor ordering, -timeouts, failure handling, object identity and interrupt behavior. A headless -fixture creates real executor owners, runs a task, shuts down and checks rejection -of new work while Bukkit/JUnit are absent from its isolated classpath. - -No live-server or downstream VotingPlugin validation is implied by those tests. diff --git a/docs/shared-sql-user-access.md b/docs/shared-sql-user-access.md deleted file mode 100644 index 73ae6a805e..0000000000 --- a/docs/shared-sql-user-access.md +++ /dev/null @@ -1,115 +0,0 @@ -# Shared SQL user access - -This follows removal of FLAT user storage. `core.user.storage.SqlUserStorage` -is a game-independent, per-user SQL access contract, and `SqlUserDataAccess` -implements row interpretation and typed reads/writes against it. A native host -can supply a storage implementation without an `AdvancedCorePlugin` instance. - -`BukkitSqlUserStorage` reuses the existing MySQL and SQLite user-table providers. -It resolves the plugin, provider, and UUID lazily at operation time. It neither -constructs connections nor owns a cache, executor, identity resolver, schema, or -shutdown lifecycle. Shared access performs no game-thread dispatch. SQL calls -may block and must remain on the caller's appropriate storage execution context. - -The headless `SqliteUserBackend` deliberately does not shade another SQLite driver -into AdvancedCore. Bukkit/Paper/Folia deployments use the server runtime's -`org.sqlite.JDBC`; a future Fabric/Forge/NeoForge host must provide `sqlite-jdbc` -as a loader/runtime dependency (or download it in that loader's dependency phase) -before constructing the backend. The test-scoped Maven dependency exists only so -headless persistence tests can run and must not be interpreted as runtime bundling. - -The existing `UserData` facade delegates its SQL operations to this access layer. -Its temporary-cache field and accessors, six fetch modes, user-cache precedence, -cache updates, notifications, list encoding, and synchronous/asynchronous write -scheduling stay in place. Row reads still dispatch through the virtual -`getMySqlRow()` / `getSQLiteRow()` methods, and `getValues` still calls the virtual -`convert` method. Reload/replacement does not leave the adapter holding old tables -or an eagerly captured UUID. No new cache or queue is introduced. - -The port deliberately preserves existing provider behavior, including the -MySQL missing-provider bulk-write no-op and SQLite cumulative per-entry bulk -updates. It does not claim that void-returning provider methods establish durable -completion, atomic batches, or successful disk commits. Async reward completion -and checkpoint work in PR #317 remain separate; do not substitute this interface -for that work's completion contract. - -## Scope and validation - -This is the SQL-access boundary, not the complete native user runtime. Existing -Bukkit-facing provider construction, SQLite connection/table bootstrap, schema -registration, `UserManager` enumeration, and user-cache lifecycle still need -native wiring. The current full AdvancedCore JAR is not a Fabric/Forge/NeoForge -artifact. No alternate database implementation or duplicate JDBC stack is added. - -Tests cover typed/default/case behavior, live rows, subclass hooks, map identity, -backend delegation, bulk call ordering, lazy ownership, all existing fetch modes, -queued writes, asynchronous identity resolution, deletion/rejection failures, and -isolated shared-class loading. The headless fixture uses an in-memory port; it is -not a database integration test. Preserve existing SQL tests as well. - -One Maven project, existing package layout, SimpleAPI `1.0.2-SNAPSHOT`, no new -workflow or dependency pin. Merge the FLAT-removal prerequisite first, reconcile -#317's overlapping user/cache work, and validate the exact candidate through -AdvancedCore and VotingPlugin builds plus packaged database/server tests. - -## Direct SQL mutation and published snapshots - -An integration that commits a direct SQL change must not remove a column from -`UserDataCache.getCache()` while leaving the snapshot published. That can turn a -missing `Points` value into an apparent zero on a platform-thread read. - -Use `UserDataCache.invalidateStorageSnapshot(String... keys)` to invalidate the -changed values and their publication atomically. This cache-local operation does -not flush pending writes or perform SQL. It preserves unrelated queued values and -advances the snapshot generation so an older in-flight storage read cannot -republish the removed value. Default shared-user reads fail explicitly while the -required value is unavailable instead of consulting JDBC on a platform thread. - -Arrange an authoritative refresh through `UserDataManager.cacheUser` on the -existing storage worker, outside the cache monitor. A completed mutation should -refresh before publishing its completion where that API promises refreshed reads. -If storage is unavailable, leave publication invalid and report the refresh -failure. Cache-only/no-database fetch modes retain their explicit default-return -contract. This is snapshot coherence, not a guarantee that another backend cannot -commit a later update or that external side effects share the SQL transaction. - -## SQL acquisition failures - -The headless MySQL/MariaDB/PostgreSQL user backend and Bukkit user-table SQL -paths acquire connections with SimpleAPI's additive `getConnectionChecked()`. -A pool timeout reaches the existing SQL failure handling with its original -`SQLException` cause instead of dereferencing a null connection. Shared user -operations wrap that cause in `IllegalStateException`; failed acquisition never -enters the transaction callback, replaces the pool, or closes the backend. -Operations may be retried after a lease is released. The Bukkit PostgreSQL UUID -existence lookup also propagates acquisition and query failures with their cause; -only a successful lookup without a row means absent. Malformed UUID input still -returns false before acquiring a connection, and cached identities remain local. - -The public legacy SimpleAPI `getConnection()` retains its nullable failure -contract for existing callers. Rebuild both AdvancedCore and shaded VotingPlugin -consumers against SimpleAPI containing merged PR #106 before validating the -packaged storage API. - -The Bukkit shared MySQL backend uses `getExactStrict` and `deletePlayerStrict` -to surface acquisition and query failures with their original cause. A failed read -cannot publish a schema-only/default snapshot, and a failed delete cannot remove -identity or runtime caches. Existing `getExact`/`getExactQuery` callers retain -the legacy read fallback; legacy deletion also leaves caches intact on SQL failure. - -## Spigot shutdown worker ownership - -The user-data manager retains the exact identity of its private storage executor -thread. That worker remains a storage lane during server shutdown, even when -Spigot's primary-thread probe reports every thread as primary after the server -has stopped. Shared-cache retirement still runs asynchronously on this worker. -All other threads retain the existing Bukkit and Folia tick-thread checks; -matching a worker name or disabling a plugin does not bypass them. This needs no -configuration or data migration and does not change the public retirement API. - -Shutdown uses one 15-second monotonic budget from plugin-disable entry, including -the deferred-storage watchdog and forced-worker waits. A driver that ignores an -interrupt keeps its retirement stage pending and its native provider owned until -the actual worker exits. Owned workers are daemon threads; deadline expiry never -reports an unfinished flush as successful. See the shared runtime foundation -contract for final-file persistence and synchronous consumer-hook limitations. diff --git a/docs/sql-only-user-storage.md b/docs/sql-only-user-storage.md deleted file mode 100644 index c730fad214..0000000000 --- a/docs/sql-only-user-storage.md +++ /dev/null @@ -1,24 +0,0 @@ -# SQL-only user storage - -User storage now supports `SQLITE` and `MYSQL` only. `FLAT` is removed from the -enum, normal reads/writes, enumeration, column operations, startup, and deprecated -per-player file helpers. Existing `Data/*.yml` files are neither deleted nor -converted by this change. Ordinary YAML configuration and generic file utilities -remain available. - -An explicit `DataStorage: FLAT` setting fails with an explanatory error instead -of silently starting with an empty SQL database. Complete conversion with the -existing conversion commands in the previous version before upgrading, then -select the populated `SQLITE` or `MYSQL` backend. No new migration, automatic -storage switch, schema change, or rewrite of the existing SQL converter is added. -SQL-to-SQL conversion remains available through `convertDataStorage`. - -This deliberately removes the deprecated `UserStorage.FLAT` enum constant, -`UserData.getData(String)`, and the per-player file CRUD methods on -`FileThread.ReadThread`. Integrations still calling those FLAT-only APIs must -migrate before upgrading. SQL-facing public APIs, fetch modes, cached changes, -write scheduling, serialized values, and reward queues are unchanged. - -The follow-up SQL abstraction builds on this removal. Coordinate both branches -with changes to user data/cache code in PR #317; this removal does not merge or -modify that PR. SimpleAPI remains `1.0.2-SNAPSHOT` in the single Maven project.