pgkiln 0.51.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/SECURITY.md +5 -0
  2. package/db/migrations/121_ai_agents.sql +94 -0
  3. package/db/migrations/122_team_development.sql +186 -0
  4. package/db/migrations/123_workflow_outcomes.sql +407 -0
  5. package/db/migrations/124_component_details.sql +51 -0
  6. package/docs/apex-feature-parity.md +12 -10
  7. package/docs/guide/01-installation.md +7 -3
  8. package/docs/guide/03-builder.md +121 -7
  9. package/docs/guide/04-pages-and-regions.md +71 -21
  10. package/docs/guide/05-items.md +7 -1
  11. package/docs/guide/06-processing.md +123 -8
  12. package/docs/guide/07-dynamic-actions.md +34 -0
  13. package/docs/guide/09-reference.md +2 -1
  14. package/docs/guide/11-from-apex.md +2 -1
  15. package/docs/guide/12-development.md +44 -8
  16. package/docs/guide/20-ai-agents.md +4 -2
  17. package/examples/hr/README.md +2 -0
  18. package/examples/hr/hr_64_ai_agent.sql +97 -0
  19. package/examples/hr/hr_65_ask_reports.sql +21 -0
  20. package/examples/hr/hr_66_team_development.sql +58 -0
  21. package/examples/hr/hr_67_workflow_outcomes.sql +113 -0
  22. package/examples/hr/hr_68_component_details.sql +96 -0
  23. package/package.json +1 -1
  24. package/public/app.css +40 -0
  25. package/public/app.js +134 -20
  26. package/public/builder.css +83 -1
  27. package/public/builder.js +47 -0
  28. package/src/ai/chat.ts +18 -18
  29. package/src/ai/service.ts +35 -11
  30. package/src/ai/types.ts +7 -2
  31. package/src/app.ts +6 -0
  32. package/src/builder/advisor.ts +6 -3
  33. package/src/builder/ai-agents.ts +372 -0
  34. package/src/builder/ai-builder.ts +3 -3
  35. package/src/builder/ai.ts +13 -9
  36. package/src/builder/assistant.ts +22 -7
  37. package/src/builder/components.ts +38 -9
  38. package/src/builder/designer.ts +8 -3
  39. package/src/builder/feedback.ts +6 -1
  40. package/src/builder/locks.ts +7 -1
  41. package/src/builder/region-help.ts +2 -2
  42. package/src/builder/routes.ts +4 -1
  43. package/src/builder/shared.ts +1 -1
  44. package/src/builder/team.ts +991 -0
  45. package/src/builder/ui.ts +2 -1
  46. package/src/builder/workflow-migrate.ts +210 -0
  47. package/src/builder/workflows.ts +29 -6
  48. package/src/builder/workspace-home.ts +2 -0
  49. package/src/builder/workspace-transfer.ts +1 -1
  50. package/src/cli/replace.ts +7 -1
  51. package/src/i18n/ar.ts +17 -0
  52. package/src/i18n/cs.ts +17 -0
  53. package/src/i18n/da.ts +17 -0
  54. package/src/i18n/de.ts +17 -0
  55. package/src/i18n/el.ts +17 -0
  56. package/src/i18n/es.ts +17 -0
  57. package/src/i18n/fi.ts +17 -0
  58. package/src/i18n/fr.ts +17 -0
  59. package/src/i18n/he.ts +17 -0
  60. package/src/i18n/it.ts +17 -0
  61. package/src/i18n/ja.ts +17 -0
  62. package/src/i18n/ko.ts +17 -0
  63. package/src/i18n/nb.ts +17 -0
  64. package/src/i18n/pl.ts +17 -0
  65. package/src/i18n/pt.ts +17 -0
  66. package/src/i18n/ru.ts +17 -0
  67. package/src/i18n/sv.ts +17 -0
  68. package/src/i18n/tr.ts +17 -0
  69. package/src/i18n/uk.ts +17 -0
  70. package/src/i18n/zh.ts +17 -0
  71. package/src/i18n.ts +34 -0
  72. package/src/instance.ts +2 -0
  73. package/src/metadata.ts +9 -3
  74. package/src/runtime/ai-agents.ts +365 -0
  75. package/src/runtime/ai-filter.ts +385 -107
  76. package/src/runtime/assistant.ts +64 -11
  77. package/src/runtime/context.ts +2 -0
  78. package/src/runtime/ir-phrases.ts +812 -0
  79. package/src/runtime/ir-plan.ts +449 -0
  80. package/src/runtime/items.ts +1 -1
  81. package/src/runtime/regions.ts +6 -2
  82. package/src/runtime/render.ts +1 -1
  83. package/src/runtime/report.ts +1 -1
  84. package/src/runtime/tasks.ts +63 -12
  85. package/src/task-events.ts +232 -0
  86. package/src/team-samples.ts +74 -0
  87. package/src/team.ts +342 -0
  88. package/src/workflow.ts +135 -18
  89. package/src/workspace-export.ts +16 -1
