Skip to content

Commit 06a09b0

Browse files
authored
Merge pull request #104 from constructive-io/feat/compound-conditions-docs
docs: add compound conditions + DataImageEmbedding to skills
2 parents 33a1edb + de81be6 commit 06a09b0

3 files changed

Lines changed: 164 additions & 7 deletions

File tree

.agents/skills/constructive-jobs/SKILL.md

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: constructive-jobs
3-
description: "Background job system — DataJobTrigger blueprint node for enqueuing jobs on row changes, payload strategies, the Knative worker pipeline, scheduled jobs, and the app_jobs database extension. Use when asked to 'trigger a job', 'enqueue a background task', 'add a job trigger', 'run a function on row change', 'schedule a job', or when working with DataJobTrigger in blueprints."
3+
description: "Background job system — DataJobTrigger blueprint node for enqueuing jobs on row changes (with compound conditions support: AND/OR/NOT combinators, column-aware type resolution), DataImageEmbedding composition wrapper, payload strategies, the Knative worker pipeline, scheduled jobs, and the app_jobs database extension. Use when asked to 'trigger a job', 'enqueue a background task', 'add a job trigger', 'run a function on row change', 'schedule a job', 'compound conditions', 'image embedding trigger', or when working with DataJobTrigger/DataImageEmbedding in blueprints."
44
metadata:
55
author: constructive-io
66
version: "1.0.0"
@@ -73,8 +73,9 @@ This creates INSERT and UPDATE triggers that enqueue a `process_invoice` job wit
7373
| `payload_custom` | object || Key-to-column mapping for `custom` strategy |
7474
| `events` | `("INSERT" \| "UPDATE" \| "DELETE")[]` | `["INSERT", "UPDATE"]` | Which DML events fire the trigger |
7575
| `watch_fields` | string[] || For UPDATE: only fire when these columns change |
76-
| `condition_field` | string || Column for conditional WHEN clause |
77-
| `condition_value` | string || Value to match in WHEN clause |
76+
| `condition_field` | string || Legacy: column for simple equality WHEN clause |
77+
| `condition_value` | string || Legacy: value to match for `condition_field` |
78+
| `conditions` | object \| array || Compound conditions for WHEN clause (see below) |
7879
| `include_old` | boolean | `false` | Include OLD row in UPDATE payload |
7980
| `include_meta` | boolean | `false` | Include table/schema metadata in payload |
8081
| `job_key` | string || Static key for upsert semantics (deduplication) |
@@ -83,7 +84,49 @@ This creates INSERT and UPDATE triggers that enqueue a `process_invoice` job wit
8384
| `run_at_delay` | string || PostgreSQL interval delay (e.g., `'30 seconds'`) |
8485
| `max_attempts` | integer | `25` | Maximum retry attempts |
8586

86-
**Constraint:** `condition_field` and `watch_fields` cannot both be specified on the same trigger.
87+
**Constraints:** `conditions`, `condition_field`, and `watch_fields` are mutually exclusive — only one can be specified per trigger.
88+
89+
### Compound Conditions
90+
91+
The `conditions` parameter accepts a structured JSON syntax for complex WHEN clauses. Column types are resolved automatically from the PostgreSQL schema — values in JSON are cast to the correct type at generation time.
92+
93+
**Leaf condition:**
94+
```typescript
95+
{ field: 'status', op: '=', value: 'ready', row: 'NEW' }
96+
```
97+
98+
| Key | Required | Default | Description |
99+
|-----|----------|---------|-------------|
100+
| `field` | yes || Column name (validated against the table) |
101+
| `op` | yes || `=`, `!=`, `>`, `<`, `>=`, `<=`, `LIKE`, `NOT LIKE`, `IS NULL`, `IS NOT NULL`, `IS DISTINCT FROM` |
102+
| `value` | conditional || Comparison value (omit for `IS NULL`, `IS NOT NULL`, `IS DISTINCT FROM`) |
103+
| `row` | no | `'NEW'` | Row reference: `'NEW'` or `'OLD'` |
104+
| `ref` | no || Column reference for field-to-field comparison: `{ field: '...', row: '...' }` |
105+
106+
**Array shorthand (implicit AND):**
107+
```typescript
108+
conditions: [
109+
{ field: 'status', op: '=', value: 'ready' },
110+
{ field: 'status', op: '=', value: 'pending', row: 'OLD' },
111+
{ field: 'mime_type', op: 'LIKE', value: 'image/%' },
112+
]
113+
```
114+
115+
**Nested combinators (AND/OR/NOT):**
116+
```typescript
117+
conditions: {
118+
AND: [
119+
{ field: 'status', op: '=', value: 'ready' },
120+
{ OR: [
121+
{ field: 'mime_type', op: 'LIKE', value: 'image/%' },
122+
{ field: 'mime_type', op: 'LIKE', value: 'video/%' },
123+
]},
124+
{ NOT: { field: 'is_draft', op: '=', value: true } },
125+
]
126+
}
127+
```
128+
129+
See [references/common-patterns.md](references/common-patterns.md) for full blueprint examples.
87130

