Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7a265d5
Add kernel runtime management with Docker containerization (#281)
Edwardvaneechoud Jan 31, 2026
b24fa27
Add Kernel Manager UI for Python execution environments (#282)
Edwardvaneechoud Jan 31, 2026
2f1ae51
Add artifact context tracking for python_script nodes (#283)
Edwardvaneechoud Jan 31, 2026
6efdf2a
Merge branch 'main' into feauture/kernel-implementation
Edwardvaneechoud Feb 1, 2026
e6657c6
Add Python Script node with kernel and artifact support (#287)
Edwardvaneechoud Feb 1, 2026
9ee26f6
Add kernel persistence and multi-user access control (#286)
Edwardvaneechoud Feb 1, 2026
8b875d3
Add kernel runtime management with Docker containerization (#281) (#290)
Edwardvaneechoud Feb 2, 2026
fc4049f
Merge branch 'main' into feauture/kernel-implementation
Edwardvaneechoud Feb 2, 2026
7adb95a
Implement selective artifact clearing for incremental flow execution …
Edwardvaneechoud Feb 2, 2026
102522b
Merge branch 'main' into feauture/kernel-implementation
Edwardvaneechoud Feb 2, 2026
69e0a3a
fixing issue in index.ts
Edwardvaneechoud Feb 2, 2026
f358b66
Fix artifact not found on re-run when consumer deletes artifact (#294)
Edwardvaneechoud Feb 3, 2026
040c8f9
Add catalog service layer with repository pattern (#298)
Edwardvaneechoud Feb 3, 2026
431feaa
Add artifact visualization with edges and node badges (#288)
Edwardvaneechoud Feb 3, 2026
55ec489
Add synchronous kernel management and auto-restart functionality (#296)
Edwardvaneechoud Feb 3, 2026
736193f
fix ref to python image
Edwardvaneechoud Feb 3, 2026
076b3ff
adding python image
Edwardvaneechoud Feb 3, 2026
d011727
fixing img
Edwardvaneechoud Feb 3, 2026
d745da2
Add comprehensive README for kernel_runtime (#301)
Edwardvaneechoud Feb 3, 2026
84b8e41
Fix parquet corruption race condition in kernel execution (#302)
Edwardvaneechoud Feb 3, 2026
79d76a0
Add artifact persistence and recovery system to kernel runtime (#299)
Edwardvaneechoud Feb 3, 2026
477f4b4
Add interactive display outputs for notebook-like cell execution (#303)
Edwardvaneechoud Feb 4, 2026
6ac4001
Fix artifact duplication on node re-execution
claude Feb 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 54 additions & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
backend_worker: ${{ steps.filter.outputs.backend_worker }}
backend_frame: ${{ steps.filter.outputs.backend_frame }}
backend_flowfile: ${{ steps.filter.outputs.backend_flowfile }}
kernel: ${{ steps.filter.outputs.kernel }}
frontend: ${{ steps.filter.outputs.frontend }}
docs: ${{ steps.filter.outputs.docs }}
shared: ${{ steps.filter.outputs.shared }}
Expand All @@ -46,6 +47,11 @@ jobs:
- 'flowfile_frame/**'
backend_flowfile:
- 'flowfile/**'
kernel:
- 'kernel_runtime/**'
- 'flowfile_core/flowfile_core/kernel/**'
- 'flowfile_core/tests/flowfile/test_kernel_integration.py'
- 'flowfile_core/tests/kernel_fixtures.py'
frontend:
- 'flowfile_frontend/**'
docs:
Expand Down Expand Up @@ -145,7 +151,7 @@ jobs:
needs.detect-changes.outputs.shared == 'true' ||
needs.detect-changes.outputs.test_workflow == 'true' ||
github.event.inputs.run_all_tests == 'true'
run: poetry run pytest flowfile_core/tests --disable-warnings $COV_ARGS
run: poetry run pytest flowfile_core/tests -m "not kernel" --disable-warnings $COV_ARGS
env:
COV_ARGS: ${{ (matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12') && '--cov --cov-append --cov-report=' || '' }}

Expand Down Expand Up @@ -271,7 +277,7 @@ jobs:
needs.detect-changes.outputs.test_workflow == 'true' ||
github.event.inputs.run_all_tests == 'true'
shell: pwsh
run: poetry run pytest flowfile_core/tests --disable-warnings
run: poetry run pytest flowfile_core/tests -m "not kernel" --disable-warnings

- name: Run pytest for flowfile_worker
if: |
Expand Down Expand Up @@ -299,6 +305,48 @@ jobs:
shell: pwsh
run: poetry run pytest flowfile/tests --disable-warnings

# Kernel integration tests - runs in parallel on a separate worker
kernel-tests:
needs: detect-changes
if: |
needs.detect-changes.outputs.kernel == 'true' ||
needs.detect-changes.outputs.backend_core == 'true' ||
needs.detect-changes.outputs.shared == 'true' ||
needs.detect-changes.outputs.test_workflow == 'true' ||
github.event.inputs.run_all_tests == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: 'pip'

- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python -
echo "$HOME/.poetry/bin" >> $GITHUB_PATH

- name: Install Dependencies
run: |
poetry install --no-interaction --no-ansi --with dev

- name: Build kernel Docker image
run: |
docker build -t flowfile-kernel -f kernel_runtime/Dockerfile kernel_runtime/

- name: Run kernel_runtime unit tests
run: |
pip install -e "kernel_runtime/[test]"
python -m pytest kernel_runtime/tests -v --disable-warnings

- name: Run kernel integration tests
run: |
poetry run pytest flowfile_core/tests -m kernel -v --disable-warnings

# Frontend web build test - runs when frontend changes or test workflow changes
test-web:
needs: detect-changes
Expand Down Expand Up @@ -472,7 +520,7 @@ jobs:

# Summary job - always runs to provide status
test-summary:
needs: [detect-changes, backend-tests, backend-tests-windows, test-web, electron-tests-macos, electron-tests-windows, docs-test]
needs: [detect-changes, backend-tests, backend-tests-windows, kernel-tests, test-web, electron-tests-macos, electron-tests-windows, docs-test]
if: always()
runs-on: ubuntu-latest
steps:
Expand All @@ -485,6 +533,7 @@ jobs:
echo " - Backend Worker: ${{ needs.detect-changes.outputs.backend_worker }}"
echo " - Backend Frame: ${{ needs.detect-changes.outputs.backend_frame }}"
echo " - Backend Flowfile: ${{ needs.detect-changes.outputs.backend_flowfile }}"
echo " - Kernel: ${{ needs.detect-changes.outputs.kernel }}"
echo " - Frontend: ${{ needs.detect-changes.outputs.frontend }}"
echo " - Docs: ${{ needs.detect-changes.outputs.docs }}"
echo " - Shared/Dependencies: ${{ needs.detect-changes.outputs.shared }}"
Expand All @@ -493,6 +542,7 @@ jobs:
echo "Job results:"
echo " - Backend Tests: ${{ needs.backend-tests.result }}"
echo " - Backend Tests (Windows): ${{ needs.backend-tests-windows.result }}"
echo " - Kernel Tests: ${{ needs.kernel-tests.result }}"
echo " - Web Tests: ${{ needs.test-web.result }}"
echo " - Electron Tests (macOS): ${{ needs.electron-tests-macos.result }}"
echo " - Electron Tests (Windows): ${{ needs.electron-tests-windows.result }}"
Expand All @@ -501,6 +551,7 @@ jobs:
# Fail if any non-skipped job failed
if [[ "${{ needs.backend-tests.result }}" == "failure" ]] || \
[[ "${{ needs.backend-tests-windows.result }}" == "failure" ]] || \
[[ "${{ needs.kernel-tests.result }}" == "failure" ]] || \
[[ "${{ needs.test-web.result }}" == "failure" ]] || \
[[ "${{ needs.electron-tests-macos.result }}" == "failure" ]] || \
[[ "${{ needs.electron-tests-windows.result }}" == "failure" ]] || \
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ htmlcov/
# Docker
flowfile_data/

# Egg info
*.egg-info/

# Secrets and keys - NEVER commit these
master_key.txt
*.key
Expand Down
44 changes: 44 additions & 0 deletions flowfile_core/flowfile_core/catalog/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Flow Catalog service layer.

Public interface:

* ``CatalogService`` — business-logic orchestrator
* ``CatalogRepository`` — data-access protocol (for type-hints / mocking)
* ``SQLAlchemyCatalogRepository`` — concrete SQLAlchemy implementation
* Domain exceptions (``CatalogError`` hierarchy)
"""

from .exceptions import (
CatalogError,
FavoriteNotFoundError,
FlowExistsError,
FlowNotFoundError,
FollowNotFoundError,
NamespaceExistsError,
NamespaceNotEmptyError,
NamespaceNotFoundError,
NestingLimitError,
NoSnapshotError,
NotAuthorizedError,
RunNotFoundError,
)
from .repository import CatalogRepository, SQLAlchemyCatalogRepository
from .service import CatalogService

__all__ = [
"CatalogService",
"CatalogRepository",
"SQLAlchemyCatalogRepository",
"CatalogError",
"NamespaceNotFoundError",
"NamespaceExistsError",
"NestingLimitError",
"NamespaceNotEmptyError",
"FlowNotFoundError",
"FlowExistsError",
"RunNotFoundError",
"NotAuthorizedError",
"FavoriteNotFoundError",
"FollowNotFoundError",
"NoSnapshotError",
]
121 changes: 121 additions & 0 deletions flowfile_core/flowfile_core/catalog/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""Domain-specific exceptions for the Flow Catalog system.

These exceptions represent business-rule violations and are raised by the
service layer. Route handlers catch them and translate to appropriate
HTTP responses.
"""


class CatalogError(Exception):
"""Base exception for all catalog domain errors."""


class NamespaceNotFoundError(CatalogError):
"""Raised when a namespace lookup fails."""

def __init__(self, namespace_id: int | None = None, name: str | None = None):
self.namespace_id = namespace_id
self.name = name
detail = "Namespace not found"
if namespace_id is not None:
detail = f"Namespace with id={namespace_id} not found"
elif name is not None:
detail = f"Namespace '{name}' not found"
super().__init__(detail)


class NamespaceExistsError(CatalogError):
"""Raised when attempting to create a duplicate namespace."""

def __init__(self, name: str, parent_id: int | None = None):
self.name = name
self.parent_id = parent_id
super().__init__(
f"Namespace '{name}' already exists"
+ (f" under parent_id={parent_id}" if parent_id is not None else " at root level")
)


class NestingLimitError(CatalogError):
"""Raised when attempting to nest namespaces deeper than catalog -> schema."""

def __init__(self, parent_id: int, parent_level: int):
self.parent_id = parent_id
self.parent_level = parent_level
super().__init__("Cannot nest deeper than catalog -> schema")


class NamespaceNotEmptyError(CatalogError):
"""Raised when trying to delete a namespace that still has children or flows."""

def __init__(self, namespace_id: int, children: int = 0, flows: int = 0):
self.namespace_id = namespace_id
self.children = children
self.flows = flows
super().__init__("Cannot delete namespace with children or flows")


class FlowNotFoundError(CatalogError):
"""Raised when a flow registration lookup fails."""

def __init__(self, registration_id: int | None = None, name: str | None = None):
self.registration_id = registration_id
self.name = name
detail = "Flow not found"
if registration_id is not None:
detail = f"Flow with id={registration_id} not found"
elif name is not None:
detail = f"Flow '{name}' not found"
super().__init__(detail)


class FlowExistsError(CatalogError):
"""Raised when attempting to create a duplicate flow registration."""

def __init__(self, name: str, namespace_id: int | None = None):
self.name = name
self.namespace_id = namespace_id
super().__init__(f"Flow '{name}' already exists in namespace_id={namespace_id}")


class RunNotFoundError(CatalogError):
"""Raised when a flow run lookup fails."""

def __init__(self, run_id: int):
self.run_id = run_id
super().__init__(f"Run with id={run_id} not found")


class NotAuthorizedError(CatalogError):
"""Raised when a user attempts an action they are not permitted to perform."""

def __init__(self, user_id: int, action: str = "perform this action"):
self.user_id = user_id
self.action = action
super().__init__(f"User {user_id} is not authorized to {action}")


class FavoriteNotFoundError(CatalogError):
"""Raised when a favorite record is not found."""

def __init__(self, user_id: int, registration_id: int):
self.user_id = user_id
self.registration_id = registration_id
super().__init__(f"Favorite not found for user={user_id}, flow={registration_id}")


class FollowNotFoundError(CatalogError):
"""Raised when a follow record is not found."""

def __init__(self, user_id: int, registration_id: int):
self.user_id = user_id
self.registration_id = registration_id
super().__init__(f"Follow not found for user={user_id}, flow={registration_id}")


class NoSnapshotError(CatalogError):
"""Raised when a run has no flow snapshot available."""

def __init__(self, run_id: int):
self.run_id = run_id
super().__init__(f"No flow snapshot available for run id={run_id}")
Loading