@@ -0,0 +1,407 @@
1
+ -- =====================================================================
2
+ -- 123: workflows and approvals (APEX 26.1 / 26.2): custom task outcomes,
3
+ -- task events and notifications, draft steps, migrating running instances
4
+ --
5
+ -- Task definitions:
6
+ -- outcomes [{"name": "YES", "label": "Yes", "colour": "success"}, …]: the buttons of the
7
+ -- task and the outcomes a task may end with (stored lower case in meta.task.outcome);
8
+ -- empty or null: the type's own (approval: approved / rejected, action: completed).
9
+ -- Colours: default, primary, success, danger, warning.
10
+ -- events [{"on": "complete" | "expire" | "fail", "type": "sql" | "mail" | "workflow", …}]:
11
+ -- actions when a task is completed, expires, or its completion SQL fails
12
+ -- (src/task-events.ts runs them as the application's role).
13
+ -- fail_recovery after a failing completion SQL: reopen (the decision is undone; the default) or
14
+ -- complete (the decision stands without the SQL's effect).
15
+ -- notifications {"create": "TEMPLATE", "complete": "TEMPLATE", "expire": "TEMPLATE"}: built-in
16
+ -- e-mails from the application's e-mail templates (meta.send_mail_template) to the
17
+ -- potential owners (create) or the initiator (complete; expire: both).
18
+ -- All are columns of meta.task_definition: they travel with export/import as they are.
19
+ --
20
+ -- meta.task_hook: the outbox of task events and notifications (create, complete, expire), filled
21
+ -- by a trigger in the task's transaction and worked off by the server after the commit. Runtime
22
+ -- data like the tasks themselves (no application foreign key: not exported, kept on replace).
23
+ --
24
+ -- Workflows:
25
+ -- a "draft" step type, a placeholder while modelling: allowed in a development version only
26
+ -- (a check on the active steps; activation refuses it);
27
+ -- a "send_mail" step (src/workflow.ts) through meta.send_mail / meta.send_mail_template;
28
+ -- meta.migrate_workflows(): running instances move to a newer version of their definition,
29
+ -- each current step mapped to a step of that version, in one transaction, logged in
30
+ -- meta.workflow_event ("migrated") and meta.workflow_migration (installation data).
31
+ -- =====================================================================
32
+
33
+ -- ---------------------------------------------------------------- outcomes
34
+ /** Why a task definition's outcomes are not valid, or null. */
35
+ create function meta.task_outcomes_problem(p jsonb) returns text
36
+ language plpgsql immutable set search_path = pg_catalog as $$
37
+ declare
38
+ e jsonb;
39
+ v_seen text[] := '{}';
40
+ v_name text;
41
+ begin
42
+ if p is null or p = '[]'::jsonb then
43
+ return null;
44
+ end if;
45
+ if jsonb_typeof(p) <> 'array' then
46
+ return 'Outcomes are a JSON array.';
47
+ end if;
48
+ if jsonb_array_length(p) > 10 then
49
+ return 'At most 10 outcomes.';
50
+ end if;
51
+ for e in select * from jsonb_array_elements(p) loop
52
+ if jsonb_typeof(e) <> 'object' then
53
+ return 'Each outcome is an object {"name": …, "label": …, "colour": …}.';
54
+ end if;
55
+ v_name := lower(e->>'name');
56
+ if v_name is null or v_name !~ '^[a-z][a-z0-9_]{0,29}$' then
57
+ return format('Outcome name %s: letters, digits and underscores (at most 30), e.g. YES.', coalesce(e->>'name', '(none)'));
58
+ end if;
59
+ if v_name in ('cancelled', 'expired', 'default') then
60
+ return format('Outcome name %s is reserved.', e->>'name');
61
+ end if;
62
+ if v_name = any (v_seen) then
63
+ return format('Outcome %s is listed twice.', e->>'name');
64
+ end if;
65
+ v_seen := v_seen || v_name;
66
+ if e ? 'label' and (jsonb_typeof(e->'label') <> 'string' or length(btrim(e->>'label')) not between 1 and 60) then
67
+ return format('Outcome %s: the label is text of 1 to 60 characters.', e->>'name');
68
+ end if;
69
+ if e ? 'colour' and coalesce(e->>'colour', '') not in ('default', 'primary', 'success', 'danger', 'warning') then
70
+ return format('Outcome %s: the colour is default, primary, success, danger or warning.', e->>'name');
71
+ end if;
72
+ end loop;
73
+ return null;
74
+ end
75
+ $$;
76
+
77
+ alter table meta.task_definition
78
+ add column outcomes jsonb constraint task_definition_outcomes_check check (meta.task_outcomes_problem(outcomes) is null),
79
+ add column events jsonb check (events is null or (jsonb_typeof(events) = 'array' and jsonb_array_length(events) <= 20)),
80
+ add column fail_recovery text check (fail_recovery in ('reopen', 'complete')),
81
+ add column notifications jsonb check (notifications is null or (jsonb_typeof(notifications) = 'object'
82
+ and notifications - 'create' - 'complete' - 'expire' = '{}'::jsonb));
83
+ comment on column meta.task_definition.outcomes is 'Custom outcomes (123): [{"name", "label", "colour"}]; empty: the type''s own.';
84
+ comment on column meta.task_definition.events is 'Task events (123): [{"on": complete|expire|fail, "type": sql|mail|workflow, …}].';
85
+ comment on column meta.task_definition.notifications is 'Built-in notifications (123): {"create"|"complete"|"expire": e-mail template name}.';
86
+
87
+ /** The outcomes a task of this definition may end with (lower case). */
88
+ create function meta.task_outcome_names(d meta.task_definition) returns text[]
89
+ language sql immutable set search_path = pg_catalog as $$
90
+ select case when d.outcomes is not null and jsonb_array_length(d.outcomes) > 0
91
+ then array(select lower(e->>'name') from jsonb_array_elements(d.outcomes) e)
92
+ when d.type = 'approval' then array['approved', 'rejected']
93
+ else array['completed'] end
94
+ $$;
95
+
96
+ alter table meta.task drop constraint task_outcome_check;
97
+ alter table meta.task add constraint task_outcome_check check (outcome ~ '^[a-z][a-z0-9_]{0,29}$');
98
+ alter table meta.task_event drop constraint task_event_event_check;
99
+ alter table meta.task_event add constraint task_event_event_check
100
+ check (event in ('created', 'claimed', 'released', 'delegated', 'approved', 'rejected', 'completed', 'cancelled', 'commented', 'closed',
101
+ 'expired', 'renewed', 'escalated', 'failed'));
102
+
103
+ -- Same as 021, plus: the outcomes of the definition (custom ones too). A custom outcome is logged as
104
+ -- "completed" with its label. Returns also the outcome, its label, the fail events and the recovery.
105
+ create or replace function meta.complete_task(p_id bigint, p_outcome text, p_comment text default null) returns jsonb
106
+ language plpgsql security definer set search_path = meta, pg_catalog as $$
107
+ declare
108
+ t meta.task;
109
+ d meta.task_definition;
110
+ v_out text := lower(btrim(coalesce(p_outcome, '')));
111
+ v_label text;
112
+ begin
113
+ t := meta.task_load(p_id, 'act');
114
+ select * into d from task_definition where id = t.definition_id;
115
+ if not v_out = any (meta.task_outcome_names(d)) then
116
+ raise exception 'A task of % cannot end as %.', d.name, left(coalesce(p_outcome, '(none)'), 60);
117
+ end if;
118
+ select e->>'label' into v_label from jsonb_array_elements(coalesce(d.outcomes, '[]')) e where lower(e->>'name') = v_out;
119
+ update task set state = 'completed', outcome = v_out, completed_at = now(), completed_by = meta.app_user(),
120
+ actual_owner = coalesce(actual_owner, meta.app_user())
121
+ where id = p_id;
122
+ if v_out in ('approved', 'rejected', 'completed') then
123
+ perform meta.task_log(p_id, v_out, p_comment);
124
+ else
125
+ perform meta.task_log(p_id, 'completed', coalesce(v_label, upper(v_out)) || coalesce(': ' || nullif(btrim(p_comment), ''), ''));
126
+ end if;
127
+ return jsonb_build_object('action_code', d.action_code, 'detail_pk', t.detail_pk, 'params', t.params, 'initiator', t.initiator,
128
+ 'outcome', v_out, 'label', coalesce(v_label, v_out), 'subject', t.subject, 'definition', d.name,
129
+ 'fail_events', coalesce((select jsonb_agg(e) from jsonb_array_elements(coalesce(d.events, '[]')) e where e->>'on' = 'fail'), '[]'),
130
+ 'fail_recovery', coalesce(d.fail_recovery, 'reopen'));
131
+ end
132
+ $$;
133
+
134
+ -- Same as 021, plus: a custom outcome of the definition.
135
+ create or replace function meta.close_tasks(p_name text, p_detail_pk text, p_outcome text default null) returns int
136
+ language plpgsql security definer set search_path = meta, pg_catalog as $$
137
+ declare
138
+ v_n int := 0;
139
+ v_id bigint;
140
+ d meta.task_definition;
141
+ begin
142
+ select * into d from task_definition where app_id = meta.app_id() and name = upper(p_name);
143
+ if p_outcome is not null and not (found and lower(p_outcome) = any (meta.task_outcome_names(d))) then
144
+ raise exception 'Invalid outcome %.', left(p_outcome, 60);
145
+ end if;
146
+ for v_id in
147
+ update task t set state = case when p_outcome is null then 'cancelled' else 'completed' end,
148
+ outcome = lower(p_outcome), completed_at = now(), completed_by = meta.app_user()
149
+ where t.definition_id = d.id and t.detail_pk = p_detail_pk and t.state in ('unassigned', 'assigned')
150
+ returning t.id
151
+ loop
152
+ perform meta.task_log(v_id, 'closed', p_outcome);
153
+ v_n := v_n + 1;
154
+ end loop;
155
+ return v_n;
156
+ end
157
+ $$;
158
+
159
+ -- The completion SQL of a task failed and its fail events ran (the task list, as the acting user).
160
+ create function meta.log_task_failure(p_id bigint, p_detail text) returns void
161
+ language plpgsql security definer set search_path = meta, pg_catalog as $$
162
+ declare
163
+ t meta.task;
164
+ begin
165
+ select * into t from task where id = p_id and app_id = meta.app_id();
166
+ if not found or not ((meta.task_rights(t)).may_act or (t.state = 'completed' and lower(t.completed_by) = lower(meta.app_user()))) then
167
+ raise exception 'Task % not found.', p_id;
168
+ end if;
169
+ perform meta.task_log(p_id, 'failed', left(p_detail, 4000));
170
+ end
171
+ $$;
172
+ revoke all on function meta.log_task_failure(bigint, text) from public;
173
+ grant execute on function meta.log_task_failure(bigint, text) to public;
174
+
175
+ -- ---------------------------------------------------------------- events and notifications: the outbox
176
+ create table meta.task_hook (
177
+ id bigserial primary key,
178
+ task_id bigint not null references meta.task on delete cascade,
179
+ event text not null check (event in ('create', 'complete', 'expire')),
180
+ created_at timestamptz not null default now(),
181
+ attempts int not null default 0,
182
+ done_at timestamptz,
183
+ error text check (length(error) <= 2000)
184
+ );
185
+ create index task_hook_pending on meta.task_hook (id) where done_at is null;
186
+ revoke all on meta.task_hook from public;
187
+ comment on table meta.task_hook is 'Task events and notifications to run after the commit (123); runtime data, not exported.';
188
+
189
+ create function meta.task_hook_enqueue() returns trigger
190
+ language plpgsql security definer set search_path = meta, pg_catalog as $$
191
+ declare
192
+ d meta.task_definition;
193
+ v_event text := case when tg_op = 'INSERT' then 'create' when new.state = 'completed' then 'complete' when new.state = 'expired' then 'expire' end;
194
+ begin
195
+ if v_event is null then
196
+ return new;
197
+ end if;
198
+ select * into d from task_definition where id = new.definition_id;
199
+ if coalesce(d.notifications ? v_event, false)
200
+ or exists (select 1 from jsonb_array_elements(coalesce(d.events, '[]')) e where e->>'on' = v_event) then
201
+ insert into task_hook (task_id, event) values (new.id, v_event);
202
+ perform pg_notify('pgkiln_workflow', 'task');
203
+ end if;
204
+ return new;
205
+ end
206
+ $$;
207
+ create trigger task_hook_create after insert on meta.task for each row execute function meta.task_hook_enqueue();
208
+ create trigger task_hook_state after update of state on meta.task
209
+ for each row when (old.state is distinct from new.state and new.state in ('completed', 'expired')) execute function meta.task_hook_enqueue();
210
+
211
+ /**
212
+ * The e-mail addresses of a task's people (accounts with an address): its potential owners (named users,
213
+ * the actual owner, the application's users with one of its owner roles) and/or its initiator.
214
+ */
215
+ create function meta.task_addresses(p_task_id bigint, p_owners boolean, p_initiator boolean) returns text[]
216
+ language sql stable security definer set search_path = meta, pg_catalog as $$
217
+ select coalesce(array_agg(distinct ac.email order by ac.email), '{}')
218
+ from task t
219
+ join account ac on ac.active and ac.email is not null
220
+ where t.id = p_task_id
221
+ and ((p_initiator and lower(ac.username) = lower(t.initiator))
222
+ or (p_owners and (lower(ac.username) = any (select lower(u) from unnest(t.owner_users) u)
223
+ or lower(ac.username) = lower(t.actual_owner)
224
+ or exists (select 1 from app_access aa
225
+ where aa.app_id = t.app_id and aa.account_id = ac.id
226
+ and exists (select 1 from unnest(aa.roles) r where lower(r) = any (select lower(x) from unnest(t.owner_roles) x))))))
227
+ $$;
228
+ revoke all on function meta.task_addresses(bigint, boolean, boolean) from public;
229
+
230
+ -- ---------------------------------------------------------------- draft steps
231
+ alter table meta.workflow_definition add constraint workflow_definition_no_draft
232
+ check (not jsonb_path_exists(steps, '$[*] ? (@.type == "draft")'));
233
+
234
+ -- Same as 027, plus: a version with draft steps is not activated.
235
+ create or replace function meta.activate_workflow_version(p_definition_id int) returns text
236
+ language plpgsql set search_path = meta, pg_catalog as $$
237
+ declare
238
+ d meta.workflow_definition;
239
+ v_drafts text;
240
+ begin
241
+ select * into d from workflow_definition where id = p_definition_id for update;
242
+ if not found then
243
+ raise exception 'Workflow definition % does not exist.', p_definition_id;
244
+ end if;
245
+ if d.dev_version is null then
246
+ raise exception 'Workflow % has no version in development.', d.name;
247
+ end if;
248
+ if jsonb_array_length(d.dev_steps) = 0 then
249
+ raise exception 'Version % of workflow % has no steps.', d.dev_version, d.name;
250
+ end if;
251
+ select string_agg(s->>'name', ', ') into v_drafts from jsonb_array_elements(d.dev_steps) s where s->>'type' = 'draft';
252
+ if v_drafts is not null then
253
+ raise exception 'Version % of workflow % has draft steps (%): replace them before activating it.', d.dev_version, d.name, v_drafts;
254
+ end if;
255
+ update workflow_definition
256
+ set inactive_versions = inactive_versions || jsonb_build_array(jsonb_build_object(
257
+ 'version', version, 'steps', steps, 'activated_at', activated_at, 'deactivated_at', now())),
258
+ version = dev_version, steps = dev_steps, activated_at = now(),
259
+ dev_version = null, dev_steps = null
260
+ where id = d.id;
261
+ return d.dev_version;
262
+ end
263
+ $$;
264
+ revoke all on function meta.activate_workflow_version(int) from public;
265
+
266
+ -- ---------------------------------------------------------------- migrating running instances
267
+ alter table meta.workflow_event drop constraint workflow_event_event_check;
268
+ alter table meta.workflow_event add constraint workflow_event_event_check
269
+ check (event in ('started', 'step', 'task', 'waiting', 'resumed', 'completed', 'terminated', 'faulted', 'retried', 'split', 'joined', 'cancelled', 'migrated'));
270
+
271
+ create table meta.workflow_migration (
272
+ id bigserial primary key,
273
+ app_id int not null references meta.app on delete cascade,
274
+ definition_name text not null,
275
+ to_version text not null,
276
+ -- current step → step of the new version
277
+ mapping jsonb not null,
278
+ workflow_ids bigint[] not null,
279
+ -- per instance: {"id", "from_version", "from_step", "to_step"}
280
+ detail jsonb not null default '[]',
281
+ migrated_by text not null,
282
+ migrated_at timestamptz not null default now()
283
+ );
284
+ create index on meta.workflow_migration (app_id, migrated_at);
285
+ revoke all on meta.workflow_migration from public;
286
+ comment on table meta.workflow_migration is 'Log of migrations of running workflow instances to a newer version (123); installation data, not exported.';
287
+
288
+ /**
289
+ * Move running instances (p_ids) of a definition to a newer version (p_to_version: the active version or a
290
+ * later inactive one), each at the step p_map gives for its current step. Returns one row per instance with
291
+ * the problem that stops it (null: it can move). With p_apply, nothing changes unless every instance can
292
+ * move; then all move in this transaction, with a "migrated" event each and a row in meta.workflow_migration.
293
+ */
294
+ create function meta.migrate_workflows(p_definition_id int, p_to_version text, p_ids bigint[], p_map jsonb, p_apply boolean, p_by text)
295
+ returns table (workflow_id bigint, from_version text, from_step text, to_step text, problem text)
296
+ language plpgsql set search_path = meta, pg_catalog as $$
297
+ declare
298
+ d meta.workflow_definition;
299
+ v_order text[];
300
+ v_target int;
301
+ v_steps jsonb;
302
+ v_to jsonb;
303
+ v_from jsonb;
304
+ w record;
305
+ v_rows jsonb := '[]';
306
+ v_bad text[] := '{}';
307
+ v_id bigint;
308
+ r jsonb;
309
+ begin
310
+ select * into d from workflow_definition where id = p_definition_id;
311
+ if not found then
312
+ raise exception 'Workflow definition % does not exist.', p_definition_id;
313
+ end if;
314
+ if coalesce(cardinality(p_ids), 0) = 0 then
315
+ raise exception 'Choose the instances to migrate.';
316
+ end if;
317
+ if jsonb_typeof(coalesce(p_map, '{}')) <> 'object' then
318
+ raise exception 'The step mapping is a JSON object.';
319
+ end if;
320
+ -- versions from the oldest to the active one
321
+ v_order := array(select x->>'version' from jsonb_array_elements(d.inactive_versions) with ordinality o(x, i) order by i) || d.version;
322
+ v_target := array_position(v_order, p_to_version);
323
+ if v_target is null then
324
+ raise exception 'Workflow % has no version % to migrate to (the active version or an inactive one).', d.name, p_to_version;
325
+ end if;
326
+ v_steps := case when p_to_version = d.version then d.steps
327
+ else (select x->'steps' from jsonb_array_elements(d.inactive_versions) x where x->>'version' = p_to_version limit 1) end;
328
+
329
+ foreach v_id in array (select array_agg(distinct x order by x) from unnest(p_ids) x) loop
330
+ select wf.id, wf.app_id, wf.definition_id, coalesce(wf.version, '1') as version, wf.state, wf.current_step, wf.steps, wf.waiting_task, wf.wait_until,
331
+ (select td.name from task tk join task_definition td on td.id = tk.definition_id where tk.id = wf.waiting_task) as task_name,
332
+ exists (select 1 from workflow_branch b where b.workflow_id = wf.id and b.state in ('active', 'waiting', 'faulted')) as branches
333
+ into w from workflow wf where wf.id = v_id for update;
334
+ workflow_id := v_id;
335
+ from_version := w.version;
336
+ from_step := w.current_step;
337
+ to_step := case when w.current_step is not null then p_map->>w.current_step end;
338
+ problem := null;
339
+ v_to := (select s from jsonb_array_elements(v_steps) s where s->>'name' = to_step limit 1);
340
+ v_from := (select s from jsonb_array_elements(w.steps) s where s->>'name' = w.current_step limit 1);
341
+ if w.id is null or w.definition_id is distinct from d.id or w.app_id <> d.app_id then
342
+ problem := 'not an instance of this workflow';
343
+ from_version := null;
344
+ from_step := null;
345
+ to_step := null;
346
+ elsif w.state not in ('active', 'waiting', 'faulted') then
347
+ problem := format('is %s', w.state);
348
+ elsif coalesce(array_position(v_order, w.version), 0) >= v_target then
349
+ problem := format('runs version %s, which is not older than %s', w.version, p_to_version);
350
+ elsif w.branches then
351
+ problem := 'runs parallel branches: let them reach their join first';
352
+ elsif to_step is null or to_step = '' then
353
+ problem := format('no step of version %s is mapped to %s', p_to_version, coalesce(w.current_step, '(none)'));
354
+ elsif v_to is null then
355
+ problem := format('version %s has no step %s', p_to_version, to_step);
356
+ elsif v_to->>'type' = 'draft' then
357
+ problem := format('%s is a draft step', to_step);
358
+ elsif w.waiting_task is not null and v_to->>'type' <> 'task' then
359
+ problem := format('waits for a task: %s must be a task step', to_step);
360
+ elsif w.waiting_task is not null and upper(v_to->>'task') is distinct from w.task_name then
361
+ problem := format('waits for a task of %s, but %s creates %s', w.task_name, to_step, coalesce(v_to->>'task', '(none)'));
362
+ elsif w.state = 'waiting' and w.waiting_task is null and v_from->>'type' = 'invoke_api' then
363
+ problem := 'is calling a web service: try again when the call is done';
364
+ elsif w.state = 'waiting' and w.waiting_task is null and v_from->>'type' = 'wait' and v_to->>'type' <> 'wait' then
365
+ problem := format('waits at a wait step: %s must be a wait step', to_step);
366
+ elsif w.state = 'waiting' and w.waiting_task is null and v_from->>'type' = 'join' and v_to->>'type' <> 'join' then
367
+ problem := format('waits at a join: %s must be a join step', to_step);
368
+ end if;
369
+ if problem is not null then
370
+ v_bad := v_bad || format('%s %s', v_id, problem);
371
+ end if;
372
+ v_rows := v_rows || jsonb_build_object('id', v_id, 'from_version', from_version, 'from_step', from_step, 'to_step', to_step);
373
+ return next;
374
+ end loop;
375
+
376
+ if not p_apply then
377
+ return;
378
+ end if;
379
+ if cardinality(v_bad) > 0 then
380
+ raise exception 'Nothing was migrated: instance %.', array_to_string(v_bad, '; instance ');
381
+ end if;
382
+ for r in select * from jsonb_array_elements(v_rows) loop
383
+ update workflow set steps = v_steps, version = p_to_version, current_step = r->>'to_step', updated_at = now()
384
+ where id = (r->>'id')::bigint;
385
+ perform meta.workflow_log((r->>'id')::bigint, r->>'to_step', 'migrated',
386
+ format('version %s → %s: %s → %s (by %s)', r->>'from_version', p_to_version, r->>'from_step', r->>'to_step', p_by));
387
+ end loop;
388
+ insert into workflow_migration (app_id, definition_name, to_version, mapping, workflow_ids, detail, migrated_by)
389
+ values (d.app_id, d.name, p_to_version, coalesce(p_map, '{}'), array(select (x->>'id')::bigint from jsonb_array_elements(v_rows) x), v_rows, p_by);
390
+ -- active ones go on with their new step
391
+ perform pg_notify('pgkiln_workflow', 'migrated');
392
+ end
393
+ $$;
394
+ revoke all on function meta.migrate_workflows(int, text, bigint[], jsonb, boolean, text) from public;
395
+
396
+ -- Same as 072, plus the definition's custom outcomes (the task list's buttons).
397
+ create or replace view meta.tasks with (security_barrier) as
398
+ select t.id, t.app_id, d.name as definition, d.type, t.subject, t.detail_pk, t.params, t.state, t.outcome,
399
+ t.priority, t.initiator, t.actual_owner, t.owner_users, t.owner_roles, t.created_at, t.due_at,
400
+ t.completed_at, t.completed_by, d.details_page, d.details_item,
401
+ t.due_at is not null and t.due_at < now() and t.state in ('unassigned', 'assigned') as overdue,
402
+ (r).is_initiator, (r).is_owner, (r).is_admin, (r).may_act, (r).may_claim, (r).may_release, (r).may_delegate, (r).may_cancel,
403
+ t.tenant_id, d.outcomes
404
+ from (select t, meta.task_rights(t) as r from meta.task t where t.app_id = meta.app_id()) x
405
+ cross join lateral (select (x.t).*) t
406
+ join meta.task_definition d on d.id = t.definition_id
407
+ where (x.r).may_see;
@@ -0,0 +1,51 @@
1
+ -- =====================================================================
2
+ -- 124: component details of APEX 26.1 (sprint 115, WS-11)
3
+ --
4
+ -- Dynamic actions:
5
+ -- * event "menu_entry" (APEX: Menu entry chosen): a menu button's entry
6
+ -- {"label": "…", "action": "NAME"} runs the dynamic actions of event
7
+ -- menu_entry whose trigger names the entry (NAME) or the menu button
8
+ -- (any of its entries; the entry's name is the value a client-side
9
+ -- condition tests).
10
+ -- * actions "wait_popup" and "remove_wait_popup" (APEX: Show / Remove
11
+ -- Wait Popup): a modal "please wait" overlay (Message is its text)
12
+ -- until remove_wait_popup or the next page; e.g. before_submit →
13
+ -- wait_popup around a long process, or wait_popup, execute_sql,
14
+ -- remove_wait_popup around an Ajax callback.
15
+ -- Menu entries: exactly one of "page", "request" or "action" (new).
16
+ -- No new tables or columns: the button's menu jsonb and the dynamic
17
+ -- action's event/action travel with export_app / import_app unchanged.
18
+ -- =====================================================================
19
+
20
+ alter table meta.dynamic_action drop constraint dynamic_action_event_check;
21
+ alter table meta.dynamic_action add constraint dynamic_action_event_check check (event in (
22
+ 'change', 'click', 'load', 'dialog_closed', 'after_refresh', 'selection_change', 'focus', 'blur', 'key_release', 'before_submit', 'custom',
23
+ 'menu_entry'));
24
+
25
+ alter table meta.dynamic_action drop constraint dynamic_action_action_check;
26
+ alter table meta.dynamic_action add constraint dynamic_action_action_check check (action in (
27
+ 'show', 'hide', 'enable', 'disable', 'set_value', 'execute_sql', 'refresh_region', 'refresh_item', 'alert', 'submit',
28
+ 'set_focus', 'add_class', 'remove_class', 'show_success', 'show_error', 'clear_errors', 'ai_generate', 'execute_javascript', 'plugin',
29
+ 'push_subscribe', 'confirm', 'close_dialog', 'cancel_dialog', 'clear', 'open_region', 'close_region', 'download', 'print',
30
+ 'current_position', 'share', 'wait_popup', 'remove_wait_popup'));
31
+
32
+ -- A menu entry: {"label": "…", "page": 3, "items": {"P3_ID": "&P2_ID."}} (a link),
33
+ -- {"label": "…", "request": "ARCHIVE"} (submits the page with that request) or
34
+ -- {"label": "…", "action": "EXPORT"} (runs the dynamic actions of event menu_entry);
35
+ -- optional "confirm", "authz" and "icon". At most 20 entries.
36
+ create or replace function meta.button_menu_ok(p_menu jsonb) returns boolean
37
+ language sql immutable set search_path = meta, pg_catalog as $$
38
+ select jsonb_typeof(p_menu) = 'array' and jsonb_array_length(p_menu) <= 20
39
+ and not exists (
40
+ select 1 from jsonb_array_elements(p_menu) e
41
+ where jsonb_typeof(e) <> 'object'
42
+ or jsonb_typeof(e->'label') is distinct from 'string' or length(e->>'label') not between 1 and 100
43
+ or (e ? 'page')::int + (e ? 'request')::int + (e ? 'action')::int <> 1
44
+ or (e ? 'page' and (jsonb_typeof(e->'page') <> 'number' or (e->>'page')::numeric <= 0 or (e->>'page')::numeric <> floor((e->>'page')::numeric)))
45
+ or (e ? 'request' and coalesce(e->>'request', '') !~ '^[A-Z][A-Z0-9_]*$')
46
+ or (e ? 'action' and (jsonb_typeof(e->'action') <> 'string' or e->>'action' !~ '^[A-Z][A-Z0-9_]{0,59}$'))
47
+ or (e ? 'items' and jsonb_typeof(e->'items') <> 'object')
48
+ or (e ? 'confirm' and jsonb_typeof(e->'confirm') <> 'string')
49
+ or (e ? 'icon' and coalesce(e->>'icon', '') !~ '^[a-z][a-z0-9-]{0,40}$')
50
+ or (e ? 'authz' and coalesce(e->>'authz', '') !~ '^!?[A-Z][A-Z0-9_]*$'))
51
+ $$;
@@ -7,23 +7,23 @@ open item and open an issue or pull request; see [CONTRIBUTING.md](../CONTRIBUTI
7
7
  Legend: ✅ available · 🟡 partial (see notes) · ❌ not yet · ➖ not planned (a deliberate choice,
8
8
  or better served by the PostgreSQL ecosystem; see the notes and [extensions](guide/15-extensions.md))
9
9
 
10
- Last reviewed: 2026-10-19 (sprint 110: Data Reporter as a workspace tool with datasets, reporting applications, editors and viewers, published reports with stable URLs and pins; sprint 109: the Workspace home above the App Builder and SQL Workshop, with a summary, top applications, users and developers, and an optional release check; sprint 108: the code editor's syntax colours in the builder's light and dark themes, read-only SQL highlighted, the Data Modeler in dark mode; sprint 107: a transaction mode in the SQL Worksheet with Commit and Rollback; DA-18: JavaScript (MLE) not planned, plv8 where installed; sprint 106: the change log of DDL change sets with Flyway, Liquibase and SQL downloads; sprint 104: RESTful Services modules, templates and handlers with PL/pgSQL and media handlers, privileges, OpenAPI and Try it; sprint 103: Schema Export / Import with pg_dump and pg_restore; sprint 102: the SQL Monitor with live sessions, Top SQL and a statement's plan; sprint 101: Scheduling of pg_cron jobs with a schedule builder, run now and run history; sprint 100: RESTful Services, tables and views published as REST endpoints with paging, filters, keys and OpenAPI; sprint 99: Administration → Database Users for PostgreSQL roles; sprint 98: the Data Modeler with relational diagrams, DDL and a data dictionary; sprint 97: charts of a query in the worksheet, saved charts and their dashboard; sprint 96: the SQL Workshop's Launchpad with pins and tool groups, the builder's search with Ctrl+K; sprint 95: Table Properties: create, edit or copy a table in a form with a DDL preview and Apply in one transaction; sprint 94: an object's menu with Open in a panel, Quick DDL, Add index / trigger / policy and Drop with dependents; sprint 93: worksheets saved as SQL Scripts with tabs and a Files list, Format, Convert case, find and replace, bind and substitution variables; sprint 92: the worksheet's Query Result with sort, value dialog, copy and downloads, plans as a tree with the slowest step and misestimates, SQL History with status, search and run again; sprint 91: the SQL Worksheet like Database Actions' SQL: navigator, Run Statement at the cursor and Run Script with a log per statement, Explain Plan, Autotrace, notices, history, Run as an application role; sprint 90: Comment Out in the page designer, gallery labels in title case; sprint 89: the Object Browser's schema, one row of tabs, blank NULLs, Modify column; sprint 88: home cards with colour bands and round icons, the hero on the page background, a page footer with the version and Back to top; sprint 87: the Create Application wizard reloads the page list for a schema, RLS badges, the alias from the name; sprint 86: the page designer by page number, HTML error pages in the builder; sprint 85: the Form Designer, beyond APEX; sprint 84: the date picker, facet menus; sprint 83: Component Settings and Component Groups; sprint 82: Report Queries; sprint 81: Application Computations and Shortcuts in Shared Components; sprint 80: the master grid's first row selected when the page opens and after a search; grid cells as text with their format mask until edited, with cell navigation by arrow keys, Enter, F2 and Escape; sprint 79: generated dashboards with sums, donuts and lines, leaving out charts that say nothing; applications open in their own light or dark mode until the user chooses, per application; a user menu with Appearance and Sign in when signed out; the developer toolbar's App and Page texts and debug state; the report search's column scope in the magnifier; sprint 78: generated faceted search reports with one column per foreign key, the faceted search's total row count, Show all and Yes/No values, one search field, friendly headings and number formats in generated grids and master-detail pages, friendly names in the Create Application wizard's page list; sprint 77: the Page Designer's layout and tree by display position, button positions, the tree's context menu with Duplicate and Copy to other page, Show Common / Show All, Go to group and the Template Options dialog; display positions, button positions and side-column page templates at runtime; sprint 76: the interactive report's menus and pagination, date items in the date format; sprint 75: the SQL Workshop's Object Browser and tabs; sprint 74: the builder's information architecture: Shared Components grid, Settings tabs, application home search and tasks; sprint 73: the Create Application wizard on one page with page types and features; sprint 72: the runtime chrome compared with APEX 26.2's: the developer toolbar, the Iris look, the navigation bar's Help menu and Install button; sprint 71: generated applications compared with APEX 26.2's: labels, foreign key names in reports, file columns, format masks, the Home page and the dashboard; sprint 70: treemap, sunburst and timeline charts; sprint 69: dragging tasks on a Gantt chart; sprint 68: the Data Reporter's welcome page, Find my workspace; sprint 67: REST source catalogs from OpenAPI documents, a workspace's export and import (two rows added); sprint 66: a blueprint from an existing application; sprint 65: facet charts; sprint 64: approval vacation rules and expiration policies; sprint 63: range, box plot and stock charts; sprint 62: workspace preferences (SQL Workshop and RESTful services per workspace), extension links, Manage backups across applications, SQL Workshop utilities with object reports, About Database and a database monitor; sprint 61: the builder's screens compared with APEX 26.1: the workspace sign-in page, the Administration menu with workspace message, environment banner and utilization, the SQL Workshop home, the App Gallery, application groups and cross application reports (four rows added); sprint 60: isolated workspaces with their own database logins, row-level security on pgkiln's tables and workspace database roles, workspace administrators; sprint 59: the Page Designer's help pane, Quick edit from the developer toolbar; sprint 58: page groups; sprint 57: region templates hero, title bar, buttons container, inline dialog and drawer, wizard with wizard progress, the carousel; sprint 56: Generate DDL and Schema Comparison in the SQL Workshop, `pgkiln ddl` and `pgkiln compare`; sprint 55: an editable login page with a Login region and the Minimal page template, Forgot password through the application's mail service; sprint 54: interactive grid views, the single row view, group by and chart; sprint 53: application settings, user preferences and items kept per user, dynamic translations, `meta.data_export` and data export downloads, display image and geocoded address items; sprint 52: every dynamic action event and action, Ajax callbacks, an error handling function, the session timeout warning; sprint 51: checked imports, application backups, availability and Run only, feedback, type-specific attribute help; sprint 50: lost update detection, warn on unsaved changes, the URL and search regions with search configurations, the developer toolbar with a session state viewer, and e-mail through SMTP with OAuth2, Microsoft Graph, Amazon SES and HTTP APIs; import checks, application availability, property help and e-mail compared again; 2026-10-06: pgkiln 0.31.0, in progress: static application files and the Execute JavaScript dynamic action; region, item, dynamic action and process plug-ins; conditional and dynamic theme styles; XML and Excel in `meta.parse_data`, zips in SQL, `meta.v_boolean`; file items in S3-compatible object storage; map layers by the visible area and as vector tiles; a graphical query builder; tenants for workflows and tasks; application files as YAML and region static IDs; Lucide icons and icon modifiers; PWA push notifications; 0.30.0: 136 icons and an icon picker; an axe-core accessibility audit; cropping pictures; session sharing between applications; twenty-two built-in languages; 0.29.0: workspaces; the Iris base style; drawers; built-in template components including the metric card; Theme Roller dark-mode colours, live preview and template options on items and report columns; report selection across pages; instance settings; fourteen built-in languages; 0.28.0: AI with Claude and OpenAI; 0.27.0: Data Reporter; sample data generator; create application from several sheets, pasted data or existing tables; 0.26.0: working copies with a three-way merge; theme, library and boilerplate applications with subscriptions; 0.25.0: Gantt, pyramid and polar charts; map layers, clustering and server-side spatial filters; REST data sources that write back from forms and grids and synchronise into tables, OAuth2 password and refresh-token grants; debug messages per request and an install/upgrade log; `meta.web_request()` and `meta.parse_data()`; Theme Roller style variants and template options; 0.24.0: automations with several actions, error handling per row and runs from SQL; invoke-API workflow steps; Unload Data in the Data Workshop; create page wizards for cards, calendar, chart, map, faceted search, form and master-detail; create an application from a file; 0.23.0: interactive grid aggregates, frozen/moved/resized columns, layouts and saved grid reports, master-detail, row actions and copy/paste; download processes, execution chains in the background, workflow processes, branches to a function's URL or another app, the Dialog Closed event; number format masks, automatic time zone, runtime messages in German, French and Spanish; SQL scripts, Quick SQL, a query builder, XML loading and data load definitions; custom authentication, lists, page and application locks with developer comments, supporting objects; 0.22.0: keyset paging, report PDFs and REST collections streamed from a cursor, database-account authentication; 0.21.0: HTTP-header authentication behind a reverse proxy; 0.20.0: a popup list of values with server-side search; 0.19.0: row-range pagination, maximum row counts and row limits, lazy loading and caching of regions, streamed CSV/Excel downloads; 0.18.0: calendar week/day/list views with drag and drop, bubble/gauge/funnel/radar charts and drill-down, smart filters, a region display selector and more facet types, computations, conditional branches, build options, menu buttons, REST data sources and web credentials, rich text/Markdown, rating, combobox, date range and QR code items; 0.17.0: an App Builder home, workspace dashboard and utilities like APEX's; 0.16.0: a Page Designer with drag-and-drop layout, a code editor with autocomplete, template components and plug-ins, parallel branches and versions of workflows, the `pgkiln` command line with one file per component; 0.15.0: drop and paste files, heat maps, filtering a report by the map area; 0.14.0: stacked, combo, scatter and pie charts, several files per upload item; 0.13.0: map and tree regions; 0.12.0: REST modules in the builder; 0.11.0: workflows and Progressive Web Apps; 0.10.0: LDAP, SAML, "Keep me signed in", document templates, JSON loading, approvals and the task list; pgkiln installs no application, HR is an example).
10
+ Last reviewed: 2026-10-19 (sprint 115: popup LOVs that load on scroll, Wait Popup dynamic actions and dynamic actions on menu entries; sprint 114: Workflow 26.2 with custom task outcomes, task events and notifications, Send E-Mail and draft steps, migrating running instances and the diagram as SVG / PNG; sprint 113: Team Development with issues, labels, milestones, templates, a board and a set-up wizard; sprint 112: AI interactive reports with chips, Undo, computed columns and a phrase parser that needs no AI service; sprint 111: AI Agents with tools as a shared component, a Navigate tool with checksummed URLs, Gemini and OpenAI-compatible services; sprint 110: Data Reporter as a workspace tool with datasets, reporting applications, editors and viewers, published reports with stable URLs and pins; sprint 109: the Workspace home above the App Builder and SQL Workshop, with a summary, top applications, users and developers, and an optional release check; sprint 108: the code editor's syntax colours in the builder's light and dark themes, read-only SQL highlighted, the Data Modeler in dark mode; sprint 107: a transaction mode in the SQL Worksheet with Commit and Rollback; DA-18: JavaScript (MLE) not planned, plv8 where installed; sprint 106: the change log of DDL change sets with Flyway, Liquibase and SQL downloads; sprint 104: RESTful Services modules, templates and handlers with PL/pgSQL and media handlers, privileges, OpenAPI and Try it; sprint 103: Schema Export / Import with pg_dump and pg_restore; sprint 102: the SQL Monitor with live sessions, Top SQL and a statement's plan; sprint 101: Scheduling of pg_cron jobs with a schedule builder, run now and run history; sprint 100: RESTful Services, tables and views published as REST endpoints with paging, filters, keys and OpenAPI; sprint 99: Administration → Database Users for PostgreSQL roles; sprint 98: the Data Modeler with relational diagrams, DDL and a data dictionary; sprint 97: charts of a query in the worksheet, saved charts and their dashboard; sprint 96: the SQL Workshop's Launchpad with pins and tool groups, the builder's search with Ctrl+K; sprint 95: Table Properties: create, edit or copy a table in a form with a DDL preview and Apply in one transaction; sprint 94: an object's menu with Open in a panel, Quick DDL, Add index / trigger / policy and Drop with dependents; sprint 93: worksheets saved as SQL Scripts with tabs and a Files list, Format, Convert case, find and replace, bind and substitution variables; sprint 92: the worksheet's Query Result with sort, value dialog, copy and downloads, plans as a tree with the slowest step and misestimates, SQL History with status, search and run again; sprint 91: the SQL Worksheet like Database Actions' SQL: navigator, Run Statement at the cursor and Run Script with a log per statement, Explain Plan, Autotrace, notices, history, Run as an application role; sprint 90: Comment Out in the page designer, gallery labels in title case; sprint 89: the Object Browser's schema, one row of tabs, blank NULLs, Modify column; sprint 88: home cards with colour bands and round icons, the hero on the page background, a page footer with the version and Back to top; sprint 87: the Create Application wizard reloads the page list for a schema, RLS badges, the alias from the name; sprint 86: the page designer by page number, HTML error pages in the builder; sprint 85: the Form Designer, beyond APEX; sprint 84: the date picker, facet menus; sprint 83: Component Settings and Component Groups; sprint 82: Report Queries; sprint 81: Application Computations and Shortcuts in Shared Components; sprint 80: the master grid's first row selected when the page opens and after a search; grid cells as text with their format mask until edited, with cell navigation by arrow keys, Enter, F2 and Escape; sprint 79: generated dashboards with sums, donuts and lines, leaving out charts that say nothing; applications open in their own light or dark mode until the user chooses, per application; a user menu with Appearance and Sign in when signed out; the developer toolbar's App and Page texts and debug state; the report search's column scope in the magnifier; sprint 78: generated faceted search reports with one column per foreign key, the faceted search's total row count, Show all and Yes/No values, one search field, friendly headings and number formats in generated grids and master-detail pages, friendly names in the Create Application wizard's page list; sprint 77: the Page Designer's layout and tree by display position, button positions, the tree's context menu with Duplicate and Copy to other page, Show Common / Show All, Go to group and the Template Options dialog; display positions, button positions and side-column page templates at runtime; sprint 76: the interactive report's menus and pagination, date items in the date format; sprint 75: the SQL Workshop's Object Browser and tabs; sprint 74: the builder's information architecture: Shared Components grid, Settings tabs, application home search and tasks; sprint 73: the Create Application wizard on one page with page types and features; sprint 72: the runtime chrome compared with APEX 26.2's: the developer toolbar, the Iris look, the navigation bar's Help menu and Install button; sprint 71: generated applications compared with APEX 26.2's: labels, foreign key names in reports, file columns, format masks, the Home page and the dashboard; sprint 70: treemap, sunburst and timeline charts; sprint 69: dragging tasks on a Gantt chart; sprint 68: the Data Reporter's welcome page, Find my workspace; sprint 67: REST source catalogs from OpenAPI documents, a workspace's export and import (two rows added); sprint 66: a blueprint from an existing application; sprint 65: facet charts; sprint 64: approval vacation rules and expiration policies; sprint 63: range, box plot and stock charts; sprint 62: workspace preferences (SQL Workshop and RESTful services per workspace), extension links, Manage backups across applications, SQL Workshop utilities with object reports, About Database and a database monitor; sprint 61: the builder's screens compared with APEX 26.1: the workspace sign-in page, the Administration menu with workspace message, environment banner and utilization, the SQL Workshop home, the App Gallery, application groups and cross application reports (four rows added); sprint 60: isolated workspaces with their own database logins, row-level security on pgkiln's tables and workspace database roles, workspace administrators; sprint 59: the Page Designer's help pane, Quick edit from the developer toolbar; sprint 58: page groups; sprint 57: region templates hero, title bar, buttons container, inline dialog and drawer, wizard with wizard progress, the carousel; sprint 56: Generate DDL and Schema Comparison in the SQL Workshop, `pgkiln ddl` and `pgkiln compare`; sprint 55: an editable login page with a Login region and the Minimal page template, Forgot password through the application's mail service; sprint 54: interactive grid views, the single row view, group by and chart; sprint 53: application settings, user preferences and items kept per user, dynamic translations, `meta.data_export` and data export downloads, display image and geocoded address items; sprint 52: every dynamic action event and action, Ajax callbacks, an error handling function, the session timeout warning; sprint 51: checked imports, application backups, availability and Run only, feedback, type-specific attribute help; sprint 50: lost update detection, warn on unsaved changes, the URL and search regions with search configurations, the developer toolbar with a session state viewer, and e-mail through SMTP with OAuth2, Microsoft Graph, Amazon SES and HTTP APIs; import checks, application availability, property help and e-mail compared again; 2026-10-06: pgkiln 0.31.0, in progress: static application files and the Execute JavaScript dynamic action; region, item, dynamic action and process plug-ins; conditional and dynamic theme styles; XML and Excel in `meta.parse_data`, zips in SQL, `meta.v_boolean`; file items in S3-compatible object storage; map layers by the visible area and as vector tiles; a graphical query builder; tenants for workflows and tasks; application files as YAML and region static IDs; Lucide icons and icon modifiers; PWA push notifications; 0.30.0: 136 icons and an icon picker; an axe-core accessibility audit; cropping pictures; session sharing between applications; twenty-two built-in languages; 0.29.0: workspaces; the Iris base style; drawers; built-in template components including the metric card; Theme Roller dark-mode colours, live preview and template options on items and report columns; report selection across pages; instance settings; fourteen built-in languages; 0.28.0: AI with Claude and OpenAI; 0.27.0: Data Reporter; sample data generator; create application from several sheets, pasted data or existing tables; 0.26.0: working copies with a three-way merge; theme, library and boilerplate applications with subscriptions; 0.25.0: Gantt, pyramid and polar charts; map layers, clustering and server-side spatial filters; REST data sources that write back from forms and grids and synchronise into tables, OAuth2 password and refresh-token grants; debug messages per request and an install/upgrade log; `meta.web_request()` and `meta.parse_data()`; Theme Roller style variants and template options; 0.24.0: automations with several actions, error handling per row and runs from SQL; invoke-API workflow steps; Unload Data in the Data Workshop; create page wizards for cards, calendar, chart, map, faceted search, form and master-detail; create an application from a file; 0.23.0: interactive grid aggregates, frozen/moved/resized columns, layouts and saved grid reports, master-detail, row actions and copy/paste; download processes, execution chains in the background, workflow processes, branches to a function's URL or another app, the Dialog Closed event; number format masks, automatic time zone, runtime messages in German, French and Spanish; SQL scripts, Quick SQL, a query builder, XML loading and data load definitions; custom authentication, lists, page and application locks with developer comments, supporting objects; 0.22.0: keyset paging, report PDFs and REST collections streamed from a cursor, database-account authentication; 0.21.0: HTTP-header authentication behind a reverse proxy; 0.20.0: a popup list of values with server-side search; 0.19.0: row-range pagination, maximum row counts and row limits, lazy loading and caching of regions, streamed CSV/Excel downloads; 0.18.0: calendar week/day/list views with drag and drop, bubble/gauge/funnel/radar charts and drill-down, smart filters, a region display selector and more facet types, computations, conditional branches, build options, menu buttons, REST data sources and web credentials, rich text/Markdown, rating, combobox, date range and QR code items; 0.17.0: an App Builder home, workspace dashboard and utilities like APEX's; 0.16.0: a Page Designer with drag-and-drop layout, a code editor with autocomplete, template components and plug-ins, parallel branches and versions of workflows, the `pgkiln` command line with one file per component; 0.15.0: drop and paste files, heat maps, filtering a report by the map area; 0.14.0: stacked, combo, scatter and pie charts, several files per upload item; 0.13.0: map and tree regions; 0.12.0: REST modules in the builder; 0.11.0: workflows and Progressive Web Apps; 0.10.0: LDAP, SAML, "Keep me signed in", document templates, JSON loading, approvals and the task list; pgkiln installs no application, HR is an example).
11
11
 
12
12
  ## At a glance
13
13
 
14
14
  | Area | ✅ | 🟡 | ❌ | ➖ | In short |
15
15
  |---|---:|---:|---:|---:|---|
16
- | App Builder and development | 20 | 0 | 0 | 0 | Page Designer with drag-and-drop and a code editor, wizards, search, where used, an Advisor, a CLI with one file per component, page locks and comments, supporting objects, working copies with a three-way merge, theme/library/boilerplate apps with subscriptions; App Builder AI (pages, SQL and table descriptions) |
16
+ | App Builder and development | 21 | 0 | 0 | 0 | Page Designer with drag-and-drop and a code editor, wizards, search, where used, an Advisor, a CLI with one file per component, page locks and comments, supporting objects, working copies with a three-way merge, theme/library/boilerplate apps with subscriptions; App Builder AI (pages, SQL and table descriptions) |
17
17
  | Regions | 23 | 0 | 0 | 0 | All everyday regions, a search region over search configurations and a URL region; twenty-two chart types with drill-down; calendars with week/day/list views and drag and drop; faceted search, smart filters and a region display selector; interactive reports with breaks, aggregates, highlights, compute, group by, pivot, chart view and saved reports; interactive grids with single row, group by and chart views; maps (with vector tiles) and trees; template components; row ranges, lazy loading and region caching for large tables |
18
18
  | Items | 13 | 0 | 0 | 0 | All common items, file upload (several files per item), rich text and Markdown editors, star rating, combobox, date range, QR code, password reveal, display image and geocoded address |
19
- | Logic and processing | 17 | 0 | 0 | 1 | User preferences and application settings; all dynamic action events and actions, Ajax callbacks and an error handling function; Core APEX model complete with computations, conditional branches, build options and menu buttons, download, chain and workflow processes; JavaScript from static application files in dynamic actions; region, item, dynamic action and process plug-ins |
19
+ | Logic and processing | 18 | 0 | 0 | 1 | User preferences and application settings; all dynamic action events and actions, Ajax callbacks and an error handling function; Core APEX model complete with computations, conditional branches, build options and menu buttons, download, chain and workflow processes; JavaScript from static application files in dynamic actions; region, item, dynamic action and process plug-ins |
20
20
  | Security | 22 | 0 | 0 | 0 | An editable login page and Forgot password; a session timeout warning; On par or stricter (CSP without `unsafe-inline`); OIDC, SAML and LDAP; header authentication behind a proxy; database accounts; custom authentication |
21
21
  | User interface | 12 | 0 | 0 | 0 | Region templates incl. hero, inline dialog and drawer, wizard and carousel; Universal Theme-like and responsive; a Theme Roller with style variants and conditional, dynamic styles; about 1,700 icons with Font APEX-like modifiers |
22
22
  | Globalization | 6 | 0 | 0 | 0 | One translated app like 26.1 with dynamic translations of table values; number format masks and automatic time zone; twenty-two built-in languages |
23
23
  | Data and integration | 13 | 0 | 0 | 2 | REST source catalogs from OpenAPI documents; Generate DDL and schema comparison; `meta.data_export` and data export downloads (CSV, JSON, XML, HTML, Excel, PDF); e-mail through SMTP (password or OAuth2), Microsoft Graph, Amazon SES and HTTP APIs; REST APIs via PostgREST, REST data sources that write back and synchronise, web credentials with OAuth2 grants, CSV/XLSX/JSON/XML loading with saved definitions and unloading, SQL scripts and Quick SQL, report PDFs and document templates |
24
24
  | Workflow, automation and AI | 6 | 0 | 0 | 0 | Scheduled automations with several actions and runs from SQL, approvals, a task list and workflows with parallel branches, versions and invoke-API steps; AI with Claude or OpenAI: *Generate text with AI*, an assistant region with tools, natural-language report filters and blueprints |
25
25
  | Administration | 12 | 0 | 0 | 0 | An APEX-like sign-in with a workspace and a Workspace home; a workspace's components exported and imported as one file; workspace message, environment banner and utilization; isolated workspaces with their own database logins and roles, and workspace administrators; application availability and Run only, automatic backups, user feedback; a developer toolbar with a session state viewer and debug per session; Top SQL per app; debug messages per request; an install/upgrade log |
26
- | **Total** | **144** | **0** | **0** | **3** | 147 APEX features compared: 98% available, none partial or missing |
26
+ | **Total** | **146** | **0** | **0** | **3** | 149 APEX features compared: 98% available, none partial or missing |
27
27
 
28
28
  (Counts are of the rows in the tables below. A review on 2026-10-06 added 18 rows for APEX features that were
29
29
  not compared before, one on 2026-10-12 added two more (property help, application availability), and one on
@@ -43,6 +43,7 @@ two, REST source catalogs and the workspace export, with their features.)
43
43
  | Export / import | ✅ | `meta.export_app()` / `meta.import_app()`: portable JSON, also in the builder (new applications; `pgkiln import --replace` and the MCP `import_app` update one in place, live for every user at the commit). (sprint 51) **Checked imports**: `pgkiln import --check` (`--dry-run`, `--force`), the MCP import (by default) and the builder's import run the Advisor inside the import's transaction (SQL planned as the application's role: missing tables, columns, functions, types, grants; references) and refuse on errors, so nothing changes; APEX doesn't check. The version an import replaces is [backed up](guide/03-builder.md#backups) first ([chapter 18](guide/18-cli.md#checking-before-an-import)) |
44
44
  | APEXlang: human-readable, diffable app files; static IDs (26.1) | ✅ | `pgkiln export --format dir`: one JSON file per component with sorted keys, SQL and HTML in sibling files, references by static id instead of database ids; `pgkiln import --replace` updates an app in place, `pgkiln diff` compares ([chapter 18](guide/18-cli.md)). (0.31) **`--format text`**: the same files as YAML (a strict subset any YAML tool reads) with the SQL and templates inline, read back losslessly ([text files](guide/18-cli.md#text-files-apexlang)); regions have a stored **Static ID** (exported as their key, rendered as `data-static-id`), other components are named by their stored names. The syntax is YAML rather than APEXlang's own, so APEXlang files don't import |
45
45
  | Working copies, merge, team development | ✅ | **Working copies** of an application ([chapter 3](guide/03-builder.md#working-copies)): a second app on the same schema and data, compared three ways per component (settings, each shared component, page, region, item, …) with the main app as it was when copied; changes on one side merge, conflicts are chosen per component with a line diff; merge into the main app or refresh the copy, keeping users, sessions, saved reports, secrets and automation switches; locks respected. Conflicts are resolved per component, not per property |
46
+ | Team Development: issues, labels, milestones, templates, feedback | ✅ | (sprint 113) Builder → **Team Development** ([chapter 3](guide/03-builder.md#team-development)): issues per workspace with labels in groups, milestones, assignees, application and page links, Markdown comments with in-app @mentions and a history; a filtered list with saved views and a board by label group or milestone (no JavaScript needed); a set-up wizard installs sample labels, templates, canned responses and milestones. Users' feedback and developer comments become issues; open issues show in the Page Designer, on App Home and on the Workspace home; issues travel in the workspace export. Not included: e-mail notifications, GitHub sync |
46
47
  | Application lock (26.1), page locks, comments | ✅ | Page and application locks, enforced on the server for every builder change; the owner or an administrator unlocks, and an administrator breaking a lock is logged. Developer comments per page and per app ([chapter 3](guide/03-builder.md)) |
47
48
  | Static application files | ✅ | Shared Components → **Static application files** ([chapter 3](guide/03-builder.md#static-application-files)): JavaScript, CSS, JSON, images and fonts uploaded or written in the builder, served at `/a/<alias>/static/<name>` with long caching per version, loaded by every page or by one page (APEX's *JavaScript/CSS File URLs*), exported with the application (as files in a directory export). No HTML; the CSP stays `script-src 'self'` |
48
49
  | Supporting objects (install scripts) | ✅ | Install, upgrade and deinstall scripts travel with the export (JSON and directory formats), never run on import; reviewed and run from the builder as the app's role, in one transaction, with a result per statement ([chapter 3](guide/03-builder.md#supporting-objects)) |
@@ -91,7 +92,7 @@ two, REST source catalogs and the workspace export, with their features.)
91
92
  | Text field, textarea, number, date picker, password, hidden, display only | ✅ | Native inputs, with the right phone keyboard (`inputmode`) |
92
93
  | Select list, radio group, checkbox, switch | ✅ | |
93
94
  | Checkbox group, shuttle / multi-select | ✅ | Colon-separated values, as in APEX |
94
- | Popup LOV | ✅ | `popup_lov`: a dialog with server-side search (paged, at most 100 rows a page) of the item's own shared, static or SQL list of values, extra display columns, return vs display value; the posted value is checked against the list; a select list without JavaScript ([chapter 5](guide/05-items.md)) |
95
+ | Popup LOV | ✅ | `popup_lov`: a dialog with server-side search (loaded page after page as the dialog scrolls, at most 100 rows a request; ‹ / More without IntersectionObserver) of the item's own shared, static or SQL list of values, extra display columns, return vs display value; the posted value is checked against the list; a select list without JavaScript ([chapter 5](guide/05-items.md)) |
95
96
  | Cascading, shared and static lists of values | ✅ | `cascade_parents`, `LOV:NAME`, `STATIC:` |
96
97
  | E-mail, phone, URL, colour picker | ✅ | Typed inputs |
97
98
  | Read-only condition, required, help text, default | ✅ | |
@@ -120,7 +121,8 @@ two, REST source catalogs and the workspace export, with their features.)
120
121
  | Error handling function | ✅ | (sprint 52) `schema.function(jsonb) returns jsonb` (Settings → Errors and sessions): every message users see (raised, constraint, unexpected with its reference) can be rewritten, as the app's role with a time limit ([chapter 6](guide/06-processing.md#error-handling)) |
121
122
  | User preferences, application settings (`APEX_UTIL.SET_PREFERENCE`, `APEX_APP_SETTING`) | ✅ | (sprint 53) `meta.get/set/remove_preference`, and items with `{"persist": "user"}` keep their value per user across sessions; application settings with valid values and *required*, `meta.get/set_app_setting`, exported with the application and kept on upgrade |
122
123
  | APEX PL/SQL APIs | ✅ | `meta.app_user()`, `meta.has_role()`, `meta.v()`, `meta.page_url()`, `meta.message()`, `meta.html_escape()`, password functions, `meta.debug()`; **`meta.web_request()` / `meta.web_request_source()` + `meta.web_response()`** (APEX_WEB_SERVICE: queued and made by the server, right after the page process that queued it or by the scheduler after commit, through the allow-list/SSRF checks and the app's web credentials; responses kept 24 h) and **`meta.parse_data()` / `meta.parse_data_columns()`** (APEX_DATA_PARSER for CSV/TSV and JSON in SQL, same names and types as the data loader) ([chapter 9](guide/09-reference.md)). (070) **XML and Excel** in `meta.parse_data` (as the data loader reads them; Excel files that pgkiln received: uploads and web responses), **APEX_ZIP** (`meta.zip_add`/`zip_finish`/`zip_agg` build zips in SQL, `meta.zip_entries`/`zip_entry` read them), **BOOLEAN** state (`meta.v_boolean`), and a mapping from APEX_JSON to PostgreSQL's JSON functions ([chapter 9](guide/09-reference.md#zip-files-in-sql)). A synchronous HTTP call inside one statement needs the `http` extension ([chapter 15](guide/15-extensions.md)); `meta.web_request` is queued by design |
123
- | Declarative menu buttons, button badges (26.1) | ✅ | Menu buttons with links and submit requests (a hidden button can't be reached through a menu), works without JavaScript; badges as text with `&ITEM.` or from SQL ([chapter 6](guide/06-processing.md#menu-buttons-and-badges)) |
124
+ | Declarative menu buttons, button badges (26.1) | ✅ | Menu buttons with links, submit requests and (sprint 115) dynamic actions (event *Menu entry chosen*, `menu_entry`) (a hidden button can't be reached through a menu), works without JavaScript except action entries; badges as text with `&ITEM.` or from SQL ([chapter 6](guide/06-processing.md#menu-buttons-and-badges)) |
125
+ | Wait Popup (26.1) | ✅ | (sprint 115) `wait_popup` / `remove_wait_popup` dynamic actions: a modal dialog with a status message (aria-busy, Escape-proof), shown until removed or the next page; `pgkiln.waitPopup()` in JavaScript ([chapter 7](guide/07-dynamic-actions.md#wait-popup)) |
124
126
  | Plug-ins | ✅ | **Region, item, dynamic action and process plug-ins** with their own code ([chapter 4](guide/04-pages-and-regions.md#plug-ins-with-their-own-code)): one `pgkiln-plugin/2` file with custom attributes, JavaScript and CSS (static application files, registered with `pgkiln.plugins.register`; the CSP stays strict), a template component for regions, a PL/pgSQL function for processes and install SQL that a developer reviews and runs; Shared Components → Plug-ins, `pgkiln plugin build|install`, four examples. Template component plug-ins (`pgkiln-plugin/1`) as before. Authentication and authorization plug-ins: custom authentication and SQL authorization schemes cover them |
125
127
  | Build options | ✅ | Include / exclude (and `!NAME` for the reverse) on pages, regions, items, buttons, dynamic actions, validations, processes, computations, branches, navigation entries and application processes; fail closed; *Used in*, Advisor and export ([chapter 6](guide/06-processing.md#build-options)) |
126
128
  | Collections (`APEX_COLLECTION`) | ➖ | Temporary or unlogged tables, or `jsonb` in session state |
@@ -204,10 +206,10 @@ two, REST source catalogs and the workspace export, with their features.)
204
206
 
205
207
  | APEX | pgkiln | Notes |
206
208
  |---|---|---|
207
- | Approvals and task list | ✅ | Task definitions (approval / action, owner roles and users, business administrators, priority, due date, details page), `meta.create_task` from application SQL, a task list region (claim, approve/reject/complete with comment, release, delegate, cancel, history), completion SQL in the same transaction. (sprint 64) **Vacation rules** (a stand-in acts on a user's tasks while the rule is active, checked on every request) and **expiration policies** (expire, renew up to N times, escalate to a role once; a waiting workflow goes on with the outcome `expired`). **Missing:** built-in e-mail notifications (send them with `meta.send_mail` from the completion SQL or an automation) |
208
- | Workflow (26.1: parallel flows, multi-tenancy) | ✅ | Workflow definitions of task, SQL, switch, wait, **invoke API** (a REST data source or URL through the invoke-API code, response values and the HTTP status into variables, retryable faults, no transaction open during the call) and end steps with variables, started from application SQL, run by the server (NOTIFY + polling) as the app's role; console region (terminate, retry a faulted step); diagram in the builder. **Parallel branches** (split, then a join that waits for all or for the first branch) and **versions** (development, active, inactive; running instances keep theirs) ([chapter 6](guide/06-processing.md#workflows)). (0.31) **Tenants**: `meta.set_tenant()` (APEX: `APEX_SESSION.SET_TENANT_ID`); workflows, their tasks, background chains, the task list, the console and every action follow the session's tenant ([chapter 6](guide/06-processing.md#tenants)). No *Send E-Mail* activity: send mail with `meta.send_mail` from an SQL step |
209
+ | Approvals and task list | ✅ | Task definitions (approval / action, owner roles and users, business administrators, priority, due date, details page), `meta.create_task` from application SQL, a task list region (claim, approve/reject/complete with comment, release, delegate, cancel, history), completion SQL in the same transaction. (sprint 64) **Vacation rules** (a stand-in acts on a user's tasks while the rule is active, checked on every request) and **expiration policies** (expire, renew up to N times, escalate to a role once; a waiting workflow goes on with the outcome `expired`). (sprint 114) **custom outcomes** with labels and colours (Yes / No / Undecided), **task events** on complete, expire and a failed completion SQL (SQL, e-mail or start a workflow; reopen or keep the task) and **notifications** from e-mail templates on create, complete and expire ([chapter 6](guide/06-processing.md#custom-outcomes)) |
210
+ | Workflow (26.1: parallel flows, multi-tenancy) | ✅ | Workflow definitions of task, SQL, switch, wait, **invoke API** (a REST data source or URL through the invoke-API code, response values and the HTTP status into variables, retryable faults, no transaction open during the call) and end steps with variables, started from application SQL, run by the server (NOTIFY + polling) as the app's role; console region (terminate, retry a faulted step); diagram in the builder. **Parallel branches** (split, then a join that waits for all or for the first branch) and **versions** (development, active, inactive; running instances keep theirs) ([chapter 6](guide/06-processing.md#workflows)). (0.31) **Tenants**: `meta.set_tenant()` (APEX: `APEX_SESSION.SET_TENANT_ID`); workflows, their tasks, background chains, the task list, the console and every action follow the session's tenant ([chapter 6](guide/06-processing.md#tenants)). (sprint 114, 26.1 / 26.2) **Send E-Mail** steps (a template or subject and body), **draft** steps allowed only in development versions, branching on custom task outcomes, **migrating running instances** to a newer version (step mapping, preview, one transaction, a log; not yet for instances inside open parallel branches: [chapter 6](guide/06-processing.md#migrating-instances)) and the diagram as **SVG / PNG** |
209
211
  | Automations (scheduled) | ✅ | Shared Components → Automations: cron schedules with time zones, once or per row of a query, **several ordered actions** with their own conditions, **error handling** (stop, skip the row and continue, or disable; row errors in the run history), roles, run history and Run now, **on-demand runs from SQL** with `meta.run_automation()` (like `APEX_AUTOMATION.EXECUTE`, synchronous in the caller's transaction), safe with several servers ([chapter 6](guide/06-processing.md#automations)) |
210
- | AI assistant, natural-language reports (NL2IR), AI agents and tools (26.1) | ✅ | Region type **AI assistant**: a conversation per session, context queries (RAG over developer queries, run as the app's role, read-only), and **tools** the model may call: SQL with bound arguments checked against declared parameters (read-only unless marked as writing, row limit, timeout) and REST data sources, each optionally behind an authorization scheme. **Natural-language report filters**: an "Ask in your own words" box turns a question into the report's normal filters, search and sort, limited to its visible columns ([chapter 4](guide/04-pages-and-regions.md)). Agents are defined per region (not as shared components); answers are not streamed. `pgvector` covers semantic search on the data side |
212
+ | AI assistant, natural-language reports (NL2IR), AI agents and tools (26.1) | ✅ | Region type **AI assistant**: a conversation per session, context queries (RAG over developer queries, run as the app's role, read-only), and **tools** the model may call: SQL with bound arguments checked against declared parameters (read-only unless marked as writing, row limit, timeout) and REST data sources, each optionally behind an authorization scheme. **AI Interactive Reports** (sprint 112; 26.1 / 26.2): an "Ask in your own words" box turns a question into the report's own settings (filters, highlights, search, sort, control break, totals, group by, pivot, chart and computed columns such as "bonus is 10% of salary"), never SQL, limited to its shown columns; each setting is a chip with Edit and Remove, plus Undo. Without an AI service a built-in phrase parser (English, Dutch, German) answers ([chapter 4](guide/04-pages-and-regions.md#ask-in-your-own-words-ai-interactive-reports)). Not yet: phrases mapped to faceted search and smart filter values. (sprint 111) **AI Agents** as a shared component (26.1 / 26.2): an AI service, system prompt, reasoning effort (low / medium / high, mapped per provider), approved tools, max steps and tokens and an authorization scheme; an assistant region names one. **AI Tools**: SQL (read-only transaction, the app role through RLS, timeout), PL/pgSQL function, REST data source, JavaScript action (a registered action or custom-event dynamic action), and **Navigate**: pgkiln builds the checksummed URL, only to pages the user may open. Every tool call is logged with its arguments (debug and Activity → AI usage). Providers add Gemini and OpenAI-compatible endpoints (Ollama, vLLM, Azure OpenAI). Answers are not streamed; no OCI provider. `pgvector` covers semantic search on the data side |
211
213
  | *Generate Text with AI* process, structured outputs (26.1) | ✅ | **AI services** (Workspace utilities, administrators): Claude through `@anthropic-ai/sdk` or OpenAI through `openai`, encrypted write-only keys (or `ANTHROPIC_API_KEY` / `OPENAI_API_KEY`), per-app access with daily request and token limits, a usage log. Page process `ai_generate` (text into an item, or structured output into several items with a schema built from the items or your own) and a dynamic action that runs it without a page submit; `meta.ai_generate` / `ai_result` / `ai_available` from SQL ([chapter 6](guide/06-processing.md)). Prompts, item values included, go to the chosen provider |
212
214
  | Blueprints, spec-driven development (26.1) | ✅ | Create → **From a blueprint**: a JSON spec of tables (types, required, unique, allowed values, references), pages, navigation and sample data; optionally drafted by AI from a description; always reviewed (problems, SQL, pages, menu) before the application is created in one transaction; saved blueprints; (sprint 66) **a blueprint from an existing application** (its tables, pages, menu and optionally sample rows, with notes on what didn't carry over) |
213
215
 
@@ -333,7 +335,7 @@ Sources: [APEX 26.1 new features](https://docs.oracle.com/en/database/oracle/ape
333
335
  | Workflows, Task Definitions, Automations | The same names |
334
336
  | Text Messages, Translate, Dynamic Translations, Globalization Attributes | Translations and Text Messages, Dynamic Translations, Settings → Globalization |
335
337
  | Map Backgrounds | ➖ Not planned per application: the installation's map tile server (`MAP_TILE_URL`) is every map's background |
336
- | AI Attributes, AI Services, AI Agents | AI Services; AI agents ➖ not planned |
338
+ | AI Attributes, AI Services, AI Agents | AI Services; AI Agents and AI Tools (sprint 111) |
337
339
 
338
340
  ## Database Actions (SQL Developer Web) and the SQL Workshop
339
341