@adula/kit 0.2.0-alpha.4 → 1.2.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 (120) hide show
  1. package/README.md +12 -2
  2. package/build/agent/AGENTS.template.md +2 -2
  3. package/build/agent/capabilities.md +54 -7
  4. package/build/agent/skills/adula-frontend-design/SKILL.md +1 -1
  5. package/build/agent/skills/idea-review/SKILL.md +26 -1
  6. package/build/agent/skills/module-review/SKILL.md +22 -0
  7. package/build/agent/skills/perf-review/SKILL.md +24 -0
  8. package/build/agent/skills/schema-review/SKILL.md +22 -0
  9. package/build/agent/skills/security-review/SKILL.md +24 -0
  10. package/build/agent/skills/ui-review/SKILL.md +22 -0
  11. package/build/commands/capabilities.d.ts +4 -0
  12. package/build/commands/capabilities.js +35 -4
  13. package/build/commands/doctor.js +37 -1
  14. package/build/commands/gaps.js +16 -6
  15. package/build/commands/install.js +19 -5
  16. package/build/commands/main.d.ts +4 -2
  17. package/build/commands/main.js +2 -0
  18. package/build/commands/module_add.js +2 -2
  19. package/build/commands/resource.js +1 -1
  20. package/build/commands/resource_snapshot.d.ts +15 -0
  21. package/build/commands/resource_snapshot.js +61 -0
  22. package/build/database/migrations/1770000000004_kit_collaboration.d.ts +5 -0
  23. package/build/database/migrations/1770000000004_kit_collaboration.js +10 -0
  24. package/build/database/migrations/1770000000005_kit_assignments.d.ts +5 -0
  25. package/build/database/migrations/1770000000005_kit_assignments.js +10 -0
  26. package/build/database/migrations/1770000000006_kit_messaging.d.ts +5 -0
  27. package/build/database/migrations/1770000000006_kit_messaging.js +10 -0
  28. package/build/database/migrations/1770000000007_kit_webhooks.d.ts +5 -0
  29. package/build/database/migrations/1770000000007_kit_webhooks.js +10 -0
  30. package/build/database/migrations/1770000000008_kit_imports.d.ts +5 -0
  31. package/build/database/migrations/1770000000008_kit_imports.js +10 -0
  32. package/build/database/migrations/1770000000010_kit_workflows.d.ts +5 -0
  33. package/build/database/migrations/1770000000010_kit_workflows.js +10 -0
  34. package/build/database/migrations/1770000000011_kit_managed_assignments.d.ts +5 -0
  35. package/build/database/migrations/1770000000011_kit_managed_assignments.js +10 -0
  36. package/build/database/migrations/1770000000012_kit_role_keys.d.ts +5 -0
  37. package/build/database/migrations/1770000000012_kit_role_keys.js +10 -0
  38. package/build/database/migrations/1770000000013_kit_notification_targets.d.ts +5 -0
  39. package/build/database/migrations/1770000000013_kit_notification_targets.js +10 -0
  40. package/build/database/migrations/1770000000014_kit_upload_grants.d.ts +5 -0
  41. package/build/database/migrations/1770000000014_kit_upload_grants.js +10 -0
  42. package/build/database/migrations/1770000000015_kit_inbound_webhooks.d.ts +5 -0
  43. package/build/database/migrations/1770000000015_kit_inbound_webhooks.js +10 -0
  44. package/build/index.d.ts +31 -2
  45. package/build/index.js +17 -2
  46. package/build/src/admin/contracts.d.ts +7 -0
  47. package/build/src/admin/contracts.js +34 -12
  48. package/build/src/admin/controller.d.ts +2 -0
  49. package/build/src/admin/controller.js +52 -1
  50. package/build/src/admin/presentation.d.ts +6 -0
  51. package/build/src/admin/record_title.d.ts +14 -0
  52. package/build/src/admin/record_title.js +50 -0
  53. package/build/src/admin/resource_service.d.ts +184 -2
  54. package/build/src/admin/resource_service.js +713 -48
  55. package/build/src/attachments/attachment_service.d.ts +12 -0
  56. package/build/src/attachments/attachment_service.js +28 -2
  57. package/build/src/attachments/upload_grants.d.ts +44 -0
  58. package/build/src/attachments/upload_grants.js +105 -0
  59. package/build/src/auth/ability.d.ts +1 -1
  60. package/build/src/auth/ability.js +4 -1
  61. package/build/src/auth/actor_store.js +6 -1
  62. package/build/src/auth/conditions.d.ts +15 -0
  63. package/build/src/auth/conditions.js +36 -0
  64. package/build/src/auth/sql.js +6 -2
  65. package/build/src/collaboration/assignments.d.ts +129 -0
  66. package/build/src/collaboration/assignments.js +333 -0
  67. package/build/src/collaboration/record_collaboration.d.ts +86 -0
  68. package/build/src/collaboration/record_collaboration.js +348 -0
  69. package/build/src/commands/agent_assets.js +5 -0
  70. package/build/src/commands/capabilities.d.ts +19 -0
  71. package/build/src/commands/capabilities.js +179 -0
  72. package/build/src/commands/doctor.d.ts +31 -0
  73. package/build/src/commands/doctor.js +114 -0
  74. package/build/src/commands/gap_report.d.ts +50 -2
  75. package/build/src/commands/gap_report.js +102 -4
  76. package/build/src/commands/generator.js +3 -3
  77. package/build/src/commands/snapshot.d.ts +28 -0
  78. package/build/src/commands/snapshot.js +48 -0
  79. package/build/src/commands/source_markers.d.ts +10 -1
  80. package/build/src/commands/source_markers.js +36 -4
  81. package/build/src/core/administration_guard.js +3 -1
  82. package/build/src/core/message_templates.d.ts +83 -0
  83. package/build/src/core/message_templates.js +293 -0
  84. package/build/src/core/module_seed.d.ts +15 -0
  85. package/build/src/core/module_seed.js +31 -0
  86. package/build/src/core/notifications.d.ts +18 -1
  87. package/build/src/core/notifications.js +27 -1
  88. package/build/src/core/roles.d.ts +27 -1
  89. package/build/src/core/roles.js +133 -5
  90. package/build/src/database/schema.d.ts +45 -0
  91. package/build/src/database/schema.js +290 -0
  92. package/build/src/eslint/index.js +26 -0
  93. package/build/src/events/outbox.d.ts +1 -0
  94. package/build/src/events/outbox.js +1 -1
  95. package/build/src/events/record_mutation.d.ts +11 -0
  96. package/build/src/events/record_mutation.js +15 -2
  97. package/build/src/integrations/imports.d.ts +74 -0
  98. package/build/src/integrations/imports.js +333 -0
  99. package/build/src/integrations/inbound_webhooks.d.ts +94 -0
  100. package/build/src/integrations/inbound_webhooks.js +276 -0
  101. package/build/src/integrations/openapi.d.ts +39 -0
  102. package/build/src/integrations/openapi.js +323 -0
  103. package/build/src/integrations/print.d.ts +37 -0
  104. package/build/src/integrations/print.js +124 -0
  105. package/build/src/integrations/webhooks.d.ts +98 -0
  106. package/build/src/integrations/webhooks.js +298 -0
  107. package/build/src/resource/define_resource.d.ts +1 -0
  108. package/build/src/resource/define_resource.js +21 -1
  109. package/build/src/resource/registry.d.ts +3 -0
  110. package/build/src/resource/registry.js +42 -0
  111. package/build/src/resource/types.d.ts +67 -1
  112. package/build/src/resource/values.js +2 -1
  113. package/build/src/services/settings.d.ts +13 -1
  114. package/build/src/services/settings.js +9 -2
  115. package/build/src/workflows/define_workflow.d.ts +124 -0
  116. package/build/src/workflows/define_workflow.js +123 -0
  117. package/build/src/workflows/engine.d.ts +141 -0
  118. package/build/src/workflows/engine.js +752 -0
  119. package/build/stubs/resource_contract.txt +103 -41
  120. package/package.json +7 -3
