-
Notifications
You must be signed in to change notification settings - Fork 1
feat(edgeV2): EDGE snippet-aligned pursue/skip guards with shared PRISM helpers #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
210fbc7
remove support for decision variables
vieirin 47515a3
add new edge snippet
vieirin 3a1f16a
add new engine
vieirin aabc548
edgeV2: replace goal achievable formulas with child achieved composition
vieirin 84b4aae
edgeV2: align goal module PRISM variables with snippet encoding
vieirin b6a2db6
fix(edgeV2): align pursue/achieve/skip with goal _state and formula _…
vieirin 22ef609
feat(edgeV2): declare decision_<goalId> per goal module
vieirin 192d76e
feat(ui): pass achievabilitySpace for Edge V2 decision bound
vieirin 2ae7aa0
feat(edgeV2): restore achievable formulas from Edge v1
vieirin aa44296
refactor(edgeV2): top-level const int decision vars like Edge engine
vieirin 805f8ca
feat(edgeV2): match snippets for sequential and interleaved AND pursues
vieirin 23d8f8b
feat(edgeV2): declare _decision_<id> const for OR goals
vieirin 2e55314
fix(edgeV2): OR degradation pursues, sibling failed counters, and val…
vieirin 716d5a4
feat(edgeV2): OR choice and degradation skip lines match snippets
vieirin 1639a91
feat(edgeV2): decision thresholds for basic goals and tasks
vieirin ea7efef
refactor(edgeV2): centralize shared PRISM guard helpers
vieirin 9b6bd4b
fix(edgeV2): strict integer parsing for retry counts and resource bounds
vieirin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| AND goal sequential | ||
|
|
||
| module G0 | ||
| g0 : [0..1] init 0; //0 means not pursued, 1 means currently pursued | ||
|
|
||
| [pursue_G0] !g0_achieved & g0=0 & GUARD -> (g0'=1); //triggering the goal from upper layer | ||
| [pursue_G1] !g0_achieved & g0=1 & G0_achievable*10.0 > decision_G0 -> true; //this only checks if we should indeed pursue on of the children or skip | ||
| [pursue_G2] !g0_achieved & g0=1 & G0_achievable*10.0 > decision_G0 & g1_achieved -> true; //here we add the guard that G1 needs to be achieved first since we're in sequential | ||
|
|
||
| [skip_G0] !g0_achieved & g0=1 & g1=0 & g2=0 & G0_achievable*10.0 <= decision_G0 -> (g0'=0); //we can skip only when no child is pursued | ||
|
|
||
|
|
||
| [achieved_G0] g0=1 & g0_achieved & g1=0 & g2=0 -> (g0'=0); | ||
|
|
||
| endmodule | ||
|
|
||
| formula g0_achieved = (g1_achieved & g2_achieved); | ||
|
|
||
| AND goal any order | ||
|
|
||
| module G0 | ||
| g0 : [0..1] init 0; //0 means not pursued, 1 means currently pursued | ||
|
|
||
| [pursue_G0] !g0_achieved & g0=0 & GUARD -> (g0'=1); //triggering the goal from upper layer | ||
| [pursue_G1] !g0_achieved & g0=1 & G0_achievable*10.0 > decision_G0 & g2!=1 & (g2_pursued | (G1_achievable/(G1_achievable+G2_achievable))*10.0 > _decision_G0) -> true; | ||
| [pursue_G2] !g0_achieved & g0=1 & G0_achievable*10.0 > decision_G0 & g1!=1 & (g1_pursued | (G1_achievable/(G1_achievable+G2_achievable))*10.0 <= _decision_G0) -> true; | ||
|
|
||
| [skip_G0] !g0_achieved & g0=1 & g1=0 & g2=0 & G0_achievable*10.0 <= decision_G0 -> (g0'=0); | ||
|
|
||
|
|
||
| [achieved_G0] g0=1 & g0_achieved & g1=0 & g2=0 -> (g0'=0); | ||
|
|
||
| endmodule | ||
| formula g0_achieved = (g1_achieved & g2_achieved); | ||
|
|
||
|
|
||
| AND goal interleaved | ||
|
|
||
| module G0 | ||
| g0 : [0..1] init 0; //0 means not pursued, 1 means currently pursued | ||
|
|
||
| [pursue_G0] !g0_achieved & g0=0 & GUARD -> (g0'=1); //triggering the goal from upper layer | ||
| [pursue_G1] !g0_achieved & g0=1 & G1_achievable*10.0 > decision_G1 -> true; | ||
| [pursue_G2] !g0_achieved & g0=1 & G2_achievable*10.0 > decision_G2 -> true; //here the trick is that we already skip g0 if none of the children should be pursued; this prohibits livelocks (circles of pursue-skip-pursue without any advancements) | ||
|
|
||
| [skip_G0] g0=1 & g1=0 & g2=0 & !g0_achieved & !(G2_achievable*10.0 > decision_G2 | G1_achievable*10.0 > decision_G1) -> (g0'=0); | ||
|
|
||
|
|
||
| [achieved_G0] g0=1 & g0_achieved & g1=0 & g2=0 -> (g0'=0); | ||
|
|
||
| endmodule | ||
| formula g0_achieved = (g1_achieved & g2_achieved); | ||
|
|
||
|
|
||
|
|
||
| OR goal | ||
| module G0 | ||
| g0 : [0..1] init 0; //0 means not pursued, 1 means currently pursued | ||
|
|
||
| [pursue_G0] !g0_achieved & g0=0 & GUARD -> (g0'=1); //triggering the goal from upper layer | ||
| [pursue_G1] !g0_achieved & g0=1 & G0_achievable*10.0 > decision_G0 & g2=0 & (G1_achievable/(G1_achievable+G2_achievable))*10.0 > _decision_G0 -> true; | ||
| [pursue_G2] !g0_achieved & g0=1 & G0_achievable*10.0 > decision_G0 & g1=0 & (G1_achievable/(G1_achievable+G2_achievable))*10.0 <= _decision_G0 -> true; | ||
|
|
||
| [skip_G0] !g0_achieved & g0=1 & g1=0 & g2=0 & G0_achievable*10.0 <= decision_G0 -> (g0'=0); | ||
|
|
||
|
|
||
| [achieved_G0] g0=1 & g1=0 & g2=0 & g0_achieved -> (g0'=0); | ||
|
|
||
| endmodule | ||
| formula g0_achieved = (g1_achieved | g2_achieved); | ||
|
|
||
|
|
||
| OR goal choose once | ||
| module G0 | ||
| g0 : [0..1] init 0; //0 means not pursued, 1 means currently pursued | ||
| g0_chosen: [0..2] init 0; //0 not chosen, 1 chose 1, 2 chose 2 | ||
|
|
||
| [pursue_G0] !g0_achieved & g0=0 & GUARD -> (g0'=1); //triggering the goal from upper layer | ||
| [pursue_G1] !g0_achieved & g0=1 & g0_chosen=0 & G0_achievable*10.0 > decision_G0 & g2=0 & (G1_achievable/(G1_achievable+G2_achievable))*10.0 > _decision_G0 -> (g0_chosen'=1); | ||
| [pursue_G2] !g0_achieved & g0=1 & g0_chosen=0 & G0_achievable*10.0 > decision_G0 & g1=0 & (G1_achievable/(G1_achievable+G2_achievable))*10.0 <= _decision_G0 -> (g0_chosen'=2); | ||
|
|
||
| [pursue_G1] !g0_achieved & g0=1 & g0_chosen=1 & G1_achievable*10.0 > decision_G1 -> true; | ||
| [pursue_G2] !g0_achieved & g0=1 & g0_chosen=2 & G2_achievable*10.0 > decision_G2 -> true; | ||
|
|
||
| [skip_G0] !g0_achieved & g0=1 & g1=0 & g2=0 & g0_chosen=0 & G0_achievable*10.0 <= decision_G0 -> (g0'=0); | ||
| [skip_G0] !g0_achieved & g0=1 & g1=0 & g0_chosen=1 & G1_achievable*10.0 <= decision_G1 -> (g0'=0); //skip condition if g0_chosen=1 | ||
| [skip_G0] !g0_achieved & g0=1 & g2=0 & g0_chosen=2 & G2_achievable*10.0 <= decision_G2 -> (g0'=0); | ||
|
|
||
| [achieved_G0] g0=1 & g1=0 & g2=0 & g0_achieved -> (g0'=0); | ||
|
|
||
| endmodule | ||
| formula g0_achieved = (g1_achieved | g2_achieved); | ||
|
|
||
|
|
||
| OR goal degradation | ||
| module G0 | ||
| g0 : [0..1] init 0; //0 means not pursued, 1 means currently pursued | ||
| g0_failed : [0..N] init 0; | ||
|
|
||
| [pursue_G0] !g0_achieved & g0=0 & GUARD -> (g0'=1); //triggering the goal from upper layer | ||
|
|
||
| [pursue_G1] !g0_achieved & g0=1 & g0_failed<N & G1_achievable*10.0 > decision_G1 -> (g0_failed'=g0_failed+1); //if we need to retry N times | ||
| [skip_G0] !g0_achieved & g0=1 & g1=0 & g0_failed<N & G1_achievable*10.0 <= decision_G1 -> (g0'=0); | ||
|
|
||
| [pursue_G1] !g0_achieved & g0=1 & g0_failed=N & G0_achievable*10.0 > decision_G0 & g2=0 & (G1_achievable/(G1_achievable+G2_achievable))*10.0 > _decision_G0 -> true; | ||
| [pursue_G2] !g0_achieved & g0=1 & g0_failed=N & G0_achievable*10.0 > decision_G0 & g1=0 & (G1_achievable/(G1_achievable+G2_achievable))*10.0 <= _decision_G0 -> true; | ||
|
|
||
| [skip_G0] !g0_achieved & g0=1 & g1=0 & g0_failed<N & G1_achievable*10.0 <= decision_G1 -> (g0'=0); | ||
| [skip_G0] !g0_achieved & g0=1 & g1=0 & g2=0 & g0_failed=N & G0_achievable*10.0 <= decision_G0 -> (g0'=0); | ||
|
|
||
| [achieved_G0] !g0_achieved & g0=1 & g1=0 & g2=0 & g0_achieved -> (g0'=0); | ||
|
|
||
| endmodule | ||
| formula g0_achieved = (g1_achieved | g2_achieved); | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| /** | ||
| * Edge Engine | ||
| * Generates PRISM models for probabilistic verification | ||
| * | ||
| * Architecture: | ||
| * - mapper.ts: Engine creation - maps raw iStar model to Edge-specific properties | ||
| * - template/: Transformation - generates PRISM model output | ||
| */ | ||
|
|
||
| // Engine creation (mapper) | ||
| export { | ||
| EDGE_GOAL_KEYS, | ||
| EDGE_RESOURCE_KEYS, | ||
| EDGE_TASK_KEYS, | ||
| edgeEngineMapper, | ||
| type EdgeGoalKey, | ||
| type EdgeGoalNode, | ||
| type EdgeGoalPropsResolved, | ||
| type EdgeGoalTree, | ||
| type EdgeResource, | ||
| type EdgeResourceKey, | ||
| type EdgeTask, | ||
| type EdgeTaskKey, | ||
| } from './mapper'; | ||
|
|
||
| // Types | ||
| export type { | ||
| EdgeGoalProps, | ||
| EdgeResourceProps, | ||
| EdgeResourceVariable, | ||
| EdgeTaskProps, | ||
| ExecCondition, | ||
| GoalExecutionDetail, | ||
| } from './types'; | ||
|
|
||
| // Transformation (template engine) | ||
| export { generateValidatedPrismModel } from './template'; | ||
|
|
||
| // Logger | ||
| export { getLogger, initLogger, type LoggerReport } from './logger/logger'; | ||
|
|
||
| // Validator | ||
| export { formatValidationReport, validate } from './validator'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
|
|
||
| /** | ||
| * Calculates the log file path for a given model file name | ||
| * @param modelFileName The model file name (e.g., "examples/experiments/1-minimal.txt") | ||
| * @param extension The file extension (default: ".log") | ||
| * @returns The full path to the log file (e.g., "logs/examples/experiments/1-minimal.txt.log") | ||
| */ | ||
| export const getLogFilePath = ( | ||
| modelFileName: string, | ||
| extension: string = '.log', | ||
| ): string => { | ||
| return `logs/${modelFileName}${extension}`; | ||
| }; | ||
|
|
||
| /** | ||
| * Ensures the directory for a log file exists and returns the full path | ||
| * @param modelFileName The model file name | ||
| * @param extension The file extension (default: ".log") | ||
| * @returns The full path to the log file, or null if directory creation fails (e.g., in serverless environments) | ||
| */ | ||
| export const ensureLogFileDirectory = ( | ||
| modelFileName: string, | ||
| extension: string = '.log', | ||
| ): string | null => { | ||
| // In serverless environments (like Vercel), filesystem is read-only | ||
| // Check if we're in a serverless environment | ||
| if ( | ||
| process.env.VERCEL || | ||
| process.env.AWS_LAMBDA_FUNCTION_NAME || | ||
| process.env.NEXT_PHASE | ||
| ) { | ||
| return null; | ||
| } | ||
|
|
||
| try { | ||
| const logFilePath = getLogFilePath(modelFileName, extension); | ||
| const logDir = path.dirname(logFilePath); | ||
| fs.mkdirSync(logDir, { recursive: true }); | ||
| return logFilePath; | ||
| } catch { | ||
| // If directory creation fails (e.g., read-only filesystem), return null | ||
| return null; | ||
| } | ||
| }; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: vieirin/goal-controller
Length of output: 50379
🏁 Script executed:
Repository: vieirin/goal-controller
Length of output: 587
🏁 Script executed:
Repository: vieirin/goal-controller
Length of output: 1466
🏁 Script executed:
Repository: vieirin/goal-controller
Length of output: 699
🏁 Script executed:
Repository: vieirin/goal-controller
Length of output: 907
🏁 Script executed:
Repository: vieirin/goal-controller
Length of output: 697
Constrain log paths to the
logsroot to prevent directory traversal attacks.modelFileNameparameter is interpolated directly into a filesystem path without validation. An attacker passing../../../tmp/malicious.txtvia CLI arguments could escape thelogs/directory and create files anywhere the process has write permissions.🔒 Proposed hardening
Also applies to: packages/lib/src/engines/edge/logger/filePath.ts (lines 10-15)
🤖 Prompt for AI Agents