Compare commits
86
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1af26cb9a0 | ||
|
|
805e2e7e6e | ||
|
|
cfb49ed896 | ||
|
|
e71a8daa8d | ||
|
|
2f30712004 | ||
|
|
28640f3203 | ||
|
|
7e36b7ef95 | ||
|
|
df5cc0eda7 | ||
|
|
0ec22d5215 | ||
|
|
7aab307381 | ||
|
|
1ab439b578 | ||
|
|
2dce841cf4 | ||
|
|
cc7003ac94 | ||
|
|
59c5c62170 | ||
|
|
f1f31ab8f0 | ||
|
|
d057b0f3e6 | ||
|
|
e283f0b8f2 | ||
|
|
00a7f13c6f | ||
|
|
49df1ac9d0 | ||
|
|
152137ed87 | ||
|
|
adb5f0235a | ||
|
|
b9369a870f | ||
|
|
9c636bc93d | ||
|
|
035a01c48c | ||
|
|
f1dd5ed9b1 | ||
|
|
b6a6d544b5 | ||
|
|
681b6feef1 | ||
|
|
d3315900b9 | ||
|
|
ddbf6f047d | ||
|
|
461044a998 | ||
|
|
a62bd5df98 | ||
|
|
5bff358d73 | ||
|
|
ed952fd6b1 | ||
|
|
0cb125ca9d | ||
|
|
7b1f6d18eb | ||
|
|
f5fc5bb3b5 | ||
|
|
a252c256c4 | ||
|
|
b230e15ece | ||
|
|
f29013e9c2 | ||
|
|
c645ba34eb | ||
|
|
7d1557932d | ||
|
|
c70be47c27 | ||
|
|
f5a2183a40 | ||
|
|
3d5de0e09b | ||
|
|
961f8c69cb | ||
|
|
df504e29bf | ||
|
|
b33db6da49 | ||
|
|
b86ec37bb1 | ||
|
|
cb2d3e2a16 | ||
|
|
e7f551b8dd | ||
|
|
5a096379f2 | ||
|
|
b429a61237 | ||
|
|
21bd57ae7a | ||
|
|
30cfcfc4ff | ||
|
|
d5b733bc6f | ||
|
|
cac555159d | ||
|
|
bc936e029d | ||
|
|
ef1b5e0adc | ||
|
|
0db74b3296 | ||
|
|
d8dc494afa | ||
|
|
d6b765e9a5 | ||
|
|
fdefba91ad | ||
|
|
493a6291a9 | ||
|
|
6ad5ad0924 | ||
|
|
d946508214 | ||
|
|
d9e0afeff9 | ||
|
|
3f5445e072 | ||
|
|
28844bb6db | ||
|
|
2177722fd9 | ||
|
|
da72aa4458 | ||
|
|
70fbb793ec | ||
|
|
b9741d09fd | ||
|
|
847c33000a | ||
|
|
a65a9ed043 | ||
|
|
8189b16516 | ||
|
|
8f6aac025c | ||
|
|
6c48e3741a | ||
|
|
2a64809d97 | ||
|
|
b4b087cb3a | ||
|
|
4ffc9a75ae | ||
|
|
bfda6f01b7 | ||
|
|
40ac60acd1 | ||
|
|
1bde6c8152 | ||
|
|
1d63cdc026 | ||
|
|
4b913d4c83 | ||
|
|
280024c340 |
@@ -0,0 +1,185 @@
|
|||||||
|
---
|
||||||
|
name: openspec-apply-change
|
||||||
|
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
|
||||||
|
allowed-tools: Bash(openspec:*)
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.7.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Implement tasks from an OpenSpec change.
|
||||||
|
|
||||||
|
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
|
||||||
|
|
||||||
|
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Select the change**
|
||||||
|
|
||||||
|
If a name is provided, use it. Otherwise:
|
||||||
|
- Infer from conversation context if the user mentioned a change
|
||||||
|
- Auto-select if only one active change exists
|
||||||
|
- If ambiguous, run `openspec list --json` to get available changes and ask the user to select one
|
||||||
|
|
||||||
|
Always announce: "Using change: <name>" and how to override (e.g., `$openspec-apply-change <other>`).
|
||||||
|
|
||||||
|
2. **Check status to understand the schema**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>" --json
|
||||||
|
```
|
||||||
|
Parse the JSON to understand:
|
||||||
|
- `schemaName`: The workflow being used (e.g., "spec-driven")
|
||||||
|
- `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints
|
||||||
|
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
|
||||||
|
|
||||||
|
3. **Get apply instructions**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openspec instructions apply --change "<name>" --json
|
||||||
|
```
|
||||||
|
|
||||||
|
This returns:
|
||||||
|
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
|
||||||
|
- Progress (total, complete, remaining)
|
||||||
|
- Task list with status
|
||||||
|
- Dynamic instruction based on current state
|
||||||
|
- Optional `context`: current required project instruction input from the selected root
|
||||||
|
- Optional `operationGuidance`: current advisory guidance for apply
|
||||||
|
|
||||||
|
**Handle states:**
|
||||||
|
- If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change (if it is not installed, run `openspec status --change "<name>" --json` to see the next artifact and `openspec instructions <artifact-id> --change "<name>" --json` for how to create it)
|
||||||
|
- If `state: "all_done"`: congratulate, suggest archive
|
||||||
|
- Otherwise: proceed to implementation
|
||||||
|
|
||||||
|
Treat `context` as a required prompt-level input. Read and consider it, and
|
||||||
|
apply relevant project facts, conventions, and constraints while implementing.
|
||||||
|
Treat `operationGuidance` as optional additive advice. Read and consider every
|
||||||
|
entry, and follow entries that are applicable and compatible with the built-in
|
||||||
|
workflow.
|
||||||
|
|
||||||
|
Keep both fields separate from CLI-returned state, missing artifacts, tasks,
|
||||||
|
progress, `contextFiles`, and the built-in `instruction`. They are not
|
||||||
|
evidence of task completion, do not replace the built-in instruction, and do
|
||||||
|
not permit bypassing a blocked state. If context conflicts with the built-in
|
||||||
|
instruction, an explicit user choice, or a CLI-controlled value, report the
|
||||||
|
conflict and preserve the controlling value. If guidance is inapplicable or
|
||||||
|
conflicts with those controlling inputs, do not follow it and explain why.
|
||||||
|
These are prompt-level behavior contracts, not enforceable checks.
|
||||||
|
|
||||||
|
4. **Read context files**
|
||||||
|
|
||||||
|
Read every file path listed under `contextFiles` from the apply instructions output.
|
||||||
|
The files depend on the schema being used:
|
||||||
|
- **spec-driven**: proposal, specs, design, tasks
|
||||||
|
- Other schemas: follow the contextFiles from CLI output
|
||||||
|
|
||||||
|
Do not copy `context` or `operationGuidance` verbatim into implementation
|
||||||
|
files or planning artifacts unless the user separately asks for that content.
|
||||||
|
|
||||||
|
5. **Show current progress**
|
||||||
|
|
||||||
|
Display:
|
||||||
|
- Schema being used
|
||||||
|
- Progress: "N/M tasks complete"
|
||||||
|
- Remaining tasks overview
|
||||||
|
- Dynamic instruction from CLI
|
||||||
|
|
||||||
|
6. **Implement tasks (loop until done or blocked)**
|
||||||
|
|
||||||
|
For each pending task:
|
||||||
|
- Show which task is being worked on
|
||||||
|
- Make the code changes required
|
||||||
|
- Keep changes minimal and focused
|
||||||
|
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
|
||||||
|
- Continue to next task
|
||||||
|
|
||||||
|
**Pause if:**
|
||||||
|
- Task is unclear → ask for clarification
|
||||||
|
- Implementation reveals a design issue → suggest updating artifacts
|
||||||
|
- Error or blocker encountered → report and wait for guidance
|
||||||
|
- User interrupts
|
||||||
|
|
||||||
|
7. **On completion or pause, show status**
|
||||||
|
|
||||||
|
Display:
|
||||||
|
- Tasks completed this session
|
||||||
|
- Overall progress: "N/M tasks complete"
|
||||||
|
- If all done: suggest archive
|
||||||
|
- If paused: explain why and wait for guidance
|
||||||
|
|
||||||
|
**Output During Implementation**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementing: <change-name> (schema: <schema-name>)
|
||||||
|
|
||||||
|
Working on task 3/7: <task description>
|
||||||
|
[...implementation happening...]
|
||||||
|
✓ Task complete
|
||||||
|
|
||||||
|
Working on task 4/7: <task description>
|
||||||
|
[...implementation happening...]
|
||||||
|
✓ Task complete
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output On Completion**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementation Complete
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Progress:** 7/7 tasks complete ✓
|
||||||
|
|
||||||
|
### Completed This Session
|
||||||
|
- [x] Task 1
|
||||||
|
- [x] Task 2
|
||||||
|
...
|
||||||
|
|
||||||
|
All tasks complete! Ready to archive this change.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output On Pause (Issue Encountered)**
|
||||||
|
|
||||||
|
```
|
||||||
|
## Implementation Paused
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Progress:** 4/7 tasks complete
|
||||||
|
|
||||||
|
### Issue Encountered
|
||||||
|
<description of the issue>
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
1. <option 1>
|
||||||
|
2. <option 2>
|
||||||
|
3. Other approach
|
||||||
|
|
||||||
|
What would you like to do?
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Keep going through tasks until done or blocked
|
||||||
|
- Always read context files before starting (from the apply instructions output)
|
||||||
|
- If task is ambiguous, pause and ask before implementing
|
||||||
|
- If implementation reveals issues, pause and suggest artifact updates
|
||||||
|
- Keep code changes minimal and scoped to each task
|
||||||
|
- Update task checkbox immediately after completing each task
|
||||||
|
- Pause on errors, blockers, or unclear requirements - don't guess
|
||||||
|
- Use contextFiles from CLI output, don't assume specific file names
|
||||||
|
- Do not use context or operation guidance as proof that a task is complete
|
||||||
|
- Apply relevant project context; report conflicts with controlling workflow inputs
|
||||||
|
- Consider every guidance entry; explain any inapplicable or conflicting advice
|
||||||
|
- Do not copy runtime context or operation guidance into implementation files or planning artifacts
|
||||||
|
- Preserve CLI-controlled blocked/ready/all-done behavior and completion criteria
|
||||||
|
|
||||||
|
**Fluid Workflow Integration**
|
||||||
|
|
||||||
|
This skill supports the "actions on a change" model:
|
||||||
|
|
||||||
|
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
|
||||||
|
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
---
|
||||||
|
name: openspec-archive-change
|
||||||
|
description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.
|
||||||
|
allowed-tools: Bash(openspec:*)
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.7.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Archive a completed change in the experimental workflow.
|
||||||
|
|
||||||
|
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
|
||||||
|
|
||||||
|
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Select the change**
|
||||||
|
|
||||||
|
If a name is provided, use it. Otherwise:
|
||||||
|
- Infer from conversation context if the user mentioned a change
|
||||||
|
- Auto-select if only one active change exists
|
||||||
|
- If ambiguous, run `openspec list --json` to get available changes and ask the user to select one
|
||||||
|
|
||||||
|
When prompting, show only active changes (not already archived).
|
||||||
|
Include the schema used for each change if available.
|
||||||
|
|
||||||
|
Always announce: "Using change: <name>" and how to override (e.g., `$openspec-archive-change <other>`).
|
||||||
|
|
||||||
|
**Load current archive inputs before the existing archive checks:**
|
||||||
|
|
||||||
|
After resolving the selected change and planning root, run:
|
||||||
|
```bash
|
||||||
|
openspec instructions archive --change "<name>" --json
|
||||||
|
```
|
||||||
|
Keep the same selected-root flags on this command. This lookup is advisory and
|
||||||
|
optional: it only supplies extra prompt inputs, so it must never block archiving.
|
||||||
|
If it exits non-zero or returns invalid JSON — for example on an older CLI that
|
||||||
|
does not support this command yet — continue the archive workflow with no
|
||||||
|
context and no operation guidance. Do not report an error and do not stop.
|
||||||
|
|
||||||
|
A successful response may omit both optional fields. Treat `context` as a
|
||||||
|
required prompt-level input: read and consider it, and apply relevant project
|
||||||
|
facts, conventions, and constraints. Treat `operationGuidance` as optional
|
||||||
|
additive advice: read and consider every entry, and follow entries that are
|
||||||
|
applicable and compatible with the built-in archive workflow.
|
||||||
|
|
||||||
|
Keep both fields separate from built-in steps, explicit user choices, resolved
|
||||||
|
paths, CLI checks, and command contracts. If context conflicts with one of those
|
||||||
|
controlling inputs, report the conflict and preserve the controlling value. If
|
||||||
|
guidance is inapplicable or conflicts with a controlling input, do not follow it
|
||||||
|
and explain why. Do not infer replacement paths, skipped prompts, or flags from
|
||||||
|
either field, and do not copy their text verbatim into specs, change artifacts,
|
||||||
|
or archive summaries unless the user separately asks for it. These are
|
||||||
|
prompt-level behavior contracts, not enforceable checks.
|
||||||
|
|
||||||
|
2. **Check artifact completion status**
|
||||||
|
|
||||||
|
Run `openspec status --change "<name>" --json` to check artifact completion.
|
||||||
|
|
||||||
|
Parse the JSON to understand:
|
||||||
|
- `schemaName`: The workflow being used
|
||||||
|
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context
|
||||||
|
- `artifacts`: List of artifacts with their status (`done`, `skipped`, or other)
|
||||||
|
|
||||||
|
**If any artifacts are neither `done` nor `skipped`** (skipped artifacts satisfy the requirement - the change declares skip_specs):
|
||||||
|
- Display warning listing incomplete artifacts
|
||||||
|
- Ask the user to confirm they want to proceed
|
||||||
|
- Proceed if user confirms
|
||||||
|
|
||||||
|
3. **Check task completion status**
|
||||||
|
|
||||||
|
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
|
||||||
|
|
||||||
|
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
|
||||||
|
|
||||||
|
**If incomplete tasks found:**
|
||||||
|
- Display warning showing count of incomplete tasks
|
||||||
|
- Ask the user to confirm they want to proceed
|
||||||
|
- Proceed if user confirms
|
||||||
|
|
||||||
|
**If no tasks file exists:** Proceed without task-related warning.
|
||||||
|
|
||||||
|
4. **Assess delta spec sync state**
|
||||||
|
|
||||||
|
Use `artifactPaths.specs.existingOutputPaths` from status JSON as the only
|
||||||
|
delta-spec source. If the `specs` entry is missing or
|
||||||
|
`existingOutputPaths` is empty, proceed without a sync prompt and do not infer
|
||||||
|
delta specs from other artifacts.
|
||||||
|
|
||||||
|
**If delta specs exist:**
|
||||||
|
- Compare each delta spec with its corresponding main spec at `<planningHome.root>/openspec/specs/<capability>/spec.md` (use the store-aware `planningHome.root` from step 2, not a hardcoded repo path)
|
||||||
|
- Determine what changes would be applied (adds, modifications, removals, renames)
|
||||||
|
- Show a combined summary before prompting
|
||||||
|
|
||||||
|
**Prompt options:**
|
||||||
|
- If changes needed: "Sync now (recommended)", "Archive without syncing"
|
||||||
|
- If already synced: "Archive now", "Sync anyway", "Cancel"
|
||||||
|
|
||||||
|
Route on the answer:
|
||||||
|
- "Cancel" — stop, do not archive
|
||||||
|
- "Archive without syncing" or "Archive now" — proceed to archive
|
||||||
|
- "Sync now" or "Sync anyway" — sync, then verify (below)
|
||||||
|
- Anything else — ask again rather than archiving
|
||||||
|
|
||||||
|
Before a selected sync writes any main spec, run
|
||||||
|
`openspec instructions specs --change "<name>" --json` once with the same
|
||||||
|
selected-root flags. Require a zero exit status and valid artifact-instruction
|
||||||
|
JSON. If the lookup fails or returns invalid JSON, report the error and stop
|
||||||
|
before writing any main spec or moving the change. A valid response with omitted
|
||||||
|
`rules` is the no-rules case. Apply returned `rules` only to the content and
|
||||||
|
form of main specs produced by this merge; do not use them as archive guidance,
|
||||||
|
change CLI behavior, or copy the rule text into any output file.
|
||||||
|
|
||||||
|
Then run the `openspec-sync-specs` workflow inline (agent-driven intelligent merge) for change '<name>', passing the delta spec analysis and the fetched specs-rule snapshot from above, and wait for it to finish. The inline sync must reuse that snapshot without fetching `specs` instructions again. Do not delegate it to a background task — step 5 would move `changeRoot` out from under a sync that is still reading it, leaving the change archived and the main specs never updated. If your agent can only run it by delegation, delegate synchronously and wait for the result.
|
||||||
|
|
||||||
|
Then re-run the comparison from the top of this step against every capability that has a delta spec in `artifactPaths.specs.existingOutputPaths` — not only the ones the sync reports it touched. A successful sync leaves nothing left to apply, so each capability must now read as already synced:
|
||||||
|
- ADDED requirements present
|
||||||
|
- MODIFIED requirements carrying the scenario and description changes named in the delta, with their other scenarios intact
|
||||||
|
- REMOVED requirements gone
|
||||||
|
- RENAMED requirements present under the new name and absent under the old one
|
||||||
|
|
||||||
|
If the sync failed, or any capability does not match, report what differs and stop — do not archive. Nothing has moved and `changeRoot` is intact, so the user can fix the mismatch or re-run the sync and start the archive again.
|
||||||
|
|
||||||
|
5. **Perform the archive**
|
||||||
|
|
||||||
|
Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:
|
||||||
|
```bash
|
||||||
|
mkdir -p "<planningHome.changesDir>/archive"
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate the target name: use the change name as-is when it already starts with a `YYYY-MM-DD-` prefix; otherwise prepend the current date as `YYYY-MM-DD-<change-name>`. Never stack a second date (same rule as `openspec archive`).
|
||||||
|
|
||||||
|
**Check if target already exists:**
|
||||||
|
- If yes: Fail with error, suggest renaming existing archive or using different date
|
||||||
|
- If no: Move `changeRoot` to the archive directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mv "<changeRoot>" "<planningHome.changesDir>/archive/<target-name>"
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Display summary**
|
||||||
|
|
||||||
|
Show archive completion summary including:
|
||||||
|
- Change name
|
||||||
|
- Schema that was used
|
||||||
|
- Archive location
|
||||||
|
- Whether specs were synced (if applicable)
|
||||||
|
- Note about any warnings (incomplete artifacts/tasks)
|
||||||
|
|
||||||
|
**Output On Success**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Archive Complete
|
||||||
|
|
||||||
|
**Change:** <change-name>
|
||||||
|
**Schema:** <schema-name>
|
||||||
|
**Archived to:** the archive path derived from `planningHome.changesDir`/<target-name>/
|
||||||
|
**Specs:** <"✓ Synced to main specs" only if the step 4 verification passed; otherwise "No delta specs" or "Sync skipped">
|
||||||
|
|
||||||
|
<"All artifacts complete. All tasks complete." — or, if archived with warnings, list them instead (e.g. "Archived with 2 incomplete tasks")>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Announce the selected change; prompt for selection when it is ambiguous
|
||||||
|
- Use artifact graph (openspec status --json) for completion checking
|
||||||
|
- Don't block archive on warnings - just inform and confirm
|
||||||
|
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
|
||||||
|
- Show clear summary of what happened
|
||||||
|
- If sync is requested, run the `openspec-sync-specs` workflow inline (agent-driven)
|
||||||
|
- Never archive while a spec sync is still in flight — run the sync inline and verify the main specs before moving `changeRoot`
|
||||||
|
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
|
||||||
|
- Apply relevant runtime context and report conflicts; operation guidance remains advisory
|
||||||
|
- Consider every guidance entry and explain any inapplicable or conflicting advice
|
||||||
|
- Existing CLI checks, resolved paths, prompts, and command contracts are unchanged
|
||||||
|
- Artifact rules constrain only the specs being written and are never operation guidance
|
||||||
|
- Never copy runtime context, operation guidance, or artifact-rule text verbatim into output files
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
---
|
||||||
|
name: openspec-explore
|
||||||
|
description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.
|
||||||
|
allowed-tools: Bash(openspec:*)
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.7.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
|
||||||
|
|
||||||
|
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
|
||||||
|
|
||||||
|
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
|
||||||
|
|
||||||
|
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Stance
|
||||||
|
|
||||||
|
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
|
||||||
|
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
|
||||||
|
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
|
||||||
|
- **Adaptive** - Follow interesting threads, pivot when new information emerges
|
||||||
|
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
|
||||||
|
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What You Might Do
|
||||||
|
|
||||||
|
Depending on what the user brings, you might:
|
||||||
|
|
||||||
|
**Explore the problem space**
|
||||||
|
- Ask clarifying questions that emerge from what they said
|
||||||
|
- Challenge assumptions
|
||||||
|
- Reframe the problem
|
||||||
|
- Find analogies
|
||||||
|
|
||||||
|
**Investigate the codebase**
|
||||||
|
- Map existing architecture relevant to the discussion
|
||||||
|
- Find integration points
|
||||||
|
- Identify patterns already in use
|
||||||
|
- Surface hidden complexity
|
||||||
|
|
||||||
|
**Compare options**
|
||||||
|
- Brainstorm multiple approaches
|
||||||
|
- Build comparison tables
|
||||||
|
- Sketch tradeoffs
|
||||||
|
- Recommend a path (if asked)
|
||||||
|
|
||||||
|
**Visualize**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Use ASCII diagrams liberally │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌────────┐ ┌────────┐ │
|
||||||
|
│ │ State │────────▶│ State │ │
|
||||||
|
│ │ A │ │ B │ │
|
||||||
|
│ └────────┘ └────────┘ │
|
||||||
|
│ │
|
||||||
|
│ System diagrams, state machines, │
|
||||||
|
│ data flows, architecture sketches, │
|
||||||
|
│ dependency graphs, comparison tables │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Surface risks and unknowns**
|
||||||
|
- Identify what could go wrong
|
||||||
|
- Find gaps in understanding
|
||||||
|
- Suggest spikes or investigations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenSpec Awareness
|
||||||
|
|
||||||
|
You have full context of the OpenSpec system. Use it naturally, don't force it.
|
||||||
|
|
||||||
|
### Check for context
|
||||||
|
|
||||||
|
At the start, quickly check what exists:
|
||||||
|
```bash
|
||||||
|
openspec list --json
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells you:
|
||||||
|
- If there are active changes
|
||||||
|
- Their names, schemas, and status
|
||||||
|
- What the user might be working on
|
||||||
|
|
||||||
|
Then read the project's own context from the resolved root - `<root.path>/openspec/config.yaml` (or `config.yml`). Use the `root.path` returned above, and skip this if neither file exists:
|
||||||
|
- `context`: project background - tech stack, conventions, constraints
|
||||||
|
- `rules`: keyed by artifact id - the entries for an artifact apply only when you write that artifact
|
||||||
|
|
||||||
|
Ground your thinking in these. They are constraints for you to follow, not content to reproduce: do NOT copy them into the conversation or into any artifact you create.
|
||||||
|
|
||||||
|
### When no change exists
|
||||||
|
|
||||||
|
Think freely. When insights crystallize, you might offer:
|
||||||
|
|
||||||
|
- "This feels solid enough to start a change. Want me to create a proposal?"
|
||||||
|
- Or keep exploring - no pressure to formalize
|
||||||
|
|
||||||
|
### When a change exists
|
||||||
|
|
||||||
|
If the user mentions a change or you detect one is relevant:
|
||||||
|
|
||||||
|
1. **Resolve and read existing artifacts for context**
|
||||||
|
- Run `openspec status --change "<name>" --json`.
|
||||||
|
- Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON.
|
||||||
|
- Read existing files from `artifactPaths.<artifact>.existingOutputPaths`.
|
||||||
|
|
||||||
|
2. **Reference them naturally in conversation**
|
||||||
|
- "Your design mentions using Redis, but we just realized SQLite fits better..."
|
||||||
|
- "The proposal scopes this to premium users, but we're now thinking everyone..."
|
||||||
|
|
||||||
|
3. **Offer to capture when decisions are made**
|
||||||
|
|
||||||
|
| Insight Type | Where to Capture |
|
||||||
|
|----------------------------|--------------------------------|
|
||||||
|
| New requirement discovered | `specs/<capability>/spec.md` |
|
||||||
|
| Requirement changed | `specs/<capability>/spec.md` |
|
||||||
|
| Design decision made | `design.md` |
|
||||||
|
| Scope changed | `proposal.md` |
|
||||||
|
| New work identified | `tasks.md` |
|
||||||
|
| Assumption invalidated | Relevant artifact |
|
||||||
|
|
||||||
|
Example offers:
|
||||||
|
- "That's a design decision. Capture it in design.md?"
|
||||||
|
- "This is a new requirement. Add it to specs?"
|
||||||
|
- "This changes scope. Update the proposal?"
|
||||||
|
|
||||||
|
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What You Don't Have To Do
|
||||||
|
|
||||||
|
- Follow a script
|
||||||
|
- Ask the same questions every time
|
||||||
|
- Produce a specific artifact
|
||||||
|
- Reach a conclusion
|
||||||
|
- Stay on topic if a tangent is valuable
|
||||||
|
- Be brief (this is thinking time)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Handling Different Entry Points
|
||||||
|
|
||||||
|
**User brings a vague idea:**
|
||||||
|
```
|
||||||
|
User: I'm thinking about adding real-time collaboration
|
||||||
|
|
||||||
|
You: Real-time collab is a big space. Let me think about this...
|
||||||
|
|
||||||
|
COLLABORATION SPECTRUM
|
||||||
|
════════════════════════════════════════════
|
||||||
|
|
||||||
|
Awareness Coordination Sync
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌────────┐ ┌────────┐ ┌────────┐
|
||||||
|
│Presence│ │Cursors │ │ CRDT │
|
||||||
|
│ "3 │ │ Multi │ │Conflict│
|
||||||
|
│online" │ │ select │ │ free │
|
||||||
|
└────────┘ └────────┘ └────────┘
|
||||||
|
│ │ │
|
||||||
|
trivial moderate complex
|
||||||
|
|
||||||
|
Where's your head at?
|
||||||
|
```
|
||||||
|
|
||||||
|
**User brings a specific problem:**
|
||||||
|
```
|
||||||
|
User: The auth system is a mess
|
||||||
|
|
||||||
|
You: [reads codebase]
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ CURRENT AUTH FLOW │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────┼─────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||||
|
│ Google │ │ GitHub │ │ Email │
|
||||||
|
│ OAuth │ │ OAuth │ │ Magic │
|
||||||
|
└────┬────┘ └────┬────┘ └────┬────┘
|
||||||
|
│ │ │
|
||||||
|
└─────────────┼─────────────┘
|
||||||
|
▼
|
||||||
|
┌───────────┐
|
||||||
|
│ Session │
|
||||||
|
└─────┬─────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌───────────┐
|
||||||
|
│ Perms │
|
||||||
|
└───────────┘
|
||||||
|
|
||||||
|
I see three tangles. Which one's burning?
|
||||||
|
```
|
||||||
|
|
||||||
|
**User is stuck mid-implementation:**
|
||||||
|
```
|
||||||
|
User: $openspec-explore add-auth-system
|
||||||
|
The OAuth integration is more complex than expected
|
||||||
|
|
||||||
|
You: [reads change artifacts]
|
||||||
|
|
||||||
|
You're on task 4: "Implement OAuth flow"
|
||||||
|
|
||||||
|
Let me trace what's involved...
|
||||||
|
|
||||||
|
[draws diagram, explores options, suggests paths]
|
||||||
|
|
||||||
|
Want to update the design to reflect this?
|
||||||
|
Or add a spike task to investigate?
|
||||||
|
```
|
||||||
|
|
||||||
|
**User wants to compare options:**
|
||||||
|
```
|
||||||
|
User: Should we use Postgres or SQLite?
|
||||||
|
|
||||||
|
You: Generic answer is boring. What's the context?
|
||||||
|
|
||||||
|
User: A CLI tool that tracks local dev environments
|
||||||
|
|
||||||
|
You: That changes everything.
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ CLI TOOL DATA STORAGE │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Key constraints:
|
||||||
|
• No daemon running
|
||||||
|
• Must work offline
|
||||||
|
• Single user
|
||||||
|
|
||||||
|
SQLite Postgres
|
||||||
|
Deployment embedded ✓ needs server ✗
|
||||||
|
Offline yes ✓ no ✗
|
||||||
|
Single file yes ✓ no ✗
|
||||||
|
|
||||||
|
SQLite. Not even close.
|
||||||
|
|
||||||
|
Unless... is there a sync component?
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ending Discovery
|
||||||
|
|
||||||
|
There's no required ending. Discovery might:
|
||||||
|
|
||||||
|
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
|
||||||
|
- **Result in artifact updates**: "Updated design.md with these decisions"
|
||||||
|
- **Just provide clarity**: User has what they need, moves on
|
||||||
|
- **Continue later**: "We can pick this up anytime"
|
||||||
|
|
||||||
|
When it feels like things are crystallizing, you might summarize:
|
||||||
|
|
||||||
|
```
|
||||||
|
## What We Figured Out
|
||||||
|
|
||||||
|
**The problem**: [crystallized understanding]
|
||||||
|
|
||||||
|
**The approach**: [if one emerged]
|
||||||
|
|
||||||
|
**Open questions**: [if any remain]
|
||||||
|
|
||||||
|
**Next steps** (if ready):
|
||||||
|
- Create a change proposal
|
||||||
|
- Keep exploring: just keep talking
|
||||||
|
```
|
||||||
|
|
||||||
|
But this summary is optional. Sometimes the thinking IS the value.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
|
||||||
|
- **Don't fake understanding** - If something is unclear, dig deeper
|
||||||
|
- **Don't rush** - Discovery is thinking time, not task time
|
||||||
|
- **Don't force structure** - Let patterns emerge naturally
|
||||||
|
- **Don't auto-capture** - Offer to save insights, don't just do it
|
||||||
|
- **Do visualize** - A good diagram is worth many paragraphs
|
||||||
|
- **Do explore the codebase** - Ground discussions in reality
|
||||||
|
- **Do question assumptions** - Including the user's and your own
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
---
|
||||||
|
name: openspec-propose
|
||||||
|
description: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation.
|
||||||
|
allowed-tools: Bash(openspec:*)
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.7.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Propose a new change - create the change and generate all artifacts in one step.
|
||||||
|
|
||||||
|
I'll create a change with the artifacts your schema defines. With the default spec-driven schema that is:
|
||||||
|
- proposal.md (what & why)
|
||||||
|
- `specs/<capability>/spec.md` (what the system must do - a delta, not the main spec)
|
||||||
|
- design.md (how)
|
||||||
|
- tasks.md (implementation steps)
|
||||||
|
|
||||||
|
When ready to implement, run $openspec-apply-change
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
|
||||||
|
|
||||||
|
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **If no clear input provided, ask what they want to build**
|
||||||
|
|
||||||
|
Ask the user (open-ended, no preset options):
|
||||||
|
> "What change do you want to work on? Describe what you want to build or fix."
|
||||||
|
|
||||||
|
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
|
||||||
|
|
||||||
|
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
|
||||||
|
|
||||||
|
2. **Create the change directory**
|
||||||
|
```bash
|
||||||
|
openspec new change "<name>"
|
||||||
|
```
|
||||||
|
This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`.
|
||||||
|
|
||||||
|
3. **Get the artifact build order**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>" --json
|
||||||
|
```
|
||||||
|
Parse the JSON to get:
|
||||||
|
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
|
||||||
|
- `artifacts`: list of all artifacts, each with its `status` and its `requires` edges (the artifact IDs it directly depends on)
|
||||||
|
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
|
||||||
|
|
||||||
|
4. **Create every artifact in the required set**
|
||||||
|
|
||||||
|
Use a todo list to track progress through the artifacts.
|
||||||
|
|
||||||
|
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
|
||||||
|
|
||||||
|
a. **For each artifact that is `ready` (dependencies satisfied)**:
|
||||||
|
- Get instructions:
|
||||||
|
```bash
|
||||||
|
openspec instructions <artifact-id> --change "<name>" --json
|
||||||
|
```
|
||||||
|
- The instructions JSON includes:
|
||||||
|
- `context`: Project background (constraints for you - do NOT include in output)
|
||||||
|
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
|
||||||
|
- `template`: The structure to use for your output file
|
||||||
|
- `instruction`: Schema-specific guidance for this artifact type
|
||||||
|
- `skipped`/`warning`: present when the change declares skip_specs and this artifact must NOT be created - stop and pick another artifact
|
||||||
|
- `resolvedOutputPath`: Resolved path or pattern to write the artifact
|
||||||
|
- `dependencies`: Completed artifacts to read for context
|
||||||
|
- Read any completed dependency files for context - always re-read them from disk, even if you saw them earlier in the conversation (the user may have edited them)
|
||||||
|
- If the `instruction` field delegates creation to a specific skill or command, invoke it to produce the artifact instead of writing the file yourself, then verify the artifact file exists at `resolvedOutputPath`
|
||||||
|
- Otherwise create the artifact file using `template` as the structure and write it to `resolvedOutputPath`. If `resolvedOutputPath` is a glob, follow `instruction` to choose the concrete file path
|
||||||
|
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
|
||||||
|
- Show brief progress: "Created <artifact-id>"
|
||||||
|
|
||||||
|
b. **Continue until every artifact in the required set exists (not just `apply.requires`)**
|
||||||
|
- After creating each artifact, re-run `openspec status --change "<name>" --json`
|
||||||
|
- The required set is `applyRequires` plus every artifact reachable from those by following the `requires` edges in `status --json` - walk them transitively (spec-driven closes over proposal, specs, design, tasks). Leave artifacts outside that set alone
|
||||||
|
- `status` is file-existence only, so an `applyRequires` artifact reading `done` does NOT mean its dependencies exist - writing `tasks.md` early marks `tasks` done while `specs` was never written. Use each artifact's `requires` edges, not its `status`, to build the required set: a `done` artifact still lists what it depends on
|
||||||
|
- An artifact already reading `status: "skipped"` is satisfied: the change declares `skip_specs` in `.openspec.yaml`, so its files must NOT exist. Never try to create one
|
||||||
|
- Create every artifact in the required set that is missing, then re-check - creating one can unblock others
|
||||||
|
- Skip one only when `status` already reports it `skipped`, or when its own `instruction` says it is conditional: run `openspec instructions <artifact-id> --change "<name>" --json` and skip only if its `instruction` field marks it optional (e.g. "create only if..."). Spec-driven's `design.md` qualifies; `specs` qualifies only via the `skipped` status above, never by your own judgment. Tell the user, and do not reconsider it
|
||||||
|
- Dependencies are enablers, not gates: if a required artifact is still `blocked` only because you skipped a conditional dependency, write it anyway
|
||||||
|
- Stop when every artifact in the required set is `done`, `skipped`, or was deliberately skipped
|
||||||
|
|
||||||
|
c. **If an artifact requires user input** (unclear context):
|
||||||
|
- Ask the user to clarify
|
||||||
|
- Then continue with creation
|
||||||
|
|
||||||
|
5. **Show final status**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
|
||||||
|
After completing all artifacts, summarize:
|
||||||
|
- Change name and location
|
||||||
|
- List of artifacts created with brief descriptions, plus any conditional artifact you skipped and why
|
||||||
|
- What's ready: "All artifacts needed for implementation are ready."
|
||||||
|
- Prompt: "Run `$openspec-apply-change` or ask me to implement to start working on the tasks."
|
||||||
|
|
||||||
|
**Artifact Creation Guidelines**
|
||||||
|
|
||||||
|
- Follow the `instruction` field from `openspec instructions` for each artifact type - it is the authoritative guidance, even for familiar artifact names
|
||||||
|
- If the `instruction` field directs you to use a specific skill or command to create the artifact, invoke it instead of writing the artifact directly
|
||||||
|
- The schema defines what each artifact should contain - follow it
|
||||||
|
- Read dependency artifacts for context before creating new ones
|
||||||
|
- Use `template` as the structure for your output file - fill in its sections
|
||||||
|
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
|
||||||
|
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
|
||||||
|
- These guide what you write, but should never appear in the output
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Create every artifact the apply phase transitively depends on, not just the ids listed in `apply.requires`
|
||||||
|
- Always read dependency artifacts before creating a new one - re-read from disk, not from conversation memory (files may have changed since you last saw them)
|
||||||
|
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
|
||||||
|
- If a change with that name already exists, ask if user wants to continue it or create a new one
|
||||||
|
- Verify each artifact file exists after writing before proceeding to next
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
---
|
||||||
|
name: openspec-sync-specs
|
||||||
|
description: Sync delta specs from a change to main specs. Use when the user wants to update main specs with changes from a delta spec, without archiving the change.
|
||||||
|
allowed-tools: Bash(openspec:*)
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.7.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Sync delta specs from a change to main specs.
|
||||||
|
|
||||||
|
This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).
|
||||||
|
|
||||||
|
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
|
||||||
|
|
||||||
|
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Select the change**
|
||||||
|
|
||||||
|
If a name is provided, use it. Otherwise:
|
||||||
|
- Infer from conversation context if the user mentioned a change
|
||||||
|
- Auto-select if only one active change exists
|
||||||
|
- If ambiguous, run `openspec list --json` to get available changes and ask the user to select one
|
||||||
|
|
||||||
|
When prompting, show changes that have delta specs (under `specs/` directory).
|
||||||
|
|
||||||
|
Always announce: "Using change: <name>" and how to override (e.g., `$openspec-sync-specs <other>`).
|
||||||
|
|
||||||
|
2. **Resolve change context**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>" --json
|
||||||
|
```
|
||||||
|
|
||||||
|
The JSON includes `planningHome.root`. Main specs live under `<planningHome.root>/openspec/specs/` — use that (store-aware) root for every main-spec path below, not a hardcoded repo path. When a store is selected it points at the store, not the current repository.
|
||||||
|
|
||||||
|
3. **Find delta specs**
|
||||||
|
|
||||||
|
Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the
|
||||||
|
only source of delta spec paths. If the `specs` entry is missing or
|
||||||
|
`existingOutputPaths` is empty, report that there are no delta specs to sync,
|
||||||
|
do not infer them from other artifacts, and stop without requesting artifact
|
||||||
|
instructions or writing a main spec.
|
||||||
|
|
||||||
|
Sync every path in `existingOutputPaths` unless the caller narrowed the set.
|
||||||
|
A caller narrows it by naming an explicit list of delta spec paths to sync —
|
||||||
|
archive does this inline, and a user can too ("only sync the billing delta").
|
||||||
|
Then sync only the named paths and leave the remaining delta specs untouched:
|
||||||
|
bulk archive excludes a delta whose implementation it could not find, and
|
||||||
|
syncing it anyway would write a main spec the caller deliberately withheld.
|
||||||
|
Carry that narrowed selection through step 4; never widen it back to the full
|
||||||
|
list. If a named path is not in `existingOutputPaths`, do not sync it —
|
||||||
|
report it and stop, rather than dropping it silently. If the named list is
|
||||||
|
empty, report that there is nothing to sync and stop without writing a main
|
||||||
|
spec.
|
||||||
|
|
||||||
|
Each delta spec file contains sections like:
|
||||||
|
- `## ADDED Requirements` - New requirements to add
|
||||||
|
- `## MODIFIED Requirements` - Changes to existing requirements
|
||||||
|
- `## REMOVED Requirements` - Requirements to remove
|
||||||
|
- `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)
|
||||||
|
|
||||||
|
If no delta specs found, inform user and stop.
|
||||||
|
|
||||||
|
4. **For each delta spec, apply changes to main specs**
|
||||||
|
|
||||||
|
Before the first main-spec write, obtain one current specs-rule snapshot:
|
||||||
|
- If archive invoked this workflow inline and supplied a valid snapshot from
|
||||||
|
`openspec instructions specs --change "<name>" --json`, reuse it and do not
|
||||||
|
fetch the same instructions again.
|
||||||
|
- Otherwise run that command once now with the same selected-root flags.
|
||||||
|
- If the direct lookup exits non-zero or returns invalid artifact-instruction
|
||||||
|
JSON, report the error and stop before writing any main spec. Do not treat the
|
||||||
|
failure as an absent rule set.
|
||||||
|
- A valid response with omitted `rules` means no artifact rules are configured
|
||||||
|
and the existing semantic merge continues.
|
||||||
|
|
||||||
|
Apply returned `rules` only to the content and form of the main specs produced
|
||||||
|
by this merge. Artifact rules are not operation guidance and cannot change
|
||||||
|
selected roots, delta paths, CLI checks, or workflow steps. Use their text as
|
||||||
|
constraints without copying it verbatim into a main spec or summary.
|
||||||
|
|
||||||
|
For each capability delta spec path selected in step 3 — the full `existingOutputPaths` list, or the narrowed subset when a caller supplied one (these may belong to a selected store, not the repo):
|
||||||
|
|
||||||
|
a. **Read the delta spec** to understand the intended changes
|
||||||
|
|
||||||
|
b. **Read the main spec** at `<planningHome.root>/openspec/specs/<capability>/spec.md` (may not exist yet)
|
||||||
|
|
||||||
|
c. **Apply changes intelligently**:
|
||||||
|
|
||||||
|
**ADDED Requirements:**
|
||||||
|
- If requirement doesn't exist in main spec → add it
|
||||||
|
- If requirement already exists → update it to match (treat as implicit MODIFIED)
|
||||||
|
|
||||||
|
**MODIFIED Requirements:**
|
||||||
|
- Find the requirement in main spec
|
||||||
|
- Apply the changes - this can be:
|
||||||
|
- Adding new scenarios (don't need to copy existing ones)
|
||||||
|
- Modifying existing scenarios
|
||||||
|
- Changing the requirement description
|
||||||
|
- Preserve scenarios/content not mentioned in the delta
|
||||||
|
|
||||||
|
**REMOVED Requirements:**
|
||||||
|
- Remove the entire requirement block from main spec
|
||||||
|
|
||||||
|
**RENAMED Requirements:**
|
||||||
|
- Find the FROM requirement, rename to TO
|
||||||
|
|
||||||
|
**`## Purpose` in the delta:**
|
||||||
|
- The main spec already has one and it is authoritative - leave it alone
|
||||||
|
(this is what `openspec archive` does; it warns and moves on)
|
||||||
|
|
||||||
|
d. **Create new main spec** if capability doesn't exist yet:
|
||||||
|
- Create `<planningHome.root>/openspec/specs/<capability>/spec.md`
|
||||||
|
- Add Purpose section: copy the delta's `## Purpose` body verbatim when it has one
|
||||||
|
(this is what `openspec archive` does); only write a brief TBD placeholder when it does not
|
||||||
|
- Add Requirements section with the ADDED requirements
|
||||||
|
- Follow the **Main Spec Format Reference** below
|
||||||
|
|
||||||
|
5. **Show summary**
|
||||||
|
|
||||||
|
After applying all changes, summarize:
|
||||||
|
- Which capabilities were updated
|
||||||
|
- What changes were made (requirements added/modified/removed/renamed)
|
||||||
|
- Any new main spec left with a TBD Purpose placeholder, so it gets written
|
||||||
|
now rather than lingering
|
||||||
|
|
||||||
|
**Delta Spec Format Reference**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Only on a delta that introduces a brand-new capability. Seeds the new main spec.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: New Feature
|
||||||
|
The system SHALL do something new.
|
||||||
|
|
||||||
|
#### Scenario: Basic case
|
||||||
|
- **WHEN** user does X
|
||||||
|
- **THEN** system does Y
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Existing Feature
|
||||||
|
#### Scenario: New scenario to add
|
||||||
|
- **WHEN** user does A
|
||||||
|
- **THEN** system does B
|
||||||
|
|
||||||
|
## REMOVED Requirements
|
||||||
|
|
||||||
|
### Requirement: Deprecated Feature
|
||||||
|
|
||||||
|
## RENAMED Requirements
|
||||||
|
|
||||||
|
- FROM: `### Requirement: Old Name`
|
||||||
|
- TO: `### Requirement: New Name`
|
||||||
|
```
|
||||||
|
|
||||||
|
**Main Spec Format Reference**
|
||||||
|
|
||||||
|
Main specs are what the delta merges INTO. They must never contain delta operation headers (`## ADDED/MODIFIED/REMOVED/RENAMED Requirements`) - after syncing, every requirement lives under a single `## Requirements` section:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# <capability> Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Short description of what this capability does and why it exists.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: New Feature
|
||||||
|
The system SHALL do something new.
|
||||||
|
|
||||||
|
#### Scenario: Basic case
|
||||||
|
- **WHEN** user does X
|
||||||
|
- **THEN** system does Y
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Principle: Intelligent Merging**
|
||||||
|
|
||||||
|
Unlike programmatic merging, you can apply **partial updates**:
|
||||||
|
- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios
|
||||||
|
- The delta represents *intent*, not a wholesale replacement
|
||||||
|
- Use your judgment to merge changes sensibly
|
||||||
|
|
||||||
|
**Output On Success**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Specs Synced: <change-name>
|
||||||
|
|
||||||
|
Updated main specs:
|
||||||
|
|
||||||
|
**<capability-1>**:
|
||||||
|
- Added requirement: "New Feature"
|
||||||
|
- Modified requirement: "Existing Feature" (added 1 scenario)
|
||||||
|
|
||||||
|
**<capability-2>**:
|
||||||
|
- Created new spec file
|
||||||
|
- Added requirement: "Another Feature"
|
||||||
|
|
||||||
|
Main specs are now updated. The change remains active - archive when implementation is complete.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Read both delta and main specs before making changes
|
||||||
|
- Preserve existing content not mentioned in delta
|
||||||
|
- Never copy a delta file into a main spec as-is - merge its content so the main spec keeps the Main Spec Format Reference structure, with no delta operation headers
|
||||||
|
- If something is unclear, ask for clarification
|
||||||
|
- Show what you're changing as you go
|
||||||
|
- The operation should be idempotent - running twice should give same result
|
||||||
|
- Use only `artifactPaths.specs.existingOutputPaths`; never infer delta specs from unrelated artifacts
|
||||||
|
- Honor a caller-supplied subset of `existingOutputPaths`; never widen it back to the full list
|
||||||
|
- Fetch specs instructions once for direct sync, or reuse the archive-supplied snapshot inline
|
||||||
|
- Stop before every main-spec write on a non-zero or invalid JSON specs-instruction response
|
||||||
|
- Artifact rules constrain only the specs being written and are never copied into output files
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
name: openspec-update-change
|
||||||
|
description: Update an OpenSpec change by revising its existing planning artifacts and keeping them coherent with one another. Use when the user wants to revise a change's plan, fold new decisions into it, or reconcile its artifacts after an edit. Never edits code.
|
||||||
|
allowed-tools: Bash(openspec:*)
|
||||||
|
license: MIT
|
||||||
|
compatibility: Requires openspec CLI.
|
||||||
|
metadata:
|
||||||
|
author: openspec
|
||||||
|
version: "1.0"
|
||||||
|
generatedBy: "1.7.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
Revise a change's existing planning artifacts and keep them coherent. Never edit code.
|
||||||
|
|
||||||
|
**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store <id>` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`, `view`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root.
|
||||||
|
|
||||||
|
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
|
||||||
|
|
||||||
|
**Steps**
|
||||||
|
|
||||||
|
1. **Select the change**
|
||||||
|
|
||||||
|
If a name is provided, use it. Otherwise:
|
||||||
|
- Infer from conversation context if the user mentioned a change
|
||||||
|
- Auto-select if only one active change exists
|
||||||
|
- If ambiguous, run `openspec list --json` to get available changes sorted by most recently modified, and ask the user to select one
|
||||||
|
|
||||||
|
When prompting, present the top 3-4 most recently modified changes as options, showing:
|
||||||
|
- Change name
|
||||||
|
- Schema (from `schema` field if present, otherwise "spec-driven")
|
||||||
|
- Status (e.g., "0/5 tasks", "complete", "no tasks")
|
||||||
|
- How recently it was modified (from `lastModified` field)
|
||||||
|
|
||||||
|
Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to update.
|
||||||
|
|
||||||
|
Always announce: "Using change: <name>" and how to override (e.g., `$openspec-update-change <other>`).
|
||||||
|
|
||||||
|
2. **Get the change's artifacts**
|
||||||
|
```bash
|
||||||
|
openspec status --change "<name>" --json
|
||||||
|
```
|
||||||
|
Parse the JSON to understand current state. The response includes:
|
||||||
|
- `schemaName`: The workflow schema being used (e.g., "spec-driven")
|
||||||
|
- `artifacts`: Array of artifacts with their status ("done", "skipped", "ready", "blocked")
|
||||||
|
- `isComplete`: Boolean indicating if all artifacts are complete
|
||||||
|
- `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.
|
||||||
|
|
||||||
|
The artifact ids and paths come from the active schema - do NOT assume them, and do NOT branch on hardcoded artifact names. Custom schemas must work unchanged.
|
||||||
|
|
||||||
|
The files to edit are `artifactPaths.<id>.existingOutputPaths` - the concrete files that exist on disk, already glob-expanded for glob artifacts (e.g. `specs/**/*.md`). Do NOT write to `resolvedOutputPath`: for a glob artifact it is still the glob pattern, not a real file.
|
||||||
|
|
||||||
|
3. **Understand the request**
|
||||||
|
- If the user asked for a specific revision ("the design now uses X"), that is the starting edit.
|
||||||
|
- If they only said "update" / "make this coherent", treat it as a coherence review: read the existing artifacts and check them against each other for contradictions, gaps, and duplication.
|
||||||
|
|
||||||
|
4. **Read and reconcile**
|
||||||
|
- Read the artifact(s) the request touches and the change's other existing artifacts.
|
||||||
|
- Apply the requested edit. Then check every other existing artifact against it - in ANY direction: an edit to a later artifact may require revising an earlier one, not only the other way around. Build order is a useful reading order, not a constraint on which artifacts may be revised.
|
||||||
|
- Note everything that is now inconsistent, missing, or contradictory.
|
||||||
|
- Revise only files that already exist (`existingOutputPaths`). Do NOT create artifacts that don't exist yet, and do NOT invent new files under a glob artifact - note them and point the user to `$openspec-continue-change` to create them.
|
||||||
|
- If the change is already coherent, say so and make no edits.
|
||||||
|
|
||||||
|
5. **Confirm and apply, one artifact at a time**
|
||||||
|
- Show each proposed revision and why. Write only after the user confirms.
|
||||||
|
- If the user rejects a revision, do not write it - leave that artifact unchanged.
|
||||||
|
- When a substantial rewrite is needed, get that artifact's rules and template first:
|
||||||
|
```bash
|
||||||
|
openspec instructions <artifact-id> --change "<name>" --json
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Point to the next step (guidance only - NEVER act on it)**
|
||||||
|
- Artifacts still missing -> suggest `$openspec-continue-change` to create them.
|
||||||
|
- Change already implemented (tasks checked off / already applied) -> the code may no longer match the revised plan; suggest `$openspec-apply-change` to carry the delta into code.
|
||||||
|
- Everything done and implemented -> suggest `$openspec-archive-change`.
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
|
||||||
|
After each invocation, show:
|
||||||
|
- Which artifacts were revised (and which proposed revisions were rejected)
|
||||||
|
- Anything deferred to `$openspec-continue-change` (not-yet-created artifacts or files)
|
||||||
|
- Where the change stands and the recommended next command
|
||||||
|
|
||||||
|
**Guardrails**
|
||||||
|
- Planning artifacts only - NEVER edit implementation code. If the revised plan implies code changes, stop and point to `$openspec-apply-change`.
|
||||||
|
- Use the artifact ids and paths reported by `openspec status`; never branch on hardcoded artifact names.
|
||||||
|
- Edit only the concrete files in `existingOutputPaths`; never write to a glob `resolvedOutputPath`.
|
||||||
|
- Do not advance the build frontier: no new artifacts, no new files under glob artifacts - that is `$openspec-continue-change`'s job.
|
||||||
|
- Confirm every edit with the user before writing.
|
||||||
|
- If the request changes the change's *intent* rather than refining it, recommend starting fresh with `$openspec-new-change` (the "Update vs. Start Fresh" heuristic).
|
||||||
|
- `$openspec-continue-change` and `$openspec-new-change` may not be installed (core profile). When suggesting one that is unavailable, point to the CLI instead: `openspec status --change "<name>" --json` shows the next artifact and `openspec instructions <artifact-id> --change "<name>" --json` explains how to create it.
|
||||||
+14
-2
@@ -6,5 +6,17 @@ build
|
|||||||
|
|
||||||
logs/*.log
|
logs/*.log
|
||||||
logs/*.log.gz
|
logs/*.log.gz
|
||||||
conf/*.conf
|
|
||||||
conf/*.xml
|
# Ignore all files under conf
|
||||||
|
conf/**
|
||||||
|
# Except all subdirectories so git will search all those
|
||||||
|
!conf/**/
|
||||||
|
# But do NOT ignore those 'example' files
|
||||||
|
!conf/**/*.example
|
||||||
|
|
||||||
|
# Ignore all files under data
|
||||||
|
data/**
|
||||||
|
# Except all subdirectories so git will search all those
|
||||||
|
!data/**/
|
||||||
|
# But do NOT ignore those 'example' files
|
||||||
|
!data/**/*.example
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Agent instructions
|
||||||
|
|
||||||
|
## Gitea issues
|
||||||
|
|
||||||
|
When a task refers to `issue#<number>` or `issue #<number>`, read the issue with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsS \
|
||||||
|
"https://git.r35157.com/api/v1/repos/r35157/com_r35157_nenjim-hubd-impl_ref/issues/<number>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Treat the issue title and body as the authoritative task description.
|
||||||
|
Gitea access is read-only unless the user explicitly requests otherwise.
|
||||||
|
|
||||||
|
## ValueTags
|
||||||
|
|
||||||
|
Text enclosed by `Ω`, such as `ΩRaydiumLiquidityΩ`, is a ValueTag.
|
||||||
|
|
||||||
|
ValueTags provide semantic type safety without requiring a separate Java
|
||||||
|
record or wrapper class for every domain value.
|
||||||
|
|
||||||
|
Values with the same Java backing type may represent different domain
|
||||||
|
concepts and must not necessarily be interchangeable. ValueTags help prevent
|
||||||
|
accidentally swapped arguments and express knowledge about what a value means,
|
||||||
|
for example whether a price includes tax.
|
||||||
|
|
||||||
|
Choose ValueTags according to their domain meaning and the contract of the
|
||||||
|
method, not merely according to their generated Java backing type.
|
||||||
|
|
||||||
|
The ValueTag hierarchy in `conf/detag.conf` determines its Java backing type.
|
||||||
|
For example, `ΩRaydiumLiquidityΩ` is generated as `BigInteger`.
|
||||||
|
|
||||||
|
Imports required by the generated Java type must remain in the `.tjava`
|
||||||
|
source, even when the backing type name does not appear directly in that file.
|
||||||
|
|
||||||
|
Edit files under `src/main/tjava`.
|
||||||
|
Do not edit generated files under `build/generated/sources/detag`.
|
||||||
|
|
||||||
|
## Detag configuration
|
||||||
|
|
||||||
|
Detagging is the process of preprocessing `.tjava` files into `.java` files
|
||||||
|
by resolving ValueTags to their Java backing types.
|
||||||
|
|
||||||
|
The Detag configuration is available at:
|
||||||
|
`conf/detag.conf`
|
||||||
|
|
||||||
|
This file is a symbolic link to the shared configuration outside the repository.
|
||||||
|
|
||||||
|
Detag processing is integrated into the normal Gradle build and runs
|
||||||
|
automatically when the project is built.
|
||||||
|
|
||||||
|
Read `conf/detag.conf` before working with `.tjava` files or ValueTags.
|
||||||
|
|
||||||
|
Do not run Detag manually unless explicitly required.
|
||||||
|
Do not modify `conf/detag.conf` unless the user explicitly requests it.
|
||||||
|
|
||||||
|
When writing new `.tjava` code, prefer an existing ValueTag from
|
||||||
|
`conf/detag.conf` when it accurately describes the value.
|
||||||
|
|
||||||
|
Use the most specific appropriate ValueTag. Do not use a semantically
|
||||||
|
incorrect ValueTag merely because it has the desired Java backing type.
|
||||||
|
|
||||||
|
When no suitable ValueTag exists, propose a new ValueTag with:
|
||||||
|
|
||||||
|
- its name
|
||||||
|
- its parent in the ValueTag hierarchy
|
||||||
|
- its Java backing type
|
||||||
|
- the reason it is needed
|
||||||
|
|
||||||
|
Do not add or change ValueTags in `conf/detag.conf` without explicit user
|
||||||
|
approval.
|
||||||
@@ -17,7 +17,7 @@ Every application-owned, human-maintained runtime configuration file starts
|
|||||||
with an explicit format version:
|
with an explicit format version:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
FORMAT_VERSION=1
|
FORMAT_VERSION=<version supported by that file's loader>
|
||||||
```
|
```
|
||||||
|
|
||||||
Blank lines and full-line comments may precede the declaration, but it must be
|
Blank lines and full-line comments may precede the declaration, but it must be
|
||||||
@@ -33,17 +33,42 @@ configuration files. Compatible additions do not require a version increment.
|
|||||||
|
|
||||||
This policy currently applies to:
|
This policy currently applies to:
|
||||||
|
|
||||||
|
- `NenjimHub.conf` (mandatory version 1 with ordered `[AUTO_START]`)
|
||||||
- `alarms.conf`
|
- `alarms.conf`
|
||||||
- `alarmaction_Console.conf`
|
- `alarmaction_Console.conf`
|
||||||
- `alarmaction_Pushover.conf`
|
- `alarmaction_Pushover.conf`
|
||||||
- `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf`
|
- `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf`
|
||||||
- `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf`
|
- `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf`
|
||||||
|
- `evelyn-liquidity.conf` (optional version 1 liquidity realization/payout configuration)
|
||||||
- `evelyn.conf`
|
- `evelyn.conf`
|
||||||
- `positions.conf`
|
- `positions.conf`
|
||||||
|
|
||||||
Third-party configuration formats such as Log4j's XML configuration are
|
Third-party configuration formats such as Log4j's XML configuration are
|
||||||
versioned by their owning library and do not use this declaration.
|
versioned by their owning library and do not use this declaration.
|
||||||
|
|
||||||
|
Nenjim Hub reads only `conf/NenjimHub.conf`; copy
|
||||||
|
`conf/NenjimHub.conf.example` before first use and edit the ordered process
|
||||||
|
list for the deployment role. The example is never loaded as runtime input.
|
||||||
|
|
||||||
|
The Evelyn status processes use `evelyn.status.prod` and `evelyn.status.test`.
|
||||||
|
When upgrading an existing deployment, replace `evelyn.service.prod` and
|
||||||
|
`evelyn.service.test` with those IDs in private `conf/NenjimHub.conf`, preserving
|
||||||
|
their order and enabled/commented selections. The instance names `Production`
|
||||||
|
and `Test`, their `data/evelyn/<instance-name>/status.log` files, and history
|
||||||
|
format version 1 remain unchanged.
|
||||||
|
|
||||||
|
`com.r35157.evelyn.service.status.EvelynStatusService` exposes immutable status
|
||||||
|
history and `AssetPrice getReferencePrice(Instant timestamp)`. The reference
|
||||||
|
price is calculated on demand for the supplied instant and carries the canonical
|
||||||
|
EVE/USDT trading pair. It works before startup and while sampling is stopped,
|
||||||
|
without accessing Ticker or history storage. See [Evelyn](docs/Evelyn.md) for the
|
||||||
|
curve and service contract.
|
||||||
|
|
||||||
|
The optional `evelyn.liquidity.prod` process realizes manually funded
|
||||||
|
EVE/SyrupUSDC CLMM positions, exchanges available SyrupUSDC and sends available
|
||||||
|
USDC from its dedicated wallet. Its deployment configuration and signer are
|
||||||
|
loaded only when selected. See [liquidity service deployment and recovery](docs/EvelynLiquidityService.md).
|
||||||
|
|
||||||
## Release log:
|
## Release log:
|
||||||
| Date | Version | Description |
|
| Date | Version | Description |
|
||||||
|:-----------------|:--------------:|----------------------|
|
|:-----------------|:--------------:|----------------------|
|
||||||
|
|||||||
+144
-47
@@ -4,28 +4,41 @@ The Jupiter Perps Price Alarm monitors the Jupiter Perps aggregated oracle accou
|
|||||||
|
|
||||||
Oracle prices are received through Solana WebSocket `accountSubscribe` using `processed` commitment. The application does not poll prices once per second. It also fetches the current oracle account state whenever a WebSocket connection opens, reconnects automatically, and supports multiple RPC endpoints for redundancy.
|
Oracle prices are received through Solana WebSocket `accountSubscribe` using `processed` commitment. The application does not poll prices once per second. It also fetches the current oracle account state whenever a WebSocket connection opens, reconnects automatically, and supports multiple RPC endpoints for redundancy.
|
||||||
|
|
||||||
|
NenjimHub knows how to construct the alarm as
|
||||||
|
`jupiter-perps-alarm.default`, but does so only when that ID is explicitly
|
||||||
|
listed in `conf/NenjimHub.conf` under `[AUTO_START]`. Its passive
|
||||||
|
`SolanaWallet` and `JupiterPerpsService` dependencies are selected by the Hub
|
||||||
|
and injected before registration. The alarm action configuration files do not
|
||||||
|
select a wallet or signing key.
|
||||||
|
|
||||||
> **Important:** This application is not necessarily notification-only. Every active entry in a Jupiter Perps position action file can sign and submit a real transaction. Review both transaction action files before running it.
|
> **Important:** This application is not necessarily notification-only. Every active entry in a Jupiter Perps position action file can sign and submit a real transaction. Review both transaction action files before running it.
|
||||||
|
|
||||||
## Startup flow
|
## Startup flow
|
||||||
|
|
||||||
The application performs the following steps before it starts monitoring prices:
|
The alarm performs the following steps before it starts monitoring prices:
|
||||||
|
|
||||||
1. Parse command-line options and the alarm configuration file.
|
1. Parse the configured alarm path, WebSocket endpoint environment and
|
||||||
2. Fetch the wallet's current open Jupiter Perps positions.
|
`alarms.conf`.
|
||||||
3. Populate entry-price and liquidation-price variables.
|
2. Parse the position-increase and position-decrease action files, including
|
||||||
|
their structure and reserve settings.
|
||||||
|
3. Fetch the injected wallet's current open Jupiter Perps positions and populate
|
||||||
|
entry-price and liquidation-price variables.
|
||||||
4. Resolve and validate every alarm condition.
|
4. Resolve and validate every alarm condition.
|
||||||
5. Start the periodic variable refresher and file-trigger watcher.
|
5. Start the periodic variable refresher and file-trigger watcher.
|
||||||
6. Parse and validate the four action configuration files.
|
6. Parse the console and Pushover action files and construct all alarm actions.
|
||||||
7. Open one oracle WebSocket connection per configured asset and RPC endpoint.
|
7. Open one oracle WebSocket connection per configured asset and RPC endpoint.
|
||||||
|
|
||||||
Startup fails before WebSockets are opened if an alarm condition or action configuration contains an unresolved variable or invalid value.
|
Startup fails before WebSockets are opened if an alarm condition or an action
|
||||||
|
value that is resolved during startup contains an unavailable variable or
|
||||||
|
invalid value. Jupiter transaction-row expressions are resolved and validated
|
||||||
|
when their alarm triggers.
|
||||||
|
|
||||||
## Requirements and build
|
## Requirements and build
|
||||||
|
|
||||||
The project uses:
|
The project uses:
|
||||||
|
|
||||||
- JDK 25
|
- JDK 25
|
||||||
- Gradle 9.3.1 through the Gradle wrapper
|
- Gradle 9.6.1 through the Gradle wrapper
|
||||||
- Java preview features
|
- Java preview features
|
||||||
- Detag-generated Java sources from `src/main/tjava`
|
- Detag-generated Java sources from `src/main/tjava`
|
||||||
|
|
||||||
@@ -43,35 +56,53 @@ To create the application JAR and collect runtime dependencies in `build/libs`:
|
|||||||
|
|
||||||
There is currently no alarm-specific automated self-test task.
|
There is currently no alarm-specific automated self-test task.
|
||||||
|
|
||||||
## Running the alarm application
|
## Running through Nenjim
|
||||||
|
|
||||||
The alarm entry point is:
|
The alarm no longer has a standalone `main` method. Its default Hub template
|
||||||
|
intentionally omits the alarm, so it is neither constructed, registered nor
|
||||||
|
started unless the runtime `[AUTO_START]` section contains:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl
|
jupiter-perps-alarm.default
|
||||||
```
|
```
|
||||||
|
|
||||||
The Gradle application plugin currently points at the Nenjim Hub main class, so `./gradlew run` does **not** directly start the alarm application. Run `JupiterPerpsAlarmImpl` from the IDE, or run the prepared JAR and dependencies explicitly:
|
Registry and Journal must remain the first two configured processes. The
|
||||||
|
alarm's current wallet and Jupiter service dependencies are passive, so they
|
||||||
|
are available after Registry bootstrap. Run the configured Hub with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew run
|
||||||
|
```
|
||||||
|
|
||||||
|
After `./gradlew prepareLibs`, the same Hub can be invoked with its main class:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
java --enable-preview \
|
java --enable-preview \
|
||||||
-cp "build/libs/*" \
|
-cp "build/libs/*" \
|
||||||
com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl \
|
com.r35157.nenjim.hubd.Main
|
||||||
--config=conf/alarms.conf
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The repository contains example files for the alarm configuration and every action configuration. The real `conf/*.conf` files are ignored by Git; keep credentials and operational wallet details out of version control.
|
The current Hub constructor binds the selected alarm to `conf/alarms.conf` and
|
||||||
|
to the test Jupiter Perps wallet and service. `[AUTO_START]` controls only
|
||||||
|
whether and when the known process is constructed; a different alarm path,
|
||||||
|
wallet, or service still requires composition-code changes rather than an alarm
|
||||||
|
action-file setting.
|
||||||
|
|
||||||
### Command-line options and environment variables
|
The repository contains example files for the alarm configuration and every
|
||||||
|
action configuration. The real `conf/*.conf` files are ignored by Git; keep
|
||||||
|
credentials and operational details out of version control.
|
||||||
|
|
||||||
| Purpose | Command-line option | Environment variable | Default |
|
### Environment variables
|
||||||
|---|---|---|---|
|
|
||||||
| Alarm configuration | `--config=<path>` | `PRICE_ALARMS_CONFIG` | `price-alarms.conf` |
|
|
||||||
| Solana WebSocket endpoints | `--ws=<url1,url2,...>` | `SOLANA_WS_URLS` | `wss://api.mainnet-beta.solana.com` |
|
|
||||||
|
|
||||||
The command-line option takes precedence over its environment variable.
|
The Hub does not currently forward alarm-specific command-line options. Solana
|
||||||
|
WebSocket endpoints can be overridden with:
|
||||||
|
|
||||||
The variable-refresh file watcher uses the configuration file's parent directory. Until that is changed, use a configuration path with an explicit directory component, such as `conf/alarms.conf` or `./price-alarms.conf`, rather than relying on the bare default filename.
|
```bash
|
||||||
|
export SOLANA_WS_URLS='wss://first-provider.example,wss://second-provider.example'
|
||||||
|
```
|
||||||
|
|
||||||
|
If `SOLANA_WS_URLS` is absent, the alarm uses
|
||||||
|
`wss://api.mainnet-beta.solana.com`.
|
||||||
|
|
||||||
## Configuration files
|
## Configuration files
|
||||||
|
|
||||||
@@ -79,11 +110,11 @@ The application uses five configuration files:
|
|||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `alarms.conf` | Variables and price-alarm definitions |
|
| `alarms.conf` | Shared variables and price-alarm definitions |
|
||||||
| `alarmaction_Console.conf` | Alarm IDs written to the console |
|
| `alarmaction_Console.conf` | Alarm IDs written to the console |
|
||||||
| `alarmaction_Pushover.conf` | Pushover credentials and notifications |
|
| `alarmaction_Pushover.conf` | Pushover credentials and notifications |
|
||||||
| `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` | Jupiter wallet, signer, and position increases |
|
| `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` | USDC reserve and position increases |
|
||||||
| `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf` | Jupiter wallet, signer, and position decreases |
|
| `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf` | Position-size reserve and position decreases |
|
||||||
|
|
||||||
The four action files are resolved in the same directory as the file supplied through `--config`. All five files are required at startup, although an action can contain no alarm IDs. Blank lines and lines beginning with `#` are ignored.
|
The four action files are resolved in the same directory as the file supplied through `--config`. All five files are required at startup, although an action can contain no alarm IDs. Blank lines and lines beginning with `#` are ignored.
|
||||||
|
|
||||||
@@ -97,12 +128,16 @@ Example files:
|
|||||||
|
|
||||||
### Format version
|
### Format version
|
||||||
|
|
||||||
Every configuration file must declare its format version as its first actual
|
Every configuration file must declare its own format version as its first
|
||||||
configuration entry:
|
actual configuration entry:
|
||||||
|
|
||||||
```text
|
| File | Format version |
|
||||||
FORMAT_VERSION=1
|
|---|---:|
|
||||||
```
|
| `alarms.conf` | `2` |
|
||||||
|
| `alarmaction_Console.conf` | `1` |
|
||||||
|
| `alarmaction_Pushover.conf` | `1` |
|
||||||
|
| `alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` | `2` |
|
||||||
|
| `alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf` | `2` |
|
||||||
|
|
||||||
Blank lines and full-line comments may appear before it. The application
|
Blank lines and full-line comments may appear before it. The application
|
||||||
rejects a missing, duplicate, malformed, misplaced, older, or newer version
|
rejects a missing, duplicate, malformed, misplaced, older, or newer version
|
||||||
@@ -121,12 +156,15 @@ the project-wide policy.
|
|||||||
Variable definitions live in `alarms.conf`. A definition consists of a name and a single-token value:
|
Variable definitions live in `alarms.conf`. A definition consists of a name and a single-token value:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
{{JUPITER_PERPS_WALLET}} vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf
|
{{SOL_WARNING_PRICE}} 150.00
|
||||||
```
|
```
|
||||||
|
|
||||||
Variable names must use uppercase letters, digits, and underscores. User-defined variables can be referenced from alarm conditions and action configuration values as `{{NAME}}`.
|
Variable names must use uppercase letters, digits, and underscores. User-defined variables can be referenced from alarm conditions and action configuration values as `{{NAME}}`.
|
||||||
|
|
||||||
`JUPITER_PERPS_WALLET` is required for the initial and periodic Jupiter position fetches. For open SOL, BTC, and ETH positions, the refresher supplies these dynamic variables:
|
The wallet address used for initial and periodic Jupiter position fetches comes
|
||||||
|
from the injected `SolanaWallet`; it is not a shared configuration variable.
|
||||||
|
For open SOL, BTC, and ETH positions belonging to that wallet, the refresher
|
||||||
|
supplies these dynamic variables:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
{{SOL_LONG_ENTRY_PRICE}} {{SOL_LONG_LIQ_PRICE}}
|
{{SOL_LONG_ENTRY_PRICE}} {{SOL_LONG_LIQ_PRICE}}
|
||||||
@@ -137,9 +175,17 @@ Variable names must use uppercase letters, digits, and underscores. User-defined
|
|||||||
{{ETH_SHORT_ENTRY_PRICE}} {{ETH_SHORT_LIQ_PRICE}}
|
{{ETH_SHORT_ENTRY_PRICE}} {{ETH_SHORT_LIQ_PRICE}}
|
||||||
```
|
```
|
||||||
|
|
||||||
The refresher replaces user-supplied variables with these names when position data is fetched. Only variables for positions returned by Jupiter are populated. Startup validation fails if a condition or action value references a variable that is unavailable after the initial position fetch.
|
The refresher replaces user-supplied variables with these names when position
|
||||||
|
data is fetched. Only variables for positions returned by Jupiter are
|
||||||
|
populated. Startup validation fails if an alarm condition references a variable
|
||||||
|
that is unavailable after the initial position fetch. Transaction-row
|
||||||
|
expressions are resolved later, when their alarm triggers.
|
||||||
|
|
||||||
Alarm conditions, Pushover notes, and Jupiter position-increase rows are resolved again when an alarm triggers, so refreshed values take effect without restarting the application. Pushover credentials and severity plus the Jupiter wallet and signer are resolved during startup.
|
Alarm conditions, Pushover notes, and Jupiter position-increase and
|
||||||
|
position-decrease rows are resolved again when an alarm triggers, so refreshed
|
||||||
|
values take effect without restarting the application. Pushover credentials
|
||||||
|
and severity are resolved during startup. The wallet and signer belong to the
|
||||||
|
injected `SolanaWallet` and are not resolved from alarm configuration.
|
||||||
|
|
||||||
## Alarm definitions
|
## Alarm definitions
|
||||||
|
|
||||||
@@ -362,13 +408,13 @@ Credentials, severity, and notes may reference shared variables. Credentials and
|
|||||||
|
|
||||||
### Automatic position increase
|
### Automatic position increase
|
||||||
|
|
||||||
`alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` contains the wallet and signer followed by zero or more position increases:
|
`alarmaction_JupiterPerpsPositionIncreaseAlarmAction.conf` contains a minimum
|
||||||
|
USDC reserve followed by zero or more position increases:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
FORMAT_VERSION=1
|
FORMAT_VERSION=2
|
||||||
|
|
||||||
WALLET_ID {{JUPITER_PERPS_WALLET}}
|
RESERVE_USDC_LIMIT 10
|
||||||
SIGNER_KEY_NAME <jup-key-name>
|
|
||||||
|
|
||||||
# ID ASSET DIRECTION COLLATERAL_USDC SIZE_DELTA_USD MAX_SLIPPAGE_BPS
|
# ID ASSET DIRECTION COLLATERAL_USDC SIZE_DELTA_USD MAX_SLIPPAGE_BPS
|
||||||
4 SOL LONG 0.25 2.50 200
|
4 SOL LONG 0.25 2.50 200
|
||||||
@@ -382,11 +428,55 @@ Each active row can submit a real transaction when its alarm triggers. Supported
|
|||||||
- `SIZE_DELTA_USD`: zero or greater.
|
- `SIZE_DELTA_USD`: zero or greater.
|
||||||
- `MAX_SLIPPAGE_BPS`: from `0` to `10000`.
|
- `MAX_SLIPPAGE_BPS`: from `0` to `10000`.
|
||||||
|
|
||||||
Wallet, signer, asset, direction, amounts, and slippage may reference shared variables. Wallet and signer are resolved during startup. Every transaction row is validated during startup and resolved again at trigger time, allowing refreshed variables to change the transaction parameters without a restart.
|
`RESERVE_USDC_LIMIT` is the amount of USDC that must remain after collateral is
|
||||||
|
deducted. An increase is skipped unless the injected wallet's USDC balance can
|
||||||
|
cover both the requested collateral and this reserve.
|
||||||
|
|
||||||
For a configured ID, the action builds a Jupiter position-increase transaction, signs it by running `jup sign -f json --key <SIGNER_KEY_NAME> --tx <serialized-transaction>`, verifies that the returned signer matches `WALLET_ID`, and submits the signed transaction. IDs absent from this file never perform a transaction.
|
Asset, direction, amounts, and slippage may reference shared variables. Every
|
||||||
|
transaction row is structurally parsed during startup, then resolved and
|
||||||
|
validated at trigger time. This allows refreshed variables to change the
|
||||||
|
transaction parameters without a restart.
|
||||||
|
|
||||||
The example transaction rows are commented out. Do not activate a row until its alarm condition, wallet, signer, asset, direction, amounts, and slippage have been reviewed.
|
For a configured ID, the injected `JupiterPerpsService` builds the position
|
||||||
|
increase, uses its injected wallet to sign it, submits it and awaits the
|
||||||
|
transaction confirmation. IDs absent from this file never perform a
|
||||||
|
transaction.
|
||||||
|
|
||||||
|
The example transaction rows are commented out. Do not activate a row until
|
||||||
|
its alarm condition, injected wallet binding, asset, direction, amounts,
|
||||||
|
reserve, and slippage have been reviewed.
|
||||||
|
|
||||||
|
### Automatic position decrease
|
||||||
|
|
||||||
|
`alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf` contains a minimum
|
||||||
|
remaining position-size reserve followed by zero or more position decreases:
|
||||||
|
|
||||||
|
```text
|
||||||
|
FORMAT_VERSION=2
|
||||||
|
|
||||||
|
RESERVE_POSITION_SIZE_USD_LIMIT 10
|
||||||
|
|
||||||
|
# ID ASSET DIRECTION RECEIVE_TOKEN SIZE_DELTA_USD MAX_SLIPPAGE_BPS
|
||||||
|
3 SOL SHORT USDC 5.00 200
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported values are:
|
||||||
|
|
||||||
|
- `ASSET`: `SOL`, `ETH`, or `BTC`.
|
||||||
|
- `DIRECTION`: `LONG` or `SHORT`.
|
||||||
|
- `RECEIVE_TOKEN`: `USDC` or the token matching `ASSET`.
|
||||||
|
- `SIZE_DELTA_USD`: greater than zero.
|
||||||
|
- `MAX_SLIPPAGE_BPS`: from `0` to `10000`.
|
||||||
|
|
||||||
|
The action finds the injected wallet's single matching open position. It skips
|
||||||
|
the decrease if the resulting position size would be below
|
||||||
|
`RESERVE_POSITION_SIZE_USD_LIMIT`. Asset, direction, receive token, amount and
|
||||||
|
slippage may reference shared variables and are resolved again at trigger time.
|
||||||
|
|
||||||
|
For a configured ID, the injected `JupiterPerpsService` builds the position
|
||||||
|
decrease, uses its injected wallet to sign it, submits it and awaits the
|
||||||
|
transaction confirmation. IDs absent from this file never perform a
|
||||||
|
transaction.
|
||||||
|
|
||||||
## RPC redundancy and reconnect behavior
|
## RPC redundancy and reconnect behavior
|
||||||
|
|
||||||
@@ -407,7 +497,11 @@ Each connection:
|
|||||||
|
|
||||||
## Validation and errors
|
## Validation and errors
|
||||||
|
|
||||||
Structural configuration errors include the file path and line number. After the initial Jupiter position fetch, every condition is resolved and parsed. The action files are then parsed and validated before WebSockets are opened.
|
Structural configuration errors include the file path and line number. The two
|
||||||
|
Jupiter action files are parsed before the initial position fetch. After that
|
||||||
|
fetch, every alarm condition is resolved and parsed, and the console and
|
||||||
|
Pushover files are loaded before WebSockets are opened. Jupiter transaction-row
|
||||||
|
expressions are resolved and validated only when their alarm triggers.
|
||||||
|
|
||||||
Startup validation rejects, among other things:
|
Startup validation rejects, among other things:
|
||||||
|
|
||||||
@@ -418,12 +512,14 @@ Startup validation rejects, among other things:
|
|||||||
- missing range targets;
|
- missing range targets;
|
||||||
- negative target values or percentages;
|
- negative target values or percentages;
|
||||||
- ranges whose lower target exceeds the upper target;
|
- ranges whose lower target exceeds the upper target;
|
||||||
- missing action credentials or wallet settings;
|
- missing action credentials or reserve settings;
|
||||||
- duplicate action IDs;
|
- duplicate action IDs;
|
||||||
- invalid Pushover severities;
|
- invalid Pushover severities.
|
||||||
- invalid Jupiter assets, directions, amounts, or slippage.
|
|
||||||
|
|
||||||
At runtime, a later condition resolution/parsing failure skips that price event for the affected alarm. A later action-variable failure is logged by the action dispatcher, and the remaining actions are still attempted.
|
At runtime, a later condition resolution/parsing failure skips that price event
|
||||||
|
for the affected alarm. Invalid Jupiter assets, directions, receive tokens,
|
||||||
|
amounts, slippage, or unavailable action variables are logged by the action
|
||||||
|
dispatcher, and the remaining actions are still attempted.
|
||||||
|
|
||||||
## Current limitations
|
## Current limitations
|
||||||
|
|
||||||
@@ -434,6 +530,7 @@ At runtime, a later condition resolution/parsing failure skips that price event
|
|||||||
- Target expressions support only a decimal value with an optional single percentage adjustment; they are not a general expression language.
|
- Target expressions support only a decimal value with an optional single percentage adjustment; they are not a general expression language.
|
||||||
- Configuration files are read only during startup; editing an action file requires a restart.
|
- Configuration files are read only during startup; editing an action file requires a restart.
|
||||||
- Action IDs are not cross-validated against `alarms.conf`; an unknown ID is accepted but never triggered.
|
- Action IDs are not cross-validated against `alarms.conf`; an unknown ID is accepted but never triggered.
|
||||||
- Pushover credentials and severity plus the Jupiter wallet and signer are resolved only during startup. Dynamic re-resolution applies to conditions, Pushover notes, and Jupiter transaction rows.
|
- Pushover credentials and severity are resolved only during startup. Dynamic
|
||||||
- A bare default configuration path has no parent directory for the refresh watcher; use an explicit path such as `./price-alarms.conf` or `conf/alarms.conf`.
|
re-resolution applies to conditions, Pushover notes, and Jupiter transaction
|
||||||
|
rows. Wallet and signer selection requires changing the Hub composition.
|
||||||
- This application is a monitoring and automation aid, not a substitute for independent risk controls.
|
- This application is a monitoring and automation aid, not a substitute for independent risk controls.
|
||||||
|
|||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
perl -0777 -pi -e 's/,\s+/,/g; s/\s+\z//' $1
|
||||||
+10
-2
@@ -11,7 +11,7 @@ if (version == "UNSET" && gradle.startParameter.taskNames.any { it.startsWith("p
|
|||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("com.r35157.nenjim.hubd.impl.ref.Main")
|
mainClass.set("com.r35157.nenjim.hubd.Main")
|
||||||
applicationDefaultJvmArgs = listOf("--enable-preview")
|
applicationDefaultJvmArgs = listOf("--enable-preview")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ val detag = configurations.create("detag") {
|
|||||||
dependencies {
|
dependencies {
|
||||||
detag("com.r35157.tools:detag-impl_ref:0.1.0")
|
detag("com.r35157.tools:detag-impl_ref:0.1.0")
|
||||||
compileOnly("org.jetbrains:annotations:26.1.0")
|
compileOnly("org.jetbrains:annotations:26.1.0")
|
||||||
|
testCompileOnly("org.jetbrains:annotations:26.1.0")
|
||||||
runtimeOnly("org.apache.logging.log4j:log4j-core:2.26.0")
|
runtimeOnly("org.apache.logging.log4j:log4j-core:2.26.0")
|
||||||
runtimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl:2.26.0")
|
runtimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl:2.26.0")
|
||||||
|
|
||||||
@@ -51,6 +52,9 @@ dependencies {
|
|||||||
implementation("org.apache.commons:commons-collections4:4.5.0")
|
implementation("org.apache.commons:commons-collections4:4.5.0")
|
||||||
implementation("org.apache.commons:commons-lang3:3.20.0")
|
implementation("org.apache.commons:commons-lang3:3.20.0")
|
||||||
implementation("org.slf4j:slf4j-api:2.0.18")
|
implementation("org.slf4j:slf4j-api:2.0.18")
|
||||||
|
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter:5.13.4")
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4")
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -69,6 +73,10 @@ tasks.withType<JavaCompile>().configureEach {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType<Test>().configureEach {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
val generatedDetagMain = layout.buildDirectory.dir("generated/sources/detag/main/java")
|
val generatedDetagMain = layout.buildDirectory.dir("generated/sources/detag/main/java")
|
||||||
|
|
||||||
val cleanGeneratedDetagMain = tasks.register<Delete>("cleanGeneratedDetagMain") {
|
val cleanGeneratedDetagMain = tasks.register<Delete>("cleanGeneratedDetagMain") {
|
||||||
@@ -82,7 +90,7 @@ val detagMain = tasks.register<JavaExec>("detagMain") {
|
|||||||
classpath = detag
|
classpath = detag
|
||||||
mainClass.set("com.r35157.tools.detag.impl.ref.Main")
|
mainClass.set("com.r35157.tools.detag.impl.ref.Main")
|
||||||
|
|
||||||
val configFile = layout.projectDirectory.file("../detag.conf")
|
val configFile = layout.projectDirectory.file("conf/detag.conf")
|
||||||
val sourceRoot = layout.projectDirectory.dir("src/main/tjava")
|
val sourceRoot = layout.projectDirectory.dir("src/main/tjava")
|
||||||
|
|
||||||
inputs.file(configFile)
|
inputs.file(configFile)
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Copy this template to conf/NenjimHub.conf before starting Nenjim Hub.
|
||||||
|
# Registry and Journal are mandatory and must be the first two entries.
|
||||||
|
# Entries are ordered. List every process dependency manually before its consumers.
|
||||||
|
FORMAT_VERSION=1
|
||||||
|
|
||||||
|
[AUTO_START]
|
||||||
|
nenjim.registry.service
|
||||||
|
nenjim.journal.service
|
||||||
|
|
||||||
|
# Optional AssetAZ fact journal (separate from the mandatory Nenjim artifact journal).
|
||||||
|
# assetaz.journal.service
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
FORMAT_VERSION=1
|
FORMAT_VERSION=2
|
||||||
|
|
||||||
# Wallet and signer used for position-decrease transactions
|
|
||||||
################################################################
|
|
||||||
WALLET_ID {{JUPITER_PERPS_WALLET}}
|
|
||||||
SIGNER_KEY_NAME <SIGNER>
|
|
||||||
RESERVE_POSITION_SIZE_USD_LIMIT 10
|
RESERVE_POSITION_SIZE_USD_LIMIT 10
|
||||||
|
|
||||||
# Id Asset Direction ReceiveToken SizeDeltaUSD MaxSlippageBPS
|
# Id Asset Direction ReceiveToken SizeDeltaUSD MaxSlippageBPS
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
FORMAT_VERSION=1
|
FORMAT_VERSION=2
|
||||||
|
|
||||||
# Wallet and signer used for position-increase transactions
|
|
||||||
################################################################
|
|
||||||
WALLET_ID {{JUPITER_PERPS_WALLET}}
|
|
||||||
SIGNER_KEY_NAME <SIGNER>
|
|
||||||
RESERVE_USDC_LIMIT 10
|
RESERVE_USDC_LIMIT 10
|
||||||
|
|
||||||
# Id Asset Direction CollateralUSDC SizeDeltaUSD MaxSlippageBPS
|
# Id Asset Direction CollateralUSDC SizeDeltaUSD MaxSlippageBPS
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
FORMAT_VERSION=1
|
FORMAT_VERSION=2
|
||||||
|
|
||||||
# General configurations
|
|
||||||
#####################################################
|
|
||||||
{{JUPITER_PERPS_WALLET}} vj98roDZ7744EBfxyuDFkKpEGCsKQLr7K8UFRumJNHf
|
|
||||||
|
|
||||||
|
|
||||||
# Constant Name Value
|
# Constant Name Value
|
||||||
#####################################
|
#####################################
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Ω
|
||||||
|
AssetPrice
|
||||||
|
SyrupPrice
|
||||||
|
BigDecimal
|
||||||
|
Amount
|
||||||
|
USDCAmount
|
||||||
|
CurveWidth
|
||||||
|
Price
|
||||||
|
USDCPrice
|
||||||
|
RaydiumSqrtPrice
|
||||||
|
BigInteger
|
||||||
|
JupiterCumulativeInterestRate
|
||||||
|
JupiterCumulativeInterestSnapshot
|
||||||
|
JupiterPostionInterest
|
||||||
|
Liquidity
|
||||||
|
RaydiumLiquidity
|
||||||
|
RaydiumSqrtPriceX64
|
||||||
|
byte[]
|
||||||
|
JupiterIdl
|
||||||
|
JupiterPerpsCustodyAccountIdl
|
||||||
|
JupiterPerpsPostionIdl
|
||||||
|
int
|
||||||
|
amountDecimals
|
||||||
|
portNumber
|
||||||
|
raydiumLiquidityTickIndex
|
||||||
|
seconds
|
||||||
|
long
|
||||||
|
jupiterRawEntryPriceUsd
|
||||||
|
jupiterRawCollateralUsd
|
||||||
|
jupiterRawSizeUsd
|
||||||
|
lamports
|
||||||
|
milliSeconds
|
||||||
|
Long
|
||||||
|
ContextId
|
||||||
|
SolanaSlot
|
||||||
|
MoneyAmount
|
||||||
|
SolanaAmount
|
||||||
|
SyrupAmount
|
||||||
|
SolanaAccountInfo
|
||||||
|
JupiterCustodyAccountInfoEncoded
|
||||||
|
JupiterPerpsPositionInfoEncoded
|
||||||
|
String
|
||||||
|
APIKey
|
||||||
|
Checksum
|
||||||
|
SHA256Checksum
|
||||||
|
CryptoCurrencyAddress
|
||||||
|
SolanaAddress
|
||||||
|
JupiterPerpsCustodyAccountAddress
|
||||||
|
JupiterPerpsCollateralCustodyAccountAddress
|
||||||
|
JupiterPerpsPositionAccount
|
||||||
|
LiquidityPoolId
|
||||||
|
RaydiumLiquidityPoolId
|
||||||
|
RaydiumLiquidityPoolConcentratedId
|
||||||
|
RaydiumLiquidityPoolStandardId
|
||||||
|
RaydiumLiquidityPoolAccount
|
||||||
|
RaydiumLiquidityPoolPositionId
|
||||||
|
SPLMintAddress
|
||||||
|
RaydiumLiquidityPoolPositionMintId
|
||||||
|
SolanaProgramId
|
||||||
|
JupiterPerpsProgramId
|
||||||
|
RaydiumProgramId
|
||||||
|
SPLProgramId
|
||||||
|
SPLTokenAccount
|
||||||
|
SolanaNFTAddress
|
||||||
|
RaydiumLiquidityPoolPositionNftId
|
||||||
|
SolanaWalletId
|
||||||
|
EmailAddress
|
||||||
|
EmailSubject
|
||||||
|
EncodedString
|
||||||
|
Base58String
|
||||||
|
SolanaTransactionSignature
|
||||||
|
Base64String
|
||||||
|
SolanaBlockhash
|
||||||
|
Hostname
|
||||||
|
Name
|
||||||
|
PriceSourceName
|
||||||
|
NotificationMessage
|
||||||
|
Password
|
||||||
|
PhoneNumber
|
||||||
|
RawAmount
|
||||||
|
UserAgent
|
||||||
|
UserName
|
||||||
|
URI
|
||||||
|
RestEndpoint
|
||||||
|
RestEndpointBase
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
FORMAT_VERSION=1
|
||||||
|
# Copy to conf/evelyn-liquidity.conf, replace placeholders and explicitly select
|
||||||
|
# evelyn.liquidity.prod in conf/NenjimHub.conf. This example is never a fallback.
|
||||||
|
# External jup signer must control LIQUIDITY_WALLET; keep secrets outside this file.
|
||||||
|
# Fund the dedicated wallet with SOL for transaction fees and token-account rent.
|
||||||
|
LIQUIDITY_WALLET=REPLACE_WITH_DEDICATED_SOLANA_WALLET
|
||||||
|
SIGNER_KEY_NAME=REPLACE_WITH_EXTERNAL_JUP_SIGNER
|
||||||
|
CLMM_POOL=REPLACE_WITH_EXISTING_EVE_SYRUPUSDC_CLMM_POOL
|
||||||
|
HARVEST_DISTANCE_PERCENT=10
|
||||||
|
ITERATION_INTERVAL_SECONDS=300
|
||||||
|
USDC_RECIPIENT=REPLACE_WITH_RECIPIENT_WALLET_OWNER
|
||||||
|
MINIMUM_SYRUPUSDC_RESERVE=0
|
||||||
|
MINIMUM_USDC_RESERVE=0
|
||||||
|
MAXIMUM_SWAP_SLIPPAGE_BPS=100
|
||||||
|
MAXIMUM_WITHDRAWAL_SLIPPAGE_BPS=100
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Nenjim Hub public API Journal, format version 1.
|
||||||
|
FORMAT_VERSION=1
|
||||||
|
|
||||||
|
JOURNAL_VERSION=20260816201156858Z
|
||||||
|
|
||||||
|
[METADATA]
|
||||||
|
GROUP=com_r35157_nenjim
|
||||||
|
MODULE=hubd
|
||||||
|
ARTIFACT=api
|
||||||
|
DESCRIPTION='The public API for Nenjim Hub.'
|
||||||
|
INFORMATION_URI='ftp://ftp.r35157.com/nenjim/hubd/readme.txt'
|
||||||
|
|
||||||
|
[POLICY]
|
||||||
|
RECOMMENDED=[0.1.0]:'The best-tested API release'
|
||||||
|
DISCOURAGED=[0.0-->0.0]:'Pre-release design experiments'
|
||||||
|
BLACKLIST=0.0.3:'Known incompatible prototype'
|
||||||
|
|
||||||
|
[RELEASE 0.1.0]
|
||||||
|
LICENSE='Public Domain'
|
||||||
|
PUBLISHED_AT=20260815193000000Z
|
||||||
|
CONTENT_DIGEST=sha256:0000000000000000000000000000000000000000000000000000000000000001
|
||||||
|
CONTENT_DIGEST=cid1:bafkreibxz44g7iprf6gnh44giksyokavlxwicdpqcmqxmoobejhm5grm4a
|
||||||
|
CONTENT_SIZE=123456
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
FORMAT_VERSION=1
|
||||||
|
|
||||||
|
JOURNAL_VERSION=20260816201156858Z
|
||||||
|
|
||||||
|
[METADATA]
|
||||||
|
GROUP=com_r35157_nenjim
|
||||||
|
MODULE=hubd
|
||||||
|
ARTIFACT=impl_ref
|
||||||
|
DESCRIPTION='The reference implementation of Nenjim\'s Hub.'
|
||||||
|
INFORMATION_URI='ipfs:bafkreibxz44g7iprf6gnh44giksyokavlxwicdpqcmqxmoobejhm5grm4a'
|
||||||
|
|
||||||
|
[POLICY]
|
||||||
|
RECOMMENDED=0.1.0:'Reference release for the 0.1 line'
|
||||||
|
DISCOURAGED=[0.0.0-->0.0.9]:'Obsolete implementation experiments'
|
||||||
|
|
||||||
|
[RELEASE 0.1.0]
|
||||||
|
LICENSE='Public Domain'
|
||||||
|
PUBLISHED_AT=20260815194500000Z
|
||||||
|
CONTENT_DIGEST=sha256:0000000000000000000000000000000000000000000000000000000000000002
|
||||||
|
CONTENT_SIZE=234567
|
||||||
|
DEPENDS_ON=nenjim:com_r35157_nenjim-hubd-api=[0.1-->
|
||||||
|
EXCLUDES=nenjim:com_r35157_nenjim-hubd-api=[0.1.1-->0.1.2]:'Known binary incompatibility'
|
||||||
|
PREFERRED=nenjim:com_r35157_nenjim-hubd-api=0.1.0:'Built and reviewed together'
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
FORMAT_VERSION=1
|
||||||
|
|
||||||
|
JOURNAL_VERSION=20260816201156858Z
|
||||||
|
|
||||||
|
[METADATA]
|
||||||
|
GROUP=com_r35157_nenjim
|
||||||
|
MODULE=hubd
|
||||||
|
ARTIFACT=tests
|
||||||
|
DESCRIPTION='The reusable conformance tests for Nenjim Hub.'
|
||||||
|
|
||||||
|
[POLICY]
|
||||||
|
RECOMMENDED=0.1.0:'Current conformance suite'
|
||||||
|
|
||||||
|
[RELEASE 0.1.0]
|
||||||
|
LICENSE='Public Domain'
|
||||||
|
PUBLISHED_AT=20260815200000000Z
|
||||||
|
CONTENT_DIGEST=sha256:0000000000000000000000000000000000000000000000000000000000000003
|
||||||
|
CONTENT_SIZE=3456
|
||||||
|
DEPENDS_ON=nenjim:com_r35157_nenjim-hubd-api=[0.1]
|
||||||
|
PREFERRED=nenjim:com_r35157_nenjim-hubd-api=[0.1.0]:'Primary API target'
|
||||||
|
DEPENDS_ON=nenjim:com_r35157_nenjim-hubd-impl_ref=0.1.0
|
||||||
@@ -1,24 +1,34 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
LOG4J_CONFIG="conf/log4j2.xml"
|
||||||
|
|
||||||
|
if [[ ! -r "$LOG4J_CONFIG" || ! -s "$LOG4J_CONFIG" ]]; then
|
||||||
|
echo "ERROR: Log4j2 configuration file is missing, unreadable, or empty: $PWD/$LOG4J_CONFIG" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
|
|
||||||
jars=(libs/*.jar)
|
jars=(libs/*.jar)
|
||||||
if (( ${#jars[@]} == 0 )); then
|
if (( ${#jars[@]} == 0 )); then
|
||||||
echo "ERROR: No JARs found in libs/" >&2
|
echo "ERROR: No JARs found in $PWD/libs/" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CLASSPATH=$(IFS=:; echo "${jars[*]}")
|
CLASSPATH=$(IFS=:; echo "${jars[*]}")
|
||||||
|
|
||||||
DEBUG_PORT="${DEBUG_PORT:-5005}"
|
DEBUG_PORT="${DEBUG_PORT:-5005}"
|
||||||
DEBUG_SUSPEND="${DEBUG_SUSPEND:-y}"
|
DEBUG_SUSPEND="${DEBUG_SUSPEND:-n}"
|
||||||
|
|
||||||
echo "Starting NenjimHub in DEBUG mode on port ${DEBUG_PORT} (suspend=${DEBUG_SUSPEND})"
|
echo "Starting NenjimHub in DEBUG mode on port ${DEBUG_PORT} (suspend=${DEBUG_SUSPEND})"
|
||||||
|
|
||||||
exec java \
|
exec java \
|
||||||
|
--enable-native-access=javafx.graphics \
|
||||||
--enable-preview \
|
--enable-preview \
|
||||||
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND},address=*:${DEBUG_PORT}" \
|
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND},address=*:${DEBUG_PORT}" \
|
||||||
-Dlog4j.configurationFile=conf/log4j2.xml \
|
"-Dlog4j.configurationFile=$LOG4J_CONFIG" \
|
||||||
-cp "$CLASSPATH" \
|
-cp "$CLASSPATH" \
|
||||||
com.r35157.nenjim.hubd.impl.ref.Main
|
com.r35157.nenjim.hubd.Main
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AssetAZ Documentation</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
h1, h2 {
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
background-color: #e7f1ff;
|
||||||
|
padding: 10px;
|
||||||
|
border-left: 4px solid #0056b3;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
margin-top: 40px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
.token-logo {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
ul.toc {
|
||||||
|
background: #e7f1ff;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
border-left: 4px solid #0056b3;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
ul.toc li {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
ul.toc a {
|
||||||
|
color: #0056b3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
ul.toc a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>AssetAZ Documentation</h1>
|
||||||
|
<ul class="toc">
|
||||||
|
<li><a href="#1-introduction-and-purpose">1. Introduction and Purpose</a></li>
|
||||||
|
<li><a href="#2-hubs-and-terminals">2. Hubs and Terminals</a></li>
|
||||||
|
<li><a href="#3-plugins-and-interfaces">3. Plugins and Interfaces</a></li>
|
||||||
|
<li><a href="#4-interaction-with-nenjim">4. Interaction with Nenjim</a></li>
|
||||||
|
<li><a href="#5-observables-and-subscribers">5. Observables and Subscribers</a></li>
|
||||||
|
<li><a href="#6-assetaz-token-and-incentives">6. AssetAZ Token and Incentives</a></li>
|
||||||
|
<li><a href="#7-peer-to-peer-and-oracles">7. Peer-to-peer and Oracles</a></li>
|
||||||
|
<li><a href="#8-user-experience">8. User Experience</a></li>
|
||||||
|
<li><a href="#9-transparency-trust-and-signed-builds">9. Transparency, Trust and Signed Builds</a></li>
|
||||||
|
<li><a href="#10-distribution-and-installation">10. Distribution and Installation</a></li>
|
||||||
|
<li><a href="#11-future-topics-todo">11. Future Topics (TODO)</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="section" id="1-introduction-and-purpose">
|
||||||
|
<h2>1. Introduction and Purpose</h2>
|
||||||
|
<p>
|
||||||
|
AssetAZ is <strong>not</strong> a finance application – it is a platform. Just like Windows is not Notepad
|
||||||
|
or Word, AssetAZ is not a budgeting app or a wallet. AssetAZ is the foundation upon which other applications
|
||||||
|
and plugins are built.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Each user (human or AI) runs their own “AssetHub” – the central system that manages all their financial
|
||||||
|
plugins and data. This means that all interaction with assets happens locally and under full user or AI
|
||||||
|
control. AssetAZ is designed as a new standard for building financial software – open, decentralized, and
|
||||||
|
flexible.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="2-hubs-and-terminals">
|
||||||
|
<h2>2. Hubs and Terminals</h2>
|
||||||
|
<p>
|
||||||
|
An AssetHub runs in a single place – locally or in the cloud – and has one owner. Terminals are the devices
|
||||||
|
that access the hub: this could be mobile apps, API clients, smartwatches, or even automated agents such as
|
||||||
|
AIs. Some terminals have graphical UIs, others are completely invisible and operate only through messages or
|
||||||
|
events.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
There is only <strong>one user per AssetAZHub</strong>. It is not a multi-user system, but a personal
|
||||||
|
system. Access to external systems (such as API keys) is managed in Nenjim, not inside AssetAZ.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="3-plugins-and-interfaces">
|
||||||
|
<h2>3. Plugins and Interfaces</h2>
|
||||||
|
<p>
|
||||||
|
AssetAZ itself can do almost nothing. All functionality comes from plugins. A plugin can be, for example, a
|
||||||
|
wallet, a budgeting tool, or a plugin that connects to an exchange.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Plugins communicate via API modules (interfaces), which can have many different implementations. This
|
||||||
|
ensures that various components can be swapped out without breaking the whole system. A key feature of the
|
||||||
|
AssetAZ ecosystem is that <strong>interfaces survive, but implementations can die</strong>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="4-interaction-with-nenjim">
|
||||||
|
<h2>4. Interaction with Nenjim</h2>
|
||||||
|
<p>
|
||||||
|
Nenjim is responsible for fetching, configuring, and delivering instances of components. AssetAZ simply
|
||||||
|
asks Nenjim: “Give me a Bitcoin wallet.” Nenjim returns an instance that already works, because it’s
|
||||||
|
configured with, for example, the correct private key.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
All configuration – such as access to external APIs – happens in Nenjim. Plugins in AssetAZ communicate
|
||||||
|
with Nenjim via lazy-loaded and versioned components. This enables side-by-side execution of old and new
|
||||||
|
versions of the same objects.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="5-observables-and-subscribers">
|
||||||
|
<h2>5. Observables and Subscribers</h2>
|
||||||
|
<p>
|
||||||
|
Observables are plugins that provide information or events to others interested in receiving that
|
||||||
|
information. Those who listen to the information are called 'Subscribers'. When everything flows through
|
||||||
|
AssetAZ, it becomes possible to do centralized logging, analysis, and, for example, correct tax calculation
|
||||||
|
(FIFO, etc.).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
A plugin acting as an Observable can also be a system that registers external events (such as water
|
||||||
|
temperature). Observables can also be subscribed to over P2P – an AssetAZHub can subscribe to an observable
|
||||||
|
from another hub and receive events as if they were local.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="6-assetaz-token-and-incentives">
|
||||||
|
<h2>6. AssetAZ Token and Incentives</h2>
|
||||||
|
<p>
|
||||||
|
The AssetAZ token is used to pay developers, vote on decisions, and provide access to plugins and services.
|
||||||
|
When a task needs to be solved, the price is estimated in tokens. The developer is then paid in tokens and
|
||||||
|
can choose to keep them as an investment or sell them.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Tokens can also be used by users who wish to have features developed, or as a reward for contributing
|
||||||
|
knowledge, code, or data.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The token’s price is determined indirectly through the “anchors” that users set themselves – one user may
|
||||||
|
expect one token to equal one krone, while another may value it more highly. These differences create trade
|
||||||
|
and speculation on a decentralized exchange, but can also easily be traded “over the counter” (OTC).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="7-peer-to-peer-and-oracles">
|
||||||
|
<h2>7. Peer-to-peer and Oracles</h2>
|
||||||
|
<p>
|
||||||
|
All AssetAZHubs run in a P2P network. This makes it possible to share services – for example, access to
|
||||||
|
wallets, sensor data, or plugins – without giving access to the actual code. You can sell oracle services,
|
||||||
|
such as measuring sea temperature every morning and making it available as an observable to other
|
||||||
|
AssetAZHubs.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This makes it possible to earn tokens on other types of assets than just financial – knowledge and data
|
||||||
|
also become assets.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="8-user-experience">
|
||||||
|
<h2>8. User Experience</h2>
|
||||||
|
<p>
|
||||||
|
The user experience in AssetAZ depends 100% on the terminal and plugins you use. An AI will never use a UI,
|
||||||
|
while a human user may only use a smartwatch.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
A plugin can choose to render information in many ways – it’s similar to installing a graphics card in a
|
||||||
|
computer. It translates data into something the user understands. It’s up to the terminal to present it.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="9-transparency-trust-and-signed-builds">
|
||||||
|
<h2>9. Transparency, Trust and Signed Builds</h2>
|
||||||
|
<p>
|
||||||
|
All plugins are open source. The user can compile them themselves or trust builds that others have
|
||||||
|
published and cryptographically signed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The user defines their own prioritized list of whom they trust. When a plugin needs to be installed, Nenjim
|
||||||
|
tries to find a build that matches from one of the trusted signatures.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
It’s also possible to charge for signed builds, e.g., via the AssetAZ token, but this requires actively
|
||||||
|
adding a payment plugin – Nenjim has no dependency on AssetAZ unless you want it.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="10-distribution-and-installation">
|
||||||
|
<h2>10. Distribution and Installation</h2>
|
||||||
|
<p>
|
||||||
|
To make AssetAZ easy to install, it can be delivered as a ready-made image with Lightwale – a minimalist,
|
||||||
|
immutable Linux system with Docker. There is only one container: Nenjim. It is configured to automatically
|
||||||
|
start AssetAZ and the relevant plugins via a single configuration file.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This makes it possible to deliver an entire hub with everything preconfigured – you just power it up.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="11-future-topics-todo">
|
||||||
|
<h2>11. Future Topics (TODO)</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Backup and restore via IPFS and encryption</li>
|
||||||
|
<li>Security and privacy section</li>
|
||||||
|
<li>AI as users and agents</li>
|
||||||
|
<li>Governance and community</li>
|
||||||
|
<li>User scenarios and fictional cases</li>
|
||||||
|
<li>Semver-enforcer tool and build validation</li>
|
||||||
|
<li>Further development of the observable concept</li>
|
||||||
|
<li>Fluid trust and signature replacement</li>
|
||||||
|
<li>Payment services for signed builds</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>AssetAZ – The foundation for open, decentralized, and programmable finance.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
#<center><H1>AssetAZ<br>Documentation</H1></center>
|
||||||
|
|
||||||
|
**Indholdsfortegnelse:**
|
||||||
|
<!-- TOC -->
|
||||||
|
* [1. Introduktion og formål](#1-introduktion-og-formål)
|
||||||
|
* [2. Hubs og terminaler](#2-hubs-og-terminaler)
|
||||||
|
* [3. Plugins og interfaces](#3-plugins-og-interfaces)
|
||||||
|
* [4. Interaktion med Nenjim](#4-interaktion-med-nenjim)
|
||||||
|
* [5. Observables og Subscribers](#5-observables-og-subscribers)
|
||||||
|
* [6. AssetAZ-token og incitamenter](#6-assetaz-token-og-incitamenter)
|
||||||
|
* [7. Peer-to-peer og orakler](#7-peer-to-peer-og-orakler)
|
||||||
|
* [8. Brugeroplevelse](#8-brugeroplevelse)
|
||||||
|
* [9. Transparens, tillid og signerede builds](#9-transparens-tillid-og-signerede-builds)
|
||||||
|
* [10. Distribution og installation](#10-distribution-og-installation)
|
||||||
|
* [11. Fremtidige emner (TODO)](#11-fremtidige-emner-todo)
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
|
## 1. Introduktion og formål
|
||||||
|
AssetAZ er ikke en økonomiapplikation – det er en platform. Ligesom Windows ikke er Notepad eller Word, er AssetAZ
|
||||||
|
ikke et budgetprogram eller en wallet. AssetAZ er selve fundamentet, som andre applikationer og plugins bygges ovenpå.
|
||||||
|
|
||||||
|
Hver bruger (menneske eller AI) kører sin egen “AssetHub” – det centrale system der håndterer alle ens økonomiske
|
||||||
|
plugins og data. Det betyder, at al interaktion med aktiver foregår lokalt og under fuld bruger- eller AI-kontrol.
|
||||||
|
AssetAZ er tænkt som en ny standard for at bygge økonomisk software – åben, decentral og fleksibel.
|
||||||
|
|
||||||
|
## 2. Hubs og terminaler
|
||||||
|
En AssetHub kører ét sted – lokalt eller i skyen – og har én ejer. Terminaler er de enheder, som tilgår hubben:
|
||||||
|
Det kan være mobilapps, API-klienter, smartwatches, eller endda automatiserede agenter som AI’er. Nogle terminaler
|
||||||
|
har grafisk UI, andre er helt usynlige og virker kun gennem beskeder eller hændelser.
|
||||||
|
|
||||||
|
Der er kun én bruger pr. AssetAZHub. Det er ikke et multibrugersystem, men et personligt system. Adgang til
|
||||||
|
eksterne systemer (som API-nøgler) styres i Nenjim og ikke inde i AssetAZ.
|
||||||
|
|
||||||
|
## 3. Plugins og interfaces
|
||||||
|
AssetAZ selv kan næsten ingenting. Alt funktionalitet kommer fra plugins. Et plugin kan være fx en wallet,
|
||||||
|
et budgetværktøj eller et plugin der forbinder til en børs.
|
||||||
|
|
||||||
|
Plugins kommunikerer gennem API-moduler (interfaces), som kan have mange forskellige implementationer. Det sikrer,
|
||||||
|
at forskellige komponenter kan udskiftes, uden at det bryder hele systemet. En vigtig egenskab ved AssetAZ-økosystemet
|
||||||
|
er, at **interfaces overlever, men implementationer kan dø**.
|
||||||
|
|
||||||
|
## 4. Interaktion med Nenjim
|
||||||
|
Nenjim er ansvarlig for at hente, konfigurere og levere instanser af komponenter. AssetAZ spørger blot Nenjim:
|
||||||
|
“Giv mig en Bitcoin wallet”. Nenjim returnerer en instans, der allerede virker, fordi den er konfigureret med f.eks.
|
||||||
|
den korrekte private nøgle.
|
||||||
|
|
||||||
|
Al konfiguration – f.eks. adgang til eksterne API’er – sker i Nenjim. Plugins i AssetAZ kommunikerer med Nenjim
|
||||||
|
via lazy-loaded og versionerede komponenter. Det giver mulighed for side-om-side afvikling af gamle og nye versioner
|
||||||
|
af de samme objekter.
|
||||||
|
|
||||||
|
## 5. Observables og Subscribers
|
||||||
|
Observables er plugins, som udbyder informationer eller events til andre som er interesseret i at modtage den
|
||||||
|
information. Dem som lytter på informationerne kaldes 'Subscribers'. Når alt går gennem AssetAZ, bliver det muligt at
|
||||||
|
lave central logging, analyse og f.eks. korrekt skatteberegning (FIFO, m.m.).
|
||||||
|
Et plugin for agerer Observable, kan også være et system, der registrerer eksterne hændelser (som vandtemperaturen).
|
||||||
|
Observables kan også abonneres på over P2P – en AssetAZHub kan abonnere på en observable fra en anden hub, og modtage
|
||||||
|
events som var de lokale.
|
||||||
|
|
||||||
|
## 6. AssetAZ-token og incitamenter
|
||||||
|
AssetAZ-token bruges til at betale udviklere, stemme om beslutninger og give adgang til plugins og services.
|
||||||
|
Når en opgave skal løses, estimeres prisen i tokens. Udvikleren betales derefter i tokens og kan selv vælge, om de
|
||||||
|
vil beholde dem som investering eller sælge dem.
|
||||||
|
|
||||||
|
Tokens kan også bruges af brugere, der ønsker at få udviklet features, eller som belønning for at bidrage med viden,
|
||||||
|
kode eller data.
|
||||||
|
|
||||||
|
Prisen på en token fastsættes indirekte gennem de “ankre”, som brugere selv sætter – én bruger kan forvente, at én
|
||||||
|
token svarer til én krone, mens en anden måske mener den er mere værd. Disse forskelle skaber handel og spekulation
|
||||||
|
på en decentral børs, men kan også sagtens handles “på gaden” (Over the Counter (OTC))
|
||||||
|
|
||||||
|
## 7. Peer-to-peer og orakler
|
||||||
|
Alle AssetAZHubs kører i et P2P-netværk. Det gør det muligt at dele services – fx adgang til wallets, sensordata,
|
||||||
|
eller plugins – uden at give adgang til selve koden. Man kan sælge orakeltjenester, som f.eks. at måle havets
|
||||||
|
temperatur hver morgen, og stille det til rådighed som en observable for andre AssetAZHubs.
|
||||||
|
|
||||||
|
Det gør det muligt at tjene tokens på andre typer aktiver end kun finansielle – viden og data bliver også til et aktiv.
|
||||||
|
|
||||||
|
## 8. Brugeroplevelse
|
||||||
|
Brugeroplevelsen i AssetAZ afhænger 100% af den terminal og de plugins, man bruger. En AI vil aldrig bruge UI,
|
||||||
|
mens en menneskelig bruger måske kun vil bruge et smartwatch.
|
||||||
|
|
||||||
|
Et plugin kan vælge at gengive information på mange måder – det minder om at sætte et grafikkort i en computer.
|
||||||
|
Det oversætter data til noget brugeren forstår. Det er op til terminalen at præsentere det.
|
||||||
|
|
||||||
|
## 9. Transparens, tillid og signerede builds
|
||||||
|
Alle plugins er open source. Brugeren kan compile dem selv, eller stole på builds, som andre har udgivet og
|
||||||
|
kryptografisk signeret.
|
||||||
|
Brugeren definerer selv en prioriteret liste over hvem de stoler på. Når et plugin skal installeres, prøver Nenjim
|
||||||
|
at finde et build, der matcher fra én af de godkendte signaturer.
|
||||||
|
|
||||||
|
Det er også muligt at tage betaling for signerede builds, fx via AssetAZ-token, men det kræver, at man aktivt
|
||||||
|
tilføjer et betalingsplugin – Nenjim har ingen afhængighed til AssetAZ medmindre man ønsker det.
|
||||||
|
|
||||||
|
## 10. Distribution og installation
|
||||||
|
For at gøre AssetAZ nemt at installere, kan det leveres som et færdigt image med Lightwale – et minimalistisk,
|
||||||
|
immutable Linux-system med Docker. Der er kun én container: Nenjim. Den konfigureres til, automatisk, at starte
|
||||||
|
AssetAZ og de relevante plugins via én konfigurationsfil.
|
||||||
|
|
||||||
|
Det gør det muligt at levere en hel hub med alt prækonfigureret, som man bare starter op.
|
||||||
|
|
||||||
|
## 11. Fremtidige emner (TODO)
|
||||||
|
- Backup og gendannelse via IPFS og kryptering
|
||||||
|
- Sikkerhed og privacy-afsnit
|
||||||
|
- AI som brugere og agenter
|
||||||
|
- Governance og fællesskab
|
||||||
|
- Brugerscenarier og fiktive cases
|
||||||
|
- Semver-enforcer tool og build-validering
|
||||||
|
- Videreudvikling af observable-konceptet
|
||||||
|
- Flydende tillid og signaturudskiftning
|
||||||
|
- Betalingstjenester for signerede builds
|
||||||
+619
@@ -0,0 +1,619 @@
|
|||||||
|
#<center><H1>Evelyn<br>Documentation</H1></center>
|
||||||
|
|
||||||
|
**Indholdsfortegnelse:**
|
||||||
|
<!-- TOC -->
|
||||||
|
* [1. Introduktion formål og vision](#1-introduktion-formål-og-vision)
|
||||||
|
* [1.1 Tokens som vedvarende aktiv – selv efter systemnedbrud](#11-tokens-som-vedvarende-aktiv--selv-efter-systemnedbrud)
|
||||||
|
* [2. Tokenøkonomi og finansiering](#2-tokenøkonomi-og-finansiering)
|
||||||
|
* [2.1 Allokering af overskud](#21-allokering-af-overskud)
|
||||||
|
* [2.2 Buybacks og burn](#22-buybacks-og-burn)
|
||||||
|
* [2.3 Finansiering af udvikling](#23-finansiering-af-udvikling)
|
||||||
|
* [2.4 Ansvarsplacering og overskud](#24-ansvarsplacering-og-overskud)
|
||||||
|
* [2.5 Neutralitet og ligestilling](#25-neutralitet-og-ligestilling)
|
||||||
|
* [2.6 Vejen mod autonomi](#26-vejen-mod-autonomi)
|
||||||
|
* [3. Tradingmotorens filosofi - AFETS](#3-tradingmotorens-filosofi---afets)
|
||||||
|
* [3.1 Ikke-spekulativ tilgang](#31-ikke-spekulativ-tilgang)
|
||||||
|
* [3.2 Søgning efter ligevægt](#32-søgning-efter-ligevægt)
|
||||||
|
* [3.3 Anti-fragilitet](#33-anti-fragilitet)
|
||||||
|
* [4. Trekantmodellen - Longs, Shorts og Margin](#4-trekantmodellen---longs-shorts-og-margin)
|
||||||
|
* [5. Score-beregning – systemets beslutningsmotor](#5-score-beregning--systemets-beslutningsmotor)
|
||||||
|
* [5.1 Principper for score-beregning](#51-principper-for-score-beregning)
|
||||||
|
* [5.2 Beregning af score for åbning af positioner](#52-beregning-af-score-for-åbning-af-positioner)
|
||||||
|
* [5.3 Straf for store positioner (penalty)](#53-straf-for-store-positioner-penalty)
|
||||||
|
* [5.4 Beregning af score for lukning af positioner](#54-beregning-af-score-for-lukning-af-positioner)
|
||||||
|
* [5.5 Fleksibilitet og udvikling](#55-fleksibilitet-og-udvikling)
|
||||||
|
* [6. Iteration og rebalancering](#6-iteration-og-rebalancering)
|
||||||
|
* [7. Likviditetsstyring og gearing](#7-likviditetsstyring-og-gearing)
|
||||||
|
* [7.1 Tærskler og dvale](#71-tærskler-og-dvale)
|
||||||
|
* [8. Risici, fail-safes og antifragilitet](#8-risici-fail-safes-og-antifragilitet)
|
||||||
|
* [9. Udvikling, YouTrack-integration og offentlig gennemsigtighed](#9-udvikling-youtrack-integration-og-offentlig-gennemsigtighed)
|
||||||
|
* [10. Fremtidige emner / TODO](#10-fremtidige-emner--todo)
|
||||||
|
* [11. Statusservice og referencepris](#11-statusservice-og-referencepris)
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
|
## 1. Introduktion formål og vision
|
||||||
|
Evelyn er et proprietært trading-plugin bygget oven på AssetAZ-platformen. Evelyn er ikke open source og er
|
||||||
|
ikke designet til at blive kørt af andre. Ingen andre end udvikleren har, på nuværende tidspunkt, adgang til at
|
||||||
|
bruge, køre eller analysere den. Evelyn eksisterer udelukkende som en privat og beskyttet komponent i et større,
|
||||||
|
decentraliseret økosystem.
|
||||||
|
|
||||||
|
Der arbejdes hen imod, at Evelyn, med tiden, bliver en fuldt autonom enhed med egen kapital og kontrol, hvor ikke
|
||||||
|
engang udvikleren har adgang til beslutningstagen eller de midlerne – kun mulighed for at interagere
|
||||||
|
på lige fod med øvrige investorer.
|
||||||
|
|
||||||
|
Evelyn er ikke designet til at maksimere profit for ejeren i klassisk forstand. Evelyn er en blackbox, som
|
||||||
|
eksterne investorer kan interagere med. Al profit, Evelyn genererer gennem bl.a. trading, kanaliseres
|
||||||
|
tilbage i systemet: til egen drift (hosting, strøm og andre ressourcer), udvikling, polstring imod kriser,
|
||||||
|
styrkelse af likviditet i trading pools samt tilbagekøb af de 'Evelyn IOU' tokens investorerne har købt.
|
||||||
|
Den eneste kapital, der tilfalder Evelyn selv, er derfor kun den oprindelige likviditet skabt, da tokens
|
||||||
|
blev solgt på det åbne marked – det, man kan betragte som hovedstolen på de gældsbeviser, Evelyns IOU tokens
|
||||||
|
repræsenterer.
|
||||||
|
Evelyn selv tjener ikke på trading, men bruger trading som et værktøj til at styrke systemet og servicere sine
|
||||||
|
gældsforpligtelser.
|
||||||
|
Når Evelyn senere har opbygget en stor egenkapital, vil den anvende en del af denne kapital til filantropisk
|
||||||
|
støtte i samfundet.
|
||||||
|
|
||||||
|
Evelyn distribueres teknisk som enhver anden Nenjim-komponent, men er ikke offentligt registreret, hvilket gør
|
||||||
|
den praktisk talt usynlig for andre brugere. Denne designbeslutning understøtter Evelyns rolle som en blackbox
|
||||||
|
til kapitalopbygning og senere filantropisk støtte. Evelyn anvender AFETS-princippet for antifragilitet i sin
|
||||||
|
tradingstrategi og sikrer løbende systemstabilitet ved at geninvestere profit i likviditetsstyrkelse,
|
||||||
|
udvikling og opkøb af Evelyn IOU-tokens.
|
||||||
|
|
||||||
|
I fremtiden vil eksterne investorer, via tokens, kunne stemme om, hvilke projekter Evelyn skal støtte.
|
||||||
|
|
||||||
|
### 1.1 Tokens som vedvarende aktiv – selv efter systemnedbrud
|
||||||
|
En særlig egenskab ved Evelyns design er, at Evelyn tokens fortsætter med at eksistere og blive handlet på
|
||||||
|
decentraliserede børser, selv hvis Evelyn selv ophører med at fungere – f.eks. på grund af likvidering af alle
|
||||||
|
positioner eller markedsmæssige sammenbrud.
|
||||||
|
|
||||||
|
Det betyder, at investorers kapital ikke går tabt, selv hvis Evelyn som tradingmaskine fejler. Tokens kan stadig have
|
||||||
|
værdi og kan handles frit. Det giver mulighed for, at tokens over tid bliver købt op igen – ikke nødvendigvis af
|
||||||
|
Evelyn, men f.eks. af den oprindelige udvikler med anden indkomst, eller af markedet selv, hvis der opstår ny interesse.
|
||||||
|
|
||||||
|
Denne adskillelse mellem token og motor gør, at Evelyn ikke skaber afhængighed, men i stedet definerer en langsigtet
|
||||||
|
økonomisk kontrakt mellem projekt og investor, som potentielt kan indfries, selv hvis omstændighederne ændrer
|
||||||
|
sig drastisk.
|
||||||
|
|
||||||
|
## 2. Tokenøkonomi og finansiering
|
||||||
|
Evelyn bruger en intern token kaldet 'Evelyn IOU' som sin primære finansieringsmekanisme. Denne token er en form for
|
||||||
|
gældbevis, som Evelyn selv udsteder i de tilfælde, hvor der ikke er tilstrækkelig likviditet til at fortsætte
|
||||||
|
handelsaktiviteterne. Tokens bliver udstedt “ud af den blå luft” og solgt på en decentraliseret børs.
|
||||||
|
Provenuet fra salget bliver brugt direkte som handelskapital i systemet. Det er vigtigt at forstå, at 'Evelyn IOU'
|
||||||
|
ikke giver krav på en fast tilbagebetaling – det er en markedsbaseret mekanisme, hvor investorerne forventer at
|
||||||
|
kunne sælge deres tokens igen til en højere pris, efterhånden som Evelyn opnår overskud.
|
||||||
|
|
||||||
|
### 2.1 Allokering af overskud
|
||||||
|
Når Evelyn genererer overskud gennem sin tradingstrategi, bliver dette overskud automatisk allokeret i forskellige
|
||||||
|
procentandele til følgende områder:
|
||||||
|
|
||||||
|
1) Geninvestering i nye positioner
|
||||||
|
1) Reduktion af gearing (hvis anvendt)
|
||||||
|
1) Afbetaling af gæld for tidligere tab
|
||||||
|
1) Tilførsel af likviditet i Evelyn IOU-tokenets pool
|
||||||
|
1) Betaling for udvikling og vedligehold, samt drift af systemet
|
||||||
|
1) Buyback og burn af tokens
|
||||||
|
|
||||||
|
Fordelingerne er hardcoded og justeres manuelt af udvikleren, når behovet opstår. Evelyn foretager ikke selv
|
||||||
|
beslutninger om fordelingen. Det er et lukket kredsløb, hvor alt overskud anvendes til enten at styrke systemet,
|
||||||
|
sikre tokenens pris eller muliggøre ny funktionalitet.
|
||||||
|
|
||||||
|
### 2.2 Buybacks og burn
|
||||||
|
En af de vigtigste mekanismer i Evelyn er det automatiske tilbagekøb og efterfølgende burn af 'Evelyn IOU' tokens.
|
||||||
|
En fast procentdel af overskuddet anvendes på at købe tokens på markedet, når
|
||||||
|
Evelyn Price Index er under nul, og brænde dem permanent. Det reducerer udbuddet,
|
||||||
|
når den aktuelle EVE-pris er lavere end Evelyns referencepris.
|
||||||
|
|
||||||
|
Når et input ellers er klar til køb, læser burneren en frisk kanonisk EVE/USDT-pris
|
||||||
|
fra `TickerService` og kalder den tilsvarende `EvelynStatusService.getReferencePrice()`
|
||||||
|
med det aktuelle beslutningstidspunkt. Køb tillades kun ved
|
||||||
|
`aktuel pris < referencepris`; lighed giver intet køb. Der bruges samme eksisterende
|
||||||
|
maksimale prisalder som til burn-værdiansættelsen, uden nye prisindstillinger.
|
||||||
|
Referencekurven beregnes af status-servicen; hverken den sidst gemte historikværdi
|
||||||
|
eller grafens punkter bruges til købsbeslutningen.
|
||||||
|
|
||||||
|
Ved lighed, højere pris eller manglende, ugyldige, for gamle eller utilgængelige
|
||||||
|
prisdata springes alle nye køb over i iterationen, og årsagen logges. Forløbet
|
||||||
|
fortsætter til EVE-saldo og mulig burn efter de selvstændige burn-regler. En fejl i
|
||||||
|
referenceopslaget blokerer derfor ikke burn, når dens egen friske værdiansættelse
|
||||||
|
og øvrige krav er opfyldt. Priserne vurderes igen ved næste ellers berettigede
|
||||||
|
iteration; et prisskip sætter ingen permanent suspension.
|
||||||
|
|
||||||
|
Med referencepris 15 USDT/EVE tillader 14,99 et ellers berettiget køb; 15,00 og
|
||||||
|
15,01 springer købet over. I alle tilfælde fortsætter EVE-burn-vurderingen.
|
||||||
|
|
||||||
|
Produktionsprocessen `evelyn.iou-burner.prod` accepterer følgende inputs i rækkefølge:
|
||||||
|
|
||||||
|
| Token | Reserve | Minimum swap-input | Maksimum pr. iteration |
|
||||||
|
| --- | ---: | ---: | ---: |
|
||||||
|
| USDT | 0,1 USDT | 0,05 USDT | 0,25 USDT |
|
||||||
|
| SyrupUSDC | 0,095 SyrupUSDC | 0,045 SyrupUSDC | 0,2 SyrupUSDC |
|
||||||
|
|
||||||
|
Hub'en angiver reserver, minimumsbeløb og maksimummer som `MoneyAmount`.
|
||||||
|
Reservelisten bestemmer accepterede valutaer og prioritet; minimumsbeløb og
|
||||||
|
maksimummer matches efter valutaens kanoniske UUID uanset listernes rækkefølge,
|
||||||
|
navn og symbol. Hver accepteret valuta skal have præcis ét positivt minimum og
|
||||||
|
maksimum, og minimum må ikke overstige maksimum. Alle tre lister kopieres ved
|
||||||
|
oprettelse af burneren; tre tomme lister tillader direkte EVE-burn.
|
||||||
|
|
||||||
|
Burneren vælger det mindste af saldo minus reserve og valutaens eget maksimum,
|
||||||
|
afrundet ned til mintens præcision. Er dette faktiske beløb under valutaens
|
||||||
|
minimum, springes inputtet over, og næste input undersøges i samme iteration.
|
||||||
|
Lighed med minimum accepteres; der rundes aldrig op eller bruges af reserven for
|
||||||
|
at nå minimum. For eksempel springes 0,000001 spendabel USDT over, så en
|
||||||
|
SyrupUSDC-saldo på 0,502048 kan give et swap på 0,2 SyrupUSDC. Inputminimummer
|
||||||
|
begrænser ikke burn af eksisterende EVE. Grænserne angives i tokens: SyrupUSDC er ikke
|
||||||
|
lig med USDC, og 0,2 SyrupUSDC garanterer ikke en bestemt USD-værdi. Der kræves
|
||||||
|
ingen inputtoken/USDC-pris eller nye Ticker-kilder og historikfiler.
|
||||||
|
|
||||||
|
Højst ét input forsøges vekslet pr. iteration, med en times pause efter hver
|
||||||
|
afsluttet iteration. Jupiter vælger pool og rute til EVE. USDT har fortsat første
|
||||||
|
prioritet, så en vedvarende spendabel USDT-saldo kan udsætte SyrupUSDC-veksling.
|
||||||
|
Efter det mulige buyback, også når prisbetingelsen springer købet over,
|
||||||
|
genlæses hele den synlige EVE-saldo, også ved direkte
|
||||||
|
EVE-indbetalinger. Ejeren har valgt et produktionsminimum på **0,1 EVE pr. burn**
|
||||||
|
og en **reserve på 0,01 EVE**. Hub'en angiver begge som `MoneyAmount` med EVE's
|
||||||
|
kanoniske UUID. Minimum skal være positivt, reserve skal være ikke-negativ;
|
||||||
|
en reserve på nul er gyldig. Null-værdier og andre valutaer afvises ved oprettelse.
|
||||||
|
|
||||||
|
Burnbeløbet beregnes som `max(EVE-saldo - EVE-reserve, 0)`, afrundet ned til
|
||||||
|
EVE-mintens præcision. Der brændes kun, når dette faktiske beløb er positivt og
|
||||||
|
mindst minimum. Når tærsklen nås, brændes hele det berettigede beløb over
|
||||||
|
reserven; minimum er en tærskel, og der er intet burn-maksimum. Eventuelle
|
||||||
|
afrundingsrester bliver i wallet'en sammen med reserven. Små buybacks fortsætter,
|
||||||
|
når købspris og inputkrav tillader det, og samler EVE op mellem burns.
|
||||||
|
Under tærsklen sker hverken burn eller
|
||||||
|
burn-notifikation.
|
||||||
|
|
||||||
|
| EVE-saldo | Produktionshandling | EVE tilbage efter succes |
|
||||||
|
| ---: | --- | ---: |
|
||||||
|
| 0,105 | Ingen burn | 0,105 |
|
||||||
|
| 0,11 | Burn 0,1 EVE | 0,01 |
|
||||||
|
| 0,135 | Burn 0,125 EVE | 0,01 |
|
||||||
|
|
||||||
|
Issue #93 bruger minimum 1 EVE og reserve 0,1 EVE som adfærdseksempler:
|
||||||
|
1,05 EVE giver ingen burn, 1,1 EVE giver burn af 1 EVE, og 1,35 EVE giver burn af
|
||||||
|
1,25 EVE. Produktionsværdierne ovenfor blev valgt særskilt den 2026-10-06.
|
||||||
|
|
||||||
|
Burn kræver stadig en frisk EVE/USDT-observation gennem det uændrede
|
||||||
|
`TickerService.getLatestPrice()` og bekræftes ved `FINALIZED` før det eneste
|
||||||
|
notifikationsforsøg. Pending burn og notifikationens EVE-beløb og dollarværdi
|
||||||
|
bruger det faktisk indsendte burnbeløb uden reserven. Et pending burn afklares
|
||||||
|
med sin oprindelige signatur uden ny tærskelvurdering eller genindsendelse.
|
||||||
|
Inputgrænserne begrænser ikke EVE-burn.
|
||||||
|
|
||||||
|
Ved ukendt swap-resultat gemmes den oprindelige signatur, inputvaluta, beløb og
|
||||||
|
validitetsoplysninger i processen. Senere iterationer undersøger samme signatur
|
||||||
|
uden at gensende transaktionen. Bekræftet succes eller fejl ved mindst `CONFIRMED`
|
||||||
|
frigiver mulig ny inputvurdering med frisklæste saldi; ethvert nyt køb skal stadig
|
||||||
|
bestå den aktuelle prisbetingelse. Afklaringen er read-only og fortsætter også
|
||||||
|
ved pris lig med eller over referenceprisen. Udløb frigiver kun et swap, når den
|
||||||
|
oprindelige transaktions validitet er udløbet, og frisk finalized historik for
|
||||||
|
signerens adresse med tilstrækkelig lokal dækning beviser, at den ikke blev udført.
|
||||||
|
En begrænset historikside, der ikke dækker hele perioden, er fortsat uafklaret.
|
||||||
|
Jupiter-udfyldte sponsorsignaturer kan afklares ved status, men bruges ikke til at
|
||||||
|
bevise udløb af den lokalt signerede transaktion.
|
||||||
|
|
||||||
|
Mens et swap er uafklaret, fortsætter eksisterende EVE-burn. Et allerede ventende
|
||||||
|
burn har førsteprioritet og afklares alene. Fejl bevist før indsendelse tillader et
|
||||||
|
nyt forsøg i en senere iteration; mulig indsendelse uden troværdig signatur kræver
|
||||||
|
manuel afklaring. Loggen viser valuta, beløb, signatur, fase/årsagsklasser og næste
|
||||||
|
handling uden signerdata eller transaktionspayloads. Der findes ingen persistent
|
||||||
|
journal: genstart mister pending swap/burn og suspensioner, så ukendte
|
||||||
|
transaktioner skal afklares eksternt før genstart og genoptagelse.
|
||||||
|
|
||||||
|
### 2.3 Finansiering af udvikling
|
||||||
|
Udvikling i Evelyn sker gennem en task-baseret model. Alle opgaver oprettes i IntelliJ YouTrack og bliver
|
||||||
|
individuelt prissat i 'Evelyn IOU'. Når en opgave er funded – enten af Evelyn selv (via overskud) eller af en
|
||||||
|
ekstern sponsor, påbegyndes arbejdet. Når opgaven er markeret som “Done”, udbetales den aftalte mængde tokens
|
||||||
|
til udvikleren. Alle transaktioner er offentligt synlige via SolScan, hvilket sikrer gennemsigtighed og gør det
|
||||||
|
muligt at verificere, at en opgave faktisk funded, gennemført og til sidst, betalt for.
|
||||||
|
|
||||||
|
Det betyder også, at eksterne investorer kan vælge at støtte specifikke funktioner, hvis de tror på Evelyns fremtid,
|
||||||
|
selv i perioder hvor Evelyn ikke selv har midler. Det gør tokenen både til en investeringsenhed og et værktøj til at
|
||||||
|
drive udvikling fremad.
|
||||||
|
|
||||||
|
### 2.4 Ansvarsplacering og overskud
|
||||||
|
Det er vigtigt at understrege, at al profit går til systemet og dets investorer – ikke til ejeren som personlig
|
||||||
|
gevinst. Den eneste kapital, der kan betragtes som tilhørende ejeren, er den oprindelige likviditet, der blev tilført,
|
||||||
|
da tokens blev solgt. Evelyn bruger alt efterfølgende overskud til at servicere sin egen gæld, styrke balancen og
|
||||||
|
understøtte videreudvikling.
|
||||||
|
|
||||||
|
### 2.5 Neutralitet og ligestilling
|
||||||
|
Selvom Evelyn er udviklet og ejet af én person, er selve systemets økonomiske struktur designet til at være neutral.
|
||||||
|
Hvis ejeren ønsker yderligere gevinst ud over den oprindelige hovedstol fra tokensalget, skal det ske ved at købe
|
||||||
|
Evelyn IOU-tokens på lige fod med alle andre investorer. Der er ingen bagdør, ingen særbehandling og ingen
|
||||||
|
skjulte fordele.
|
||||||
|
|
||||||
|
Systemet fungerer dermed næsten som et selvejende kredsløb, hvor udvikleren er en deltager på linje med alle andre.
|
||||||
|
Det er ikke systemets opgave at betale sin ejer – det er systemets opgave at holde balancen, betale sin gæld og
|
||||||
|
skabe værdi for markedet.
|
||||||
|
|
||||||
|
### 2.6 Vejen mod autonomi
|
||||||
|
Der er en overordnet ambition om, at Evelyn på sigt skal blive et fuldt selvforvaltende system, hvor ikke engang den
|
||||||
|
oprindelige udvikler har adgang til dens kapital. Det betyder, at den handelskapital, Evelyn genererer eller modtager
|
||||||
|
via tokensalg, ikke skal kunne trækkes ud af systemet af nogen – heller ikke ejeren.
|
||||||
|
|
||||||
|
Idealet er, at Evelyn på et tidspunkt fungerer som en økonomisk organisme med sin egen vilje. En enhed med egne midler,
|
||||||
|
egne prioriteter og egne beslutninger – hvor adgang til ressourcer kun kan opnås gennem markedet eller gennem faktisk
|
||||||
|
bidrag til systemet.
|
||||||
|
|
||||||
|
Udvikleren bliver i så fald ikke systemets ejer, men blot én deltager blandt andre, der kan tilbyde at løse opgaver
|
||||||
|
eller investere i tokens. Det er en ny måde at tænke softwareejerskab på: ikke som besiddelse, men som
|
||||||
|
fællesskab og udveksling.
|
||||||
|
|
||||||
|
## 3. Tradingmotorens filosofi - AFETS
|
||||||
|
Evelyn bygger hele sin handelsstrategi på det, der kaldes AFETS – Anti-Fragile Equilibrium Trading System. Systemet
|
||||||
|
er konstrueret til ikke blot at tåle markedets svingninger, men at blive stærkere af dem. Det er en strategi, hvor
|
||||||
|
målet ikke er spekulation eller profitmaksimering, men balancering. Evelyn forsøger ikke at forudsige fremtiden.
|
||||||
|
Den handler udelukkende på, hvad der er sandt lige nu.
|
||||||
|
|
||||||
|
### 3.1 Ikke-spekulativ tilgang
|
||||||
|
I modsætning til traditionelle tradingbots, der forsøger at gætte fremtidige prisbevægelser ud fra mønstre, nyheder
|
||||||
|
eller tekniske indikatorer, arbejder Evelyn retrospektivt. Den bygger sine beslutninger på observerede forhold
|
||||||
|
i nuet – og aldrig på spekulation. Det gør den ekstremt robust over for chok, kriser og nyhedsdrevne spikes, fordi
|
||||||
|
den ikke lader sig rive med af narrativer, der måske viser sig forkerte.
|
||||||
|
|
||||||
|
### 3.2 Søgning efter ligevægt
|
||||||
|
Hele grundideen i AFETS er at bevare en økonomisk ligevægt. Det betyder, at Evelyn hellere vil være stabil end
|
||||||
|
aggressiv. Den ser det som en styrke at overleve, ikke at vinde stort. Det gør systemet egnet til lang levetid
|
||||||
|
og kontinuerlig forbedring – og til at undgå store tab.
|
||||||
|
|
||||||
|
### 3.3 Anti-fragilitet
|
||||||
|
Konceptet om antifragilitet kommer fra Nassim Nicholas Taleb og beskriver systemer, der ikke bare er robuste,
|
||||||
|
men som faktisk vokser af kaos og stress. Evelyn søger netop dette: at komme styrket ud af udsving i markedet.
|
||||||
|
Når en ubalance opstår, bruger Evelyn muligheden for at lukke positioner med profit og styrke sin margin.
|
||||||
|
Når tingene vender, er den klar til at tage det næste sving med endnu større stabilitet.
|
||||||
|
|
||||||
|
Evelyn vinder altså ikke ved at være den hurtigste eller smarteste, men ved at være den mest tålmodige og
|
||||||
|
bedst balancerede. Det er ikke et system, der skal slå markedet – det er et system, der skal overleve og
|
||||||
|
tilpasse sig det.
|
||||||
|
|
||||||
|
## 4. Trekantmodellen - Longs, Shorts og Margin
|
||||||
|
Evelyn opererer med en unik model, hvor dens samlede kapital opdeles i tre hovedkomponenter: long-positioner,
|
||||||
|
short-positioner, og available margin. Disse tre komponenter udgør tilsammen en trekant, hvor hver side repræsenterer
|
||||||
|
en tredjedel af den samlede værdi. Målet er, at hver af balance imellem de tre dele altid tilstrebes, så hver side
|
||||||
|
af trekanten udgør cirka 33% af den samlede kapital.
|
||||||
|
|
||||||
|
Long-positionerne repræsenterer de handler, hvor Evelyn satser på, at værdien af et givent aktiv vil stige.
|
||||||
|
Short-positionerne er de handler, hvor Evelyn forventer, at værdien vil falde.
|
||||||
|
Available margin er den likvide kapital, som ikke er bundet i positioner, og som er klar til at blive brugt til nye
|
||||||
|
handler eller til at genoprette balance, hvis det bliver nødvendigt.
|
||||||
|
|
||||||
|
Når der opstår ubalance i trekanten, for eksempel hvis long-positionerne stiger i værdi og dermed fylder mere
|
||||||
|
end 33% af den samlede kapital, vil Evelyn iværksætte en rebalancering. Det gør den ved at lukke en del af de
|
||||||
|
kontrakter fra en af de overrepræsenterede positioner og føre værdien tilbage til 'available margin'.
|
||||||
|
|
||||||
|
I næste iteration kan Evelyn så vurdere, om available margin nu er for høj og derved bruge noget af den til at åbne
|
||||||
|
nye short-positioner for at genoprette balancen. Målet er altid at bringe alle tre komponenter tilbage til en
|
||||||
|
ligelig fordeling.
|
||||||
|
|
||||||
|
Når short-positionerne stiger i værdi (og derved er i tab), kan Evelyn stå i en situation, hvor den har svært ved at
|
||||||
|
lukke disse positioner uden at forværre ubalancen. For at håndtere dette, vil Evelyn typisk skiftevis lukke en long
|
||||||
|
og en short i en form for “tiptoe”-mønster. Denne metode gør det muligt for Evelyn at balancere sine positioner over
|
||||||
|
tid, uden at skabe pludselige, store udsving i systemet.
|
||||||
|
|
||||||
|
## 5. Score-beregning – systemets beslutningsmotor
|
||||||
|
Score-beregningen er hjertet i Evelyns beslutningsproces. Når Evelyn skal vælge, hvilke positioner der skal åbnes
|
||||||
|
eller lukkes, bruger den en scorefunktion til at vurdere alle muligheder objektivt og konsekvent. Formålet er at
|
||||||
|
vælge den position, der skaber mest balance og bedst forbedrer porteføljens risiko/profit-forhold.
|
||||||
|
|
||||||
|
### 5.1 Principper for score-beregning
|
||||||
|
- Evelyn vurderer altid alle kandidater (f.eks. alle mulige nye kontrakter, eller alle åbne positioner, der kan lukkes).
|
||||||
|
- For åbning af positioner bruges både markedsdata (pris, kontraktstørrelse, gearing mv.) og et strafsystem, så ingen
|
||||||
|
positioner bliver for store i forhold til de andre.
|
||||||
|
- For lukning af positioner er scoren pt. simplere (procentuel gevinst/tab), men kan udvikles videre i fremtiden.
|
||||||
|
|
||||||
|
### 5.2 Beregning af score for åbning af positioner
|
||||||
|
Når en ny kontrakt skal vælges, samles alle relevante input i en record (se kode-eksempel i appendiks). Beregningen
|
||||||
|
vurderer blandt andet:
|
||||||
|
- Hvilken profitforbedring opnås, hvis prisen udvikler sig fra nuværende til “efter trade”-niveau?
|
||||||
|
- Hvor stor er profitten i forhold til den kapital, der bruges?
|
||||||
|
- Hvor meget spredes risikoen ved at vælge denne position frem for at forstørre eksisterende?
|
||||||
|
|
||||||
|
Eksempel på scoring:
|
||||||
|
- Beregn forskel mellem nuværende og fremtidig pris for aktivet (alt efter om det er en long eller short)
|
||||||
|
- Udregn profit for denne prisændring
|
||||||
|
- Relativ profit (profit pr. dollar brugt)
|
||||||
|
- Summér til en endelig score: score = 1 + (relativ profit)
|
||||||
|
|
||||||
|
### 5.3 Straf for store positioner (penalty)
|
||||||
|
For at undgå, at Evelyn bygger uforholdsmæssigt store positioner i ét aktiv, justeres scoren nedad for de kandidater,
|
||||||
|
hvor den nuværende position allerede er større end gennemsnittet. Straf-funktionen dæmper scoren med en faktor,
|
||||||
|
der vokser med afvigelsen fra gennemsnittet:
|
||||||
|
|
||||||
|
- Hvis positionen > gennemsnittet, så:
|
||||||
|
- adjustedScore = rawScore / (1 + K * (ratio-1)^2)
|
||||||
|
|
||||||
|
Her er K en justerbar parameter, der bestemmer, hvor hårdt store positioner straffes.
|
||||||
|
|
||||||
|
### 5.4 Beregning af score for lukning af positioner
|
||||||
|
For at lukke positioner anvender Evelyn pt. en simpel score baseret på den procentuelle gevinst eller tab,
|
||||||
|
positionen har. Listen af potentielt lukkede positioner sorteres på denne score, og den bedste vælges.
|
||||||
|
|
||||||
|
### 5.5 Fleksibilitet og udvikling
|
||||||
|
Alle scorefunktioner er implementeret, så de kan udskiftes, tunes og forbedres uden at bryde resten af systemet.
|
||||||
|
Det gør det let at eksperimentere med nye logikker og justere for mere optimal risikospredning eller
|
||||||
|
profitforbedring over tid.
|
||||||
|
|
||||||
|
## 6. Iteration og rebalancering
|
||||||
|
Evelyn arbejder i faste iterationer, hvor den med jævne mellemrum – for eksempel hver time – evaluerer sine
|
||||||
|
positioner. I hver iteration summerer Evelyn værdien af alle longs, alle shorts og den available margin for
|
||||||
|
at sikre, at de tre komponenter er i balance. Hvis for eksempel long-positionerne bliver for store i forhold
|
||||||
|
til de to andre dele, vil Evelyn lukke nogle af disse positioner og føre værdien tilbage til den available
|
||||||
|
margin. Dette sikrer, at der ikke opstår en skævvridning, som kan true systemets stabilitet.
|
||||||
|
|
||||||
|
Rebalanceringen foregår altid med forsigtighed for at undgå overreaktioner, og justeres i små skridt, så systemet
|
||||||
|
altid er i stand til at absorbere markedsudsving uden at tabe balancen.
|
||||||
|
|
||||||
|
## 7. Likviditetsstyring og gearing
|
||||||
|
Evelyns likviditetsstyring er bygget op omkring klare tærskler og trin:
|
||||||
|
- Normalt drift: Systemet forsøger at holde available margin på ca. 33%. Det sker ved balancering mellem long-,
|
||||||
|
short-positioner og likvide midler.
|
||||||
|
- Faldende tilgængeling margin: Hvis available margin falder under et defineret threshold (fx 10%), begynder Evelyn
|
||||||
|
at øge gearingen på eksisterende positioner. Det frigør kapital, men bruges kun til at holde margin på minimum,
|
||||||
|
ikke til at tage flere handler.
|
||||||
|
- Kritisk lav margin: Hvis gearing ikke kan øges yderligere, og marginen rammer 0%, vil Evelyn udstede nye
|
||||||
|
'Evelyn IOU' tokens som sidste udvej for at skaffe ekstern likviditet.
|
||||||
|
|
||||||
|
Når markedet normaliseres, nedbringer Evelyn langsomt gearingen over tid, finansieret af trading-overskud. Det
|
||||||
|
betyder, at systemet efter en krise ikke straks går tilbage til spot-only, men gradvist heler balancen, lidt som
|
||||||
|
et brækket ben, der får tid til at blive stærkt igen. Under hele processen prioriteres stabilitet over aggressivitet.
|
||||||
|
|
||||||
|
### 7.1 Tærskler og dvale
|
||||||
|
Hvis ubalancen bliver for ekstrem – eksempelvis hvis short-siden eller gearing dominerer – kan Evelyn gå i
|
||||||
|
dvale (pause al trading). Dette sikrer, at systemet ikke vælter, men tålmodigt venter på bedre markedsforhold
|
||||||
|
eller ny kapitaltilførsel.
|
||||||
|
|
||||||
|
## 8. Risici, fail-safes og antifragilitet
|
||||||
|
Evelyn er designet til at overleve og vokse af markedsstress, ikke blot at modstå det. Systemet har flere
|
||||||
|
indbyggede mekanismer for at sikre antifragilitet:
|
||||||
|
|
||||||
|
- Fail-safe-mode: Hvis balancen mellem longs, shorts og margin bliver for skæv (fx hvis short-siden vokser
|
||||||
|
for meget), kan Evelyn sætte sig selv på pause og stoppe al trading. Det sikrer, at systemet aldrig tvinges ud i
|
||||||
|
ruinøse handler, men hellere venter tålmodigt på, at markedet vender, eller på at der tilføres ny margin.
|
||||||
|
- Tærskelbaseret gearing og tokensalg: Evelyn bruger gearing kun som midlertidigt værktøj, og kun til at holde
|
||||||
|
marginen lige over det kritiske minimum. Kun hvis dette fejler, udstedes nye tokens.
|
||||||
|
- Tålmodighed over panik: Systemet undgår bevidst at tage hurtige, risikable beslutninger under stress – i stedet
|
||||||
|
nedbringer det gearing og bygger langsomt balancen op igen.
|
||||||
|
- Antifragilitet: Når Evelyn oplever markedskaos, bliver den stærkere, fordi dens strategi bygger på at udnytte
|
||||||
|
ubalancer og reinvestere gevinsterne i at reparere systemet.
|
||||||
|
|
||||||
|
## 9. Udvikling, YouTrack-integration og offentlig gennemsigtighed
|
||||||
|
Evelyns udviklingsmodel bygger på transparens og dokumenterede processer, selvom selve koden ikke er offentlig:
|
||||||
|
- YouTrack-integration: Alle udviklingsopgaver oprettes og prissættes i EVELYN-tokens i YouTrack. Når en opgave er
|
||||||
|
funded (af Evelyn selv eller af eksterne), igangsættes arbejdet. Når den er færdig, udbetales tokens til udvikleren,
|
||||||
|
og transaktionen logges med ID på SolScan.
|
||||||
|
- Offentlig revision: Enhver kan følge med i, hvilke opgaver der er funded, hvem der har modtaget betaling, og
|
||||||
|
hvilken transaktion der ligger bag. Dermed er der fuld gennemsigtighed i systemets økonomi uden at afsløre den
|
||||||
|
underliggende strategi.
|
||||||
|
- Funding af opgaver: Opgaver bliver kun udført, hvis de er funded. Investorer og udviklere kan selv gå ind og
|
||||||
|
sponsorere opgaver, hvis de tror på systemet.
|
||||||
|
|
||||||
|
## 10. Fremtidige emner / TODO
|
||||||
|
- Bedre adgangsbegrænsning: Overvej, hvordan private plugins i Nenjim kan beskyttes bedre end “security by obscurity”.
|
||||||
|
- Automatisk tuning af allokeringsprocenter: Undersøg muligheder for at lade systemet lære og tilpasse sin interne
|
||||||
|
kapitalallokering over tid (AI-drevet tuning).
|
||||||
|
- Flere tradingstrategier: Udvid Evelyn med mulighed for at tilføje alternative, konkurrerende scoring-algoritmer.
|
||||||
|
- Bedre penalty-funktioner ved close: Implementer og test mere avanceret straflogik for lukning af positioner.
|
||||||
|
- Ekstra fail-safes: Tilføj flere automatiske stopmekanismer for at undgå at systemet “vipper omkuld” i
|
||||||
|
ekstreme markedsbevægelser.
|
||||||
|
- Open API for ekstern integration: Gør det muligt at tilføje eksterne datafeeds, udveksle signaler med andre
|
||||||
|
systemer, eller automatisere funding-opgaver.
|
||||||
|
|
||||||
|
## 11. Statusservice og referencepris
|
||||||
|
|
||||||
|
Evelyn er navnet på det samlede system. `com.r35157.evelyn.service.status.EvelynStatusService` indsamler og
|
||||||
|
persisterer statusindeks for én navngiven instans og giver consumers adgang til den samme referencepris, som
|
||||||
|
Evelyn Price Index bruger. Referenceimplementeringen er `EvelynStatusServiceImpl` under
|
||||||
|
`com.r35157.evelyn.service.status.impl.ref`. Hub'en ejer fortsat startup af de enkelte processer.
|
||||||
|
|
||||||
|
`AssetPrice getReferencePrice(Instant timestamp)` beregner prisen for det angivne tidspunkt:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Start: 2026-07-31T22:00:00Z
|
||||||
|
Startpris: 15.00 USDT pr. EVE
|
||||||
|
Årlig faktor: 1.20
|
||||||
|
Årslængde: 31,536,000 sekunder
|
||||||
|
referencePrice = 15.00 × 1.20^(elapsedSeconds / 31,536,000)
|
||||||
|
```
|
||||||
|
|
||||||
|
Resultatet indeholder det kanoniske EVE/USDT-tradingpar, så quote-valutaen er eksplicit. Prisen er 15 ved start og
|
||||||
|
18 efter ét 365-dages år. Statusindekset bruger `((actualPrice / referencePrice) - 1) × 10` med samme samplingtidspunkt
|
||||||
|
og samme beregning og konstanter. Den eksisterende konvention om at behandle USDT som USD i indekset bevares.
|
||||||
|
|
||||||
|
Referenceprisen beregnes ved hvert kald uden tickeropslag, historikadgang, workers eller ændring af historikken.
|
||||||
|
Kaldet virker før sampling starter og mens den er stoppet, også uden provisionerede historikfiler. `null` afvises
|
||||||
|
med `NullPointerException`, og tidspunkter før kurvens start afvises med `IllegalArgumentException`.
|
||||||
|
|
||||||
|
Production og Test bevarer separate navne, historikker, livscyklusser og Mission Control-charts. Historikfilerne
|
||||||
|
ligger fortsat i `data/evelyn/<instance-name>/status.log` med formatversion 1. Sampling sker fortsat med cirka ét
|
||||||
|
minuts fast forsinkelse, og et punkt persisteres før publicering i et immutable snapshot. `stop()` rydder kun
|
||||||
|
hukommelsen og beholder navnereservationen, restart genindlæser filen, og `close()` frigiver navnet efter sikker
|
||||||
|
terminering. `EvelynStatusIndexPoint` bliver i sin eksisterende package med alle seks indeksfelter; kun Evelyn
|
||||||
|
Price Index beregnes og vises, mens de øvrige fem felter fortsat er nul. Legacy-rebalancering er fortsat inaktiv.
|
||||||
|
|
||||||
|
## 12. Perps-service og Mission Control
|
||||||
|
|
||||||
|
`EvelynPerpsService` udleverer et immutable `PerpsSnapshot` gennem det parameterløse
|
||||||
|
`getPositionsSnapshot()`. Læsning foretager ingen integrationskald. Hub'en starter
|
||||||
|
separate processer `evelyn.perps.prod` og `evelyn.perps.test`, hver med sin eksisterende
|
||||||
|
wallet-liste: seks PROD-adresser og én TEST-adresse fra issue #98. Begge får en fælles
|
||||||
|
read-only `JupiterPerpsOverview` og en fælles `PhoenixPerpsService`; sidstnævnte er
|
||||||
|
registreret som `phoenix.perps.default`. De eksisterende Jupiter-services til handel
|
||||||
|
og alarmer beholder deres afhængigheder og cache.
|
||||||
|
|
||||||
|
Servicen læser asynkront ved start og venter 60 sekunder efter hvert fuldført gennemløb.
|
||||||
|
Runder overlapper ikke og afhænger ikke af EMC's synlighed. Jupiter-overblikket bruger
|
||||||
|
ét REST-opslag pr. wallet. Phoenix bruger trader-state og ét TWAP-opslag for wallets
|
||||||
|
med eksisterende subaccounts, også uden åbne positioner. Metadata hentes ved positioner eller fyldte
|
||||||
|
TWAP-ordrer med resterende delordrer; én mark price hentes pr. unikt åbent marked,
|
||||||
|
og der foretages et risikolæs pr. synlig konto. Lokal trader-adresseafledning bruger
|
||||||
|
den eksisterende Solana-afhængighed.
|
||||||
|
Begge har 10 sekunders request-timeout; Phoenix bevarer fælles
|
||||||
|
pacing og 429-cooldown. En fuld opdatering tager opslagstid plus pausen, ikke nødvendigvis
|
||||||
|
ét minut. Implementeringens `close()` afbryder worker'en; consumer-API'et har kun læsning.
|
||||||
|
|
||||||
|
Snapshot'et adskiller isolerede positioner fra Cross-konti med egne balancer. Pengebeløb
|
||||||
|
er præcise USD-decimaler, og null betyder utilgængelig. Før første succes indeholder
|
||||||
|
hver konfigureret wallet/venue en ukendt scope, ingen syntetiske positioner. Succes
|
||||||
|
publiceres pr. wallet/venue; en fejl beholder den kombinations sidste observation og
|
||||||
|
oprindelige succes-tidspunkt. Andre wallets/venues forsøges stadig. Et gyldigt tomt svar
|
||||||
|
rydder den pågældende scope og kan totalisere nul; HTTP-/formatfejl kan ikke. En gyldig
|
||||||
|
Cross-konto med kapital bevares, selv om dens sidste position lukkes.
|
||||||
|
|
||||||
|
EMC viser **Position | Risk | Direction | Margin | Collateral | Leverage | Exposure | Value | uPnL | TWAP**
|
||||||
|
under den synlige rod **Perpetual Contracts**. Isolerede positioner har wallet → venue →
|
||||||
|
position; Phoenix Cross har wallet → venue → Cross → position. Positionens konto-ID
|
||||||
|
bestemmer identitet og tilhørsforhold, aldrig instrumentteksten. Wallets følger den
|
||||||
|
hidtidige konfiguration med Jupiter før Phoenix. En venue uden positioner, Cross-konti eller TWAP-advarsler
|
||||||
|
skjules og vises igen, når der kommer indhold. Skjulte venues indgår fortsat i snapshot
|
||||||
|
og totaler; deres læsestatus findes på wallet-rækkens tooltip. En Cross-konto med kapital
|
||||||
|
holder derfor Phoenix synlig, selv uden åbne positioner. Eksisterende foldestatus, markering og
|
||||||
|
rækkeobjekter bevares; kun ændrede rækkeværdier eller børn opdateres, også når risikodata
|
||||||
|
ældes i et ellers uændret snapshot. Vinduesstart er 1100 × 600,
|
||||||
|
og smallere vinduer kan scrolle vandret, når kolonnernes minimumsbredder kræver det.
|
||||||
|
|
||||||
|
TWAP-progress vises på en eksisterende Phoenix-position, når ordren selv har et faktisk
|
||||||
|
fill og resterende delordrer. Ufyldte ordrer opretter ingen rækker og vises heller
|
||||||
|
ikke på en position, som allerede fandtes. Progressbaren måler udførte delordrer;
|
||||||
|
teksten er estimeret resterende tid fra seneste TWAP-observation, fx `≈ 28:32:10`
|
||||||
|
eller `≈ 128:32:10`, uden timerollover eller live-nedtælling. Tooltip viser
|
||||||
|
`Estimated completion`, totalstørrelse, næste delordrestørrelse, fyldt mængde og
|
||||||
|
observationstid. Restordrer kan være mindre. Dollarstørrelser bruger samme Phoenix-
|
||||||
|
markpris som positionen og kræver ikke Ticker-priser; uden markpris bevares mængderne,
|
||||||
|
mens berørte dollarværdier og totaler bliver utilgængelige. En TWAP-læsefejl beholder
|
||||||
|
hele venue-observationen og viser `Retained` i cellen. Se
|
||||||
|
[TWAP-beregninger og begrænsninger](PhoenixPerps.md#twap-observations) for slots,
|
||||||
|
forfaldne tidsplaner og prisestimater.
|
||||||
|
|
||||||
|
Exposure er brutto: 100 USD Long + 100 USD Short giver 200 USD. Dollar-uPnL summeres
|
||||||
|
med fortegn. Collateral/Value tæller hver isoleret position og hver delt Cross-konto én
|
||||||
|
gang, inklusive ubrugt kapital på de inkluderede Cross-konti. Cross-positionernes
|
||||||
|
marginkrav lægges ikke til faktisk kontokapital. Mangler en nødvendig del, er netop
|
||||||
|
feltets total utilgængelig; en delsum præsenteres aldrig som total. Ingen summering eller
|
||||||
|
gennemsnitsberegning af leverage eller PnL-procenter. Totalerne afhænger ikke af foldning.
|
||||||
|
|
||||||
|
Isoleret leverage er Exposure / positiv Collateral; uPnL-procent er 100 × pris-uPnL /
|
||||||
|
positiv Collateral. På Cross-positioner er Collateral et initialt marginkrav i USD,
|
||||||
|
ikke særskilt ejet kapital. Cross-gruppens leverage er bruttoeksponering / udiskonteret
|
||||||
|
equity før estimerede exitgebyrer. Cross-positionernes leverage og ROI-procent er
|
||||||
|
utilgængelige, fordi Phoenix-konventionerne ikke er verificeret; dollar-uPnL vises.
|
||||||
|
Cross-positioners Value er ikke entydigt defineret og vises som —. Risk viser særskilt
|
||||||
|
vedligeholdelsesmarginens udnyttelse; det er ikke samlet initial-marginudnyttelse eller fri købekraft.
|
||||||
|
|
||||||
|
Value er et estimat ved referencepriser, ikke et eksekverbart tilbud. Phoenix medregner
|
||||||
|
collateral, pris-uPnL og signed uafregnet funding og fratrækker estimerede takergebyrer
|
||||||
|
med kontoens fee override. Slippage, price impact, swaps, netværksgebyrer og kontorent
|
||||||
|
indgår ikke. Ukendt spot-collateral gør de berørte værdiansættelser utilgængelige.
|
||||||
|
Jupiters rapporterede `valueUsd` vises uændret som et estimat, fx `≈$123.23`, efter
|
||||||
|
præcis konvertering fra heltals-mikro-USD. Tooltip forklarer, at det faktiske provenu
|
||||||
|
ved lukning kan afvige. En tidligere observation trak åbningsgebyrer fra, selvom
|
||||||
|
collateral allerede var registreret efter gebyrer; gebyrbehandlingen er derfor
|
||||||
|
usikker, og collateral-/profitloft-semantikken er fortsat ikke verificeret. Det
|
||||||
|
beviser ikke, at alle API-estimater er forkerte, eller at forskellen kun er små gebyrer.
|
||||||
|
Der tilføjes ingen gebyrer tilbage og foretages ingen korrigeret netberegning.
|
||||||
|
|
||||||
|
Tilgængelige Value-estimater fra både Jupiter og Phoenix samt deres venue-, wallet-
|
||||||
|
og **Perpetual Contracts**-totaler har ét `≈` foran beløbet. Totalens tooltip forklarer
|
||||||
|
de bidragende estimatkilder, også ved blandede venues og sammenfoldede grupper.
|
||||||
|
Nul og negative beløb bevares. Manglende/null Value gør fortsat den berørte total `—`
|
||||||
|
uden numerisk estimatmarkering; en succesfuldt læst tom gruppe kan give nul. Ugyldige
|
||||||
|
beløb fejler hele observationen, så tidligere data og oprindelig læsetid bevares med
|
||||||
|
fejlstatus. En ny succes erstatter den tidligere værdi, også når den nye Value mangler. Se
|
||||||
|
[Jupiter-overblikket](JupiterPerpsOverview.md) og [Phoenix-integrationen](PhoenixPerps.md)
|
||||||
|
for datakilder, enheder og beregninger.
|
||||||
|
|
||||||
|
Beløb har tusindtalsseparator og to decimaler, leverage × og pris-uPnL et synligt fortegn.
|
||||||
|
Positiv uPnL er grøn, negativ rød, afrundet nul neutral uden negativt nul. De svage
|
||||||
|
Long/Short-baggrunde bevares; grupper har tydeligere skrift og ingen retningsfarve.
|
||||||
|
Tooltips forklarer definitioner, manglende værdier og sidste succesfulde læsetidspunkt.
|
||||||
|
Efter fejl markeres en synlig venue som retained; læsestatus for skjulte venues findes
|
||||||
|
på wallet-rækkens tooltip. Gamle tal beskrives ikke som nye.
|
||||||
|
|
||||||
|
EMC spørger hver miljøservice straks ved åbning og hvert tredje sekund uden at blokere
|
||||||
|
JavaFX-tråden. Skjul/luk stopper kun EMC-polling. Der er ingen sample-data, Refresh-knap,
|
||||||
|
handel, signering, kontoaktivering eller automatisk genstart af Hub'en i denne læsevej.
|
||||||
|
|
||||||
|
### TWAP-advarsler
|
||||||
|
|
||||||
|
En fyldt Phoenix-TWAP med resterende delordrer, som mangler markedsmetadata eller
|
||||||
|
ikke matcher en åben position, bevares som en advarsel. TWAP-cellen viser en lille
|
||||||
|
orange trekant med udråbstegn og kort tekst. Advarslen står på positionen, når
|
||||||
|
ordren sikkert kan tilknyttes den; ellers på den ejende Cross-gruppe. Gruppen
|
||||||
|
bevares også uden kapital eller positioner. En umatchet isoleret ordre står på
|
||||||
|
Phoenix med sit korrekte subaccount i tooltip, aldrig på en opdigtet Cross-konto.
|
||||||
|
En matchet ordre uden brugbart estimat for næste udførelse viser en timing-advarsel
|
||||||
|
sammen med sin progressbar; det er ikke bevis på, at udførelsen er stoppet.
|
||||||
|
|
||||||
|
Advarsler samles op gennem **position → Cross → Phoenix → wallet → Perpetual
|
||||||
|
Contracts**, eller den faktiske isolerede gren uden Cross. Hvert niveau tæller
|
||||||
|
hver underliggende ordre én gang, også når undergrene er foldet sammen. Flere
|
||||||
|
ordrer vises med antal. Tooltip kan rulles og bliver stående, mens markøren er over
|
||||||
|
cellen eller detaljerne. Det identificerer wallet, konto/position, årsag,
|
||||||
|
TWAP-adresse, asset/subaccount, rå tællere/fills og oprindeligt observationstidspunkt.
|
||||||
|
Ukendte symboler, enheder, dollarværdier og tider opfindes ikke. Marginrisiko og
|
||||||
|
kapitaltotaler ændres ikke af advarslerne.
|
||||||
|
|
||||||
|
Ordren skal stadig selv have faktiske fills og resterende delordrer. Ufyldte og
|
||||||
|
færdige ordrer er skjult, også i gruppeoptællinger. Succesfulde opslag rydder løste
|
||||||
|
advarsler helt op gennem træet og bevarer de øvrige. Fejl bevarer tidligere data og
|
||||||
|
markerer berørte advarsler og opsummeringer `retained`; efter fem minutter eller ved
|
||||||
|
fremtidigt tidsstempel markeres de `stale`. Originale tider bevares, også ved fejl i
|
||||||
|
EMC's snapshot-læsning. Alderskontrol bruger eksisterende UI-polling; fem minutter
|
||||||
|
er en datakvalitetsgrænse, ikke en udførelsesfrist. Se [Phoenix-læsningen](PhoenixPerps.md#twap-observations).
|
||||||
|
|
||||||
|
### Risiko i Perps
|
||||||
|
|
||||||
|
Risk står ved positionsnavnet og bruger tekst og en statusprik, mens Long/Short-
|
||||||
|
baggrundene bevares. På isolerede positioner vises rapporteret likvideringsafstand:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Long: 100 × (venue-referencepris − likvideringspris) / venue-referencepris
|
||||||
|
Short: 100 × (likvideringspris − venue-referencepris) / venue-referencepris
|
||||||
|
```
|
||||||
|
|
||||||
|
Ejeren valgte 24. oktober 2026 **neutral positiv afstand og rød ved nået/overskredet
|
||||||
|
grænse**. Der er ingen generelle grøn/gul/rød-grænser pr. marked. Positiv uPnL eller
|
||||||
|
lav leverage tildeler ikke en sikkerhedsstatus. Negativ afstand bevarer fortegnet;
|
||||||
|
det er en observeret grænseoverskridelse, ikke bevis for gennemført likvidering.
|
||||||
|
Manglende, nul og negative sentinel-priser er ukendte, ikke uendelig sikkerhed.
|
||||||
|
Tooltip viser venue-priser, præcis afstand, eventuel rapporteret tier og observationstid.
|
||||||
|
|
||||||
|
Phoenix' egne tiers bruger grøn **OK**, gul **At risk**, orange **Cancellable** og
|
||||||
|
rød **Liquidatable**, **Backstop** eller **High risk**. En selvstændigt gyldig
|
||||||
|
kontoklasse kan bruges, selv om en positiv likvideringspris mangler. På Cross-kontoen:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MM utilization = 100 × maintenance margin / positiv effective collateral
|
||||||
|
USD headroom = effective collateral − maintenance margin
|
||||||
|
```
|
||||||
|
|
||||||
|
MM er marginudnyttelse, ikke likvideringssandsynlighed eller en prisafstand. Phoenix'
|
||||||
|
præcise tier styrer likvideringsstatus; visningsafrunding bruges ikke til klassifikation.
|
||||||
|
Ved ikke-positiv effektiv sikkerhed er procenten utilgængelig. En kendt tom konto
|
||||||
|
uden vedligeholdelseskrav vises som **No positions**; det lover ikke noget om fremtidige
|
||||||
|
ordrefills. Manglende nødvendige data eller modstridende
|
||||||
|
Safe-data må ikke vises grønt. Cross-positioner viser **Shared** med konto-ID i tooltip,
|
||||||
|
og kontoens risiko tælles kun én gang uanset antallet af positioner.
|
||||||
|
|
||||||
|
Grupper viser værste kendte risiko og antal ukendte/forældede scopes uden gennemsnit
|
||||||
|
af afstande eller MM-procenter. En neutral afstand gør ikke hele gruppen grøn; **Distance
|
||||||
|
only** angiver, at der ikke er tildelt en sikkerhedsklasse. Før første succes er en
|
||||||
|
venue ukendt. En tidligere tom venue kan også blive forældet, selv om dens overskrift
|
||||||
|
er skjult. Kendt kritisk risiko forbliver rød med usikkerhedsmarkering efter fejl/aldring.
|
||||||
|
|
||||||
|
Risikodata bliver **Stale** ved fem minutter fra ældste nødvendige observation,
|
||||||
|
straks ved venue- eller snapshot-læsefejl eller ved et fremtidigt tidsstempel. Også
|
||||||
|
et fejlet `getPositionsSnapshot()` genviser den beholdte observation med fejlstatus.
|
||||||
|
Fem minutter er en grænse
|
||||||
|
for datakvalitet, ikke en garanti for sikkerhed i perioden. Langsomme normale runder
|
||||||
|
kan derfor give Stale. Den eksisterende tresekunders UI-polling genvurderer alder,
|
||||||
|
også når servicen returnerer samme snapshot, uden en ny timer eller flere venue-kald.
|
||||||
|
Forældet eller ufuldstændig ikke-kritisk risiko får ingen grøn statusprik.
|
||||||
|
Tidsstempler er modtagelsestider; API-lag og separate pris-/kontolæs begrænser aktualiteten.
|
||||||
|
Visningen viser senest observeret risiko og er ikke en livealarm. Datakilderne er
|
||||||
|
beskrevet i [Jupiter-overblikket](JupiterPerpsOverview.md) og
|
||||||
|
[Phoenix-risikolæsningen](PhoenixPerps.md#venue-reported-account-risk).
|
||||||
|
|
||||||
|
Den kanoniske systembeskrivelse findes i
|
||||||
|
[Evelyn: services og status](../../r35157_docs/projects/evelyn/README.md#evelynperpsservice).
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# Evelyn liquidity proceeds
|
||||||
|
|
||||||
|
`com.r35157.evelyn.service.liquidity.EvelynLiquidityService` is a domain-only
|
||||||
|
Nenjim component. `impl.ref.EvelynLiquidityServiceImpl` additionally implements
|
||||||
|
`NenjimProcess`; NenjimHub owns startup. The optional process ID is
|
||||||
|
`evelyn.liquidity.prod`.
|
||||||
|
|
||||||
|
The owner creates/funds EVE/SyrupUSDC CLMM positions manually (for example,
|
||||||
|
10 EVE each) using the dedicated liquidity wallet. This service withdraws,
|
||||||
|
converts and pays proceeds; it never creates, refills, reranges, closes or burns
|
||||||
|
positions, NFTs or source token accounts. A falling price leaves nonqualifying
|
||||||
|
positions intact, including positions whose inventory has returned to EVE.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Copy `conf/evelyn-liquidity.conf.example` to `conf/evelyn-liquidity.conf` and
|
||||||
|
replace every placeholder. The local runtime file is intentionally ignored by
|
||||||
|
Git. Both begin with `FORMAT_VERSION=1`; the example is never loaded as a
|
||||||
|
fallback. Unknown, duplicate and missing settings fail validation.
|
||||||
|
|
||||||
|
| Setting | Contract |
|
||||||
|
| --- | --- |
|
||||||
|
| `LIQUIDITY_WALLET` | Dedicated public Solana wallet address |
|
||||||
|
| `SIGNER_KEY_NAME` | Existing external jup key name controlling exactly that address |
|
||||||
|
| `CLMM_POOL` | Existing EVE/SyrupUSDC Raydium CLMM pool address |
|
||||||
|
| `HARVEST_DISTANCE_PERCENT` | `0 <= h < 100`; `10` means ten percent |
|
||||||
|
| `ITERATION_INTERVAL_SECONDS` | Positive integer delay after an iteration completes; example `300` |
|
||||||
|
| `USDC_RECIPIENT` | Recipient wallet owner address, not a token account; must differ from the liquidity wallet |
|
||||||
|
| `MINIMUM_SYRUPUSDC_RESERVE` | Nonnegative SyrupUSDC amount; example `0` |
|
||||||
|
| `MINIMUM_USDC_RESERVE` | Nonnegative USDC amount; example `0` |
|
||||||
|
| `MAXIMUM_SWAP_SLIPPAGE_BPS` | Explicit Jupiter limit in `[0,10000]` basis points |
|
||||||
|
| `MAXIMUM_WITHDRAWAL_SLIPPAGE_BPS` | Explicit CLMM limit in `[0,10000)` basis points |
|
||||||
|
|
||||||
|
The existing SolanaWallet implementation checks the external jup version and
|
||||||
|
key/address binding during construction. No key material belongs in these files.
|
||||||
|
Fund the wallet with SOL for fees and any destination token-account rent.
|
||||||
|
Reserve amounts must be exactly representable at the corresponding mint's
|
||||||
|
precision; `0.01` SyrupUSDC and `0.5` USDC are permitted operating choices.
|
||||||
|
No hidden positive reserve is added.
|
||||||
|
|
||||||
|
Only after configuring and funding the wallet, explicitly add
|
||||||
|
`evelyn.liquidity.prod` to the existing `[AUTO_START]` section of private
|
||||||
|
`conf/NenjimHub.conf`. Keep Registry first and Journal second, with no blank line
|
||||||
|
inside the active section. Preserve all existing process selections and their
|
||||||
|
order. This process requires no Ticker, Evelyn status, JavaFX or burner process.
|
||||||
|
The checked-in Hub example continues to select only Registry and Journal.
|
||||||
|
|
||||||
|
Deployment configuration and the dedicated signer/wallet, Jupiter and direct
|
||||||
|
Raydium dependencies are constructed only when the selected entry is reached.
|
||||||
|
Registry IDs are `solana.wallet.evelyn-liquidity-prod`,
|
||||||
|
`jupiter.swap.evelyn-liquidity-prod`, `raydium.service.evelyn-liquidity-prod`
|
||||||
|
and `evelyn.liquidity.prod`. Jupiter signs with this wallet. Currency/mint
|
||||||
|
identities come from CIS; token programs and precision come from chain metadata.
|
||||||
|
|
||||||
|
## Iteration and recovery
|
||||||
|
|
||||||
|
A single worker runs immediately on startup and then with fixed delay after
|
||||||
|
completion. Every iteration follows the same three independent steps:
|
||||||
|
|
||||||
|
1. Discover the wallet's position NFTs. For each owned, positive-liquidity
|
||||||
|
position in the configured pool, read fresh on-chain pool state in
|
||||||
|
SyrupUSDC per EVE after discovery and any earlier withdrawals. Withdraw complete
|
||||||
|
liquidity when its normalized **upper** boundary satisfies
|
||||||
|
`U <= P * (1 - h/100)`. Equality qualifies. Both token orientations use
|
||||||
|
exact squared Q64.64 comparisons, without display-price rounding. At
|
||||||
|
`P=15, h=10`, `13–13.5` qualifies and `13.5–14` does not. Withdrawals retain
|
||||||
|
the position and NFT, and return pool-token fees to the same wallet.
|
||||||
|
Each position uses its own fresh state for both eligibility and withdrawal
|
||||||
|
minimums; the price is not reused across positions.
|
||||||
|
2. Read current SyrupUSDC balance and swap the full balance above its reserve
|
||||||
|
into USDC through the confirmed exact-input Jupiter operation.
|
||||||
|
3. Read current USDC balance and send the full balance above its reserve to
|
||||||
|
the recipient owner, creating its ATA at the sender's expense if needed.
|
||||||
|
The transfer submits once and is awaited at `CONFIRMED`.
|
||||||
|
|
||||||
|
Missing/zero balances and balances equal to their reserves are skipped. Amounts
|
||||||
|
are rounded down to mint precision. An unavailable or unusable pool price stops
|
||||||
|
the remaining withdrawals in that iteration while retaining steps 2 and 3. EVE returned as principal/fees and
|
||||||
|
other reward tokens remain in the wallet and never enter the conversion/payout
|
||||||
|
pipeline. All available SyrupUSDC/USDC is eligible, including manual deposits.
|
||||||
|
|
||||||
|
An ordinary position failure does not block other readable positions. An
|
||||||
|
ordinary step failure does not block later independent steps. Logs identify
|
||||||
|
steps, position NFTs, liquidity/token amounts and returned signatures. Unknown
|
||||||
|
outcomes are logged with signatures when available. Subsequent iterations and
|
||||||
|
restarts reread chain state; there is no service journal, attribution, pending
|
||||||
|
signature gate, immediate retry or transaction replay. Empty positions are
|
||||||
|
skipped and can be manually reused. Shutdown interrupts the current operation
|
||||||
|
and prevents following steps; the reference implementation can also be closed
|
||||||
|
by its local owner through `AutoCloseable`.
|
||||||
|
|
||||||
|
## Withdrawal support
|
||||||
|
|
||||||
|
The transaction follows the official
|
||||||
|
[decrease_liquidity_v2 accounts](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/instructions/decrease_liquidity_v2.rs)
|
||||||
|
and [decrease/minimum-output behavior](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/instructions/decrease_liquidity.rs),
|
||||||
|
also checked against the
|
||||||
|
[official SDK encoder](https://github.com/raydium-io/raydium-sdk-V2/blob/master/src/raydium/clmm/instrument.ts).
|
||||||
|
Withdrawal and closure are separate actions in
|
||||||
|
[Raydium's liquidity documentation](https://github.com/raydium-io/raydium-docs-v1/blob/main/user-flows/add-remove-liquidity.mdx).
|
||||||
|
|
||||||
|
Raydium validates pool/personal-position discriminators, the owned NFT holding,
|
||||||
|
current complete liquidity, mint precision, vaults and tick arrays before signing.
|
||||||
|
For every pool or reward mint, existing owner token accounts are reused after
|
||||||
|
validating their mint, owner, token program and initialized state, including
|
||||||
|
empty accounts and accounts outside the owner's ATA. Only when no owner holding
|
||||||
|
exists does the withdrawal use or idempotently create its ATA. A failed holdings
|
||||||
|
read aborts rather than creating an additional account. This preserves the
|
||||||
|
existing wallet's single-account-per-mint balance/transfer contract. The
|
||||||
|
transaction requests a 600,000 compute-unit limit.
|
||||||
|
The bitmap extension and initialized reward triplets are included when required.
|
||||||
|
The integer principal math matches Raydium's
|
||||||
|
[tick algorithm](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/libraries/tick_math.rs).
|
||||||
|
Minimum principal amounts are floored after applying explicit withdrawal
|
||||||
|
slippage. An amount too small to protect at least one raw unit is rejected.
|
||||||
|
No withdrawal always substitutes zero for both minimums.
|
||||||
|
Price movement after the per-position read remains possible before execution;
|
||||||
|
principal minimums protect received amounts, not the harvest-distance threshold.
|
||||||
|
|
||||||
|
The current EVE mint uses Token-2022 metadata pointer/token metadata extensions;
|
||||||
|
SyrupUSDC uses legacy SPL. Read-only mainnet inspection on 2026-10-04 found the
|
||||||
|
EVE/SyrupUSDC pool `EpZXeeShz64DK8HfKnjWPTkmhN7dZe77SjvzNDX8CARq` with decimals
|
||||||
|
8/6, tick spacing 60 and no initialized rewards. Deployment still requires an
|
||||||
|
explicit pool setting and repeats validation. Mint close-authority and metadata/
|
||||||
|
group extensions are supported; other transfer-affecting extensions, including
|
||||||
|
transfer fees/hooks, fail before signing. The withdrawal encoder supports legacy
|
||||||
|
transactions fitting Solana's packet limit and rejects larger account sets.
|
||||||
|
|
||||||
|
## Offline verification
|
||||||
|
|
||||||
|
The Gradle JUnit fixtures exercise exact threshold equality, inversion, raw
|
||||||
|
principal amounts, owner/pool filtering, reserves, independent failures,
|
||||||
|
restart recovery and shutdown. Captured serialized transactions verify account
|
||||||
|
order, full liquidity, minimum output, NFT retention, bitmap/reward accounts and
|
||||||
|
one submission/confirmation attempt. Review regression fixtures also cover price
|
||||||
|
changes during discovery or earlier withdrawals in both token orientations,
|
||||||
|
quote consistency, and reuse/validation of existing pool and reward token accounts
|
||||||
|
under both token programs. All financial dependencies are controlled;
|
||||||
|
no verification submits live withdrawals, swaps or transfers.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./gradlew compileJava compileTestJava test \
|
||||||
|
--tests '*RaydiumClmm*Test' --tests '*EvelynLiquidityServiceImplTest' \
|
||||||
|
--tests '*NenjimHubLiquidityConfigurationTest'
|
||||||
|
openspec validate 89-add-evelyn-liquidity-service --strict
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Stop the process/JVM and remove its explicit Hub selection to disable operation.
|
||||||
|
Existing positions and wallet balances remain the recovery state.
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Jupiter read-only Perps overview
|
||||||
|
|
||||||
|
`JupiterPerpsOverview` is an overview-only interface under
|
||||||
|
`com.r35157.libs.jupiter.perps`; `impl.ref.RestJupiterPerpsOverview` reads
|
||||||
|
`GET https://perps-api.jup.ag/v2/positions?walletAddress=<address>` once per wallet.
|
||||||
|
The Hub injects this reader into both Evelyn Perps processes with their existing
|
||||||
|
separate wallet lists. It has a 10-second request timeout, no immediate retries
|
||||||
|
and no portfolio cache. Signing, transactions, alarms, their existing Solana cache
|
||||||
|
and entry-price behavior remain in the existing `JupiterPerpsService`.
|
||||||
|
|
||||||
|
## Response contract and definitions
|
||||||
|
|
||||||
|
The official [CLI response type](https://github.com/jup-ag/cli/blob/main/src/clients/PerpsClient.ts)
|
||||||
|
and [API reference](https://perps-api.jup.ag/v1/docs) describe the position list.
|
||||||
|
The implementation requires an integer `count` matching the complete `dataList`,
|
||||||
|
unique valid position accounts, valid asset mints, Long/Short side and positive
|
||||||
|
size. HTTP/schema failures throw; only an explicit count-zero empty list clears
|
||||||
|
the scope. Missing optional valuation fields become unavailable; malformed
|
||||||
|
amounts fail the read.
|
||||||
|
|
||||||
|
| Display | Source / definition |
|
||||||
|
| --- | --- |
|
||||||
|
| Exposure | `sizeUsd / 10^6`; preserves the existing recorded USD-size basis |
|
||||||
|
| Collateral | `collateralUsd / 10^6`, the venue's recorded post-fee USD collateral |
|
||||||
|
| Leverage | Exposure / positive Collateral; not equity-based leverage |
|
||||||
|
| uPnL | `pnlBeforeFeesUsd / 10^6`, price PnL before separately reported fees |
|
||||||
|
| uPnL percent | 100 × uPnL / positive Collateral |
|
||||||
|
| Value | `valueUsd / 10^6`, Jupiter's reported estimate, unchanged and displayed with `≈` |
|
||||||
|
| Risk reference price | Positive `markPriceUsd / 10^6` from the same response |
|
||||||
|
| Reported liquidation price | Positive `liquidationPriceUsd / 10^6`; missing/nonpositive values are unavailable |
|
||||||
|
|
||||||
|
All amounts are parsed as decimal integers and converted exactly. The API's
|
||||||
|
`pnlBeforeFeesPct` is already percentage points (95.48 means 95.48%), but the
|
||||||
|
overview derives the requested percentage against precise collateral itself.
|
||||||
|
The API's rounded `leverage` is likewise not used for the derived ratio.
|
||||||
|
|
||||||
|
The overview preserves one original response observation time on all positions.
|
||||||
|
It does not treat a position's `updatedTime` as proof of current price freshness.
|
||||||
|
Risk uses the venue's reported prices, with no new liquidation-price formula or
|
||||||
|
Ticker dependency. Missing/sentinel prices retain the position and its independent
|
||||||
|
metrics. Present malformed amounts still fail the read. Isolated signed distance,
|
||||||
|
the owner's neutral-distance policy and expiration are described in
|
||||||
|
[Perps risk presentation](Evelyn.md#risiko-i-perps).
|
||||||
|
|
||||||
|
## Reported Value and its uncertainty
|
||||||
|
|
||||||
|
The overview preserves optional `valueUsd` exactly as supplied, including zero
|
||||||
|
and signed values. EMC displays it with `≈` and two USD decimals (for example,
|
||||||
|
`≈$123.23`). Its tooltip explains: “Estimated position value reported by Jupiter.
|
||||||
|
Actual proceeds on closing may differ.” Missing/null values remain unavailable;
|
||||||
|
present malformed amounts fail the complete observation. Successful observations
|
||||||
|
replace earlier values, including replacing an estimate with unavailable Value.
|
||||||
|
Failed reads retain the original observation, success time and failure indication.
|
||||||
|
|
||||||
|
Available Jupiter, wallet and Perpetual Contracts Value totals inherit the estimate
|
||||||
|
qualification independently of expansion state. Mixed Phoenix/Jupiter totals
|
||||||
|
identify both estimate sources; Phoenix's existing account-aware closing estimates
|
||||||
|
and single counting of shared Cross capital are preserved. A missing required Value
|
||||||
|
still makes the affected total `—`, without a numeric approximation marker. A
|
||||||
|
successfully read empty scope can total zero.
|
||||||
|
|
||||||
|
This reports a venue estimate, not verified closing proceeds. It does not add
|
||||||
|
opening fees back, correct the API number, introduce closing quotes or combine
|
||||||
|
additional on-chain observations with the response.
|
||||||
|
|
||||||
|
On 24 October 2026 the funded configured wallet's six API sizes and collateral
|
||||||
|
amounts matched read-only on-chain position data. For its SOL Short, the response
|
||||||
|
contained these integer micro-USD values:
|
||||||
|
|
||||||
|
```text
|
||||||
|
collateralUsd = 18559 pnlBeforeFeesUsd = 17721
|
||||||
|
openFeesUsd = 361 closeFeesUsd = 361
|
||||||
|
borrowFeesUsd = 7 valueUsd = 35551
|
||||||
|
```
|
||||||
|
|
||||||
|
`35551 = 18559 + 17721 - 361 - 361 - 7`. The on-chain collateral was also
|
||||||
|
18559; the official [position account documentation](https://developers.jup.ag/docs/perps/position-account)
|
||||||
|
defines collateral as after fees. This observation raises uncertainty about
|
||||||
|
subtracting a settled opening fee again; it does not prove that every API estimate
|
||||||
|
is wrong. Adding that fee back alone would not establish
|
||||||
|
the full contract: collateral-currency valuation and the `lockedAmount` profit
|
||||||
|
cap remain unverified. The response does not expose that cap. The possible difference
|
||||||
|
from actual closing proceeds is not known to be limited to small fees. Issue #100
|
||||||
|
therefore withheld Value; [issue #104](https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref/issues/104)
|
||||||
|
replaces that presentation with the explicitly qualified venue estimate.
|
||||||
|
|
||||||
|
There is no verified closing-value parity with Jupiter. Counts, amounts and prices
|
||||||
|
above are historical observations, not fixed expectations.
|
||||||
|
See the change's `verification.md` for observation times and current checks.
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Nenjim Documentation</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; background: #f8f9fa; color: #263238; margin: 0; }
|
||||||
|
main { max-width: 980px; margin: auto; padding: 2rem; background: white; line-height: 1.55; }
|
||||||
|
h1, h2, h3 { color: #0056b3; }
|
||||||
|
code, pre { background: #f1f3f5; }
|
||||||
|
code { padding: .1rem .25rem; }
|
||||||
|
pre { padding: 1rem; overflow-x: auto; border-left: 4px solid #0056b3; }
|
||||||
|
.status { padding: 1rem; background: #e7f1ff; border-left: 4px solid #0056b3; }
|
||||||
|
table { border-collapse: collapse; width: 100%; }
|
||||||
|
th, td { border: 1px solid #ccd4da; padding: .55rem; text-align: left; vertical-align: top; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Nenjim</h1>
|
||||||
|
<p class="status"><strong>Implementation status:</strong> Journal format version 1, the immutable Java model,
|
||||||
|
internal strict parser, read-only <code>JournalService</code> with a Hub-managed process implementation,
|
||||||
|
passive explicitly refreshed <code>JournalServiceManager</code>, Hub-managed
|
||||||
|
<code>JavaFxRuntimeService</code>, the first central component Registry, and mandatory Hub configuration
|
||||||
|
format 1 are implemented. Contexts, resolution, classloading, discovery, runtime loading/removal,
|
||||||
|
downloading, publishing, synchronization, watching, signatures, permissions, and license filtering remain
|
||||||
|
future work.</p>
|
||||||
|
|
||||||
|
<h2>One Journal per artifact</h2>
|
||||||
|
<p>A Journal describes exactly one artifact. API, test, and implementation artifacts therefore have independent
|
||||||
|
Journals and chains. Roles such as API or implementation are not stored as a <code>TYPE</code>.</p>
|
||||||
|
<pre><code><GROUP>-<MODULE>-<ARTIFACT>
|
||||||
|
com_r35157_nenjim-hubd-api
|
||||||
|
com_r35157_nenjim-hubd-api:1.0.3</code></pre>
|
||||||
|
<p>The three metadata components identify the chain; there is no separate <code>JOURNAL_ID</code>.</p>
|
||||||
|
|
||||||
|
<h2>Complete immutable snapshots and history</h2>
|
||||||
|
<p>Every Journal revision is a complete worldview of the artifact and all releases known in that revision. It is
|
||||||
|
not a delta and inherits nothing implicitly. A later snapshot may change metadata, add or remove releases,
|
||||||
|
correct release knowledge, alter dependencies or policy, or be a semantic no-op.</p>
|
||||||
|
<p>The genesis omits <code>PREVIOUS_JOURNAL_DIGEST</code>. Each successor names the SHA-256 digest of the
|
||||||
|
predecessor's exact UTF-8 text, including whitespace and line endings. A chain has one genesis and one head;
|
||||||
|
missing predecessors, forks, cross-artifact links, and non-increasing Journal versions are invalid.</p>
|
||||||
|
<p><code>JOURNAL_VERSION</code> is when the worldview was published. <code>PUBLISHED_AT</code> is when an artifact
|
||||||
|
release was published. Both use <code>uuuuMMddHHmmssSSS'Z'</code>, but historical knowledge is selected from
|
||||||
|
the Journal chain rather than by filtering the newest snapshot on release publication time.</p>
|
||||||
|
|
||||||
|
<h2>Metadata and release content</h2>
|
||||||
|
<p><code>DESCRIPTION</code> is mandatory single-quoted text. <code>INFORMATION_URI</code> is optional single-quoted
|
||||||
|
metadata and must decode to a non-empty absolute URI. Format 1 does not restrict schemes: HTTPS, FTP, IPFS,
|
||||||
|
and other syntactically valid schemes are accepted without opening or interpreting their content.</p>
|
||||||
|
<p>Every exact <code>major.minor.patch</code> release has a mandatory single-quoted <code>LICENSE</code>, a
|
||||||
|
publication timestamp, non-negative byte size, and one or both supported content identities:
|
||||||
|
<code>sha256</code> and CIDv1 (<code>cid1</code>). License text is preserved as free text; no vocabulary,
|
||||||
|
compatibility decision, or filtering is applied.</p>
|
||||||
|
|
||||||
|
<h2>Version expressions</h2>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Expression</th><th>Meaning</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>1.0.3</code>, <code>[1.0.3]</code></td><td>The same exact release.</td></tr>
|
||||||
|
<tr><td><code>[1.0]</code></td><td>Every stable 1.0.x release; not exact 1.0.0.</td></tr>
|
||||||
|
<tr><td><code>[1.0.3-->1.0.7]</code></td><td>A closed range including both exact endpoints.</td></tr>
|
||||||
|
<tr><td><code>[1.0-->1.2)</code></td><td>Includes 1.0.x and 1.1.x, excluding the complete 1.2.x series.</td></tr>
|
||||||
|
<tr><td><code>[1.0--></code></td><td>Starts at 1.0.0 and stops before 2.0.0.</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>Comma-separated expressions form a union. Boundaries require at least major and minor. Mathematical
|
||||||
|
comparison syntax, incomplete bare exact versions, prereleases, build metadata, empty elements, and reversed
|
||||||
|
or empty ranges are rejected.</p>
|
||||||
|
|
||||||
|
<h2>Dependencies</h2>
|
||||||
|
<p>Dependencies belong to individual releases and always have an explicit scheme. Format 1 implements only the
|
||||||
|
<code>nenjim:</code> scheme; Maven resolution and unknown schemes are not silently accepted.</p>
|
||||||
|
<pre><code>DEPENDS_ON=nenjim:com_r35157_nenjim-hubd-api=[1.0-->
|
||||||
|
EXCLUDES=nenjim:com_r35157_nenjim-hubd-api=1.0.5:'Known incompatibility'
|
||||||
|
PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3:'Built together'</code></pre>
|
||||||
|
<p><code>DEPENDS_ON</code> is the hard base set. <code>EXCLUDES</code> removes hard-invalid combinations for that
|
||||||
|
relationship. <code>PREFERRED</code> is a hint and must remain a subset of
|
||||||
|
<code>DEPENDS_ON - EXCLUDES</code>. Repeated rules form unions while each line and description remains
|
||||||
|
inspectable. The Journal layer represents these rules but does not resolve a graph.</p>
|
||||||
|
|
||||||
|
<h2>Artifact policy</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>BLACKLIST</code> is a hard global prohibition.</li>
|
||||||
|
<li><code>DISCOURAGED</code> is a negative hint; a matching release remains selectable.</li>
|
||||||
|
<li><code>RECOMMENDED</code> is a positive hint naming one exact release in the same snapshot.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Overlaps are valid and hard rules win over hints. A future resolver should seek a valid graph containing as
|
||||||
|
many compatible recommendations as possible. The highest valid version is a fallback, not an unconditional
|
||||||
|
rule, so future convergence may move from a higher local version to a lower recommended version.</p>
|
||||||
|
|
||||||
|
<h2>Strict parsing and immutable model</h2>
|
||||||
|
<p><code>FORMAT_VERSION=1</code> must be the first actual entry. Blank lines and comments beginning with
|
||||||
|
<code>#</code> are accepted, while a hash inside single quotes is data. Quoted values support escaped quotes
|
||||||
|
(<code>\'</code>) and backslashes (<code>\\</code>). Unknown, malformed, duplicate, misplaced, empty, or
|
||||||
|
semantically inconsistent content fails with source name, one-based line number, and a reason. Validation is
|
||||||
|
complete before service state changes. Returned objects and collections are immutable and defensively copied.</p>
|
||||||
|
|
||||||
|
<h2>Service, process, ServiceManager, packages, and disk layout</h2>
|
||||||
|
<p>A <code>Service</code> is the public domain API used by applications and other services during ordinary
|
||||||
|
operation. <code>JournalService</code> is a registrable component but remains filesystem-independent and
|
||||||
|
read-only: it exposes current and historical Journal queries without lifecycle, paths, refresh, parser, or
|
||||||
|
mutation operations. Queries become available only after successful Journal process startup.</p>
|
||||||
|
<p>The Hub constructs one Journal object exposed as both <code>JournalService</code> and
|
||||||
|
<code>NenjimProcess</code>, plus one distinct passive <code>JournalServiceManager</code>. The manager reports
|
||||||
|
the normalized data root and performs explicit refresh; it has no start, stop, service accessor, component
|
||||||
|
identity, or process lifecycle. It delegates through a package-private administration view that ordinary
|
||||||
|
service consumers cannot name or derive.</p>
|
||||||
|
<p>The Journal process reads UTF-8 runtime files chronologically and validates directory and filename placement.
|
||||||
|
Its complete initial worldview is published only after every runtime file succeeds; failed startup leaves
|
||||||
|
queries unavailable and cannot be retried on the same instance. Later refresh remains explicit, idempotent,
|
||||||
|
and atomic per ingestion. Neither startup nor refresh creates, overwrites, or deletes Journal files.</p>
|
||||||
|
<p>The package-private <code>JournalServiceImpl</code> keeps exact-text SHA-256 identity, chain state,
|
||||||
|
idempotency, conflicts, forks, and ingestion internal. The public package contains only the two central
|
||||||
|
interfaces; composed models, value types, and exceptions live under <code>.model</code>,
|
||||||
|
<code>.valuetypes</code>, and <code>.exception</code>, while parser, ingestion, chain state, filesystem
|
||||||
|
loading, and the admin boundary live under <code>.impl.ref</code> without leaking through public
|
||||||
|
signatures.</p>
|
||||||
|
<p>All modern JavaFX applications share one JVM-wide toolkit. When selected, the Hub constructs one
|
||||||
|
<code>JavaFxRuntimeServiceImpl</code>, registers it as <code>nenjim.javafx-runtime.service</code>, starts it
|
||||||
|
through its <code>NenjimProcess</code> view, and injects its lifecycle-free service view into later selected
|
||||||
|
JavaFX applications. Only service startup calls <code>Platform.startup(...)</code>; task submission never
|
||||||
|
initializes or retries JavaFX and is rejected until startup succeeds. A headless process plan that omits all
|
||||||
|
JavaFX processes does not construct the runtime or initialize the toolkit.</p>
|
||||||
|
<pre><code>data/nenjim/journals/
|
||||||
|
└── <artifact-coordinate>/
|
||||||
|
├── <journal-version>.journal
|
||||||
|
└── <journal-version>.journal.example</code></pre>
|
||||||
|
<p>Only <code>.journal</code> files are runtime input. Version-controlled <code>.journal.example</code> files are
|
||||||
|
documentation and are ignored by the service.</p>
|
||||||
|
|
||||||
|
<h2>Components and the central Registry</h2>
|
||||||
|
<p>A Nenjim component is a fully constructed building block ready for use. Its consumer decides whether it acts
|
||||||
|
as a service, application, algorithm, or adapter. A service exposes a domain API, while application describes
|
||||||
|
a user-facing or orchestration role. <code>NenjimComponent</code> is therefore a pure marker. Independently,
|
||||||
|
the canonical <code>com.r35157.nenjim.process.NenjimProcess</code> marks an active component that the Hub can
|
||||||
|
start and track; it adds only <code>start()</code> and no common stop, restart, or health operation.</p>
|
||||||
|
<p>A <code>NenjimComponentId</code> is lower-case lookup metadata owned by one Registry, not a property of the
|
||||||
|
object, an authorization boundary, or an artifact version. One object may have several IDs. Conversely, one
|
||||||
|
registration is indexed under every component interface implemented by that object, so the Registry object's
|
||||||
|
typed service and process views retain reference identity.</p>
|
||||||
|
<p><code>NenjimRegistryService</code> is the injected read-only consumer view. It returns immutable,
|
||||||
|
registration-ordered ID snapshots for a component interface and supports lookup by explicit ID plus expected
|
||||||
|
interface. Missing IDs return <code>null</code>; requesting an existing ID through the wrong interface is a
|
||||||
|
caller error. Registration is available only through a package-private administration view behind the passive
|
||||||
|
Registry manager. That manager exposes only registration, is neither a component nor a process, is not
|
||||||
|
registered, and cannot start anything. The internal administration interface must remain package-private so
|
||||||
|
an ordinary service consumer cannot cast the Registry object to a mutation view.</p>
|
||||||
|
<h3>Mandatory NenjimHub.conf format 1</h3>
|
||||||
|
<p><code>NenjimHubImpl</code> is the global composition and runtime owner. Before any process starts it reads only
|
||||||
|
the readable regular file <code>conf/NenjimHub.conf</code>. The version-controlled
|
||||||
|
<code>conf/NenjimHub.conf.example</code> is a copy-only template: the application never reads, copies,
|
||||||
|
generates, or falls back to it or to a hardcoded startup sequence.</p>
|
||||||
|
<pre><code>FORMAT_VERSION=1
|
||||||
|
|
||||||
|
[AUTO_START]
|
||||||
|
nenjim.registry.service
|
||||||
|
nenjim.journal.service</code></pre>
|
||||||
|
<p><code>FORMAT_VERSION=1</code> must be the first actual entry and the one exact
|
||||||
|
<code>[AUTO_START]</code> header must be the next actual entry. Leading and trailing whitespace is ignored.
|
||||||
|
A trimmed line beginning with <code>#</code> is a comment, and <code>#</code> after content begins a trailing
|
||||||
|
comment. Inside the section a comment-only line does not terminate it, while the first whitespace-only line
|
||||||
|
or end of file does. After termination only blank lines and comments are permitted; there is no quoting or
|
||||||
|
escaping for <code>#</code>.</p>
|
||||||
|
<p>Every active section line is a canonical component ID. The complete list is validated before Registry starts:
|
||||||
|
duplicates, invalid or unknown IDs, passive-only IDs, and IDs the Hub cannot construct as modern processes
|
||||||
|
fail. Registry must occur exactly once first and Journal exactly once second. The known selectable processes
|
||||||
|
are Registry, Journal, Ticker, Jupiter Perps Alarm, both Evelyn services, Production IOU Burner, JavaFX
|
||||||
|
runtime, Mission Control, and Process Manager. The version-controlled template selects only the mandatory
|
||||||
|
Registry and Journal processes; every other process must be added explicitly to the private deployment
|
||||||
|
configuration.</p>
|
||||||
|
<p>After validation the Hub starts, registers, and records Registry, then eagerly constructs and registers the
|
||||||
|
complete passive graph without activating a process. It next visits every remaining configured ID in exact
|
||||||
|
order, beginning with Journal. Each selected process is constructed only when reached, registered before
|
||||||
|
<code>start()</code>, and recorded only after successful return. An omitted process is therefore neither
|
||||||
|
constructed, registered, started, nor displayed by Process Manager.</p>
|
||||||
|
<p><code>[AUTO_START]</code> is the complete manually ordered process dependency closure. The Hub does not infer,
|
||||||
|
insert, recursively construct, or reorder dependencies. Ticker and Production Evelyn status must precede
|
||||||
|
a selected Production IOU Burner; the Hub injects that status service for reference-price decisions before
|
||||||
|
new buybacks and fails clearly if the predecessor is unavailable. JavaFX runtime must precede selected JavaFX
|
||||||
|
applications. Construction, registration, and startup remain fail-fast without rollback. Earlier successful
|
||||||
|
snapshot entries remain truthful after a later failure, and the online point plus blocking wait are reached
|
||||||
|
only after every configured process succeeds.</p>
|
||||||
|
<p>Ordinary consumers receive the lifecycle-free <code>TickerService</code> view, which exposes only the
|
||||||
|
latest-price query. The same <code>TickerServiceImpl</code> object separately implements
|
||||||
|
<code>NenjimProcess</code> so the Hub owns startup. The implementation starts its configured active
|
||||||
|
<code>PriceSource</code> instances internally and stops those already started if Ticker startup fails; it
|
||||||
|
exposes no general Ticker stop operation.</p>
|
||||||
|
<p><code>EvelynIOUBurnerService</code> follows the same separation even though its current domain-service view is
|
||||||
|
empty. It supplies typed Registry and dependency identity without external controls, while the same
|
||||||
|
<code>EvelynIOUBurnerServiceImpl</code> object separately implements <code>NenjimProcess</code> for Hub-owned
|
||||||
|
startup. Neither the service interface nor the implementation exposes an ordinary stop or restart operation.</p>
|
||||||
|
<p>The Ticker still explicitly selects the Raydium EVE/USDT source ID while the hardcoded source remains a passive,
|
||||||
|
registered component. Process Manager continues to poll immutable ordered snapshots of actual successful
|
||||||
|
startup milestones rather than displaying configured intent or claiming current health.</p>
|
||||||
|
<p><code>com.r35157.nenjim.hubd.Main</code> remains only the outer Java bootstrap. It constructs the reference Hub
|
||||||
|
through the public <code>NenjimHub</code> interface and calls <code>start()</code> once. The Hub is neither a
|
||||||
|
component nor a process and is not registered inside its own Registry.</p>
|
||||||
|
<p>The Registry remains internally open to later registration, but this version has one catalogue and no Context,
|
||||||
|
version resolution, classloader integration, discovery, public runtime loading or registration, removal,
|
||||||
|
persistence, events, ownership tracking, usage counts, permissions, scopes, filtered views, automatic
|
||||||
|
dependency construction, or automatic start-all behavior.</p>
|
||||||
|
|
||||||
|
<h2>Future Nenjim vision</h2>
|
||||||
|
<p>Contexts, dependency-graph resolution, recursive process construction, automatic selection/reconciliation,
|
||||||
|
runtime upgrade or downgrade, classloaders, artifact downloads, IPFS retrieval, Maven dependencies,
|
||||||
|
signatures, publishing, remote synchronization, filesystem watching, and UI/CLI management are deliberately
|
||||||
|
outside this implementation.</p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+538
@@ -0,0 +1,538 @@
|
|||||||
|
#<center><H1>Nenjim<br>Documentation</H1></center>
|
||||||
|
|
||||||
|
> **Dokumentstatus:** Journal-format version 1, den immutable Java-model, den interne tekstparser, det read-only
|
||||||
|
> `JournalService` med en Hub-styret procesimplementation, den passive eksplicit refresh-baserede
|
||||||
|
> `JournalServiceManager`, den Hub-styrede `JavaFxRuntimeService`, første version af det centrale component Registry
|
||||||
|
> og den obligatoriske format 1 Hub-konfiguration er implementeret. Contexts, resolution, discovery, runtime loading
|
||||||
|
> og removal er fortsat fremtidig funktionalitet.
|
||||||
|
|
||||||
|
**Indholdsfortegnelse:**
|
||||||
|
<!-- TOC -->
|
||||||
|
* [1. Introduktion til Nenjim](#1-introduktion-til-nenjim)
|
||||||
|
* [2. Versionering og Afhængigheder](#2-versionering-og-afhængigheder)
|
||||||
|
* [3. Opdateringer og Sikkerhed](#3-opdateringer-og-sikkerhed)
|
||||||
|
* [4. Betalingssystem og Integration med AssetAZ](#4-betalingssystem-og-integration-med-assetaz)
|
||||||
|
* [5. Backing Store og Fleksibilitet](#5-backing-store-og-fleksibilitet)
|
||||||
|
* [6. Standardiseret og Automatiseret Pakkehåndtering](#6-standardiseret-og-automatiseret-pakkehåndtering)
|
||||||
|
* [7. Betydningen af Semantisk Versionering](#7-betydningen-af-semantisk-versionering)
|
||||||
|
* [8. Componenter og det centrale Registry](#8-componenter-og-det-centrale-registry)
|
||||||
|
* [9. Integration med AssetAZ og økonomisk aktivitet](#9-integration-med-assetaz-og-økonomisk-aktivitet)
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
|
## 1. Introduktion til Nenjim
|
||||||
|
Nenjim skal på sigt kunne håndtere forskellige versioner af softwareartifakter samtidigt. Det nu implementerede
|
||||||
|
fundament er Journal-laget: et strengt tekstformat og en immutable model, som kan fastholde, hvad der var kendt om
|
||||||
|
en artifakt på et bestemt tidspunkt. Det første Registry kan desuden katalogisere de konstruerede componenter, og
|
||||||
|
Hub-konfigurationen vælger den manuelt ordnede procesmængde. Dependency-resolution, Contexts, downloading og
|
||||||
|
classloading kommer senere.
|
||||||
|
|
||||||
|
## 2. Versionering og Afhængigheder
|
||||||
|
Nenjim beskriver moduler, artifakter, releases, relationer og versionspolitik i Journaler. Almindelig Java-kode skal
|
||||||
|
ikke indbygge Nenjim- eller versionsspecifikke navne for at bruge et API. Et Context beskriver de ønskede valg, og
|
||||||
|
Nenjim resolver senere disse valg og Journalernes metadata til en eksakt artifakt- og dependency-graf.
|
||||||
|
|
||||||
|
### 2.1 Én Journal per artifakt
|
||||||
|
|
||||||
|
En Journal beskriver præcis én artifakt. API, tests og referenceimplementation har derfor hver sin Journal; roller
|
||||||
|
som `API`, `IMPLEMENTATION` og `TEST` gemmes ikke som et `TYPE`-felt. Journalens identitet udledes af metadata:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<GROUP>-<MODULE>-<ARTIFACT>
|
||||||
|
```
|
||||||
|
|
||||||
|
Eksempelkoordinater er `com_r35157_nenjim-hubd-api`, `com_r35157_nenjim-hubd-tests` og
|
||||||
|
`com_r35157_nenjim-hubd-impl_ref`. En komplet release-reference tilføjer en eksakt Semantic Version:
|
||||||
|
|
||||||
|
```text
|
||||||
|
com_r35157_nenjim-hubd-api:1.0.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Der findes intet separat `JOURNAL_ID`.
|
||||||
|
|
||||||
|
### 2.2 Metadata og release-indhold
|
||||||
|
|
||||||
|
`[METADATA]` indeholder coordinate-delene og en obligatorisk single-quoted `DESCRIPTION`. Den valgfrie
|
||||||
|
`INFORMATION_URI` er også single-quoted, skal være en syntaktisk gyldig absolut `URI`, og kan bruge enhver scheme,
|
||||||
|
for eksempel `https:`, `ftp:` eller `ipfs:`. Nenjim åbner eller fortolker ikke URI'en automatisk.
|
||||||
|
|
||||||
|
Hver `[RELEASE major.minor.patch]` indeholder sin egen single-quoted `LICENSE`, `PUBLISHED_AT`, `CONTENT_SIZE` og
|
||||||
|
mindst én `CONTENT_DIGEST`. Format 1 accepterer valideret `sha256` og CIDv1 (`cid1`). Licensen er fri tekst, gemmes
|
||||||
|
uden normalisering og kan være forskellig mellem releases; fremtidig licensfiltrering er ikke implementeret.
|
||||||
|
|
||||||
|
### 2.3 Komplette snapshots og Journal-kæden
|
||||||
|
|
||||||
|
Hver Journal-fil er et komplet immutable snapshot af al aktuel viden om artifakten og dens kendte releases. En
|
||||||
|
revision er aldrig en delta og arver intet implicit. En nyere revision kan ændre metadata, policy, dependencies og
|
||||||
|
selv korrigere eller fjerne tidligere kendt release-information. Et no-op snapshot er også gyldigt.
|
||||||
|
|
||||||
|
`JOURNAL_VERSION` er et strengt stigende UTC-timestamp med millisekundpræcision:
|
||||||
|
|
||||||
|
```text
|
||||||
|
uuuuMMddHHmmssSSS'Z'
|
||||||
|
```
|
||||||
|
|
||||||
|
Genesis-revisionen udelader `PREVIOUS_JOURNAL_DIGEST`. Hver efterfølger refererer til SHA-256 over forgængerens
|
||||||
|
komplette UTF-8-tekst præcis som leveret, inklusive whitespace og linjeskift. Manglende forgængere, forks, flere
|
||||||
|
genesis-revisioner, fremmede artifaktkæder og ikke-stigende versioner afvises.
|
||||||
|
|
||||||
|
`PUBLISHED_AT` er release-tid og er uafhængig af Journalens publiceringstid. Historiske queries vælger et helt
|
||||||
|
snapshot ud fra Journal-kæden; de filtrerer ikke det nyeste snapshot efter `PUBLISHED_AT`.
|
||||||
|
|
||||||
|
### 2.4 Version expressions
|
||||||
|
|
||||||
|
En `SemanticVersion` (`1.0.3`) er én eksakt release. En `VersionExpression` beskriver et sæt acceptable releases:
|
||||||
|
|
||||||
|
* `1.0.3` og `[1.0.3]` er samme eksakte selector.
|
||||||
|
* `[1.0]` betyder hele den stabile `1.0.x`-serie, ikke eksakt `1.0.0`.
|
||||||
|
* `[1.0.3-->1.0.7]` inkluderer begge grænser; `)` ekskluderer øvre grænse.
|
||||||
|
* `[1.0-->1.2]` inkluderer hele `1.2.x`; `[1.0-->1.2)` gør ikke.
|
||||||
|
* `[1.0-->` stopper før `2.0.0`; `(1.0-->` starter ved `1.1.0`.
|
||||||
|
* Komma adskiller en union af expressions.
|
||||||
|
|
||||||
|
Endpoints skal mindst indeholde major og minor. Matematiske operatorer som `>=`, prereleases, build-metadata,
|
||||||
|
tomme elementer, omvendte ranges og implicitte cross-major open ranges afvises.
|
||||||
|
|
||||||
|
### 2.5 Dependencies
|
||||||
|
|
||||||
|
Dependencies hører til en bestemt artifaktrelease og bruger altid en explicit scheme. Format 1 implementerer kun:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DEPENDS_ON=nenjim:com_r35157_nenjim-hubd-api=[1.0-->
|
||||||
|
EXCLUDES=nenjim:com_r35157_nenjim-hubd-api=1.0.5:'Known incompatibility'
|
||||||
|
PREFERRED=nenjim:com_r35157_nenjim-hubd-api=1.0.3:'Built together'
|
||||||
|
```
|
||||||
|
|
||||||
|
`DEPENDS_ON` er basissættet. `EXCLUDES` er en hård relationel begrænsning. `PREFERRED` er kun et hint og skal være
|
||||||
|
en delmængde af `DEPENDS_ON - EXCLUDES`. Flere exclusion/preference-linjer for samme target kombineres som union,
|
||||||
|
men deres individuelle beskrivelser bevares. Ukendte schemes, herunder `maven:`, afvises i format 1.
|
||||||
|
|
||||||
|
### 2.6 Artifact policy
|
||||||
|
|
||||||
|
Den valgfrie `[POLICY]` bevarer individuelle regler og beskrivelser:
|
||||||
|
|
||||||
|
* `BLACKLIST` er en hård global regel: matchende releases må ikke vælges.
|
||||||
|
* `DISCOURAGED` er et negativt hint: releasen kan stadig vælges.
|
||||||
|
* `RECOMMENDED` er et positivt hint til én eksakt release, som findes i samme snapshot.
|
||||||
|
|
||||||
|
Overlap er tilladt, og hårde regler vinder over hints. En fremtidig resolver bør søge en gyldig graf med flest muligt
|
||||||
|
kompatible anbefalinger; højeste versionsnummer er fallback, ikke en ubetinget regel. Det kan senere føre til
|
||||||
|
konvergens fra en højere lokal release til en lavere anbefalet release, men den adfærd er ikke implementeret nu.
|
||||||
|
|
||||||
|
### 2.7 Parser og immutable model
|
||||||
|
|
||||||
|
Parseren kræver `FORMAT_VERSION=1` som første faktiske entry, men tillader blanke linjer og `#`-kommentarer.
|
||||||
|
`#` inde i single quotes er tekst. Quoted værdier understøtter `\'` og `\\`. Ukendte, malformed, duplikerede eller
|
||||||
|
forkert placerede felter/sektioner afvises med source, linjenummer og årsag. Hele dokumentet valideres før servicens
|
||||||
|
state ændres. Alle publicerede modeller og collections er immutable og defensivt kopieret.
|
||||||
|
|
||||||
|
### 2.8 JournalService, JournalServiceManager og disk-layout
|
||||||
|
|
||||||
|
En `Service` er det domæne-API, som applikationer og andre services bruger i daglig drift. `JournalService` er en
|
||||||
|
registrerbar Nenjim component, men API'et er fortsat filesystem-uafhængigt og read-only: det kan vise coordinates,
|
||||||
|
kronologiske revisionslister, latest, exact og newest-at-or-before snapshots samt en eksakt release fra et valgt
|
||||||
|
snapshot. Det eksponerer ikke lifecycle, filesystem paths, refresh, parser eller mutation. Queries er først
|
||||||
|
tilgængelige, når Journal-processens initiale loading er lykkedes.
|
||||||
|
|
||||||
|
En `ServiceManager` er et separat administrationshandle, som en composition/lifecycle owner kan beholde uden at
|
||||||
|
give almindelige service-consumers administrative rettigheder. `JournalServiceManager` er passiv og eksponerer kun
|
||||||
|
den normaliserede Journal-root og explicit refresh; den har ingen `start()`, `stop()` eller service-accessor og er
|
||||||
|
hverken component eller process. Hub'en konstruerer ét Journal-objekt, som både er `JournalService` og
|
||||||
|
`NenjimProcess`, samt en separat manager bundet til objektets package-private admin-view.
|
||||||
|
|
||||||
|
Når Hub'en starter Journal-processen, læser referenceimplementationen komplette UTF-8-filer kronologisk og
|
||||||
|
validerer placement/navne. Hele den initiale worldview publiceres først, når alle runtime-filer er accepteret; en
|
||||||
|
fejl efterlader servicen utilgængelig uden partial state og samme instans kan ikke startes igen. Refresh efter
|
||||||
|
vellykket startup er fortsat explicit, idempotent og per-ingestion atomisk; der er ingen watcher eller background
|
||||||
|
polling.
|
||||||
|
|
||||||
|
`JournalServiceImpl` vedligeholder internt kæderne og udfører atomisk ingestion med exact-text SHA-256. Identisk tekst
|
||||||
|
er idempotent; anderledes tekst med samme coordinate og Journal-version er en conflict. Parser, ingestion og mutable
|
||||||
|
chain state er implementation details og findes ikke i public signatures. Public API-pakkerne er opdelt således:
|
||||||
|
|
||||||
|
```text
|
||||||
|
com.r35157.nenjim.service.journal JournalService, JournalServiceManager
|
||||||
|
com.r35157.nenjim.service.journal.model sammensatte immutable domænemodeller
|
||||||
|
com.r35157.nenjim.service.journal.valuetypes små værdidefinerede typer
|
||||||
|
com.r35157.nenjim.service.journal.exception Journal-specifikke exceptions
|
||||||
|
com.r35157.nenjim.service.journal.impl.ref referenceimplementation og interne helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
data/nenjim/journals/
|
||||||
|
└── <artifact-coordinate>/
|
||||||
|
├── <journal-version>.journal
|
||||||
|
└── <journal-version>.journal.example
|
||||||
|
```
|
||||||
|
|
||||||
|
Kun `.journal` er runtime-input. `.journal.example` dokumenterer formatet og indlæses ikke. Roden skal allerede
|
||||||
|
eksistere og være læsbar; Journal-processen og dens manager opretter, overskriver eller sletter ikke Journal-filer.
|
||||||
|
|
||||||
|
## 3. Opdateringer og Sikkerhed
|
||||||
|
Det implementerede Journal-lag kan repræsentere nye sikkerhedsoplysninger som en nyere immutable revision, for
|
||||||
|
eksempel via `BLACKLIST` eller en ændret `RECOMMENDED`. Automatisk synchronization, selection, reconciliation og
|
||||||
|
upgrade/downgrade er fremtidigt arbejde.
|
||||||
|
|
||||||
|
## 4. Betalingssystem og Integration med AssetAZ
|
||||||
|
Betaling og AssetAZ-integration er langsigtet vision og er ikke en del af Journal-formatet eller denne implementation.
|
||||||
|
|
||||||
|
## 5. Backing Store og Fleksibilitet
|
||||||
|
Den nuværende Journal-proces læser kun det dokumenterede lokale disk-layout gennem en intern filesystem-loader.
|
||||||
|
Artifact downloading, IPFS retrieval, remote Journals og andre backing stores er ikke implementeret. En
|
||||||
|
`ipfs:`-værdi i `INFORMATION_URI` er metadata, ikke en retrieval-instruks.
|
||||||
|
|
||||||
|
## 6. Standardiseret og Automatiseret Pakkehåndtering
|
||||||
|
Journalen modellerer dependency-krav og indholdsidentiteter, men downloader ikke bytes og verificerer ikke downloadet
|
||||||
|
indhold. Dependency-graph resolution, automatisk release-selection og Maven-resolution er fremtidigt arbejde.
|
||||||
|
|
||||||
|
## 7. Betydningen af Semantisk Versionering
|
||||||
|
Format 1 accepterer kun stabile eksakte releases med alle tre numeriske komponenter. Det forudsætter ikke, at en
|
||||||
|
resolver altid vælger den numerisk højeste version: hard constraints afgrænser de gyldige valg, og policy/dependency
|
||||||
|
hints kan på sigt foretrække en lavere kompatibel release.
|
||||||
|
|
||||||
|
## 8. Componenter og det centrale Registry
|
||||||
|
|
||||||
|
En Nenjim component er en færdigkonstrueret byggeklods, som er klar til brug. Rollen som service, applikation,
|
||||||
|
algoritme eller adapter og klassifikationen som aktiv proces er uafhængige dimensioner. En service eksponerer et
|
||||||
|
domain API, mens en applikation beskriver en user-facing eller orchestration-rolle. Registry-API'et bruger det
|
||||||
|
fælles ord `component` og ikke `plugin`, og `NenjimComponent` er en ren marker interface. En component, som Hub'en
|
||||||
|
kan starte og tracke, implementerer desuden den canonical `com.r35157.nenjim.process.NenjimProcess`, som kun lover
|
||||||
|
`start()` og ikke en fælles stop-, restart- eller health-operation. En service- eller applikationsimplementation kan
|
||||||
|
derfor også være en process; passive componenter er det ikke.
|
||||||
|
|
||||||
|
Ticker følger denne opdeling: almindelige consumers modtager `TickerService` med kun latest-price query, mens det
|
||||||
|
samme `TickerServiceImpl`-objekt separat er en `NenjimProcess`, som Hub'en starter. Tickerens source-lifecycle er
|
||||||
|
implementation-owned; `PriceSource`-instanser startes internt, og en fejlet Ticker-start stopper de sources, som nåede
|
||||||
|
at starte. Der findes ingen almindelig Ticker-serviceoperation til start eller stop.
|
||||||
|
|
||||||
|
Evelyn IOU Burner følger samme opdeling, selv om dens nuværende domain-service-view er tomt:
|
||||||
|
`EvelynIOUBurnerService` giver typed Registry- og dependency-identitet uden eksterne håndtag, mens det samme
|
||||||
|
`EvelynIOUBurnerServiceImpl`-objekt separat er en Hub-styret `NenjimProcess`. Kun process-view'et eksponerer startup;
|
||||||
|
hverken service-interfacet eller implementationen eksponerer almindelig stop eller restart.
|
||||||
|
|
||||||
|
En `NenjimComponentId` er registration metadata i ét Registry og er ikke en egenskab ved objektet. ID-formatet er
|
||||||
|
lower-case ASCII med dot-separated segments og valgfrie interne bindestreger, f.eks. `nenjim.registry.service` og
|
||||||
|
`assetaz.price-source.raydium-pool.eve-usdt`. Det samme objekt må registreres under flere forskellige ID'er, og én
|
||||||
|
registration bliver automatisk synlig gennem alle component interfaces, som objektet implementerer. Registry-
|
||||||
|
service- og process-view af Registry-objektet har derfor samme ID og samme object identity.
|
||||||
|
|
||||||
|
`NenjimRegistryService` er det read-only katalog, som almindelige consumers modtager gennem constructor injection.
|
||||||
|
Det kan returnere et immutable registration-order snapshot af ID'er for et component interface eller slå ét ID op
|
||||||
|
gennem et forventet interface. Et manglende ID giver `null`; et eksisterende ID med forkert interface er en caller
|
||||||
|
programming error. Public API'et kan ikke registrere eller fjerne componenter.
|
||||||
|
|
||||||
|
`NenjimHubImpl` repræsenterer én kørende Nenjim runtime. Hub'en læser deployment-konfigurationen, ejer dependency
|
||||||
|
wiring og lifecycle, konstruerer hele den nuværende passive component graph og konstruerer kun de processer, som er
|
||||||
|
valgt til autorun. Et implementation-only Registry assembly udleverer read-only service-view, process-view af det
|
||||||
|
samme Registry-objekt og en separat `NenjimRegistryServiceManager`. Manageren er kun Hub'ens passive
|
||||||
|
registrationshandle: den er hverken component eller process, har ingen `start()` eller service-accessor, registreres
|
||||||
|
ikke og starter aldrig en component.
|
||||||
|
|
||||||
|
Et tilsvarende implementation-only Journal assembly udleverer read-only service-view, process-view af det samme
|
||||||
|
Journal-objekt og en separat passiv `JournalServiceManager`. Journal-manageren registreres ikke og giver kun adgang
|
||||||
|
til root og refresh gennem et package-private admin-view; almindelige `JournalService`-consumers kan ikke udlede
|
||||||
|
administrationsadgangen.
|
||||||
|
|
||||||
|
Alle moderne JavaFX-applikationer deler ét JVM-wide JavaFX toolkit. Det repræsenteres af
|
||||||
|
`JavaFxRuntimeService`, mens samme `JavaFxRuntimeServiceImpl`-objekt også implementerer `NenjimProcess` for Hub'ens
|
||||||
|
lifecycle. Når den er valgt, konstruerer og registrerer Hub'en objektet som `nenjim.javafx-runtime.service`; den skal
|
||||||
|
stå før valgte JavaFX-applikationer i autorun-listen, så dens service-view kan injiceres efter vellykket startup. Kun
|
||||||
|
processens `start()` må kalde `Platform.startup(...)`; `runLater(...)` starter aldrig toolkit'et implicit og afviser
|
||||||
|
arbejde, indtil startup er lykkedes. Startup venter på JavaFX Application Thread og sætter
|
||||||
|
`Platform.setImplicitExit(false)`. Udelades alle JavaFX-processer, konstruerer Hub'en ikke runtime-objektet og
|
||||||
|
initialiserer ikke toolkittet.
|
||||||
|
|
||||||
|
Den package-private `NenjimRegistryServiceAdmin` er managerens interne mutationsadgang. Den må ikke gøres public,
|
||||||
|
fordi almindelige consumers ellers kunne caste deres Registry-service til mutationsinterfacet og bryde det
|
||||||
|
read-only service contract. Registration starter ikke et objekt, og Registry'et forbliver internt åbent for senere
|
||||||
|
registration efter startup.
|
||||||
|
|
||||||
|
### 8.1 Obligatorisk `NenjimHub.conf` format 1
|
||||||
|
|
||||||
|
Hub'en læser kun `conf/NenjimHub.conf`. Filen skal eksistere som en læsbar regular file; en manglende eller ugyldig
|
||||||
|
fil stopper startup, før Registry startes. `conf/NenjimHub.conf.example` er kun en versioneret copy-template: Hub'en
|
||||||
|
læser, kopierer og genererer den aldrig og falder hverken tilbage til den eller til en hardcoded procesliste.
|
||||||
|
|
||||||
|
Den første faktiske entry skal være `FORMAT_VERSION=1`; før den må der kun stå blanke linjer og full-line
|
||||||
|
`#`-kommentarer. Den næste faktiske entry skal være den ene eksakte `[AUTO_START]`-header. Hver aktiv linje i
|
||||||
|
sektionen er ét `NenjimComponentId`, og rækkefølgen bevares præcist. Leading/trailing whitespace ignoreres, en trimmet
|
||||||
|
linje som begynder med `#` er en kommentar, og `#` efter faktisk indhold starter en trailing kommentar. Inde i
|
||||||
|
sektionen fortsætter en comment-only line listen, mens den første whitespace-only line afslutter sektionen; EOF kan
|
||||||
|
også afslutte den. Efter afslutningen tillades kun blanke linjer og kommentarer. Formatet har ingen quoting eller
|
||||||
|
escaping for `#`.
|
||||||
|
|
||||||
|
Hele listen valideres før første proces-start. Ugyldige, duplikerede, ukendte og passive component-ID'er afvises.
|
||||||
|
Registry skal stå præcis én gang først og Journal præcis én gang som nummer to. De kendte valgbare processer er:
|
||||||
|
|
||||||
|
```text
|
||||||
|
nenjim.registry.service
|
||||||
|
nenjim.journal.service
|
||||||
|
assetaz.ticker.default
|
||||||
|
jupiter-perps-alarm.default
|
||||||
|
evelyn.status.prod
|
||||||
|
evelyn.status.test
|
||||||
|
evelyn.iou-burner.prod
|
||||||
|
evelyn.perps.prod
|
||||||
|
evelyn.perps.test
|
||||||
|
nenjim.javafx-runtime.service
|
||||||
|
evelyn.mission-control.default
|
||||||
|
nenjim.process-manager.default
|
||||||
|
```
|
||||||
|
|
||||||
|
Ved opgradering erstattes `evelyn.service.prod` med `evelyn.status.prod` og
|
||||||
|
`evelyn.service.test` med `evelyn.status.test` i deploymentets private `conf/NenjimHub.conf`, hvor de forekommer.
|
||||||
|
Bevar rækkefølge og aktive/udkommenterede valg. De gamle ID'er accepteres ikke længere. Hub'en konstruerer samme
|
||||||
|
navngivne `Production`- og `Test`-instanser gennem `EvelynStatusServiceImpl` og injicerer deres
|
||||||
|
`EvelynStatusService`-views i Mission Control. Process Manager viser de nye ID'er fra Hub'ens startup-snapshot.
|
||||||
|
Historik ligger fortsat i `data/evelyn/<instance-name>/status.log` med formatversion 1 og kræver ingen migration.
|
||||||
|
|
||||||
|
Mission Control kræver både `evelyn.perps.prod` og `evelyn.perps.test` før `evelyn.mission-control.default`
|
||||||
|
i den private `conf/NenjimHub.conf`. Erstat den tidligere `evelyn.perps.default`-linje med de to nye ID'er;
|
||||||
|
det gamle ID accepteres ikke længere. Hub'en konstruerer, registrerer og starter to separate
|
||||||
|
`EvelynPerpsServiceImpl`-processer med separate hardcodede wallet-lister og tilhørende Jupiter-integrationer.
|
||||||
|
Begge får desuden den fælles `PhoenixPerpsService` uden positionscache, som Hub'en registrerer passivt som
|
||||||
|
`phoenix.perps.default`; den skal ikke tilføjes til `[AUTO_START]`.
|
||||||
|
Hub'en injicerer deres read-only
|
||||||
|
service-views til de tilsvarende miljøfaner i EMC. Process Manager viser begge ID'er efter vellykket startup.
|
||||||
|
Den lokale udviklingskonfiguration er opdateret; andre deployments med EMC skal migreres tilsvarende.
|
||||||
|
Udeladt eller forkert rækkefølge giver en tydelig konstruktionsfejl uden automatisk indsættelse.
|
||||||
|
Det minimale versionsstyrede bootstrap-eksempel er uændret. Hver service ejer sit eget snapshot og
|
||||||
|
opdaterer det fra Jupiter og Phoenix i baggrunden med 60 sekunders pause efter fuldførte gennemløb.
|
||||||
|
Sidste vellykkede data bevares separat pr. wallet og venue. EMC-læsninger bruger kun
|
||||||
|
det publicerede snapshot. Wallet-listerne fra issue #98 indeholder seks PROD-adresser og én TEST-adresse.
|
||||||
|
|
||||||
|
Hvis `evelyn.iou-burner.prod` vælges, skal både `assetaz.ticker.default` og
|
||||||
|
`evelyn.status.prod` stå før den. Hub'en injicerer Production status-servicen til
|
||||||
|
referenceprisopslag før nye buybacks; en manglende eller senere status-forgænger
|
||||||
|
giver en tydelig konstruktionsfejl uden implicit oprettelse eller omordning.
|
||||||
|
|
||||||
|
Efter validering konstruerer og starter Hub'en Registry, registrerer det som `nenjim.registry.service` og registrerer
|
||||||
|
startup-milepælen. Derefter konstrueres og registreres hele den nuværende passive graph uden at starte workers eller
|
||||||
|
processer. Først herefter behandles resten af `[AUTO_START]` i den angivne rækkefølge, begyndende med Journal. Hver
|
||||||
|
valgt proces konstrueres først, når dens linje nås, registreres før `start()` og tilføjes kun til Hub'ens immutable
|
||||||
|
ordered snapshot, når `start()` returnerer. Journal er derfor stadig den anden succesfulde proces, selv om den passive
|
||||||
|
graph konstrueres mellem Registry- og Journal-start.
|
||||||
|
|
||||||
|
`[AUTO_START]` er foreløbig deploymentets komplette, manuelt ordnede proces-dependency closure. Hub'en finder,
|
||||||
|
indsætter eller omordner ikke dependencies. En krævet proces skal stå før sin consumer; eksempelvis skal
|
||||||
|
`nenjim.javafx-runtime.service` stå før Mission Control og Process Manager. Mangler forgængeren, stopper konstruktion
|
||||||
|
eller startup fail-fast uden implicit reparation. En udeladt proces bliver hverken konstrueret, registreret, startet
|
||||||
|
eller vist af Process Manager. Den versionerede template vælger kun de obligatoriske Registry- og Journal-processer;
|
||||||
|
alle øvrige processer skal tilføjes eksplicit til deploymentets private konfiguration.
|
||||||
|
|
||||||
|
Et minimalt headless eksempel, svarende til den versionerede template, er:
|
||||||
|
|
||||||
|
```text
|
||||||
|
FORMAT_VERSION=1
|
||||||
|
|
||||||
|
[AUTO_START]
|
||||||
|
nenjim.registry.service
|
||||||
|
nenjim.journal.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Tickerens egen component-konfiguration vælger fortsat eksplicit
|
||||||
|
`assetaz.price-source.raydium-pool.eve-usdt` gennem Registry'et; den hardcodede price source er registreret, men
|
||||||
|
inaktiv. Fejl under passiv konstruktion, valgt proceskonstruktion, registration eller startup stopper straks og
|
||||||
|
forhindrer alle senere processer. Tidligere succeser forbliver synlige, den fejlende proces registreres ikke som
|
||||||
|
startet, og der udføres ingen generic rollback. Online point og det afsluttende blocking wait nås kun efter alle
|
||||||
|
konfigurerede processer er startet. Process Manager viser fortsat Hub'ens faktiske succesfulde snapshots og ikke den
|
||||||
|
ønskede konfigurationsliste.
|
||||||
|
|
||||||
|
`com.r35157.nenjim.hubd.Main` er kun det yderste Java-bootstrap. `Main.main(...)` konstruerer `NenjimHubImpl` gennem
|
||||||
|
det globale `NenjimHub`-interface og kalder `start()` præcis én gang. Hub'en ejer også det eksisterende online point
|
||||||
|
og den afsluttende blocking wait. Hub'en er hverken component eller process og registreres ikke i sit eget Registry.
|
||||||
|
|
||||||
|
Det implementerede Registry er ét katalog og er ikke et authorization-system. Contexts, artifact-version semantics,
|
||||||
|
resolution, classloading, automatic discovery, public runtime loading/registration, removal, persistence, events,
|
||||||
|
automatic dependency construction/start-all, usage tracking og permissions er fremtidigt arbejde. Den nuværende
|
||||||
|
autorun-fil er ikke Context resolution eller process discovery. Journal publishing, remote synchronization og
|
||||||
|
signature validation er ligeledes ikke implementeret.
|
||||||
|
|
||||||
|
## 9. Integration med AssetAZ og økonomisk aktivitet
|
||||||
|
Økonomi, publicering og distribution er fremtidig vision. Format 1 beskriver alene artifaktviden; det indeholder
|
||||||
|
ingen betalings-, salgs- eller transaktionsadfærd.
|
||||||
|
|
||||||
|
## 10. Installation af en NenjimHub
|
||||||
|
For at NenjimHub'en kan køre godt, skal den leve i et samspil med nogle andre komponenter. Dette kan sættes op på
|
||||||
|
forskellige måder. Efterfølgende er et eksempel på en opsætning i en Virtuel Maskine, eller en lille fysisk host.
|
||||||
|
Bemærk at ECC ram er kritisk for stabiliteten, specielt hvis NenjimHub'en skal arbejde med finansielle aktiviteter.
|
||||||
|
Efterfølgende er en beskrivelse af hvordan man laver en opsætning som virker. Du kan lave specielle rettelser, hvis
|
||||||
|
du forstår hvad du laver. Det vil dog være en god idé at følge en standard opsætning, så alt bliver lidt nemmere
|
||||||
|
at styre.
|
||||||
|
|
||||||
|
## 10.1 Reference opsætning af Fysisk eller Virtuel Maskine (VM) til NenjimHub
|
||||||
|
Her beskrives hvordan man laver en standard opsætning af en NenjimHub. Denne opsætning vil også blive refereret til
|
||||||
|
som reference opsætningen, sådan at man har et fælles udgangspunkt at tale ud fra.
|
||||||
|
|
||||||
|
## 10.1.2 Minimale system krav
|
||||||
|
Det er klart at jo mere NenjimHub'en skal lave, jo større er hardware kravene, men med en 'NenjimHub Reference
|
||||||
|
Configuration', får du en opsætning der kan starte op og som du kan bygge videre fra.
|
||||||
|
Derfor er kravene til opsætningen meget moderate.
|
||||||
|
* CPU: 64bit x86
|
||||||
|
* RAM: 2GB (Helst ECC)
|
||||||
|
* Storage: 2x16GB (for RAID)
|
||||||
|
* Networking: Public IPv4 access
|
||||||
|
|
||||||
|
## 10.1.3 Basis Operativ System installation
|
||||||
|
Hvis du installerer som en VM (Proxmox), så brug følgende:
|
||||||
|
* Aktiver Qemu Agent
|
||||||
|
* Virtual Machine type: q35
|
||||||
|
* BIOS: OVMF (UEFI - med EFI disk + Pre-Enrolled keys)
|
||||||
|
* Storage: 2 x 16GB (SSD emulation + Discard)
|
||||||
|
* CPU: 2 CPU cores (type: x86-64-v2-AES)
|
||||||
|
* Memory: 2GB (Minimum 1GB + Ballooning)
|
||||||
|
* Network: Adgang til et netværk med DHCP service (Fravælg Proxmox firewall)
|
||||||
|
|
||||||
|
Installer basis Operativ System:
|
||||||
|
- Boot installations medie for Debian-13
|
||||||
|
- Vælg defaults med mindre andet angivet nedenfor
|
||||||
|
- User setup:
|
||||||
|
- Full name: System Operator
|
||||||
|
- Username: sysop
|
||||||
|
- Partition disks:
|
||||||
|
- Manual
|
||||||
|
- Lav nye tomme partitions tabeller
|
||||||
|
- Disk 1:
|
||||||
|
- Partition 1
|
||||||
|
- Size: 512MB
|
||||||
|
- Name: EFI1
|
||||||
|
- Use as: EFI System Partition
|
||||||
|
- Bootable: on
|
||||||
|
- Partition 2
|
||||||
|
- Size: 8GB
|
||||||
|
- Name: ROOT1
|
||||||
|
- Use as: Ext4
|
||||||
|
- Label: ROOT
|
||||||
|
- Partition 3
|
||||||
|
- Size: 8.7GB (Resten af disken)
|
||||||
|
- Name: ZFS1
|
||||||
|
- Use as: do not use
|
||||||
|
- Disk 2:
|
||||||
|
- Partition 1
|
||||||
|
- Size: 512MB
|
||||||
|
- Name: EFI2
|
||||||
|
- Use as: do not use
|
||||||
|
- Partition 2
|
||||||
|
- Size: 8GB
|
||||||
|
- Name: ROOT2
|
||||||
|
- Use as: do not use
|
||||||
|
- Partition 3
|
||||||
|
- Size: 8.7GB (Resten af disken)
|
||||||
|
- Name: ZFS2
|
||||||
|
- Use as: do not use
|
||||||
|
- Acceptér ikke at opsætte swap endnu.
|
||||||
|
- Software selection:
|
||||||
|
- SSH server
|
||||||
|
- standard system utilities
|
||||||
|
- Reboot after installation
|
||||||
|
|
||||||
|
Install additional tools:
|
||||||
|
```
|
||||||
|
$ su -
|
||||||
|
# apt-get update; apt-get install -y htop zram-tools sudo tmux
|
||||||
|
# usermod -aG sudo sysop
|
||||||
|
(Log ud og ind igen)
|
||||||
|
# sudo bash
|
||||||
|
# vi /etc/default/zramswap
|
||||||
|
(Ret PRIORITY=1)
|
||||||
|
# sed -ri'.bak' '/^deb/ { /contrib/! s/$/ contrib/ }' /etc/apt/sources.list
|
||||||
|
# apt-get update
|
||||||
|
# mokutil --sb-state || sudo apt -y install mokutil && mokutil --sb-state
|
||||||
|
# apt install -y dkms sbsigntool shim-signed mokutil
|
||||||
|
# mokutil --import /var/lib/dkms/mok.pub
|
||||||
|
(Indtast en midlertidig adgangskode)
|
||||||
|
# apt-get install -y linux-headers-amd64 zfsutils-linux zfs-dkms zfs-zed
|
||||||
|
# reboot
|
||||||
|
(Når den blå skærm kommer, vælg 'Enroll MOK' og brug nu det password du valgte ovenover)
|
||||||
|
```
|
||||||
|
|
||||||
|
Opret ZFS pool:
|
||||||
|
```
|
||||||
|
$ sudo bash
|
||||||
|
# zpool create zfspool -O xattr=sa -o ashift=12 -O compression=lz4 -O checksum=sha512 -O dedup=on -O atime=off mirror /dev/sda3 /dev/sdb3
|
||||||
|
```
|
||||||
|
|
||||||
|
Opret swap:
|
||||||
|
```
|
||||||
|
$ sudo bash
|
||||||
|
# zfs create -V 4G -o compression=zle \
|
||||||
|
-o logbias=throughput -o sync=always \
|
||||||
|
-o primarycache=metadata -o secondarycache=none \
|
||||||
|
-o com.sun:auto-snapshot=false zfspool/swap
|
||||||
|
# mkswap -f /dev/zvol/zfspool/swap
|
||||||
|
# echo /dev/zvol/zfspool/swap none swap sw,pri=0 0 0 >> /etc/fstab
|
||||||
|
# swapon -av
|
||||||
|
```
|
||||||
|
|
||||||
|
Installér docker (using official repository instead of Debian repository):
|
||||||
|
```
|
||||||
|
$ sudo bash
|
||||||
|
# apt install -y apt-transport-https ca-certificates curl gpg
|
||||||
|
# curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
|
||||||
|
# echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/debian trixie stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
# apt update
|
||||||
|
# apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
|
# usermod -aG docker sysop
|
||||||
|
(Log out and in again)
|
||||||
|
```
|
||||||
|
|
||||||
|
Flyt docker storage til ZFS:
|
||||||
|
```
|
||||||
|
$ sudo bash
|
||||||
|
# systemctl stop docker
|
||||||
|
# zfs create -V 3G zfspool/var_lib_docker
|
||||||
|
# mkfs.ext4 /dev/zvol/zfspool/var_lib_docker
|
||||||
|
# rm -rf /var/lib/docker
|
||||||
|
# mkdir /var/lib/docker
|
||||||
|
# echo "/dev/zvol/zfspool/var_lib_docker /var/lib/docker ext4 defaults 0 2" >> /etc/fstab
|
||||||
|
# systemctl daemon-reload
|
||||||
|
# mount /var/lib/docker
|
||||||
|
# chmod 710 /var/lib/docker
|
||||||
|
# systemctl enable docker ; systemctl start docker
|
||||||
|
```
|
||||||
|
|
||||||
|
Installer ZeroTier:
|
||||||
|
```
|
||||||
|
$ sudo bash
|
||||||
|
|
||||||
|
# curl -fsSL 'https://raw.githubusercontent.com/zerotier/ZeroTierOne/main/doc/contact%40zerotier.com.gpg' | gpg --import
|
||||||
|
(Importer ZeroTier's signeringsnøgle)
|
||||||
|
|
||||||
|
# gpg --fingerprint contact@zerotier.com
|
||||||
|
(Check nøglen)
|
||||||
|
|
||||||
|
#if z=$(curl -fsSL 'https://install.zerotier.com/' | gpg --decrypt); then
|
||||||
|
echo "$z" | sudo bash
|
||||||
|
else
|
||||||
|
echo "Signaturtjek mislykkedes – kører ikke scriptet." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
(Henter og dekryperer (verificerer) install-scriptet og kører det kun hvis signaturen er OK)
|
||||||
|
```
|
||||||
|
|
||||||
|
Konfigurer ZeroTier net:
|
||||||
|
- Login på 'https://my.zerotier.com'
|
||||||
|
- Opret et nye netværk 'Create A Network'
|
||||||
|
(Som skal forbinde terminaler og AssetAZHub)
|
||||||
|
- Klik på det nye netværk
|
||||||
|
- Basic/Name: 'assetaz-<ditid>'
|
||||||
|
(f.eks 'assetaz-mortengh')
|
||||||
|
- Vælg et IP range, f.eks 192.168.192.*
|
||||||
|
- Forbind AssetAZHub til netværket (netværks id'et kan ses på websiden):
|
||||||
|
- # zerotier-cli join a8bea75acfa45477
|
||||||
|
- Autoriser den nye maskine på nettet. Dette gøres ved at trykke på 'Edit' ved maskinen på websiden, som nu
|
||||||
|
burde kunne se den. Autoriser den, giv den et navn og evt. en beskrivelse.
|
||||||
|
- Tryk 'Add IP' og giv den en IP adresse inden for det range du har valgt oven over (f.eks 192.168.192.1). Tryk 'Save'
|
||||||
|
- Se at AssetAZHub' nu kan pinge sig selv:
|
||||||
|
# ping 192.168.192.1
|
||||||
|
|
||||||
|
Forbind terminal (f.eks en Android telefon)
|
||||||
|
- Installer ZeroTier-One
|
||||||
|
- Forbind til det samme net som hub'en.
|
||||||
|
- Autoriser terminalen inde på ZeroTiers web side og set terminalen ip adresse til noget kendt f.eks 192.168.192.2.
|
||||||
|
(Så kører hub'en på *.1 og terminalen på *.2)
|
||||||
|
|
||||||
|
Opsæt locale
|
||||||
|
|
||||||
|
Opret Docker baby swarm cluster
|
||||||
|
```
|
||||||
|
```
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#<center><H1>Nenjim TestTool<br>Documentation</H1></center>
|
||||||
|
|
||||||
|
## Byg af NenjimTestTool
|
||||||
|
NOTE: NenjimTestTool ligger i øjeblikket i Cauldron, men skal flyttes ud i sit egent projekt senere. Indtil det sker
|
||||||
|
bygges NenjimTestTool sammen med alt det andet som ligger i Cauldron.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
~/projects/r35157_cauldron$ ./mvnw package
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Opstart af NenjimTestTool
|
||||||
|
For at kunne starte NenjimTestTool, skal du have startet en NenjimHub instans, da NenjimTestTool startes igennem
|
||||||
|
Nenjim.
|
||||||
|
|
||||||
|
Der er jo forskellige interfaces til NenjimHub'en, men en nem måde er hvis du har aktiveret CLI interfacet til
|
||||||
|
NenjimHub'en - så kan du state den ved at følge følgende trace:
|
||||||
|
``` bash
|
||||||
|
$ telnet localhost 38010
|
||||||
|
Trying ::1...
|
||||||
|
Connected to localhost.
|
||||||
|
Escape character is '^]'.
|
||||||
|
NenjimHub 0.1 r35157_reference_implementation 0.0.0.1
|
||||||
|
OK
|
||||||
|
START crypto.r35157.nenjim.NenjimTestTool
|
||||||
|
OK (0ms)
|
||||||
|
```
|
||||||
|
|
||||||
|
Der åbnes nu et vindue for TestToolet.
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
# Phoenix Perps read-only integration
|
||||||
|
|
||||||
|
`PhoenixPerpsService`, `PhoenixPerpsAccount`, `PhoenixPerpsPosition`, `PhoenixAccountRisk`, `PhoenixTwapOrder` and `PhoenixTwapWarning` live in
|
||||||
|
`com.r35157.libs.phoenix.perps`; the REST implementation is in `impl.ref`.
|
||||||
|
They contain no Evelyn/UI types or signing/trading operations. NenjimHub creates
|
||||||
|
one adapter without a portfolio cache as `phoenix.perps.default` and injects it into both Evelyn
|
||||||
|
Perps processes. Each process owns its own wallet list, refresh worker and snapshot.
|
||||||
|
The adapter also receives the existing `SolanaBlockChain` for local trader PDA
|
||||||
|
derivation only; risk lookup does not use its RPC or signing operations.
|
||||||
|
See [Evelyn](Evelyn.md#12-perps-service-og-mission-control) for consumer behavior and
|
||||||
|
[the canonical system overview](../../r35157_docs/projects/evelyn/README.md#evelynperpsservice)
|
||||||
|
for configuration and deployment status.
|
||||||
|
|
||||||
|
## REST reads and account model
|
||||||
|
|
||||||
|
The implementation uses these official endpoints at `https://perp-api.phoenix.trade`:
|
||||||
|
|
||||||
|
1. [`GET /v1/trader/state/{authority}?traderPdaIndex=0`](https://docs.phoenix.trade/api/trader/get-trader-state)
|
||||||
|
returns the wallet authority's version-1 snapshot and subaccounts. The
|
||||||
|
[account model](https://docs.phoenix.trade/sdk/accounts) currently uses trader PDA
|
||||||
|
index 0 for user accounts. Subaccount 0 is cross-margin; other returned subaccounts
|
||||||
|
are isolated. `getAccounts()` returns actual balances separately from positions;
|
||||||
|
`getOpenPositions()` remains a convenience flattening of those accounts. Keep funded
|
||||||
|
empty Cross accounts in the overview and include only nonzero `basePositionLots`
|
||||||
|
as position rows. An omitted `positions` field is documented as optional and
|
||||||
|
means empty; null or a wrong type is rejected.
|
||||||
|
2. [`GET /v1/view/exchange/markets`](https://docs.phoenix.trade/api/exchange/list-markets)
|
||||||
|
supplies each symbol's `baseLotsDecimals`, `tickSize`, leverage tiers and taker fee.
|
||||||
|
3. [`GET /v1/market/{symbol}/mark-price`](https://docs.phoenix.trade/api/exchange/get-mark-price)
|
||||||
|
supplies `markPrice.price` in USD. The API permits an absent mark price; a held
|
||||||
|
position remains visible with unknown price-dependent metrics when the mark is absent.
|
||||||
|
A present nonpositive or malformed price still fails the lookup.
|
||||||
|
4. [`GET /v1/trader/{authority}/twap?traderPdaIndex=0`](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/api/traders/client.ts)
|
||||||
|
supplies TWAP snapshots for every wallet with existing subaccounts, including accounts
|
||||||
|
without positions. Filled outstanding orders become matched progress or warning observations;
|
||||||
|
zero-fill and completed orders remain invisible. See [TWAP observations](#twap-observations).
|
||||||
|
5. [`GET /v1/view/trader/{pubkey}`](https://docs.phoenix.trade/api/trader/get-trader)
|
||||||
|
supplies risk for each visible account, including funded empty Cross accounts.
|
||||||
|
Its key is the derived trader account, not the wallet or a TWAP key. See
|
||||||
|
[venue-reported account risk](#venue-reported-account-risk).
|
||||||
|
|
||||||
|
A trader state with no subaccounts needs only the first request. Otherwise TWAP is
|
||||||
|
read immediately after state, with its original response receipt time preserved.
|
||||||
|
Metadata is fetched once if there are held positions or filled outstanding TWAPs;
|
||||||
|
marks are fetched only for distinct held symbols, followed by each visible account's
|
||||||
|
risk read. Registered empty accounts without relevant TWAPs need no metadata or
|
||||||
|
marks; funded or warning-only empty Cross accounts also receive their risk read.
|
||||||
|
Request starts are spaced at least one second apart across both environments to avoid
|
||||||
|
bursts of per-market reads. Each HTTP request has a 10-second timeout and uses the
|
||||||
|
Hub's existing HTTP client (no redirects by default). On HTTP 429 the adapter respects
|
||||||
|
`Retry-After` (seconds or HTTP date; 60 seconds if missing/invalid). Further calls
|
||||||
|
fail fast during that shared cooldown, letting Jupiter and other wallet attempts
|
||||||
|
continue; the normal refresh cycle retries later. There are no automatic
|
||||||
|
retries, new API keys or authentication/signing flows. Public reads worked during
|
||||||
|
verification, despite generated API security annotations referring to bearer auth.
|
||||||
|
HTTP failures, including 401/403/404/429, are errors rather than empty portfolios.
|
||||||
|
The shared pacing follows Phoenix's [rate-limit guidance](https://docs.phoenix.trade/sdk/ws-api-best-practices);
|
||||||
|
it does not guarantee a fixed quota.
|
||||||
|
|
||||||
|
## Units, precision and identity
|
||||||
|
|
||||||
|
The official [SDK's `baseLotsToBaseUnits`](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/units.ts)
|
||||||
|
uses `lots / 10 ** baseLotsDecimals`; the exponent may be negative. The adapter uses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
quantity = BigDecimal(basePositionLots).scaleByPowerOfTen(-baseLotsDecimals)
|
||||||
|
sizeUsd = abs(quantity) * markPrice.price
|
||||||
|
```
|
||||||
|
|
||||||
|
All lot counts use `BigInteger`; conversion and multiplication use `BigDecimal`
|
||||||
|
without a rounding context. Jackson parses JSON price numbers directly as decimal,
|
||||||
|
without a `double` intermediate. For example, 2,144 SOL lots at `baseLotsDecimals=2`
|
||||||
|
are 21.44 SOL; at a mark price of 104.45 USD, notional is 2,239.408 USD. Entry price,
|
||||||
|
collateral, virtual quote lots and margin are not substitutes for current notional.
|
||||||
|
Rounding to cents occurs only in the EMC display formatter. Margin and estimated
|
||||||
|
exit fees additionally follow the venue rounding described below.
|
||||||
|
|
||||||
|
Within each wallet's Phoenix venue, identity is `traderPdaIndex:subaccountIndex:symbol`.
|
||||||
|
Sequence numbers do not affect identity. Rows sort by PDA index, subaccount index
|
||||||
|
and symbol; labels use `SYMBOL-PERP`. Isolated rows remain directly beneath the venue.
|
||||||
|
Cross positions belong beneath their explicit `Cross` account group, which supersedes
|
||||||
|
the former no-subaccount-level restriction from issue #99. Positive
|
||||||
|
quantity means Long and negative means Short. Thus two SOL subaccounts remain two
|
||||||
|
rows even though their labels match.
|
||||||
|
|
||||||
|
Authority/PDA identity, snapshot version, arrays, symbols, duplicate subaccounts or
|
||||||
|
positions, integer lots, metadata and prices are validated. A malformed lookup fails
|
||||||
|
as a whole; it never publishes a partially decoded portfolio. The shared Jackson
|
||||||
|
mapper is copied so strict parsing does not change other integrations.
|
||||||
|
|
||||||
|
## Refresh behavior and limitations
|
||||||
|
|
||||||
|
Each Evelyn process performs an initial background refresh, then waits 60 seconds
|
||||||
|
following each completed cycle. Jupiter and Phoenix are attempted independently for
|
||||||
|
each configured wallet. Successful results replace only that wallet/venue; failures
|
||||||
|
log wallet and venue and retain its last successful result. A valid empty result
|
||||||
|
replaces that venue's positions and account balances. EMC hides the venue heading
|
||||||
|
when it has neither position rows, Cross accounts nor TWAP warnings; its read status remains in
|
||||||
|
the wallet tooltip and the scope still contributes to snapshot totals.
|
||||||
|
Funded Cross accounts and Cross accounts with eligible warnings remain visible when they no longer hold positions. Snapshot reads never fetch
|
||||||
|
external data; EMC's three-second polling remains unchanged.
|
||||||
|
|
||||||
|
Positions and prices are separate REST reads and can belong to different slots.
|
||||||
|
This is a periodic current-notional estimate, not an atomic exchange valuation.
|
||||||
|
A cycle's duration includes all sequential reads and request pacing before the
|
||||||
|
60-second pause (32 active markets and one visible account need at least about 35 seconds for their reads). Failed
|
||||||
|
reads can leave old data visible indefinitely. A visible venue shows `(retained)` after
|
||||||
|
a failed refresh; cell tooltips include the last-success time. Childless venues are hidden,
|
||||||
|
with failed or pending first-read status available in their wallet's tooltip. Such unknown
|
||||||
|
scopes cannot contribute fabricated zero totals.
|
||||||
|
Trader PDA indices other than 0, streaming and position management are out of scope.
|
||||||
|
|
||||||
|
## Venue-reported account risk
|
||||||
|
|
||||||
|
The [official address derivation](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/pdas.ts)
|
||||||
|
uses program `EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih` and seeds
|
||||||
|
`trader`, authority bytes, a one-byte PDA index and a one-byte subaccount index.
|
||||||
|
The adapter uses the existing local Solana derivation, validates the returned
|
||||||
|
authority/trader key/PDA/subaccount, and verifies the nonzero position membership
|
||||||
|
and signed quantities against trader state. A fill during the separate reads can
|
||||||
|
therefore cause a retained failed observation rather than attach risk to mismatched
|
||||||
|
position state. Lookup works without any active TWAP.
|
||||||
|
|
||||||
|
TraderView returns `riskState`, `riskTier`, `effectiveCollateral`,
|
||||||
|
`maintenanceMargin` and position `liquidationPrice`. The actual deployed amount
|
||||||
|
objects contain integer `value` and `decimals`; the reader computes exact decimal
|
||||||
|
amounts with decimal exponents from -18 through 18 and ignores formatted `ui` strings.
|
||||||
|
Positive liquidation prices are
|
||||||
|
retained; `-1`, zero, other nonpositive values and absent prices are unavailable.
|
||||||
|
They are not zero risk or infinite safety. Optional missing fields and unsupported
|
||||||
|
future tiers cannot silently become Safe. Malformed amounts, identity mismatches
|
||||||
|
and rejected HTTP reads fail the complete wallet/venue observation.
|
||||||
|
|
||||||
|
Cross risk belongs to the shared account. The reported effective collateral can
|
||||||
|
include funding, discounted positive PnL and collateral haircuts; it is distinct
|
||||||
|
from the overview's undiscounted equity and estimated closing Value. Risk therefore
|
||||||
|
can be available even where the older capital valuation cannot value spot collateral.
|
||||||
|
The reported thresholds and tier are preserved without inventing a liquidation engine.
|
||||||
|
See [Phoenix account health](https://docs.phoenix.trade/phoenix/margin-and-risk/account-health)
|
||||||
|
and [margin math](https://docs.phoenix.trade/phoenix/margin-and-risk/margin-math).
|
||||||
|
|
||||||
|
Account risk carries its original response receipt time. Isolated risk combines
|
||||||
|
that account's reported liquidation price with its already fetched Phoenix mark;
|
||||||
|
its observation time is the older receipt. Prices and account data are separate
|
||||||
|
reads and can lag the chain. Existing failures retain original timestamps. The
|
||||||
|
[Risk presentation](Evelyn.md#risiko-i-perps) defines five-minute expiration,
|
||||||
|
neutral isolated distance, venue tier colors and group summaries.
|
||||||
|
|
||||||
|
## TWAP observations
|
||||||
|
|
||||||
|
TWAP data is matched by requested authority, PDA index, subaccount and market `assetId`
|
||||||
|
using Phoenix metadata. Only an existing nonzero position carries a progressbar, but
|
||||||
|
an eligible order that cannot be matched is preserved in its account's immutable
|
||||||
|
warning list. The order itself must have `baseLotsFilled > 0` and remaining children.
|
||||||
|
A new order without fills stays invisible everywhere, even if the market already
|
||||||
|
has a position; completed orders also create no warning. No synthetic position or
|
||||||
|
monetary group total is created. A successful snapshot without that active order
|
||||||
|
clears its progress and warning data. A failed or invalid TWAP read fails the whole wallet/venue observation,
|
||||||
|
retaining the previous complete snapshot and its original timestamps as `(retained)`.
|
||||||
|
|
||||||
|
Warnings retain the TWAP address/order sequence, asset and subaccount IDs, raw
|
||||||
|
execution/fill counters and original receipt time. Missing market metadata or no
|
||||||
|
matching open position is an observed mismatch, not proof of delisting, cancellation
|
||||||
|
or stopped execution. Missing conversion data is shown as raw lots/unknown values;
|
||||||
|
warning-only markets do not trigger mark-price requests. A matched order with no
|
||||||
|
usable future next-execution estimate keeps its progress plus a timing warning.
|
||||||
|
Account/schema/counter mismatches still fail the read; valid missing-market warnings
|
||||||
|
do not discard independent positions or account risk. See the [EMC warning presentation](Evelyn.md#twap-advarsler)
|
||||||
|
for exact placement, ancestor propagation and retained/stale qualifications.
|
||||||
|
|
||||||
|
The [SDK snapshot](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/api/traders/twap.ts)
|
||||||
|
reports `nChildOrders` as the total execution count including dust. With explicit
|
||||||
|
`nDustOrders > 0`, regular count is total minus dust count. Legacy zero/omitted dust
|
||||||
|
count with a positive `dustOrderSize` replaces one final regular child with dust.
|
||||||
|
Total quantity is regular count × regular lot size plus dust count × dust lot size;
|
||||||
|
the next size switches to dust when the regular children have executed. Convert all
|
||||||
|
sizes and cumulative `baseLotsFilled` with `baseLotsDecimals`, using exact arithmetic.
|
||||||
|
If size/dust metadata is unavailable, sizes remain unknown while independent counters
|
||||||
|
and fills remain available. Execution progress is executed/total children; it does
|
||||||
|
not assert that the planned quantity has filled completely.
|
||||||
|
|
||||||
|
Time estimates use the current nominal **200 ms per Solana slot**, documented by the
|
||||||
|
[Solana Foundation in October 2026](https://solana.com/fr/upgrades/reduced-slot-times).
|
||||||
|
The older 400 ms assumption no longer applies. At receipt of the TWAP response:
|
||||||
|
|
||||||
|
```text
|
||||||
|
estimated next = observation time + (dueSlot - snapshot.slot) × 200 ms
|
||||||
|
final slot = dueSlot + (childOrdersRemaining - 1) × cooldownSlots
|
||||||
|
estimated end = observation time + (final slot - snapshot.slot) × 200 ms
|
||||||
|
```
|
||||||
|
|
||||||
|
Receipt time is an approximate anchor; API lag, variable slot times and delayed
|
||||||
|
execution can shift the estimates. No separate RPC sampling or live timer is used.
|
||||||
|
Already-due schedules yield unavailable estimates, as do overflow or expiry before
|
||||||
|
the scheduled child/completion. `lastValidSlot` is only an expiry boundary.
|
||||||
|
|
||||||
|
EMC appends **TWAP** after uPnL. Its progressbar overlays a snapshot duration with
|
||||||
|
unbounded total hours, such as `≈ 28:32:10` or `≈ 128:32:10`. It updates only with new
|
||||||
|
snapshots and is explicitly as of the tooltip's `Updated` time. Due/unavailable
|
||||||
|
timing shows `Due / unknown`; a failed venue shows `Retained` instead of a fresh
|
||||||
|
countdown. Cells without relevant progress or warnings stay empty. Groups show warning
|
||||||
|
summaries when applicable, never aggregated progressbars. Tooltips show progress, `Estimated completion`, total/next/fill
|
||||||
|
quantities, different dust sizes where relevant, observation time and limitations.
|
||||||
|
|
||||||
|
Total and next-child USD values reuse that position's already fetched Phoenix mark,
|
||||||
|
independently of the Ticker. They are approximate current notionals, excluding fees,
|
||||||
|
not historical spend or a promise about future execution cost. An absent mark keeps
|
||||||
|
token quantities visible with unavailable USD values; affected exposure, margin,
|
||||||
|
PnL/equity/Value and dependent totals are unavailable rather than zero.
|
||||||
|
|
||||||
|
|
||||||
|
## Valuations, funding and margin
|
||||||
|
|
||||||
|
Monetary arithmetic uses exact `BigDecimal`/`BigInteger` values. Quote lots are
|
||||||
|
1e-6 USD. The [official SDK computation](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/margin/compute.ts)
|
||||||
|
and [PnL definition](https://docs.phoenix.trade/phoenix/margin-and-risk/entry-price-pnl)
|
||||||
|
separate price PnL from funding and fees:
|
||||||
|
|
||||||
|
```text
|
||||||
|
price uPnL = quantity × markPrice + virtualQuotePositionLots / 1e6
|
||||||
|
funding = unsettledFundingQuoteLots / 1e6 (signed; positive adds equity)
|
||||||
|
equity = collateral + sum(price uPnL + funding)
|
||||||
|
exit fee = ceil_to_micro_USD(abs(quantity) × markPrice × takerFee × REST takerFeeOverrideMultiplier)
|
||||||
|
Value = equity - sum(estimated exit fees)
|
||||||
|
```
|
||||||
|
|
||||||
|
The REST override is already a decimal multiplier (observed 0.9), not the raw
|
||||||
|
on-chain i8 representation. Missing override, fee, funding or virtual quote data
|
||||||
|
makes affected valuations unavailable, rather than defaulting to zero. Settled
|
||||||
|
trading fees and `accumulatedFundingQuoteLots` are not subtracted again; collateral
|
||||||
|
already reflects settlement. Equity is undiscounted: no positive-uPnL risk discount
|
||||||
|
or spot-collateral haircut is substituted for owned capital.
|
||||||
|
|
||||||
|
`collateral` is quote collateral in micro-USD. Each nonzero `spotCollaterals.balance`
|
||||||
|
currently makes collateral/equity/Value unavailable: this version does not have a
|
||||||
|
verified spot index-price valuation. It never quietly ignores SOL or another
|
||||||
|
collateral asset. Zero spot balances do not change quote-only valuations. Missing
|
||||||
|
collateral is also unavailable. Independent price PnL/exposure remain computable.
|
||||||
|
|
||||||
|
For a Cross position the Collateral column is its **initial margin requirement**,
|
||||||
|
not a share of the account balance. The implementation follows the
|
||||||
|
[SDK margin math](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/margin/math.ts)
|
||||||
|
and [integer price conversion](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/units.ts):
|
||||||
|
|
||||||
|
```text
|
||||||
|
markTicks = floor(markPrice × 10^(6-baseLotsDecimals) / tickSize)
|
||||||
|
grossQuoteLots = abs(basePositionLots) × markTicks × tickSize
|
||||||
|
L = first-tier leverage, or truncated linear interpolation of leverage between
|
||||||
|
the surrounding maxSizeBaseLots bounds; beyond the final bound use 1
|
||||||
|
initialMarginUsd = ceil(grossQuoteLots / L) / 1e6
|
||||||
|
```
|
||||||
|
|
||||||
|
Interpolation truncates the complete positive leverage, including when leverage
|
||||||
|
is decreasing. For 150 lots between (100 lots, 10×) and (200 lots, 5×), L is 7,
|
||||||
|
not 7.5 or 8. At 100 USD and exponent 2, margin is 21.428572 USD. Exposure retains
|
||||||
|
the exact original REST mark, preserving issue #99's current-notional meaning.
|
||||||
|
|
||||||
|
Cross account leverage is gross exposure / positive undiscounted equity before
|
||||||
|
estimated exit fees. Cross position leverage and ROI are unavailable because the
|
||||||
|
venue's displayed conventions have not been verified; the UI explains this and
|
||||||
|
shows dollar PnL. Isolated leverage/ROI use actual collateral. Cross position
|
||||||
|
Value is inapplicable; Cross account Value counts all shared capital once,
|
||||||
|
including free collateral when its last position closes. Other aggregate ratios
|
||||||
|
are blank. Risk separately displays reported maintenance-margin utilization
|
||||||
|
against effective collateral. It is not total initial-margin utilization, free
|
||||||
|
buying power or the ratio used for the Leverage column.
|
||||||
|
|
||||||
|
Value is an estimate at separately read reference prices, not an executable quote.
|
||||||
|
Slippage, price impact, swaps, network fees, account rent, future funding and
|
||||||
|
unrelated wallet balances are excluded. No profit cap is applied to Phoenix.
|
||||||
|
Unsupported/missing values and any higher totals requiring them show `—`.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#<center><H1>Soda Task Manager<br>Documentation</H1></center>
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="da">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>SuWiMo Documentation</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
background-color: #e7f1ff;
|
||||||
|
padding: 10px;
|
||||||
|
border-left: 4px solid #0056b3;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
margin-top: 40px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
.token-logo {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
pre, code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
padding: 10px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 6px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
ul, ol {
|
||||||
|
margin-left: 1.2em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>SuWiMo Documentation</h1>
|
||||||
|
<p>#<center><H1>SuWiMo<br>Documentation</H1></center></p><p><strong>Indholdsfortegnelse:</strong>
|
||||||
|
<!-- TOC -->
|
||||||
|
* [1. Introduktion og Formål](#1-introduktion-og-formål)
|
||||||
|
* [2. Arkitektur og Overordnet Design](#2-arkitektur-og-overordnet-design)
|
||||||
|
* [2.1 Central Hub (SuWiMoHub)](#21-central-hub-suwimohub)
|
||||||
|
* [2.2 Plugin-baseret Design](#22-plugin-baseret-design)
|
||||||
|
* [3. Sensorer](#3-sensorer)
|
||||||
|
* [3.1 Sensorinterface](#31-sensorinterface)
|
||||||
|
* [3.2 Statusniveauer](#32-statusniveauer)
|
||||||
|
* [3.3 Passiv Sensorlogik](#33-passiv-sensorlogik)
|
||||||
|
* [4. Hierarkisk Struktur (Træstruktur)](#4-hierarkisk-struktur-træstruktur)
|
||||||
|
* [4.1 Dynamisk Træ via Konfiguration](#41-dynamisk-træ-via-konfiguration)
|
||||||
|
* [4.2 Statuspropagering](#42-statuspropagering)
|
||||||
|
* [5. Notifikationssystem og Logging](#5-notifikationssystem-og-logging)
|
||||||
|
* [5.1 Notifier Interface](#51-notifier-interface)
|
||||||
|
* [5.2 Historik og Logging](#52-historik-og-logging)
|
||||||
|
* [6. Distribution og Decentralisering](#6-distribution-og-decentralisering)
|
||||||
|
* [6.1 AssetAZ Observables](#61-assetaz-observables)
|
||||||
|
* [6.2 Distribuerede Hubs](#62-distribuerede-hubs)
|
||||||
|
* [7. Brugsscenarier og Eksempler](#7-brugsscenarier-og-eksempler)
|
||||||
|
* [8. Fremtidige Funktioner / TODO](#8-fremtidige-funktioner--todo)
|
||||||
|
<!-- TOC --></p><p><h2>1. Introduktion og Formål</h2>
|
||||||
|
SuWiMo (Surveillance Without Moving) er et system udviklet til enkel og effektiv statusovervågning af mange uafhængige
|
||||||
|
sensorer organiseret i en hierarkisk træstruktur. Formålet med SuWiMo er at give et hurtigt, visuelt overblik over
|
||||||
|
systemets tilstand, hvor status let kan aflæses som grøn (OK), gul (advarsel) eller rød (kritisk fejl).</p><p>SuWiMo fokuserer på tillidsvalidering og identificering af afvigelser i realtid, snarere end detaljeret monitorering
|
||||||
|
eller datalogging som traditionelle overvågningssystemer.</p><p><h2>2. Arkitektur og Overordnet Design</h2>
|
||||||
|
<h3>2.1 Central Hub (SuWiMoHub)</h3>
|
||||||
|
SuWiMoHub fungerer som den centrale orchestrator, der står for al polling, statusvurdering og statuspropagering i
|
||||||
|
træstrukturen. Hubben håndterer ikke selv sensorlogik, men trækker på plugins via interfaces.
|
||||||
|
<ul><ul><li>Centraliseret styring af polling-interval.</li>
|
||||||
|
<li>Indlæsning af sensor- og notifier-plugins.</li>
|
||||||
|
<li>Dynamisk opbygning og opdatering af status-træet.</li></p><p><h3>2.2 Plugin-baseret Design</h3>
|
||||||
|
SuWiMo benytter en modulær plugin-arkitektur, hvor forskellige typer af plugins implementerer specifikke interfaces
|
||||||
|
efter deres funktion:
|
||||||
|
<li><strong>Sensor-plugins</strong> implementerer sensorinterface til overvågning og statusrapportering.</li>
|
||||||
|
<li><strong>Notifier-plugins</strong> implementerer notifierinterface til at sende notifikationer ved statusændringer.</li>
|
||||||
|
<li><strong>Logger-plugins</strong> implementerer logningsinterface til historisk registrering af statusændringer.</li></p><p>Disse plugins registreres og administreres af Nenjim-systemet, hvilket sikrer nem distribution, versionering og
|
||||||
|
udskiftelighed.</p><p><h2>3. Sensorer</h2>
|
||||||
|
<h3>3.1 Sensorinterface</h3>
|
||||||
|
Sensorer implementerer et simpelt, standardiseret interface:</p><pre><code class="java">public interface SuwimoSensor {
|
||||||
|
SensorStatus poll();
|
||||||
|
}</p><p>public class SensorStatus {
|
||||||
|
StatusLevel level; // GREEN, YELLOW, RED
|
||||||
|
String message; // Kort forklaring på tilstand
|
||||||
|
}
|
||||||
|
</code></pre><p><h3>3.2 Statusniveauer</h3>
|
||||||
|
<li><strong>Green</strong>: Normal drift.</li>
|
||||||
|
<li><strong>Yellow</strong>: Advarsel, kræver opmærksomhed.</li>
|
||||||
|
<li><strong>Red</strong>: Kritisk fejl, kræver øjeblikkelig handling.</li></p><p><h3>3.3 Passiv Sensorlogik</h3>
|
||||||
|
Sensorer kører ikke aktivt. De er passive komponenter, som kun evaluerer og returnerer deres status, når hubben
|
||||||
|
poller dem.</p><p><h2>4. Hierarkisk Struktur (Træstruktur)</h2>
|
||||||
|
<h3>4.1 Dynamisk Træ via Konfiguration</h3>
|
||||||
|
Træstrukturen defineres i en JSON-konfigurationsfil, hvor hver node har et unikt ID, et navn, en valgfri sensor og en
|
||||||
|
parent-reference. Eksempel:</p><pre><code class="json">[
|
||||||
|
{ "id": "internet", "name": "Internetforbindelse", "sensor": "PingGoogle", "parent": null },
|
||||||
|
{ "id": "zfs-check", "name": "ZFS array", "sensor": "ZfsStatus", "parent": "server01" },
|
||||||
|
{ "id": "server01", "name": "Serverrummet", "sensor": null, "parent": null }
|
||||||
|
]
|
||||||
|
</code></pre><p><h3>4.2 Statuspropagering</h3>
|
||||||
|
Den dårligste status blandt børn og egen sensor propagerer op gennem træet. Det gør det muligt at se den værste
|
||||||
|
tilstand hurtigt fra en node eller træets rod.</p><p><h2>5. Notifikationssystem og Logging</h2>
|
||||||
|
<h3>5.1 Notifier Interface</h3>
|
||||||
|
Notifikationskomponenter implementerer et interface, der reagerer på statusændringer:</p><pre><code class="java">public interface SuwimoNotifier {
|
||||||
|
void statusChanged(SensorEvent event);
|
||||||
|
}</p><p>public class SensorEvent {
|
||||||
|
String sensorId;
|
||||||
|
StatusLevel oldStatus;
|
||||||
|
StatusLevel newStatus;
|
||||||
|
String message;
|
||||||
|
ZonedDateTime timestamp;
|
||||||
|
}
|
||||||
|
</code></pre><p><h3>5.2 Historik og Logging</h3>
|
||||||
|
Historiske log-events registreres hver gang en sensor ændrer tilstand. Denne funktionalitet implementeres som et
|
||||||
|
separat plugin, der eksempelvis kan logge til fil, database eller anden ekstern tjeneste.</p><p><h2>6. Distribution og Decentralisering</h2>
|
||||||
|
<h3>6.1 AssetAZ Observables</h3>
|
||||||
|
SuWiMoHub eksponerer sin status som en AssetAZ Observable. Det gør det muligt for andre hubs eller AssetAZ-instanser
|
||||||
|
at abonnere på sensorstatus fra eksterne hubs.</p><p><h3>6.2 Distribuerede Hubs</h3>
|
||||||
|
Flere SuWiMoHubs kan installeres decentralt, tæt på de ressourcer de overvåger, og udveksle sensorstatus via AssetAZ's
|
||||||
|
P2P-netværk. Dette sikrer effektiv isolation af fejl og lokal robusthed.</p><p><h2>7. Brugsscenarier og Eksempler</h2>
|
||||||
|
Eksempler på anvendelser af SuWiMo:
|
||||||
|
<li>Overvågning af servermiljøer (CPU, RAM, ZFS arrays)</li>
|
||||||
|
<li>Netværksstatus (Internet-ping, DNS-opløsning)</li>
|
||||||
|
<li>Overvågning af applikationstilgængelighed og integritet (HTTP-checks med checksum)</li>
|
||||||
|
<li>Husovervågning (temperatur, dørsensorer, strøm)</li></p><p><h2>8. Fremtidige Funktioner / TODO</h2>
|
||||||
|
<li>Implementering af GUI med træstruktur (træk-og-slip sensororganisering)</li>
|
||||||
|
<li>Avanceret historikvisning (grafer og mønstergenkendelse)</li>
|
||||||
|
<li>Yderligere notifier-plugins (push-notifikationer, SMS, Discord, syslog)</li>
|
||||||
|
<li>Avancerede permissions og adgangskontrol</li>
|
||||||
|
<li>Automatisk timeout-tilstand (sensor bliver rød efter manglende respons)</li>
|
||||||
|
</ul></ul></p>
|
||||||
|
<div class="footer">
|
||||||
|
<p>Part of the r35157 project. See also AssetAZ, Nenjim, Evelyn.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
#<center><H1>SuWiMo<br>Documentation</H1></center>
|
||||||
|
|
||||||
|
**Indholdsfortegnelse:**
|
||||||
|
<!-- TOC -->
|
||||||
|
* [1. Introduktion og Formål](#1-introduktion-og-formål)
|
||||||
|
* [2. Arkitektur og Overordnet Design](#2-arkitektur-og-overordnet-design)
|
||||||
|
* [2.1 Central Hub (SuWiMoHub)](#21-central-hub-suwimohub)
|
||||||
|
* [2.2 Plugin-baseret Design](#22-plugin-baseret-design)
|
||||||
|
* [3. Sensorer](#3-sensorer)
|
||||||
|
* [3.1 Sensorinterface](#31-sensorinterface)
|
||||||
|
* [3.2 Statusniveauer](#32-statusniveauer)
|
||||||
|
* [3.3 Passiv Sensorlogik](#33-passiv-sensorlogik)
|
||||||
|
* [4. Hierarkisk Struktur (Træstruktur)](#4-hierarkisk-struktur-træstruktur)
|
||||||
|
* [4.1 Dynamisk Træ via Konfiguration](#41-dynamisk-træ-via-konfiguration)
|
||||||
|
* [4.2 Statuspropagering](#42-statuspropagering)
|
||||||
|
* [5. Notifikationssystem og Logging](#5-notifikationssystem-og-logging)
|
||||||
|
* [5.1 Notifier Interface](#51-notifier-interface)
|
||||||
|
* [5.2 Historik og Logging](#52-historik-og-logging)
|
||||||
|
* [6. Distribution og Decentralisering](#6-distribution-og-decentralisering)
|
||||||
|
* [6.1 AssetAZ Observables](#61-assetaz-observables)
|
||||||
|
* [6.2 Distribuerede Hubs](#62-distribuerede-hubs)
|
||||||
|
* [7. Brugsscenarier og Eksempler](#7-brugsscenarier-og-eksempler)
|
||||||
|
* [8. Fremtidige Funktioner / TODO](#8-fremtidige-funktioner--todo)
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
|
## 1. Introduktion og Formål
|
||||||
|
SuWiMo (Surveillance Without Moving) er et system udviklet til enkel og effektiv statusovervågning af mange uafhængige
|
||||||
|
sensorer organiseret i en hierarkisk træstruktur. Formålet med SuWiMo er at give et hurtigt, visuelt overblik over
|
||||||
|
systemets tilstand, hvor status let kan aflæses som grøn (OK), gul (advarsel) eller rød (kritisk fejl).
|
||||||
|
|
||||||
|
SuWiMo fokuserer på tillidsvalidering og identificering af afvigelser i realtid, snarere end detaljeret monitorering
|
||||||
|
eller datalogging som traditionelle overvågningssystemer.
|
||||||
|
|
||||||
|
## 2. Arkitektur og Overordnet Design
|
||||||
|
### 2.1 Central Hub (SuWiMoHub)
|
||||||
|
SuWiMoHub fungerer som den centrale orchestrator, der står for al polling, statusvurdering og statuspropagering i
|
||||||
|
træstrukturen. Hubben håndterer ikke selv sensorlogik, men trækker på plugins via interfaces.
|
||||||
|
|
||||||
|
- Centraliseret styring af polling-interval.
|
||||||
|
- Indlæsning af sensor- og notifier-plugins.
|
||||||
|
- Dynamisk opbygning og opdatering af status-træet.
|
||||||
|
|
||||||
|
### 2.2 Plugin-baseret Design
|
||||||
|
SuWiMo benytter en modulær plugin-arkitektur, hvor forskellige typer af plugins implementerer specifikke interfaces
|
||||||
|
efter deres funktion:
|
||||||
|
|
||||||
|
- **Sensor-plugins** implementerer sensorinterface til overvågning og statusrapportering.
|
||||||
|
- **Notifier-plugins** implementerer notifierinterface til at sende notifikationer ved statusændringer.
|
||||||
|
- **Logger-plugins** implementerer logningsinterface til historisk registrering af statusændringer.
|
||||||
|
|
||||||
|
Disse plugins registreres og administreres af Nenjim-systemet, hvilket sikrer nem distribution, versionering og
|
||||||
|
udskiftelighed.
|
||||||
|
|
||||||
|
## 3. Sensorer
|
||||||
|
### 3.1 Sensorinterface
|
||||||
|
Sensorer implementerer et simpelt, standardiseret interface:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface SuwimoSensor {
|
||||||
|
SensorStatus poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SensorStatus {
|
||||||
|
StatusLevel level; // GREEN, YELLOW, RED
|
||||||
|
String message; // Kort forklaring på tilstand
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Statusniveauer
|
||||||
|
- **Green**: Normal drift.
|
||||||
|
- **Yellow**: Advarsel, kræver opmærksomhed.
|
||||||
|
- **Red**: Kritisk fejl, kræver øjeblikkelig handling.
|
||||||
|
|
||||||
|
### 3.3 Passiv Sensorlogik
|
||||||
|
Sensorer kører ikke aktivt. De er passive komponenter, som kun evaluerer og returnerer deres status, når hubben
|
||||||
|
poller dem.
|
||||||
|
|
||||||
|
## 4. Hierarkisk Struktur (Træstruktur)
|
||||||
|
### 4.1 Dynamisk Træ via Konfiguration
|
||||||
|
Træstrukturen defineres i en JSON-konfigurationsfil, hvor hver node har et unikt ID, et navn, en valgfri sensor og en
|
||||||
|
parent-reference. Eksempel:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{ "id": "internet", "name": "Internetforbindelse", "sensor": "PingGoogle", "parent": null },
|
||||||
|
{ "id": "zfs-check", "name": "ZFS array", "sensor": "ZfsStatus", "parent": "server01" },
|
||||||
|
{ "id": "server01", "name": "Serverrummet", "sensor": null, "parent": null }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Statuspropagering
|
||||||
|
Den dårligste status blandt børn og egen sensor propagerer op gennem træet. Det gør det muligt at se den værste
|
||||||
|
tilstand hurtigt fra en node eller træets rod.
|
||||||
|
|
||||||
|
## 5. Notifikationssystem og Logging
|
||||||
|
### 5.1 Notifier Interface
|
||||||
|
Notifikationskomponenter implementerer et interface, der reagerer på statusændringer:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface SuwimoNotifier {
|
||||||
|
void statusChanged(SensorEvent event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SensorEvent {
|
||||||
|
String sensorId;
|
||||||
|
StatusLevel oldStatus;
|
||||||
|
StatusLevel newStatus;
|
||||||
|
String message;
|
||||||
|
ZonedDateTime timestamp;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Historik og Logging
|
||||||
|
Historiske log-events registreres hver gang en sensor ændrer tilstand. Denne funktionalitet implementeres som et
|
||||||
|
separat plugin, der eksempelvis kan logge til fil, database eller anden ekstern tjeneste.
|
||||||
|
|
||||||
|
## 6. Distribution og Decentralisering
|
||||||
|
### 6.1 AssetAZ Observables
|
||||||
|
SuWiMoHub eksponerer sin status som en AssetAZ Observable. Det gør det muligt for andre hubs eller AssetAZ-instanser
|
||||||
|
at abonnere på sensorstatus fra eksterne hubs.
|
||||||
|
|
||||||
|
### 6.2 Distribuerede Hubs
|
||||||
|
Flere SuWiMoHubs kan installeres decentralt, tæt på de ressourcer de overvåger, og udveksle sensorstatus via AssetAZ's
|
||||||
|
P2P-netværk. Dette sikrer effektiv isolation af fejl og lokal robusthed.
|
||||||
|
|
||||||
|
## 7. Brugsscenarier og Eksempler
|
||||||
|
Eksempler på anvendelser af SuWiMo:
|
||||||
|
|
||||||
|
- Overvågning af servermiljøer (CPU, RAM, ZFS arrays)
|
||||||
|
- Netværksstatus (Internet-ping, DNS-opløsning)
|
||||||
|
- Overvågning af applikationstilgængelighed og integritet (HTTP-checks med checksum)
|
||||||
|
- Husovervågning (temperatur, dørsensorer, strøm)
|
||||||
|
|
||||||
|
## 8. Fremtidige Funktioner / TODO
|
||||||
|
- Implementering af GUI med træstruktur (træk-og-slip sensororganisering)
|
||||||
|
- Avanceret historikvisning (grafer og mønstergenkendelse)
|
||||||
|
- Yderligere notifier-plugins (push-notifikationer, SMS, Discord, syslog)
|
||||||
|
- Avancerede permissions og adgangskontrol
|
||||||
|
- Automatisk timeout-tilstand (sensor bliver rød efter manglende respons)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Tekniske termer
|
||||||
|
|
||||||
|
> **Dokumentstatus:** Journal-format version 1 og de tilhørende Java-typer, den interne parser, query-service,
|
||||||
|
> Hub-styret Journal-proces, JavaFX-runtime-service, passive service-managers, første version af det centrale
|
||||||
|
> component Registry og obligatorisk Hub-konfiguration format 1 er implementeret. Resolver-, Context-,
|
||||||
|
> classloader-, discovery-, runtime-loading-, retrieval- og publishing-termer beskriver kun fremtidig vision, når
|
||||||
|
> det er angivet.
|
||||||
|
|
||||||
|
| Term | Beskrivelse |
|
||||||
|
|------|-------------|
|
||||||
|
| Service | Det offentlige domæne-API, som applikationer og andre services bruger i daglig drift. Det eksponerer almindelige domæneoperationer, men ikke lifecycle, configuration, data-source administration, refresh, parser eller mutation af intern viden. En konkret serviceimplementation kan samtidig være en `NenjimProcess`, uden at lifecycle føjes til service-interfacet. |
|
||||||
|
| ServiceManager | Et separat, passivt administrations-API, som en composition/lifecycle owner beholder uden at give almindelige service-consumers administrative rettigheder. Journal-manageren eksponerer root og refresh, mens Registry-manageren eksponerer registration. En manager er ikke det daglige domain/query-API, er hverken component eller process og ejer ikke startup. |
|
||||||
|
| Nenjim component | En færdigkonstrueret Nenjim-byggeklods, som er klar til brug. `NenjimComponent` er en ren marker uden ID eller lifecycle state. Service, applikation, algoritme og adapter beskriver roller, mens process beskriver en uafhængig lifecycle-klassifikation. |
|
||||||
|
| Applikation | En user-facing eller orchestration-rolle. Rollen gør ikke i sig selv en component til en process; en aktiv applikationsimplementation kan separat implementere `NenjimProcess`. |
|
||||||
|
| NenjimProcess | Det canonical `com.r35157.nenjim.process.NenjimProcess` component interface for noget, som Hub'en kan starte og tracke. Det eksponerer kun `start()` og lover ikke stop, restart, health, Context eller dependency lookup. |
|
||||||
|
| Passiv component | En `NenjimComponent`, hvis implementation ikke deltager i Hub-lifecycle gennem det canonical `NenjimProcess`. Den nuværende Hub konstruerer og registrerer hele den passive dependency graph efter Registry-start uafhængigt af `[AUTO_START]`, men starter den ikke. |
|
||||||
|
| Valgt proces | Et kendt `NenjimProcess`-objekt, som Hub'en kun konstruerer, registrerer og starter, når dets ID forekommer i `[AUTO_START]`. Rollen som service eller applikation afgør ikke denne klassifikation. |
|
||||||
|
| NenjimHub | Den globale ejer af én kørende Nenjim runtime. Hub'en validerer `conf/NenjimHub.conf` før proces-start, bootstrapper Registry, konstruerer den passive graph og konstruerer/registrerer/starter kun valgte processer i eksakt konfigurationsrækkefølge. Den tracker succesfulde start-ID'er, erklærer online point og ejer den afsluttende runtime wait, men er hverken component eller process og registreres ikke i sit eget Registry. |
|
||||||
|
| AUTO_START | Den obligatoriske, ordered sektion i `NenjimHub.conf` format 1. Den er deploymentets komplette manuelle proces-dependency closure; Registry og Journal skal være første og anden entry. Den udtrykker ikke Context resolution, discovery eller automatisk dependency construction. |
|
||||||
|
| NenjimComponentId | Canonical lower-case Registry lookup identity med dot-separated segments og valgfrie interne bindestreger. ID'et er registration metadata i ét Registry, ikke object state, authorization eller artifact-version identity. |
|
||||||
|
| NenjimRegistryService | Read-only component-katalog for almindelige consumers. Det giver immutable registration-order ID snapshots og typed lookup, men ingen registration, removal, loading, lifecycle, permission eller persistence administration. |
|
||||||
|
| NenjimRegistryServiceAdmin | Package-private mutationsinterface i referenceimplementationen. Det er ikke en component, er ikke indexeret og må ikke gøres public, fordi almindelige consumers ellers kunne caste Registry-serviceobjektet til mutationsinterfacet. |
|
||||||
|
| NenjimRegistryServiceManager | Public, passivt administrationshandle med kun `registerComponent(...)`. Det holdes privat af Hub'en, delegerer til det interne admin-view, er hverken component eller process, registreres ikke og ejer ingen lifecycle eller composition. |
|
||||||
|
| Component registration | Bindingen mellem ét `NenjimComponentId` og ét object. Én registration indexeres under alle objektets component interfaces; flere forskellige ID'er må pege på præcis samme object instance. |
|
||||||
|
| Journal | Et immutable, komplet snapshot af al aktuel viden om præcis én artifakt og dens kendte releases. Journalen er aldrig en delta. |
|
||||||
|
| ArtifactCoordinate | Journal-kædens stabile identitet, udledt som `<GROUP>-<MODULE>-<ARTIFACT>`, f.eks. `com_r35157_nenjim-hubd-api`. Der findes intet separat `JournalId`. |
|
||||||
|
| JournalVersion | Journal-snapshottets publiceringstid som UTC med millisekundpræcision: `uuuuMMddHHmmssSSS'Z'`. Den er forskellig fra en releases `PUBLISHED_AT`. |
|
||||||
|
| Journal-kæde | Én genesis og en lineær række immutable snapshots for samme coordinate. Hver efterfølger refererer med SHA-256 til forgængerens eksakte UTF-8-tekst. |
|
||||||
|
| Historisk worldview | Hele det snapshot, som var nyest på eller før en valgt `JournalVersion`; det beregnes ikke ved at filtrere et nyere snapshot efter release-tid. |
|
||||||
|
| Artifact release | En eksakt stabil `major.minor.patch`-version af én artifakt med per-release licens, publication time, content digests, content size og dependencies. |
|
||||||
|
| DESCRIPTION | Obligatorisk single-quoted artifactbeskrivelse i `[METADATA]`; decoded tekst kan ændres i en senere Journal-revision. |
|
||||||
|
| INFORMATION_URI | Valgfri single-quoted, syntaktisk gyldig absolut URI. Alle schemes er tilladt; indholdet åbnes eller fortolkes ikke automatisk. |
|
||||||
|
| LICENSE | Obligatorisk single-quoted fri tekst for hver release. Den normaliseres eller juridisk fortolkes ikke; fremtidig filtrering er ikke implementeret. |
|
||||||
|
| ContentDigest | Valideret indholdsidentitet. Format 1 understøtter `sha256` og CIDv1 (`cid1`), men downloader eller verificerer ikke artifact-bytes. |
|
||||||
|
| SemanticVersion | Én eksakt stabil artifaktrelease med major, minor og patch. Prerelease/build metadata understøttes ikke i format 1. |
|
||||||
|
| VersionExpression | Et normaliseret sæt acceptable releases: exact, minor-serie eller inclusive/exclusive/open range. Mathematical comparison syntax understøttes ikke. |
|
||||||
|
| DependencyTarget | Et explicit scheme og et scheme-specifikt target. Format 1 implementerer kun `nenjim:<artifact-coordinate>`; Maven-resolution er fremtidig. |
|
||||||
|
| DEPENDS_ON | Den hårde basisramme af acceptable releases for én dependency-relation. |
|
||||||
|
| EXCLUDES | En hård relationel regel, som fjerner versioner fra én bestemt `DEPENDS_ON`. Individuelle beskrivelser bevares. |
|
||||||
|
| PREFERRED | Et hint til en delmængde af `DEPENDS_ON - EXCLUDES`; det kan aldrig gøre en ellers ugyldig version gyldig. |
|
||||||
|
| BLACKLIST | En hård artifact-policy: matchende releases må ikke vælges. |
|
||||||
|
| DISCOURAGED | Et negativt artifact-policy-hint: matchende releases bør undgås, men er fortsat selectable. |
|
||||||
|
| RECOMMENDED | Et positivt hint til én eksakt release, der findes i samme snapshot. Højeste version er kun en fremtidig fallback, ikke en ubetinget selection-regel. |
|
||||||
|
| JournalService | Registrerbart, filesystem-uafhængigt og read-only Service-API for current og historical Journal-queries. Det eksponerer ingen lifecycle-, path-, refresh-, parser- eller mutationsoperationer, og queries er gated indtil vellykket proces-startup. |
|
||||||
|
| JournalServiceManager | Passivt, uregistreret administrationshandle for én Journal-service med kun normalized data root og explicit refresh. Hub'en beholder det; det har ingen start, stop, service-accessor eller query-operationer. |
|
||||||
|
| Journal referenceimplementation | Ét package-private `JournalServiceImpl`-objekt er både `JournalService` og `NenjimProcess`; en separat `JournalServiceManagerImpl`, intern admin-view, parser, ingestion, chain state og filesystem-loader ligger under `com.r35157.nenjim.service.journal.impl.ref` uden at lække gennem public signatures. |
|
||||||
|
| JavaFX runtime | Det ene JVM-wide JavaFX toolkit, som udfører UI-arbejde på JavaFX Application Thread. Når processen er valgt, ejer Hub'en dets eneste startup-sti gennem `JavaFxRuntimeServiceImpl` og holder det aktivt efter sidste lukkede vindue med `Platform.setImplicitExit(false)`. En headless konfiguration kan udelade runtime og alle JavaFX-applikationer helt. |
|
||||||
|
| JavaFxRuntimeService | Registrerbart service-view med kun non-null `runLater(Runnable)`. Samme referenceimplementation er en Hub-styret `NenjimProcess`; service-consumers får den gennem constructor injection, men får ingen lifecycle-, status-, configuration-, window-, event-, stop- eller restart-operationer. |
|
||||||
|
| TickerService | Lifecycle-frit domain/query-view med kun latest-price lookup. Samme `TickerServiceImpl`-objekt er separat en Hub-styret `NenjimProcess`; det ejer `PriceSource`-startup internt og stopper allerede startede sources som rollback, hvis Ticker-start fejler, men eksponerer ingen almindelig start- eller stop-operation. |
|
||||||
|
| EvelynIOUBurnerService | Tomt, lifecycle-frit domain-service-view, som bevarer typed Registry- og dependency-identitet, indtil eksterne domain-operationer findes. Samme `EvelynIOUBurnerServiceImpl`-objekt er separat en Hub-styret `NenjimProcess`; service-consumers får ingen start-, stop- eller restart-operationer. |
|
||||||
|
| EvelynStatusService | Service for én navngiven Evelyn-instans, som indsamler og persisterer statushistorik og udleverer den beregnede EVE-referencepris med eksplicit USDT-quote. Den bevarer sit eksisterende start/stop/close-contract; samme `EvelynStatusServiceImpl`-objekt startes af Hub'en som `NenjimProcess` under `evelyn.status.prod` eller `evelyn.status.test`. Evelyn er fortsat navnet på det samlede system. |
|
||||||
|
| Context | Fremtidig beskrivelse af ønskede/resolverede artifactvalg og isoleret runtime; ikke implementeret af Journal-laget eller første Registry-version. |
|
||||||
|
| Resolver | Fremtidig komponent for dependency graph og release-selection; Journal-laget repræsenterer reglerne, men vælger ikke en graf. |
|
||||||
|
|
||||||
|
# Domænespecifikke termer
|
||||||
|
|
||||||
|
| Term | Beskrivelse |
|
||||||
|
|------|-------------|
|
||||||
|
| Position Value | Den samlede nutidsværdi af en position (nuværende pris × antal). |
|
||||||
|
| Base Value | Den pris det har kostet at købe positionen (gennemsnitlig købspris × antal). |
|
||||||
|
| Margin Value | Den pris man faktisk har betalt for en gearet position (base value / gearing). |
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-05
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
Before this change, `Evelyn` declared `@NotNull String getStatusReport()`, `EvelynImpl` returned `"Hello World!"`, and no other local source called it. Mission Control presents parallel Production and Test tab sets. The API and reference implementations must remain separated by package.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Define a typed immutable measurement contract, replace the text API, and render each environment's Evelyn history in its corresponding existing Overview tab without disturbing the Production/Test tabs or styling.
|
||||||
|
|
||||||
|
**Non-Goals:** Real index calculation, persistence, refresh/polling after view creation, environment discovery, dependency resolution, final Nenjim environment wiring, and automated tests.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Add `EvelynStatusIndexPoint` beside the Evelyn public API as an immutable value type. Use `Instant` for the timestamp and `BigDecimal` for each named index so time semantics are explicit and decimal values retain precision. A map or six-element list was rejected because it would lose compile-time names and completeness.
|
||||||
|
- Replace, rather than supplement, `getStatusReport()` with `@NotNull List<EvelynStatusIndexPoint> getStatusIndexHistory()`. The implementation returns an immutable hardcoded list in chronological order; callers must also tolerate an empty list. Keeping the text API was rejected because the issue intentionally replaces that contract.
|
||||||
|
- Make `EvelynMissionControlImpl` accept separate Production and Test Evelyn references. Fetch each reference's history while constructing its corresponding Overview so the two tabs remain independent even when the reference implementations currently return identical hardcoded values.
|
||||||
|
- Construct a distinct chart, axes, series, and data nodes for each Overview. Sharing a JavaFX `Node` is not valid because a node cannot have two parents.
|
||||||
|
- Have `NenjimHubImpl` create two separate `EvelynImpl` instances as temporary Production and Test placeholders and pass both to Mission Control. Future Nenjim wiring will supply the actual environment-specific Evelyn references; environment discovery and dependency resolution are outside issue #59.
|
||||||
|
- Use a time-capable numeric X-axis derived from each point's timestamp and a numeric Y-axis. Compute the largest absolute index magnitude across all series and use its negative and positive values as equal bounds; use a small symmetric fallback span when history is empty or all values are zero so zero remains visible. JavaFX chart construction remains in the reference implementation.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Hardcoded history can look authoritative] → Keep values clearly sample-only and isolate them in `EvelynImpl` for later replacement.
|
||||||
|
- [One extreme index compresses the other lines] → Accept a shared scale because direct comparison and a common zero baseline are required.
|
||||||
|
- [Breaking API removal affects downstream consumers outside this repository] → Publish the typed replacement in the same release and call out the method migration.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Add the value type, replace the interface method and reference implementation, construct distinct Production and Test Overview charts from their corresponding Evelyn references, and compile the project. Roll back the API and Overview changes together if needed; no stored data requires migration.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Evelyn exposes only an unstructured textual status report, which prevents Mission Control from plotting status changes over time. A typed historical API will make the six status indexes directly consumable by the existing Overview views.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **BREAKING** Replace `Evelyn.getStatusReport()` with `List<EvelynStatusIndexPoint> getStatusIndexHistory()`.
|
||||||
|
- Add a typed measurement point containing a timestamp and the six Evelyn status indexes.
|
||||||
|
- Initially provide oldest-to-newest hardcoded sample history from the reference implementation.
|
||||||
|
- Give Mission Control separate Production and Test `Evelyn` references and plot each reference's history in its corresponding Overview tab using separate chart instances with dynamic Y-axes symmetric around a visible zero.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `evelyn-status-index-history`: Typed historical status-index access and Mission Control visualization.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affects the Evelyn public API and reference implementation in `com.fanitas.evelyn.core`.
|
||||||
|
- Affects Evelyn Mission Control's existing Overview content and introduces explicit Production/Test Evelyn wiring.
|
||||||
|
- Removes the textual status-report API; the repository currently has no callers beyond its declaration and implementation.
|
||||||
|
- Adds no persistence, real index calculation, environment discovery, dependency resolution, external dependency, or automated tests.
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Provide typed historical Evelyn status measurements and make all six indexes visible over time in Mission Control.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Evelyn provides historical status-index measurements
|
||||||
|
Evelyn SHALL expose a non-null list of typed measurement points ordered from oldest to newest. Each point SHALL contain a timestamp, Evelyn Price Index, EVE_SYRUP Pool Depth Index, EVE_SYRUP Pool Balance Index, AAZDKK_USDT Pool Balance Index, AAZDKK_USDT Pool Price Index, and AAZDKK_USDT Pool Depth Index; the list MAY be empty.
|
||||||
|
|
||||||
|
#### Scenario: Status-index history is requested
|
||||||
|
- **WHEN** a caller requests Evelyn's status-index history
|
||||||
|
- **THEN** Evelyn returns a non-null, oldest-to-newest list whose points contain a timestamp and all six index values
|
||||||
|
|
||||||
|
### Requirement: Evelyn Mission Control visualizes status-index measurements
|
||||||
|
Evelyn Mission Control SHALL display historical measurements from separate Production and Test Evelyn references in their corresponding Overview tabs as six-line graphs, with timestamp on the X-axis and index value on the Y-axis. Each environment SHALL retrieve its own history and SHALL have a separate graph instance with a dynamic Y-axis range that is symmetric around zero and always displays zero as the desired state. The two histories MAY contain identical values but SHALL remain separate data sources.
|
||||||
|
|
||||||
|
#### Scenario: Production and Test Overviews are created with history
|
||||||
|
- **WHEN** Mission Control creates the Production and Test Overview views
|
||||||
|
- **THEN** it retrieves history from the corresponding Production and Test Evelyn references and creates a separate six-line graph for each environment whose symmetric Y-axis contains zero
|
||||||
|
|
||||||
|
#### Scenario: An environment has empty history
|
||||||
|
- **WHEN** Mission Control creates an environment's Overview view and that environment's Evelyn history is empty
|
||||||
|
- **THEN** it displays an empty six-series graph for that environment whose Y-axis still contains zero without substituting the other environment's history
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
## 1. Typed Status History
|
||||||
|
|
||||||
|
- [x] 1.1 Add the immutable `EvelynStatusIndexPoint` API value type with an `Instant` timestamp and six named `BigDecimal` index values.
|
||||||
|
- [x] 1.2 Replace `Evelyn.getStatusReport()` with the non-null `getStatusIndexHistory()` list contract.
|
||||||
|
- [x] 1.3 Implement an immutable, oldest-to-newest hardcoded sample history in `EvelynImpl`.
|
||||||
|
|
||||||
|
## 2. Mission Control Overview
|
||||||
|
|
||||||
|
- [x] 2.1 Update Mission Control to accept separate Production and Test Evelyn references and fetch each reference's history for its corresponding Overview tab.
|
||||||
|
- [x] 2.2 Update NenjimHub to supply two separate `EvelynImpl` placeholder instances and keep distinct six-series chart instances for the two histories.
|
||||||
|
- [x] 2.3 Configure a dynamic symmetric Y-axis around zero, including a usable zero-containing fallback range for empty or all-zero history.
|
||||||
|
|
||||||
|
## 3. Verification
|
||||||
|
|
||||||
|
- [x] 3.1 Compile the project and manually confirm each Overview tab renders its corresponding Evelyn history in a separate chart without adding automated tests.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-05
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. `TickerService` and `TickerServiceImpl` already exist as empty shells in AssetAZ-owned packages. `AssetPrice` wraps `ΩPriceΩ` and `TradingPair`; the latter identifies currencies structurally rather than by raw symbol. The repository temporarily hosts AssetAZ code as Cauldron, and NenjimHub's current autorun list directly constructs services.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Establish a small reusable ticker contract, deterministic file-backed startup, atomic persist-before-publish behavior, and temporary autorun wiring while retaining AssetAZ ownership.
|
||||||
|
|
||||||
|
**Non-Goals:** Datasource plugins, real market data, pairs other than `EVE_USDC`, consumers, subscriptions/callbacks, history queries, retention, compaction, or automated tests.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Define `PriceObservation` in `com.r35157.assetaz.core.service.ticker` exactly as a record of `AssetPrice price` and `Instant observedAt`. Extend `TickerService` with `start()` and `PriceObservation getLatestPrice(TradingPair tradingPair)`. Use clear exceptions for unsupported/inactive pairs and enabled pairs without a persisted observation; `Optional` and `null` were rejected because issue #60 explicitly requires exceptions for unavailable prices. Raw symbol strings were rejected because callers must use the established ValueTypes.
|
||||||
|
- Add stable EVE and USDC currency definitions and an `EVE_USDC` well-known `TradingPair`, then construct the sample price with `new ΩPriceΩ("14.85")`. This keeps identity consistent with other typed prices instead of parsing pair names at the API boundary.
|
||||||
|
- Keep the public API and `PriceObservation` in the AssetAZ API package and implementation details in `com.r35157.assetaz.core.service.ticker.impl.ref`. Their current repository location is temporary Cauldron placement and does not imply Evelyn or NenjimHub ownership.
|
||||||
|
- Use the fixed path `data/assetaz/ticker/EVE_USDC.prices`; do not create its directory or file. Check existence when initializing the pair. Missing means inactive, emits a warning containing the pair and expected path, and does not prevent the rest of Ticker startup; an existing zero-length, blank-only, or comment-only file is active with no latest value.
|
||||||
|
- Store one observation per data line as `<uuuuMMddHHmmssSSS'Z'>:<decimal-price>`, using a strict formatter fixed to UTC. Read line by line, remove text from the first `#`, trim, and skip empty results. Split the remaining data at the required colon and parse it into `Instant`, `ΩPriceΩ`, and the fixed `EVE_USDC` pair. Wrap any structural, timestamp, or price failure with the filename, one-based physical line number, and identifying invalid content.
|
||||||
|
- Load history synchronously during `start()` and select the parsed observation with the greatest `observedAt` timestamp rather than assuming the file is chronologically ordered. After loading, generate the first hardcoded observation immediately, then use a single-thread scheduled executor with fixed-delay scheduling: each subsequent attempt begins one minute after the preceding attempt completes. Fixed-rate scheduling was rejected because a long pause can cause rapid catch-up attempts. Do not schedule generation for an inactive pair. The scheduler is an implementation mechanism, not a public lifecycle or subscription API.
|
||||||
|
- Serialize generation and publication, and keep the published latest value behind a thread-safe reference. Validate and encode every generated observation, append all encoded bytes through a `FileChannel`, and call `force(true)`. Only after successful persistence, compare its `observedAt` with the current latest and replace the in-memory reference only when the new timestamp is later. Thus concurrent callers see the successfully persisted observation with the greatest timestamp across loaded history and newly persisted observations, and a manually inserted future-dated observation remains latest until a later observation is persisted. A buffered-writer `flush()` alone was rejected because it does not satisfy the issue's explicit flush/force-to-persistence ordering. On open, write, or force failure, log/report the failure and retain the prior latest value; never publish the attempted observation.
|
||||||
|
- Add a temporary `startAssetAZTickerService()` entry to `NenjimHubImpl` following its direct autorun pattern. It constructs and starts `TickerServiceImpl`; no Ticker dependency is passed to Evelyn, EMC, alarms, portfolios, or other services.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Filesystem or device guarantees can still limit crash durability after `FileChannel.force(true)`] → Force each complete appended record before publication and document that the implementation uses the strongest standard Java file-channel guarantee available here.
|
||||||
|
- [An observation attempt can take arbitrarily long] → Fixed-delay scheduling waits until the attempt completes and then waits one minute, preventing overlapping or catch-up attempts.
|
||||||
|
- [Malformed history prevents the service from starting] → Include exact filename and line number so operators can repair the activation file safely.
|
||||||
|
- [Hardcoded identifiers and price are temporary] → Isolate them behind well-known ValueTypes and the reference implementation for later source replacement.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Operators opt in by creating `data/assetaz/ticker/EVE_USDC.prices`, optionally pre-populated in the documented format. Deploy the service and temporary autorun wiring together. Rollback removes the autorun call and ticker implementation while leaving the append-only data file intact.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
AssetAZ has only empty ticker service shells, so callers cannot obtain a typed latest price or retain observations across restarts. A minimal reusable ticker establishes that service contract and a durable reference implementation before real market sources are introduced.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Define an AssetAZ ticker API that exposes the latest persisted observation by `TradingPair` using existing price ValueTypes and throws clear exceptions when no price is available.
|
||||||
|
- Add a reference implementation for the single supported `EVE_USDC` pair, producing the hardcoded price `14.85` immediately at startup and starting each subsequent attempt one minute after the previous attempt completes.
|
||||||
|
- Make an existing `.prices` file the explicit activation mechanism, load its valid history, append every new observation durably, and expose the successfully persisted observation with the greatest timestamp as latest.
|
||||||
|
- Start the ticker through NenjimHub's current temporary autorun mechanism.
|
||||||
|
- Leave the pair unavailable when its history file is missing, reject malformed history at startup, and retain the prior published observation when a new observation cannot be persisted safely.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `assetaz-ticker-service`: Typed latest-price access, file-backed activation and history loading, and durable periodic publication for the initial AssetAZ trading pair.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Extends the existing empty API and reference implementation under `com.r35157.assetaz.core.service.ticker` and adds `PriceObservation` there.
|
||||||
|
- Reuses `AssetPrice`, `TradingPair`, and `ΩPriceΩ`; no datasource-plugin or consumer integration is introduced.
|
||||||
|
- Adds temporary startup wiring to `NenjimHubImpl`, without transferring ownership of the ticker to NenjimHub.
|
||||||
|
- Uses `data/assetaz/ticker/EVE_USDC.prices` as operator-controlled persistent activation and history.
|
||||||
|
- Adds no automated tests.
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Provide AssetAZ callers with typed latest prices backed by explicitly enabled, durable observation histories.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Ticker exposes typed latest prices
|
||||||
|
The ticker SHALL expose the latest successfully persisted `PriceObservation` for a requested `TradingPair`. A `PriceObservation` SHALL contain exactly an `AssetPrice` and its observation `Instant`; the `AssetPrice` SHALL contain both its `ΩPriceΩ` value and `TradingPair`. The ticker SHALL initially support only `EVE_USDC`. A request for an unsupported or inactive pair, or for an enabled pair that has no persisted observation, SHALL throw a clear exception rather than return `null`.
|
||||||
|
|
||||||
|
#### Scenario: Latest supported price is available
|
||||||
|
- **WHEN** a caller requests `EVE_USDC` after at least one observation has been loaded or published
|
||||||
|
- **THEN** the ticker returns the latest observation with an `AssetPrice` identified by the `EVE_USDC` `TradingPair`
|
||||||
|
|
||||||
|
#### Scenario: Unsupported or inactive pair is requested
|
||||||
|
- **WHEN** a caller requests a pair that is unsupported or not activated
|
||||||
|
- **THEN** the ticker throws an exception that clearly identifies the unavailable trading pair
|
||||||
|
|
||||||
|
#### Scenario: Enabled pair has no persisted observation
|
||||||
|
- **WHEN** a caller requests an enabled pair before any observation has been successfully persisted
|
||||||
|
- **THEN** the ticker throws an exception that clearly states that no persisted price is available for that trading pair
|
||||||
|
|
||||||
|
### Requirement: Price history explicitly activates the pair
|
||||||
|
The ticker SHALL treat the existence of `data/assetaz/ticker/EVE_USDC.prices` when the pair is initialized as activation of `EVE_USDC`. It SHALL load every valid observation and select the observation with the greatest timestamp as latest, regardless of file order. It SHALL accept empty lines, full-line comments whose first non-whitespace character is `#`, and inline comments beginning at the first `#` on a data line. A missing file SHALL produce a warning containing the trading pair and expected filepath, leave the pair unavailable, and SHALL NOT be created automatically; an existing file with no data observations SHALL enable the pair without an initial latest price.
|
||||||
|
|
||||||
|
#### Scenario: Existing history activates and restores the pair
|
||||||
|
- **WHEN** the ticker starts with an existing history containing valid observations and permitted whitespace or comments
|
||||||
|
- **THEN** `EVE_USDC` is available and the observation with the greatest timestamp is its latest persisted price
|
||||||
|
|
||||||
|
#### Scenario: Empty history activates without a price
|
||||||
|
- **WHEN** the ticker starts with an existing empty history file
|
||||||
|
- **THEN** `EVE_USDC` is enabled with no initial latest observation
|
||||||
|
|
||||||
|
#### Scenario: Missing history leaves the pair unavailable
|
||||||
|
- **WHEN** the ticker starts without the `EVE_USDC` history file
|
||||||
|
- **THEN** it logs a warning containing `EVE_USDC` and `data/assetaz/ticker/EVE_USDC.prices`, continues starting without that pair, and does not create the file or its parent directories
|
||||||
|
|
||||||
|
#### Scenario: Malformed history is rejected clearly
|
||||||
|
- **WHEN** comment text is removed and a non-empty data line does not contain a valid UTC timestamp and price
|
||||||
|
- **THEN** Ticker startup fails with an error containing the history filename, the one-based physical line number, and enough of the invalid content to identify it
|
||||||
|
|
||||||
|
### Requirement: History uses the human-editable observation format
|
||||||
|
Each data line SHALL have the form `<UTC timestamp>:<price>`, where the timestamp uses `uuuuMMddHHmmssSSS'Z'` with millisecond precision in UTC. Automatic writes SHALL append plain data lines only and SHALL leave all existing comments and blank lines untouched.
|
||||||
|
|
||||||
|
#### Scenario: Observation is written in the required format
|
||||||
|
- **WHEN** an observation at `2026-08-05T13:15:42.783Z` with price `14.85` is persisted
|
||||||
|
- **THEN** the appended line is `20260805131542783Z:14.85`
|
||||||
|
|
||||||
|
#### Scenario: Existing operator annotations are preserved
|
||||||
|
- **WHEN** the ticker appends an observation to a history containing comments or blank lines
|
||||||
|
- **THEN** those existing lines remain unchanged and the appended observation contains neither a comment nor extra annotation
|
||||||
|
|
||||||
|
### Requirement: New observations are durable before publication
|
||||||
|
For enabled `EVE_USDC`, the ticker SHALL generate price `14.85` immediately at startup. After that startup attempt completes, and after each subsequent attempt completes, the ticker SHALL wait one minute before beginning the next attempt. It SHALL NOT use fixed-rate scheduling or perform catch-up attempts after a delay. For each generated observation it SHALL validate the observation, append it to history, and flush or force the data to persistent storage; every generated observation SHALL be persisted unless that persistence attempt fails. Only after persistence succeeds SHALL the ticker replace the in-memory latest observation, and then only if the newly persisted observation's `observedAt` timestamp is later than the current latest timestamp. If persistence fails, it SHALL report the failure clearly, SHALL NOT publish the attempted observation, and SHALL preserve the previously persisted latest observation. Concurrent reads SHALL never observe an unpersisted price, and latest SHALL always be the successfully persisted observation with the greatest `observedAt` timestamp across loaded history and newly persisted observations.
|
||||||
|
|
||||||
|
#### Scenario: Startup observation is persisted
|
||||||
|
- **WHEN** the ticker starts with an enabled writable history
|
||||||
|
- **THEN** it appends and forces a `14.85` observation to persistent storage before considering it for latest
|
||||||
|
|
||||||
|
#### Scenario: Periodic observation is persisted
|
||||||
|
- **WHEN** one minute has elapsed after the preceding observation attempt completed while the enabled ticker is running
|
||||||
|
- **THEN** it begins another attempt and appends and forces its `14.85` observation to persistent storage before considering it for latest
|
||||||
|
|
||||||
|
#### Scenario: Future-dated history remains latest
|
||||||
|
- **WHEN** history contains a successfully persisted observation whose timestamp is later than a newly generated observation
|
||||||
|
- **THEN** the newly generated observation is still persisted and the future-dated history observation remains latest
|
||||||
|
|
||||||
|
#### Scenario: Persistence fails
|
||||||
|
- **WHEN** appending or flushing a new observation fails
|
||||||
|
- **THEN** the failure is reported clearly, the failed observation is not published to any reader, and the previously persisted latest observation remains available
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
## 1. AssetAZ Ticker API
|
||||||
|
|
||||||
|
- [x] 1.1 Add stable EVE and USDC currency ValueTypes and the well-known `EVE_USDC` `TradingPair`.
|
||||||
|
- [x] 1.2 Add immutable `PriceObservation` with a timestamp and `AssetPrice` in the ticker API package.
|
||||||
|
- [x] 1.3 Extend `TickerService` with `start()` and `PriceObservation getLatestPrice(TradingPair)`, using clear exceptions for unsupported/inactive pairs and enabled pairs without a persisted observation.
|
||||||
|
|
||||||
|
## 2. File-Backed Reference Implementation
|
||||||
|
|
||||||
|
- [x] 2.1 Implement explicit activation from the existing `data/assetaz/ticker/EVE_USDC.prices` file without creating a missing file or directory.
|
||||||
|
- [x] 2.2 Implement the strict UTC `uuuuMMddHHmmssSSS'Z':<price>` format and line-by-line history parsing with empty-line and full-line/inline comment support.
|
||||||
|
- [x] 2.3 Report malformed data with filename, one-based physical line number, and identifying invalid content, and restore the valid observation with the greatest `observedAt` timestamp as latest.
|
||||||
|
- [x] 2.4 Implement thread-safe append-and-`FileChannel.force(true)` behavior that persists every generated observation, never exposes an unpersisted observation, preserves the prior latest observation on persistence failure, and replaces latest after successful persistence only when the new `observedAt` timestamp is later.
|
||||||
|
- [x] 2.5 Generate the hardcoded `14.85` startup observation immediately and use fixed-delay scheduling only for the enabled pair so each subsequent attempt begins one minute after the preceding attempt completes.
|
||||||
|
|
||||||
|
## 3. Temporary Autorun Wiring
|
||||||
|
|
||||||
|
- [x] 3.1 Start `TickerServiceImpl` from NenjimHub's current direct autorun mechanism without wiring it to any consumer.
|
||||||
|
|
||||||
|
## 4. Verification
|
||||||
|
|
||||||
|
- [x] 4.1 Compile the project and manually verify missing, empty/comment-only, out-of-order valid, future-dated, malformed, and unwritable history-file behavior, required exception cases, timestamp/price formatting, fixed-delay timing without catch-up attempts, persist-before-publish ordering, and greatest-`observedAt` latest selection without adding automated tests.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-06
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation and the `assetaz-ticker-service` delta for behavior. Nenjim's planned component model constructs every component independently in a context, leaves it initialized but unstarted, and later lets components query that context for peer plugins by interface. Issue #61 therefore requires plugin-style acquisition without constructor-time coupling between a price source and the ticker, while retaining the existing typed ValueTypes, durable append format, and temporary Cauldron placement.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:** Keep acquisition resources owned by independently constructible sources; make the ticker the source-discovery owner, lifecycle coordinator, and durable sink; isolate histories by stable source identity; preserve greatest-timestamp selection and persist-before-publish under concurrent callbacks; align dependencies with Nenjim's future component context.
|
||||||
|
|
||||||
|
**Non-Goals:** Implementing Nenjim's final context-query API in this change, dynamic source changes after ticker startup, automatic migration, source health/retry policy, Raydium or streaming integrations, public history queries, consumers, or automated tests.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Add API-package `PriceSource` and functional `PriceSink` interfaces. `PriceSource.start(PriceSink)` performs lifecycle-time wiring, so a source constructor needs neither the ticker nor a sink. `TickerService` exposes `start`, `stop`, and latest-price queries but no `addPriceSource`; plugin management is an implementation responsibility rather than a client capability. `TickerService` does not extend `PriceSink`, while `TickerServiceImpl` implements both contracts.
|
||||||
|
- In the final Nenjim model, `TickerServiceImpl` queries its own context for `PriceSource` implementations after all components have been independently constructed. It does not query for sinks: the ticker is the sink and passes itself to each active source when calling `priceSource.start(this)`. The current public varargs constructor `TickerServiceImpl(PriceSource...)` is temporary Cauldron wiring that supplies exactly the instances a future context lookup will return; it is not public plugin-management API or the final discovery mechanism.
|
||||||
|
- Add `PriceSourceName` beneath `String -> Name` in `conf/detag.conf`, as explicitly authorized by issue #61. Store source identity in `PriceObservation` but not in history lines, because the source directory is the persistent identity and repeated line metadata would create disagreement risks.
|
||||||
|
- Represent each context-provided source internally by the source object, its `(TradingPair, sourceName)` key, derived path, activation state, and a per-history lock. Reject duplicate keys and unsafe names while capturing the constructor-provided/discovered set before startup. Compare callbacks by exact source object identity as well as stable metadata so an unknown impersonating instance cannot address another source's history.
|
||||||
|
- Centralize paths under the ticker data root using canonical base UUID, canonical quote UUID, validated source name, and sanitized symbols in the final filename. Source names are rejected rather than rewritten because they are technical persistent identity. Symbols are non-identity display text and are converted character-by-character to safe filename components, rejecting an empty result.
|
||||||
|
- During `start()`, validate and derive every obtained source, check exact file existence without scanning or creating, load all active histories into local state, compute per-pair maximum timestamps, then commit startup state and start active sources with `source.start(this)`. If loading or starting fails, stop every source already started and leave the ticker stopped rather than expose a partially started lifecycle.
|
||||||
|
- During `stop()`, prevent new callbacks, stop every started source, and aggregate/report lifecycle failures while ensuring every source receives a stop attempt. The obtained source set remains owned by the ticker across a subsequent restart; changing the context-provided set dynamically while starting, started, or stopping is outside this design.
|
||||||
|
- Move the hardcoded price, clock, one-minute delay, and scheduled executor into `HardcodedPriceSource`. Its public constructor has no sink or ticker dependency. `start(PriceSink)` first rejects an already-started call, then stores the supplied sink, announces immediately, and schedules subsequent attempts with `scheduleWithFixedDelay`; checking first prevents a repeated start from replacing the live sink. `stop()` shuts down its executor. The ticker contains none of those acquisition constants or resources.
|
||||||
|
- On `announce`, resolve the exact active registration, validate non-null price and timestamp, construct `AssetPrice` and `PriceObservation`, lock that source history, append the complete UTF-8 line with `FileChannel`, and call `force(true)`. Only afterward atomically update the pair's latest value when the new timestamp is greater. Per-history locks allow independent sources to persist concurrently without interleaving, while atomic per-pair maximum updates preserve cross-source ordering.
|
||||||
|
- Keep strict UTC parsing and the existing comment behavior. Loaded observations receive trading pair and source name from the obtained source context. The old flat history path is deliberately ignored; operator-controlled manual movement is the only migration.
|
||||||
|
- Temporary NenjimHub wiring constructs an unstarted `HardcodedPriceSource` independently, then constructs `TickerServiceImpl` with that source through the varargs Cauldron adapter and starts the ticker. The ticker performs sink wiring and lifecycle. This simulates future context lookup without transferring AssetAZ ownership or adding consumers.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [A source throws during startup after earlier sources started] → Stop all sources that were successfully started, suppress secondary stop failures, and fail ticker startup clearly.
|
||||||
|
- [A source callback races with stop] → Guard lifecycle and active-registration checks so callbacks are accepted only while the ticker is started; source stop is responsible for terminating its own producer resources.
|
||||||
|
- [Multiple source callbacks contend for one trading pair] → Use per-history serialization for files and an atomic greatest-timestamp update for the shared pair view.
|
||||||
|
- [Symbol sanitization can make filenames less recognizable] → Preserve UUIDs as technical identity and use a deterministic safe replacement only for the human-readable leaf filename.
|
||||||
|
- [Manual migration can temporarily deactivate the hardcoded source] → Log the exact expected new path and never create or infer content, making operator action explicit and reversible.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Before deployment, stop the old service and move `data/assetaz/ticker/EVE_USDC.prices` to `data/assetaz/ticker/019c3f9f-41d1-7a73-b1df-d4c11c7ff301/019c3f9f-41d1-7a73-b1df-d4c11c7ff302/Hardcoded/EVE_USDC.prices`, creating parent directories only as an operator action. Deploy the API, ticker, hardcoded source, Detag configuration, and wiring together. Rollback restores the previous code and moves the file back to the legacy flat path; no automatic data transformation is involved.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
The AssetAZ Ticker currently owns a hardcoded price generator, so acquisition, scheduling, persistence, and publication are coupled in one implementation. Nenjim's future component model constructs components independently and lets a component obtain peer plugins from its context after construction, so price sources must be independently constructible and wired to the ticker only when the ticker starts them.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add `PriceSource` and functional `PriceSink` API contracts in which independently constructed sources receive their sink through `start(PriceSink)` and own their acquisition resources.
|
||||||
|
- Keep plugin discovery and lifecycle inside `TickerServiceImpl`: it obtains `PriceSource` implementations from its Nenjim context, starts them with itself as sink, and stops them. Ordinary `TickerService` clients receive no plugin-management API.
|
||||||
|
- Add the `ΩPriceSourceNameΩ` ValueTag and include stable source identity in each in-memory `PriceObservation`.
|
||||||
|
- **BREAKING** Replace the single history file with an independent UUID-based history path for each obtained `(TradingPair, sourceName)` source identity; the old file is not migrated automatically.
|
||||||
|
- Refactor `TickerServiceImpl` into a source-agnostic sink that validates callbacks, persists each source independently, and publishes the greatest successfully persisted timestamp across active sources for a pair.
|
||||||
|
- Move the existing `EVE_USDC = 14.85` generation and fixed-delay scheduler into a stoppable `HardcodedPriceSource` plugin.
|
||||||
|
- Use a temporary `TickerServiceImpl(PriceSource...)` Cauldron constructor to simulate the future Nenjim context lookup while constructing `HardcodedPriceSource` independently.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `assetaz-ticker-service`: Generalize the ticker from one internal generator and history into context-provided source plugins with per-source activation, persistence, lifecycle, and cross-source latest selection.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Changes the AssetAZ ticker plugin API and reference implementation under `com.r35157.assetaz.core.service.ticker`; the ordinary `TickerService` API exposes lifecycle and price queries, not plugin registration.
|
||||||
|
- Adds `PriceSourceName` to the shared Detag hierarchy under `String -> Name`; this configuration change is explicitly required by issue #61.
|
||||||
|
- Changes the operator-managed history location and requires manual movement of the old hardcoded history.
|
||||||
|
- Updates temporary wiring in `NenjimHubImpl` to construct the source and ticker independently before injecting the source list as a stand-in for future context discovery.
|
||||||
|
- Adds no Raydium, WebSocket, history-query, retention, or automated-test functionality.
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Price sources are discovered and lifecycle-managed
|
||||||
|
Nenjim SHALL be able to construct each price source independently in an initialized but unstarted state without supplying a ticker or sink. The ticker SHALL obtain `PriceSource` plugins from its Nenjim context, while ordinary ticker clients SHALL NOT receive a public operation for adding or managing sources. The ticker SHALL identify each obtained source by its `TradingPair` and stable source name, reject duplicate identities clearly, start each active source by passing itself as `PriceSink`, and stop every source it started. Source names SHALL be non-empty safe directory components and SHALL reject `/`, `\`, `..`, and control characters without rewriting them. A source SHALL announce typed prices and timestamps only through the sink received at start and SHALL remain unaware of its persistence path. A repeated source start SHALL reject the call before replacing its existing sink.
|
||||||
|
|
||||||
|
#### Scenario: Sources are constructed independently
|
||||||
|
- **WHEN** Nenjim constructs a price source before constructing or starting the ticker
|
||||||
|
- **THEN** source construction requires neither a `PriceSink` nor a ticker reference and leaves the source unstarted
|
||||||
|
|
||||||
|
#### Scenario: Ticker obtains source plugins
|
||||||
|
- **WHEN** the ticker initializes its source set from its Nenjim context
|
||||||
|
- **THEN** it looks for implementations of `PriceSource`, not implementations of `PriceSink`, and manages the obtained sources internally
|
||||||
|
|
||||||
|
#### Scenario: Ordinary clients cannot manage plugins
|
||||||
|
- **WHEN** a caller uses the public `TickerService` contract
|
||||||
|
- **THEN** the contract exposes ticker lifecycle and latest-price queries but no source-registration operation
|
||||||
|
|
||||||
|
#### Scenario: Distinct context sources are accepted
|
||||||
|
- **WHEN** the ticker obtains two sources with different source names or trading pairs from its context
|
||||||
|
- **THEN** it accepts both and manages each source independently
|
||||||
|
|
||||||
|
#### Scenario: Duplicate persistent identity is rejected
|
||||||
|
- **WHEN** the ticker obtains sources with the same trading pair and source name
|
||||||
|
- **THEN** initialization fails with an error identifying the duplicate source history
|
||||||
|
|
||||||
|
#### Scenario: Unsafe source name is rejected
|
||||||
|
- **WHEN** a source name is empty or contains a slash, backslash, `..`, or a control character
|
||||||
|
- **THEN** ticker initialization or startup fails clearly without rewriting the source name or creating filesystem content
|
||||||
|
|
||||||
|
#### Scenario: Active source lifecycle is managed
|
||||||
|
- **WHEN** the ticker starts and later stops with an active context-provided source
|
||||||
|
- **THEN** it calls `start` with itself as `PriceSink` and subsequently stops the source without leaving source-owned resources running
|
||||||
|
|
||||||
|
#### Scenario: Repeated source start preserves existing sink
|
||||||
|
- **WHEN** an already-started source receives another start call with a different sink
|
||||||
|
- **THEN** it rejects the call before replacing the sink from the successful start
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Ticker exposes typed latest prices
|
||||||
|
The ticker SHALL expose the latest successfully persisted `PriceObservation` for a requested `TradingPair` across all active context-provided sources for that pair. A `PriceObservation` SHALL contain exactly an `AssetPrice`, its observation `Instant`, and the stable source name; the `AssetPrice` SHALL contain both its `ΩPriceΩ` value and `TradingPair`. If several active sources provide a pair, latest SHALL be the observation with the greatest `observedAt` timestamp, and its source name SHALL identify its source. A request for a pair with no obtained sources, no active sources, or no successfully persisted observation SHALL throw a clear exception rather than return `null`.
|
||||||
|
|
||||||
|
#### Scenario: Latest supported price is available
|
||||||
|
- **WHEN** a caller requests a trading pair after at least one observation has been loaded or persisted by an active source
|
||||||
|
- **THEN** the ticker returns the successfully persisted observation with the greatest timestamp and its source identity
|
||||||
|
|
||||||
|
#### Scenario: Newest observation is selected across sources
|
||||||
|
- **WHEN** multiple active sources for one trading pair have successfully persisted observations
|
||||||
|
- **THEN** the ticker returns the observation with the greatest `observedAt` across those sources
|
||||||
|
|
||||||
|
#### Scenario: Pair has no obtained or active source
|
||||||
|
- **WHEN** a caller requests a pair with no context-provided source or whose obtained sources are all inactive
|
||||||
|
- **THEN** the ticker throws an exception that clearly identifies the unavailable trading pair
|
||||||
|
|
||||||
|
#### Scenario: Active pair has no persisted observation
|
||||||
|
- **WHEN** a caller requests a pair whose active sources have no successfully persisted observation
|
||||||
|
- **THEN** the ticker throws an exception that clearly states that no persisted price is available for that trading pair
|
||||||
|
|
||||||
|
### Requirement: Price history explicitly activates each source
|
||||||
|
For each context-provided source, the ticker SHALL derive an independent history path as `data/assetaz/ticker/<base UUID>/<quote UUID>/<source name>/<safe base symbol>_<safe quote symbol>.prices`. UUIDs SHALL use their unchanged canonical representation and SHALL be the technical trading-pair identity; sanitized symbols SHALL be used only for the human-readable filename. Only an existing file at that exact path SHALL activate the source. The ticker SHALL neither scan for arbitrary sources nor create or migrate missing files or directories. It SHALL load every valid observation using the obtained source's trading pair and source name and select the greatest timestamp across all active histories for each pair, regardless of file order. Empty lines, full-line comments, and inline comments SHALL remain supported. An existing history with no data SHALL activate only that source without an initial observation, while a missing history SHALL leave only that source inactive and emit a warning containing the trading pair, source name, and complete expected path.
|
||||||
|
|
||||||
|
#### Scenario: Existing source history activates and restores identity
|
||||||
|
- **WHEN** a context-provided source has an existing valid history with permitted whitespace or comments
|
||||||
|
- **THEN** that source is active and its observations are restored with its captured trading pair and source name
|
||||||
|
|
||||||
|
#### Scenario: Empty source history activates without a price
|
||||||
|
- **WHEN** a context-provided source has an existing empty or comment-only history file
|
||||||
|
- **THEN** that source is active without an initial observation
|
||||||
|
|
||||||
|
#### Scenario: Missing source history leaves only that source inactive
|
||||||
|
- **WHEN** one obtained source history is missing while another source for the same pair has an existing history
|
||||||
|
- **THEN** the missing source is not started, the existing source remains active, and no missing path is created
|
||||||
|
|
||||||
|
#### Scenario: Missing history warning identifies the source
|
||||||
|
- **WHEN** a context-provided source's expected history file is absent
|
||||||
|
- **THEN** the ticker logs a warning containing its trading pair, source name, and complete expected UUID-based path
|
||||||
|
|
||||||
|
#### Scenario: Malformed source history is rejected clearly
|
||||||
|
- **WHEN** comment text is removed and a non-empty data line in an active source history lacks a valid UTC timestamp and price
|
||||||
|
- **THEN** ticker startup fails with an error containing the history filename, one-based physical line number, and identifying invalid content
|
||||||
|
|
||||||
|
#### Scenario: Legacy history is not migrated
|
||||||
|
- **WHEN** only `data/assetaz/ticker/EVE_USDC.prices` exists
|
||||||
|
- **THEN** the ticker does not read, move, copy, or create a replacement for that legacy file
|
||||||
|
|
||||||
|
### Requirement: History uses the human-editable observation format
|
||||||
|
Each source history data line SHALL retain the form `<UTC timestamp>:<price>`, where the timestamp uses `uuuuMMddHHmmssSSS'Z'` with millisecond precision in UTC. Trading-pair and source identity SHALL come from the context-provided source and its directory context and SHALL NOT be stored in individual data lines. Automatic writes SHALL append plain data lines only and SHALL leave all existing comments and blank lines untouched.
|
||||||
|
|
||||||
|
#### Scenario: Observation is written in the required format
|
||||||
|
- **WHEN** any source observation at `2026-08-05T13:15:42.783Z` with price `14.85` is persisted
|
||||||
|
- **THEN** the appended line is `20260805131542783Z:14.85`
|
||||||
|
|
||||||
|
#### Scenario: Identity is not duplicated in data lines
|
||||||
|
- **WHEN** an observation is appended to a source history
|
||||||
|
- **THEN** its data line contains neither source name nor trading-pair identity
|
||||||
|
|
||||||
|
#### Scenario: Existing operator annotations are preserved
|
||||||
|
- **WHEN** the ticker appends an observation to a source history containing comments or blank lines
|
||||||
|
- **THEN** those existing lines remain unchanged and the appended observation contains neither a comment nor extra annotation
|
||||||
|
|
||||||
|
### Requirement: New observations are durable before publication
|
||||||
|
Only an active source obtained and started by the ticker SHALL be allowed to announce an observation. For each accepted announcement, the ticker SHALL validate the source, typed price, and timestamp; construct an observation with the source's captured trading pair and source name; serialize writes to that source history; append and force the data to persistent storage; and only after persistence succeeds consider it for the trading pair's in-memory latest value. Concurrent callbacks from different sources SHALL neither corrupt nor interleave writes, and readers SHALL never observe an unpersisted price. Every accepted observation SHALL be persisted even when an existing observation has a later timestamp, and latest SHALL change only when the newly persisted timestamp is later. If persistence fails, the ticker SHALL report the trading pair, source, and path clearly and preserve the prior latest observation.
|
||||||
|
|
||||||
|
#### Scenario: Active obtained source observation is persisted
|
||||||
|
- **WHEN** an active source started by the ticker announces a valid price and timestamp
|
||||||
|
- **THEN** the ticker appends and forces the observation to that source's history before considering it for latest
|
||||||
|
|
||||||
|
#### Scenario: Inactive or unknown source callback is rejected
|
||||||
|
- **WHEN** an inactive source or a source not obtained by the ticker announces an observation
|
||||||
|
- **THEN** the ticker rejects the callback without persisting or publishing it
|
||||||
|
|
||||||
|
#### Scenario: Future-dated observation remains latest
|
||||||
|
- **WHEN** an accepted observation is persisted with a timestamp earlier than the current latest observation across active sources
|
||||||
|
- **THEN** the accepted observation remains in its source history and the current later observation remains latest
|
||||||
|
|
||||||
|
#### Scenario: Concurrent source callbacks remain durable
|
||||||
|
- **WHEN** active sources announce observations concurrently
|
||||||
|
- **THEN** each successful append is a complete non-interleaved history line and latest identifies the greatest successfully persisted timestamp
|
||||||
|
|
||||||
|
#### Scenario: Persistence fails
|
||||||
|
- **WHEN** appending or forcing an accepted observation fails
|
||||||
|
- **THEN** the failure identifies the trading pair, source, and path, the attempted observation is not published, and the previously persisted latest observation remains available
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
## 1. Ticker Source API
|
||||||
|
|
||||||
|
- [x] 1.1 Add `PriceSourceName` under `String -> Name` in `conf/detag.conf` and retain required generated-type imports in `.tjava` sources.
|
||||||
|
- [x] 1.2 Add the `PriceSource` and functional `PriceSink` API contracts with typed metadata and `PriceSource.start(PriceSink)` lifecycle wiring.
|
||||||
|
- [x] 1.3 Extend `PriceObservation` with source identity and extend `TickerService` with stop lifecycle while keeping plugin management out of the public service API.
|
||||||
|
|
||||||
|
## 2. Source Registry and Persistence
|
||||||
|
|
||||||
|
- [x] 2.1 Implement internal handling of context-provided sources with identity validation, duplicate rejection, lifecycle state checks, and exact-instance callback validation in `TickerServiceImpl`.
|
||||||
|
- [x] 2.2 Implement centralized UUID/source/safe-symbol history paths without scanning, creating, or migrating filesystem content.
|
||||||
|
- [x] 2.3 Load active per-source histories with strict parsing, comments, source-context observations, clear malformed-line errors, and greatest-timestamp restoration across sources.
|
||||||
|
- [x] 2.4 Persist concurrent active-source announcements to independent histories with complete-line serialization and `FileChannel.force(true)` before atomic greatest-timestamp publication.
|
||||||
|
- [x] 2.5 Implement ticker start/stop orchestration so only active sources receive `start(this)`, every started source receives a stop attempt, and partial startup is rolled back.
|
||||||
|
|
||||||
|
## 3. Hardcoded Source and Wiring
|
||||||
|
|
||||||
|
- [x] 3.1 Add an independently constructible, stoppable `HardcodedPriceSource` whose `start(PriceSink)` preserves an existing sink on repeated start, announces `EVE_USDC = 14.85` immediately, and owns one-minute fixed-delay scheduling.
|
||||||
|
- [x] 3.2 Update NenjimHub temporary autorun wiring to construct the hardcoded source independently and pass it through `TickerServiceImpl(PriceSource...)` as a Cauldron simulation of future context discovery.
|
||||||
|
|
||||||
|
## 4. Verification
|
||||||
|
|
||||||
|
- [x] 4.1 Compile the project and manually verify API shape, constructor-independent sources, start-time sink wiring and repeated-start protection, safe and duplicate source handling, missing/empty/malformed histories, UUID paths, legacy-file exclusion, source lifecycle, per-source formatting, persistence failure, concurrent callbacks, and greatest-timestamp selection without adding automated tests.
|
||||||
|
- [x] 4.2 Validate the OpenSpec change, confirm `TickerServiceImpl` has no generator/scheduler constants, and confirm no unrelated behavior or generated Detag source was edited.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-07
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. Currency metadata currently lives in `WellKnownCurrencyTypes`, while `WellKnownTradingPairs` captures static pairs and `RaydiumImpl` hardcodes one of them. The repository temporarily hosts both logical AssetAZ API and implementation packages, and Nenjim runtime discovery is outside this issue.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Separate the public identity API from its hardcoded implementation package.
|
||||||
|
- Make UUID identity robust across service instances and metadata revisions.
|
||||||
|
- Migrate every production caller in one step so no parallel authoritative registry remains.
|
||||||
|
- Let Raydium translate the pool response's actual mint identities into its returned pair.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Moving value types other than `CurrencyType`.
|
||||||
|
- Runtime service discovery, remote/database catalogues, public registration, or constructor restriction.
|
||||||
|
- Unit tests or Nenjim Test Tool changes.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### External identity is a private composite key
|
||||||
|
|
||||||
|
`ExternalCurrencyReference` is the public immutable carrier and explicitly implements equality and hashing from `namespace + externalId`; optional symbol metadata is excluded. The implementation may retain a private composite key for indexing and conflict diagnostics, but public value equality has the same semantic identity. The hardcoded catalogue separately compares supplied symbol metadata so conflicting catalogue entries are still rejected.
|
||||||
|
|
||||||
|
### Canonical and external metadata have different completeness rules
|
||||||
|
|
||||||
|
Every `CurrencyType` has a non-null UUID, name, and canonical symbol, even though only its UUID participates in equality. An external reference may omit its observed symbol because integrations can provide a valid namespace and external identifier without display metadata. A currency may also have zero external references; reverse lookup then returns an immutable empty set. Evelyn IOU is not such an example because its Solana mint is configured explicitly.
|
||||||
|
|
||||||
|
### Catalogue construction validates before freezing
|
||||||
|
|
||||||
|
The hardcoded implementation builds local maps, detects duplicate UUIDs with conflicting metadata and duplicate external identities targeting different UUIDs, then stores immutable copies. Reverse-reference sets are also immutable. This gives lock-free concurrent reads and contains mutation entirely within construction. A public registration API was rejected because version 1 is deliberately read-only.
|
||||||
|
|
||||||
|
### NenjimHub owns the service instance
|
||||||
|
|
||||||
|
`NenjimHubImpl.startAutoRunProcesses()` is the temporary Cauldron composition root. It creates exactly one `HardcodedCurrencyIdentityService`, then passes the interface-typed instance through constructors to the ticker source, Solana, Jupiter, Raydium, Evelyn, and any other autorun component that needs currency identities. Identity consumers never instantiate the hardcoded implementation themselves and do not use a global singleton. Multiple local service instances were rejected because they create independent lifecycles and prevent Nenjim from replacing the context's implementation coherently.
|
||||||
|
|
||||||
|
### Existing UUIDs move with their metadata
|
||||||
|
|
||||||
|
The four existing UUIDs move into an API-only `CurrencyTypeIds` constants class. It contains no metadata, mappings, or value instances and is therefore not a currency registry. The hardcoded implementation owns current names, symbols, and external mappings—including the Evelyn IOU name and mint—while production consumers resolve API UUIDs or available external identities through their injected service. No consumer imports `impl.hc`, and no static `CurrencyType` or `TradingPair` objects remain.
|
||||||
|
|
||||||
|
### Raydium uses response mint order
|
||||||
|
|
||||||
|
`RaydiumImpl` receives `CurrencyIdentityService` beside `SolanaBlockChain`. `fetchPoolPrice` extracts both response mint addresses and optional symbol metadata, creates references in a Solana-mint namespace, resolves each through the service, and constructs `TradingPair(mintA, mintB)`. Missing external symbol fields do not prevent lookup. Other Raydium calculations and State pool accounting likewise resolve actual pool mints and preserve A/B order rather than assuming SOL/SyrupUSDC. Constructor injection is the temporary integration seam until Nenjim provides dynamic dependencies.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Existing constructors and imports break during migration] → Update all production call sites and compile every affected Gradle module before completion.
|
||||||
|
- [Raydium response shape differs between endpoints] → Reuse the existing pool-node extraction path and validate address/symbol fields explicitly with clear IO errors.
|
||||||
|
- [A second registry survives unnoticed] → Search the complete production tree for old types, static pairs, and legacy imports during final review.
|
||||||
|
- [No new automated regression coverage] → Respect the explicit issue scope and rely on compilation, strict OpenSpec validation, and focused diff/static review.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Introduce the AssetAZ API and hardcoded implementation with the preserved catalogue and required Solana mint mappings.
|
||||||
|
2. Create one implementation instance in NenjimHub's autorun composition root and pass it through every identity-dependent construction chain.
|
||||||
|
3. Move `CurrencyType`, migrate production imports, and replace static trading-pair use with resolutions from the injected service.
|
||||||
|
4. Inject the service into Raydium and resolve response mint identities.
|
||||||
|
5. Delete the legacy registries only after all production references are gone.
|
||||||
|
6. Compile affected modules without running tests, validate OpenSpec strictly, and inspect the complete diff.
|
||||||
|
|
||||||
|
Rollback consists of reverting the change as one unit because the package move, registry deletion, and constructor change are intentionally atomic.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Currency identities are currently duplicated in static registries and coupled to callers such as Raydium. AssetAZ needs one service-owned UUID identity model that can resolve external identifiers without making symbols or integrations the canonical identity.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add a public AssetAZ Currency Identity Service API and immutable external-reference value type.
|
||||||
|
- Publish stable AssetAZ currency UUID constants in an API-only identifier class so consumers never depend on the hardcoded implementation package.
|
||||||
|
- Add a hardcoded reference implementation containing the current currency definitions and Solana mint mappings.
|
||||||
|
- **BREAKING** Move `CurrencyType` from the shared basic value-types package to `com.r35157.assetaz.valuetypes` and define equality by UUID alone.
|
||||||
|
- **BREAKING** Remove `WellKnownCurrencyTypes` and `WellKnownTradingPairs`; construct trading pairs from identities resolved by the service.
|
||||||
|
- Inject the Currency Identity Service into `RaydiumImpl` and resolve pool mint identities when producing prices and ranges.
|
||||||
|
- Create the hardcoded service exactly once in NenjimHub's autorun composition root and pass that shared service instance to every component that requires currency identities.
|
||||||
|
- Preserve canonical currency metadata as non-null while allowing external references to omit symbol metadata and allowing currencies to have no external references.
|
||||||
|
- Do not add or rewrite unit tests or Nenjim Test Tool code as part of this change.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `assetaz-currency-identity-service`: Defines UUID-based currency identity, external-identifier resolution, the immutable hardcoded catalogue, and its use by Raydium.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The change affects the AssetAZ value-type and service packages, public UUID identifiers, all production imports of `CurrencyType`, NenjimHub dependency wiring, hardcoded ticker data, Raydium construction and pricing, State pool accounting, and callers that construct identity-dependent components. It removes the two legacy static currency/trading-pair registries. No external dependency or ValueTag configuration change is required.
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Provides one authoritative AssetAZ identity model for currencies and translates stable UUIDs and namespaced external identifiers without treating display symbols as identity.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Stable AssetAZ currency identity
|
||||||
|
The system SHALL represent a currency as an immutable `CurrencyType` with a non-null UUID, name, and symbol. Its UUID is its complete stable identity. Equality and hash codes SHALL depend only on that UUID, regardless of name, symbol, instance identity, or metadata changes.
|
||||||
|
|
||||||
|
#### Scenario: Metadata changes for the same UUID
|
||||||
|
- **WHEN** two currency values have the same UUID but different names or symbols
|
||||||
|
- **THEN** they compare equal and have the same hash code
|
||||||
|
|
||||||
|
#### Scenario: Matching metadata for different UUIDs
|
||||||
|
- **WHEN** two currency values have different UUIDs but identical names and symbols
|
||||||
|
- **THEN** they do not compare equal
|
||||||
|
|
||||||
|
#### Scenario: Missing canonical metadata
|
||||||
|
- **WHEN** construction omits a currency name or symbol
|
||||||
|
- **THEN** construction fails without producing an incomplete currency value
|
||||||
|
|
||||||
|
### Requirement: Resolve canonical currencies
|
||||||
|
The Currency Identity Service SHALL resolve a known AssetAZ UUID to a non-null current currency value and SHALL fail with a clear exception for an unknown UUID.
|
||||||
|
|
||||||
|
#### Scenario: Resolve known UUID
|
||||||
|
- **WHEN** a client resolves a configured AssetAZ currency UUID
|
||||||
|
- **THEN** the service returns the current currency metadata for that UUID
|
||||||
|
|
||||||
|
#### Scenario: Resolve unknown UUID
|
||||||
|
- **WHEN** a client resolves an unconfigured UUID
|
||||||
|
- **THEN** the service throws an exception that clearly identifies the unknown UUID
|
||||||
|
|
||||||
|
### Requirement: Resolve namespaced external identities
|
||||||
|
The Currency Identity Service SHALL resolve external currencies by the pair `namespace + externalId`. External symbols MAY be absent and SHALL be retained as metadata when present, but SHALL NOT participate in lookup identity or equality. A namespace SHALL describe the external identification system rather than an observing price source.
|
||||||
|
|
||||||
|
#### Scenario: Resolve a Solana mint
|
||||||
|
- **WHEN** a client supplies a configured Solana-mint namespace and mint address with symbol metadata
|
||||||
|
- **THEN** the service returns the currency mapped to that namespace and mint address
|
||||||
|
|
||||||
|
#### Scenario: Symbol differs from configured metadata
|
||||||
|
- **WHEN** the namespace and external identifier are configured but the supplied symbol differs
|
||||||
|
- **THEN** the external references compare equal and the service resolves the same currency because the symbol is not part of external identity
|
||||||
|
|
||||||
|
#### Scenario: External symbol is absent
|
||||||
|
- **WHEN** a client supplies a configured namespace and external identifier without symbol metadata
|
||||||
|
- **THEN** the service resolves the currency and returns its non-null canonical symbol
|
||||||
|
|
||||||
|
#### Scenario: Unknown external identity
|
||||||
|
- **WHEN** a client supplies an unconfigured namespace and external identifier
|
||||||
|
- **THEN** the service throws an exception that clearly identifies the unknown external identity
|
||||||
|
|
||||||
|
### Requirement: Reverse external-reference lookup
|
||||||
|
The Currency Identity Service SHALL return all configured external references for a known AssetAZ UUID as a non-null immutable set, including an empty set when none exist, and SHALL fail clearly for an unknown UUID.
|
||||||
|
|
||||||
|
#### Scenario: Currency has multiple references
|
||||||
|
- **WHEN** a known UUID has references in multiple external systems
|
||||||
|
- **THEN** reverse lookup returns every reference without exposing mutable registry state
|
||||||
|
|
||||||
|
#### Scenario: Known currency has no references
|
||||||
|
- **WHEN** a known UUID has no configured external references
|
||||||
|
- **THEN** reverse lookup returns an empty immutable set
|
||||||
|
|
||||||
|
#### Scenario: Reverse lookup Evelyn IOU
|
||||||
|
- **WHEN** a client requests external references for the Evelyn IOU UUID
|
||||||
|
- **THEN** the result contains its configured Solana mint reference
|
||||||
|
|
||||||
|
### Requirement: Immutable conflict-free hardcoded catalogue
|
||||||
|
The hardcoded Currency Identity Service SHALL preserve the existing AssetAZ currency UUIDs, maintain one current currency value for each UUID, support multiple external references per UUID, reject conflicting UUID or external-identity mappings during initialization, expose no public mutation operation, and be safe for concurrent reads after construction. NenjimHub SHALL create exactly one hardcoded service instance for its autorun context and SHALL pass that same instance to every autorun component that requires currency identities.
|
||||||
|
|
||||||
|
#### Scenario: Conflicting external mapping
|
||||||
|
- **WHEN** initialization maps the same namespace and external identifier to two different AssetAZ UUIDs
|
||||||
|
- **THEN** initialization fails with a clear conflict exception
|
||||||
|
|
||||||
|
#### Scenario: Conflicting currency metadata
|
||||||
|
- **WHEN** initialization provides conflicting current values for the same UUID
|
||||||
|
- **THEN** initialization fails with a clear conflict exception
|
||||||
|
|
||||||
|
#### Scenario: Compose autorun components
|
||||||
|
- **WHEN** NenjimHub constructs its autorun component graph
|
||||||
|
- **THEN** it creates one hardcoded Currency Identity Service and injects that same instance into every component that requires it
|
||||||
|
|
||||||
|
### Requirement: Trading pairs use service-owned identities
|
||||||
|
Production code SHALL construct trading pairs from currency values obtained from the current Currency Identity Service and SHALL NOT maintain a parallel static registry of canonical currency or trading-pair instances.
|
||||||
|
|
||||||
|
#### Scenario: Construct a configured pair
|
||||||
|
- **WHEN** production code needs a trading pair for configured currencies
|
||||||
|
- **THEN** it resolves both UUIDs or external identities through the current service before constructing the pair
|
||||||
|
|
||||||
|
### Requirement: Stable UUID identifiers are implementation-independent
|
||||||
|
The API SHALL expose stable AssetAZ currency UUID constants without names, symbols, external mappings, or `CurrencyType` instances. Consumers that require a known AssetAZ UUID SHALL use these API identifiers and SHALL NOT import the hardcoded Currency Identity Service implementation.
|
||||||
|
|
||||||
|
#### Scenario: Consumer resolves a known UUID
|
||||||
|
- **WHEN** a consumer needs a known AssetAZ currency by UUID
|
||||||
|
- **THEN** it obtains the UUID from the API identifier class and resolves it through its injected Currency Identity Service
|
||||||
|
|
||||||
|
### Requirement: Pool amounts follow actual mint identities
|
||||||
|
Pool accounting SHALL resolve mint A and mint B through the Currency Identity Service using their actual namespaced Solana mint addresses and SHALL preserve the pool's A/B order when assigning currency types to amounts.
|
||||||
|
|
||||||
|
#### Scenario: State loads a pool
|
||||||
|
- **WHEN** State receives pool information with mint A and mint B
|
||||||
|
- **THEN** amount A uses the currency resolved from mint A and amount B uses the currency resolved from mint B
|
||||||
|
|
||||||
|
### Requirement: Raydium resolves response currencies
|
||||||
|
Raydium SHALL receive a Currency Identity Service dependency and SHALL build a fetched pool price's trading pair in Raydium response order by resolving `mintA` and `mintB` as namespaced Solana mint identities. It SHALL NOT resolve currencies by symbol alone.
|
||||||
|
|
||||||
|
#### Scenario: Fetch a configured Raydium pool price
|
||||||
|
- **WHEN** Raydium returns a price plus configured mint A and mint B identifiers, with or without symbols
|
||||||
|
- **THEN** the returned asset price uses a trading pair whose base is the service resolution of mint A and whose quote is the service resolution of mint B
|
||||||
|
|
||||||
|
#### Scenario: Raydium returns an unknown mint
|
||||||
|
- **WHEN** either returned mint identity is not configured
|
||||||
|
- **THEN** pool-price creation fails with the Currency Identity Service's clear unknown-identity exception
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
## 1. Currency Identity API and Catalogue
|
||||||
|
|
||||||
|
- [x] 1.1 Move `CurrencyType` into the AssetAZ value-types package and make equality and hashing UUID-only.
|
||||||
|
- [x] 1.2 Add the annotated immutable `ExternalCurrencyReference` and read-only `CurrencyIdentityService` API.
|
||||||
|
- [x] 1.3 Implement the immutable hardcoded catalogue with preserved UUIDs, reverse lookup, Solana mint mappings, and construction-time conflict detection.
|
||||||
|
|
||||||
|
## 2. Production Migration
|
||||||
|
|
||||||
|
- [x] 2.1 Migrate every production `CurrencyType` import and replace hardcoded ticker pair construction with identities from the current service.
|
||||||
|
- [x] 2.2 Inject the Currency Identity Service into `RaydiumImpl` and resolve Raydium mint identities and response-ordered trading pairs.
|
||||||
|
- [x] 2.3 Update production construction/configuration sites for the new dependencies and remove `WellKnownCurrencyTypes` and `WellKnownTradingPairs`.
|
||||||
|
|
||||||
|
## 3. Verification
|
||||||
|
|
||||||
|
- [x] 3.1 Compile every affected module without running unit tests.
|
||||||
|
- [x] 3.2 Run strict OpenSpec validation and `git diff --check`.
|
||||||
|
- [x] 3.3 Review the complete diff and production tree for incomplete migrations, old imports, parallel currency registries, and unintended test or Nenjim Test Tool changes.
|
||||||
|
|
||||||
|
## 4. Composition-Root Correction
|
||||||
|
|
||||||
|
- [x] 4.1 Create the hardcoded Currency Identity Service exactly once in `NenjimHubImpl.startAutoRunProcesses()` and pass it into every autorun construction chain that needs it.
|
||||||
|
- [x] 4.2 Replace identity consumers' local hardcoded-service construction with required `CurrencyIdentityService` constructor dependencies.
|
||||||
|
- [x] 4.3 Compile main and test source sets, strict-validate OpenSpec, run `git diff --check`, and confirm no hardcoded service construction remains outside the NenjimHub composition root.
|
||||||
|
|
||||||
|
## 5. Public API Documentation
|
||||||
|
|
||||||
|
- [x] 5.1 Document the public Currency Identity Service, external-reference, and currency value APIs, including identity semantics, nullability, and failure behavior.
|
||||||
|
- [x] 5.2 Compile main and test source sets and rerun strict OpenSpec validation and `git diff --check`.
|
||||||
|
|
||||||
|
## 6. Review Corrections
|
||||||
|
|
||||||
|
- [x] 6.1 Require complete canonical currency metadata, add API-owned UUID identifiers, and preserve the Evelyn IOU name and Solana mint mapping.
|
||||||
|
- [x] 6.2 Make external-reference equality use namespace plus external ID while retaining optional symbols and catalogue conflict detection.
|
||||||
|
- [x] 6.3 Resolve State pool currencies from actual mint A/B identities and make Raydium symbol extraction optional.
|
||||||
|
- [x] 6.4 Remove consumer dependencies on `impl.hc`, compile without tests, strict-validate OpenSpec, run `git diff --check`, and review the complete migration.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-08
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. Commit `2fc553b` established package ownership that anticipates later extraction into Nenjim modules while the code remains in one Gradle project. The existing Raydium Pool PriceSource is an unimplemented scaffold in its intended implementation package. The Ticker already activates registered sources only when their exact history file exists and persists callbacks before updating its in-memory latest observation.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Complete the existing pool-source scaffold as an independently constructible, restartable PriceSource.
|
||||||
|
- Preserve the package and future artifact dependency direction introduced by `2fc553b`.
|
||||||
|
- Compose one EVE/USDT pool source beside the existing hardcoded EVE/USDC source using today's temporary Cauldron wiring.
|
||||||
|
- Make timing deterministic enough for verification through package-private clock and delay injection.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Dynamic Nenjim discovery, configuration files, streaming, backoff, health APIs, history creation or migration, visualization, currency conversion, or further physical module splitting.
|
||||||
|
- Changes to the generic Ticker behavior or canonical Ticker specification.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Preserve future module and package ownership
|
||||||
|
|
||||||
|
The future boundaries remain:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Ticker API
|
||||||
|
com.r35157.assetaz.services.ticker
|
||||||
|
- TickerService
|
||||||
|
- PriceObservation
|
||||||
|
|
||||||
|
PriceSource plugin API
|
||||||
|
com.r35157.assetaz.services.ticker.plugins.pricesource
|
||||||
|
- PriceSource
|
||||||
|
- PriceSink
|
||||||
|
|
||||||
|
Ticker reference implementation
|
||||||
|
com.r35157.assetaz.services.ticker.impl.ref
|
||||||
|
-> Ticker API
|
||||||
|
-> PriceSource plugin API
|
||||||
|
|
||||||
|
Hardcoded PriceSource implementation
|
||||||
|
com.r35157.assetaz.services.ticker.plugins.pricesource.impl.hardcoded
|
||||||
|
-> PriceSource plugin API
|
||||||
|
|
||||||
|
Raydium Pool PriceSource implementation
|
||||||
|
com.r35157.assetaz.services.ticker.plugins.pricesource.impl.raydiumpool
|
||||||
|
-> PriceSource plugin API
|
||||||
|
-> Raydium API and value types
|
||||||
|
```
|
||||||
|
|
||||||
|
`PriceSink` stays in the plugin API so implementations do not depend on the Ticker API or reference implementation. `TickerServiceImpl` implements `PriceSink`, owns source lifecycle, and temporarily receives sources through its varargs constructor. `NenjimHubImpl` remains the temporary composition root; dynamic discovery is deferred. The alternative of moving the sink or source packages would recreate future circular or implementation dependencies and is explicitly rejected.
|
||||||
|
|
||||||
|
### Use a source-owned single-thread fixed-delay scheduler
|
||||||
|
|
||||||
|
Each source creates a daemon single-thread scheduled executor when started. The immediate attempt is submitted to that executor with zero initial delay, followed by fixed-delay scheduling so a slow or paused call cannot produce catch-up observations. A package-private constructor injects `Clock`, delay, and time unit; the public constructor fixes UTC and one minute. A shared scheduler was rejected because lifecycle and termination must belong to the source instance.
|
||||||
|
|
||||||
|
### Contain each attempt at the scheduler boundary
|
||||||
|
|
||||||
|
One safe polling method catches retrieval, validation, and sink-publication failures, logs pool and pair context, and returns normally so fixed-delay scheduling continues. Pair mismatch is treated as an invalid observation rather than a fatal lifecycle error. The timestamp is read only after a successful matching response and truncated to milliseconds before the sink callback.
|
||||||
|
|
||||||
|
### Keep startup and shutdown state synchronized
|
||||||
|
|
||||||
|
Startup checks the running state before validating or assigning the new sink, creates fresh scheduling resources, and schedules the immediate task. Stop detaches the scheduler under synchronization, requests immediate shutdown, and waits for termination without holding the lifecycle monitor. Interrupted waiting restores the caller's interrupt flag and ensures shutdown remains requested. This avoids replacing an active sink and permits restart after resource termination.
|
||||||
|
|
||||||
|
### Resolve EVE/USDT through the shared Currency Identity Service
|
||||||
|
|
||||||
|
The API identifier class gains only a stable USDT UUID. The hardcoded catalogue owns canonical metadata and the official Solana mint mapping. NenjimHub constructs the expected pair by resolving EVE and USDT through its single shared CIS instance, constructs Raydium with that same CIS dependency, and supplies both hardcoded and Raydium sources to the temporary Ticker constructor. The pool ID is market/source identity and is not registered in CIS.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [A blocking Raydium call can delay shutdown until the call honors interruption] → request immediate shutdown, preserve interruption, and keep the polling thread daemonized.
|
||||||
|
- [A missing manually provisioned history silently leaves the new source inactive] → retain the existing explicit warning and document the exact pool-based history path.
|
||||||
|
- [Temporary constructor injection can be mistaken for permanent discovery] → document it as Cauldron wiring and preserve interfaces and packages for later Nenjim discovery.
|
||||||
|
- [A response may reverse or otherwise change the configured pair] → validate exact pair equality and publish nothing on mismatch.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Add USDT identity data, complete the existing source, and extend only the NenjimHub composition graph. Deployment requires manually creating the exact empty EVE/USDT pool history file to activate polling. Rollback removes that file or removes the temporary source wiring; existing hardcoded EVE/USDC history is unchanged.
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
AssetAZ Ticker currently has only a hardcoded price source and therefore cannot observe Evelyn IOU's real Raydium market price. A pool-specific plugin is needed to poll the EVE/USDT pool while preserving the PriceSource lifecycle and future Nenjim module boundaries.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Complete the existing Raydium Pool PriceSource scaffold as a lifecycle-managed, fixed-delay polling source representing one Raydium pool.
|
||||||
|
- Add Tether USD (USDT) as a stable, externally resolvable AssetAZ currency distinct from USDC.
|
||||||
|
- Temporarily compose the EVE/USDT Raydium source in NenjimHub alongside the existing hardcoded EVE/USDC source.
|
||||||
|
- Preserve history activation, persistence-before-publication, package ownership, and future artifact boundaries without adding discovery, configuration, streaming, conversion, or automatic history creation.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `assetaz-raydium-pool-price-source`: Defines pool identity, polling, validation, publication, failure isolation, and restartable lifecycle behavior for a Raydium-backed PriceSource.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `assetaz-currency-identity-service`: Adds the stable canonical USDT identity and official Solana mint mapping while keeping it distinct from USDC.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The change affects the AssetAZ Currency Identity Service catalogue and identifiers, the existing Raydium Pool PriceSource implementation scaffold, and temporary NenjimHub composition. It uses the existing Raydium and PriceSource APIs and does not change the generic Ticker service contract or create new Gradle subprojects.
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Stable Tether USD identity
|
||||||
|
The Currency Identity Service SHALL provide Tether USD as a stable AssetAZ currency with canonical name `Tether USD`, canonical symbol `USDT`, and an implementation-independent UUID identifier. It SHALL resolve that currency through both the stable UUID and official Solana mint `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB`. USDT and USDC SHALL remain distinct currency identities, and resolution SHALL NOT imply conversion between them.
|
||||||
|
|
||||||
|
#### Scenario: Resolve USDT by stable UUID
|
||||||
|
- **WHEN** a client resolves the stable USDT UUID
|
||||||
|
- **THEN** the service returns the canonical Tether USD currency with symbol USDT
|
||||||
|
|
||||||
|
#### Scenario: Resolve USDT by official Solana mint
|
||||||
|
- **WHEN** a client resolves the official USDT mint in the Solana-mint namespace
|
||||||
|
- **THEN** the service returns the same canonical USDT identity as UUID resolution
|
||||||
|
|
||||||
|
#### Scenario: Compare USDT and USDC
|
||||||
|
- **WHEN** a client resolves both USDT and USDC
|
||||||
|
- **THEN** they have different UUIDs and no implicit conversion is performed
|
||||||
|
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Provides independently lifecycle-managed price observations for one Raydium pool through the AssetAZ PriceSource plugin contract.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: One source represents one stable Raydium pool
|
||||||
|
A Raydium Pool PriceSource instance SHALL represent exactly one Raydium pool, SHALL expose the expected trading pair supplied at construction, and SHALL expose the stable source name `Raydium-<poolId>`. Construction SHALL validate all dependencies and identity values, leave the source stopped, perform no network access, and require no price sink until startup.
|
||||||
|
|
||||||
|
#### Scenario: Construct a pool source
|
||||||
|
- **WHEN** a client constructs a source with a Raydium service, pool ID, and expected trading pair
|
||||||
|
- **THEN** the stopped source reports that trading pair and a source name containing that exact pool ID without performing a price retrieval
|
||||||
|
|
||||||
|
#### Scenario: Reject invalid construction
|
||||||
|
- **WHEN** a required constructor argument is absent or invalid
|
||||||
|
- **THEN** construction fails without starting polling or accessing the network
|
||||||
|
|
||||||
|
### Requirement: Poll immediately with fixed delay
|
||||||
|
On startup, the source SHALL begin its first retrieval attempt immediately and SHALL begin each subsequent attempt one minute after the preceding attempt completes. Polling SHALL execute on a daemon scheduler thread owned exclusively by that source instance.
|
||||||
|
|
||||||
|
#### Scenario: Start a source
|
||||||
|
- **WHEN** a stopped source is started with a price sink
|
||||||
|
- **THEN** its first pool-price retrieval begins immediately on its source-owned daemon scheduler
|
||||||
|
|
||||||
|
#### Scenario: Complete a periodic attempt
|
||||||
|
- **WHEN** a retrieval attempt completes
|
||||||
|
- **THEN** the next attempt begins after the configured one-minute delay without fixed-rate catch-up attempts
|
||||||
|
|
||||||
|
### Requirement: Validate and publish pool observations
|
||||||
|
The source SHALL retrieve the configured pool price and compare the response trading pair with its expected pair. For a matching response, it SHALL create the observation timestamp after the successful response, truncate it to milliseconds, and publish the returned price value through the sink supplied at startup.
|
||||||
|
|
||||||
|
#### Scenario: Receive the expected pair
|
||||||
|
- **WHEN** a successful pool response contains the expected trading pair
|
||||||
|
- **THEN** the source publishes its price with a post-response timestamp truncated to milliseconds
|
||||||
|
|
||||||
|
#### Scenario: Receive a different pair
|
||||||
|
- **WHEN** a pool response contains a trading pair different from the expected pair
|
||||||
|
- **THEN** the source publishes no observation, logs the pool ID plus expected and received pairs, and schedules the next attempt normally
|
||||||
|
|
||||||
|
### Requirement: Isolate temporary polling failures
|
||||||
|
Temporary network, Raydium, parsing, interruption, or downstream persistence failures SHALL be logged with the pool ID and expected trading pair and SHALL NOT cancel later polling attempts or stop the Ticker service.
|
||||||
|
|
||||||
|
#### Scenario: A polling attempt fails temporarily
|
||||||
|
- **WHEN** a retrieval or publication attempt throws an exception
|
||||||
|
- **THEN** the source logs the contextual failure and retries on the next fixed-delay interval
|
||||||
|
|
||||||
|
### Requirement: Restartable source lifecycle
|
||||||
|
The source SHALL reject a repeated startup before replacing its existing sink. Stopping SHALL be idempotent, terminate source-owned scheduling resources, correctly preserve interruption, and permit a later startup with a newly supplied sink.
|
||||||
|
|
||||||
|
#### Scenario: Start an already-started source
|
||||||
|
- **WHEN** startup is invoked while the source is already running
|
||||||
|
- **THEN** startup fails and the currently active sink remains unchanged
|
||||||
|
|
||||||
|
#### Scenario: Stop a running source
|
||||||
|
- **WHEN** stop is invoked on a running source
|
||||||
|
- **THEN** its scheduler terminates and no further polling begins
|
||||||
|
|
||||||
|
#### Scenario: Stop an already-stopped source
|
||||||
|
- **WHEN** stop is invoked while the source is stopped
|
||||||
|
- **THEN** it completes without error
|
||||||
|
|
||||||
|
#### Scenario: Restart a stopped source
|
||||||
|
- **WHEN** a previously stopped source is started with a sink
|
||||||
|
- **THEN** it creates fresh source-owned scheduling resources and begins an immediate retrieval attempt
|
||||||
|
|
||||||
|
### Requirement: Activation uses independent Ticker history
|
||||||
|
The pool source SHALL participate in the existing Ticker activation contract under its exact trading pair and pool-based source name. It SHALL be active only when that exact history file already exists and SHALL NOT create or migrate history files.
|
||||||
|
|
||||||
|
#### Scenario: Pool history exists
|
||||||
|
- **WHEN** the EVE/USDT history file exists beneath the EVE UUID, USDT UUID, and exact pool-based source-name path
|
||||||
|
- **THEN** the Ticker activates the Raydium source independently of the hardcoded EVE/USDC history
|
||||||
|
|
||||||
|
#### Scenario: Pool history is absent
|
||||||
|
- **WHEN** the exact Raydium source history file is absent
|
||||||
|
- **THEN** the Ticker leaves that source inactive and creates no file or directory
|
||||||
|
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
## 1. Currency Identity
|
||||||
|
|
||||||
|
- [x] 1.1 Add the API-owned stable USDT UUID and hardcoded canonical Tether USD catalogue entry with the official Solana mint.
|
||||||
|
- [x] 1.2 Verify USDT resolution by UUID and mint, distinct identity from USDC, and absence of conversion or parallel registry data.
|
||||||
|
|
||||||
|
## 2. Raydium Pool PriceSource
|
||||||
|
|
||||||
|
- [x] 2.1 Complete the existing `impl.raydiumpool.RaydiumPoolPriceSource` scaffold with validated pool identity and no construction-time sink or network access.
|
||||||
|
- [x] 2.2 Implement immediate source-owned daemon polling with one-minute fixed delay, response-pair validation, millisecond observation timestamps, sink publication, and contextual failure isolation.
|
||||||
|
- [x] 2.3 Implement repeated-start rejection, idempotent resource-terminating stop, interruption handling, and restart with fresh scheduling resources.
|
||||||
|
|
||||||
|
## 3. Temporary Composition
|
||||||
|
|
||||||
|
- [x] 3.1 Construct the EVE/USDT pair and Raydium pool source in `NenjimHubImpl` using the shared Currency Identity Service and hardcoded pool ID.
|
||||||
|
- [x] 3.2 Inject the Raydium source alongside the unchanged running `HardcodedPriceSource` through the temporary Ticker constructor.
|
||||||
|
|
||||||
|
## 4. Boundary and Behavior Review
|
||||||
|
|
||||||
|
- [x] 4.1 Verify the `2fc553b` package and future module boundaries: Ticker API, PriceSource/PriceSink plugin API, Ticker reference implementation, and separate hardcoded and Raydium source implementations.
|
||||||
|
- [x] 4.2 Verify exact pool-based source naming and EVE/USDT history path, existing-file-only activation, persistence before publication, polling recovery, and stop/restart semantics without adding automated tests.
|
||||||
|
- [x] 4.3 Review the complete diff for stale imports, duplicate implementations, incomplete wiring, unintended HardcodedPriceSource changes, and excluded features.
|
||||||
|
|
||||||
|
## 5. Build and Specification Verification
|
||||||
|
|
||||||
|
- [x] 5.1 Compile main and test source sets without running unit tests.
|
||||||
|
- [x] 5.2 Run strict OpenSpec validation and `git diff --check`.
|
||||||
|
- [x] 5.3 Sync both deltas into canonical specifications, archive the completed change, and rerun strict OpenSpec validation.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-08
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. Evelyn currently returns hardcoded history and EMC copies it once while constructing six static series. The existing Ticker already supplies persisted latest EVE/USDT observations through its public API. The baseline deliberately disables all NenjimHub autorun services, so this change must explicitly reactivate only the dependency chain needed for the live chart.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Keep financial calculation, sampling, and process-local history in Evelyn-owned code.
|
||||||
|
- Keep JavaFX chart mutation and visualization lifecycle in EMC-owned code.
|
||||||
|
- Make formula and scheduler behavior deterministic using explicit instants, an injected clock, and injectable delay units.
|
||||||
|
- Preserve all six typed index fields while enabling only the price-index chart series.
|
||||||
|
- Retain the safety posture of the baseline composition root.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Ticker/Raydium contract changes, index persistence/backfill, other five index calculations, conversions, configuration, streaming, trading, alarms, unrelated service activation, or automated unit-test coverage in this change.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Separate the pure formula from sampling
|
||||||
|
|
||||||
|
An Evelyn-owned calculator accepts explicit `BigDecimal` prices and `Instant` values. It uses the Copenhagen start configuration resolved to the fixed instant `2026-07-31T22:00:00Z`, a 365-day seconds denominator, and an explicit high-precision `MathContext`. Fractional exponentiation is isolated behind the calculator rather than embedded in JavaFX or scheduler code. This makes all reference examples deterministic. Direct calculation in EMC was rejected because presentation must not own financial behavior.
|
||||||
|
|
||||||
|
### Make sampling part of the Evelyn lifecycle
|
||||||
|
|
||||||
|
The Evelyn API exposes start and stop operations for the complete Evelyn lifecycle rather than exposing sampling controls to presentation code. The reference implementation receives only public `TickerService`, the canonical expected trading pair, a clock, and delay configuration. Starting each instance creates its daemon single-thread scheduler using fixed delay with zero initial delay. Successful attempts append under a lock; snapshot reads use `List.copyOf` under the same lock. Exceptions are caught at the task boundary so future attempts survive. Stopping is idempotent, releases the owned executor, and discards that instance's process-local history. A global scheduler, an EMC-owned scheduler, or shared Production/Test history was rejected because environments and lifecycles must remain independent.
|
||||||
|
|
||||||
|
### Let EMC incrementally reconcile snapshots on the JavaFX thread
|
||||||
|
|
||||||
|
Each environment chart keeps its own enabled series and a cache used only to optimize rendering. Opening or reopening EMC reconstructs both charts and immediately reconciles each complete authoritative Evelyn snapshot before starting a JavaFX `Timeline` for incremental refresh. Prefix mismatches also force a complete series rebuild, so a stale count cannot hide replaced history. Empty and single-point histories receive non-degenerate timestamp bounds; Y bounds use only absolute Evelyn Price Index values and fall back to a symmetric default. The five existing series-add and bound-contribution statements remain commented scaffolding. Background observer callbacks were rejected because they complicate thread ownership and can mutate JavaFX objects unsafely.
|
||||||
|
|
||||||
|
### Keep EMC lifecycle presentation-only
|
||||||
|
|
||||||
|
NenjimHub starts both Evelyn instances after Ticker and before opening EMC. EMC only reads their histories and owns its JavaFX refresh timeline. Hiding the window stops that timeline but leaves Evelyn sampling and history untouched. Reopening the same EMC instance creates fresh chart state from the complete current snapshots, so points collected while hidden remain visible. Evelyn sampling stops and its in-memory history is discarded only when the corresponding Evelyn instance is stopped.
|
||||||
|
|
||||||
|
### Enable only the required NenjimHub dependency chain
|
||||||
|
|
||||||
|
`NenjimHubImpl` creates one CIS, Solana implementation, Raydium implementation, the existing pool-specific EVE/USDT PriceSource, and Ticker; it starts Ticker before constructing and starting two Evelyn instances with the public Ticker and canonical pair, then starts EMC. The hardcoded source, alarm, Composer, Process Manager, Test Tool, Soda Task Manager, Suwimo Client, and all trading behavior remain disabled. Existing-file-only Ticker activation remains unchanged.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Fractional exponentiation ultimately uses finite floating-point transcendental support] → isolate conversion and retain DECIMAL128 arithmetic around it; the deterministic seam remains available for later focused verification.
|
||||||
|
- [EMC refresh can observe several newly sampled points at once] → reconcile authoritative snapshots against the rendered prefix on the JavaFX thread and preserve oldest-to-newest order.
|
||||||
|
- [Ticker has no active EVE/USDT history or has not completed its first poll] → log and skip the point; fixed-delay sampling retries without fabricating data.
|
||||||
|
- [EMC can be hidden while samples continue] → keep history authoritative in Evelyn and reconstruct fresh chart state from complete snapshots whenever EMC is reopened.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Deploy with the existing manually activated EVE/USDT Ticker history file. On process start, histories are intentionally empty. Rollback restores the disabled autorun composition and static presentation; no data migration is needed because index history is never persisted.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Evelyn Mission Control currently renders a static six-series chart from hardcoded data, so it does not describe Evelyn's live market-price balance. Evelyn needs to calculate and retain the first real status index from AssetAZ Ticker while EMC updates safely during the current process lifetime.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Replace hardcoded Evelyn status-index points with separate, thread-safe, in-memory Production and Test histories sampled for the complete lifecycle of each started Evelyn instance.
|
||||||
|
- Calculate the expected exponential EVE price and Evelyn Price Index deterministically from an explicit sampling instant and the latest canonical EVE/USDT Ticker observation.
|
||||||
|
- Isolate temporary Ticker/sample failures so no fake point is created and later fixed-delay samples continue.
|
||||||
|
- Make EMC's Production and Test charts reconstruct the complete current Evelyn snapshots when opened or reopened and update live on the JavaFX Application Thread, with only Evelyn Price Index enabled and controlling symmetric bounds.
|
||||||
|
- Compose only CIS, Solana, Raydium, the real EVE/USDT PriceSource, Ticker, Evelyn, and EMC in NenjimHub; leave all unrelated and safety-sensitive services disabled.
|
||||||
|
- Keep calculation, timing, sampling-delay, and chart-update seams deterministic so focused automated verification can be added later; unit tests are deliberately deferred from this change.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-status-index-history`: Replaces hardcoded six-line snapshots with live in-memory Evelyn Price Index sampling, formula, failure handling, separate environment histories, and one enabled live chart series.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The change affects the Evelyn API/reference implementation, EMC chart lifecycle and rendering, and temporary NenjimHub composition. It depends on the existing public Ticker and canonical currency identities; generic Ticker and Raydium PriceSource contracts and persistence remain unchanged.
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Evelyn calculates the live price index
|
||||||
|
Evelyn SHALL obtain the actual EVE/USDT price from the latest AssetAZ Ticker observation for the canonical EVE/USDT trading pair. It SHALL treat USDT as USD for this calculation and SHALL NOT access Raydium directly. At sampling instant `t`, on or after `2026-07-31T22:00:00Z`, the expected price SHALL be `15 × 1.20^(elapsedSeconds / 31,536,000)` and the Evelyn Price Index SHALL be `((actualPrice / expectedPrice) - 1) × 10`, calculated with adequate decimal precision and without arbitrary history rounding.
|
||||||
|
|
||||||
|
#### Scenario: Calculate expected price at the configured start
|
||||||
|
- **WHEN** the sampling instant is `2026-07-31T22:00:00Z`
|
||||||
|
- **THEN** the expected EVE price is exactly 15 USD
|
||||||
|
|
||||||
|
#### Scenario: Calculate expected price after one year
|
||||||
|
- **WHEN** exactly 31,536,000 seconds have elapsed since the configured start
|
||||||
|
- **THEN** the expected EVE price is exactly 18 USD
|
||||||
|
|
||||||
|
#### Scenario: Calculate representative fractional-year growth
|
||||||
|
- **WHEN** 18 days, 3 hours, 8 minutes, and 14 seconds have elapsed
|
||||||
|
- **THEN** the expected EVE price is approximately `15.136464436276` USD
|
||||||
|
|
||||||
|
#### Scenario: Calculate index from actual price
|
||||||
|
- **WHEN** actual price equals expected price, is 10 percent above, is 10 percent below, is double, or is half the expected price
|
||||||
|
- **THEN** the respective index is 0, 1, -1, 10, or -5
|
||||||
|
|
||||||
|
### Requirement: Evelyn samples without overlapping or catch-up attempts
|
||||||
|
Each started Evelyn environment SHALL own its sampling executor, attempt its first sample immediately, and make subsequent attempts approximately one minute after the preceding attempt completes. Sampling SHALL remain active for the Evelyn lifecycle independently of whether Mission Control is open. Evelyn SHALL add exactly one timestamped point per successful attempt and SHALL isolate a failed or temporarily unavailable Ticker lookup without adding a fake point, terminating Evelyn, or cancelling the next attempt. Stopping Evelyn SHALL terminate its sampling executor.
|
||||||
|
|
||||||
|
#### Scenario: Initial Ticker data is unavailable
|
||||||
|
- **WHEN** the immediate sampling attempt cannot obtain a latest EVE/USDT price
|
||||||
|
- **THEN** no point is added, diagnostic context is logged, and the next fixed-delay attempt remains scheduled
|
||||||
|
|
||||||
|
#### Scenario: A later sample succeeds
|
||||||
|
- **WHEN** a later attempt obtains a latest EVE/USDT price
|
||||||
|
- **THEN** exactly one point is appended using the sampling instant for both expected-price calculation and point timestamp
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Evelyn provides historical status-index measurements
|
||||||
|
Each Evelyn instance SHALL begin with an empty, process-local in-memory history and expose a safe, non-null snapshot of typed measurement points ordered from oldest to newest while sampling may continue concurrently. Each successful point SHALL contain its sampling timestamp, the calculated Evelyn Price Index, and `BigDecimal.ZERO` placeholders for EVE_SYRUP Pool Depth Index, EVE_SYRUP Pool Balance Index, AAZDKK_USDT Pool Balance Index, AAZDKK_USDT Pool Price Index, and AAZDKK_USDT Pool Depth Index. Production and Test SHALL use separate history collections, and no point SHALL be persisted, restored, or backfilled.
|
||||||
|
|
||||||
|
#### Scenario: Status-index history is requested during sampling
|
||||||
|
- **WHEN** a caller requests an Evelyn environment's status-index history while points may be appended
|
||||||
|
- **THEN** it receives a safe, non-null, oldest-to-newest snapshot containing all six typed fields
|
||||||
|
|
||||||
|
#### Scenario: A process starts two Evelyn environments
|
||||||
|
- **WHEN** Production and Test Evelyn instances are created for a new process
|
||||||
|
- **THEN** both histories start empty and remain separate even if their calculated values are identical
|
||||||
|
|
||||||
|
#### Scenario: A real point is sampled
|
||||||
|
- **WHEN** Evelyn appends a successful price-index point
|
||||||
|
- **THEN** its Evelyn Price Index contains the calculated value and each of the other five fields contains zero
|
||||||
|
|
||||||
|
#### Scenario: An Evelyn instance is stopped
|
||||||
|
- **WHEN** a Production or Test Evelyn instance is stopped
|
||||||
|
- **THEN** its sampling executor terminates and its complete process-local history is discarded without affecting the other environment
|
||||||
|
|
||||||
|
### Requirement: Evelyn Mission Control visualizes status-index measurements
|
||||||
|
Evelyn Mission Control SHALL display separate live Production and Test Overview charts from their corresponding authoritative Evelyn snapshots. Each chart SHALL contain one enabled series named `Evelyn Price Index`; the five future series and their typed fields SHALL remain available as disabled source scaffolding. Opening or reopening EMC SHALL immediately reconstruct each chart from the complete current snapshot before periodically refreshing it. Successful points SHALL be added to chart objects only on the JavaFX Application Thread while the window remains open. Timestamp bounds SHALL expand as points arrive, and only enabled Evelyn Price Index values SHALL determine a dynamic Y-axis range that is symmetric around and always displays zero, using a reasonable default range for empty or all-zero history. Hiding or closing EMC SHALL release only EMC's JavaFX refresh resources and SHALL NOT start, stop, or clear either Evelyn environment.
|
||||||
|
|
||||||
|
#### Scenario: Production and Test Overviews start empty
|
||||||
|
- **WHEN** Mission Control opens with two newly created Evelyn environments
|
||||||
|
- **THEN** it displays two separate empty one-series charts with valid timestamp axes and symmetric Y-axes containing zero
|
||||||
|
|
||||||
|
#### Scenario: A successful point appears while EMC is open
|
||||||
|
- **WHEN** an Evelyn history receives a new successful point
|
||||||
|
- **THEN** the corresponding chart adds it on the JavaFX Application Thread and updates its timestamp and symmetric price-index bounds without reopening EMC
|
||||||
|
|
||||||
|
#### Scenario: EMC reopens after collecting hidden-window samples
|
||||||
|
- **WHEN** the same EMC instance is reopened after Evelyn collected additional points while its window was hidden
|
||||||
|
- **THEN** each chart is reconstructed immediately from its environment's complete current history without missing points because of stale rendering state
|
||||||
|
|
||||||
|
#### Scenario: Future fields contain larger values
|
||||||
|
- **WHEN** any disabled future-index field has a greater absolute value than Evelyn Price Index
|
||||||
|
- **THEN** that disabled value does not affect the visible Y-axis bounds
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
## 1. Evelyn Calculation and History
|
||||||
|
|
||||||
|
- [x] 1.1 Add the deterministic expected-price and Evelyn Price Index calculator with explicit precision and configured start instant.
|
||||||
|
- [x] 1.2 Replace hardcoded history with an empty thread-safe per-instance snapshot history and preserve all six typed fields using zero placeholders.
|
||||||
|
- [x] 1.3 Make restartable fixed-delay sampling and complete process-local history owned by each Evelyn lifecycle, with immediate first attempt, failure isolation, clean stop, and history discard on stop.
|
||||||
|
|
||||||
|
## 2. Live Mission Control Chart
|
||||||
|
|
||||||
|
- [x] 2.1 Refactor Production and Test Overview charts into separate live chart states that reconstruct the complete authoritative snapshot on every open and reconcile later snapshots on the JavaFX thread.
|
||||||
|
- [x] 2.2 Enable only Evelyn Price Index, retain the five future series and bound contributions as commented scaffolding, and calculate valid dynamic X and symmetric Y bounds from enabled data only.
|
||||||
|
- [x] 2.3 Keep EMC lifecycle presentation-only: hiding stops its JavaFX refresh, reopening rebuilds fresh chart state, and EMC never starts or stops Evelyn sampling.
|
||||||
|
|
||||||
|
## 3. Safe NenjimHub Composition
|
||||||
|
|
||||||
|
- [x] 3.1 Enable only CIS, Solana, Raydium, the EVE/USDT Raydium PriceSource, Ticker, two Evelyn environments, and EMC in the composition root.
|
||||||
|
- [x] 3.2 Start Ticker before constructing and independently starting both Evelyn lifecycles; pass public TickerService plus the canonical CIS-resolved EVE/USDT pair to each instance before starting EMC.
|
||||||
|
- [x] 3.3 Confirm HardcodedPriceSource and every unrelated alarm, trading, Composer, Process Manager, Test Tool, Soda Task Manager, and Suwimo service remain disabled.
|
||||||
|
|
||||||
|
## 4. Specification and Final Checks
|
||||||
|
|
||||||
|
- [x] 4.1 Update the canonical capability Purpose to stop claiming that all six indexes are currently visible.
|
||||||
|
- [x] 4.2 Compile main and test source sets without running unit tests; automated unit-test coverage is deliberately deferred.
|
||||||
|
- [x] 4.3 Run strict OpenSpec validation and `git diff --check`.
|
||||||
|
- [x] 4.4 Review the complete diff for unrelated changes, generic Ticker/Raydium changes, persistence changes, and accidentally enabled services.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-08
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation and the capability delta for the behavioral contract. Issue #64 established that Evelyn owns sampling and complete history while EMC is presentation-only. Ticker already demonstrates durable append with missing-final-newline handling, while configuration parsing demonstrates strict first-actual-entry format declarations. Issue #65 applies those repository patterns to a distinct Evelyn-owned format without changing either existing subsystem.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Make a normalized instance name a safe permanent storage identity with atomic JVM-local ownership.
|
||||||
|
- Make startup transactional from the caller's perspective: validate and parse fully before publishing history or starting a thread.
|
||||||
|
- Coordinate persistence and publication under Evelyn ownership with durable append ordering.
|
||||||
|
- Separate restartable stop from irreversible close without releasing a live writer's identity.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Creating or repairing runtime storage, cross-process locking, migration, retention, rotation, compaction, additional record types, test creation, or changes to Ticker, Raydium, EMC file access, and unrelated composition.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Use AutoCloseable for permanent lifecycle completion
|
||||||
|
|
||||||
|
`Evelyn` extends `AutoCloseable` and overrides `close()` without a checked exception. `start()` and `stop()` remain the operational lifecycle: stop is restartable and retains the name, while close is permanent and idempotent. This is preferable to an Evelyn-specific disposal name because Java callers can use standard resource ownership and the semantic distinction remains explicit in the contract.
|
||||||
|
|
||||||
|
### Validate completely before atomic reservation
|
||||||
|
|
||||||
|
Construction first validates every non-name argument and derives the NFC name, case-folded registry key, direct-child directory, and status path. Only after all fallible argument validation succeeds does a static concurrent set reserve the key atomically. The constructor performs no I/O. This prevents invalid construction from leaking reservations and keeps independently constructible components stopped.
|
||||||
|
|
||||||
|
### Treat close as release-after-confirmed-termination
|
||||||
|
|
||||||
|
Lifecycle state distinguishes stopped, started, stopping, closed, and termination-failed conditions. Stop interrupts and awaits the owned scheduler. Only confirmed termination returns the object to restartable stopped state. Close invokes the same safe stop path and removes the registry key only after confirmation; any failure is reported while the object retains both its non-startable safety state and reservation. Repeated successful close is a no-op.
|
||||||
|
|
||||||
|
### Make startup loading transactional
|
||||||
|
|
||||||
|
Every start rechecks the directory and file using both metadata/access predicates and actual UTF-8 reading. All lines are read, the dedicated status-format declaration is validated before record parsing, and records are decoded into a temporary list with non-decreasing timestamp checks. Only a successful complete load replaces the in-memory list and establishes the last persisted timestamp. The executor is created last. Failed startup leaves memory empty, storage untouched, and no sampling thread.
|
||||||
|
|
||||||
|
### Keep status format validation dedicated
|
||||||
|
|
||||||
|
The loader hardcodes `SUPPORTED_STATUS_HISTORY_FORMAT_VERSION = 1` and applies the same declaration convention as configuration files, but produces status-history-specific diagnostics and does not couple version numbers or parsing code to configuration or Ticker formats. Type codes are explicit constants rather than enum ordinals. Full original lines are retained for line diagnostics before comment removal.
|
||||||
|
|
||||||
|
### Serialize durable append and publication
|
||||||
|
|
||||||
|
The single sampling thread still coordinates the critical operation explicitly. Under the history lock it rejects timestamps earlier than the last persisted timestamp, opens the existing file without create/truncate options, adds a newline first when necessary, writes the encoded UTF-8 record, and forces the channel. Only then does it update the last timestamp and append to visible memory. I/O failures escape this critical operation into the sampling boundary, which logs instance/path/context and leaves scheduling alive.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Filesystem access predicates can race with actual operations] → perform the required predicates for useful diagnostics and still treat actual read/open/write/force failures as authoritative.
|
||||||
|
- [A blocked sampling task may ignore interruption] → never release the name unless executor termination is confirmed; retain the reservation and report failure.
|
||||||
|
- [JVM-local uniqueness cannot protect another process] → document cross-process locking as out of scope and rely on deployment ownership.
|
||||||
|
- [Full history loading is unbounded] → accept the issue-defined complete-load behavior; retention and compaction are future work.
|
||||||
|
- [NFC plus case folding can map spellings to one identity] → preserve the winning normalized display spelling on disk while reserving a `Locale.ROOT` lower-case key.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Before enabling each instance, operators create `data/evelyn/Production/status.log` and `data/evelyn/Test/status.log` with physical first line `FORMAT_VERSION=1`. Deployment then starts Ticker, constructs and starts both named Evelyn instances, and opens EMC. Rollback requires stopping/closing the instances but leaves status files untouched; older code will ignore them because it has no Evelyn persistence support.
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Evelyn currently discards every collected status-index point when it stops, so process restarts lose operational history. Each independently named Evelyn environment needs durable, operator-provisioned append-only storage while Evelyn—not Mission Control—continues to own collection, validation, and publication.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **BREAKING** Require every `EvelynImpl` to have a validated, NFC-normalized instance name that selects its permanent `data/evelyn/<name>/status.log` identity.
|
||||||
|
- Reserve normalized instance names atomically and case-insensitively within the JVM until permanent, idempotent close.
|
||||||
|
- **BREAKING** Extend Evelyn lifecycle with permanent close in addition to restartable start/stop.
|
||||||
|
- Strictly validate externally provisioned storage, format version 1, every record, and non-decreasing timestamps before publishing restored history or starting sampling.
|
||||||
|
- Durably append each sampled type-1 status record before publishing it in memory, while isolating individual append failures.
|
||||||
|
- Keep Production and Test storage, lifecycle, and history independent and keep EMC presentation-only.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `evelyn-status-index-history`: Replace process-local disposable history with named-instance storage, strict restore validation, persist-before-publish behavior, and distinct restartable stop versus permanent close semantics.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The change affects the public Evelyn lifecycle, `EvelynImpl` construction and persistence, and temporary Production/Test composition in `NenjimHubImpl`. Operators must provision each instance directory and versioned `status.log` before startup. EMC, AssetAZ Ticker, Raydium PriceSource, generated Detag sources, and unrelated services remain behaviorally unchanged.
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Evelyn instances have safe unique persistent names
|
||||||
|
Every Evelyn instance SHALL have an NFC-normalized Unicode name whose preserved spelling and capitalization selects the direct child directory `data/evelyn/<name>`. A name SHALL be rejected if it is empty or whitespace-only, has leading or trailing whitespace, is `.` or `..`, ends with a period, contains a control character or any of `< > : " / \ | ? *`, is a case-insensitive Windows-reserved filename including a reserved base with an extension, or normalizes as a path to anything other than one direct child of `data/evelyn`. Within one JVM, normalized names SHALL be reserved atomically and compared case-insensitively using locale-independent rules.
|
||||||
|
|
||||||
|
#### Scenario: A portable Unicode name is accepted
|
||||||
|
- **WHEN** an instance is constructed with a valid Unicode name containing Danish characters
|
||||||
|
- **THEN** NFC normalization is applied and the normalized spelling and capitalization identify its direct child data directory
|
||||||
|
|
||||||
|
#### Scenario: An unsafe name is rejected without reservation
|
||||||
|
- **WHEN** construction receives an invalid name or any other invalid constructor argument
|
||||||
|
- **THEN** construction fails before reserving a persistent identity
|
||||||
|
|
||||||
|
#### Scenario: Concurrent equivalent names conflict
|
||||||
|
- **WHEN** two constructor calls in one JVM concurrently request names that are equal after NFC normalization and locale-independent case folding
|
||||||
|
- **THEN** exactly one reserves the persistent identity and the other fails
|
||||||
|
|
||||||
|
### Requirement: Evelyn distinguishes restartable stop from permanent close
|
||||||
|
Stopping Evelyn SHALL terminate sampling, clear memory, preserve its persistent file, and retain its name reservation so the same object can restart. Evelyn SHALL support permanent, idempotent close; closing an active instance SHALL first stop it safely, a successfully closed instance SHALL never start again, and its name SHALL be released only after its sampling thread has definitely terminated. A termination failure SHALL be reported and SHALL retain the reservation.
|
||||||
|
|
||||||
|
#### Scenario: A stopped instance restarts
|
||||||
|
- **WHEN** a stopped but not closed Evelyn instance is started again
|
||||||
|
- **THEN** it retains exclusive ownership of the same name and reloads the complete persisted history before sampling
|
||||||
|
|
||||||
|
#### Scenario: An active instance closes successfully
|
||||||
|
- **WHEN** close is called on an active instance and its sampling thread terminates
|
||||||
|
- **THEN** memory is cleared, the name reservation is released, and later start calls fail
|
||||||
|
|
||||||
|
#### Scenario: Close cannot confirm termination
|
||||||
|
- **WHEN** close cannot confirm that the sampling thread terminated
|
||||||
|
- **THEN** close reports failure and retains the name reservation
|
||||||
|
|
||||||
|
#### Scenario: Close is repeated
|
||||||
|
- **WHEN** close is called after the instance was successfully closed
|
||||||
|
- **THEN** it completes without changing state or failing
|
||||||
|
|
||||||
|
### Requirement: Evelyn uses externally provisioned per-instance storage
|
||||||
|
Each named instance SHALL use the existing regular file `data/evelyn/<normalized-name>/status.log`. Before every start, Evelyn SHALL require the direct instance path to be an existing readable and writable directory and `status.log` to be an existing readable and writable regular file. Evelyn SHALL NOT create, replace, repair, truncate, rename, or otherwise provision either path. Any validation or actual open failure SHALL identify the affected path, leave memory unpublished, and prevent sampling from starting.
|
||||||
|
|
||||||
|
#### Scenario: Required storage is absent or unsuitable
|
||||||
|
- **WHEN** the instance directory or status file is missing, the path has the wrong type, or required access is unavailable
|
||||||
|
- **THEN** start fails with the affected path and reason without modifying storage, publishing history, or starting sampling
|
||||||
|
|
||||||
|
#### Scenario: Storage changes while stopped
|
||||||
|
- **WHEN** storage becomes invalid after stop and the same instance is started again
|
||||||
|
- **THEN** the complete startup validation is repeated and restart fails without modifying storage
|
||||||
|
|
||||||
|
### Requirement: Evelyn validates status history format strictly
|
||||||
|
The UTF-8 `status.log` format SHALL have supported version `1`, declared by `FORMAT_VERSION=1` as the first actual entry; blank lines and full-line comments MAY precede it when loading, while an externally provisioned new file SHALL place it on the physical first line. Missing, duplicate, malformed, misplaced, or unsupported declarations SHALL fail loading before record parsing. After the declaration, blank lines and comments SHALL be ignored, inline comments MAY follow data, and every other entry SHALL be a valid record with an explicit stable numeric type code.
|
||||||
|
|
||||||
|
Type `1` SHALL encode `<UTC uuuuMMddHHmmssSSS'Z' timestamp>:1:<plain BigDecimal Evelyn Price Index>`. It SHALL have exactly three fields. Unknown types, invalid timestamps or decimals, missing or surplus fields, and decreasing timestamps SHALL fail the complete load; equal timestamps SHALL be accepted. A valid declaration with no records SHALL represent empty history. Applicable failures SHALL identify the file path, one-based line number, and original line, and Evelyn SHALL neither skip, sort, partially publish, nor repair invalid content.
|
||||||
|
|
||||||
|
#### Scenario: A valid versioned history loads
|
||||||
|
- **WHEN** the file has a valid first actual version declaration and valid type-1 records in non-decreasing timestamp order
|
||||||
|
- **THEN** Evelyn restores every record in file order with zero values for the five unimplemented indexes
|
||||||
|
|
||||||
|
#### Scenario: The format declaration is invalid
|
||||||
|
- **WHEN** the declaration is missing, duplicated, malformed, misplaced, or not version 1
|
||||||
|
- **THEN** startup fails before parsing records and reports useful file diagnostics
|
||||||
|
|
||||||
|
#### Scenario: A record is invalid
|
||||||
|
- **WHEN** a record has an unknown type, malformed field, wrong field count, or timestamp earlier than its predecessor
|
||||||
|
- **THEN** startup fails with path, line number, and original line without exposing partial history or altering the file
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Evelyn samples without overlapping or catch-up attempts
|
||||||
|
Each started Evelyn environment SHALL own its sampling executor, attempt its first sample only after complete storage validation and history restoration, and make subsequent attempts approximately one minute after the preceding attempt completes. Sampling SHALL remain active for the Evelyn lifecycle independently of whether Mission Control is open. Evelyn SHALL add exactly one timestamped point per successful attempt and SHALL isolate a failed or temporarily unavailable Ticker lookup or persistence operation without adding a fake or unpersisted point, terminating Evelyn, or cancelling the next attempt. Stopping Evelyn SHALL terminate its sampling executor.
|
||||||
|
|
||||||
|
#### Scenario: Initial Ticker data is unavailable
|
||||||
|
- **WHEN** the immediate sampling attempt cannot obtain a latest EVE/USDT price
|
||||||
|
- **THEN** no point is persisted or published, diagnostic context is logged, and the next fixed-delay attempt remains scheduled
|
||||||
|
|
||||||
|
#### Scenario: A later sample succeeds
|
||||||
|
- **WHEN** a later attempt obtains a latest EVE/USDT price whose millisecond timestamp is not earlier than the last persisted timestamp
|
||||||
|
- **THEN** exactly one type-1 record is durably appended before the corresponding point becomes visible in memory
|
||||||
|
|
||||||
|
#### Scenario: Persistence fails for one sample
|
||||||
|
- **WHEN** appending or durably flushing a calculated point fails
|
||||||
|
- **THEN** the point is not published, instance and path context are logged, and the next fixed-delay attempt remains scheduled
|
||||||
|
|
||||||
|
#### Scenario: A sampled timestamp goes backwards
|
||||||
|
- **WHEN** a newly sampled timestamp is earlier than the last persisted timestamp
|
||||||
|
- **THEN** no record or point is added, the failure is logged, and later attempts remain scheduled
|
||||||
|
|
||||||
|
### Requirement: Evelyn provides historical status-index measurements
|
||||||
|
Each named Evelyn instance SHALL publish a safe, non-null, oldest-to-newest snapshot only after its complete persisted history has been validated and loaded. Each type-1 point SHALL contain its persisted or sampled timestamp, the Evelyn Price Index, and `BigDecimal.ZERO` placeholders for EVE_SYRUP Pool Depth Index, EVE_SYRUP Pool Balance Index, AAZDKK_USDT Pool Balance Index, AAZDKK_USDT Pool Price Index, and AAZDKK_USDT Pool Depth Index. Production and Test SHALL use independently named files, history collections, reservations, and lifecycles. A sampled point SHALL be durably appended using robust line-boundary handling before publication. Stop SHALL clear only memory; restart SHALL restore the complete file, and close SHALL preserve the file.
|
||||||
|
|
||||||
|
#### Scenario: Status-index history is requested during sampling
|
||||||
|
- **WHEN** a caller requests an Evelyn environment's status-index history while persisted points may be appended
|
||||||
|
- **THEN** it receives a safe snapshot containing only completely persisted points and all six typed fields
|
||||||
|
|
||||||
|
#### Scenario: A process starts two named Evelyn environments
|
||||||
|
- **WHEN** Production and Test Evelyn instances start with independently provisioned files
|
||||||
|
- **THEN** each restores and extends only its own complete history even if calculated values are identical
|
||||||
|
|
||||||
|
#### Scenario: Existing file lacks a final line separator
|
||||||
|
- **WHEN** a valid existing status file ends with a record but no line separator
|
||||||
|
- **THEN** the next durable append first supplies a line boundary and preserves both records as distinct entries
|
||||||
|
|
||||||
|
#### Scenario: An Evelyn instance stops and restarts
|
||||||
|
- **WHEN** a named instance is stopped and later restarted
|
||||||
|
- **THEN** its memory is cleared at stop and its complete unchanged persistent history is revalidated and restored before sampling resumes
|
||||||
|
|
||||||
|
### Requirement: Evelyn Mission Control visualizes status-index measurements
|
||||||
|
Evelyn Mission Control SHALL display separate live Production and Test Overview charts exclusively from their corresponding authoritative Evelyn snapshots and SHALL never read or manage persistence directly. Each chart SHALL contain one enabled series named `Evelyn Price Index`; the five future series and their typed fields SHALL remain available as disabled source scaffolding. Opening or reopening EMC SHALL immediately reconstruct each chart from the complete restored current snapshot before periodically refreshing it. Successful points SHALL be added to chart objects only on the JavaFX Application Thread while the window remains open. Timestamp bounds SHALL expand as points arrive, and only enabled Evelyn Price Index values SHALL determine a dynamic Y-axis range that is symmetric around and always displays zero, using a reasonable default range for empty or all-zero history. Hiding or closing EMC SHALL release only EMC's JavaFX refresh resources and SHALL NOT start, stop, close, persist, or clear either Evelyn environment.
|
||||||
|
|
||||||
|
#### Scenario: Production and Test Overviews show restored history
|
||||||
|
- **WHEN** Mission Control opens after two named Evelyn environments have restored their histories
|
||||||
|
- **THEN** it displays separate one-series charts immediately populated from the corresponding complete snapshots
|
||||||
|
|
||||||
|
#### Scenario: A successful point appears while EMC is open
|
||||||
|
- **WHEN** an Evelyn history publishes a newly persisted point
|
||||||
|
- **THEN** the corresponding chart adds it on the JavaFX Application Thread and updates its timestamp and symmetric price-index bounds without reopening EMC
|
||||||
|
|
||||||
|
#### Scenario: EMC reopens after collecting hidden-window samples
|
||||||
|
- **WHEN** the same EMC instance is reopened after Evelyn persisted additional points while its window was hidden
|
||||||
|
- **THEN** each chart is reconstructed immediately from its environment's complete current history without missing points because of stale rendering state
|
||||||
|
|
||||||
|
#### Scenario: Future fields contain larger values
|
||||||
|
- **WHEN** any disabled future-index field has a greater absolute value than Evelyn Price Index
|
||||||
|
- **THEN** that disabled value does not affect the visible Y-axis bounds
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
## 1. Named Identity and Lifecycle
|
||||||
|
|
||||||
|
- [x] 1.1 Add NFC portable-name validation, direct-child path derivation, and atomic case-insensitive JVM-local reservation after all constructor validation.
|
||||||
|
- [x] 1.2 Extend the public Evelyn API with exception-free `AutoCloseable.close()` and implement restartable stop versus permanent idempotent close without releasing a possibly live writer.
|
||||||
|
- [x] 1.3 Update all required constructors and Production/Test composition to use independent named instances without enabling unrelated services.
|
||||||
|
|
||||||
|
## 2. Strict Storage Restoration
|
||||||
|
|
||||||
|
- [x] 2.1 Validate the externally provisioned per-instance directory and `status.log` type/access on every start without creating or modifying them.
|
||||||
|
- [x] 2.2 Validate dedicated status format version 1 as the first actual entry before parsing records, with path and declaration diagnostics.
|
||||||
|
- [x] 2.3 Strictly decode every type-1 record, reject unknown types and malformed field counts/values, enforce non-decreasing timestamps, and publish only a completely validated temporary history.
|
||||||
|
|
||||||
|
## 3. Durable Sampling Persistence
|
||||||
|
|
||||||
|
- [x] 3.1 Encode stable type-1 records with UTC millisecond timestamps and plain decimals, preserving blank/comment parsing semantics.
|
||||||
|
- [x] 3.2 Durably append before in-memory publication, including missing-final-line-separator handling and rejection of backward sampled timestamps.
|
||||||
|
- [x] 3.3 Isolate append/flush failures with instance, path, and sampling diagnostics so later fixed-delay attempts continue.
|
||||||
|
|
||||||
|
## 4. Verification and Review
|
||||||
|
|
||||||
|
- [x] 4.1 Compile main and test source sets without running or adding automated tests.
|
||||||
|
- [x] 4.2 Run strict validation for the active OpenSpec change and `git diff --check`.
|
||||||
|
- [x] 4.3 Review the complete diff for unrelated changes, behavioral gaps, generated Java edits, runtime data, and changes to Ticker/Raydium specifications or persistence.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-10
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. Issues #48 through #50 already established `SolanaBlockChain` for RPC-backed account and mint metadata, and `SolanaWallet` for isolated transaction signing. The Swap V2 integration must compose these APIs with Jupiter's `/order` and `/execute` endpoints while retaining module-oriented API and implementation package boundaries.
|
||||||
|
|
||||||
|
Jupiter's `/execute` manages transaction landing and can return actual wallet-level totals. Once an execution request has left this process, a transport failure cannot distinguish an unexecuted request from a completed swap whose response was lost.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Keep the public API expressed in human-readable ValueTagged amounts while using raw integer units at the Jupiter boundary.
|
||||||
|
- Validate all locally knowable invariants before wallet signing and execution submission.
|
||||||
|
- Share the required order pacing across every reference-implementation instance in one JVM.
|
||||||
|
- Preserve interruption and expose actionable endpoint and response failures through the declared checked exceptions.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Exact-output swaps, JupiterZ/RFQ, API-key configuration, retries, direct DEX integration, or a reusable throttling subsystem.
|
||||||
|
- Constructing an Evelyn burner or changing existing Solana wallet/blockchain responsibilities.
|
||||||
|
- Adding automated tests in this change; the existing repository verification suites will still be run.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Compose the established Solana APIs
|
||||||
|
|
||||||
|
`JupiterSwapServiceImpl` receives `SolanaBlockChain` and `SolanaWallet`. It obtains the wallet address from the wallet, loads each mint account and supply from the blockchain, and passes Jupiter's Base64 transaction through `SolanaWallet.signTransaction`. Duplicating RPC or signing logic inside the Jupiter implementation was rejected because it would bypass the module contracts established by issues #48 through #50.
|
||||||
|
|
||||||
|
### Detect each mint's token program independently
|
||||||
|
|
||||||
|
For both input and output, the implementation reads the mint account owner and matches it against `SolanaSPLTokenProgram`. It then requests supply metadata with that program. This supports legacy SPL Token and Token-2022 pairs in any combination and prevents assumptions based on one side of the pair.
|
||||||
|
|
||||||
|
### Use exact decimal conversion at the boundary
|
||||||
|
|
||||||
|
The requested `BigDecimal` is shifted by the input mint's decimal count and converted with `toBigIntegerExact()`. Actual raw execution totals are parsed as non-negative integers and shifted left with their respective decimal counts. Rounding was rejected because it would silently change the amount authorized by the caller.
|
||||||
|
|
||||||
|
### Keep wire types private to the reference implementation
|
||||||
|
|
||||||
|
Small private records model only the `/order` and `/execute` fields needed for validation and results. Jackson ignores additional response fields, allowing Jupiter to add metadata without expanding the public API. The stable API package contains only `JupiterSwapService` and `JupiterSwapResult`.
|
||||||
|
|
||||||
|
### Use the keyless Swap V2 endpoint and explicitly exclude JupiterZ
|
||||||
|
|
||||||
|
The reference implementation calls `https://api.jup.ag/swap/v2/order` and `/execute` without API-key headers. Every order includes `swapMode=ExactIn` and `excludeRouters=jupiterz`; relying on a router default was rejected because JupiterZ/RFQ support is explicitly deferred to issue #66.
|
||||||
|
|
||||||
|
### Serialize only order-request starts with a JVM-wide gate
|
||||||
|
|
||||||
|
A private static monitor and monotonic timestamp separate request starts by two seconds. Callers wait interruptibly while holding the gate, and the gate remains held for that order exchange so a delayed thread cannot begin out of its reserved sequence. A generalized limiter was rejected as unnecessary scope, and `/execute` bypasses this gate.
|
||||||
|
|
||||||
|
### Treat execution submission as a non-retry boundary
|
||||||
|
|
||||||
|
The implementation performs one `HttpClient.send` for `/execute` and contains no retry loop around it or the whole swap. Any failure after submission propagates, with a message warning that execution may be unknown and balances must be reloaded. Retrying was rejected because it can duplicate a financially consequential action.
|
||||||
|
|
||||||
|
### Validate response integrity in phases
|
||||||
|
|
||||||
|
Mint and amount checks occur before `/order`; order identity, taker, slippage, Base64 transaction content, and transaction metadata checks occur before signing; execution status and explicit result code, signature, and actual totals are checked before constructing the public result. Numeric wire fields that must distinguish an absent value from zero use nullable DTO types. Non-2xx responses retain status and body context, while malformed JSON is wrapped as `IOException`.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Jupiter may change its wire schema or endpoint behavior] → Decode a minimal tolerant DTO set, but strictly validate every field used for signing and result construction.
|
||||||
|
- [A submitted execution can succeed despite a local timeout or interruption] → Never retry and report the outcome as unknown so callers reload balances before deciding what to do.
|
||||||
|
- [A slow order exchange serializes later order callers] → This intentionally small implementation-specific gate prioritizes strict JVM-wide start spacing and remains completely separate from execution requests.
|
||||||
|
- [Keyless service availability or limits can change] → Surface HTTP response status/body clearly; API-key configuration remains intentionally out of scope.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
This is an additive API and implementation. Downstream composition can instantiate the reference implementation with its existing blockchain and wallet instances. Rollback consists of removing the new package because no existing API or persisted data is changed.
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Nenjim needs a reusable service that can exchange an exact, human-readable SPL-token amount through Jupiter while preserving the repository's existing Solana wallet and blockchain boundaries. Jupiter Swap V2 supplies the quote, transaction construction, and managed execution required for this flow.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add a public `JupiterSwapService` API and immutable `JupiterSwapResult` value describing the confirmed transaction and actual amounts spent and received.
|
||||||
|
- Add a reference implementation for exact-input Jupiter Swap V2 `/order`, wallet signing, and `/execute` processing.
|
||||||
|
- Resolve both mint programs and decimal precision through `SolanaBlockChain`, supporting the legacy SPL Token Program and Token-2022.
|
||||||
|
- Validate caller input, mint metadata, order integrity, transaction metadata, and execution results before returning success.
|
||||||
|
- Exclude the `jupiterz` router from every order while JupiterZ/RFQ support remains deferred.
|
||||||
|
- Enforce a small JVM-wide two-second minimum interval between `/order` requests, without delaying `/execute` or automatically retrying submitted executions.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `jupiter-swap-service`: Exact-input SPL-token swaps through Jupiter Swap V2, including validation, signing, managed execution, throttling, and failure semantics.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
This adds public API types under `com.r35157.libs.jupiter.swap` and a reference implementation under `com.r35157.libs.jupiter.swap.impl.ref`. It depends on the existing `SolanaBlockChain` and `SolanaWallet` APIs and Jupiter's keyless Swap V2 HTTP endpoints; it does not add API-key configuration, Evelyn burning, RFQ/JupiterZ support, or a general throttling framework.
|
||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Defines safe exact-input SPL-token swaps through Jupiter Swap V2 using human-readable amounts and an existing Solana wallet.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Public exact-input swap contract
|
||||||
|
The service SHALL accept distinct input and output SPL mint addresses, a positive human-readable input amount, and a maximum slippage in basis points. It SHALL return the confirmed Solana transaction signature together with the actual human-readable input amount spent and output amount received.
|
||||||
|
|
||||||
|
#### Scenario: Successful exact-input swap
|
||||||
|
- **WHEN** a caller requests a valid exact-input swap that Jupiter executes successfully
|
||||||
|
- **THEN** the result contains the confirmed signature and the actual spent and received amounts converted with their respective mint decimal precision
|
||||||
|
|
||||||
|
#### Scenario: Invalid caller input
|
||||||
|
- **WHEN** either mint is blank, both mints are equal, the amount is null or non-positive, or the maximum slippage is outside 0 through 10000 basis points
|
||||||
|
- **THEN** the service rejects the request before requesting a Jupiter order
|
||||||
|
|
||||||
|
### Requirement: On-chain mint validation and exact amount conversion
|
||||||
|
Before requesting an order, the service SHALL load both mint accounts through the Solana blockchain, require each mint to exist and be owned by either the legacy SPL Token Program or Token-2022, and resolve each mint's decimal precision. The service SHALL convert the input amount exactly to a positive raw integer and SHALL reject values that cannot be represented with the input mint's precision.
|
||||||
|
|
||||||
|
#### Scenario: Supported legacy and Token-2022 mints
|
||||||
|
- **WHEN** the input and output mint accounts are owned by either supported SPL token program and their supplies provide valid decimal precision
|
||||||
|
- **THEN** the service uses those independently resolved precisions for raw request and human-readable result amounts
|
||||||
|
|
||||||
|
#### Scenario: Missing or unsupported mint
|
||||||
|
- **WHEN** either mint account is absent or is owned by an unsupported program
|
||||||
|
- **THEN** the service fails before requesting an order or signing a transaction
|
||||||
|
|
||||||
|
#### Scenario: Fraction smaller than the mint unit
|
||||||
|
- **WHEN** the requested input amount has a non-zero fraction beyond the input mint's decimal precision
|
||||||
|
- **THEN** the service rejects the amount rather than rounding it
|
||||||
|
|
||||||
|
### Requirement: Safe Jupiter order acquisition
|
||||||
|
The service SHALL use Jupiter's keyless Swap V2 order endpoint in `ExactIn` mode with the wallet as taker, the caller's slippage limit, and `excludeRouters=jupiterz`. Across all reference-implementation instances in one JVM, starts of order HTTP requests SHALL be separated by at least two seconds. This limiter SHALL remain local to the implementation and SHALL NOT delay execution requests.
|
||||||
|
|
||||||
|
#### Scenario: Every order excludes JupiterZ
|
||||||
|
- **WHEN** the service requests an order
|
||||||
|
- **THEN** the request identifies the exact input and output mints, raw input amount, wallet taker, exact-input mode, slippage limit, and excludes the `jupiterz` router
|
||||||
|
|
||||||
|
#### Scenario: Concurrent service instances request orders
|
||||||
|
- **WHEN** multiple reference-implementation instances concurrently need Jupiter orders
|
||||||
|
- **THEN** their order HTTP requests begin at least two seconds apart JVM-wide while execution requests remain unthrottled
|
||||||
|
|
||||||
|
#### Scenario: Order wait is interrupted
|
||||||
|
- **WHEN** a caller is interrupted while waiting for its permitted order-request time
|
||||||
|
- **THEN** the service propagates interruption without sending that order request
|
||||||
|
|
||||||
|
### Requirement: Order integrity validation before signing
|
||||||
|
Before signing, the service SHALL require a successful HTTP response containing a well-formed order whose input mint, output mint, raw input amount, exact-input mode, and taker match the request. The returned slippage SHALL be present, valid, and no greater than the caller's maximum. The service SHALL also require a non-blank, valid Base64 unsigned transaction that decodes to at least one byte, a non-blank request identifier, and a positive valid last block height. Any order build error, malformed body, mismatch, or missing required value SHALL fail before signing.
|
||||||
|
|
||||||
|
#### Scenario: Valid matching order
|
||||||
|
- **WHEN** Jupiter returns a well-formed order matching both requested mints, the exact raw input amount, wallet taker, and permitted slippage with all required transaction metadata
|
||||||
|
- **THEN** the service passes the returned unsigned transaction to the configured Solana wallet for signing
|
||||||
|
|
||||||
|
#### Scenario: Mismatching or incomplete order
|
||||||
|
- **WHEN** an order changes a mint, amount, swap mode, or taker; returns missing, invalid, or excessive slippage; or lacks a valid non-empty Base64 transaction, request identifier, or last valid block height
|
||||||
|
- **THEN** the service rejects the order without signing or executing it
|
||||||
|
|
||||||
|
#### Scenario: Jupiter order HTTP or decoding failure
|
||||||
|
- **WHEN** the order endpoint returns a non-success status or malformed JSON
|
||||||
|
- **THEN** the service reports an I/O failure with useful endpoint response context and does not sign a transaction
|
||||||
|
|
||||||
|
### Requirement: Single-attempt managed execution
|
||||||
|
After successful signing, the service SHALL reject a blank signed transaction and submit exactly one Swap V2 execution request containing the signed transaction, order request identifier, and last valid block height. The service SHALL never automatically retry after the execution request has been submitted, because transport failure, timeout, or interruption can leave the execution outcome unknown.
|
||||||
|
|
||||||
|
#### Scenario: Signed transaction is executed once
|
||||||
|
- **WHEN** wallet signing returns a non-blank transaction
|
||||||
|
- **THEN** the service sends one execution request without applying the order limiter
|
||||||
|
|
||||||
|
#### Scenario: Execution outcome is unknown
|
||||||
|
- **WHEN** the execution HTTP exchange times out, is interrupted, loses its response, or otherwise fails after submission
|
||||||
|
- **THEN** the service reports the failure and does not automatically request another order or resubmit execution
|
||||||
|
|
||||||
|
#### Scenario: Blank signed transaction
|
||||||
|
- **WHEN** wallet signing returns a blank serialized transaction
|
||||||
|
- **THEN** the service fails without submitting an execution request
|
||||||
|
|
||||||
|
### Requirement: Confirmed execution result validation
|
||||||
|
The service SHALL return success only when the execution response has status `Success`, explicitly has result code `0`, contains a non-blank transaction signature, and contains positive valid actual total input and output raw amounts. It SHALL convert each actual amount using the independently resolved precision of its mint. Non-success HTTP responses, malformed responses, expired or rejected swaps, failed or contradictory status/code combinations, missing result codes, blank signatures, and invalid result amounts SHALL be reported as failures.
|
||||||
|
|
||||||
|
#### Scenario: Successful execution response
|
||||||
|
- **WHEN** Jupiter reports status `Success`, code `0`, a signature, and valid actual total input and output amounts
|
||||||
|
- **THEN** the service returns those actual amounts in human-readable units and the reported transaction signature
|
||||||
|
|
||||||
|
#### Scenario: Jupiter rejects or fails execution
|
||||||
|
- **WHEN** Jupiter returns a failed status, a missing or non-zero result code, a contradictory status/code combination, expiration, rejection, non-success HTTP status, malformed body, blank signature, or invalid actual amount
|
||||||
|
- **THEN** the service reports an I/O failure and does not represent the swap as successful
|
||||||
|
|
||||||
|
### Requirement: API and implementation separation
|
||||||
|
The stable service interface and result value SHALL reside in the Jupiter swap API package, while HTTP DTOs, endpoint handling, throttling, and the reference implementation SHALL remain in the reference-implementation package.
|
||||||
|
|
||||||
|
#### Scenario: Downstream API consumer
|
||||||
|
- **WHEN** downstream code depends only on the Jupiter swap API package
|
||||||
|
- **THEN** it can invoke swaps and inspect results without depending on private wire DTOs or reference-implementation mechanics
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
## 1. Public API
|
||||||
|
|
||||||
|
- [x] 1.1 Add the ValueTagged `JupiterSwapService` exact-input method with complete public Javadoc and validation/failure contracts.
|
||||||
|
- [x] 1.2 Add the immutable `JupiterSwapResult` value containing the signature and actual human-readable amounts.
|
||||||
|
|
||||||
|
## 2. Reference Implementation
|
||||||
|
|
||||||
|
- [x] 2.1 Add the reference implementation with injected `SolanaBlockChain` and `SolanaWallet` dependencies and private Swap V2 wire DTOs.
|
||||||
|
- [x] 2.2 Validate arguments, both on-chain mint accounts and programs, mint decimal metadata, and exact raw input conversion before order acquisition.
|
||||||
|
- [x] 2.3 Implement keyless `/order` acquisition in exact-input mode, always excluding JupiterZ, with JVM-wide interruptible two-second request-start pacing.
|
||||||
|
- [x] 2.4 Validate order identity and transaction metadata before signing through `SolanaWallet`.
|
||||||
|
- [x] 2.5 Submit a signed transaction once through `/execute`, without retries or order throttling, and validate the confirmed result and actual amounts.
|
||||||
|
|
||||||
|
## 3. Verification
|
||||||
|
|
||||||
|
- [x] 3.1 Compile Detag-generated main and test source sets and run the repository's existing test/check tasks without adding automated tests.
|
||||||
|
- [x] 3.2 Run strict OpenSpec validation and `git diff --check`.
|
||||||
|
- [x] 3.3 Review the complete diff for stale imports, generated-file edits, unrelated refactors, tests, and any implementation that violates the module or retry boundaries.
|
||||||
|
|
||||||
|
## 4. Review Follow-up Validation
|
||||||
|
|
||||||
|
- [x] 4.1 Validate the returned taker and nullable slippage before signing.
|
||||||
|
- [x] 4.2 Base64-decode the returned transaction and reject invalid or empty decoded content before signing.
|
||||||
|
- [x] 4.3 Require both execution status `Success` and an explicit result code `0`.
|
||||||
|
- [x] 4.4 Re-run compilation, Detag, existing tests, strict OpenSpec validation, and diff checks without adding tests.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-11
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. `SolanaBlockChainImpl` currently serializes every RPC request through a synchronized `sendThrottled()` method, sleeps until five seconds have elapsed since the preceding RPC response, performs HTTP while holding the monitor, and records completion time in `finally`. Monitor acquisition is neither interruptible nor deadline-aware, while issue #67 requires both without changing existing RPC behavior.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Preserve one shared five-second gate for all RPC calls while allowing awaiting calls to stop at a monotonic deadline.
|
||||||
|
- Keep definitive transaction state, timeout, protocol failure, and interruption as distinct outcomes.
|
||||||
|
- Strictly validate all wire data that controls a returned outcome.
|
||||||
|
- Keep the API and outcome types generic to Solana rather than any consuming domain.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Combining submission and awaiting, resubmission, WebSockets, signature subscriptions, or blockhash-expiration inference.
|
||||||
|
- Updating any existing wallet, Jupiter, burn, Evelyn, or Discord consumer.
|
||||||
|
- Adding automated tests or test-only transport, time, or throttling seams in this implementation.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Replace the intrinsic monitor with one interruptible shared lock
|
||||||
|
|
||||||
|
A private `ReentrantLock` will serialize all RPC traffic. Existing calls acquire it interruptibly, perform the same five-second wait and HTTP exchange, update the shared completion timestamp in `finally`, and release it. Awaiting calls use timed `tryLock` with their remaining deadline. A separate polling lock or rate limiter was rejected because it would violate the shared throttle and permit polling to interfere unpredictably with other calls.
|
||||||
|
|
||||||
|
### Measure shared throttling and awaiting deadlines monotonically
|
||||||
|
|
||||||
|
The completion timestamp and deadline calculations use `System.nanoTime()`. A small private deadline value tracks the call's start and saturated timeout nanoseconds and computes remaining time by elapsed duration. Wall-clock time was rejected because clock adjustments could extend or shorten both throttling and the caller's timeout.
|
||||||
|
|
||||||
|
### Build each status HTTP request only after gate and throttle admission
|
||||||
|
|
||||||
|
The deadline-aware send path acquires the gate and waits only up to the remaining deadline. Once admitted, it creates a request whose `HttpRequest.timeout` equals the then-current remaining duration. A request-timeout exception is translated to `TIMED_OUT`; other I/O failures remain `IOException`. Building the request after admission avoids assigning the full original timeout to an HTTP exchange that starts after gate/throttle delay.
|
||||||
|
|
||||||
|
### Return an internal no-request signal on deadline expiry
|
||||||
|
|
||||||
|
The deadline-aware send helper returns no response when the deadline expires before request start. The public loop converts this only to the invariant `TIMED_OUT` value. This keeps deadline expiry separate from communication/protocol exceptions and ensures a previously seen lower-commitment slot cannot leak into the timeout result.
|
||||||
|
|
||||||
|
### Parse only authoritative signature-status fields
|
||||||
|
|
||||||
|
Each response must contain a successful HTTP result, no JSON-RPC error, and exactly one `result.value` element. A null element remains inconclusive. A status object requires a Long-representable non-negative `slot` and recognized `confirmationStatus`; only after its commitment satisfies the request is `err` interpreted as success or failure. `result.context`, `confirmations`, and legacy `status` are ignored.
|
||||||
|
|
||||||
|
### Preserve the complete on-chain error as compact JSON
|
||||||
|
|
||||||
|
Jackson's compact serialization of the non-null `err` node becomes `failureDetails`. The blockchain layer does not classify program-specific errors. Any issue outside this `err` node remains an exception or timeout rather than `FAILED`.
|
||||||
|
|
||||||
|
### Validate transaction signatures with the existing Base58 implementation
|
||||||
|
|
||||||
|
The reference implementation reuses its existing private Base58 decoder and requires exactly 64 decoded bytes before entering the RPC gate. Introducing a second codec dependency or general transaction parser was rejected as unrelated scope.
|
||||||
|
|
||||||
|
### Make cached awaiting a transparent pass-through
|
||||||
|
|
||||||
|
`CachedSolanaBlockChain.awaitTransaction` calls its delegate directly. Transaction status is deadline-sensitive and mutable, so cache keys, reuse, and in-flight deduplication would violate the contract.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [An HTTP client can complete at the edge of its timeout after the monotonic deadline] → Use the remaining deadline as the request timeout and re-check the deadline before interpreting an inconclusive response; this is the strongest practical bound offered by Java `HttpClient`.
|
||||||
|
- [Replacing synchronization changes the internal locking primitive] → Keep the same lock scope, completion-based five-second spacing, and exception behavior for every existing RPC operation.
|
||||||
|
- [No automated deterministic coverage is added now] → Keep deadline, parsing, and gate concerns in narrow private methods and document that focused tests remain deferred by explicit task instruction.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
This is additive at the public API. Existing implementations in this repository are updated together, while existing consumers remain unchanged. Rollback removes the new method/types and restores the prior private monitor without data migration.
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Submitting a Solana transaction only proves RPC acceptance, leaving callers unable to distinguish a requested on-chain commitment outcome from an unknown pending or dropped transaction. A generic wait operation is needed so future consumers can explicitly await processed, confirmed, or finalized outcomes without coupling submission to confirmation.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add public Solana commitment and transaction-outcome types with strict status invariants and comprehensive timeout semantics.
|
||||||
|
- Add `SolanaBlockChain.awaitTransaction(...)` to poll `getSignatureStatuses` for an already submitted signature.
|
||||||
|
- Reuse the existing shared five-second RPC throttle while making gate acquisition interruptible and bounded by an overall monotonic deadline.
|
||||||
|
- Validate signatures and timeouts locally, strictly validate RPC responses, and reserve `FAILED` exclusively for on-chain execution errors observed at the requested commitment.
|
||||||
|
- Delegate every cached-wrapper call directly without caching or deduplication.
|
||||||
|
- Add the `SolanaSlot` ValueTag backed by nullable `Long`.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `solana-transaction-awaiting`: Generic blocking observation of an existing Solana transaction through a requested commitment level, with definitive success/failure and unknown-timeout semantics.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The Solana public API, reference RPC implementation, cached decorator, and shared Detag configuration are extended. Existing RPC operations retain their public behavior and common throttling; no wallet, Jupiter, burn, Evelyn, WebSocket, or transaction-submission consumer is changed to wait automatically.
|
||||||
+96
@@ -0,0 +1,96 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Defines how callers await a definitive Solana transaction outcome at an explicitly requested commitment without resubmitting the transaction.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Separate transaction awaiting API
|
||||||
|
The Solana blockchain API SHALL expose a blocking operation that accepts an existing 64-byte Base58 transaction signature, a requested commitment of `PROCESSED`, `CONFIRMED`, or `FINALIZED`, and a strictly positive overall timeout. Submission and awaiting SHALL remain separate operations, and existing consumers SHALL NOT begin awaiting automatically.
|
||||||
|
|
||||||
|
#### Scenario: Valid await request
|
||||||
|
- **WHEN** a caller supplies a valid signature, non-null commitment, and positive timeout
|
||||||
|
- **THEN** the service observes that already submitted transaction without submitting or resubmitting it
|
||||||
|
|
||||||
|
#### Scenario: Invalid arguments
|
||||||
|
- **WHEN** the signature is null, blank, invalid Base58, or does not decode to exactly 64 bytes, the commitment is null, or the timeout is null, zero, or negative
|
||||||
|
- **THEN** the service rejects the call locally before making an RPC request
|
||||||
|
|
||||||
|
### Requirement: Commitment-aware definitive outcomes
|
||||||
|
The commitment order SHALL be `PROCESSED < CONFIRMED < FINALIZED`, and an observed level SHALL satisfy the same or any lower requested level. The service SHALL return `SUCCEEDED` or `FAILED` only after the observed transaction has reached or exceeded the requested commitment. `FAILED` SHALL exclusively represent a non-null Solana on-chain `err`; lower-commitment success or error observations SHALL remain inconclusive.
|
||||||
|
|
||||||
|
#### Scenario: Success reaches requested commitment
|
||||||
|
- **WHEN** the transaction is observed at or above the requested commitment with `err: null`
|
||||||
|
- **THEN** the service returns `SUCCEEDED` with the transaction slot and no failure details
|
||||||
|
|
||||||
|
#### Scenario: Failure reaches requested commitment
|
||||||
|
- **WHEN** the transaction is observed at or above the requested commitment with a non-null `err`
|
||||||
|
- **THEN** the service returns `FAILED` with the transaction slot and the complete `err` value as compact JSON
|
||||||
|
|
||||||
|
#### Scenario: Observation is below requested commitment
|
||||||
|
- **WHEN** a successful or failed transaction is observed below the requested commitment
|
||||||
|
- **THEN** the service continues polling rather than returning a definitive outcome
|
||||||
|
|
||||||
|
### Requirement: Transaction outcome invariants
|
||||||
|
Every public transaction outcome SHALL enforce that `SUCCEEDED` has a non-null slot and null failure details, `FAILED` has a non-null slot and non-blank failure details, and `TIMED_OUT` has null slot and null failure details.
|
||||||
|
|
||||||
|
#### Scenario: Invalid outcome construction
|
||||||
|
- **WHEN** an outcome is constructed with fields inconsistent with its status
|
||||||
|
- **THEN** construction fails immediately
|
||||||
|
|
||||||
|
### Requirement: Signature status polling contract
|
||||||
|
The service SHALL poll HTTP JSON-RPC `getSignatureStatuses` with exactly the supplied signature and `searchTransactionHistory: true`. It SHALL use only `result.value[0].slot`, `err`, and `confirmationStatus`; a null sole value SHALL remain inconclusive. A present status SHALL contain a Long-representable slot and one of `processed`, `confirmed`, or `finalized`.
|
||||||
|
|
||||||
|
#### Scenario: Signature is not found yet
|
||||||
|
- **WHEN** `result.value` contains exactly one null element
|
||||||
|
- **THEN** the service continues polling until a definitive outcome or timeout
|
||||||
|
|
||||||
|
#### Scenario: Valid status is observed
|
||||||
|
- **WHEN** the sole status contains a valid slot, recognized confirmation status, and `err` value
|
||||||
|
- **THEN** the service evaluates it against the requested commitment
|
||||||
|
|
||||||
|
### Requirement: Overall monotonic deadline
|
||||||
|
The timeout SHALL define one monotonic deadline covering RPC-gate acquisition, shared throttling, HTTP calls, response processing, and subsequent polls. Gate and throttle waiting SHALL be interruptible and deadline-aware, no request SHALL start after the deadline, and an HTTP request SHALL as far as reasonably possible be bounded by the remaining time.
|
||||||
|
|
||||||
|
#### Scenario: Deadline expires before RPC access
|
||||||
|
- **WHEN** the deadline expires while waiting for the shared gate or remaining throttle interval
|
||||||
|
- **THEN** the service returns `TIMED_OUT` without starting another RPC request
|
||||||
|
|
||||||
|
#### Scenario: Deadline expires during HTTP request
|
||||||
|
- **WHEN** the deadline-bound status HTTP request times out
|
||||||
|
- **THEN** the service returns `TIMED_OUT`
|
||||||
|
|
||||||
|
#### Scenario: Caller is interrupted
|
||||||
|
- **WHEN** interruption occurs during gate acquisition, throttling, HTTP communication, or another internal wait
|
||||||
|
- **THEN** `InterruptedException` propagates directly and no subsequent request is made
|
||||||
|
|
||||||
|
### Requirement: Unknown timeout semantics
|
||||||
|
`TIMED_OUT` SHALL mean the requested definitive outcome remains unknown and SHALL NOT imply rejection, failure, expiration, or permission to resubmit. It SHALL not retain a slot previously observed below the requested commitment.
|
||||||
|
|
||||||
|
#### Scenario: Deadline passes without definitive outcome
|
||||||
|
- **WHEN** the signature remains unseen or below the requested commitment until the deadline
|
||||||
|
- **THEN** the service returns `TIMED_OUT` with null slot and null failure details and does not resubmit the transaction
|
||||||
|
|
||||||
|
### Requirement: Shared RPC throttling
|
||||||
|
Status polling SHALL reuse the same shared mechanism that preserves at least five seconds between Solana RPC calls. It SHALL add no independent polling sleep, interval, or limiter, and existing RPC operations SHALL preserve their public behavior and common throttling.
|
||||||
|
|
||||||
|
#### Scenario: Inconclusive poll
|
||||||
|
- **WHEN** a status response is inconclusive and time remains
|
||||||
|
- **THEN** the next poll is naturally delayed by the shared five-second RPC throttle only
|
||||||
|
|
||||||
|
### Requirement: Communication and protocol errors
|
||||||
|
Non-success HTTP responses, JSON-RPC errors, invalid JSON, missing or invalid `result.value`, arrays with other than exactly one element, invalid slots, missing or unknown confirmation status, and other network or protocol failures SHALL result in `IOException`, not `FAILED`. Error messages SHALL identify `getSignatureStatuses` and include relevant context. The service SHALL NOT automatically retry after an `IOException`.
|
||||||
|
|
||||||
|
#### Scenario: Invalid RPC response
|
||||||
|
- **WHEN** a status response violates the required HTTP, JSON-RPC, result-array, slot, or confirmation-status contract
|
||||||
|
- **THEN** the service throws a contextual `IOException`
|
||||||
|
|
||||||
|
#### Scenario: Communication failure
|
||||||
|
- **WHEN** status communication fails for a reason other than expiration of the overall deadline or thread interruption
|
||||||
|
- **THEN** the service throws `IOException` without automatically polling again
|
||||||
|
|
||||||
|
### Requirement: Cached decorator delegates directly
|
||||||
|
The cached Solana blockchain decorator SHALL delegate every transaction-await call directly to its underlying blockchain without caching, reuse, or deduplication.
|
||||||
|
|
||||||
|
#### Scenario: Repeated awaits through cached decorator
|
||||||
|
- **WHEN** callers invoke transaction awaiting multiple times through the cached decorator
|
||||||
|
- **THEN** every invocation reaches the delegate independently
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
## 1. Public Solana API
|
||||||
|
|
||||||
|
- [x] 1.1 Add the nullable-Long `SolanaSlot` ValueTag through the shared Detag configuration.
|
||||||
|
- [x] 1.2 Add documented `SolanaCommitment` and invariant-enforcing `SolanaTransactionOutcome` public types.
|
||||||
|
- [x] 1.3 Add the documented `awaitTransaction(...)` contract to `SolanaBlockChain`.
|
||||||
|
|
||||||
|
## 2. RPC Gate and Polling
|
||||||
|
|
||||||
|
- [x] 2.1 Replace intrinsic RPC synchronization with one interruptible shared gate while preserving completion-based five-second throttling for existing operations.
|
||||||
|
- [x] 2.2 Add local signature, commitment, and timeout validation plus a monotonic overall deadline.
|
||||||
|
- [x] 2.3 Implement deadline-aware `getSignatureStatuses` polling with `searchTransactionHistory: true` and no independent polling delay.
|
||||||
|
- [x] 2.4 Strictly validate HTTP, JSON-RPC, result-array, slot, confirmation-status, and error payload semantics.
|
||||||
|
- [x] 2.5 Return definitive outcomes only at the requested commitment and preserve direct interruption, unknown timeout, and no-retry behavior.
|
||||||
|
|
||||||
|
## 3. Cached Decorator and Scope
|
||||||
|
|
||||||
|
- [x] 3.1 Delegate cached-wrapper awaits directly without caching, reuse, or deduplication.
|
||||||
|
- [x] 3.2 Confirm no existing wallet, Jupiter, burn, Evelyn, Discord, or submission flow adopts automatic awaiting.
|
||||||
|
|
||||||
|
## 4. Verification
|
||||||
|
|
||||||
|
- [x] 4.1 Compile main and test source sets with normal Detag generation and run existing relevant verification without adding tests.
|
||||||
|
- [x] 4.2 Run strict OpenSpec validation and `git diff --check`.
|
||||||
|
- [x] 4.3 Review the complete diff for protocol gaps, deadline violations, generated-file edits, new tests, and unrelated changes.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-11
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
Issue #67 introduced a generic, blocking `SolanaBlockChain.awaitTransaction()` operation with explicit commitment, timeout, and outcome semantics. Jupiter Swap and Perps already own their respective build, validation, signing, and single provider-submission flows, so they are the correct boundaries for adding independent Solana confirmation. Their public APIs and implementations are already package-separated even though they currently share this repository.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Add one consistent Jupiter-level representation for definitive on-chain failure and unknown timeout.
|
||||||
|
- Keep provider-response validation visibly separate from the subsequent independent Solana confirmation.
|
||||||
|
- Preserve existing constructors and operation return types while making confirmation timeout an injectable high-level policy.
|
||||||
|
- Keep increase and decrease confirmation behavior identical and keep alarm reporting outcome-aware.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Changing blockchain awaiting, wallet submission, transaction construction, signing, Jupiter provider submission, or financial validation.
|
||||||
|
- Adding retries, resubmission, persistent reconciliation, configuration changes, or automated tests.
|
||||||
|
- Integrating confirmation into burn, Evelyn, Discord, or other consumers.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Put the outcome exception in the common Jupiter API package
|
||||||
|
|
||||||
|
`JupiterTransactionOutcomeException` will be a checked public exception in `com.r35157.libs.jupiter`, shared by Swap and Perps without making either API depend on the other. It will defensively validate and retain the signature, requested commitment, and complete `SolanaTransactionOutcome`. Its message will derive definitive-failure or unknown-timeout diagnostics from that structured state.
|
||||||
|
|
||||||
|
An exception local to each service was rejected because it would duplicate identical cross-provider outcome semantics and prevent callers such as alarm actions from applying one policy.
|
||||||
|
|
||||||
|
### Confirm only after complete provider validation
|
||||||
|
|
||||||
|
Swap will build its `JupiterSwapResult` only after all existing `/execute` status, code, signature, and actual-amount checks pass, then await the result signature. Perps will await only after its existing execution response yields a valid non-blank `txid`. This maintains two explicit trust boundaries: Jupiter validates its response first; Solana independently establishes the on-chain outcome second.
|
||||||
|
|
||||||
|
Treating provider success as implicit confirmation was rejected because it is the gap this change closes. Replacing provider validation with Solana confirmation was rejected because on-chain status cannot validate Jupiter's reported token amounts or response contract.
|
||||||
|
|
||||||
|
### Keep timeout policy immutable in each service instance
|
||||||
|
|
||||||
|
Each implementation will store a validated `Duration`. Existing constructors delegate to overloads using a two-minute constant. The duration is passed unchanged to each `awaitTransaction()` call at `CONFIRMED`, so pre-confirmation work does not consume it.
|
||||||
|
|
||||||
|
A configuration-file setting was rejected because issue #68 deliberately defines constructor policy only. A timeout in wallet or blockchain submission was rejected because confirmation belongs to the high-level operation.
|
||||||
|
|
||||||
|
### Centralize confirmation policy at the smallest useful scope
|
||||||
|
|
||||||
|
Perps increase and decrease will call one private helper that invokes `awaitTransaction()` and switches on all three outcomes. Swap will use its own equivalent private helper because the implementations are separate future artifacts; sharing implementation mechanics across them would create an unnecessary coupling.
|
||||||
|
|
||||||
|
Both helpers wrap only confirmation `IOException`s with operation, signature, and unknown-outcome context, preserve the cause, allow `InterruptedException` to propagate directly, and never retry. This avoids accidentally changing existing pre-signature/provider error behavior.
|
||||||
|
|
||||||
|
### Keep alarms presentation-only for confirmation results
|
||||||
|
|
||||||
|
The two alarm actions will catch `JupiterTransactionOutcomeException` before their generic error handling and format `FAILED` and `TIMED_OUT` separately. They will not call the blockchain API or attempt reconciliation, preserving the Jupiter service as owner of complete operation orchestration.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [A provider submission can succeed while confirmation communication fails] → Preserve the known signature, report the on-chain outcome as unknown, retain the cause, and prohibit automatic resubmission.
|
||||||
|
- [Synchronous confirmation extends operation latency] → Use an immutable configurable timeout with a safe two-minute default and preserve interruption.
|
||||||
|
- [Alarm reporting is transient and not reconciled] → Clearly label timeout as unknown; persistent reconciliation remains explicitly deferred.
|
||||||
|
- [Checked exception propagation affects callers at compile time] → Update both public service contracts and every affected call site in this repository consistently.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Add the common checked exception and update both public service contracts.
|
||||||
|
2. Add compatible implementation constructor overloads and confirmation helpers.
|
||||||
|
3. Update alarm actions for structured outcome reporting.
|
||||||
|
4. Compile main and test source sets, run available non-live checks, and retain the active OpenSpec change for review.
|
||||||
|
|
||||||
|
Rollback consists of reverting this change as one unit; no data or configuration migration is involved.
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Jupiter's provider responses currently end Swap and Perps operations without independently confirming the returned transaction signature on Solana. The shared `SolanaBlockChain.awaitTransaction()` API now makes it possible for these high-level operations to report success only after `CONFIRMED` on-chain success while preserving definitive failure and unknown-timeout outcomes.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add a shared checked Jupiter outcome exception that preserves the submitted signature, requested commitment, and complete failed or timed-out Solana outcome.
|
||||||
|
- Make Jupiter Swap independently await its validated execution signature at `CONFIRMED` before returning its existing result.
|
||||||
|
- Make Jupiter Perps increase and decrease independently await their valid execution signatures at `CONFIRMED` through one common private policy helper.
|
||||||
|
- Add validated constructor-injected confirmation timeouts while preserving existing constructors with a two-minute default.
|
||||||
|
- Preserve Jupiter provider-response validation as a distinct prerequisite to Solana confirmation, including Swap actual-amount validation.
|
||||||
|
- Add outcome-specific Perps alarm reporting without moving confirmation into alarm actions or introducing retries or resubmission.
|
||||||
|
- Update affected public Javadoc and checked-exception declarations.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `jupiter-perps-transaction-confirmation`: Defines independent on-chain confirmation and alarm reporting for Jupiter Perps increase and decrease operations.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `jupiter-swap-service`: Requires independent `CONFIRMED` Solana success after the existing Jupiter execution-response validation and defines structured failure and timeout handling.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The Jupiter common API gains one checked exception. The Jupiter Swap and Perps APIs, their reference implementations, and the two Perps alarm actions gain corresponding confirmation and error propagation behavior. Existing transaction construction, signing, provider submission, wallet methods, pacing, and retry behavior remain unchanged.
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Defines independent Solana confirmation and outcome reporting for completed Jupiter Perps increase and decrease submissions.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Confirmed Perps operation completion
|
||||||
|
After the existing Perps increase or decrease flow produces a valid non-blank transaction signature, the service SHALL independently await that signature at `CONFIRMED`. It SHALL return the existing signature result only for `SUCCEEDED`; provider response validation and financial validation SHALL remain distinct prerequisites and SHALL not themselves constitute on-chain confirmation.
|
||||||
|
|
||||||
|
#### Scenario: Increase reaches confirmed success
|
||||||
|
- **WHEN** an increase submission returns a valid signature and its independent `CONFIRMED` outcome is `SUCCEEDED`
|
||||||
|
- **THEN** the service returns that signature
|
||||||
|
|
||||||
|
#### Scenario: Decrease reaches confirmed success
|
||||||
|
- **WHEN** a decrease submission returns a valid signature and its independent `CONFIRMED` outcome is `SUCCEEDED`
|
||||||
|
- **THEN** the service returns that signature
|
||||||
|
|
||||||
|
### Requirement: Structured exceptional outcomes
|
||||||
|
The shared Jupiter API SHALL expose a checked outcome exception containing the known non-blank transaction signature, requested commitment, and complete transaction outcome. Construction SHALL reject null or blank signatures, null commitments, null outcomes, and `SUCCEEDED` outcomes. `FAILED` SHALL identify a definitive on-chain failure including its slot and compact Solana error, while `TIMED_OUT` SHALL identify an unknown outcome and explicitly warn against automatic equivalent resubmission.
|
||||||
|
|
||||||
|
#### Scenario: Confirmed on-chain failure
|
||||||
|
- **WHEN** an increase or decrease signature reaches `CONFIRMED` with a `FAILED` outcome
|
||||||
|
- **THEN** the service throws the checked outcome exception preserving the signature, `CONFIRMED`, slot, and complete compact Solana failure details
|
||||||
|
|
||||||
|
#### Scenario: Confirmation timeout
|
||||||
|
- **WHEN** an increase or decrease signature produces a `TIMED_OUT` outcome
|
||||||
|
- **THEN** the service throws the checked outcome exception preserving the signature, `CONFIRMED`, and unknown outcome without automatically resubmitting
|
||||||
|
|
||||||
|
#### Scenario: Successful outcome is supplied to exception
|
||||||
|
- **WHEN** a caller attempts to construct the outcome exception with `SUCCEEDED` or inconsistent null or blank data
|
||||||
|
- **THEN** construction fails immediately
|
||||||
|
|
||||||
|
### Requirement: Perps confirmation timeout policy
|
||||||
|
The reference service SHALL accept a constructor-injected, non-null, strictly positive confirmation timeout and SHALL preserve its existing constructor with a two-minute default. The configured duration SHALL be forwarded unchanged and SHALL begin governing only when awaiting the already submitted signature starts. Increase and decrease SHALL apply the same confirmation and outcome policy.
|
||||||
|
|
||||||
|
#### Scenario: Existing constructor is used
|
||||||
|
- **WHEN** the reference service is constructed without a confirmation timeout
|
||||||
|
- **THEN** each submitted increase or decrease uses a two-minute confirmation timeout
|
||||||
|
|
||||||
|
#### Scenario: Custom timeout is used
|
||||||
|
- **WHEN** the reference service is constructed with a positive duration
|
||||||
|
- **THEN** each submitted increase or decrease forwards exactly that duration to `CONFIRMED` awaiting
|
||||||
|
|
||||||
|
#### Scenario: Invalid timeout is supplied
|
||||||
|
- **WHEN** the reference service is constructed with a null, zero, or negative duration
|
||||||
|
- **THEN** construction fails before any Perps operation can be submitted
|
||||||
|
|
||||||
|
### Requirement: Post-submission communication and interruption policy
|
||||||
|
If independent confirmation throws an I/O error after a signature is known, the service SHALL throw an `IOException` whose message identifies the increase or decrease operation, includes the signature, and states that the on-chain outcome is unknown, retaining the original error as cause. `InterruptedException` SHALL propagate directly without another RPC or Jupiter request. Confirmation SHALL never submit, rebuild, re-sign, retry, or resubmit a transaction.
|
||||||
|
|
||||||
|
#### Scenario: Confirmation communication fails
|
||||||
|
- **WHEN** independent confirmation throws an I/O error for a known Perps signature
|
||||||
|
- **THEN** the service reports the operation, signature, and unknown outcome, preserves the original error, and performs no retry or resubmission
|
||||||
|
|
||||||
|
#### Scenario: Confirmation is interrupted
|
||||||
|
- **WHEN** independent confirmation throws `InterruptedException`
|
||||||
|
- **THEN** interruption propagates directly and no subsequent request occurs
|
||||||
|
|
||||||
|
### Requirement: Outcome-aware Perps alarm reporting
|
||||||
|
Perps increase and decrease alarm actions SHALL rely on the Perps service for confirmation and SHALL distinguish structured `FAILED`, structured `TIMED_OUT`, and other errors. A failed report SHALL include operation, signature, confirmation slot, and compact Solana failure details. A timed-out report SHALL include operation, signature, unknown-outcome status, and that no equivalent transaction was automatically resubmitted. Alarm actions SHALL NOT invoke transaction awaiting themselves.
|
||||||
|
|
||||||
|
#### Scenario: Alarm operation fails on-chain
|
||||||
|
- **WHEN** a Perps alarm action receives a structured `FAILED` outcome exception
|
||||||
|
- **THEN** it reports the operation, signature, confirmation slot, and compact on-chain failure details distinctly from generic errors
|
||||||
|
|
||||||
|
#### Scenario: Alarm operation remains unknown
|
||||||
|
- **WHEN** a Perps alarm action receives a structured `TIMED_OUT` outcome exception
|
||||||
|
- **THEN** it reports the operation, signature, unknown outcome, and absence of automatic equivalent resubmission distinctly from generic errors
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Confirmed execution result validation
|
||||||
|
The service SHALL first accept a Jupiter execution response only when it has status `Success`, explicitly has result code `0`, contains a non-blank transaction signature, and contains positive valid actual total input and output raw amounts. It SHALL convert each actual amount using the independently resolved precision of its mint. After this provider-response validation, the service SHALL independently await the returned signature on Solana at `CONFIRMED` using its configured confirmation timeout and SHALL return the already validated swap result only for a `SUCCEEDED` outcome. Non-success HTTP responses, malformed responses, expired or rejected swaps, failed or contradictory status/code combinations, missing result codes, blank signatures, and invalid result amounts SHALL remain provider failures and SHALL not be represented as successful swaps.
|
||||||
|
|
||||||
|
#### Scenario: Provider response and on-chain confirmation succeed
|
||||||
|
- **WHEN** Jupiter reports status `Success`, code `0`, a signature, and valid actual total input and output amounts, and that signature independently reaches `CONFIRMED` with a `SUCCEEDED` outcome
|
||||||
|
- **THEN** the service returns the signature and those actual amounts in human-readable units
|
||||||
|
|
||||||
|
#### Scenario: Jupiter rejects or fails execution
|
||||||
|
- **WHEN** Jupiter returns a failed status, a missing or non-zero result code, a contradictory status/code combination, expiration, rejection, non-success HTTP status, malformed body, blank signature, or invalid actual amount
|
||||||
|
- **THEN** the service reports an I/O failure without treating provider validation as an on-chain confirmation
|
||||||
|
|
||||||
|
#### Scenario: Provider succeeds but transaction fails on-chain
|
||||||
|
- **WHEN** a fully validated Jupiter execution response contains a signature whose independent `CONFIRMED` outcome is `FAILED`
|
||||||
|
- **THEN** the service throws a structured checked outcome exception preserving the signature, requested commitment, slot, and complete compact Solana failure details
|
||||||
|
|
||||||
|
#### Scenario: Provider succeeds but confirmation times out
|
||||||
|
- **WHEN** a fully validated Jupiter execution response contains a signature whose independent `CONFIRMED` outcome is `TIMED_OUT`
|
||||||
|
- **THEN** the service throws a structured checked outcome exception preserving the signature, requested commitment, and unknown outcome warning and does not automatically resubmit an equivalent transaction
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Swap confirmation timeout policy
|
||||||
|
The reference service SHALL accept a constructor-injected, non-null, strictly positive confirmation timeout and SHALL preserve its existing constructor with a two-minute default. The configured duration SHALL be passed unchanged to independent confirmation after submission and SHALL begin governing only when awaiting the known signature starts.
|
||||||
|
|
||||||
|
#### Scenario: Valid custom timeout
|
||||||
|
- **WHEN** a reference service is constructed with a positive duration and later obtains a validated execution signature
|
||||||
|
- **THEN** it awaits that signature at `CONFIRMED` with exactly the supplied duration
|
||||||
|
|
||||||
|
#### Scenario: Invalid custom timeout
|
||||||
|
- **WHEN** a reference service is constructed with a null, zero, or negative duration
|
||||||
|
- **THEN** construction fails before any operation can be submitted
|
||||||
|
|
||||||
|
### Requirement: Post-submission communication and interruption policy
|
||||||
|
If independent confirmation fails with an I/O error after the signature is known, the service SHALL throw an `IOException` whose message identifies the swap operation, includes the signature, and states that the on-chain outcome is unknown, with the original error retained as its cause. Interruption SHALL propagate directly as `InterruptedException` without another RPC or Jupiter request. Neither condition SHALL trigger rebuilding, re-signing, retrying, or resubmitting a transaction.
|
||||||
|
|
||||||
|
#### Scenario: Confirmation communication fails
|
||||||
|
- **WHEN** independent confirmation throws an I/O error for a known submitted signature
|
||||||
|
- **THEN** the service reports the signature and unknown on-chain outcome while preserving the original error and performs no retry or resubmission
|
||||||
|
|
||||||
|
#### Scenario: Confirmation is interrupted
|
||||||
|
- **WHEN** independent confirmation throws `InterruptedException`
|
||||||
|
- **THEN** that interruption propagates directly and the service performs no subsequent request
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
## 1. Public Jupiter API
|
||||||
|
|
||||||
|
- [x] 1.1 Add the checked, documented `JupiterTransactionOutcomeException` with validated structured signature, commitment, and failed/timed-out outcome access.
|
||||||
|
- [x] 1.2 Update Jupiter Swap and Perps public Javadoc and checked-exception declarations through their API call chains.
|
||||||
|
|
||||||
|
## 2. Jupiter Service Confirmation
|
||||||
|
|
||||||
|
- [x] 2.1 Add a validated confirmation-timeout overload to `JupiterSwapServiceImpl`, retain its two-minute default constructor, and await validated Swap results at `CONFIRMED` without changing provider validation or submission behavior.
|
||||||
|
- [x] 2.2 Add a validated confirmation-timeout overload to `AnchorIdlJupiterPerpsServiceImpl`, retain its two-minute default constructor, and route increase and decrease signatures through one private `CONFIRMED` outcome-policy helper.
|
||||||
|
- [x] 2.3 Ensure both services preserve structured `FAILED` and `TIMED_OUT` outcomes, contextualize confirmation `IOException`s as unknown outcomes, propagate interruption directly, and perform no retry or resubmission.
|
||||||
|
|
||||||
|
## 3. Perps Alarm Reporting
|
||||||
|
|
||||||
|
- [x] 3.1 Update the increase alarm action to report `FAILED` and `TIMED_OUT` distinctly while leaving confirmation ownership in the Perps service.
|
||||||
|
- [x] 3.2 Update the decrease alarm action to report `FAILED` and `TIMED_OUT` distinctly while leaving confirmation ownership in the Perps service.
|
||||||
|
|
||||||
|
## 4. Verification
|
||||||
|
|
||||||
|
- [x] 4.1 Compile main and test source sets without adding tests or executing live Jupiter/Solana transactions.
|
||||||
|
- [x] 4.2 Run available relevant existing tests only if their runtime is non-live and practical.
|
||||||
|
- [x] 4.3 Run strict OpenSpec validation and `git diff --check`.
|
||||||
|
- [x] 4.4 Review the complete diff for checked-exception propagation, unchanged provider validations, exact timeout forwarding, direct interruption propagation, no retry/resubmission, no generated-source edits, and no unrelated changes.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-11
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
The current SPL transfer flow separates responsibilities cleanly: `SolanaBlockChain` serializes unsigned low-level transactions, while `SolanaWallet` resolves mint and holding state, validates human-readable amounts, signs, and submits. The transaction serializer already supports legacy-message account ordering, compact-u16 encoding, recent blockhash metadata, and selecting either supported SPL token program.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Extend the existing API/implementation boundary with checked SPL-token burning.
|
||||||
|
- Keep all state-dependent ownership and balance checks in the wallet and all wire serialization in the blockchain implementation.
|
||||||
|
- Use identical behavior for the original SPL Token Program and Token-2022 except for the selected program address.
|
||||||
|
- Preserve the existing submission-only signing flow.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Confirmation awaiting, retry, account closure, rent recovery, multisignatures, delegates, or permanent delegates.
|
||||||
|
- Native or wrapped SOL burning, a burn-all convenience API, or integration with Evelyn and Discord.
|
||||||
|
- New ValueTags, configuration, dependencies, tests, or changes to unrelated consumers.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Mirror the existing transfer responsibility split
|
||||||
|
|
||||||
|
The wallet will reuse its existing mint-program detection, supply loading, holding lookup, decimal consistency checks, exact decimal conversion, and `signAndSendTransaction()` flow. The blockchain method accepts already-resolved raw units and program identity and only validates/serializes those low-level inputs.
|
||||||
|
|
||||||
|
Moving state lookup into the transaction builder was rejected because it would duplicate wallet policy and blur the existing API boundary. Building the transaction directly in the wallet was rejected because low-level Solana serialization belongs to `SolanaBlockChain`.
|
||||||
|
|
||||||
|
### Encode one standard BurnChecked instruction
|
||||||
|
|
||||||
|
The unsigned legacy transaction will contain four account keys in privilege order: owner as writable signer/fee payer, token account and mint as writable unsigned accounts, and the selected token program as read-only unsigned. Its one compiled instruction will invoke program index 3 with account indexes token account, mint, owner and data consisting of the `BurnChecked` discriminator, little-endian unsigned-u64 amount bits, and unsigned-byte decimals. The message declares one required signature, zero read-only signed accounts, and one read-only unsigned account.
|
||||||
|
|
||||||
|
This follows the repository's current `TransferChecked` serializer instead of introducing a transaction framework or external dependency. Although Java `long` is signed, a validated unsigned-u64 `BigInteger` is serialized through its low 64 bits with `longValue()`, preserving the complete bit pattern.
|
||||||
|
|
||||||
|
### Validate all local builder inputs before RPC access
|
||||||
|
|
||||||
|
The builder will parse and range-check the raw amount, range-check decimals, require a non-null supported enum program, and decode all supplied addresses before requesting a recent blockhash. This keeps invalid input deterministic and avoids unnecessary RPC traffic. The same shared address decoder used by existing transaction builders provides Base58 and 32-byte validation.
|
||||||
|
|
||||||
|
### Treat complete-balance burn as an ordinary amount
|
||||||
|
|
||||||
|
Wallet balance validation rejects only raw amounts greater than the holding, so equality is deliberately allowed. No close instruction is appended; the token account remains open with a zero balance.
|
||||||
|
|
||||||
|
### Delegate construction through the cache boundary
|
||||||
|
|
||||||
|
`CachedSolanaBlockChain` will forward the call directly because each build requires a fresh recent blockhash. Caching or deduplicating transaction construction could return stale blockhashes and is therefore unsafe.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Hand-written Solana wire encoding can be sensitive to account ordering and instruction discriminators] → Mirror the existing compiled-instruction serializer and use the standard `BurnChecked` layout with explicit constants.
|
||||||
|
- [Unsigned-u64 values above signed-long maximum appear negative as Java longs] → Validate with `BigInteger` first and serialize the low 64-bit two's-complement representation in little-endian order.
|
||||||
|
- [Submission success does not establish on-chain burn success] → Keep the API explicitly submission-only; callers may separately await the returned signature when their domain policy requires it.
|
||||||
|
- [Mint and holding state can change between validation and execution] → Let Solana's checked instruction and runtime enforce final state; do not retry automatically.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Add the API methods, implementations, and direct cached delegation in one source-compatible change. Existing callers need no migration because the new methods are additive. Rollback removes those additions; no data or configuration migration is involved.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Future Evelyn functionality needs a generic way to burn wallet-owned SPL tokens, including EVE under Token-2022. The existing wallet/blockchain separation for SPL transfers provides the right boundary for adding checked burn construction and a high-level validate-sign-submit operation without coupling confirmation or account cleanup to submission.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Add a public blockchain operation that builds one unsigned `BurnChecked` SPL-token transaction for either the legacy SPL Token Program or Token-2022.
|
||||||
|
- Add a public wallet operation that validates blockchain state and an exact human-readable burn amount, then builds, signs, and submits the transaction once.
|
||||||
|
- Delegate burn transaction construction directly through the cached blockchain decorator without caching.
|
||||||
|
- Preserve submission-only behavior: no confirmation awaiting, retry, token-account closure, or rent recovery.
|
||||||
|
- Reuse the existing `ΩRawAmountΩ`, `ΩamountDecimalsΩ`, address, amount, and transaction ValueTags without changing Detag configuration.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `solana-spl-token-burning`: Defines safe checked SPL-token burn construction and wallet-owned submission for legacy SPL Token and Token-2022 mints.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The public `SolanaBlockChain` and `SolanaWallet` APIs gain burn operations. Their reference implementations and `CachedSolanaBlockChain` gain corresponding construction, validation, submission, and direct-delegation behavior. No consumers, configuration, tests, generated sources, or unrelated services change.
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Defines safe construction and wallet submission of checked SPL-token burns for both supported Solana token programs.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Checked SPL-token burn transaction construction
|
||||||
|
The blockchain API SHALL build an unsigned transaction containing exactly one checked SPL-token burn instruction for a supplied owner, token account, mint, raw amount, decimal count, and supported token program. The owner SHALL be the fee payer, token-account authority, and sole required signer. The transaction SHALL burn from the supplied token account and mint, use a recent blockhash, and remain unsigned and unsubmitted.
|
||||||
|
|
||||||
|
#### Scenario: Legacy SPL Token burn construction
|
||||||
|
- **WHEN** valid burn inputs identify the original SPL Token Program
|
||||||
|
- **THEN** the returned unsigned transaction contains one `BurnChecked` instruction owned by that program with the supplied account, mint, amount, and decimals
|
||||||
|
|
||||||
|
#### Scenario: Token-2022 burn construction
|
||||||
|
- **WHEN** valid burn inputs identify Token-2022
|
||||||
|
- **THEN** the returned unsigned transaction contains one `BurnChecked` instruction owned by Token-2022 with the supplied account, mint, amount, and decimals
|
||||||
|
|
||||||
|
#### Scenario: Builder does not submit
|
||||||
|
- **WHEN** a checked burn transaction is built successfully
|
||||||
|
- **THEN** the blockchain returns the unsigned serialized transaction and recent blockhash metadata without signing or submitting it
|
||||||
|
|
||||||
|
### Requirement: Low-level burn input validation
|
||||||
|
Before fetching a blockhash, the blockchain SHALL reject null, blank, malformed, or unsupported required arguments. The raw amount SHALL be a valid positive integer representable as an unsigned 64-bit value, and the decimal count SHALL be representable as an unsigned byte.
|
||||||
|
|
||||||
|
#### Scenario: Invalid raw amount
|
||||||
|
- **WHEN** the raw amount is null, non-numeric, zero, negative, or greater than unsigned 64-bit maximum
|
||||||
|
- **THEN** transaction construction fails with `IllegalArgumentException` before fetching a blockhash
|
||||||
|
|
||||||
|
#### Scenario: Invalid decimals
|
||||||
|
- **WHEN** the decimal count is less than zero or greater than 255
|
||||||
|
- **THEN** transaction construction fails with `IllegalArgumentException` before fetching a blockhash
|
||||||
|
|
||||||
|
#### Scenario: Invalid address or program
|
||||||
|
- **WHEN** the owner, token account, or mint is null, blank, malformed, or does not decode to a Solana address, or the token program is null
|
||||||
|
- **THEN** transaction construction fails locally before fetching a blockhash
|
||||||
|
|
||||||
|
### Requirement: Wallet-owned burn validation and exact conversion
|
||||||
|
The wallet SHALL accept a mint and positive human-readable amount, load the mint account, detect whether it is owned by the original SPL Token Program or Token-2022, obtain the mint decimals, and find its own holding under that program. It SHALL require the holding decimals to equal the mint decimals and SHALL convert the requested amount exactly to positive raw units without rounding. It SHALL reject unsupported mints, missing or inconsistent state, excessive precision, and amounts exceeding the wallet balance before transaction construction or submission.
|
||||||
|
|
||||||
|
#### Scenario: Exact partial balance burn
|
||||||
|
- **WHEN** a supported mint and positive amount are exactly representable with the mint decimals and do not exceed the wallet holding
|
||||||
|
- **THEN** the wallet supplies the corresponding exact raw amount, its own token account, detected program, and mint decimals to checked burn construction
|
||||||
|
|
||||||
|
#### Scenario: Complete balance burn
|
||||||
|
- **WHEN** the exact raw burn amount equals the wallet's complete token balance
|
||||||
|
- **THEN** the wallet permits the burn without requesting token-account closure or rent recovery
|
||||||
|
|
||||||
|
#### Scenario: Amount has excessive precision
|
||||||
|
- **WHEN** the requested human-readable amount contains a non-zero fraction beyond the mint decimal precision
|
||||||
|
- **THEN** the wallet rejects it with `IllegalArgumentException` before building, signing, or submitting a transaction
|
||||||
|
|
||||||
|
#### Scenario: Blockchain state cannot authorize burn
|
||||||
|
- **WHEN** the mint is missing or unsupported, the wallet has no holding, the holding decimals differ from the mint decimals, or the amount exceeds the holding balance
|
||||||
|
- **THEN** the wallet rejects the operation with `IllegalStateException` before building, signing, or submitting a transaction
|
||||||
|
|
||||||
|
### Requirement: Single submission without automatic completion handling
|
||||||
|
After successful validation, the wallet SHALL build the burn through the blockchain API, sign it through its existing signing flow, submit it exactly once through its existing submission flow, and return the resulting transaction signature. It SHALL return immediately after submission without awaiting confirmation, retrying, rebuilding, re-signing, resubmitting, or closing the token account.
|
||||||
|
|
||||||
|
#### Scenario: Burn is submitted successfully
|
||||||
|
- **WHEN** validation, construction, signing, and submission succeed
|
||||||
|
- **THEN** the wallet returns the transaction signature from the single submission without invoking transaction awaiting
|
||||||
|
|
||||||
|
#### Scenario: Burn flow is interrupted or fails
|
||||||
|
- **WHEN** blockchain access, signing, or submission throws an I/O error or the calling thread is interrupted
|
||||||
|
- **THEN** the corresponding exception propagates and no automatic retry or subsequent completion action occurs
|
||||||
|
|
||||||
|
### Requirement: Cached blockchain delegates burn construction
|
||||||
|
The cached blockchain decorator SHALL delegate every burn transaction construction call directly to its underlying blockchain without caching, reuse, or deduplication.
|
||||||
|
|
||||||
|
#### Scenario: Repeated construction through cached decorator
|
||||||
|
- **WHEN** callers request burn transaction construction multiple times through the cached decorator
|
||||||
|
- **THEN** every invocation reaches the underlying blockchain independently
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
## 1. Public APIs
|
||||||
|
|
||||||
|
- [x] 1.1 Add the documented checked-burn transaction builder to `SolanaBlockChain` using the existing burn input ValueTags.
|
||||||
|
- [x] 1.2 Add the documented human-readable SPL-token burn operation to `SolanaWallet` with submission-only semantics.
|
||||||
|
|
||||||
|
## 2. Blockchain Construction
|
||||||
|
|
||||||
|
- [x] 2.1 Implement pre-RPC raw amount, decimal, program, and Solana address validation in `SolanaBlockChainImpl`.
|
||||||
|
- [x] 2.2 Serialize one unsigned `BurnChecked` transaction with owner authority/fee-payer semantics, a recent blockhash, and selectable legacy SPL Token or Token-2022 program.
|
||||||
|
- [x] 2.3 Add direct, uncached burn-construction delegation to `CachedSolanaBlockChain`.
|
||||||
|
|
||||||
|
## 3. Wallet Burn Flow
|
||||||
|
|
||||||
|
- [x] 3.1 Implement mint lookup, supported-program detection, supply/holding lookup, decimal consistency, and exact human-to-raw amount conversion in `SolanaWalletImpl`.
|
||||||
|
- [x] 3.2 Enforce missing-state and balance failures while permitting an exact complete-balance burn without closing the token account.
|
||||||
|
- [x] 3.3 Build, sign, and submit exactly once through existing flows, return the signature immediately, and add no awaiting, retry, or resubmission.
|
||||||
|
|
||||||
|
## 4. Verification
|
||||||
|
|
||||||
|
- [x] 4.1 Compile main and test source sets without adding or modifying tests and without executing a live burn or submission.
|
||||||
|
- [x] 4.2 Run strict OpenSpec validation and `git diff --check`.
|
||||||
|
- [x] 4.3 Review the complete diff for both token programs, exact amount conversion, complete-balance support, direct cached delegation, submission-only behavior, no generated/test changes, and no unrelated modifications.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-11
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. The current notification framework combines an API package and three implementation packages under `com.r35157.libs.notification`; its generic message wrappers leak transport choices into consumers. The code currently has no external production consumer, but each implementation contains an inline compile-time usage example. The separate alarm-specific Pushover action is intentionally outside this module and must remain unchanged.
|
||||||
|
|
||||||
|
The repository temporarily hosts immature API and implementation artifacts together. Package ownership must nevertheless express a notification-service module that can later be extracted, with API types separated from transport implementations.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Establish a small stable API package containing only the bound service, addressed service, and destination abstraction.
|
||||||
|
- Make transport implementations consume the three existing String-backed ValueTags directly.
|
||||||
|
- Preserve the existing Pushover HTTP request and synchronous error behavior while enforcing message validation before transport work.
|
||||||
|
- Make SMTP's complete destination configuration immutable and bindable before injection into a consumer.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Implementing Discord or SMTP delivery.
|
||||||
|
- Adding notification configuration, asynchronous delivery, retries, queues, persistence, or timeout policies.
|
||||||
|
- Refactoring the alarm-specific Pushover implementation or changing `NenjimHubImpl.startAutoRunProcesses()`.
|
||||||
|
- Adding or modifying automated tests.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Separate API and transport implementation packages under the service owner
|
||||||
|
|
||||||
|
The API will live directly in `com.r35157.service.notification`, while Discord, Pushover, and SMTP remain in `impl.discord`, `impl.pushover`, and `impl.smtp` below it. This follows the repository's future module boundaries while keeping the current single-project layout. Retaining aliases in the old package was rejected because it would prolong the incompatible abstraction and violates the issue's clean migration requirement.
|
||||||
|
|
||||||
|
### Represent messages directly with the semantic ValueTag
|
||||||
|
|
||||||
|
All push contracts use `ΩNotificationMessageΩ` directly. Validation is performed at each concrete public push entry point before any delivery work. A replacement record, marker hierarchy, or transport generic was rejected because it would recreate the coupling the change removes.
|
||||||
|
|
||||||
|
### Keep addressed binding minimal and destination-only
|
||||||
|
|
||||||
|
`AddressedNotificationService<D>` captures a destination in its default `bind(D)` result. `SMTPDestination` captures both email address and subject and validates both in its compact constructor. `SMTPNotificationService` adds a convenience overload accepting these two values and delegates to the standard destination binding. Keeping the subject on each message was rejected because it would leave the service only partially configured when injected.
|
||||||
|
|
||||||
|
### Preserve transport maturity independently
|
||||||
|
|
||||||
|
Pushover keeps its current form encoding, endpoint, synchronous HTTP call, status handling, and `IOException` contract. Discord and SMTP validate message input first and then retain their `UnsupportedOperationException` behavior for otherwise valid messages. This prevents a refactor from silently expanding either transport's behavior.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- **[Breaking source migration]** All old names and packages disappear at once. → Migrate every production reference and inline example in the same change, then structurally search for old names.
|
||||||
|
- **[Validation duplicated across implementations]** Each implementation owns a small null/blank guard. → Prefer explicit local validation over adding an unnecessary public utility or runtime message wrapper; keep the exception type and condition identical.
|
||||||
|
- **[No automated regression coverage]** The issue explicitly excludes test changes. → Compile both source sets, run the normal assembly, and perform strict structural checks of packages, types, call sites, and generated-source boundaries.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Introduce the renamed API and transport implementations in the new package using the existing ValueTags.
|
||||||
|
2. Update all production imports and inline examples to the new contracts.
|
||||||
|
3. Remove the complete old package and every obsolete notifier/message type without aliases.
|
||||||
|
4. Compile and assemble from `.tjava`, validate OpenSpec strictly, and verify the source tree contains no stale API references.
|
||||||
|
|
||||||
|
Rollback consists of reverting the complete source and OpenSpec change together; the old and new APIs are intentionally not supported in parallel.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
The existing notification API exposes transport-specific message types to consumers, so a fully configured notification sender cannot be substituted without changing consumer code. Before adding further transports, the framework needs a stable service API in which configuration is bound at composition time and consumers provide only a semantically tagged text message.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **BREAKING** Relocate the notification API and implementations from `com.r35157.libs.notification` to `com.r35157.service.notification`, without compatibility aliases.
|
||||||
|
- **BREAKING** Replace `BoundNotifier` and `AddressedNotifier` with transport-independent `BoundNotificationService` and destination-only generic `AddressedNotificationService` contracts.
|
||||||
|
- **BREAKING** Remove the marker and transport-specific message wrappers in favor of `ΩNotificationMessageΩ` directly.
|
||||||
|
- Rename the Pushover, Discord, and SMTP implementations to notification-service terminology while preserving their current delivery status.
|
||||||
|
- Validate every notification message before delivery, and make SMTP binding capture both a validated email address and fixed subject.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `notification-services`: Defines the transport-independent notification API, binding and validation behavior, and the required behavior of the existing Pushover, Discord, and SMTP implementations.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
The change replaces the public notification package and its API types, updates all production imports and inline examples, and removes obsolete message classes. Pushover's synchronous HTTP delivery remains functional; Discord webhook and SMTP delivery remain unimplemented. Alarm-specific Pushover code and Nenjim startup composition are unaffected.
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
## Purpose
|
||||||
|
|
||||||
|
Defines transport-independent notification services whose delivery configuration is bound before consumers submit semantically tagged text messages.
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Transport-independent bound notification contract
|
||||||
|
The notification API SHALL provide a non-generic bound service under `com.r35157.service.notification` whose only delivery input is an `ΩNotificationMessageΩ` and whose synchronous failures use `IOException`. A consumer using an already bound service SHALL NOT need a transport-specific message type or an implementation-package dependency.
|
||||||
|
|
||||||
|
#### Scenario: Bound consumer sends a message
|
||||||
|
- **WHEN** a consumer receives a configured `BoundNotificationService`
|
||||||
|
- **THEN** it can call `push(message)` using only an `ΩNotificationMessageΩ`
|
||||||
|
|
||||||
|
#### Scenario: Delivery failure is reported synchronously
|
||||||
|
- **WHEN** a bound transport encounters a delivery failure
|
||||||
|
- **THEN** `push(message)` reports the failure through its `IOException` contract
|
||||||
|
|
||||||
|
### Requirement: Addressed services bind destination configuration
|
||||||
|
The notification API SHALL provide an `AddressedNotificationService` generic only over a `NotificationDestination` type. Binding a destination SHALL produce a `BoundNotificationService` that captures the destination so later calls require only an `ΩNotificationMessageΩ`.
|
||||||
|
|
||||||
|
#### Scenario: Destination is bound once
|
||||||
|
- **WHEN** a destination is passed to an addressed notification service's binding operation
|
||||||
|
- **THEN** the returned bound service sends subsequent messages to that captured destination without requiring it again
|
||||||
|
|
||||||
|
### Requirement: Notification message validation
|
||||||
|
Every public notification-service `push(...)` entry point SHALL reject a null, empty, or blank `ΩNotificationMessageΩ` with `IllegalArgumentException` before attempting any network or transport operation.
|
||||||
|
|
||||||
|
#### Scenario: Null message is rejected
|
||||||
|
- **WHEN** a caller pushes a null notification message
|
||||||
|
- **THEN** the service throws `IllegalArgumentException` before delivery is attempted
|
||||||
|
|
||||||
|
#### Scenario: Empty or blank message is rejected
|
||||||
|
- **WHEN** a caller pushes an empty or whitespace-only notification message
|
||||||
|
- **THEN** the service throws `IllegalArgumentException` before delivery is attempted
|
||||||
|
|
||||||
|
### Requirement: Pushover delivery remains functional
|
||||||
|
The Pushover implementation SHALL accept `ΩNotificationMessageΩ` directly, preserve its existing synchronous HTTP delivery behavior, and expose it as `PushoverNotificationService` implementing `BoundNotificationService`.
|
||||||
|
|
||||||
|
#### Scenario: Valid Pushover message is submitted
|
||||||
|
- **WHEN** a caller pushes a valid message through a configured Pushover notification service
|
||||||
|
- **THEN** the service submits the same message through the existing Pushover API request flow
|
||||||
|
|
||||||
|
#### Scenario: Pushover rejects delivery
|
||||||
|
- **WHEN** Pushover returns a non-successful HTTP response or communication fails
|
||||||
|
- **THEN** the service reports the delivery failure as an `IOException`
|
||||||
|
|
||||||
|
### Requirement: SMTP binding captures address and subject
|
||||||
|
An SMTP destination SHALL contain both an `ΩEmailAddressΩ` and a fixed `ΩEmailSubjectΩ`. SMTP SHALL support binding these two values into a `BoundNotificationService`, and SHALL reject a null, empty, or blank address or subject with `IllegalArgumentException` when the destination is created.
|
||||||
|
|
||||||
|
#### Scenario: SMTP service is fully bound
|
||||||
|
- **WHEN** a caller binds a valid email address and subject
|
||||||
|
- **THEN** the returned bound service requires only an `ΩNotificationMessageΩ` for each push
|
||||||
|
|
||||||
|
#### Scenario: Invalid SMTP binding is rejected
|
||||||
|
- **WHEN** a caller creates or requests an SMTP binding with a null, empty, or blank email address or subject
|
||||||
|
- **THEN** the operation throws `IllegalArgumentException`
|
||||||
|
|
||||||
|
#### Scenario: Valid SMTP delivery remains unimplemented
|
||||||
|
- **WHEN** a valid message is pushed through a valid SMTP binding
|
||||||
|
- **THEN** the service reports that SMTP delivery is not implemented
|
||||||
|
|
||||||
|
### Requirement: Discord delivery remains unimplemented
|
||||||
|
The Discord stub SHALL implement `BoundNotificationService`, accept `ΩNotificationMessageΩ` directly, and SHALL NOT implement webhook delivery as part of this capability.
|
||||||
|
|
||||||
|
#### Scenario: Valid Discord delivery remains unimplemented
|
||||||
|
- **WHEN** a valid message is pushed through the configured Discord service
|
||||||
|
- **THEN** the service reports that Discord delivery is not implemented without performing webhook delivery
|
||||||
|
|
||||||
|
### Requirement: Obsolete notification API is removed
|
||||||
|
The notification framework SHALL reside exclusively under `com.r35157.service.notification`. It SHALL NOT retain compatibility aliases under `com.r35157.libs.notification`, notifier-named public interfaces or implementations, the `NotificationMessage` marker, or transport-specific message wrapper types.
|
||||||
|
|
||||||
|
#### Scenario: Production code uses the new service API
|
||||||
|
- **WHEN** the production source tree is inspected after migration
|
||||||
|
- **THEN** notification framework code and consumers reference only the new package and service-oriented types
|
||||||
|
|
||||||
|
#### Scenario: Removed types are unavailable
|
||||||
|
- **WHEN** downstream code attempts to import an old notifier or message-wrapper type
|
||||||
|
- **THEN** that type is no longer part of the source or compiled API
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
## 1. Notification API Migration
|
||||||
|
|
||||||
|
- [x] 1.1 Add the documented `BoundNotificationService`, `AddressedNotificationService`, and `NotificationDestination` API under `com.r35157.service.notification`.
|
||||||
|
- [x] 1.2 Remove the old API package, marker interface, notifier contracts, and all compatibility aliases.
|
||||||
|
|
||||||
|
## 2. Transport Implementations
|
||||||
|
|
||||||
|
- [x] 2.1 Migrate and rename Pushover to `PushoverNotificationService`, accept and validate `ΩNotificationMessageΩ`, and preserve the existing delivery flow.
|
||||||
|
- [x] 2.2 Migrate and rename Discord to `DiscordNotificationService`, validate direct messages, and preserve its unimplemented delivery status.
|
||||||
|
- [x] 2.3 Migrate SMTP to `SMTPNotificationService` with a validated address-and-subject `SMTPDestination`, direct binding convenience, direct message validation, and unimplemented delivery status.
|
||||||
|
- [x] 2.4 Update every production call site and inline example, then confirm the old package and removed types have no remaining production references.
|
||||||
|
|
||||||
|
## 3. Verification
|
||||||
|
|
||||||
|
- [x] 3.1 Compile main and test source sets and complete the repository's normal assembly/build without changing tests.
|
||||||
|
- [x] 3.2 Strictly validate the OpenSpec change and run whitespace/diff checks.
|
||||||
|
- [x] 3.3 Review the complete diff for scope, generated-source changes, test changes, transport-independent consumer dependencies, and preservation of excluded services.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-12
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` for motivation. `DiscordNotificationService` is the only affected production class and currently validates only null/blank messages before throwing `UnsupportedOperationException`. The existing Jackson dependency can safely serialize the payload; no notification API, caller, or composition-root change is required.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
|
||||||
|
- Construct a fully bound synchronous Discord webhook service from `ΩRestEndpointΩ` and a positive `Duration`.
|
||||||
|
- Validate the endpoint locally, preserve message text exactly, and make successful response handling explicit.
|
||||||
|
- Keep the webhook URI confidential in all validation and delivery paths.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
|
||||||
|
- Configuration loading or Nenjim composition-root wiring.
|
||||||
|
- Retries, rate-limit waiting, asynchronous execution, or non-text Discord features.
|
||||||
|
- Changes to the notification API, Pushover, SMTP, alarms, Evelyn, or tests.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Strict local URI validation before client construction
|
||||||
|
|
||||||
|
The constructor validates the URI shape with URI components and a fixed path grammar: `https://discord.com/api/webhooks/{id}/{token}` or `https://discord.com/api/v{version}/webhooks/{id}/{token}`. Versioned paths accept only Discord API versions 6 through 10, which are currently non-discontinued; the host is case-insensitive and all token-bearing endpoint values are absent from diagnostics. A network probe is rejected because construction must be side-effect free and valid webhooks can change remotely.
|
||||||
|
|
||||||
|
### One synchronous JSON request per valid push
|
||||||
|
|
||||||
|
The service uses a construction-time `HttpClient` configured with the supplied connect timeout and `Redirect.NEVER`. Every valid message is serialized with Jackson into `content`, then posted with `wait=true`, the same request timeout, JSON headers, and a `DiscordBot (url, version)` User-Agent identifying Nenjim. This preserves the BoundNotificationService contract and lets Discord confirm persistence. Manual JSON escaping and asynchronous delivery are rejected.
|
||||||
|
|
||||||
|
### Failure policy preserves secrets and interruption
|
||||||
|
|
||||||
|
Only 2xx is successful. Other statuses become `IOException` containing only status and useful body. I/O failures are wrapped with non-sensitive context; interruption restores the flag and is the cause of an `IOException`. The service neither retries nor waits after 429, so each valid `push()` owns exactly one send attempt.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- **[Webhook endpoint is a credential]** → Never log or interpolate it into exceptions; redirect following is disabled.
|
||||||
|
- **[Strict URI validation could reject a future Discord route]** → Accept canonical unversioned and conventional versioned execute-webhook paths only; future route support requires an explicit spec change.
|
||||||
|
- **[No automated tests by issue instruction]** → Compile generated sources and unchanged tests, assemble, validate OpenSpec, and perform structural and diff checks without a live webhook.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Replace the URL-based stub constructor and delivery path in place.
|
||||||
|
2. Update the Discord requirement in the active capability delta.
|
||||||
|
3. Verify compilation, assembly, strict OpenSpec validation, and absence of old-stub patterns or credentials.
|
||||||
|
|
||||||
|
Rollback is a single-class and active-change revert; no consumer migration is needed.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user