@@ -0,0 +1,50 @@
1
+ const TITLE_TYPES = new Set(['string', 'text', 'integer', 'money', 'date', 'datetime', 'lookup']);
2
+ /**
3
+ * The fields that name a record wherever it is referenced (#47, #32): the resource's
4
+ * `title`, or else its first sequence field and its first text field in `list`. Lookup
5
+ * keys are never shown raw; declared lookup fields are shown by their Arabic label.
6
+ */
7
+ export function titleFields(resource) {
8
+ if (resource.title)
9
+ return [...resource.title];
10
+ const sequence = Object.keys(resource.fields).find((key) => resource.fields[key].sequence);
11
+ const text = resource.list.find((key) => {
12
+ const field = resource.fields[key];
13
+ return (field.type === 'string' || field.type === 'text') && !field.sequence;
14
+ });
15
+ return [sequence, text].filter((key) => key !== undefined);
16
+ }
17
+ /** Definition check: title fields exist, are readable and have a displayable type. */
18
+ export function assertTitle(resource) {
19
+ if (!resource.title)
20
+ return;
21
+ if (!resource.title.length)
22
+ throw new Error(`${resource.name}: title needs at least one field`);
23
+ const readable = new Set(resource.serialize ?? [...resource.list, ...resource.show]);
24
+ for (const key of resource.title) {
25
+ const field = resource.fields[key];
26
+ if (!field)
27
+ throw new Error(`Unknown field: ${key}`);
28
+ if (!TITLE_TYPES.has(field.type))
29
+ throw new Error(`${resource.name}: title field ${key} cannot be a ${field.type}`);
30
+ if (!readable.has(key))
31
+ throw new Error(`${resource.name}: title field ${key} must be serialized`);
32
+ }
33
+ }
34
+ /**
35
+ * The title of one serialized record, read after field-level filtering so it never shows
36
+ * a value the viewer may not read. Returns null when no title field has a value.
37
+ */
38
+ export function formatTitle(resource, record, lookupLabel) {
39
+ const parts = [];
40
+ for (const key of titleFields(resource)) {
41
+ const value = record[key];
42
+ if (value === null || value === undefined || value === '')
43
+ continue;
44
+ const field = resource.fields[key];
45
+ parts.push(field.type === 'lookup'
46
+ ? (lookupLabel(field.group, String(value)) ?? String(value))
47
+ : String(value));
48
+ }
49
+ return parts.length ? parts.join(' · ') : null;
50
+ }
@@ -1,8 +1,27 @@
1
1
  import type { Knex } from 'knex';
