All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m50s
CI/CD / test-sqlite (push) Successful in 2m54s
CI/CD / build-and-deploy (push) Successful in 35s
669 lines
21 KiB
Markdown
669 lines
21 KiB
Markdown
# DESIGN — Storage Primitives
|
|
|
|
**Version:** v0.8.0
|
|
**Status:** Draft
|
|
**Author:** Jeff / Claude session 2026-04-02
|
|
|
|
---
|
|
|
|
## Problem
|
|
|
|
Extensions cannot access blob storage or managed disk paths. The existing
|
|
`ObjectStore` interface (PVC + S3 backends) is fully implemented but only
|
|
exposed to the admin status endpoint — no Starlark bridge exists. The `db`
|
|
module provides structured data storage via extension-scoped tables, but
|
|
there is no equivalent for binary files, no managed filesystem for tools
|
|
like `git`, and no mechanism for extensions to declare environment
|
|
requirements (pgvector, workspace root, S3) that the kernel validates at
|
|
install time.
|
|
|
|
This blocks every future capability that depends on file handling:
|
|
RAG/vector search, LLM bridge, code workspaces, file upload/sharing,
|
|
media processing, and document indexing.
|
|
|
|
---
|
|
|
|
## Non-Goals
|
|
|
|
- Replacing the `db` module. Extension-scoped tables (`ext_{pkg}_{table}`)
|
|
are the structured data primitive and remain unchanged.
|
|
- Building a KV store. Extensions that need key-value semantics declare a
|
|
table with `key TEXT, value TEXT` columns — the infrastructure exists.
|
|
- Multi-tenant file isolation beyond package scoping. Team/user-level
|
|
file ACLs are extension-layer concerns built on top of these primitives.
|
|
- Streaming / chunked upload through Starlark. Large file ingestion goes
|
|
through HTTP routes; the `files` module handles storage after receipt.
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
Three new primitives plus one extension to the existing `db` module.
|
|
|
|
### Primitive 1: `files` Starlark Module
|
|
|
|
Bridges the existing `ObjectStore` into the sandbox. Follows the same
|
|
pattern as `db_module.go`: a `Build*Module` factory, permission-gated,
|
|
package-scoped key namespacing.
|
|
|
|
**Permissions:** `files.read`, `files.write`
|
|
|
|
**Starlark API:**
|
|
|
|
```python
|
|
# Write a file. content is string (UTF-8) or bytes.
|
|
# metadata is an optional dict stored alongside (JSON-serialized).
|
|
files.put(name, content, content_type="application/octet-stream", metadata={})
|
|
|
|
# Read a file. Returns dict: {"content": <bytes>, "content_type": "...", "size": N, "metadata": {...}}
|
|
# Returns None if not found.
|
|
result = files.get(name)
|
|
|
|
# Read metadata only (no content transfer). Returns dict or None.
|
|
meta = files.meta(name)
|
|
|
|
# List files by prefix. Returns list of dicts: [{"name": "...", "size": N, "content_type": "..."}]
|
|
entries = files.list(prefix="", limit=100)
|
|
|
|
# Delete a file. Idempotent — no error if missing.
|
|
files.delete(name)
|
|
|
|
# Delete all files under a prefix. Use with caution.
|
|
files.delete_prefix(prefix)
|
|
|
|
# Check existence without reading.
|
|
exists = files.exists(name)
|
|
```
|
|
|
|
**Key namespacing:**
|
|
|
|
All keys are automatically prefixed with `ext/{packageID}/`. An extension
|
|
calling `files.put("models/v1.bin", data)` writes to the ObjectStore key
|
|
`ext/my-extension/models/v1.bin`. Extensions cannot escape their namespace.
|
|
|
|
Name validation rejects `..`, absolute paths, and control characters —
|
|
same sanitization rules as `physicalTable()` in `db_module.go`.
|
|
|
|
**Implementation notes:**
|
|
|
|
- New file: `sandbox/files_module.go`
|
|
- `FilesModuleConfig` struct mirrors `DBModuleConfig`:
|
|
```go
|
|
type FilesModuleConfig struct {
|
|
PackageID string
|
|
CanWrite bool
|
|
Store storage.ObjectStore
|
|
}
|
|
```
|
|
- Metadata is stored as a companion JSON object at key
|
|
`ext/{packageID}/_meta/{name}`. This avoids schema changes — the
|
|
ObjectStore interface is unchanged. The `files` module manages the
|
|
companion transparently.
|
|
- Size limit per `files.put()` call: 50MB (configurable via
|
|
`EXT_FILES_MAX_SIZE`). Enforced in the builtin before calling
|
|
`Store.Put()`.
|
|
- `files.get()` returns content as `starlark.Bytes` for binary safety.
|
|
Starlark's `Bytes` type was added in go.starlark.net v0.0.0-20240725214946
|
|
and handles non-UTF-8 content correctly.
|
|
- If `ObjectStore` is nil (storage not configured), the module is not
|
|
injected — same pattern as `db` module when `r.db == nil`.
|
|
|
|
**Runner wiring:**
|
|
|
|
```go
|
|
// In buildModulesWithLibCtx, add cases:
|
|
case models.ExtPermFilesRead:
|
|
if filesLevel < 1 { filesLevel = 1 }
|
|
case models.ExtPermFilesWrite:
|
|
filesLevel = 2
|
|
|
|
// After permission loop:
|
|
if filesLevel > 0 && r.objectStore != nil {
|
|
modules["files"] = BuildFilesModule(ctx, FilesModuleConfig{
|
|
PackageID: packageID,
|
|
CanWrite: filesLevel == 2,
|
|
Store: r.objectStore,
|
|
})
|
|
}
|
|
```
|
|
|
|
Runner gains `SetObjectStore(s storage.ObjectStore)` setter, called from
|
|
`main.go` after `storage.Init()`.
|
|
|
|
---
|
|
|
|
### Primitive 2: `workspace` Starlark Module
|
|
|
|
Managed disk directories for extensions that need a real filesystem —
|
|
git repos, compilers, ffmpeg, pandoc, code analysis tools. These tools
|
|
cannot operate through put/get blob semantics; they need paths.
|
|
|
|
**Permission:** `workspace.manage`
|
|
|
|
**Starlark API:**
|
|
|
|
```python
|
|
# Create a named workspace directory. Returns the absolute path.
|
|
# Idempotent — returns existing path if already created.
|
|
path = workspace.create(name)
|
|
|
|
# Get the path for an existing workspace. Returns string or None.
|
|
path = workspace.path(name)
|
|
|
|
# List workspace names for this extension.
|
|
names = workspace.list()
|
|
|
|
# Delete a workspace and all its contents.
|
|
workspace.delete(name)
|
|
|
|
# Disk usage in bytes for a workspace.
|
|
size = workspace.usage(name)
|
|
```
|
|
|
|
**Directory layout:**
|
|
|
|
```
|
|
{WORKSPACE_ROOT}/
|
|
{packageID}/
|
|
{name}/
|
|
... (extension-managed contents)
|
|
```
|
|
|
|
`WORKSPACE_ROOT` defaults to `/data/workspaces` (configurable via
|
|
`WORKSPACE_ROOT` env var). The kernel creates the package subdirectory
|
|
on first `workspace.create()`. Extensions own everything below their
|
|
directory — the kernel does not inspect contents.
|
|
|
|
**Implementation notes:**
|
|
|
|
- New file: `sandbox/workspace_module.go`
|
|
- `WorkspaceModuleConfig`:
|
|
```go
|
|
type WorkspaceModuleConfig struct {
|
|
PackageID string
|
|
WorkspaceRoot string
|
|
}
|
|
```
|
|
- Name validation: same rules as table names (`^[a-z][a-z0-9_]{0,62}$`).
|
|
No path separators, no `..`, no spaces.
|
|
- `workspace.create()` calls `os.MkdirAll` for the scoped path.
|
|
- `workspace.delete()` calls `os.RemoveAll` — destructive by design.
|
|
Extensions must handle confirmation in their own UX.
|
|
- `workspace.usage()` walks the directory tree and sums file sizes.
|
|
Bounded by a 10-second context timeout to prevent hangs on huge trees.
|
|
- **Quota enforcement** (optional): `WORKSPACE_QUOTA_MB` env var. When
|
|
set, `workspace.create()` checks cumulative usage for the package
|
|
before creating. Returns error if quota exceeded. Default: unlimited.
|
|
- If `WORKSPACE_ROOT` is empty or not writable, the module is not
|
|
injected. Extensions that declare `workspace.manage` without the
|
|
root configured will have their permission granted but the module
|
|
absent — same degradation pattern as `db` when no DB is available.
|
|
|
|
**Runner wiring:**
|
|
|
|
```go
|
|
case models.ExtPermWorkspaceManage:
|
|
if r.workspaceRoot != "" {
|
|
modules["workspace"] = BuildWorkspaceModule(ctx, WorkspaceModuleConfig{
|
|
PackageID: packageID,
|
|
WorkspaceRoot: r.workspaceRoot,
|
|
})
|
|
}
|
|
```
|
|
|
|
Runner gains `SetWorkspaceRoot(path string)` setter.
|
|
|
|
**Security considerations:**
|
|
|
|
- The returned path is an absolute filesystem path. Extensions can pass
|
|
this to `http` module calls (e.g., POST a file to an API) or use it
|
|
in `db` records as a reference. They cannot execute arbitrary binaries —
|
|
the Starlark sandbox has no `os.exec`. Execution requires a sidecar
|
|
tier package or an `api_route` handler that shells out server-side.
|
|
- For sidecar-tier packages that _can_ execute binaries, the workspace
|
|
path is the designated scratch space. The kernel ensures the path is
|
|
within the scoped directory via `filepath.Clean` + prefix check.
|
|
|
|
---
|
|
|
|
### Primitive 3: Capability Negotiation
|
|
|
|
Extensions declare environment requirements in their manifest. The kernel
|
|
validates these at install time and reports failures with actionable
|
|
messages. This is what makes progressive enhancement strategies (e.g.,
|
|
three-tier vector search) work.
|
|
|
|
**Manifest field:**
|
|
|
|
```json
|
|
{
|
|
"id": "vector-store",
|
|
"capabilities": {
|
|
"required": ["files.read", "files.write"],
|
|
"optional": ["pgvector"]
|
|
},
|
|
"permissions": ["db.write", "files.write"],
|
|
"db_tables": {
|
|
"embeddings": {
|
|
"columns": {
|
|
"source_id": "text",
|
|
"chunk_text": "text",
|
|
"embedding": "vector(384)"
|
|
},
|
|
"indexes": [["source_id"]]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
`capabilities.required` — install fails if any are missing. Admin gets
|
|
a clear error: "Package 'vector-store' requires capability 'pgvector'
|
|
which is not available. Run `CREATE EXTENSION vector;` in your PostgreSQL
|
|
database to enable it."
|
|
|
|
`capabilities.optional` — install succeeds regardless. The capability
|
|
state is queryable at runtime so extensions can degrade gracefully.
|
|
|
|
**Runtime query (Starlark):**
|
|
|
|
Settings module is the natural home since it's always available:
|
|
|
|
```python
|
|
# Returns True/False for a capability name.
|
|
has_pgvector = settings.has_capability("pgvector")
|
|
```
|
|
|
|
**Kernel capability registry:**
|
|
|
|
A simple function in the `handlers` package that probes the environment:
|
|
|
|
```go
|
|
func DetectCapabilities(db *sql.DB, isPostgres bool, workspaceRoot string, objStore storage.ObjectStore) map[string]bool {
|
|
caps := make(map[string]bool)
|
|
|
|
// pgvector: check pg_extension
|
|
if isPostgres && db != nil {
|
|
var exists bool
|
|
row := db.QueryRow("SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname='vector')")
|
|
if row.Scan(&exists) == nil && exists {
|
|
caps["pgvector"] = true
|
|
}
|
|
}
|
|
|
|
// workspace: check root is writable
|
|
if workspaceRoot != "" && storage.IsPathWritable(workspaceRoot) {
|
|
caps["workspace"] = true
|
|
}
|
|
|
|
// object storage: check configured and healthy
|
|
if objStore != nil {
|
|
if err := objStore.Healthy(context.Background()); err == nil {
|
|
caps["object_storage"] = true
|
|
}
|
|
}
|
|
|
|
// s3: specific backend check
|
|
if objStore != nil && objStore.Backend() == "s3" {
|
|
caps["s3"] = true
|
|
}
|
|
|
|
// postgres: dialect check
|
|
if isPostgres {
|
|
caps["postgres"] = true
|
|
}
|
|
|
|
return caps
|
|
}
|
|
```
|
|
|
|
Called once at startup, stored on the `Runner` (or a shared config
|
|
struct). Re-probed on admin request for the capabilities endpoint.
|
|
|
|
**Install-time validation:**
|
|
|
|
In the package install handler (`handlers/extensions.go`), after
|
|
`ParseDBTables` and before `SyncManifestPermissions`:
|
|
|
|
```go
|
|
if caps, ok := ParseCapabilities(manifestMap); ok {
|
|
missing := CheckRequiredCapabilities(caps.Required, detectedCaps)
|
|
if len(missing) > 0 {
|
|
// Roll back: delete the just-created package row
|
|
h.stores.Packages.Delete(c.Request.Context(), pkg.ID)
|
|
c.JSON(422, gin.H{
|
|
"error": "missing required capabilities",
|
|
"missing": missing,
|
|
"help": capabilityHelpText(missing),
|
|
})
|
|
return
|
|
}
|
|
}
|
|
```
|
|
|
|
**Admin API:**
|
|
|
|
```
|
|
GET /api/v1/admin/capabilities
|
|
→ {"pgvector": true, "workspace": true, "object_storage": true, "s3": false, "postgres": true}
|
|
```
|
|
|
|
Displayed in the Admin UI alongside storage status. Gives operators
|
|
visibility into what their deployment supports.
|
|
|
|
---
|
|
|
|
### Extension to `db` Module: Vector Column Type
|
|
|
|
The existing `mapColType()` in `ext_db_schema.go` gains a `vector` type
|
|
with progressive enhancement across backends.
|
|
|
|
**Manifest declaration:**
|
|
|
|
```json
|
|
"db_tables": {
|
|
"embeddings": {
|
|
"columns": {
|
|
"embedding": "vector(384)"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Column type mapping:**
|
|
|
|
| Manifest type | PG + pgvector | PG without pgvector | SQLite |
|
|
|------------------|-------------------------|---------------------|--------------|
|
|
| `vector(N)` | `vector(N)` | `JSONB` | `TEXT` |
|
|
|
|
Implementation in `mapColType`:
|
|
|
|
```go
|
|
case "vector":
|
|
// vector or vector(384) — extract dimension if present
|
|
dim := extractVectorDim(typStr) // returns "384" or ""
|
|
if isPostgres && hasPgVector {
|
|
if dim != "" {
|
|
return fmt.Sprintf("vector(%s)", dim)
|
|
}
|
|
return "vector"
|
|
}
|
|
if isPostgres {
|
|
return "JSONB" // store as JSON array, brute-force search
|
|
}
|
|
return "TEXT" // SQLite: JSON array as text
|
|
```
|
|
|
|
`hasPgVector` is passed through a new field on a `SchemaConfig` struct
|
|
(or detected inline — the capability registry result is available at
|
|
table creation time).
|
|
|
|
**New `db` module function — `db.query_similar()`:**
|
|
|
|
```python
|
|
# Find rows with embeddings closest to the query vector.
|
|
# Returns list of row dicts with _distance appended.
|
|
results = db.query_similar(
|
|
table="embeddings",
|
|
column="embedding",
|
|
vector=[0.1, 0.2, ...], # query vector (list of floats)
|
|
limit=10,
|
|
filters={"source_id": "doc-123"} # optional WHERE clause
|
|
)
|
|
```
|
|
|
|
**Backend dispatch:**
|
|
|
|
- **PG + pgvector:** Uses `ORDER BY embedding <=> $1 LIMIT $2` with
|
|
native vector distance operator.
|
|
- **PG without pgvector / SQLite:** Loads candidate rows (respecting
|
|
`filters`), deserializes JSON arrays, computes cosine similarity in
|
|
Go, sorts, returns top-N. This is the "it works but slowly" fallback —
|
|
acceptable for small corpora (<10k rows), documented as such.
|
|
|
|
Implementation: new function `dbQuerySimilar()` in `db_module.go`,
|
|
gated on `db.read` permission (read-only operation). The function
|
|
checks `cfg.HasPgVector` to choose the fast or slow path.
|
|
|
|
`DBModuleConfig` gains:
|
|
|
|
```go
|
|
type DBModuleConfig struct {
|
|
PackageID string
|
|
CanWrite bool
|
|
DB *sql.DB
|
|
IsPostgres bool
|
|
HasPgVector bool // NEW: enables native vector ops
|
|
}
|
|
```
|
|
|
|
Wired from the capability registry at module build time.
|
|
|
|
---
|
|
|
|
## New Permission Constants
|
|
|
|
```go
|
|
// In models/models_extension_perm.go:
|
|
const (
|
|
ExtPermFilesRead = "files.read"
|
|
ExtPermFilesWrite = "files.write"
|
|
ExtPermWorkspaceManage = "workspace.manage"
|
|
)
|
|
```
|
|
|
|
Added to `ValidExtensionPermissions` map.
|
|
|
|
---
|
|
|
|
## Schema Changes
|
|
|
|
**Migration 015 — none required for kernel tables.**
|
|
|
|
The `files` module uses the existing `ObjectStore` interface — no new
|
|
kernel tables. Metadata companions are stored as ObjectStore objects.
|
|
|
|
The `workspace` module uses the filesystem — no tables.
|
|
|
|
Capabilities are detected at runtime — no tables.
|
|
|
|
The `vector` column type is handled by extension DDL generation in
|
|
`ext_db_schema.go` — no kernel migration.
|
|
|
|
The new permission constants are code-only changes.
|
|
|
|
---
|
|
|
|
## New Files
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `sandbox/files_module.go` | `files` Starlark module |
|
|
| `sandbox/files_module_test.go` | Tests using mock ObjectStore |
|
|
| `sandbox/workspace_module.go` | `workspace` Starlark module |
|
|
| `sandbox/workspace_module_test.go` | Tests using temp directory |
|
|
| `handlers/capabilities.go` | `DetectCapabilities()`, `ParseCapabilities()`, admin endpoint |
|
|
| `handlers/capabilities_test.go` | Tests for capability detection and validation |
|
|
|
|
## Modified Files
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `models/models_extension_perm.go` | Add `files.read`, `files.write`, `workspace.manage` |
|
|
| `sandbox/runner.go` | Add `SetObjectStore()`, `SetWorkspaceRoot()`, wire new modules |
|
|
| `sandbox/db_module.go` | Add `dbQuerySimilar()`, `HasPgVector` field |
|
|
| `handlers/ext_db_schema.go` | Extend `mapColType()` for `vector(N)`, pass `hasPgVector` |
|
|
| `handlers/extensions.go` | Add capability validation in install handler |
|
|
| `sandbox/settings_module.go` | Add `settings.has_capability()` |
|
|
| `server/main.go` | Call `DetectCapabilities()`, wire to runner |
|
|
| `docs/PACKAGE-FORMAT.md` | Document `capabilities` manifest field |
|
|
| `docs/EXTENSION-GUIDE.md` | Document new modules and vector column type |
|
|
|
|
---
|
|
|
|
## Changeset Plan
|
|
|
|
**CS1 — `files` module + permissions**
|
|
|
|
New files: `files_module.go`, `files_module_test.go`.
|
|
Modified: `models_extension_perm.go`, `runner.go`, `main.go`.
|
|
Scope: ObjectStore bridge, permission constants, runner wiring.
|
|
CI-green independently — no schema changes, no existing behavior affected.
|
|
|
|
**CS2 — `workspace` module**
|
|
|
|
New files: `workspace_module.go`, `workspace_module_test.go`.
|
|
Modified: `models_extension_perm.go`, `runner.go`, `main.go`.
|
|
Scope: Managed disk directories, quota enforcement.
|
|
CI-green independently.
|
|
|
|
**CS3 — Capability negotiation**
|
|
|
|
New files: `capabilities.go`, `capabilities_test.go`.
|
|
Modified: `extensions.go` (install validation), `settings_module.go`
|
|
(`has_capability`), `main.go`.
|
|
Scope: Detection, install-time validation, runtime query, admin endpoint.
|
|
CI-green independently.
|
|
|
|
**CS4 — Vector column type + `db.query_similar()`**
|
|
|
|
Modified: `ext_db_schema.go`, `db_module.go`, `db_module_test.go`.
|
|
Scope: Column type mapping, similarity query with dual-path dispatch.
|
|
Depends on CS3 (needs `HasPgVector` from capability registry).
|
|
|
|
---
|
|
|
|
## Configuration Summary
|
|
|
|
| Env Var | Default | Purpose |
|
|
|---------|---------|---------|
|
|
| `WORKSPACE_ROOT` | `/data/workspaces` | Root directory for workspace module |
|
|
| `WORKSPACE_QUOTA_MB` | `0` (unlimited) | Per-extension disk quota |
|
|
| `EXT_FILES_MAX_SIZE` | `52428800` (50MB) | Max single file size via `files.put()` |
|
|
|
|
---
|
|
|
|
## Example: Vector Store Extension
|
|
|
|
A `vector-store` library extension consuming all four primitives:
|
|
|
|
**manifest.json:**
|
|
```json
|
|
{
|
|
"id": "vector-store",
|
|
"title": "Vector Store",
|
|
"type": "library",
|
|
"tier": "starlark",
|
|
"version": "0.1.0",
|
|
"permissions": ["db.write", "files.read", "connections.read"],
|
|
"capabilities": {
|
|
"required": [],
|
|
"optional": ["pgvector"]
|
|
},
|
|
"db_tables": {
|
|
"documents": {
|
|
"columns": {
|
|
"source": "text",
|
|
"chunk_text": "text",
|
|
"page": "int",
|
|
"metadata": "text"
|
|
},
|
|
"indexes": [["source"]]
|
|
},
|
|
"embeddings": {
|
|
"columns": {
|
|
"document_id": "text",
|
|
"embedding": "vector(384)",
|
|
"chunk_index": "int"
|
|
},
|
|
"indexes": [["document_id"]]
|
|
}
|
|
},
|
|
"exports": ["ingest", "search", "search_clustered"]
|
|
}
|
|
```
|
|
|
|
**script.star:**
|
|
```python
|
|
def ingest(source_name, chunks):
|
|
"""Store document chunks and generate embeddings."""
|
|
for i, chunk in enumerate(chunks):
|
|
row = db.insert("documents", {
|
|
"source": source_name,
|
|
"chunk_text": chunk["text"],
|
|
"page": chunk.get("page", 0),
|
|
"metadata": json.encode(chunk.get("metadata", {})),
|
|
})
|
|
# Embedding generation delegated to caller (llm-bridge)
|
|
# Caller passes pre-computed vectors
|
|
if "embedding" in chunk:
|
|
db.insert("embeddings", {
|
|
"document_id": row["id"],
|
|
"embedding": json.encode(chunk["embedding"]),
|
|
"chunk_index": i,
|
|
})
|
|
|
|
def search(query_vector, limit=10, source_filter=None):
|
|
"""Semantic similarity search — dispatches to native or brute-force."""
|
|
filters = {}
|
|
if source_filter:
|
|
filters["source"] = source_filter
|
|
# query_similar handles pgvector vs fallback transparently
|
|
results = db.query_similar(
|
|
table="embeddings",
|
|
column="embedding",
|
|
vector=query_vector,
|
|
limit=limit,
|
|
)
|
|
# Hydrate with document text
|
|
enriched = []
|
|
for r in results:
|
|
docs = db.query("documents", filters={"id": r["document_id"]}, limit=1)
|
|
if docs:
|
|
enriched.append({
|
|
"text": docs[0]["chunk_text"],
|
|
"source": docs[0]["source"],
|
|
"page": docs[0]["page"],
|
|
"distance": r["_distance"],
|
|
})
|
|
return enriched
|
|
|
|
def search_clustered(embeddings_list, k):
|
|
"""K-means clustering for query-free thematic selection.
|
|
Returns k representative chunks. Clustering runs in the
|
|
extension — the kernel provides the data, not the algorithm."""
|
|
# This would be implemented by a consumer extension with
|
|
# http access to call a clustering API, or by a sidecar
|
|
# that runs sklearn. The vector-store library provides
|
|
# the data access pattern; clustering logic lives elsewhere.
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
## Future Considerations
|
|
|
|
- **Content-addressed deduplication.** If multiple extensions store the
|
|
same PDF, the ObjectStore holds duplicate bytes. A SHA256-keyed blob
|
|
layer with refcounting would deduplicate transparently. Deferred —
|
|
adds complexity without clear need pre-1.0.
|
|
|
|
- **Extension-to-extension file sharing.** The current design isolates
|
|
file namespaces per extension. A `files.grant(name, target_pkg_id)`
|
|
primitive could enable controlled sharing. Deferred — composition
|
|
through API routes is sufficient initially.
|
|
|
|
- **Streaming upload for large files.** Starlark `files.put()` buffers
|
|
in memory. For files >50MB, extensions should use HTTP `api_routes`
|
|
with Go handlers that stream directly to ObjectStore. The `files`
|
|
module is for extension-internal storage, not user-facing upload.
|
|
|
|
- **Workspace snapshots.** `workspace.snapshot(name)` → creates a
|
|
tarball in the `files` store. Useful for backup/reproducibility.
|
|
Deferred — extensions can implement this themselves.
|
|
|
|
- **Rate limiting / quota on `db.query_similar()` fallback.** The
|
|
brute-force path loads all candidate rows into Go memory. For large
|
|
tables this is dangerous. A row-count guard (e.g., refuse if >50k
|
|
candidates) with a clear error message pointing to pgvector is the
|
|
right safety valve.
|