88131
### Payload Strategies
89132

@@ -99,11 +142,45 @@ See [references/payload-strategies.md](references/payload-strategies.md) for det
99142
### Common Patterns
100143

101144
See [references/common-patterns.md](references/common-patterns.md) for full blueprint examples of:
102-
- Conditional triggers (`watch_fields`, `condition_field`)
145+
- Conditional triggers (`watch_fields`, `condition_field`, `conditions`)
146+
- Compound conditions (status transitions, MIME type filtering)
103147
- Delayed/debounced jobs (`run_at_delay` + `job_key`)
104148
- Multiple triggers per table
105149
- Email on invite, Stripe sync, audit trail, webhook dispatch
106150

151+
## DataImageEmbedding Blueprint Node
152+
153+
Composition wrapper that combines SearchVector + DataJobTrigger with image-specific defaults. Creates a vector embedding field with HNSW index and a job trigger that fires when image files transition to ready status.
154+
155+
```typescript
156+
{
157+
ref: 'files',
158+
table_name: 'files',
159+
nodes: [
160+
...STORAGE_NODES,
161+
{ $type: 'DataImageEmbedding' },
162+
],
163+
}
164+
```
165+
166+
| Parameter | Type | Default | Description |
167+
|-----------|------|---------|-------------|
168+
| `field_name` | string | `'embedding'` | Vector column name |
169+
| `dimensions` | integer | `512` | Vector dimensions |
170+
| `index_method` | `'hnsw'` \| `'ivfflat'` | `'hnsw'` | Index type |
171+
| `metric` | `'cosine'` \| `'l2'` \| `'ip'` | `'cosine'` | Distance metric |
172+
| `task_identifier` | string | `'process_image_embedding'` | Job task name |
173+
| `status_field` | string | `'status'` | Upload lifecycle status column |
174+
| `status_ready_value` | string | `'ready'` | Value indicating file is ready |
175+
| `status_pending_value` | string | `'pending'` | Value indicating file is pending |
176+
| `mime_patterns` | string[] | `['image/%']` | MIME type LIKE patterns (OR'd together) |
177+
| `payload_custom` | object | `{file_id: 'id', key: 'key', ...}` | Custom job payload mapping |
178+
179+
The generated WHEN clause:
180+
```sql
181+
NEW.status = 'ready' AND OLD.status = 'pending' AND NEW.mime_type LIKE 'image/%'
182+
```
183+
107184
## Knative Worker Stack
108185

109186
The runtime consists of three packages:
@@ -156,4 +233,4 @@ The scheduler component in `knative-job-service` evaluates cron expressions and
156233
- **[`constructive-safegres`](../constructive-safegres/SKILL.md)** — Security policies for tables with job triggers
157234
- **Blueprint definition format**[blueprints.md](../constructive-platform/references/blueprint-definition-format.md) for the full node types table
158235

159-
For SQL-level internals (generator functions, AST helpers, trigger function source), see the `constructive-job-triggers` skill in `constructive-private-skills`.
236+
For SQL-level internals (generator functions, AST helpers, trigger function source), see the `constructive-db-compound-conditions` and `constructive-db-data-modules` skills in `constructive-io/constructive-db`.

.agents/skills/constructive-jobs/references/common-patterns.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,80 @@ Creates a WHEN clause: `WHEN (NEW.status = 'published')`. The trigger only fires
8989

9090
**Note:** `condition_field` and `watch_fields` cannot both be specified.
9191

92+
## 3b. Compound Conditions (Status Transition)
93+
94+
Fire only when a row transitions from one status to another:
95+
96+
```typescript
97+
{
98+
$type: 'DataJobTrigger',
99+
data: {
100+
task_identifier: 'process_published',
101+
events: ['UPDATE'],
102+
payload_strategy: 'custom',
103+
payload_custom: { doc_id: 'id', title: 'title' },
104+
conditions: [
105+
{ field: 'status', op: '=', value: 'published' },
106+
{ field: 'status', op: '=', value: 'draft', row: 'OLD' },
107+
],
108+
},
109+
}
110+
```
111+
112+
Creates a WHEN clause: `WHEN (NEW.status = 'published' AND OLD.status = 'draft')`. The trigger only fires when `status` changes from `'draft'` to `'published'`.
113+
114+
## 3c. Compound Conditions with OR (MIME Type Filtering)
115+
116+
Fire when status transitions AND the row matches one of several MIME patterns:
117+
118+
```typescript
119+
{
120+
$type: 'DataJobTrigger',
121+
data: {
122+
task_identifier: 'process_media',
123+
events: ['UPDATE'],
124+
payload_strategy: 'custom',
125+
payload_custom: { file_id: 'id', key: 'key', mime_type: 'mime_type' },
126+
include_meta: true,
127+
conditions: {
128+
AND: [
129+
{ field: 'status', op: '=', value: 'ready' },
130+
{ field: 'status', op: '=', value: 'pending', row: 'OLD' },
131+
{ OR: [
132+
{ field: 'mime_type', op: 'LIKE', value: 'image/%' },
133+
{ field: 'mime_type', op: 'LIKE', value: 'video/%' },
134+
]},
135+
]
136+
},
137+
},
138+
}
139+
```
140+
141+
## 3d. DataImageEmbedding (Composition Shorthand)
142+
143+
For the common pattern of embedding image files on status transition, use `DataImageEmbedding` instead of manually wiring SearchVector + DataJobTrigger:
144+
145+
```typescript
146+
nodes: [
147+
...STORAGE_NODES,
148+
{ $type: 'DataImageEmbedding' },
149+
]
150+
```
151+
152+
Equivalent to manually configuring SearchVector (512-dim, HNSW, cosine) + DataJobTrigger (UPDATE, `status: pending→ready`, `mime_type LIKE 'image/%'`). Override defaults as needed:
153+
154+
```typescript
155+
{
156+
$type: 'DataImageEmbedding',
157+
data: {
158+
dimensions: 1024,
159+
metric: 'l2',
160+
mime_patterns: ['image/%', 'video/%'],
161+
task_identifier: 'custom_embedding_worker',
162+
},
163+
}
164+
```
165+
92166
## 4. Audit Trail on Delete
93167

94168
Capture full row data before deletion:

.agents/skills/constructive-platform/references/blueprint-definition-format.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,13 @@ All 25 node types from the `node_type_registry`:
210210
| `DataInheritFromParent` | Copies field values from parent row (via FK) on insert/update | `parent_fk_field` (required — FK field pointing to parent), `fields` (required, array of field names to copy) |
211211
| `DataForceCurrentUser` | Forces a field to `current_user_id()` on insert/update | `field_name` (default `'actor_id'` — must already exist) |
212212
| `DataImmutableFields` | Prevents fields from being modified after initial insert | `fields` (required, array of field names to protect) |
213-
| `DataJobTrigger` | Creates triggers that enqueue background jobs via `app_jobs.add_job()` | `task_identifier` (required), `payload_strategy` (default `'row_id'`), `watch_fields` (optional array), `events` (default `['INSERT','UPDATE']`), `condition_field`/`condition_value` (optional), `payload_fields` (optional array), `include_old` (default `false`), `include_meta` (default `false`), `job_key`, `queue_name`, `priority`, `run_at_delay`, `max_attempts` — see [`constructive-jobs`](../../constructive-jobs/SKILL.md) |
213+
| `DataJobTrigger` | Creates triggers that enqueue background jobs via `app_jobs.add_job()` | `task_identifier` (required), `payload_strategy` (default `'row_id'`), `events` (default `['INSERT','UPDATE']`), `conditions` (compound WHEN clause — leaf conditions, AND/OR/NOT combinators, column-aware type resolution), `condition_field`/`condition_value` (legacy simple equality), `watch_fields` (optional array), `payload_fields` (optional array), `payload_custom` (object), `include_old` (default `false`), `include_meta` (default `false`), `job_key`, `queue_name`, `priority`, `run_at_delay`, `max_attempts` — see [`constructive-jobs`](../../constructive-jobs/SKILL.md) |
214+
215+
#### Composition
216+
217+
| Node Type | Purpose | `data` options |
218+
|-----------|---------|----------------|
219+
| `DataImageEmbedding` | Combines SearchVector + DataJobTrigger for image embedding pipelines | `field_name` (default `'embedding'`), `dimensions` (default `512`), `index_method` (`'hnsw'`\|`'ivfflat'`), `metric` (`'cosine'`\|`'l2'`\|`'ip'`), `task_identifier` (default `'process_image_embedding'`), `status_field` (default `'status'`), `status_ready_value` (default `'ready'`), `status_pending_value` (default `'pending'`), `mime_patterns` (default `['image/%']`), `payload_custom` — see [`constructive-jobs`](../../constructive-jobs/SKILL.md) |
214220

215221
#### Search & AI
216222

0 commit comments

Comments
 (0)