Skip to content

feat(subflow): let a parent override a child's long-poll window/roles and scoped views - #1028

Merged
yilmaztayfun merged 8 commits into
masterfrom
feature/subflow-override-interaction-views
Sep 24, 2026
Merged

yilmaztayfun merged 8 commits into
masterfrom
feature/subflow-override-interaction-views

Conversation

@yilmaztayfun

@yilmaztayfun yilmaztayfun commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • A parent that consumes a flow as a SubFlow can now tune the child for its context, per child state:
    • overrides.states.<childState>.interaction.longPoll.fallbackTimeoutSeconds and .roles. This is a field-level override: a field left out keeps the child's value, and roles replaces the child's list as a whole.
    • overrides.states.<childState>.views and overrides.transitions.<childTransition>.views. These swap the view the child's own rules selected, keyed by that view's key.
  • Rules are never overridden: neither view rules nor the long-poll rule, and neither is terminate. An override never adds a long-poll to a state that does not declare one; that case logs 20305. If the child authorizes the long-poll with a rule, a roles override is ignored (logs 20306) and a window override still applies.
  • The overrides travel in the stamps SubflowStarter already writes onto the child (subflow.state_role_overrides / subflow.transition_role_overrides, which carry the whole maps), so there is no new stamp key. They are resolved child-side on the child's own CurrentState, never EffectiveState. They therefore also apply when the child is addressed directly, and reach exactly one hop: in P → C → G, P's overrides never affect G.
  • The legacy overrides.views / viewOverrides view-key map is unchanged: it is still applied parent-side and is now deprecated in vnext-meta. Mixing it with the new scoped view overrides on the same subFlow is a validation error.
  • Fix found by the integration run: the long-poll ack fallback job row now persists ExecuteAt. It is the same instant the Dapr job is armed with; before this it was always NULL.

Changes

  • Model: SubFlowLongPollOverride and SubFlowStateInteractionOverride are new. SubFlowStateOverride gains Interaction and Views; SubFlowTransitionOverride gains Views.
  • One stamp parser: SubFlowOverrideStamp (Domain). Both SubFlow*OverrideReader classes delegate to it. Behaviour note: a malformed grant (ArgumentException) now degrades to "no override", the same as a JsonException already did, instead of escaping.
  • Two resolvers: Instance.ResolveEffectiveLongPoll(state) and Instance.ResolveViewOverride(state, transition, viewKey).
  • Long-poll readers all read the resolver:
    • HandleLongPollTerminationStep: the pause (arm), the roles ownership check and the fallback schedule.
    • LongPollInteractionGate: used by the state function and by authorize?ack=true.
    • ResolveInteractionAsync: fallbackTimeoutSeconds in the state body.
  • Views: ResolveViewAsync applies the override after the child's rules pick a view. A transition alias is normalised to the configured key. If the override cannot be resolved, the child's own view is served and 20101 is logged. GetSubFlowViewWithOverrideAsync (legacy) is untouched.
  • Validation:
    • WorkflowValidator.ValidateSubFlowOverrides: the window must be ≥ 1, the role grants are validated, and legacy and scoped view overrides may not be mixed.
    • New non-blocking warnings channel (WorkflowValidationResult.Warnings → ComponentValidationResult.Warnings). DefinitionAppService logs the warnings (90006); the publish response is unchanged. First user: roles: [].
  • Logs: 20101, 20305–20307, 90006.
  • Docs / meta:
    • New docs/domain/subflow-overrides.md, plus updates to docs/domain/long-poll-termination.md, docs/README.md and .claude/rules/vnext-workflow-developer.md. The rule file also had a stale ResponseShapeVersion value, corrected to v11.
    • vnext-meta/features.json gets engine.subFlowOverrides; deprecations.json gets two entries. Both use version 0.0.95, because tags reach v0.0.94.
  • Contract: no state-body shape change, so no ResponseShapeVersion bump and no new fingerprint member.

Test Plan

  • New unit tests:
    • stamp round-trip keeps an absent field absent and an explicit roles: [] empty;
    • resolver merge matrix;
    • gate, step and state-body wiring;
    • child-side state and transition view override, including alias normalisation and the fallback when the override cannot be resolved;
    • validator rules and warnings;
    • ExecuteAt persisted on the job row.
  • Full dotnet test compared against master at the branch base: 0 new failures. Branch: 5336 total / 71 failed; master: 5302 / 81. All remaining failures fail on master too.
  • vnext-meta validation passes. The only drift it reports existed before this branch: common.props says 0.0.92, version-manifest stops at 0.0.93.

