cf-workspace-store 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ cmake_minimum_required(VERSION 3.20)
2
+ project(cf_workspace_store LANGUAGES CXX)
3
+
4
+ add_subdirectory(src/cf_workspace_store/native)
5
+
6
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/cf_workspace_store/provider_descriptor.v1.json DESTINATION cf_workspace_store)
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.2
2
+ Name: cf-workspace-store
3
+ Version: 0.1.0
4
+ Summary: Embedded workspace store provider for Cogniflow
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: duckdb<2.0,>=0.10
7
+ Requires-Dist: cf-package-contracts>=0.1.0
8
+ Requires-Dist: cf-service-contracts>=0.1.0
9
+ Provides-Extra: test
10
+ Requires-Dist: pytest<9.0,>=8.0; extra == "test"
11
+ Description-Content-Type: text/markdown
12
+
13
+ # cf_workspace_store
14
+
15
+ `cf_workspace_store` owns the embedded workspace-store infrastructure surface for Cogniflow.
16
+
17
+ It provides:
18
+
19
+ - a Python API for opening the runtime-state named store plus ephemeral sessions
20
+ - a native `CfWorkspaceStoreContractV1` provider for other native packages
21
+
22
+ DuckDB is the first backend implementation, but DuckDB-specific handles and types stay internal to this package.
23
+
24
+ `pipeline_states` is the only public named store surface.
25
+ Productive RDF semantics should use `ProviderKey.SEMANTICS_STORE`; old RDF DuckDB files are cleanup-only internals.
@@ -0,0 +1,13 @@
1
+ # cf_workspace_store
2
+
3
+ `cf_workspace_store` owns the embedded workspace-store infrastructure surface for Cogniflow.
4
+
5
+ It provides:
6
+
7
+ - a Python API for opening the runtime-state named store plus ephemeral sessions
8
+ - a native `CfWorkspaceStoreContractV1` provider for other native packages
9
+
10
+ DuckDB is the first backend implementation, but DuckDB-specific handles and types stay internal to this package.
11
+
12
+ `pipeline_states` is the only public named store surface.
13
+ Productive RDF semantics should use `ProviderKey.SEMANTICS_STORE`; old RDF DuckDB files are cleanup-only internals.
@@ -0,0 +1,40 @@
1
+ schema_version: 1
2
+ package_id: cf-workspace-store
3
+ defaults:
4
+ owner: cf-team
5
+ last_reviewed: '2026-03-30'
6
+ files:
7
+ entries:
8
+ - path: CMakeLists.txt
9
+ status: current
10
+ purpose: build
11
+ - path: README.md
12
+ status: current
13
+ purpose: docs
14
+ - path: pyproject.toml
15
+ status: current
16
+ purpose: build
17
+ - path: src/cf_workspace_store/__init__.py
18
+ status: current
19
+ purpose: source
20
+ - path: src/cf_workspace_store/paths.py
21
+ status: current
22
+ purpose: source
23
+ - path: src/cf_workspace_store/store.py
24
+ status: current
25
+ purpose: source
26
+ - path: src/cf_workspace_store/provider_descriptor.v1.json
27
+ status: current
28
+ purpose: runtime
29
+ - path: src/cf_workspace_store/native/CMakeLists.txt
30
+ status: current
31
+ purpose: build
32
+ - path: src/cf_workspace_store/native/workspace_store_contract_provider.cpp
33
+ status: current
34
+ purpose: source
35
+ - path: tests/test_install_surface.py
36
+ status: current
37
+ purpose: test
38
+ - path: tests/test_store.py
39
+ status: current
40
+ purpose: test
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = [
3
+ "scikit-build-core>=0.9",
4
+ "cf-package-contracts>=0.1.0",
5
+ ]
6
+ build-backend = "scikit_build_core.build"
7
+
8
+ [project]
9
+ name = "cf-workspace-store"
10
+ version = "0.1.0"
11
+ description = "Embedded workspace store provider for Cogniflow"
12
+ readme = "README.md"
13
+ requires-python = ">=3.11"
14
+ dependencies = [
15
+ "duckdb>=0.10,<2.0",
16
+ "cf-package-contracts>=0.1.0",
17
+ "cf-service-contracts>=0.1.0",
18
+ ]
19
+
20
+ [project.entry-points."cogniflow.service_providers"]
21
+ workspace_store = "cf_workspace_store.service_contract_provider:provide_workspace_store_contract"
22
+
23
+ [project.optional-dependencies]
24
+ test = [
25
+ "pytest>=8.0,<9.0",
26
+ ]
27
+
28
+ [tool.scikit-build]
29
+ wheel.packages = ["src/cf_workspace_store"]
30
+ build-dir = "../../.native_deps/scikit-build/cf-workspace-store"
31
+
32
+ [tool.scikit-build.sdist]
33
+ include = [
34
+ "CMakeLists.txt",
35
+ "README.md",
36
+ "cf-package.yaml",
37
+ "src/cf_workspace_store/**",
38
+ "tests/**"
39
+ ]
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import metadata
4
+ from pathlib import Path
5
+
6
+ from .paths import (
7
+ DEFAULT_SEMANTICS_DB_FILES,
8
+ WorkspaceStoreKind,
9
+ WorkspaceStorePaths,
10
+ find_repo_root,
11
+ resolve_named_store_path,
12
+ resolve_semantics_dir,
13
+ resolve_workspace_dir,
14
+ resolve_workspace_store_paths,
15
+ )
16
+ from .store import (
17
+ ConstraintException,
18
+ WorkspaceQueryResult,
19
+ WorkspaceStore,
20
+ WorkspaceStoreSession,
21
+ open_ephemeral,
22
+ open_named,
23
+ open_path,
24
+ )
25
+
26
+ __version__ = "0.1.0"
27
+
28
+
29
+ def _locate_distribution_file(*relative_candidates: str) -> Path:
30
+ distribution = metadata.distribution("cf-workspace-store")
31
+ files = distribution.files or []
32
+ for candidate in relative_candidates:
33
+ for entry in files:
34
+ if entry.as_posix() == candidate:
35
+ return Path(distribution.locate_file(entry)).resolve()
36
+ raise FileNotFoundError(
37
+ f"Could not locate any of {relative_candidates!r} in cf-workspace-store distribution files."
38
+ )
39
+
40
+
41
+ def _native_root() -> Path:
42
+ try:
43
+ return _locate_distribution_file(
44
+ "cf_workspace_store/native/bin/cf_workspace_store_provider.dll",
45
+ "cf_workspace_store/native/bin/libcf_workspace_store_provider.dll",
46
+ "cf_workspace_store/native/lib/libcf_workspace_store_provider.so",
47
+ "cf_workspace_store/native/lib/libcf_workspace_store_provider.dylib",
48
+ ).parents[1]
49
+ except Exception:
50
+ return Path(__file__).resolve().parent / "native"
51
+
52
+
53
+ def cf_workspace_store_provider_path() -> str:
54
+ native_root = _native_root()
55
+ candidates = [
56
+ native_root / "bin" / "cf_workspace_store_provider.dll",
57
+ native_root / "bin" / "libcf_workspace_store_provider.dll",
58
+ native_root / "lib" / "libcf_workspace_store_provider.so",
59
+ native_root / "lib" / "libcf_workspace_store_provider.dylib",
60
+ native_root / "lib" / "cf_workspace_store_provider.dll",
61
+ native_root / "lib" / "libcf_workspace_store_provider.dll",
62
+ ]
63
+ for candidate in candidates:
64
+ if candidate.is_file():
65
+ return str(candidate)
66
+ raise FileNotFoundError("Packaged cf_workspace_store provider library not found.")
67
+
68
+
69
+ def cf_workspace_store_provider_descriptor_path() -> str:
70
+ try:
71
+ return str(_locate_distribution_file("cf_workspace_store/provider_descriptor.v1.json"))
72
+ except Exception:
73
+ return str(Path(__file__).resolve().parent / "provider_descriptor.v1.json")
74
+
75
+
76
+ __all__ = [
77
+ "__version__",
78
+ "DEFAULT_SEMANTICS_DB_FILES",
79
+ "WorkspaceQueryResult",
80
+ "WorkspaceStore",
81
+ "WorkspaceStoreKind",
82
+ "WorkspaceStorePaths",
83
+ "WorkspaceStoreSession",
84
+ "ConstraintException",
85
+ "cf_workspace_store_provider_descriptor_path",
86
+ "cf_workspace_store_provider_path",
87
+ "find_repo_root",
88
+ "open_ephemeral",
89
+ "open_named",
90
+ "open_path",
91
+ "resolve_named_store_path",
92
+ "resolve_semantics_dir",
93
+ "resolve_workspace_dir",
94
+ "resolve_workspace_store_paths",
95
+ ]
@@ -0,0 +1,194 @@
1
+ cmake_minimum_required(VERSION 3.20)
2
+ project(cf_workspace_store_provider LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+
7
+ include(GNUInstallDirs)
8
+
9
+ function(_cf_workspace_store_first_existing_path out_var)
10
+ foreach(_candidate IN LISTS ARGN)
11
+ if (NOT _candidate STREQUAL "" AND EXISTS "${_candidate}")
12
+ set(${out_var} "${_candidate}" PARENT_SCOPE)
13
+ return()
14
+ endif()
15
+ endforeach()
16
+ set(${out_var} "" PARENT_SCOPE)
17
+ endfunction()
18
+
19
+ function(_cf_workspace_store_find_repo_native_duckdb_root out_var)
20
+ set(_search_roots
21
+ "${CMAKE_SOURCE_DIR}"
22
+ "${PROJECT_SOURCE_DIR}"
23
+ "${CMAKE_CURRENT_SOURCE_DIR}"
24
+ )
25
+
26
+ foreach(_search_root IN LISTS _search_roots)
27
+ if (_search_root STREQUAL "" OR NOT IS_DIRECTORY "${_search_root}")
28
+ continue()
29
+ endif()
30
+
31
+ set(_cursor "${_search_root}")
32
+ while (TRUE)
33
+ set(_candidate "${_cursor}/.native_deps/duckdb")
34
+ if (EXISTS "${_candidate}")
35
+ set(${out_var} "${_candidate}" PARENT_SCOPE)
36
+ return()
37
+ endif()
38
+
39
+ get_filename_component(_parent "${_cursor}" DIRECTORY)
40
+ if (_parent STREQUAL "${_cursor}")
41
+ break()
42
+ endif()
43
+ set(_cursor "${_parent}")
44
+ endwhile()
45
+ endforeach()
46
+
47
+ set(${out_var} "" PARENT_SCOPE)
48
+ endfunction()
49
+
50
+ function(_cf_workspace_store_find_cache_native_duckdb_root out_var)
51
+ set(_version_candidates "")
52
+ if (NOT "$ENV{DUCKDB_VERSION}" STREQUAL "")
53
+ list(APPEND _version_candidates "$ENV{DUCKDB_VERSION}")
54
+ endif()
55
+ list(APPEND _version_candidates "1.4.2")
56
+ list(REMOVE_DUPLICATES _version_candidates)
57
+
58
+ set(_triplet_candidates
59
+ "x64-windows-static"
60
+ "x64-windows"
61
+ )
62
+
63
+ set(_cache_roots
64
+ "$ENV{CF_NATIVE_CACHE_DIR}/duckdb"
65
+ "$ENV{USERPROFILE}/.cogniflow/cache/native/duckdb"
66
+ "$ENV{HOME}/.cogniflow/cache/native/duckdb"
67
+ )
68
+
69
+ foreach(_cache_root IN LISTS _cache_roots)
70
+ if (_cache_root STREQUAL "" OR NOT IS_DIRECTORY "${_cache_root}")
71
+ continue()
72
+ endif()
73
+
74
+ foreach(_version IN LISTS _version_candidates)
75
+ foreach(_triplet IN LISTS _triplet_candidates)
76
+ set(_candidate "${_cache_root}/${_version}/${_triplet}")
77
+ if (EXISTS "${_candidate}")
78
+ set(${out_var} "${_candidate}" PARENT_SCOPE)
79
+ return()
80
+ endif()
81
+ endforeach()
82
+ endforeach()
83
+
84
+ file(GLOB _cache_version_dirs LIST_DIRECTORIES true "${_cache_root}/*")
85
+ list(SORT _cache_version_dirs COMPARE NATURAL ORDER DESCENDING)
86
+ foreach(_version_dir IN LISTS _cache_version_dirs)
87
+ if (NOT IS_DIRECTORY "${_version_dir}")
88
+ continue()
89
+ endif()
90
+ foreach(_triplet IN LISTS _triplet_candidates)
91
+ set(_candidate "${_version_dir}/${_triplet}")
92
+ if (EXISTS "${_candidate}")
93
+ set(${out_var} "${_candidate}" PARENT_SCOPE)
94
+ return()
95
+ endif()
96
+ endforeach()
97
+ endforeach()
98
+ endforeach()
99
+
100
+ set(${out_var} "" PARENT_SCOPE)
101
+ endfunction()
102
+
103
+ get_filename_component(_CF_REPO_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../../.." ABSOLUTE)
104
+ include("${_CF_REPO_ROOT}/tools/cmake/cf_contracts_include.cmake")
105
+ set(CF_SDK_INCLUDE "${CF_CONTRACTS_INCLUDE}")
106
+
107
+ set(_native_duckdb_root "$ENV{CF_WORKSPACE_STORE_DUCKDB_ROOT}")
108
+ if (_native_duckdb_root STREQUAL "")
109
+ _cf_workspace_store_find_cache_native_duckdb_root(_native_duckdb_root)
110
+ endif()
111
+ if (_native_duckdb_root STREQUAL "")
112
+ _cf_workspace_store_find_repo_native_duckdb_root(_native_duckdb_root)
113
+ endif()
114
+ if (_native_duckdb_root STREQUAL "")
115
+ set(_native_duckdb_root "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../.native_deps/duckdb")
116
+ endif()
117
+ file(TO_CMAKE_PATH "${_native_duckdb_root}" _native_duckdb_root)
118
+
119
+ set(_native_duckdb_include "${_native_duckdb_root}/include")
120
+ set(_native_duckdb_include_flat "${_native_duckdb_root}")
121
+ set(_native_duckdb_src "${_native_duckdb_root}/src")
122
+ set(_native_duckdb_lib_win "${_native_duckdb_root}/lib/duckdb.lib")
123
+ set(_native_duckdb_lib_mingw "${_native_duckdb_root}/lib/libduckdb.a")
124
+ set(_native_duckdb_lib_flat "${_native_duckdb_root}/duckdb.lib")
125
+ set(_native_duckdb_src_cpp "${_native_duckdb_src}/duckdb.cpp")
126
+ set(_native_duckdb_runtime_dll "${_native_duckdb_root}/bin/duckdb.dll")
127
+
128
+ set(_duckdb_include_candidates
129
+ "$ENV{CF_WORKSPACE_STORE_DUCKDB_INCLUDE}"
130
+ "$ENV{CF_DATAHIVE_CPP_DUCKDB_INCLUDE}"
131
+ "${_native_duckdb_src}"
132
+ "${_native_duckdb_include}"
133
+ "${_native_duckdb_include_flat}"
134
+ )
135
+ _cf_workspace_store_first_existing_path(
136
+ CF_WORKSPACE_STORE_DUCKDB_INCLUDE_DIR
137
+ ${_duckdb_include_candidates}
138
+ )
139
+ if (NOT CF_WORKSPACE_STORE_DUCKDB_INCLUDE_DIR STREQUAL "")
140
+ file(TO_CMAKE_PATH "${CF_WORKSPACE_STORE_DUCKDB_INCLUDE_DIR}" CF_WORKSPACE_STORE_DUCKDB_INCLUDE_DIR)
141
+ endif()
142
+
143
+ set(_duckdb_library_candidates
144
+ "$ENV{CF_WORKSPACE_STORE_DUCKDB_LIBRARY}"
145
+ "$ENV{CF_WORKSPACE_STORE_DUCKDB_LIB}"
146
+ "$ENV{CF_DATAHIVE_CPP_DUCKDB_LIB}"
147
+ "${_native_duckdb_lib_win}"
148
+ "${_native_duckdb_lib_mingw}"
149
+ "${_native_duckdb_lib_flat}"
150
+ )
151
+ _cf_workspace_store_first_existing_path(
152
+ CF_WORKSPACE_STORE_DUCKDB_LIBRARY
153
+ ${_duckdb_library_candidates}
154
+ )
155
+ if (NOT CF_WORKSPACE_STORE_DUCKDB_LIBRARY STREQUAL "")
156
+ file(TO_CMAKE_PATH "${CF_WORKSPACE_STORE_DUCKDB_LIBRARY}" CF_WORKSPACE_STORE_DUCKDB_LIBRARY)
157
+ endif()
158
+
159
+ if (NOT _native_duckdb_runtime_dll STREQUAL "")
160
+ file(TO_CMAKE_PATH "${_native_duckdb_runtime_dll}" _native_duckdb_runtime_dll)
161
+ endif()
162
+
163
+ if (CF_WORKSPACE_STORE_DUCKDB_INCLUDE_DIR STREQUAL "" OR NOT EXISTS "${CF_WORKSPACE_STORE_DUCKDB_INCLUDE_DIR}/duckdb.h")
164
+ message(FATAL_ERROR "DuckDB headers not found. Resolved root='${_native_duckdb_root}', include='${CF_WORKSPACE_STORE_DUCKDB_INCLUDE_DIR}'.")
165
+ endif()
166
+
167
+ if (CF_WORKSPACE_STORE_DUCKDB_LIBRARY STREQUAL "" OR NOT EXISTS "${CF_WORKSPACE_STORE_DUCKDB_LIBRARY}")
168
+ message(FATAL_ERROR "DuckDB library not found. Resolved root='${_native_duckdb_root}', library='${CF_WORKSPACE_STORE_DUCKDB_LIBRARY}'.")
169
+ endif()
170
+
171
+ add_library(cf_workspace_store_provider SHARED
172
+ workspace_store_contract_provider.cpp
173
+ )
174
+
175
+ target_compile_definitions(cf_workspace_store_provider PRIVATE CF_STEP_ABI_EXPORTS)
176
+ target_include_directories(cf_workspace_store_provider PRIVATE
177
+ ${CF_SDK_INCLUDE}
178
+ ${CF_WORKSPACE_STORE_DUCKDB_INCLUDE_DIR}
179
+ )
180
+ target_link_libraries(cf_workspace_store_provider PRIVATE
181
+ ${CF_WORKSPACE_STORE_DUCKDB_LIBRARY}
182
+ )
183
+ set_target_properties(cf_workspace_store_provider PROPERTIES
184
+ PREFIX ""
185
+ )
186
+
187
+ install(TARGETS cf_workspace_store_provider
188
+ RUNTIME DESTINATION cf_workspace_store/native/bin
189
+ LIBRARY DESTINATION cf_workspace_store/native/lib
190
+ )
191
+
192
+ if (WIN32 AND EXISTS "${_native_duckdb_runtime_dll}")
193
+ install(FILES ${_native_duckdb_runtime_dll} DESTINATION cf_workspace_store/native/bin)
194
+ endif()
@@ -0,0 +1,309 @@
1
+ #include "cf_step_abi.h"
2
+
3
+ #include <cstdint>
4
+ #include <cstdlib>
5
+ #include <cstring>
6
+ #include <filesystem>
7
+ #include <memory>
8
+ #include <optional>
9
+ #include <random>
10
+ #include <string>
11
+
12
+ #include "duckdb.h"
13
+
14
+ namespace {
15
+
16
+ struct StoreHandle {
17
+ duckdb_database db = nullptr;
18
+ duckdb_connection conn = nullptr;
19
+
20
+ ~StoreHandle() {
21
+ if (conn != nullptr) {
22
+ duckdb_disconnect(&conn);
23
+ }
24
+ if (db != nullptr) {
25
+ duckdb_close(&db);
26
+ }
27
+ }
28
+ };
29
+
30
+ std::string copy_text(const char* value, size_t size) {
31
+ if (value == nullptr || size == 0) {
32
+ return {};
33
+ }
34
+ return std::string(value, size);
35
+ }
36
+
37
+ CfStatusCode alloc_text(CfExecutionContext* ctx, const std::string& text, const char** out_text, size_t* out_size) {
38
+ if (out_text == nullptr || out_size == nullptr || ctx == nullptr || ctx->allocator == nullptr || ctx->allocator->alloc == nullptr) {
39
+ return CF_STATUS_ERROR;
40
+ }
41
+ *out_size = text.size();
42
+ if (text.empty()) {
43
+ *out_text = nullptr;
44
+ return CF_STATUS_OK;
45
+ }
46
+ char* buffer = static_cast<char*>(ctx->allocator->alloc(text.size() + 1u, ctx->allocator->user_data));
47
+ if (buffer == nullptr) {
48
+ return CF_STATUS_ERROR;
49
+ }
50
+ std::memcpy(buffer, text.data(), text.size());
51
+ buffer[text.size()] = '\0';
52
+ *out_text = buffer;
53
+ return CF_STATUS_OK;
54
+ }
55
+
56
+ std::filesystem::path resolve_named_path(const CfWorkspaceStoreOpenRequestV1& request) {
57
+ std::filesystem::path semantics_dir;
58
+ const std::string explicit_semantics_dir = copy_text(request.semantics_dir, request.semantics_dir_size);
59
+ if (!explicit_semantics_dir.empty()) {
60
+ semantics_dir = explicit_semantics_dir;
61
+ } else {
62
+ std::filesystem::path workspace_root;
63
+ const std::string explicit_workspace_root = copy_text(request.workspace_root, request.workspace_root_size);
64
+ if (!explicit_workspace_root.empty()) {
65
+ workspace_root = explicit_workspace_root;
66
+ } else {
67
+ workspace_root = std::filesystem::current_path() / "workspace";
68
+ }
69
+ semantics_dir = workspace_root / "semantics";
70
+ }
71
+
72
+ const std::string kind = copy_text(request.store_kind, request.store_kind_size);
73
+ if (kind != "pipeline_states") {
74
+ return {};
75
+ }
76
+ return semantics_dir / "cf-pipeline-states.store.db";
77
+ }
78
+
79
+ std::string escape_sql_literal(const std::string& value) {
80
+ std::string escaped;
81
+ escaped.reserve(value.size() + 8u);
82
+ for (char ch : value) {
83
+ escaped.push_back(ch);
84
+ if (ch == '\'') {
85
+ escaped.push_back('\'');
86
+ }
87
+ }
88
+ return escaped;
89
+ }
90
+
91
+ CfStatusCode write_error(CfExecutionContext* ctx, const std::string& text, const char** out_error, size_t* out_error_size) {
92
+ if (out_error == nullptr || out_error_size == nullptr) {
93
+ return CF_STATUS_ERROR;
94
+ }
95
+ return alloc_text(ctx, text, out_error, out_error_size);
96
+ }
97
+
98
+ StoreHandle* unwrap(void* raw_handle) {
99
+ return static_cast<StoreHandle*>(raw_handle);
100
+ }
101
+
102
+ CfStatusCode open_store_at_path(
103
+ CfExecutionContext* ctx,
104
+ const std::filesystem::path& path,
105
+ int read_only,
106
+ void** out_store,
107
+ const char** out_error,
108
+ size_t* out_error_size) {
109
+ if (out_store == nullptr) {
110
+ return CF_STATUS_INVALID;
111
+ }
112
+ *out_store = nullptr;
113
+ if (!read_only) {
114
+ std::error_code ec;
115
+ std::filesystem::create_directories(path.parent_path(), ec);
116
+ if (ec) {
117
+ write_error(ctx, "failed to create workspace store directory", out_error, out_error_size);
118
+ return CF_STATUS_ERROR;
119
+ }
120
+ }
121
+
122
+ std::unique_ptr<StoreHandle> handle(new StoreHandle());
123
+ duckdb_config config = nullptr;
124
+ if (duckdb_create_config(&config) != DuckDBSuccess) {
125
+ write_error(ctx, "duckdb_create_config failed for workspace store", out_error, out_error_size);
126
+ return CF_STATUS_ERROR;
127
+ }
128
+ if (read_only) {
129
+ duckdb_set_config(config, "access_mode", "READ_ONLY");
130
+ }
131
+ if (duckdb_open_ext(path.string().c_str(), &handle->db, config, nullptr) != DuckDBSuccess) {
132
+ duckdb_destroy_config(&config);
133
+ write_error(ctx, "duckdb_open failed for workspace store", out_error, out_error_size);
134
+ return CF_STATUS_ERROR;
135
+ }
136
+ duckdb_destroy_config(&config);
137
+ if (duckdb_connect(handle->db, &handle->conn) != DuckDBSuccess) {
138
+ write_error(ctx, "duckdb_connect failed for workspace store", out_error, out_error_size);
139
+ return CF_STATUS_ERROR;
140
+ }
141
+ if (read_only) {
142
+ duckdb_result result {};
143
+ duckdb_query(handle->conn, "PRAGMA disable_checkpoint_on_shutdown", &result);
144
+ duckdb_destroy_result(&result);
145
+ }
146
+ *out_store = handle.release();
147
+ return CF_STATUS_OK;
148
+ }
149
+
150
+ CfStatusCode open_named_store(
151
+ CfExecutionContext* ctx,
152
+ const CfWorkspaceStoreOpenRequestV1* request,
153
+ void** out_store,
154
+ const char** out_error,
155
+ size_t* out_error_size) {
156
+ if (request == nullptr || request->version != 1u) {
157
+ return CF_STATUS_INVALID;
158
+ }
159
+ const std::string kind = copy_text(request->store_kind, request->store_kind_size);
160
+ if (kind != "pipeline_states") {
161
+ write_error(ctx, "unsupported named workspace store kind", out_error, out_error_size);
162
+ return CF_STATUS_INVALID;
163
+ }
164
+ return open_store_at_path(ctx, resolve_named_path(*request), request->read_only, out_store, out_error, out_error_size);
165
+ }
166
+
167
+ CfStatusCode open_ephemeral_store(
168
+ CfExecutionContext* ctx,
169
+ const CfWorkspaceStoreOpenRequestV1* request,
170
+ void** out_store,
171
+ const char** out_error,
172
+ size_t* out_error_size) {
173
+ std::mt19937_64 rng(std::random_device{}());
174
+ std::uniform_int_distribution<uint64_t> dist;
175
+ const auto temp_dir =
176
+ std::filesystem::temp_directory_path() /
177
+ std::filesystem::path("cf_workspace_store_ephemeral_" + std::to_string(dist(rng)) + ".db");
178
+ const int read_only = request != nullptr ? request->read_only : 0;
179
+ return open_store_at_path(ctx, temp_dir, read_only, out_store, out_error, out_error_size);
180
+ }
181
+
182
+ CfStatusCode close_store(CfExecutionContext*, void* store_handle) {
183
+ delete unwrap(store_handle);
184
+ return CF_STATUS_OK;
185
+ }
186
+
187
+ CfStatusCode exec_sql(
188
+ CfExecutionContext* ctx,
189
+ const CfWorkspaceStoreExecRequestV1* request,
190
+ const char** out_error,
191
+ size_t* out_error_size) {
192
+ if (request == nullptr || request->version != 1u || request->store_handle == nullptr) {
193
+ return CF_STATUS_INVALID;
194
+ }
195
+ duckdb_result result {};
196
+ const std::string sql = copy_text(request->sql, request->sql_size);
197
+ if (duckdb_query(unwrap(request->store_handle)->conn, sql.c_str(), &result) != DuckDBSuccess) {
198
+ const char* detail = duckdb_result_error(&result);
199
+ std::string error = "workspace store query failed";
200
+ if (detail != nullptr && detail[0] != '\0') {
201
+ error += ": ";
202
+ error += detail;
203
+ }
204
+ duckdb_destroy_result(&result);
205
+ write_error(ctx, error, out_error, out_error_size);
206
+ return CF_STATUS_ERROR;
207
+ }
208
+ duckdb_destroy_result(&result);
209
+ return CF_STATUS_OK;
210
+ }
211
+
212
+ CfStatusCode query_scalar_text(
213
+ CfExecutionContext* ctx,
214
+ const CfWorkspaceStoreScalarTextRequestV1* request,
215
+ const char** out_text,
216
+ size_t* out_size,
217
+ const char** out_error,
218
+ size_t* out_error_size) {
219
+ if (request == nullptr || request->version != 1u || request->store_handle == nullptr) {
220
+ return CF_STATUS_INVALID;
221
+ }
222
+ duckdb_result result {};
223
+ const std::string sql = copy_text(request->sql, request->sql_size);
224
+ if (duckdb_query(unwrap(request->store_handle)->conn, sql.c_str(), &result) != DuckDBSuccess) {
225
+ const char* detail = duckdb_result_error(&result);
226
+ std::string error = "workspace store scalar text query failed";
227
+ if (detail != nullptr && detail[0] != '\0') {
228
+ error += ": ";
229
+ error += detail;
230
+ }
231
+ duckdb_destroy_result(&result);
232
+ write_error(ctx, error, out_error, out_error_size);
233
+ return CF_STATUS_ERROR;
234
+ }
235
+ if (duckdb_row_count(&result) == 0 || duckdb_column_count(&result) == 0) {
236
+ duckdb_destroy_result(&result);
237
+ return alloc_text(ctx, "", out_text, out_size);
238
+ }
239
+ const char* value = duckdb_value_varchar(&result, 0, 0);
240
+ const std::string text = value != nullptr ? value : "";
241
+ duckdb_free(const_cast<char*>(value));
242
+ duckdb_destroy_result(&result);
243
+ return alloc_text(ctx, text, out_text, out_size);
244
+ }
245
+
246
+ CfStatusCode query_scalar_i64(
247
+ CfExecutionContext*,
248
+ const CfWorkspaceStoreScalarI64RequestV1* request,
249
+ int64_t* out_value,
250
+ const char**,
251
+ size_t*) {
252
+ if (request == nullptr || request->version != 1u || request->store_handle == nullptr || out_value == nullptr) {
253
+ return CF_STATUS_INVALID;
254
+ }
255
+ duckdb_result result {};
256
+ const std::string sql = copy_text(request->sql, request->sql_size);
257
+ if (duckdb_query(unwrap(request->store_handle)->conn, sql.c_str(), &result) != DuckDBSuccess) {
258
+ duckdb_destroy_result(&result);
259
+ return CF_STATUS_ERROR;
260
+ }
261
+ *out_value = (duckdb_row_count(&result) == 0 || duckdb_column_count(&result) == 0) ? 0 : duckdb_value_int64(&result, 0, 0);
262
+ duckdb_destroy_result(&result);
263
+ return CF_STATUS_OK;
264
+ }
265
+
266
+ CfStatusCode export_query_to_parquet(
267
+ CfExecutionContext* ctx,
268
+ const CfWorkspaceStoreExportParquetRequestV1* request,
269
+ const char** out_error,
270
+ size_t* out_error_size) {
271
+ if (request == nullptr || request->version != 1u || request->store_handle == nullptr) {
272
+ return CF_STATUS_INVALID;
273
+ }
274
+ const std::string sql = copy_text(request->sql, request->sql_size);
275
+ const std::string output_path = copy_text(request->output_path, request->output_path_size);
276
+ const std::string command = "COPY (" + sql + ") TO '" + escape_sql_literal(output_path) + "' (FORMAT PARQUET)";
277
+ duckdb_result result {};
278
+ if (duckdb_query(unwrap(request->store_handle)->conn, command.c_str(), &result) != DuckDBSuccess) {
279
+ const char* detail = duckdb_result_error(&result);
280
+ std::string error = "workspace store parquet export failed";
281
+ if (detail != nullptr && detail[0] != '\0') {
282
+ error += ": ";
283
+ error += detail;
284
+ }
285
+ duckdb_destroy_result(&result);
286
+ write_error(ctx, error, out_error, out_error_size);
287
+ return CF_STATUS_ERROR;
288
+ }
289
+ duckdb_destroy_result(&result);
290
+ return CF_STATUS_OK;
291
+ }
292
+
293
+ const CfWorkspaceStoreContractV1 kWorkspaceStoreContractV1 = {
294
+ 1u,
295
+ 0u,
296
+ open_named_store,
297
+ open_ephemeral_store,
298
+ close_store,
299
+ exec_sql,
300
+ query_scalar_text,
301
+ query_scalar_i64,
302
+ export_query_to_parquet,
303
+ };
304
+
305
+ } // namespace
306
+
307
+ extern "C" CF_EXPORT const CfWorkspaceStoreContractV1* cf_get_workspace_store_contract_v1() {
308
+ return &kWorkspaceStoreContractV1;
309
+ }
@@ -0,0 +1,61 @@
1
+ """Workspace-local embedded store path helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Literal, Optional
8
+
9
+ from cf_service_contracts.cogniflow_paths import resolve_workspace_dir
10
+
11
+
12
+ WorkspaceStoreKind = Literal["pipeline_states"]
13
+
14
+ DEFAULT_SEMANTICS_DB_FILES: dict[WorkspaceStoreKind, str] = {
15
+ "pipeline_states": "cf-pipeline-states.store.db",
16
+ }
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class WorkspaceStorePaths:
21
+ pipeline_states: Path
22
+
23
+ def as_dict(self) -> dict[str, Path]:
24
+ return {
25
+ "pipeline_states": self.pipeline_states,
26
+ }
27
+
28
+
29
+ def find_repo_root(start: Optional[Path] = None) -> Optional[Path]:
30
+ probe = (start or Path.cwd()).resolve()
31
+ for candidate in (probe, *probe.parents):
32
+ if (candidate / ".git").exists():
33
+ return candidate
34
+ if (candidate / "sandcastle").is_dir() and (candidate / ".github").is_dir():
35
+ return candidate
36
+ return None
37
+
38
+
39
+ def resolve_semantics_dir() -> Path:
40
+ return (resolve_workspace_dir() / "semantics").resolve()
41
+
42
+
43
+ def resolve_named_store_path(
44
+ kind: WorkspaceStoreKind,
45
+ semantics_dir: Optional[Path] = None,
46
+ ) -> Path:
47
+ if str(kind) != "pipeline_states":
48
+ raise ValueError(
49
+ f"Named workspace store kind '{kind}' is not supported. Only 'pipeline_states' remains public."
50
+ )
51
+ base = semantics_dir or resolve_semantics_dir()
52
+ return base / DEFAULT_SEMANTICS_DB_FILES[kind]
53
+
54
+
55
+ def resolve_workspace_store_paths(
56
+ semantics_dir: Optional[Path] = None,
57
+ ) -> WorkspaceStorePaths:
58
+ base = semantics_dir or resolve_semantics_dir()
59
+ return WorkspaceStorePaths(
60
+ pipeline_states=base / DEFAULT_SEMANTICS_DB_FILES["pipeline_states"],
61
+ )
@@ -0,0 +1,18 @@
1
+ {
2
+ "schema_version": "provider_descriptor.v1",
3
+ "provider_key": "workspace_store",
4
+ "contract_symbol": "cf_get_workspace_store_contract_v1",
5
+ "contract_version": 1,
6
+ "library_candidates": [
7
+ "cf_workspace_store/native/bin/cf_workspace_store_provider.dll",
8
+ "cf_workspace_store/native/bin/libcf_workspace_store_provider.dll",
9
+ "cf_workspace_store/native/lib/cf_workspace_store_provider.dll",
10
+ "cf_workspace_store/native/lib/libcf_workspace_store_provider.dll",
11
+ "cf_workspace_store/native/lib/libcf_workspace_store_provider.so",
12
+ "cf_workspace_store/native/lib/libcf_workspace_store_provider.dylib"
13
+ ],
14
+ "runtime_dir_candidates": [
15
+ "cf_workspace_store/native/bin",
16
+ "cf_workspace_store/native/lib"
17
+ ]
18
+ }
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from .paths import resolve_named_store_path, resolve_semantics_dir, resolve_workspace_dir, resolve_workspace_store_paths
7
+ from .store import ConstraintException, open_named, open_path
8
+
9
+
10
+ class WorkspaceStoreProvider:
11
+ def resolve_semantics_dir(self) -> Path:
12
+ return resolve_semantics_dir()
13
+
14
+ def resolve_workspace_dir(self) -> Path:
15
+ return resolve_workspace_dir()
16
+
17
+ def resolve_named_store_path(self, kind: str, *, semantics_dir: Any = None) -> Path:
18
+ """Resolve the public named embedded store path.
19
+
20
+ Only `pipeline_states` remains a supported named store surface.
21
+ """
22
+ return resolve_named_store_path(kind, semantics_dir=semantics_dir)
23
+
24
+ def resolve_workspace_store_paths(self, *, semantics_dir: Any = None):
25
+ return resolve_workspace_store_paths(semantics_dir=semantics_dir)
26
+
27
+ def open_named(self, kind: str, *, semantics_dir: Any = None, read_only: bool):
28
+ """Open the supported named embedded store session.
29
+
30
+ Only `pipeline_states` remains a supported named store surface.
31
+ """
32
+ return open_named(kind, semantics_dir=semantics_dir, read_only=read_only)
33
+
34
+ def open_path(self, path: Any, *, read_only: bool):
35
+ return open_path(path, read_only=read_only)
36
+
37
+ @property
38
+ def constraint_exception(self) -> type[Exception]:
39
+ return ConstraintException
40
+
41
+
42
+ def provide_workspace_store_contract() -> WorkspaceStoreProvider:
43
+ return WorkspaceStoreProvider()
@@ -0,0 +1,142 @@
1
+ """DuckDB-backed embedded workspace store facade."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any, Iterable, Optional, Sequence
8
+ import tempfile
9
+
10
+ import duckdb
11
+
12
+ from .paths import WorkspaceStoreKind, resolve_named_store_path, resolve_semantics_dir
13
+
14
+ ConstraintException = duckdb.ConstraintException
15
+
16
+
17
+ def _normalize_params(params: Optional[Sequence[object]]) -> list[object]:
18
+ if params is None:
19
+ return []
20
+ return list(params)
21
+
22
+
23
+ @dataclass
24
+ class WorkspaceQueryResult:
25
+ _cursor: Any
26
+
27
+ @property
28
+ def description(self) -> Any:
29
+ return getattr(self._cursor, "description", None)
30
+
31
+ def fetchone(self) -> Any:
32
+ return self._cursor.fetchone()
33
+
34
+ def fetchall(self) -> list[Any]:
35
+ return list(self._cursor.fetchall())
36
+
37
+
38
+ class WorkspaceStoreSession:
39
+ def __init__(self, connection: Any, *, db_path: Optional[Path]) -> None:
40
+ self._connection = connection
41
+ self.db_path = db_path
42
+
43
+ def execute(
44
+ self,
45
+ sql: str,
46
+ params: Optional[Sequence[object]] = None,
47
+ ) -> WorkspaceQueryResult:
48
+ return WorkspaceQueryResult(self._connection.execute(sql, _normalize_params(params)))
49
+
50
+ def fetch_one(
51
+ self,
52
+ sql: str,
53
+ params: Optional[Sequence[object]] = None,
54
+ ) -> Any:
55
+ return self._connection.execute(sql, _normalize_params(params)).fetchone()
56
+
57
+ def fetch_all(
58
+ self,
59
+ sql: str,
60
+ params: Optional[Sequence[object]] = None,
61
+ ) -> list[Any]:
62
+ return list(self._connection.execute(sql, _normalize_params(params)).fetchall())
63
+
64
+ def executemany(
65
+ self,
66
+ sql: str,
67
+ params_seq: Iterable[Sequence[object]],
68
+ ) -> None:
69
+ self._connection.executemany(sql, list(params_seq))
70
+
71
+ def export_query_to_parquet(
72
+ self,
73
+ sql: str,
74
+ output_path: str | Path,
75
+ params: Optional[Sequence[object]] = None,
76
+ ) -> None:
77
+ target = Path(output_path).expanduser().resolve()
78
+ target.parent.mkdir(parents=True, exist_ok=True)
79
+ escaped = str(target).replace("\\", "\\\\").replace("'", "''")
80
+ if params:
81
+ raise ValueError("export_query_to_parquet does not support bound parameters")
82
+ self._connection.execute(f"COPY ({sql}) TO '{escaped}' (FORMAT PARQUET)")
83
+
84
+ def close(self) -> None:
85
+ self._connection.close()
86
+
87
+ def __enter__(self) -> "WorkspaceStoreSession":
88
+ return self
89
+
90
+ def __exit__(self, exc_type, exc, tb) -> None:
91
+ self.close()
92
+
93
+
94
+ class WorkspaceStore:
95
+ def __init__(self, *, semantics_dir: Optional[Path] = None) -> None:
96
+ self._semantics_dir = (semantics_dir or resolve_semantics_dir()).resolve()
97
+
98
+ @property
99
+ def semantics_dir(self) -> Path:
100
+ return self._semantics_dir
101
+
102
+ def open_named(
103
+ self,
104
+ kind: WorkspaceStoreKind,
105
+ *,
106
+ read_only: bool = False,
107
+ ) -> WorkspaceStoreSession:
108
+ if str(kind) != "pipeline_states":
109
+ raise ValueError(
110
+ f"Named workspace store kind '{kind}' is not supported. Only 'pipeline_states' remains public."
111
+ )
112
+ db_path = resolve_named_store_path(kind, self._semantics_dir)
113
+ if not read_only:
114
+ db_path.parent.mkdir(parents=True, exist_ok=True)
115
+ connection = duckdb.connect(str(db_path), read_only=bool(read_only))
116
+ return WorkspaceStoreSession(connection, db_path=db_path)
117
+
118
+ def open_ephemeral(self) -> WorkspaceStoreSession:
119
+ temp_dir = Path(tempfile.mkdtemp(prefix="cf_workspace_store_"))
120
+ db_path = temp_dir / "ephemeral.store.db"
121
+ return open_path(db_path, read_only=False)
122
+
123
+
124
+ def open_named(
125
+ kind: WorkspaceStoreKind,
126
+ *,
127
+ read_only: bool = False,
128
+ semantics_dir: Optional[Path] = None,
129
+ ) -> WorkspaceStoreSession:
130
+ return WorkspaceStore(semantics_dir=semantics_dir).open_named(kind, read_only=read_only)
131
+
132
+
133
+ def open_ephemeral(*, semantics_dir: Optional[Path] = None) -> WorkspaceStoreSession:
134
+ return WorkspaceStore(semantics_dir=semantics_dir).open_ephemeral()
135
+
136
+
137
+ def open_path(db_path: str | Path, *, read_only: bool = False) -> WorkspaceStoreSession:
138
+ path = Path(db_path).expanduser().resolve()
139
+ if not read_only:
140
+ path.parent.mkdir(parents=True, exist_ok=True)
141
+ connection = duckdb.connect(str(path), read_only=bool(read_only))
142
+ return WorkspaceStoreSession(connection, db_path=path)
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import cf_workspace_store
6
+
7
+
8
+ def test_provider_surface_is_exported() -> None:
9
+ assert cf_workspace_store.cf_workspace_store_provider_descriptor_path()
10
+ assert "cf_workspace_store_provider_path" in cf_workspace_store.__all__
11
+
12
+
13
+ def test_provider_descriptor_is_packaged() -> None:
14
+ descriptor_path = Path(cf_workspace_store.cf_workspace_store_provider_descriptor_path())
15
+ assert descriptor_path.is_file()
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import warnings
5
+
6
+ import pytest
7
+
8
+ from cf_workspace_store import WorkspaceStore, resolve_workspace_store_paths
9
+
10
+
11
+ def test_named_store_uses_pipeline_state_store_name(tmp_path: Path) -> None:
12
+ store = WorkspaceStore(semantics_dir=tmp_path)
13
+ paths = resolve_workspace_store_paths(tmp_path)
14
+
15
+ with store.open_named("pipeline_states") as session:
16
+ session.execute("CREATE TABLE IF NOT EXISTS demo(value INTEGER)")
17
+
18
+ assert paths.pipeline_states.name == "cf-pipeline-states.store.db"
19
+ assert paths.pipeline_states.is_file()
20
+
21
+
22
+ def test_pipeline_state_store_does_not_warn(tmp_path: Path) -> None:
23
+ store = WorkspaceStore(semantics_dir=tmp_path)
24
+
25
+ with warnings.catch_warnings(record=True) as caught:
26
+ warnings.simplefilter("always")
27
+ with store.open_named("pipeline_states") as session:
28
+ session.execute("CREATE TABLE IF NOT EXISTS demo(value INTEGER)")
29
+
30
+ assert caught == []
31
+
32
+
33
+ def test_open_named_rejects_removed_legacy_store_kinds(tmp_path: Path) -> None:
34
+ store = WorkspaceStore(semantics_dir=tmp_path)
35
+
36
+ with pytest.raises(ValueError, match="Only 'pipeline_states' remains public"):
37
+ store.open_named("pipelines")
38
+
39
+
40
+ def test_ephemeral_store_supports_basic_sql(tmp_path: Path) -> None:
41
+ store = WorkspaceStore(semantics_dir=tmp_path)
42
+ with store.open_ephemeral() as session:
43
+ session.execute("CREATE TABLE demo(value INTEGER)")
44
+ session.execute("INSERT INTO demo VALUES (7)")
45
+ row = session.fetch_one("SELECT value FROM demo")
46
+ assert row == (7,)