Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
67 changes: 67 additions & 0 deletions functions/function-provision/handler.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"name": "function-provision",
"version": "0.1.0",
"type": "node-graphql",
"port": 8091,
"taskIdentifier": "function-provision",
"scope": "platform",
"description": "Provisions a cloud function — validates service_url or generates gateway URL, validates required secrets exist in the function's namespace",
"requiredSecrets": [],
"requiredConfigs": [],
"payloadSchema": {
"type": "object",
"required": ["function_id"],
"properties": {
"function_id": {
"type": "string",
"format": "uuid",
"description": "UUID of the function definition to provision"
},
"name": {
"type": "string",
"description": "Function name / task_identifier"
},
"service_url": {
"type": "string",
"description": "Explicit service URL (if already known)"
},
"namespace_name": {
"type": "string",
"description": "Namespace the function belongs to"
},
"required_secrets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"required": { "type": "boolean" }
}
},
"description": "Secrets this function requires"
},
"database_id": {
"type": "string",
"format": "uuid",
"description": "Database ID for the owning tenant/platform"
}
},
"additionalProperties": true
},
"inputs": [
{ "name": "function_id", "type": "string", "description": "UUID of the function definition" },
{ "name": "name", "type": "string", "description": "Function name", "optional": true },
{ "name": "service_url", "type": "string", "description": "Explicit service URL", "optional": true },
{ "name": "namespace_name", "type": "string", "description": "Namespace name", "optional": true },
{ "name": "required_secrets", "type": "json", "description": "Required secrets array", "optional": true },
{ "name": "database_id", "type": "string", "description": "Owning database ID", "optional": true }
],
"outputs": [
{ "name": "status", "type": "string", "description": "Provisioning result status" },
{ "name": "service_url", "type": "string", "description": "Resolved service URL" },
{ "name": "missing_secrets", "type": "json", "description": "List of missing required secrets (if any)" }
],
"icon": "cloud",
"category": "infrastructure",
"dependencies": {}
}
48 changes: 48 additions & 0 deletions functions/function-provision/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { FunctionHandler } from '@constructive-io/fn-runtime';

type SecretRequirement = {
name: string;
required: boolean;
};

type FunctionProvisionPayload = {
function_id: string;
name?: string;
service_url?: string;
namespace_name?: string;
required_secrets?: SecretRequirement[];
database_id?: string;
};

/**
* Phase 1 stub: function-provision
*
* Validates the incoming function definition payload and logs the
* provisioning request. Later phases will deploy the function to Knative,
* reconcile K8s secrets from the config_secrets_module, and configure
* ingress routes.
*/
const handler: FunctionHandler<FunctionProvisionPayload> = async (params, context) => {
const { log } = context;
const { function_id, name, service_url, namespace_name, required_secrets } = params;

if (!function_id) {
throw new Error('function_id is required');
}

log.info('function-provision: validated', {
function_id,
name: name ?? null,
has_service_url: Boolean(service_url),
namespace_name: namespace_name ?? null,
required_secrets_count: required_secrets?.length ?? 0,
});

return {
status: 'validated',
function_id,
service_url: service_url ?? null,
};
};

export default handler;
86 changes: 86 additions & 0 deletions functions/function-sync-resources/handler.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"name": "function-sync-resources",
"version": "0.1.0",
"type": "node-graphql",
"port": 8092,
"taskIdentifier": "function-sync-resources",
"scope": "platform",
"description": "Resolves and validates all declared resources for a function's scope — secrets, configs, buckets, API bindings",
"requiredSecrets": [],
"requiredConfigs": [],
"payloadSchema": {
"type": "object",
"required": ["function_id"],
"properties": {
"function_id": {
"type": "string",
"format": "uuid",
"description": "UUID of the function definition to sync"
},
"name": {
"type": "string",
"description": "Function name / task_identifier"
},
"namespace_name": {
"type": "string",
"description": "Namespace the function belongs to"
},
"required_secrets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"required": { "type": "boolean" }
}
},
"description": "Declared secret requirements"
},
"required_configs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"required": { "type": "boolean" }
}
},
"description": "Declared config requirements"
},
"required_buckets": {
"type": "array",
"items": { "type": "string" },
"description": "Declared bucket names"
},
"required_models": {
"type": "array",
"items": { "type": "string" },
"description": "Declared AI model identifiers"
},
"database_id": {
"type": "string",
"format": "uuid",
"description": "Database ID for the owning tenant/platform"
}
},
"additionalProperties": true
},
"inputs": [
{ "name": "function_id", "type": "string", "description": "UUID of the function definition" },
{ "name": "name", "type": "string", "description": "Function name", "optional": true },
{ "name": "namespace_name", "type": "string", "description": "Namespace name", "optional": true },
{ "name": "required_secrets", "type": "json", "description": "Declared secret requirements", "optional": true },
{ "name": "required_configs", "type": "json", "description": "Declared config requirements", "optional": true },
{ "name": "required_buckets", "type": "json", "description": "Declared bucket names", "optional": true },
{ "name": "required_models", "type": "json", "description": "Declared model identifiers", "optional": true },
{ "name": "database_id", "type": "string", "description": "Owning database ID", "optional": true }
],
"outputs": [
{ "name": "status", "type": "string", "description": "Sync result status" },
{ "name": "resolved_count", "type": "number", "description": "Number of resources resolved" },
{ "name": "warnings", "type": "json", "description": "Warnings for missing resources" }
],
"icon": "refresh-cw",
"category": "infrastructure",
"dependencies": {}
}
63 changes: 63 additions & 0 deletions functions/function-sync-resources/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { FunctionHandler } from '@constructive-io/fn-runtime';

