Skip to content

feat: CALL NANOFLOW + GRANT/REVOKE nanoflow access#8

Open
retran wants to merge 1 commit intopr4-nanoflows-create-dropfrom
pr5-nanoflows-call-grant
Open

feat: CALL NANOFLOW + GRANT/REVOKE nanoflow access#8
retran wants to merge 1 commit intopr4-nanoflows-create-dropfrom
pr5-nanoflows-call-grant

Conversation

@retran
Copy link
Copy Markdown
Owner

@retran retran commented Apr 23, 2026

Summary

  • Add CALL NANOFLOW statement for calling nanoflows from within flow bodies (grammar, AST, visitor, flow builder, validation)
  • Add GRANT/REVOKE EXECUTE ON NANOFLOW for nanoflow security management (grammar, AST, visitor, executor)
  • Add NanoflowCallAction, NanoflowCall, NanoflowCallParameterMapping SDK types with BSON parser/writer
  • Add AllowedModuleRoles to Nanoflow struct with BSON round-trip support
  • Add cross-reference validation for nanoflow calls in validate.go
  • Add write-nanoflows.md agentic skill (embedded in binary)
  • Add comprehensive nanoflow proposal doc (PROPOSAL_nanoflow_support.md)

Why

Completes P1 nanoflow parity: without CALL NANOFLOW, nanoflows cannot invoke other nanoflows — breaking compositional flow design. Without GRANT/REVOKE, security roles cannot be managed via CLI. The agentic skill enables LLM agents to write correct nanoflow MDL without microflow-only action mistakes.

Testing

  • make build && make test && make lint-go all pass
  • Registry test updated with all new AST types
  • Formatting stash from earlier PRs not included (separate concern)

Copilot AI review requested due to automatic review settings April 23, 2026 16:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds nanoflow composition and security management to MDL/mxcli by introducing CALL NANOFLOW within flow bodies and GRANT/REVOKE EXECUTE ON NANOFLOW, plus the SDK/BSON plumbing and executor validation needed to round-trip these constructs.

Changes:

  • Extend MDL grammar/AST/visitor to support CALL NANOFLOW and GRANT/REVOKE EXECUTE ON NANOFLOW.
  • Add SDK types + BSON parser/writer support for NanoflowCallAction and Nanoflow.AllowedModuleRoles.
  • Update executor flow builder + validation to build/validate nanoflow calls and nanoflow access statements; add docs/skills.

Reviewed changes