2
2
  import type { ResourceRegistry } from '../resource/registry.js';
3
- import type { Action, RecordData, Resource, SerializedRecord } from '../resource/types.js';
3
+ import type { Action, JsonValue, RecordData, Resource, SerializedRecord } from '../resource/types.js';
4
4
  import { type Actor, type KitAbility } from '../auth/ability.js';
5
+ import { type Conditions } from '../auth/conditions.js';
5
6
  import type { ResourceDescription, ResourceNavigation, ResourceField } from './presentation.js';
7
+ export type AggregateOptions = {
8
+ /** Up to three queryable fields (docStatus and orgUnitId are also accepted). */
9
+ groupBy?: string[];
10
+ /** Count rows per group (default true). */
11
+ count?: boolean;
12
+ /** Integer or money fields to total per group; money totals are decimal strings. */
13
+ sum?: string[];
14
+ /** Equality filters, as in list(). */
15
+ filters?: Record<string, unknown>;
16
+ /** Role-rule style conditions ($eq, $ne, $in, $lt, $gt, $like, "$actor.id"). */
17
+ where?: Conditions;
18
+ search?: string;
19
+ };
20
+ export type AggregateRow = {
21
+ group: Record<string, JsonValue>;
22
+ count?: number;
23
+ sum?: Record<string, string | null>;
24
+ };
6
25
  export type ListOptions = {
7
26
  limit?: number;
8
27
  cursor?: string;
@@ -10,6 +29,8 @@ export type ListOptions = {
10
29
  sort?: string;
11
30
  direction?: 'asc' | 'desc';
12
31
  filters?: Record<string, unknown>;
32
+ /** Only records carrying this tag (see RecordCollaboration.setTags). */
33
+ tag?: string;
13
34
  estimate?: boolean;
14
35
  };
15
36
  /**
@@ -17,10 +38,30 @@ export type ListOptions = {
17
38
  * indirectly, so it needs unconditional view access at the field's level.
18
39
  */
19
40
  export declare function canQueryField(resource: Resource, actor: Actor, ability: KitAbility, key: string): boolean;
41
+ /** Options of ResourceService.systemSave. */
42
+ export type SystemSaveOptions = {
43
+ /** The user recorded as the author: created/updated by, activity and events. */
44
+ actorId: number;
45
+ /** Write inside the module's transaction. */
46
+ trx?: Knex.Transaction;
47
+ /** Why module code wrote the record; stored with the activity entry. */
48
+ reason?: string;
49
+ /** Optimistic lock: when given, it must match the stored version. */
50
+ version?: number;
51
+ /**
52
+ * The person who chose the values, when module code writes a choice made by a user,
53
+ * such as a supervisor reassigning a record. A new value of a user field must then be
54
+ * eligible for them as in a user save; without it, any active member qualifies on
55
+ * unscoped resources.
56
+ */
57
+ chooser?: Actor;
58
+ };
20
59
  export declare class ResourceService {
21
60
  private db;
22
61
  private registry;
23
62
  constructor(db: Knex, registry: ResourceRegistry);
63
+ /** The Arabic label of a registered resource, for notifications and titles. */
64
+ label(name: string): string;
24
65
  /** Project navigation is derived from registered resources and the current actor. */
25
66
  navigation(actor: Actor): ResourceNavigation;
26
67
  describe(name: string, actor: Actor): ResourceDescription;
@@ -33,7 +74,20 @@ export declare class ResourceService {
33
74
  private scopedQuery;
34
75
  private columns;
35
76
  private find;
77
+ /** A live record regardless of any actor's scope, for system writes. */
78
+ private findAny;
36
79
  private requireRecord;
80
+ /**
81
+ * Authorizes one record for collaboration features (comments, tags, assignments...).
82
+ * Scope and conditional rules apply exactly as for show/update.
83
+ */
84
+ access(name: string, id: number, actor: Actor, action?: Action, db?: Knex): Promise<{
85
+ resource: Resource;
86
+ record: RecordData;
87
+ ability: KitAbility;
88
+ }>;
89
+ /** Whether a (possibly other) actor may perform an action on a record; never throws for denial. */
90
+ permits(name: string, id: number, actor: Actor, action?: Action): Promise<boolean>;
37
91
  list(name: string, actor: Actor, options?: ListOptions): Promise<{
38
92
  data: SerializedRecord[];
39
93
  permissions: {
@@ -47,7 +101,24 @@ export declare class ResourceService {
47
101
  related: Record<string, SerializedRecord[]>;
48
102
  }>;
49
103
  private canQueryField;
104
+ /**
105
+ * Counts and totals over the records the actor may view, grouped by queryable fields,
106
+ * for dashboards. Authorization is list()'s: the same rules, organization scope and
107
+ * soft-delete filter apply, and every grouped, totalled or filtered field must be one
108
+ * the actor may query (E_FIELD_FORBIDDEN otherwise), so no hidden value leaks.
109
+ */
110
+ aggregate(name: string, actor: Actor, options?: AggregateOptions): Promise<{
111
+ rows: AggregateRow[];
112
+ truncated: boolean;
113
+ }>;
50
114
  private preload;
115
+ /** Formats record titles of one resource, loading the labels of its title lookups once. */
116
+ private titler;
117
+ /**
118
+ * Titles of records the actor may view, read under the actor's field access (#32).
119
+ * Records the actor cannot view are left out; callers fall back to the record id.
120
+ */
121
+ titles(name: string, ids: readonly number[], actor: Actor): Promise<Map<number, string>>;
51
122
  show(name: string, id: number, actor: Actor): Promise<{
52
123
  data: SerializedRecord;
53
124
  permissions: Partial<Record<Action, boolean>>;
@@ -57,6 +128,10 @@ export declare class ResourceService {
57
128
  id?: number;
58
129
  search?: string;
59
130
  cursor?: string;
131
+ /** User fields: the organization unit chosen in the form. */
132
+ orgUnitId?: number;
133
+ /** User fields: choices for a list filter instead of a form. */
134
+ purpose?: 'form' | 'filter';
60
135
  }): Promise<{
61
136
  data: {
62
137
  value: string;
@@ -64,11 +139,25 @@ export declare class ResourceService {
64
139
  }[];
65
140
  nextCursor: string | null;
66
141
  }>;
142
+ /**
143
+ * Choices for a user field. A form lists active members of the record's unit or its
144
+ * ancestors (the users the record is visible to through membership); a filter lists
145
+ * active users who share organization scope with the actor. Only names are returned.
146
+ */
147
+ private userOptions;
148
+ /**
149
+ * Active users eligible for a user field: members of the unit at `path` or of one of
150
+ * its ancestors, or (without a path) users sharing organization scope with the actor.
151
+ * A system write on an unscoped resource passes no actor: any active member qualifies.
152
+ */
153
+ private eligibleUsers;
67
154
  /** Deferred relation reads re-authorize the parent and each child on every request. */
68
155
  children(name: string, id: number, actor: Actor): Promise<Record<string, {
69
156
  rows: SerializedRecord[];
70
157
  hasMore: boolean;
71
158
  }>>;
159
+ /** Inline children of an already authorized parent record. */
160
+ private childrenOf;
72
161
  /** Active lookup labels for the lookup fields the actor may read on this resource. */
73
162
  lookups(name: string, actor: Actor): Promise<Record<string, {
74
163
  value: string;
@@ -88,10 +177,66 @@ export declare class ResourceService {
88
177
  actorEmail: string | null;
89
178
  createdAt: string;
90
179
  }[]>;
91
- editor(name: string, actor: Actor, id?: number): Promise<{
180
+ /**
181
+ * Inline children and recent activity of one record, authorized with a single record
182
+ * read (for deferred page props that would otherwise read the record twice).
183
+ */
184
+ details(name: string, id: number, actor: Actor): Promise<{
185
+ children: Record<string, {
186
+ rows: SerializedRecord[];
187
+ hasMore: boolean;
188
+ }>;
189
+ activity: {
190
+ id: number;
191
+ action: Action;
192
+ fields: string[];
193
+ actorId: number;
194
+ actorName: string | null;
195
+ /**
196
+ * @deprecated Always null. Kept so project-owned copies of resource-show from
197
+ * earlier releases still compile; they fall back to the actor id.
198
+ */
199
+ actorEmail: string | null;
200
+ createdAt: string;
201
+ }[];
202
+ }>;
203
+ /** show(), children() and activity() of one record from a single record read. */
204
+ record(name: string, id: number, actor: Actor): Promise<{
205
+ data: SerializedRecord;
206
+ permissions: Partial<Record<Action, boolean>>;
207
+ related: Record<string, SerializedRecord[]>;
208
+ children: Record<string, {
209
+ rows: SerializedRecord[];
210
+ hasMore: boolean;
211
+ }>;
212
+ activity: {
213
+ id: number;
214
+ action: Action;
215
+ fields: string[];
216
+ actorId: number;
217
+ actorName: string | null;
218
+ /**
219
+ * @deprecated Always null. Kept so project-owned copies of resource-show from
220
+ * earlier releases still compile; they fall back to the actor id.
221
+ */
222
+ actorEmail: string | null;
223
+ createdAt: string;
224
+ }[];
225
+ }>;
226
+ private activityOf;
227
+ /**
228
+ * The form description for create (no id) or update. `defaults` pre-fills a create form
229
+ * (#48), for example `?defaults[violation]=13`: only visible form fields are used, a
230
+ * related record must be viewable by the actor and a lookup must be active. Anything else
231
+ * is dropped. Defaults are only initial values; saving validates as usual.
232
+ */
233
+ editor(name: string, actor: Actor, id?: number, request?: {
234
+ defaults?: Record<string, unknown>;
235
+ }): Promise<{
92
236
  mode: string;
93
237
  name: string;
94
238
  label: string;
239
+ recordLabel: string | null;
95
240
  fields: ({
96
241
  label: import("../resource/types.js").Label;
97
242
  column?: string;
@@ -131,6 +276,18 @@ export declare class ResourceService {
131
276
  type: "belongsTo";
132
277
  resource: string;
133
278
  key: string;
279
+ } | {
280
+ label: import("../resource/types.js").Label;
281
+ column?: string;
282
+ required?: boolean;
283
+ unique?: boolean;
284
+ sortable?: boolean;
285
+ searchable?: boolean;
286
+ filterable?: boolean;
287
+ permissionLevel?: number;
288
+ sequence?: string;
289
+ type: "user";
290
+ key: string;
134
291
  } | {
135
292
  label: import("../resource/types.js").Label;
136
293
  column?: string;
@@ -160,6 +317,8 @@ export declare class ResourceService {
160
317
  group: string;
161
318
  key: string;
162
319
  })[];
320
+ /** Initial values of a create form, already checked against the actor's access. */
321
+ defaults: SerializedRecord;
163
322
  inline: Record<string, {
164
323
  rows: SerializedRecord[];
165
324
  hasMore: boolean;
@@ -182,13 +341,36 @@ export declare class ResourceService {
182
341
  label: string;
183
342
  }[];
184
343
  scoped: boolean;
344
+ /** The belongsTo field whose record decides the unit; the form shows no unit picker. */
345
+ scopeFrom: string | null;
185
346
  record: SerializedRecord | null;
186
347
  }>;
187
348
  save(name: string, actor: Actor, input: RecordData, id?: number, transaction?: Knex.Transaction): Promise<SerializedRecord>;
349
+ /**
350
+ * A write decided by module code rather than by a user's role rules: state transitions,
351
+ * snapshots and listener updates. It runs the validator, hooks, lookup, relation and
352
+ * attachment checks, versioning and the audit trail exactly like save(), and skips only
353
+ * the actor's role rules and organization scope. Values may set any stored field except
354
+ * sequences and inline children. Updates merge the given values into the stored record.
355
+ * Returns the full stored record; module code must not send it to users unfiltered.
356
+ */
357
+ systemSave(name: string, values: RecordData, id: number | undefined, options: SystemSaveOptions): Promise<RecordData>;
358
+ /**
359
+ * Moves the records whose scope follows this parent (`scope: { from }`) to the parent's
360
+ * current organization unit, through systemSave. Call it from a listener on the parent's
361
+ * `updated` event, or right after moving the parent. Returns the number of moved records.
362
+ */
363
+ rehome(parentName: string, parentId: number, options: Omit<SystemSaveOptions, 'version'>): Promise<number>;
188
364
  private persist;
189
365
  private requireVersion;
190
366
  /** Inline children share the parent's draft state, organization and update authority. */
191
367
  private requireInlineParents;
192
368
  transition(name: string, id: number, actor: Actor, action: 'delete' | 'submit' | 'cancel', version?: unknown, transaction?: Knex.Transaction): Promise<SerializedRecord>;
369
+ /**
370
+ * Amend-by-copy: a cancelled document is copied into a new draft that points to
371
+ * it through amended_from_id, together with its inline lines. The original stays
372
+ * cancelled and unchanged; sequence fields receive new numbers.
373
+ */
374
+ amend(name: string, id: number, actor: Actor, transaction?: Knex.Transaction): Promise<SerializedRecord>;
193
375
  private audit;
194
376
  }