type ResourceRequirement = {
name: string;
required: boolean;
};

type FunctionSyncResourcesPayload = {
function_id: string;
name?: string;
namespace_name?: string;
required_secrets?: ResourceRequirement[];
required_configs?: ResourceRequirement[];
required_buckets?: string[];
required_models?: string[];
database_id?: string;
};

/**
* Phase 1 stub: function-sync-resources
*
* Validates the incoming resource declarations and logs a summary.
* Later phases will reconcile K8s secrets, verify bucket existence,
* check model access, and report drift.
*/
const handler: FunctionHandler<FunctionSyncResourcesPayload> = async (params, context) => {
const { log } = context;
const {
function_id,
name,
namespace_name,
required_secrets,
required_configs,
required_buckets,
required_models,
} = params;

if (!function_id) {
throw new Error('function_id is required');
}

const declared = {
secrets: required_secrets?.length ?? 0,
configs: required_configs?.length ?? 0,
buckets: required_buckets?.length ?? 0,
models: required_models?.length ?? 0,
};

log.info('function-sync-resources: validated', {
function_id,
name: name ?? null,
namespace_name: namespace_name ?? null,
...declared,
});

return {
status: 'validated',
function_id,
declared,
};
};

export default handler;
49 changes: 49 additions & 0 deletions functions/namespace-provision/handler.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "namespace-provision",
"version": "0.1.0",
"type": "node-graphql",
"port": 8090,
"taskIdentifier": "namespace-provision",
"scope": "platform",
"description": "Provisions a namespace for function isolation — validates namespace_name, logs provisioning event, inserts created event into namespace_events",
"requiredSecrets": [],
"requiredConfigs": [],
"payloadSchema": {
"type": "object",
"required": ["namespace_id", "namespace_name"],
"properties": {
"namespace_id": {
"type": "string",
"format": "uuid",
"description": "UUID of the namespace row to provision"
},
"namespace_name": {
"type": "string",
"description": "Short namespace name (e.g. 'email')"
},
"global_name": {
"type": "string",
"description": "K8s-safe global namespace name (e.g. 'abc123_email')"
},
"database_id": {
"type": "string",
"format": "uuid",
"description": "Database ID for the owning tenant/platform"
}
},
"additionalProperties": true
},
"inputs": [
{ "name": "namespace_id", "type": "string", "description": "UUID of the namespace row" },
{ "name": "namespace_name", "type": "string", "description": "Short namespace name" },
{ "name": "global_name", "type": "string", "description": "K8s-safe global namespace name", "optional": true },
{ "name": "database_id", "type": "string", "description": "Owning database ID", "optional": true }
],
"outputs": [
{ "name": "status", "type": "string", "description": "Provisioning result status" },
{ "name": "event_type", "type": "string", "description": "Event type inserted into namespace_events" }
],
"icon": "box",
"category": "infrastructure",
"dependencies": {}
}
44 changes: 44 additions & 0 deletions functions/namespace-provision/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { FunctionHandler } from '@constructive-io/fn-runtime';

type NamespaceProvisionPayload = {
namespace_id: string;
namespace_name: string;
global_name?: string;
database_id?: string;
};

/**
* Phase 1 stub: namespace-provision
*
* Validates the incoming namespace payload and logs the provisioning request.
* Later phases will create K8s namespaces, configure network policies,
* and write events to the namespace_events table once the DB schema is ready.
*/
const handler: FunctionHandler<NamespaceProvisionPayload> = async (params, context) => {
const { log } = context;
const { namespace_id, namespace_name, global_name } = params;

if (!namespace_id || !namespace_name) {
throw new Error('namespace_id and namespace_name are required');
}

if (!/^[a-z][a-z0-9-]*$/.test(namespace_name)) {
throw new Error(
`Invalid namespace_name "${namespace_name}" — must be lowercase alphanumeric with hyphens`
);
}

log.info('namespace-provision: validated', {
namespace_id,
namespace_name,
global_name: global_name ?? null,
});

return {
status: 'validated',
namespace_id,
namespace_name,
};
};

export default handler;
4 changes: 2 additions & 2 deletions job/compute-worker/src/discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
* fetches the function definition and caches it for `ttlMs` (default 60 s).
*/

import { TtlCache } from '@constructive-io/module-loader';
import type { ComputeModuleLoader } from '@constructive-io/module-loader';
import { TtlCache } from '@constructive-io/module-loader';
import { Logger } from '@pgpmjs/logger';
import type { Pool } from 'pg';

Expand All @@ -20,7 +20,7 @@ const log = new Logger('compute:discovery');
const COLUMNS = `
id, name, task_identifier, service_url,
is_invocable, is_built_in, max_attempts,
priority, queue_name, scope, namespace_id,
priority, queue_name, scope, namespace_id, namespace_name,
required_configs, required_secrets, description,
runtime, inputs, outputs`;

Expand Down
Loading
Loading