Copilot reviewed 32 out of 43 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
sdk/mpr/writer_microflow_actions.go Serialize Microflows$NanoflowCallAction + nested call/mappings to BSON.
sdk/mpr/writer_microflow.go Serialize Nanoflow.AllowedModuleRoles in nanoflow BSON.
sdk/mpr/parser_nanoflow.go Parse AllowedModuleRoles from nanoflow BSON.
sdk/mpr/parser_microflow_actions.go Parse Microflows$NanoflowCallAction and nested structures.
sdk/mpr/parser_microflow.go Register parser for Microflows$NanoflowCallAction.
sdk/microflows/microflows_actions.go Add SDK model types: NanoflowCallAction, NanoflowCall, NanoflowCallParameterMapping.
sdk/microflows/microflows.go Add AllowedModuleRoles to Nanoflow SDK struct.
mdl/visitor/visitor_security.go Build AST for GRANT/REVOKE nanoflow access statements.
mdl/visitor/visitor_microflow_statements.go Dispatch + annotation wiring for CallNanoflowStmt.
mdl/visitor/visitor_microflow_actions.go Build CallNanoflowStmt from parser context.
mdl/grammar/parser/mdlparser_listener.go Regenerated listener interface for new productions.
mdl/grammar/parser/mdlparser_base_listener.go Regenerated base listener stubs for new productions.
mdl/grammar/MDLParser.g4 Grammar additions: callNanoflowStatement, grantNanoflowAccessStatement, revokeNanoflowAccessStatement.
mdl/executor/validate_microflow.go Treat CallNanoflowStmt like other call statements for variable/error-handling validation.
mdl/executor/validate.go Track nanoflow definitions + validate call nanoflow references in flow bodies.
mdl/executor/stmt_summary.go Add summaries for GRANT/REVOKE nanoflow access statements.
mdl/executor/registry_test.go Register new statement types in registry completeness tests.
mdl/executor/register_stubs.go Register executor handlers for GRANT/REVOKE nanoflow access.
mdl/executor/nanoflow_validation.go Include CallNanoflowStmt in nanoflow error-handling traversal.
mdl/executor/hierarchy.go Remove trailing whitespace (no functional change).
mdl/executor/cmd_security_write.go Implement GRANT/REVOKE nanoflow execute access by updating allowed roles.
mdl/executor/cmd_microflows_builder_validate.go Track output var typing + validate error handler body for CallNanoflowStmt.
mdl/executor/cmd_microflows_builder_graph.go Flow builder dispatch to create nanoflow call action.
mdl/executor/cmd_microflows_builder_calls.go Build NanoflowCallAction + mappings + output variable integration.
mdl/executor/cmd_microflows_builder_annotations.go Expose annotations for CallNanoflowStmt.
mdl/executor/cmd_microflows_builder.go Lookup nanoflow return type via backend for typing inference.
mdl/executor/cmd_diff_mdl.go Render CallNanoflowStmt into MDL for diff output.
mdl/catalog/builder_microflows_test.go Import order formatting (no functional change).
mdl/catalog/builder.go Alignment/formatting change only.
mdl/ast/ast_security.go Add AST nodes for GRANT/REVOKE nanoflow access.
mdl/ast/ast_microflow.go Add AST node CallNanoflowStmt.
docs/11-proposals/PROPOSAL_nanoflow_support.md Add comprehensive nanoflow support proposal doc.
cmd/mxcli/syntax/registry.go Whitespace/alignment only (no behavior change).
cmd/mxcli/syntax/features_workflow.go Whitespace/alignment only (no behavior change).
cmd/mxcli/syntax/features_security.go Whitespace/alignment only (no behavior change).
cmd/mxcli/syntax/features_page.go Whitespace/alignment only (no behavior change).
cmd/mxcli/syntax/features_misc.go Whitespace/alignment only (no behavior change).
cmd/mxcli/syntax/features_microflow.go Whitespace/alignment only (no behavior change).
cmd/mxcli/syntax/features_integration.go Whitespace/alignment only (no behavior change).
cmd/mxcli/syntax/features_domain_model.go Whitespace/alignment only (no behavior change).
.claude/skills/mendix/write-nanoflows.md Add agent skill guidance for writing nanoflows, including CALL + security.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +171 to +199
// addCallNanoflowAction creates a CALL NANOFLOW statement.
func (fb *flowBuilder) addCallNanoflowAction(s *ast.CallNanoflowStmt) model.ID {
nfQN := s.NanoflowName.Module + "." + s.NanoflowName.Name

// Build parameter mappings for NanoflowCall
var mappings []*microflows.NanoflowCallParameterMapping
for _, arg := range s.Arguments {
paramQN := nfQN + "." + arg.Name
mapping := &microflows.NanoflowCallParameterMapping{
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
Parameter: paramQN,
Argument: fb.exprToString(arg.Value),
}
mappings = append(mappings, mapping)
}

nfCall := &microflows.NanoflowCall{
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
Nanoflow: nfQN,
ParameterMappings: mappings,
}

action := &microflows.NanoflowCallAction{
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
ErrorHandlingType: convertErrorHandlingType(s.ErrorHandling),
NanoflowCall: nfCall,
ResultVariableName: s.OutputVariable,
UseReturnVariable: s.OutputVariable != "",
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addCallNanoflowAction emits a *microflows.NanoflowCallAction, but the MDL formatter used by DESCRIBE NANOFLOW (formatAction in cmd_microflows_format_action.go) currently has no case for NanoflowCallAction, so describing a nanoflow containing this statement will render as an unsupported/unknown action. Add formatting support for NanoflowCallAction (and its parameter mappings + optional result variable) so describe output round-trips the new syntax.

Copilot uses AI. Check for mistakes.
Comment thread mdl/grammar/MDLParser.g4
Comment on lines 1287 to 1291
| annotation* raiseErrorStatement SEMICOLON?
| annotation* logStatement SEMICOLON?
| annotation* callMicroflowStatement SEMICOLON?
| annotation* callNanoflowStatement SEMICOLON?
| annotation* callJavaActionStatement SEMICOLON?
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

microflowStatement is used by both microflow and nanoflow bodies (via microflowBody). Adding callNanoflowStatement here makes CALL NANOFLOW syntactically valid inside microflows as well. This conflicts with the PR’s own nanoflow skill/proposal text stating microflows can’t call nanoflows; either move this alternative to a nanoflow-only statement rule, or add explicit validation in microflow creation/validation to reject CallNanoflowStmt in microflows (and update docs if the intent is actually to allow it).

Copilot uses AI. Check for mistakes.
Comment on lines +171 to +219
// addCallNanoflowAction creates a CALL NANOFLOW statement.
func (fb *flowBuilder) addCallNanoflowAction(s *ast.CallNanoflowStmt) model.ID {
nfQN := s.NanoflowName.Module + "." + s.NanoflowName.Name

// Build parameter mappings for NanoflowCall
var mappings []*microflows.NanoflowCallParameterMapping
for _, arg := range s.Arguments {
paramQN := nfQN + "." + arg.Name
mapping := &microflows.NanoflowCallParameterMapping{
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
Parameter: paramQN,
Argument: fb.exprToString(arg.Value),
}
mappings = append(mappings, mapping)
}

nfCall := &microflows.NanoflowCall{
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
Nanoflow: nfQN,
ParameterMappings: mappings,
}

action := &microflows.NanoflowCallAction{
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
ErrorHandlingType: convertErrorHandlingType(s.ErrorHandling),
NanoflowCall: nfCall,
ResultVariableName: s.OutputVariable,
UseReturnVariable: s.OutputVariable != "",
}

activityX := fb.posX
activity := &microflows.ActionActivity{
BaseActivity: microflows.BaseActivity{
BaseMicroflowObject: microflows.BaseMicroflowObject{
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
Position: model.Point{X: fb.posX, Y: fb.posY},
Size: model.Size{Width: ActivityWidth, Height: ActivityHeight},
},
AutoGenerateCaption: true,
},
Action: action,
}

fb.objects = append(fb.objects, activity)
fb.posX += fb.spacing

if s.OutputVariable != "" {
fb.registerResultVariableType(s.OutputVariable, fb.lookupNanoflowReturnType(nfQN))
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are existing unit tests covering addCallMicroflowAction result typing/behavior, but no analogous coverage for addCallNanoflowAction (e.g., parameter mapping format, UseReturnVariable/ResultVariableName, and return-type inference via lookupNanoflowReturnType). Adding targeted tests will help prevent regressions in CALL NANOFLOW serialization and type tracking.

Copilot uses AI. Check for mistakes.
@retran retran force-pushed the pr5-nanoflows-call-grant branch from 2dee3d2 to 54a9353 Compare April 23, 2026 16:50
@retran retran force-pushed the pr4-nanoflows-create-drop branch from 2356562 to 4588cda Compare April 23, 2026 19:42
@retran retran force-pushed the pr5-nanoflows-call-grant branch from 82247b4 to 2183899 Compare April 23, 2026 19:42
@retran retran requested a review from Copilot April 23, 2026 19:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 35 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

model.BaseElement
ErrorHandlingType ErrorHandlingType `json:"errorHandlingType,omitempty"`
NanoflowCall *NanoflowCall `json:"nanoflowCall,omitempty"`
ResultVariableName string `json:"resultVariableName,omitempty"`
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NanoflowCallAction uses ResultVariableName/resultVariableName naming, but the repository’s generated Mendix metamodel represents Microflows$NanoflowCallAction with an OutputVariableName field (json:"outputVariableName"). If the BSON field is also OutputVariableName, the current SDK type + BSON reader/writer will silently drop result variable assignments. Align the SDK field name/tag (and corresponding BSON keys in parser/writer) with the metamodel for nanoflow calls.

Suggested change
ResultVariableName string `json:"resultVariableName,omitempty"`
OutputVariableName string `json:"outputVariableName,omitempty"`

Copilot uses AI. Check for mistakes.
Comment on lines +238 to +244
doc := bson.D{
{Key: "$ID", Value: idToBsonBinary(string(a.ID))},
{Key: "$Type", Value: "Microflows$NanoflowCallAction"},
{Key: "ErrorHandlingType", Value: stringOrDefault(string(a.ErrorHandlingType), "Rollback")},
{Key: "ResultVariableName", Value: a.ResultVariableName},
{Key: "UseReturnVariable", Value: a.UseReturnVariable},
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serializeMicroflowAction writes NanoflowCallAction using the BSON key ResultVariableName. The generated metamodel indicates Microflows$NanoflowCallAction uses OutputVariableName (and JSON outputVariableName), so this may emit invalid/ignored output-variable data in Studio Pro. Verify the correct storage field name for nanoflow call actions and update the writer (and parser/types) accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +75
func parseNanoflowCallAction(raw map[string]any) *microflows.NanoflowCallAction {
action := &microflows.NanoflowCallAction{}
action.ID = model.ID(extractBsonID(raw["$ID"]))
action.ErrorHandlingType = microflows.ErrorHandlingType(extractString(raw["ErrorHandlingType"]))
action.ResultVariableName = extractString(raw["ResultVariableName"])
action.UseReturnVariable = extractBool(raw["UseReturnVariable"], false)

Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseNanoflowCallAction reads the result variable from raw["ResultVariableName"]. If Mendix stores nanoflow call output variables under OutputVariableName (per generated metamodel), this will parse as empty and break round-tripping. Consider checking both keys (similar to other parsers that handle storageName vs qualifiedName differences) or switching to the correct key consistently with the writer.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +48
// Parse AllowedModuleRoles
for _, r := range extractBsonArray(raw["AllowedModuleRoles"]) {
if roleID, ok := r.(string); ok {
nf.AllowedModuleRoles = append(nf.AllowedModuleRoles, model.ID(roleID))
}
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop variable r in for _, r := range extractBsonArray(...) shadows the method receiver r *Reader, which makes the code harder to read and easy to trip over during future edits. Renaming the loop variable (e.g., role/item) would avoid the shadowing without changing behavior.

Copilot uses AI. Check for mistakes.
@retran retran force-pushed the pr4-nanoflows-create-drop branch from 4588cda to bc2a53a Compare April 23, 2026 20:11
@retran retran force-pushed the pr5-nanoflows-call-grant branch from 2183899 to bcb19c0 Compare April 23, 2026 20:11
@retran retran force-pushed the pr4-nanoflows-create-drop branch from bc2a53a to dabcdfb Compare April 23, 2026 20:28
@retran retran force-pushed the pr5-nanoflows-call-grant branch from bcb19c0 to dd4df09 Compare April 23, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants