diff --git a/packages/node-type-registry/src/authz/index.ts b/packages/node-type-registry/src/authz/index.ts index 34d21244e..7d4d12103 100644 --- a/packages/node-type-registry/src/authz/index.ts +++ b/packages/node-type-registry/src/authz/index.ts @@ -7,9 +7,9 @@ export { AuthzDirectOwner } from './authz-direct-owner'; export { AuthzDirectOwnerAny } from './authz-direct-owner-any'; export { AuthzEntityMembership } from './authz-entity-membership'; export { AuthzMemberList } from './authz-member-list'; -export { AuthzMemberOwner } from './authz-member-owner'; export { AuthzNotReadOnly } from './authz-not-read-only'; export { AuthzOrgHierarchy } from './authz-org-hierarchy'; +export { AuthzMemberOwner } from './authz-member-owner'; export { AuthzPeerOwnership } from './authz-peer-ownership'; export { AuthzPublishable } from './authz-publishable'; export { AuthzRelatedEntityMembership } from './authz-related-entity-membership'; diff --git a/packages/node-type-registry/src/blueprint-types.generated.ts b/packages/node-type-registry/src/blueprint-types.generated.ts index 5365cfac4..241a80caa 100644 --- a/packages/node-type-registry/src/blueprint-types.generated.ts +++ b/packages/node-type-registry/src/blueprint-types.generated.ts @@ -35,23 +35,6 @@ export interface TriggerCondition { /** Negated condition. */ NOT?: TriggerCondition; } -/** - * =========================================================================== - * Billing node type parameters - * =========================================================================== - */ -; -/** Declaratively attaches billing usage-recording triggers to a table. On INSERT the named meter is incremented via record_usage; on DELETE it is decremented (reversal). On UPDATE, if the entity_field changes, the old entity is decremented and the new entity is incremented. Requires a provisioned billing_module for the target database. */ -export interface BillingMeterParams { - /* Slug of the billing meter to record usage against (must match a meters table entry, e.g. "databases", "seats") */ - meter_slug: string; - /* Column on the target table that holds the entity id for billing */ - entity_field?: string; - /* Units to record per event (default 1) */ - quantity?: number; - /* Which DML events to attach triggers for */ - events?: ('INSERT' | 'DELETE' | 'UPDATE')[]; -} /** * =========================================================================== * Check node type parameters @@ -180,6 +163,21 @@ export interface DataJsonbParams { /* Whether to create a GIN index */ create_index?: boolean; } +/** Adds owner_id and entity_id columns with a compound AuthzMemberOwner policy. The actor must own the row (owner_id = current_user_id()) AND be a member of the entity (entity_id in SPRT). Use for private data within an entity scope — e.g., personal chat threads that belong to the company but only the author can see. */ +export interface DataMemberOwnerParams { + /* Column name for the owner reference */ + owner_field_name?: string; + /* Column name for the entity reference */ + entity_field_name?: string; + /* If true, also adds a UUID primary key column with auto-generation */ + include_id?: boolean; + /* If true, adds foreign key constraints from owner_id and entity_id to the users table */ + include_user_fk?: boolean; + /* If true, creates B-tree indexes on the owner and entity columns */ + create_index?: boolean; + /* Membership type for SPRT resolution. Required for entity-scoped provisioning. */ + membership_type?: number; +} /** Restricts which user can modify specific columns in shared objects. Creates an AFTER UPDATE trigger that throws OWNED_PROPS when a non-owner tries to change protected fields. References fields by name in data jsonb. */ export interface DataOwnedFieldsParams { /* Name of the field identifying the owner (e.g. sender_id) */ @@ -216,9 +214,9 @@ export interface DataPeoplestampsParams { /** Adds publish state columns (is_published, published_at) for content visibility. Enables AuthzPublishable and AuthzTemporal authorization. */ export interface DataPublishableParams { /* Column name for the published boolean flag */ - is_published_field?: string; + is_published_field_name?: string; /* Column name for the publish timestamp */ - published_at_field?: string; + published_at_field_name?: string; /* If true, also adds a UUID primary key column with auto-generation */ include_id?: boolean; } @@ -284,12 +282,66 @@ export type TableUserProfilesParams = {}; export type TableUserSettingsParams = {}; /** * =========================================================================== - * Limit node type parameters + * Event node type parameters + * =========================================================================== + */ +; +/** Creates triggers that record events for the referrer (inviter) when their invitees perform actions on a watched table. Resolves the referrer automatically via the invites module's claimed_invites table using the membership_type context. Supports the same compound condition system as EventTracker. Use with achievements to unlock levels and grant credits based on invitee activity. */ +export interface EventReferralParams { + /* Event type name to record for the referrer (e.g., "invitee_uploaded_avatar", "invitee_completed_onboarding") */ + event_name: string; + /* DML events that trigger recording */ + events?: ('INSERT' | 'UPDATE' | 'DELETE')[]; + /* Column containing the invitee (actor) ID on the source table — used to look up the referrer via claimed_invites.receiver_id */ + actor_field?: string; + /* Column containing the entity ID (org/group) for entity-scoped referral events. Omit for user-only events. */ + entity_field?: string; + /* Maximum depth to walk up the invite chain. Default 1 (direct inviter only). Set 2–10 to enable multi-level referral rewards. App-level only — must not be combined with entity_field. */ + max_depth?: number; + /* Automatically register the event_name in event_types during provisioning */ + auto_register_type?: boolean; + /* Column name for conditional WHEN clause (fires only when field equals condition_value) */ + condition_field?: string; + /* Value to compare against condition_field in WHEN clause */ + condition_value?: string; + /* Compound conditions for the trigger WHEN clause. Accepts a single leaf condition, an array of conditions (implicitly AND), or a nested combinator tree ({AND: [...], OR: [...], NOT: {...}}). Each leaf is {field, op, value?, row?, ref?}. Column types are resolved automatically from the table schema. Cannot be combined with condition_field or watch_fields. */ + conditions?: TriggerCondition | TriggerCondition[]; + /* For UPDATE triggers, only fire when these fields change (uses DISTINCT FROM) */ + watch_fields?: string[]; +} +/** Creates triggers that record events via the events module when table rows change. Supports the same compound condition system as JobTrigger (condition_field, watch_fields, or full AND/OR/NOT conditions). Events are recorded to app_events and aggregated automatically. Use with achievements (blueprint-level) to unlock levels and grant credits based on event accumulation. */ +export interface EventTrackerParams { + /* Event type name to record (e.g., "avatar_uploaded", "order_completed") */ + event_name: string; + /* DML events that trigger recording */ + events?: ('INSERT' | 'UPDATE' | 'DELETE')[]; + /* Number of events to record per trigger fire */ + count?: number; + /* Toggle mode: records event when condition is met, removes when condition is unmet */ + toggle?: boolean; + /* Column containing the actor (user) ID to attribute the event to */ + actor_field?: string; + /* Column containing the entity ID (org/group) for entity-scoped events. Omit for user-only events. */ + entity_field?: string; + /* Automatically register the event_name in event_types during provisioning */ + auto_register_type?: boolean; + /* Column name for conditional WHEN clause (fires only when field equals condition_value) */ + condition_field?: string; + /* Value to compare against condition_field in WHEN clause */ + condition_value?: string; + /* Compound conditions for the trigger WHEN clause. Accepts a single leaf condition, an array of conditions (implicitly AND), or a nested combinator tree ({AND: [...], OR: [...], NOT: {...}}). Each leaf is {field, op, value?, row?, ref?}. Column types are resolved automatically from the table schema. Cannot be combined with condition_field or watch_fields. */ + conditions?: TriggerCondition | TriggerCondition[]; + /* For UPDATE triggers, only fire when these fields change (uses DISTINCT FROM) */ + watch_fields?: string[]; +} +/** + * =========================================================================== + * Limit_enforce node type parameters * =========================================================================== */ ; /** Declaratively attaches aggregate limit-tracking triggers to a table. On INSERT the named limit is incremented per entity; on DELETE it is decremented. Uses org_limit_aggregates_inc/dec for per-entity (org-level) aggregate limits rather than per-user limits. Requires a provisioned limits_module for the target database. */ -export interface LimitAggregateParams { +export interface LimitEnforceAggregateParams { /* Name of the aggregate limit to track (must match a default_limits entry, e.g. "databases", "members") */ limit_name: string; /* Column on the target table that holds the entity id for aggregate limit lookup */ @@ -297,8 +349,19 @@ export interface LimitAggregateParams { /* Which DML events to attach triggers for */ events?: ('INSERT' | 'DELETE' | 'UPDATE')[]; } +/** Declaratively attaches limit-tracking triggers to a table. On INSERT the named limit is incremented; on DELETE it is decremented. Requires a provisioned limits_module for the target scope. */ +export interface LimitEnforceCounterParams { + /* Name of the limit to track (must match a default_limits entry, e.g. "projects", "members") */ + limit_name: string; + /* Limit scope: "app" (membership_type=1, user-level) or "org" (membership_type=2, entity-level) */ + scope?: 'app' | 'org'; + /* Column on the target table that holds the actor or entity id used for limit lookup */ + actor_field?: string; + /* Which DML events to attach triggers for */ + events?: ('INSERT' | 'DELETE' | 'UPDATE')[]; +} /** Gates a table behind a feature flag backed by the cap tables. Attaches a BEFORE INSERT trigger that checks whether the named feature cap value is > 0. Features are modeled as caps with max=0 (disabled) or max=1 (enabled) in limit_caps / limit_caps_defaults tables. Resolution: COALESCE(per-entity cap, scope default, 0). */ -export interface LimitFeatureFlagParams { +export interface LimitEnforceFeatureParams { /* Cap name representing this feature (must match a limit_caps_defaults entry with max=0 or max=1) */ feature_name: string; /* Feature scope: "app" (membership_type=1, app-level caps) or "org" (membership_type=2, per-entity caps) */ @@ -306,16 +369,64 @@ export interface LimitFeatureFlagParams { /* Column on the target table that holds the entity id for per-entity cap lookups (only used for org scope) */ entity_field?: string; } -/** Declaratively attaches limit-tracking triggers to a table. On INSERT the named limit is incremented; on DELETE it is decremented. Requires a provisioned limits_module for the target scope. */ -export interface LimitCounterParams { - /* Name of the limit to track (must match a default_limits entry, e.g. "projects", "members") */ +/** Attaches a BEFORE trigger that calls check_rate_limit() to enforce sliding-window rate limits before allowing mutations. The function checks all three scopes (entity, actor-in-entity, actor) in a single call; which scopes are actually enforced is controlled by what rows exist in rate_window_limits (plan-based config). Requires a provisioned meter_rate_limits_module and billing_module for the target database. */ +export interface LimitEnforceRateParams { + /* Slug of the billing meter to check rate limits against (must match a meters table entry, e.g. "messaging", "inference") */ + meter_slug: string; + /* Column on the target table that holds the entity id (org) for rate limiting */ + entity_field?: string; + /* Column on the target table that holds the actor id (user) for rate limiting */ + actor_field?: string; + /* Which DML events to enforce rate limits on (DELETE is excluded since it reduces load) */ + events?: ('INSERT' | 'UPDATE')[]; +} +/** + * =========================================================================== + * Limit_track node type parameters + * =========================================================================== + */ +; +/** Declaratively attaches billing usage-recording triggers to a table. On INSERT the named meter is incremented via record_usage; on DELETE it is decremented (reversal). On UPDATE, if the entity_field changes, the old entity is decremented and the new entity is incremented. Requires a provisioned billing_module for the target database. */ +export interface LimitTrackUsageParams { + /* Slug of the billing meter to record usage against (must match a meters table entry, e.g. "databases", "seats") */ + meter_slug: string; + /* Column on the target table that holds the entity id for billing */ + entity_field?: string; + /* Units to record per event (default 1) */ + quantity?: number; + /* Which DML events to attach triggers for */ + events?: ('INSERT' | 'DELETE' | 'UPDATE')[]; +} +/** + * =========================================================================== + * Limit_warning node type parameters + * =========================================================================== + */ +; +/** Attaches an AFTER INSERT trigger that checks if the entity's aggregate usage has crossed any warning threshold configured in the limit_warnings table. If a threshold is reached for the first time, enqueues a background job (e.g. email notification). Uses limit_warning_state for one-time dedup per warning/actor/entity triple. Requires a provisioned limits_module with limit_warnings and aggregate limits enabled. */ +export interface LimitWarningAggregateParams { + /* Name of the aggregate limit to watch (must match a limit_warnings.name entry, e.g. "databases", "members") */ + limit_name: string; + /* Column on the target table that holds the entity id for aggregate limit lookup */ + entity_field?: string; +} +/** Attaches an AFTER INSERT trigger that checks if the actor's current usage has crossed any warning threshold configured in the limit_warnings table. If a threshold is reached for the first time, enqueues a background job (e.g. email notification). Uses limit_warning_state for one-time dedup per warning/actor pair. Requires a provisioned limits_module with limit_warnings enabled. */ +export interface LimitWarningCounterParams { + /* Name of the limit to watch (must match a limit_warnings.name entry, e.g. "projects", "members") */ limit_name: string; /* Limit scope: "app" (membership_type=1, user-level) or "org" (membership_type=2, entity-level) */ scope?: 'app' | 'org'; - /* Column on the target table that holds the actor or entity id used for limit lookup */ + /* Column on the target table that holds the actor id for limit lookup */ + actor_field?: string; +} +/** Attaches an AFTER INSERT trigger that checks if the actor's current request count in the active sliding window has crossed any warning threshold configured in the limit_warnings table. If a threshold is reached for the first time, enqueues a background job (e.g. email notification). Uses limit_warning_state for one-time dedup per warning/actor pair. Requires both a limits_module with limit_warnings enabled and a rate_limit_meters_module. */ +export interface LimitWarningRateParams { + /* Slug of the billing meter to check rate limits against (must match a meters table entry) */ + meter_slug: string; + /* Column on the target table that holds the entity id for rate limit lookup */ + entity_field?: string; + /* Column on the target table that holds the actor id for rate limit lookup */ actor_field?: string; - /* Which DML events to attach triggers for */ - events?: ('INSERT' | 'DELETE' | 'UPDATE')[]; } /** * =========================================================================== @@ -419,6 +530,8 @@ export interface SearchUnifiedParams { index_method?: 'hnsw' | 'ivfflat'; metric?: 'cosine' | 'l2' | 'ip'; source_fields?: string[]; + /* Embedding model identifier. When null, the worker falls back to runtime config. */embedding_model?: string; + /* Embedding provider name. When null, the worker falls back to runtime config. */embedding_provider?: string; search_score_weight?: number; /* Chunking configuration for long-text embedding. Creates an embedding_chunks record that drives automatic text splitting and per-chunk embedding. Omit to skip chunking. */chunks?: { /* Name of the text content column in the chunks table */content_field_name?: string; @@ -465,6 +578,10 @@ export interface SearchVectorParams { }; /* Column names that feed the embedding. Used by stale trigger to detect content changes. */ source_fields?: string[]; + /* Embedding model identifier (e.g. "nomic-embed-text", "text-embedding-3-small"). Included in the job payload so the worker knows which model to use. When null, the worker falls back to runtime config (llm_module / env vars). */ + embedding_model?: string; + /* Embedding provider name (e.g. "ollama", "openai"). When null, the worker falls back to runtime config. */ + embedding_provider?: string; /* Auto-create trigger that enqueues embedding generation jobs */ enqueue_job?: boolean; /* Task identifier for the job queue */ @@ -514,16 +631,13 @@ export interface JobTriggerParams { conditions?: TriggerCondition | TriggerCondition[]; /* For UPDATE triggers, only fire when these fields change (uses DISTINCT FROM) */ watch_fields?: string[]; - /* Column on the trigger table that holds (or references) the entity_id for billing scope */ + /* Column on the trigger table that holds (or references) the entity_id for billing scope. For direct entity_id columns, just set this field. For FK lookups (e.g., channel_id → channels.entity_id), combine with entity_lookup. */ entity_field?: string; - /* FK lookup configuration for resolving entity_id through a related table */ + /* FK lookup configuration for resolving entity_id through a related table. Used when entity_field is a FK (e.g., channel_id) rather than a direct entity_id. The generator validates all fields against metaschema within the same database_id. */ entity_lookup?: { - /* Name of the related table to look up entity_id from */ - obj_table: string; - /* Schema of the related table (optional — resolved by table name if omitted) */ - obj_schema?: string; - /* Column on the related table that holds the entity_id */ - obj_field: string; + /* Name of the related table to look up entity_id from (e.g., "channels"). Required. */obj_table: string; + /* Schema of the related table (user-facing name, e.g., "public"). Optional — if omitted, resolved by table name within the same database_id (raises error if ambiguous). */obj_schema?: string; + /* Column on the related table that holds the entity_id (e.g., "entity_id"). Required. */obj_field: string; }; /* Static job key for upsert semantics (prevents duplicate jobs) */ job_key?: string; @@ -556,25 +670,66 @@ export interface ProcessChunksParams { dimensions?: number; /* Distance metric for the HNSW index on chunk embeddings */ metric?: 'cosine' | 'l2' | 'ip'; + /* Embedding model identifier for per-chunk embeddings. When null, the worker falls back to runtime config (llm_module / env vars). */ + embedding_model?: string; + /* Embedding provider name (e.g. "ollama", "openai"). When null, the worker falls back to runtime config. */ + embedding_provider?: string; /* Override the chunks table name. Defaults to {parent_table}_chunks. */ chunks_table_name?: string; /* Field names from the parent table to copy into chunk metadata */ metadata_fields?: string[]; - /* Text search indexes to create on the chunks content column */ + /* Text search indexes to create on the chunks content column. Omit to mirror the parent table's text search indexes. Set explicitly to override (e.g. ["fulltext", "bm25"]). */ search_indexes?: ('fulltext' | 'bm25' | 'trigram')[]; - /* Column on the parent table that holds (or references) the entity_id for billing scope */ + /* Column on the parent table that holds (or references) the entity_id for billing scope. Forwarded to the chunking job trigger. */ entity_field?: string; - /* FK lookup configuration for resolving entity_id through a related table */ + /* FK lookup configuration for resolving entity_id through a related table. Forwarded to the chunking job trigger. */ entity_lookup?: { - obj_table: string; - obj_schema?: string; - obj_field: string; + /* Name of the related table to look up entity_id from */obj_table: string; + /* Schema of the related table (user-facing name, optional) */obj_schema?: string; + /* Column on the related table that holds the entity_id */obj_field: string; }; /* Whether to create a job trigger that auto-enqueues chunking on parent INSERT/UPDATE */ enqueue_chunking_job?: boolean; /* Task identifier for the chunking job queue */ chunking_task_name?: string; } +/** Creates extraction output fields and a job trigger for file text extraction. Fires when a file is uploaded (status = 'uploaded') or on INSERT. The external worker extracts text/metadata from the file (PDF, DOCX, HTML, etc.) and writes the result back to the configured output fields. Typically used upstream of ProcessFileEmbedding or ProcessChunks. */ +export interface ProcessExtractionParams { + /* Field to store extracted text/markdown */ + text_field?: string; + /* JSONB field for extraction metadata (page count, language, etc.) */ + metadata_field?: string; + /* Extraction model identifier (e.g. a vision model for OCR, an LLM for structured extraction). Included in the job payload so the worker knows which model to use. When null, the worker falls back to runtime config. */ + extraction_model?: string; + /* Extraction provider name (e.g. "ollama", "openai"). When null, the worker falls back to runtime config. */ + extraction_provider?: string; + /* MIME type LIKE patterns to match. Multiple patterns are OR'd together. Examples: ['application/pdf', 'text/%'], ['application/vnd.openxmlformats%']. */ + mime_patterns?: string[]; + /* Job task identifier for the extraction worker */ + task_identifier?: string; + /* Trigger events that fire the job */ + events?: ('INSERT' | 'UPDATE')[]; + /* Custom payload key-to-column mapping for the job trigger */ + payload_custom?: { + [key: string]: unknown; + }; + /* Additional compound conditions beyond auto-generated filtering. Merged with the auto-generated conditions via AND. */ + trigger_conditions?: TriggerCondition | TriggerCondition[]; + /* Column on the trigger table that holds (or references) the entity_id for billing scope. Forwarded to the composed JobTrigger. */ + entity_field?: string; + /* FK lookup configuration for resolving entity_id through a related table. Forwarded to the composed JobTrigger. */ + entity_lookup?: { + /* Name of the related table to look up entity_id from */obj_table: string; + /* Schema of the related table (user-facing name, optional) */obj_schema?: string; + /* Column on the related table that holds the entity_id */obj_field: string; + }; + /* Job queue name for extraction tasks */ + queue_name?: string; + /* Maximum number of retry attempts */ + max_attempts?: number; + /* Job priority (lower = higher priority) */ + priority?: number; +} /** Generic, MIME-scoped embedding node for file tables. Supports two modes: direct (whole-file to single vector, e.g. CLIP for images) when extraction is omitted, or extract (file to text to chunks to per-chunk vectors) when extraction config is provided. Composes SearchVector + JobTrigger + ProcessChunks (enabled by default in extract mode) internally. Multiple instances can coexist on the same table with different MIME scopes, field names, and embedding strategies. */ export interface ProcessFileEmbeddingParams { /* Name of the vector embedding column */ @@ -589,6 +744,10 @@ export interface ProcessFileEmbeddingParams { index_options?: { [key: string]: unknown; }; + /* Embedding model identifier (e.g. "nomic-embed-text", "text-embedding-3-small", "clip-vit-base-patch32"). Included in the job payload so the worker knows which model to use. When null, the worker falls back to runtime config (llm_module / env vars). */ + embedding_model?: string; + /* Embedding provider name (e.g. "ollama", "openai"). When null, the worker falls back to runtime config. */ + embedding_provider?: string; /* MIME type LIKE patterns to match. Multiple patterns are OR'd together. Examples: ['image/%'], ['application/pdf', 'text/%'], ['audio/%']. */ mime_patterns?: string[]; /* Job task identifier for the worker. In direct mode this is the embedding worker; in extract mode this is the extraction worker. */ @@ -599,15 +758,15 @@ export interface ProcessFileEmbeddingParams { payload_custom?: { [key: string]: unknown; }; - /* Additional compound conditions beyond MIME filtering. Merged with the auto-generated MIME conditions via AND. Use this to add status checks, field guards, etc. */ + /* Additional compound conditions beyond auto-generated filtering. Merged with the auto-generated conditions via AND. */ trigger_conditions?: TriggerCondition | TriggerCondition[]; - /* Column on the trigger table that holds (or references) the entity_id for billing scope */ + /* Column on the trigger table that holds (or references) the entity_id for billing scope. Forwarded to the composed JobTrigger. */ entity_field?: string; - /* FK lookup configuration for resolving entity_id through a related table */ + /* FK lookup configuration for resolving entity_id through a related table. Forwarded to the composed JobTrigger. */ entity_lookup?: { - obj_table: string; - obj_schema?: string; - obj_field: string; + /* Name of the related table to look up entity_id from */obj_table: string; + /* Schema of the related table (user-facing name, optional) */obj_schema?: string; + /* Column on the related table that holds the entity_id */obj_field: string; }; /* Text extraction configuration. When present, the generator creates extraction output fields on the table and configures SearchVector with source_fields + stale tracking. When absent, the node operates in direct mode (single vector per file, no text extraction). */ extraction?: { @@ -623,6 +782,7 @@ export interface ProcessFileEmbeddingParams { /* Number of overlapping characters between consecutive chunks */chunk_overlap?: number; /* Strategy for splitting text into chunks */chunk_strategy?: 'fixed' | 'sentence' | 'paragraph' | 'semantic'; /* Field names from parent to copy into chunk metadata */metadata_fields?: string[]; + /* Text search indexes to create on the chunks content column. Omit to mirror the parent table's text search indexes. Set explicitly to override. */search_indexes?: ('fulltext' | 'bm25' | 'trigram')[]; /* Whether to auto-enqueue a chunking job on insert/update */enqueue_chunking_job?: boolean; /* Task identifier for the chunking job queue */chunking_task_name?: string; }; @@ -641,6 +801,10 @@ export interface ProcessImageEmbeddingParams { index_options?: { [key: string]: unknown; }; + /* Embedding model identifier (e.g. "clip-vit-base-patch32"). Included in the job payload so the worker knows which model to use. When null, the worker falls back to runtime config (llm_module / env vars). */ + embedding_model?: string; + /* Embedding provider name (e.g. "ollama", "openai"). When null, the worker falls back to runtime config. */ + embedding_provider?: string; /* MIME type LIKE patterns to match. Multiple patterns are OR'd together. */ mime_patterns?: string[]; /* Job task identifier for the image embedding worker */ @@ -651,15 +815,15 @@ export interface ProcessImageEmbeddingParams { payload_custom?: { [key: string]: unknown; }; - /* Additional compound conditions beyond MIME filtering. Merged with the auto-generated MIME conditions via AND. */ + /* Additional compound conditions beyond auto-generated filtering. Merged with the auto-generated conditions via AND. */ trigger_conditions?: TriggerCondition | TriggerCondition[]; - /* Column on the trigger table that holds (or references) the entity_id for billing scope */ + /* Column on the trigger table that holds (or references) the entity_id for billing scope. Forwarded to the composed JobTrigger. */ entity_field?: string; - /* FK lookup configuration for resolving entity_id through a related table */ + /* FK lookup configuration for resolving entity_id through a related table. Forwarded to the composed JobTrigger. */ entity_lookup?: { - obj_table: string; - obj_schema?: string; - obj_field: string; + /* Name of the related table to look up entity_id from */obj_table: string; + /* Schema of the related table (user-facing name, optional) */obj_schema?: string; + /* Column on the related table that holds the entity_id */obj_field: string; }; /* Text extraction configuration. Forwarded to ProcessFileEmbedding. When present, enables extract mode (e.g., OCR for images). */ extraction?: { @@ -679,39 +843,6 @@ export interface ProcessImageEmbeddingParams { chunking_task_name?: string; }; } -/** Creates extraction output fields and a job trigger for file text extraction. Fires when a file is uploaded (status = 'uploaded') or on INSERT. The external worker extracts text/metadata from the file (PDF, DOCX, HTML, etc.) and writes the result back to the configured output fields. Typically used upstream of ProcessFileEmbedding or ProcessChunks. */ -export interface ProcessExtractionParams { - /* Field to store extracted text/markdown */ - text_field?: string; - /* JSONB field for extraction metadata (page count, language, etc.) */ - metadata_field?: string; - /* MIME type LIKE patterns to match. Multiple patterns are OR'd together. Examples: ['application/pdf', 'text/%'], ['application/vnd.openxmlformats%']. */ - mime_patterns?: string[]; - /* Job task identifier for the extraction worker */ - task_identifier?: string; - /* Trigger events that fire the job */ - events?: ('INSERT' | 'UPDATE')[]; - /* Custom payload key-to-column mapping for the job trigger */ - payload_custom?: { - [key: string]: unknown; - }; - /* Additional compound conditions beyond MIME filtering. Merged with the auto-generated MIME conditions via AND. Use this to add status checks (e.g., status = 'uploaded'). */ - trigger_conditions?: TriggerCondition | TriggerCondition[]; - /* Column on the trigger table that holds (or references) the entity_id for billing scope */ - entity_field?: string; - /* FK lookup configuration for resolving entity_id through a related table */ - entity_lookup?: { - obj_table: string; - obj_schema?: string; - obj_field: string; - }; - /* Job queue name for extraction tasks */ - queue_name?: string; - /* Maximum number of retry attempts */ - max_attempts?: number; - /* Job priority (lower = higher priority) */ - priority?: number; -} /** Creates a job trigger for image variant generation. Fires when an image file is uploaded (status = 'uploaded') or on INSERT. The external worker generates resized, cropped, or reformatted versions (thumbnails, previews, WebP conversions, etc.) and stores them as new file records linked to the source image. */ export interface ProcessImageVersionsParams { /* Array of version definitions. Each version specifies dimensions, format, and quality for a generated image variant. Required — the blueprint must explicitly define what variants to generate. */ @@ -733,15 +864,15 @@ export interface ProcessImageVersionsParams { payload_custom?: { [key: string]: unknown; }; - /* Additional compound conditions beyond MIME filtering. Merged with the auto-generated MIME conditions via AND. */ + /* Additional compound conditions beyond auto-generated filtering. Merged with the auto-generated conditions via AND. */ trigger_conditions?: TriggerCondition | TriggerCondition[]; - /* Column on the trigger table that holds (or references) the entity_id for billing scope */ + /* Column on the trigger table that holds (or references) the entity_id for billing scope. Forwarded to the composed JobTrigger. */ entity_field?: string; - /* FK lookup configuration for resolving entity_id through a related table */ + /* FK lookup configuration for resolving entity_id through a related table. Forwarded to the composed JobTrigger. */ entity_lookup?: { - obj_table: string; - obj_schema?: string; - obj_field: string; + /* Name of the related table to look up entity_id from */obj_table: string; + /* Schema of the related table (user-facing name, optional) */obj_schema?: string; + /* Column on the related table that holds the entity_id */obj_field: string; }; /* Job queue name for image processing tasks */ queue_name?: string; @@ -850,6 +981,23 @@ export interface AuthzOrgHierarchyParams { /* Optional max depth to limit visibility */ max_depth?: number; } +/** Compound policy: the row must be owned by the current user (owner_field = current_user_id) AND the current user must be a member of the entity referenced by entity_field. Combines direct ownership with entity membership — the actor can only access rows they own within entities they belong to. */ +export interface AuthzMemberOwnerParams { + /* Column name containing the owner user ID (e.g., owner_id) */ + owner_field: string; + /* Column name referencing the entity (e.g., entity_id) */ + entity_field: string; + /* SPRT column to select for the entity match */ + sel_field?: string; + /* Scope: 1=app, 2=org, 3+=dynamic entity types (or string name resolved via membership_types_module) */ + membership_type?: number | string; + /* Entity type prefix (e.g. 'channel', 'department'). Resolved to membership_type integer via memberships_module lookup. */ + entity_type?: string; + /* Single permission name to check (resolved to bitstring mask) */ + permission?: string; + /* Multiple permission names to check (ORed together into mask) */ + permissions?: string[]; +} /** Peer visibility through shared entity membership. Authorizes access to user-owned rows when the owner and current user are both members of the same entity. Self-joins the SPRT table to find peers. */ export interface AuthzPeerOwnershipParams { /* Column name on protected table referencing the owning user (e.g., owner_id) */ @@ -1148,7 +1296,7 @@ export interface BlueprintField { /** An RLS policy entry for a blueprint table. Uses $type to match the blueprint JSON convention. */ export interface BlueprintPolicy { /** Authz* policy type name (e.g., "AuthzDirectOwner", "AuthzAllowAll"). */ - $type: 'AuthzAllowAll' | 'AuthzAppMembership' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzFilePath' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal'; + $type: 'AuthzAllowAll' | 'AuthzAppMembership' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzFilePath' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzMemberOwner' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal'; /** Privileges this policy applies to (e.g., ["select"], ["insert", "update", "delete"]). */ privileges?: string[]; /** Whether this policy is permissive (true) or restrictive (false). Defaults to true. */ @@ -1245,7 +1393,7 @@ export interface BlueprintTableUniqueConstraint { /** Optional schema name override. */ schema_name?: string; } -/** A bucket seed entry for storage_config.buckets[]. Creates an initial bucket row in the {prefix}_buckets table during entity type provisioning. Only used for app-level storage (not entity-scoped). */ +/** A bucket seed entry for storage.buckets[]. Creates an initial bucket row in the {prefix}_buckets table during entity type provisioning. Only used for app-level storage (not entity-scoped). */ export interface BlueprintBucketSeed { /** Bucket key name (e.g., "avatars", "documents"). Becomes the key column value. */ name: string; @@ -1321,6 +1469,74 @@ export interface BlueprintAchievement { /** Entity prefix to scope this achievement to (e.g., "org", "app"). Used to resolve the correct events_module. Defaults to "app". */ entity_prefix?: string; } +/** Namespace module configuration. When used at the top level of a blueprint, the scope field controls whether namespaces are app-level ("app", default) or org-level ("org"). When used inside entity_types[], scope is inherited from the entity type. Provisions a namespaces table with computed-name proxy, rename trigger, and entity-scoped RLS. */ +export interface BlueprintNamespaceConfig { + /** Namespace scope. "app" (default) creates app-level namespaces (membership_type = NULL). "org" creates per-org namespaces. Only used at the top level of a blueprint definition — entity-scoped namespaces inherit scope from the entity type. */ + scope?: 'app' | 'org'; + /** Module discriminator for multi-module namespaces. Defaults to "default" (omitted from table names). Non-default keys appear as an infix: {prefix}_{key}_namespaces. */ + key?: string; + /** RLS policy overrides for the namespaces table. NULL = apply defaults from apply_namespace_security(). */ + policies?: BlueprintPolicy[]; + /** Per-table overrides for namespace tables. Each key targets a specific table (namespaces, namespace_events) and uses the same shape as table_provision: { nodes, fields, grants, use_rls, policies }. Fanned out to secure_table_provision. */ + provisions?: { + namespaces?: BlueprintEntityTableProvision; + namespace_events?: BlueprintEntityTableProvision; + }; +} +/** Function module configuration. When used at the top level of a blueprint, the scope field controls whether functions are app-level ("app", default) or org-level ("org"). When used inside entity_types[], scope is inherited from the entity type. Provisions function_definitions, function_invocations (partitioned, 12-month retention), and function_execution_logs tables. */ +export interface BlueprintFunctionConfig { + /** Function scope. "app" (default) creates app-level functions (membership_type = NULL). "org" creates per-org functions. Only used at the top level of a blueprint definition — entity-scoped functions inherit scope from the entity type. */ + scope?: 'app' | 'org'; + /** Module discriminator for multi-module functions. Defaults to "default" (omitted from table names). Non-default keys appear as an infix: {prefix}_{key}_function_definitions. */ + key?: string; + /** RLS policy overrides for the function tables. NULL = apply defaults from apply_function_security(). */ + policies?: BlueprintPolicy[]; + /** Per-table overrides for function tables. Each key targets a specific table (definitions, invocations, execution_logs) and uses the same shape as table_provision: { nodes, fields, grants, use_rls, policies }. Fanned out to secure_table_provision. */ + provisions?: { + definitions?: BlueprintEntityTableProvision; + invocations?: BlueprintEntityTableProvision; + execution_logs?: BlueprintEntityTableProvision; + }; +} +/** Agent module configuration. When used at the top level of a blueprint, the scope field controls whether agents are app-level ("app", default) or org-level ("org"). When used inside entity_types[], scope is inherited from the entity type. Provisions thread, message, task, prompt tables (and optionally knowledge with vector embeddings). */ +export interface BlueprintAgentConfig { + /** Agent scope. "app" (default) creates app-level agent tables (membership_type = NULL). "org" creates per-org agent tables. Only used at the top level of a blueprint definition — entity-scoped agents inherit scope from the entity type. */ + scope?: 'app' | 'org'; + /** Module discriminator for multi-module agents. Defaults to "default" (omitted from table names). Non-default keys appear as an infix: {prefix}_{key}_agent_thread. */ + key?: string; + /** API name for the agent module. Used in GraphQL naming. Defaults to "agent". */ + api_name?: string; + /** Whether to provision the agent_knowledge table with vector embeddings, tags, and trigger_phrases. Also inferred when a "knowledge" key is present. Defaults to false. */ + has_knowledge?: boolean; + /** Knowledge configuration overrides. Set has_chunks to false to disable the chunking pipeline. Controls vector dimensions, chunking strategy, embedding model/provider, and text search indexes for the agent_knowledge table. Presence implies has_knowledge = true. */ + knowledge?: { + has_chunks?: boolean; + dimensions?: number; + chunk_size?: number; + chunk_overlap?: number; + chunk_strategy?: 'fixed' | 'sentence' | 'paragraph' | 'semantic'; + embedding_model?: string; + embedding_provider?: string; + search_indexes?: ('fulltext' | 'bm25' | 'trigram')[]; + }; + /** RLS policy overrides for the agent tables. NULL = apply defaults from apply_agent_security(). */ + policies?: BlueprintPolicy[]; + /** Per-table overrides for agent tables. Each key targets a specific table (thread, message, task, prompt, knowledge) and uses the same shape as table_provision: { nodes, fields, grants, use_rls, policies }. Fanned out to secure_table_provision. */ + provisions?: { + thread?: BlueprintEntityTableProvision; + message?: BlueprintEntityTableProvision; + task?: BlueprintEntityTableProvision; + prompt?: BlueprintEntityTableProvision; + knowledge?: BlueprintEntityTableProvision; + }; +} +/** Graph module configuration. Presence triggers permission registration (manage_graphs, execute_graphs). The graph module requires a merkle_store_module_id dependency, so entity_type_provision only registers permissions here — the graph module itself must be provisioned separately. */ +export interface BlueprintGraphConfig { + /** Module discriminator for multi-module graphs. Defaults to "default". */ + key?: string; + /** RLS policy overrides for the graph tables. NULL = apply defaults from apply_graph_security(). */ + policies?: BlueprintPolicy[]; +} /** Override object for the entity table created by a BlueprintEntityType. Shape mirrors BlueprintTable / secure_table_provision vocabulary. When supplied, policies[] replaces the default entity-table policies entirely. */ export interface BlueprintEntityTableProvision { /** Whether to enable RLS on the entity table. Forwarded to secure_table_provision. Defaults to true. */ @@ -1357,7 +1573,6 @@ export interface BlueprintEntityType { has_profiles?: boolean; /** Whether to provision a levels module for this entity type. Defaults to false. */ has_levels?: boolean; - /** Whether to provision entity-scoped invite tables ({prefix}_invites, {prefix}_claimed_invites) and a submit_{prefix}_invite_code() function. Defaults to false. */ has_invites?: boolean; /** Whether to auto-attach an EventTracker to the claimed_invites table for invite-based achievements. Requires has_invites=true AND has_levels=true. When true, records 'invite_claimed' events credited to the sender (inviter) on each claimed invite. Defaults to false. */ @@ -1366,8 +1581,16 @@ export interface BlueprintEntityType { skip_entity_policies?: boolean; /** Override for the entity table. Shape mirrors BlueprintTable / secure_table_provision vocabulary. When supplied, its policies[] replaces the five default entity-table policies; is_visible becomes a no-op. When NULL (default), the five default policies are applied (gated by is_visible). */ table_provision?: BlueprintEntityTableProvision; - /** Storage configuration (array-only). A non-empty array enables storage provisioning. Each entry creates a separate storage module with its own tables ({prefix}_{storage_key}_buckets/files). Controls RLS policies, bucket seeding, and module-level settings. */ + /** Storage module configuration array. Presence triggers provisioning (same inference model as namespaces, functions, agents). Each entry provisions a separate storage module with its own tables, RLS, and settings. Each entry may specify a storage_key for multi-module support (defaults to "default"). */ storage?: BlueprintStorageConfig[]; + /** Namespace module configuration array. Presence triggers provisioning. Each entry provisions a namespace_module with its own tables, computed-name proxy, and entity-scoped RLS. Registers manage_namespaces permission bit. "[{}]" = provision one default namespace module. */ + namespaces?: BlueprintNamespaceConfig[]; + /** Function module configuration array. Presence triggers provisioning. Each entry provisions function_definitions, function_invocations (partitioned), and function_execution_logs tables. Registers manage_functions + invoke_functions permission bits. "[{}]" = provision one default function module. */ + functions?: BlueprintFunctionConfig[]; + /** Agent module configuration array. Presence triggers provisioning. Each entry provisions thread, message, task, prompt tables (and optionally knowledge with vector embeddings). "[{}]" = provision one default agent module. */ + agents?: BlueprintAgentConfig[]; + /** Graph module configuration array. Presence triggers permission registration (manage_graphs, execute_graphs). Graph module requires a merkle_store_module_id dependency, so entity_type_provision only registers permissions here. "[{}]" = register default graph permissions. */ + graphs?: BlueprintGraphConfig[]; } /** * =========================================================================== @@ -1376,7 +1599,7 @@ export interface BlueprintEntityType { */ ; /** String shorthand -- just the node type name. */ -export type BlueprintNodeShorthand = 'AuthzAllowAll' | 'AuthzAppMembership' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzFilePath' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal' | 'CheckGreaterThan' | 'CheckLessThan' | 'CheckNotEqual' | 'CheckOneOf' | 'LimitAggregate' | 'BillingMeter' | 'DataBulk' | 'ProcessChunks' | 'DataCompositeField' | 'DataDirectOwner' | 'DataEntityMembership' | 'ProcessFileEmbedding' | 'LimitFeatureFlag' | 'DataForceCurrentUser' | 'DataId' | 'ProcessImageEmbedding' | 'DataImmutableFields' | 'DataInflection' | 'DataInheritFromParent' | 'JobTrigger' | 'LimitCounter' | 'DataJsonb' | 'DataOwnedFields' | 'ProcessExtraction' | 'ProcessImageVersions' | 'DataOwnershipInEntity' | 'DataPeoplestamps' | 'DataPublishable' | 'DataRealtime' | 'DataSlug' | 'DataSoftDelete' | 'DataStatusField' | 'DataTags' | 'DataTimestamps' | 'SearchBm25' | 'SearchFullText' | 'SearchSpatial' | 'SearchSpatialAggregate' | 'SearchTrgm' | 'SearchUnified' | 'SearchVector' | 'TableOrganizationSettings' | 'TableUserProfiles' | 'TableUserSettings'; +export type BlueprintNodeShorthand = 'AuthzAllowAll' | 'AuthzAppMembership' | 'AuthzComposite' | 'AuthzDenyAll' | 'AuthzFilePath' | 'AuthzDirectOwner' | 'AuthzDirectOwnerAny' | 'AuthzEntityMembership' | 'AuthzMemberList' | 'AuthzNotReadOnly' | 'AuthzOrgHierarchy' | 'AuthzMemberOwner' | 'AuthzPeerOwnership' | 'AuthzPublishable' | 'AuthzRelatedEntityMembership' | 'AuthzRelatedMemberList' | 'AuthzRelatedPeerOwnership' | 'AuthzTemporal' | 'CheckGreaterThan' | 'CheckLessThan' | 'CheckNotEqual' | 'CheckOneOf' | 'DataBulk' | 'DataCompositeField' | 'DataDirectOwner' | 'DataEntityMembership' | 'DataForceCurrentUser' | 'DataId' | 'DataImmutableFields' | 'DataInflection' | 'DataInheritFromParent' | 'DataJsonb' | 'DataMemberOwner' | 'DataOwnedFields' | 'DataOwnershipInEntity' | 'DataPeoplestamps' | 'DataPublishable' | 'DataRealtime' | 'DataSlug' | 'DataSoftDelete' | 'DataStatusField' | 'DataTags' | 'DataTimestamps' | 'SearchBm25' | 'SearchFullText' | 'SearchSpatial' | 'SearchSpatialAggregate' | 'SearchTrgm' | 'SearchUnified' | 'SearchVector' | 'TableOrganizationSettings' | 'TableUserProfiles' | 'TableUserSettings' | 'EventReferral' | 'EventTracker' | 'JobTrigger' | 'LimitEnforceAggregate' | 'LimitEnforceCounter' | 'LimitEnforceFeature' | 'LimitEnforceRate' | 'LimitTrackUsage' | 'LimitWarningAggregate' | 'LimitWarningCounter' | 'LimitWarningRate' | 'ProcessChunks' | 'ProcessExtraction' | 'ProcessFileEmbedding' | 'ProcessImageEmbedding' | 'ProcessImageVersions'; /** Object form -- { $type, data } with typed parameters. */ export type BlueprintNodeObject = { $type: 'AuthzAllowAll'; @@ -1411,6 +1634,9 @@ export type BlueprintNodeObject = { } | { $type: 'AuthzOrgHierarchy'; data: AuthzOrgHierarchyParams; +} | { + $type: 'AuthzMemberOwner'; + data: AuthzMemberOwnerParams; } | { $type: 'AuthzPeerOwnership'; data: AuthzPeerOwnershipParams; @@ -1441,18 +1667,9 @@ export type BlueprintNodeObject = { } | { $type: 'CheckOneOf'; data: CheckOneOfParams; -} | { - $type: 'LimitAggregate'; - data: LimitAggregateParams; -} | { - $type: 'BillingMeter'; - data: BillingMeterParams; } | { $type: 'DataBulk'; data: DataBulkParams; -} | { - $type: 'ProcessChunks'; - data: ProcessChunksParams; } | { $type: 'DataCompositeField'; data: DataCompositeFieldParams; @@ -1462,21 +1679,12 @@ export type BlueprintNodeObject = { } | { $type: 'DataEntityMembership'; data: DataEntityMembershipParams; -} | { - $type: 'ProcessFileEmbedding'; - data: ProcessFileEmbeddingParams; -} | { - $type: 'LimitFeatureFlag'; - data: LimitFeatureFlagParams; } | { $type: 'DataForceCurrentUser'; data: DataForceCurrentUserParams; } | { $type: 'DataId'; data: DataIdParams; -} | { - $type: 'ProcessImageEmbedding'; - data: ProcessImageEmbeddingParams; } | { $type: 'DataImmutableFields'; data: DataImmutableFieldsParams; @@ -1486,24 +1694,15 @@ export type BlueprintNodeObject = { } | { $type: 'DataInheritFromParent'; data: DataInheritFromParentParams; -} | { - $type: 'JobTrigger'; - data: JobTriggerParams; -} | { - $type: 'LimitCounter'; - data: LimitCounterParams; } | { $type: 'DataJsonb'; data: DataJsonbParams; +} | { + $type: 'DataMemberOwner'; + data: DataMemberOwnerParams; } | { $type: 'DataOwnedFields'; data: DataOwnedFieldsParams; -} | { - $type: 'ProcessExtraction'; - data: ProcessExtractionParams; -} | { - $type: 'ProcessImageVersions'; - data: ProcessImageVersionsParams; } | { $type: 'DataOwnershipInEntity'; data: DataOwnershipInEntityParams; @@ -1561,6 +1760,54 @@ export type BlueprintNodeObject = { } | { $type: 'TableUserSettings'; data?: Record; +} | { + $type: 'EventReferral'; + data: EventReferralParams; +} | { + $type: 'EventTracker'; + data: EventTrackerParams; +} | { + $type: 'JobTrigger'; + data: JobTriggerParams; +} | { + $type: 'LimitEnforceAggregate'; + data: LimitEnforceAggregateParams; +} | { + $type: 'LimitEnforceCounter'; + data: LimitEnforceCounterParams; +} | { + $type: 'LimitEnforceFeature'; + data: LimitEnforceFeatureParams; +} | { + $type: 'LimitEnforceRate'; + data: LimitEnforceRateParams; +} | { + $type: 'LimitTrackUsage'; + data: LimitTrackUsageParams; +} | { + $type: 'LimitWarningAggregate'; + data: LimitWarningAggregateParams; +} | { + $type: 'LimitWarningCounter'; + data: LimitWarningCounterParams; +} | { + $type: 'LimitWarningRate'; + data: LimitWarningRateParams; +} | { + $type: 'ProcessChunks'; + data: ProcessChunksParams; +} | { + $type: 'ProcessExtraction'; + data: ProcessExtractionParams; +} | { + $type: 'ProcessFileEmbedding'; + data: ProcessFileEmbeddingParams; +} | { + $type: 'ProcessImageEmbedding'; + data: ProcessImageEmbeddingParams; +} | { + $type: 'ProcessImageVersions'; + data: ProcessImageVersionsParams; }; /** A node entry in a blueprint table. Either a string shorthand or a typed object. */ export type BlueprintNode = BlueprintNodeShorthand | BlueprintNodeObject; @@ -1654,4 +1901,10 @@ export interface BlueprintDefinition { storage?: BlueprintStorageConfig[]; /** Achievement definitions. Each entry creates a level with requirements and optional rewards in the events_module. Requires events_module to be provisioned (e.g., via entity_types[].has_levels = true or modules includes events_module). */ achievements?: BlueprintAchievement[]; + /** Top-level namespace configuration array (Phase 0.6). Each entry has an optional scope ("app" or "org"). App-scoped (default) creates namespace_module with membership_type = NULL. Org-scoped creates per-org namespaces. For entity-scoped namespaces, use entity_types[].namespaces instead. */ + namespaces?: BlueprintNamespaceConfig[]; + /** Top-level function configuration array (Phase 0.6). Each entry has an optional scope ("app" or "org"). App-scoped (default) creates function_module with membership_type = NULL. Org-scoped creates per-org functions. For entity-scoped functions, use entity_types[].functions instead. */ + functions?: BlueprintFunctionConfig[]; + /** Top-level agent configuration array (Phase 0.6). Each entry has an optional scope ("app" or "org"). App-scoped (default) creates agent_module with membership_type = NULL. Org-scoped creates per-org agents. For entity-scoped agents, use entity_types[].agents instead. */ + agents?: BlueprintAgentConfig[]; } diff --git a/packages/node-type-registry/src/codegen/generate-types.ts b/packages/node-type-registry/src/codegen/generate-types.ts index 5fbd23326..660ed500d 100644 --- a/packages/node-type-registry/src/codegen/generate-types.ts +++ b/packages/node-type-registry/src/codegen/generate-types.ts @@ -739,7 +739,7 @@ function buildBlueprintTableUniqueConstraint(): t.ExportNamedDeclaration { /** * Build the BlueprintBucketSeed interface. * - * Matches the bucket entries in storage_config.buckets[]. + * Matches the bucket entries in storage.buckets[]. */ function buildBlueprintBucketSeed(): t.ExportNamedDeclaration { return addJSDoc( @@ -775,14 +775,14 @@ function buildBlueprintBucketSeed(): t.ExportNamedDeclaration { 'CORS allowed origins for this bucket.' ) ]), - 'A bucket seed entry for storage_config.buckets[]. Creates an initial bucket row in the {prefix}_buckets table during entity type provisioning. Only used for app-level storage (not entity-scoped).' + 'A bucket seed entry for storage.buckets[]. Creates an initial bucket row in the {prefix}_buckets table during entity type provisioning. Only used for app-level storage (not entity-scoped).' ); } /** * Build the BlueprintStorageConfig interface. * - * Matches the jsonb shape accepted by storage_config on entity_type_provision. + * Matches the jsonb shape accepted by storage on entity_type_provision. */ function buildBlueprintStorageConfig(): t.ExportNamedDeclaration { return addJSDoc( @@ -954,6 +954,247 @@ function buildBlueprintAchievement(): t.ExportNamedDeclaration { ); } +// --------------------------------------------------------------------------- +// Module config types (namespace, function, agent, graph) +// --------------------------------------------------------------------------- + +/** + * Build the BlueprintNamespaceConfig interface. + * + * Matches the jsonb shape accepted by namespaces on entity_type_provision + * and the top-level definition.namespaces[] array in construct_blueprint(). + */ +function buildBlueprintNamespaceConfig(): t.ExportNamedDeclaration { + return addJSDoc( + exportInterface('BlueprintNamespaceConfig', [ + addJSDoc( + optionalProp( + 'scope', + t.tsUnionType([ + t.tsLiteralType(t.stringLiteral('app')), + t.tsLiteralType(t.stringLiteral('org')) + ]) + ), + 'Namespace scope. "app" (default) creates app-level namespaces (membership_type = NULL). "org" creates per-org namespaces. Only used at the top level of a blueprint definition — entity-scoped namespaces inherit scope from the entity type.' + ), + addJSDoc( + optionalProp('key', t.tsStringKeyword()), + 'Module discriminator for multi-module namespaces. Defaults to "default" (omitted from table names). Non-default keys appear as an infix: {prefix}_{key}_namespaces.' + ), + addJSDoc( + optionalProp( + 'policies', + t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy'))) + ), + 'RLS policy overrides for the namespaces table. NULL = apply defaults from apply_namespace_security().' + ), + addJSDoc( + optionalProp( + 'provisions', + t.tsTypeLiteral([ + optionalProp( + 'namespaces', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ), + optionalProp( + 'namespace_events', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ) + ]) + ), + 'Per-table overrides for namespace tables. Each key targets a specific table (namespaces, namespace_events) and uses the same shape as table_provision: { nodes, fields, grants, use_rls, policies }. Fanned out to secure_table_provision.' + ) + ]), + 'Namespace module configuration. When used at the top level of a blueprint, the scope field controls whether namespaces are app-level ("app", default) or org-level ("org"). When used inside entity_types[], scope is inherited from the entity type. Provisions a namespaces table with computed-name proxy, rename trigger, and entity-scoped RLS.' + ); +} + +/** + * Build the BlueprintFunctionConfig interface. + * + * Matches the jsonb shape accepted by functions on entity_type_provision + * and the top-level definition.functions[] array in construct_blueprint(). + */ +function buildBlueprintFunctionConfig(): t.ExportNamedDeclaration { + return addJSDoc( + exportInterface('BlueprintFunctionConfig', [ + addJSDoc( + optionalProp( + 'scope', + t.tsUnionType([ + t.tsLiteralType(t.stringLiteral('app')), + t.tsLiteralType(t.stringLiteral('org')) + ]) + ), + 'Function scope. "app" (default) creates app-level functions (membership_type = NULL). "org" creates per-org functions. Only used at the top level of a blueprint definition — entity-scoped functions inherit scope from the entity type.' + ), + addJSDoc( + optionalProp('key', t.tsStringKeyword()), + 'Module discriminator for multi-module functions. Defaults to "default" (omitted from table names). Non-default keys appear as an infix: {prefix}_{key}_function_definitions.' + ), + addJSDoc( + optionalProp( + 'policies', + t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy'))) + ), + 'RLS policy overrides for the function tables. NULL = apply defaults from apply_function_security().' + ), + addJSDoc( + optionalProp( + 'provisions', + t.tsTypeLiteral([ + optionalProp( + 'definitions', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ), + optionalProp( + 'invocations', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ), + optionalProp( + 'execution_logs', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ) + ]) + ), + 'Per-table overrides for function tables. Each key targets a specific table (definitions, invocations, execution_logs) and uses the same shape as table_provision: { nodes, fields, grants, use_rls, policies }. Fanned out to secure_table_provision.' + ) + ]), + 'Function module configuration. When used at the top level of a blueprint, the scope field controls whether functions are app-level ("app", default) or org-level ("org"). When used inside entity_types[], scope is inherited from the entity type. Provisions function_definitions, function_invocations (partitioned, 12-month retention), and function_execution_logs tables.' + ); +} + +/** + * Build the BlueprintAgentConfig interface. + * + * Matches the jsonb shape accepted by agents on entity_type_provision + * and the top-level definition.agents[] array in construct_blueprint(). + */ +function buildBlueprintAgentConfig(): t.ExportNamedDeclaration { + return addJSDoc( + exportInterface('BlueprintAgentConfig', [ + addJSDoc( + optionalProp( + 'scope', + t.tsUnionType([ + t.tsLiteralType(t.stringLiteral('app')), + t.tsLiteralType(t.stringLiteral('org')) + ]) + ), + 'Agent scope. "app" (default) creates app-level agent tables (membership_type = NULL). "org" creates per-org agent tables. Only used at the top level of a blueprint definition — entity-scoped agents inherit scope from the entity type.' + ), + addJSDoc( + optionalProp('key', t.tsStringKeyword()), + 'Module discriminator for multi-module agents. Defaults to "default" (omitted from table names). Non-default keys appear as an infix: {prefix}_{key}_agent_thread.' + ), + addJSDoc( + optionalProp('api_name', t.tsStringKeyword()), + 'API name for the agent module. Used in GraphQL naming. Defaults to "agent".' + ), + addJSDoc( + optionalProp('has_knowledge', t.tsBooleanKeyword()), + 'Whether to provision the agent_knowledge table with vector embeddings, tags, and trigger_phrases. Also inferred when a "knowledge" key is present. Defaults to false.' + ), + addJSDoc( + optionalProp( + 'knowledge', + t.tsTypeLiteral([ + optionalProp('has_chunks', t.tsBooleanKeyword()), + optionalProp('dimensions', t.tsNumberKeyword()), + optionalProp('chunk_size', t.tsNumberKeyword()), + optionalProp('chunk_overlap', t.tsNumberKeyword()), + optionalProp( + 'chunk_strategy', + t.tsUnionType([ + t.tsLiteralType(t.stringLiteral('fixed')), + t.tsLiteralType(t.stringLiteral('sentence')), + t.tsLiteralType(t.stringLiteral('paragraph')), + t.tsLiteralType(t.stringLiteral('semantic')) + ]) + ), + optionalProp('embedding_model', t.tsStringKeyword()), + optionalProp('embedding_provider', t.tsStringKeyword()), + optionalProp( + 'search_indexes', + t.tsArrayType( + t.tsUnionType([ + t.tsLiteralType(t.stringLiteral('fulltext')), + t.tsLiteralType(t.stringLiteral('bm25')), + t.tsLiteralType(t.stringLiteral('trigram')) + ]) + ) + ) + ]) + ), + 'Knowledge configuration overrides. Set has_chunks to false to disable the chunking pipeline. Controls vector dimensions, chunking strategy, embedding model/provider, and text search indexes for the agent_knowledge table. Presence implies has_knowledge = true.' + ), + addJSDoc( + optionalProp( + 'policies', + t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy'))) + ), + 'RLS policy overrides for the agent tables. NULL = apply defaults from apply_agent_security().' + ), + addJSDoc( + optionalProp( + 'provisions', + t.tsTypeLiteral([ + optionalProp( + 'thread', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ), + optionalProp( + 'message', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ), + optionalProp( + 'task', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ), + optionalProp( + 'prompt', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ), + optionalProp( + 'knowledge', + t.tsTypeReference(t.identifier('BlueprintEntityTableProvision')) + ) + ]) + ), + 'Per-table overrides for agent tables. Each key targets a specific table (thread, message, task, prompt, knowledge) and uses the same shape as table_provision: { nodes, fields, grants, use_rls, policies }. Fanned out to secure_table_provision.' + ) + ]), + 'Agent module configuration. When used at the top level of a blueprint, the scope field controls whether agents are app-level ("app", default) or org-level ("org"). When used inside entity_types[], scope is inherited from the entity type. Provisions thread, message, task, prompt tables (and optionally knowledge with vector embeddings).' + ); +} + +/** + * Build the BlueprintGraphConfig interface. + * + * Matches the jsonb shape accepted by graphs on entity_type_provision. + * Graph module requires a merkle_store_module_id dependency, so + * entity_type_provision only registers permissions. The graph module itself + * must be provisioned separately with the merkle store dependency resolved. + */ +function buildBlueprintGraphConfig(): t.ExportNamedDeclaration { + return addJSDoc( + exportInterface('BlueprintGraphConfig', [ + addJSDoc( + optionalProp('key', t.tsStringKeyword()), + 'Module discriminator for multi-module graphs. Defaults to "default".' + ), + addJSDoc( + optionalProp( + 'policies', + t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy'))) + ), + 'RLS policy overrides for the graph tables. NULL = apply defaults from apply_graph_security().' + ) + ]), + 'Graph module configuration. Presence triggers permission registration (manage_graphs, execute_graphs). The graph module requires a merkle_store_module_id dependency, so entity_type_provision only registers permissions here — the graph module itself must be provisioned separately.' + ); +} + function buildBlueprintEntityTableProvision(): t.ExportNamedDeclaration { return addJSDoc( exportInterface('BlueprintEntityTableProvision', [ @@ -1065,7 +1306,43 @@ function buildBlueprintEntityType(): t.ExportNamedDeclaration { t.tsTypeReference(t.identifier('BlueprintStorageConfig')) ) ), - 'Storage module configuration array. Each entry provisions a separate storage module with its own tables, RLS, and settings. When non-empty, has_storage is derived as true. Each entry may specify a storage_key for multi-module support (defaults to "default").' + 'Storage module configuration array. Presence triggers provisioning (same inference model as namespaces, functions, agents). Each entry provisions a separate storage module with its own tables, RLS, and settings. Each entry may specify a storage_key for multi-module support (defaults to "default").' + ), + addJSDoc( + optionalProp( + 'namespaces', + t.tsArrayType( + t.tsTypeReference(t.identifier('BlueprintNamespaceConfig')) + ) + ), + 'Namespace module configuration array. Presence triggers provisioning. Each entry provisions a namespace_module with its own tables, computed-name proxy, and entity-scoped RLS. Registers manage_namespaces permission bit. "[{}]" = provision one default namespace module.' + ), + addJSDoc( + optionalProp( + 'functions', + t.tsArrayType( + t.tsTypeReference(t.identifier('BlueprintFunctionConfig')) + ) + ), + 'Function module configuration array. Presence triggers provisioning. Each entry provisions function_definitions, function_invocations (partitioned), and function_execution_logs tables. Registers manage_functions + invoke_functions permission bits. "[{}]" = provision one default function module.' + ), + addJSDoc( + optionalProp( + 'agents', + t.tsArrayType( + t.tsTypeReference(t.identifier('BlueprintAgentConfig')) + ) + ), + 'Agent module configuration array. Presence triggers provisioning. Each entry provisions thread, message, task, prompt tables (and optionally knowledge with vector embeddings). "[{}]" = provision one default agent module.' + ), + addJSDoc( + optionalProp( + 'graphs', + t.tsArrayType( + t.tsTypeReference(t.identifier('BlueprintGraphConfig')) + ) + ), + 'Graph module configuration array. Presence triggers permission registration (manage_graphs, execute_graphs). Graph module requires a merkle_store_module_id dependency, so entity_type_provision only registers permissions here. "[{}]" = register default graph permissions.' ) ]), 'An entity type entry for Phase 0 of construct_blueprint(). When name is provided, provisions a new entity type with its own entity table, membership modules, and security policies via entity_type_provision. When name is omitted and only prefix is given, extends an existing entity type (e.g., the built-in "org") with additional capabilities like storage — without creating a new entity type.' @@ -1214,6 +1491,33 @@ function buildBlueprintDefinition(): t.ExportNamedDeclaration { ) ), 'Achievement definitions. Each entry creates a level with requirements and optional rewards in the events_module. Requires events_module to be provisioned (e.g., via entity_types[].has_levels = true or modules includes events_module).' + ), + addJSDoc( + optionalProp( + 'namespaces', + t.tsArrayType( + t.tsTypeReference(t.identifier('BlueprintNamespaceConfig')) + ) + ), + 'Top-level namespace configuration array (Phase 0.6). Each entry has an optional scope ("app" or "org"). App-scoped (default) creates namespace_module with membership_type = NULL. Org-scoped creates per-org namespaces. For entity-scoped namespaces, use entity_types[].namespaces instead.' + ), + addJSDoc( + optionalProp( + 'functions', + t.tsArrayType( + t.tsTypeReference(t.identifier('BlueprintFunctionConfig')) + ) + ), + 'Top-level function configuration array (Phase 0.6). Each entry has an optional scope ("app" or "org"). App-scoped (default) creates function_module with membership_type = NULL. Org-scoped creates per-org functions. For entity-scoped functions, use entity_types[].functions instead.' + ), + addJSDoc( + optionalProp( + 'agents', + t.tsArrayType( + t.tsTypeReference(t.identifier('BlueprintAgentConfig')) + ) + ), + 'Top-level agent configuration array (Phase 0.6). Each entry has an optional scope ("app" or "org"). App-scoped (default) creates agent_module with membership_type = NULL. Org-scoped creates per-org agents. For entity-scoped agents, use entity_types[].agents instead.' ) ]), 'The complete blueprint definition -- the JSONB shape accepted by construct_blueprint().' @@ -1263,7 +1567,7 @@ function buildProgram(meta?: MetaTableInfo[]): string { statements.push(buildTriggerConditionInterface()); // -- Parameter interfaces grouped by category -- - const categoryOrder = ['billing', 'check', 'data', 'limit', 'search', 'job', 'process', 'authz', 'relation', 'view']; + const categoryOrder = ['billing', 'check', 'data', 'event', 'limit', 'limit_enforce', 'limit_track', 'limit_warning', 'search', 'job', 'process', 'authz', 'relation', 'view']; for (const cat of categoryOrder) { const nts = categories.get(cat); if (!nts || nts.length === 0) continue; @@ -1295,6 +1599,10 @@ function buildProgram(meta?: MetaTableInfo[]): string { statements.push(buildBlueprintAchievementRequirement()); statements.push(buildBlueprintAchievementReward()); statements.push(buildBlueprintAchievement()); + statements.push(buildBlueprintNamespaceConfig()); + statements.push(buildBlueprintFunctionConfig()); + statements.push(buildBlueprintAgentConfig()); + statements.push(buildBlueprintGraphConfig()); statements.push(buildBlueprintEntityTableProvision()); statements.push(buildBlueprintEntityType()); diff --git a/packages/node-type-registry/src/module-presets/full.ts b/packages/node-type-registry/src/module-presets/full.ts index dc26176b0..79a71d42c 100644 --- a/packages/node-type-registry/src/module-presets/full.ts +++ b/packages/node-type-registry/src/module-presets/full.ts @@ -90,7 +90,7 @@ export const PresetFull: ModulePreset = { omits_notes: { compute_log_module: 'Usage logging is opt-in. Add explicitly if needed.', inference_log_module: 'Usage logging is opt-in. Add explicitly if needed.', - agent_chat_module: 'Agent infrastructure is opt-in.' + agent_module: 'Agent infrastructure is opt-in.' }, extends: ['b2b'] };