Integration test evidence

  • Scenario: vnext-example Tests/SubflowOverrideLab, run with --filter FullyQualifiedName~SubflowOverrideLab. It is a new scenario: extending authorization-chain-lab would have changed that suite's ack contract.
  • Runtime: commits e7cb023c and 5f60ab4b, at VNEXT_BASE_URL=http://localhost:4201 (run-docker.sh up core, local build).
  • Result: 12/12 green (three runs). Neighbouring suites on the same runtime: AuthorizationChainLab 44 passed (+7 skipped morph-idm provider tests), SubflowOrchestration 22/22, TimeoutLab 3/3.
  • Measured:
    • With the child declaring 600 s, the parent's 120 s override fired after 120.2 s and a 5 s override after 5.1 s.
    • After the fix, postgres subflow_override_lab_child.InstanceJobs (JobType=4) has 10/10 rows with ExecuteAt set, versus 0/22 before. ExecuteAt − CreatedAt is 600.0 / 120.0 / 5.0 s.
    • The roles override governs who gets the interaction and who authorize?ack=true admits, both through the parent and on the child directly. State and transition view overrides take effect, including on the directly addressed child. P's override does not reach G. The legacy map behaves as before.
  • Evidence comes from host logs and postgres. OpenObserve and Elasticsearch were not connected, so there is no trace evidence.
  • TEST-SCENARIOS.md row added: yes — test(subflow-override-lab): cover parent long-poll and scoped view overrides end to end vnext-example#22.

Notes

  • Release order: domain authors can only use the new fields once vnext-schema ships them. subFlowStateOverride / subFlowTransitionOverride have additionalProperties: false, and vnext-schema 0.0.52 rejects the new fields in npm run validate. The schema change is feat(schema): subflow long-poll and state/transition-scoped view overrides vnext-schema#139. Shipping the runtime first is safe: the fields are additive, and older runtimes ignore them in the stamp.
  • Decision recorded in docs: a roles-gated long-poll on a runtime-started child pauses only if the parent's input mapping forwards the caller's role headers. SubflowStarter sends only the headers the mapping supplies. This is the consuming parent's business decision; the runtime does not carry caller roles across the subflow start.
  • Stamps are a start-time snapshot: children already running keep the overrides they started with.
  • Non-blocking follow-ups from the final review:
    • The stamp is re-parsed on each read; one parse is small, but the step parses it on every hop.
    • An unreadable stamp is silent on the view path; the long-poll path logs 20307.
    • A view override's reference is not checked at publish time.
    • HasTransitionRoleOverrides / HasQueryRoleOverrides are now also true for maps that hold only views or interaction entries, so the names mislead.
    • No test asserts that the warning logs are emitted.
  • Council was not run: the design decisions were taken by the requester (spec dated 2026-09-23).

🤖 Generated with Claude Code

yilmaztayfun and others added 8 commits September 23, 2026 14:20
…rides

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne stamp parser

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry surface

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… child-side

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on-blocking warnings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e the view-key map

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…se transition alias for view override lookup

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The fallback job was armed with an instant that was never written to its
InstanceJobs row, so the deadline was invisible in the database. One
instant now feeds both the Dapr schedule and InstanceJob.ExecuteAt. Also
documents that role headers on a runtime-started child are the consuming
parent's responsibility (via its input mapping).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yilmaztayfun
yilmaztayfun requested review from a team September 23, 2026 20:32
@coderabbitai

coderabbitai Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 30d0ec13-989b-404b-9be5-f1d78793bd06


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yilmaztayfun yilmaztayfun self-assigned this Sep 23, 2026
@yilmaztayfun yilmaztayfun added this to the v0.0.95 milestone Sep 23, 2026
@deepsource-io

deepsource-io Bot commented Sep 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in eb98fb3...5f60ab4 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Sep 23, 2026 8:33p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@yilmaztayfun
yilmaztayfun merged commit f90a925 into master Sep 24, 2026
4 of 6 checks passed
enginkopan added a commit that referenced this pull request Sep 24, 2026
Brings the branch up to date with master (#1027 authorize single decision
point, #1028 subflow long-poll/view overrides, #1029 state-function
annotations + Aether 1.0.42). Sole conflict was docs/README.md's numbered
index: kept master's "18. The authorize Function" insertion and its
renumbering, appended the flow-telemetry entries as 43 (Transition and State
Metrics) and 44 (Function Execution Metrics). features.json, WorkflowLogs.cs
and InstanceQueryAppService.cs auto-merged cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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