interaqt 0.7.4 → 0.8.1

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.
@@ -10,6 +10,14 @@ color: blue
10
10
 
11
11
  ## START: Select Next Uncompleted Item
12
12
 
13
+ **📖 FIRST: Determine current module and confirm context.**
14
+
15
+ **🔴 STEP 0: Determine Current Module**
16
+ 1. Read module name from `.currentmodule` file in project root
17
+ 2. If file doesn't exist, STOP and ask user which module to work on
18
+ 3. Use this module name for all subsequent file operations
19
+ 4. Module status file location: `docs/{module}.status.json`
20
+
13
21
  **📖 Reference**
14
22
  - `./backend/crud.example.ts` + `./tests/crud.example.test.ts` - Simple CRUD operations with state machines, relations, and permissions
15
23
  - `./backend/versionControl.example.ts` + `./tests/versionControl.example.test.ts` - Version control with soft delete pattern
@@ -17,9 +25,13 @@ color: blue
17
25
  - `./agentspace/knowledge/generator/computation-implementation.md` - Detailed computation implementation patterns and examples
18
26
 
19
27
 
28
+ **🔴 CRITICAL: Module-Based File Naming**
29
+ - Read module name from `.currentmodule` and use it as file prefix
30
+ - All file references must use `{module}.` prefix format
31
+
20
32
  **🔴 CRITICAL: Implement ONLY ONE computation per session, then STOP and wait for user confirmation.**
21
33
 
22
- 1. **Read `docs/computation-implementation-plan.json`** to find the FIRST item with `completed: false`
34
+ 1. **Read `docs/{module}.computation-implementation-plan.json`** to find the FIRST item with `completed: false`
23
35
  - ALWAYS select the FIRST item where `completed` field is `false`
24
36
  - NEVER skip ahead - dependencies must be completed in order
25
37
  - Phase 1 must complete before Phase 2, etc.
@@ -35,12 +47,11 @@ color: blue
35
47
  2. **Analyze Root Cause**:
36
48
  - Verify implementation code correctness
37
49
  - Check all `expandedDependencies` are properly handled
38
- - Cross-reference with `requirements/interaction-matrix.md` for business logic
39
50
  - Confirm test expectations match business requirements
40
51
  - Review similar successful computations for patterns
41
52
 
42
53
  3. **Apply Fix Based on Analysis**:
43
- - **Implementation Issue** → Fix computation code in backend/index.ts (refer to API reference)
54
+ - **Implementation Issue** → Fix computation code in `backend/{module}.ts` (refer to API reference)
44
55
  - **Test Issue** → Fix test case logic or expectations
45
56
  - **Dependency Issue** → Fix data creation order
46
57
  - **Business Logic Issue** → Re-read requirements and adjust
@@ -58,14 +69,84 @@ color: blue
58
69
 
59
70
  1. **Implement the Computation** (following API Reference)
60
71
  - **📖 MANDATORY FIRST STEP: Completely read `./agentspace/knowledge/generator/api-reference.md` to understand all API usage before writing any code**
61
- - **📖 MANDATORY SECOND STEP: Completely read `./backend/index.ts` to understand all existing implementations from previous tasks**
72
+ - **📖 MANDATORY SECOND STEP: Completely read `./backend/{module}.ts` to understand all existing implementations from previous tasks**
62
73
  - **📖 MANDATORY THIRD STEP: Study the reference example files above to understand the standard code structure and computation patterns**
74
+ - **🔴 CRITICAL: Recognize Split Computation Nodes** - If the current item's ID contains `@InteractionName` (e.g., `Entity@CreateInteraction`, `Entity@UpdateInteraction`):
75
+ - This indicates the entity/relation can be created through multiple interaction paths
76
+ - Multiple nodes with same entity name but different `@InteractionName` suffixes represent ONE unified computation with multiple trigger paths
77
+ - **First node implementation**: Design the Transform to support ALL interactions from the start, even if you're only implementing the first path. Check the plan file for other nodes with the same entity name to see all interaction paths.
78
+ - **Subsequent nodes**: Add new interaction branch to the existing Transform, don't create separate computation
79
+ - Use `event.interactionName` or match conditions to branch between different interaction paths within a single Transform callback
80
+ - All nodes with same entity name share the same `ownerProperties` and `createdWithRelations` - implement them once in the unified Transform
81
+ - Example: `User@CreateUser` and `User@ImportUser` should result in ONE `User.computation` Transform with two branches
63
82
  - **🔴 CRITICAL: Check for existing computations** - If the target entity/relation already has computation code:
64
83
  - **NEVER overwrite** existing computation logic
65
84
  - **ADD branch logic** to handle the new interaction/scenario within existing Transform callback
66
85
  - **PRESERVE all existing branches** to ensure previous test cases continue to pass
67
86
  - Example: Add `else if` conditions or extend existing conditions in Transform callback
68
87
  - **⚠️ Regression Prevention**: All previous tests must continue passing after adding new computation branches
88
+ - **Note**: The backend file is `./backend/{module}.ts` where {module} is from `.currentmodule`
89
+ - **🔴 CRITICAL: NO DATA MUTATIONS IN COMPUTATIONS** - Computations must NEVER directly use `this.system.storage.create()`, `this.system.storage.update()`, or `this.system.storage.delete()`:
90
+ - ❌ **FORBIDDEN**: `await this.system.storage.create('Entity', {...})` in computation code
91
+ - ❌ **FORBIDDEN**: `await this.system.storage.update('Entity', MatchExp..., {...})` in computation code
92
+ - ❌ **FORBIDDEN**: `await this.system.storage.delete('Entity', MatchExp...)` in computation code
93
+ - ✅ **ALLOWED**: `await this.system.storage.find()` or `await this.system.storage.findOne()` to READ data
94
+ - **WHY**: Computations are reactive and should only compute values based on existing data. Data mutations should happen through Interactions or RecordMutationSideEffect, NOT in computations
95
+ - **IF YOU NEED TO MUTATE DATA**: Use Interactions (for user-triggered actions) or RecordMutationSideEffect (for reactive side effects)
96
+ - **🔴 CRITICAL: NO MOCK DATA OR PLACEHOLDERS** - The reactive framework is complete and powerful. Every computation must be fully implemented:
97
+ - ❌ **FORBIDDEN**: `return 100 // TODO: implement later`
98
+ - ❌ **FORBIDDEN**: `return 0 // Placeholder`
99
+ - ❌ **FORBIDDEN**: `if (record.id === 'test') return mockValue`
100
+ - ✅ **REQUIRED**: Complete implementation with real data queries and calculations
101
+ - **IF DATA IS MISSING**: Add it to the data model (entities/relations/properties)
102
+ - **IF CALCULATION IS COMPLEX**: Break it down into steps, but implement all steps
103
+ - **🔴 CRITICAL: NO SIDE EFFECTS IN COMPUTATIONS** - Side effects (email, AI calls, external APIs) must be in integrations:
104
+ - ❌ **FORBIDDEN**: `await sendEmail(...)` in computation code
105
+ - ❌ **FORBIDDEN**: `await callOpenAI(...)` in computation code
106
+ - ❌ **FORBIDDEN**: `await stripeAPI.charge(...)` in computation code
107
+ - ✅ **CORRECT**: Create separate integration (see `implement-integration-handler`)
108
+ - **COMPUTATION ROLE**: Read data → Calculate → Return value (pure reactive computation)
109
+ - **INTEGRATION ROLE**: Handle all side effects triggered by data changes
110
+ - **🔴 CRITICAL: API Call Entity Property Computations** - If implementing Property computation for API Call entities that depend on Integration Event entities:
111
+ - **Key Matching Pattern**: Find the affected API Call record by matching `externalId` fields
112
+ - **Why**: Both API Call entity and Integration Event entity have `externalId` field that records the external API task/job ID
113
+ - **Pattern**: Use `event.externalId` to query and find the corresponding API Call record with matching `externalId`
114
+ - **Example**: When VolcTTSEvent arrives, find the corresponding VolcTTSCall record where `VolcTTSCall.externalId === event.externalId`
115
+ - **🔴 Special Case - externalId Property**:
116
+ - The `externalId` property is computed from the integration event with `eventType: 'initialized'`
117
+ - The 'initialized' event ALWAYS contains both `entityId` (the API Call's id) and `externalId` (from external system)
118
+ - This event establishes the link between API Call entity and external task/job ID
119
+ - Use `event.entityId` to find the API Call record when processing 'initialized' events
120
+ - Subsequent events (processing/completed/failed) use `externalId` for matching
121
+ - This pattern enables reactive updates to API Call status/result properties based on external system events
122
+ - **🔴 CRITICAL: Entity nodes with ownerProperties** - If the current item has `ownerProperties` field:
123
+ - These are `_owner` type properties that must be set in the entity's creation computation
124
+ - Include ALL ownerProperties in the owner entity computation return value when creating the entity
125
+ - **🔴 CRITICAL: Entity nodes with createdWithRelations** - If the current item has `createdWithRelations` field:
126
+ - These are `created-with-entity` type relations that must be created together with the entity
127
+ - Read `docs/{module}.data-design.json` to find the `sourceProperty` or `targetProperty` name for each relation
128
+ - In the entity's computation, return these property names with corresponding data to create relations
129
+ - Framework will automatically create relation records based on these property values. Example:
130
+ ```typescript
131
+ // Entity with createdWithRelations (create relations via property names)
132
+ Order.computation = Transform.create({
133
+ eventDeps: {
134
+ orderInteraction: {
135
+ recordName: InteractionEventEntity.name,
136
+ type: 'create',
137
+ record: {
138
+ interactionName: 'createOrder'
139
+ }
140
+ }
141
+ },
142
+ callback: async function(event) {
143
+ return {
144
+ orderNumber: event.payload.orderNumber,
145
+ owner: event.user // Creates OrderOwner relations via 'owner' sourceProperty
146
+ }
147
+ }
148
+ })
149
+ ```
69
150
  - **🔴 SPECIAL CASE 1: `_parent:[parent]` notation**
70
151
  - If the computation name contains `_parent:[parent]` (e.g., `_parent:[User]`), this means:
71
152
  - You should modify the PARENT entity's computation, not the current entity
@@ -89,42 +170,14 @@ color: blue
89
170
  }
90
171
  });
91
172
  ```
92
- - **🔴 SPECIAL CASE 2: `_owner` notation**
93
- - If the computation decision is `_owner`, this means:
94
- - The property's value is fully controlled by its owner entity/relation's computation
95
- - You should modify the OWNER entity/relation's creation or derivation logic, not add a separate property computation. Add the property assignment logic in the entity/relation's creation Transform
96
- - Example: For a `createdAt` property with `_owner`, add timestamp assignment in the entity's Transform that creates it
97
173
  - Add computation code using assignment pattern at end of file:
98
174
  ```typescript
99
- // At end of backend/index.ts, after exports:
100
-
101
- // Normal property computation
102
- User.properties.find(p => p.name === 'postCount').computation = Count.create({
175
+ // At end of backend/{module}.ts, after exports:
176
+ // Property computation
177
+ const userProperty = User.properties.find(p => p.name === 'postCount')!
178
+ userProperty.computation = Count.create({
103
179
  property: 'posts'
104
180
  })
105
-
106
- // For _owner properties, modify the owner entity's computation instead:
107
- Post.computationTarget = Transform.create({
108
- items: [
109
- TransformItem.create({
110
- from: 'InteractionEventEntity',
111
- name: 'event',
112
- transform: async function(this: Controller, event: InteractionEventEntity) {
113
- if (event.interaction === 'CreatePost') {
114
- // Create the Post entity with _owner properties
115
- return {
116
- title: event.payload.title,
117
- content: event.payload.content,
118
- createdAt: Math.floor(Date.now() / 1000), // _owner property set here
119
- status: 'draft' // _owner property set here
120
- }
121
- }
122
- return null
123
- }
124
- })
125
- ]
126
- })
127
- ```
128
181
  - Remove any `defaultValue` if adding computation to that property
129
182
  - Never use Transform in Property computation
130
183
  - For `_owner` properties, always set them in the owner's creation/derivation logic
@@ -134,10 +187,13 @@ color: blue
134
187
  - Fix all type errors before proceeding to tests
135
188
 
136
189
  3. **Create Test Case Plan**
137
- - Read item details from `docs/computation-implementation-plan.json`
190
+ - Read item details from `docs/{module}.computation-implementation-plan.json`
138
191
  - Check `expandedDependencies` to understand all required dependencies
139
192
  - Write test plan comment with: dependencies, test steps, business logic notes
140
- - Cross-reference with `requirements/interaction-matrix.md` and `docs/data-design.json`
193
+ - Cross-reference with `docs/{module}.data-design.json`
194
+ - **🔴 For split computation nodes** (ID contains `@InteractionName`): Write a separate test for the specific interaction path this node represents, even though the implementation is a unified Transform. Each node's test verifies its particular trigger path works correctly.
195
+ - **🔴 For entity nodes with `ownerProperties`**: Test entity creation AND verify ALL ownerProperties are correctly set
196
+ - **🔴 For entity nodes with `createdWithRelations`**: Test entity creation AND query each relation to verify it was created with correct source/target
141
197
  - **🔴 For `_parent:[parent]` computations**: Test the parent entity's behavior that creates/manages the child entities
142
198
  - **🔴 For `_owner` computations**: Test that the property is correctly set when the owner entity/relation is created
143
199
 
@@ -152,6 +208,48 @@ color: blue
152
208
  // Implementation...
153
209
  })
154
210
 
211
+ // For split computation nodes (same entity, different interaction paths):
212
+ test('VolcanoEngineStreamURLCall entity via CreateLivestreamRoom (Node: VolcanoEngineStreamURLCall@CreateLivestreamRoom)', async () => {
213
+ /**
214
+ * Test Plan for: VolcanoEngineStreamURLCall@CreateLivestreamRoom
215
+ * Note: This tests ONE trigger path of the unified Transform computation
216
+ * Steps: 1) Trigger CreateLivestreamRoom 2) Verify VolcanoEngineStreamURLCall created with correct properties
217
+ * Business Logic: API call entity auto-created when room is created
218
+ */
219
+ // Implementation...
220
+ })
221
+
222
+ test('VolcanoEngineStreamURLCall entity via RetryStreamURLGeneration (Node: VolcanoEngineStreamURLCall@RetryStreamURLGeneration)', async () => {
223
+ /**
224
+ * Test Plan for: VolcanoEngineStreamURLCall@RetryStreamURLGeneration
225
+ * Note: This tests ANOTHER trigger path of the SAME unified Transform computation
226
+ * Steps: 1) Trigger RetryStreamURLGeneration 2) Verify new VolcanoEngineStreamURLCall created
227
+ * Business Logic: New API call created when user retries URL generation
228
+ */
229
+ // Implementation...
230
+ })
231
+
232
+ // For entity with ownerProperties:
233
+ test('Post entity creation with ownerProperties', async () => {
234
+ /**
235
+ * Test Plan for: Post entity (with ownerProperties: [createdAt, status])
236
+ * This tests the entity creation AND all ownerProperties
237
+ * Steps: 1) Create Post via interaction 2) Verify entity exists 3) Verify ALL ownerProperties are set
238
+ * Business Logic: Post creation sets createdAt timestamp and initial status
239
+ */
240
+ // Implementation...
241
+ })
242
+
243
+ // For entity with createdWithRelations:
244
+ test('Order entity with createdWithRelations', async () => {
245
+ /**
246
+ * Test Plan for: Order entity (with createdWithRelations: [OrderItemRelation])
247
+ * Steps: 1) Create Order 2) Verify Order exists 3) Query each relation to verify creation
248
+ * Note: Check relation source/target are correctly linked
249
+ */
250
+ // Implementation...
251
+ })
252
+
155
253
  // For _parent:[parent] computations:
156
254
  test('Post creation through User Transform (_parent:[User])', async () => {
157
255
  /**
@@ -176,7 +274,7 @@ color: blue
176
274
  ```
177
275
 
178
276
  4. **Write Test Implementation**
179
- - Add test to `tests/basic.test.ts` in 'Basic Functionality' describe group
277
+ - Add test to `tests/{module}.business.test.ts` in 'Basic Functionality' describe group
180
278
  - Follow the test plan created above
181
279
  - **📖 Reference the example test files above for testing patterns and structure**
182
280
  - For StateMachine computations, test ALL StateTransfer transitions
@@ -187,6 +285,15 @@ color: blue
187
285
  - NEVER hardcode relation names: `storage.find('UserPostRelation', ...)` ❌
188
286
  - This ensures tests work regardless of whether relation names are manually specified or auto-generated
189
287
 
288
+ **⚠️ Testing Integration Event Entity Computations:**
289
+ - When testing computations based on **Integration Event Entities** (NOT InteractionEventEntity):
290
+ - Use `controller.system.storage.create('${EventEntityName}', {...})` to directly create event records
291
+ - Do NOT use `controller.callInteraction()` - integration events are created by external systems
292
+ - Example: For StripePaymentEvent entity, use `controller.system.storage.create(StripePaymentEvent.name, { transactionId: '...', paymentStatus: 'success', ... })`
293
+ - This simulates the webhook/callback from external systems that creates these events
294
+ - When testing computations based on **InteractionEventEntity**:
295
+ - Use `controller.callInteraction()` as normal - these are triggered by user interactions
296
+
190
297
  Example patterns:
191
298
  ```typescript
192
299
  test('User.status has correct default value', async () => {
@@ -205,6 +312,53 @@ color: blue
205
312
  expect(foundUser.status).toBe('active')
206
313
  })
207
314
 
315
+ test('Post entity with ownerProperties (createdAt, status)', async () => {
316
+ // Create Post - ownerProperties should be set by entity's Transform
317
+ const result = await controller.callInteraction('CreatePost', {
318
+ user: testUser,
319
+ payload: { title: 'Test Post', content: 'Content' }
320
+ })
321
+
322
+ // Verify entity creation AND all ownerProperties
323
+ const post = await system.storage.findOne(
324
+ 'Post',
325
+ MatchExp.atom({ key: 'id', value: ['=', result.data.id] }),
326
+ undefined,
327
+ ['id', 'title', 'createdAt', 'status'] // Include ALL ownerProperties in attributeQuery
328
+ )
329
+
330
+ expect(post.title).toBe('Test Post')
331
+ expect(post.createdAt).toBeGreaterThan(0) // ownerProperty: createdAt
332
+ expect(post.status).toBe('draft') // ownerProperty: status
333
+ })
334
+
335
+ test('Order entity with createdWithRelations (OrderItemRelation)', async () => {
336
+ // Create Order with items - relations created automatically via sourceProperty
337
+ const result = await controller.callInteraction('CreateOrder', {
338
+ user: testUser,
339
+ payload: {
340
+ orderNumber: 'ORD001',
341
+ items: [{ productId: 'P1', quantity: 2 }, { productId: 'P2', quantity: 1 }]
342
+ }
343
+ })
344
+
345
+ // Verify Order exists
346
+ const order = await system.storage.findOne('Order',
347
+ MatchExp.atom({ key: 'id', value: ['=', result.data.id] }),
348
+ undefined, ['id', 'orderNumber']
349
+ )
350
+ expect(order.orderNumber).toBe('ORD001')
351
+
352
+ // Verify ALL relations in createdWithRelations are created
353
+ const relations = await system.storage.find(OrderItemRelation.name,
354
+ MatchExp.atom({ key: 'source.id', value: ['=', order.id] }),
355
+ undefined,
356
+ ['id', ['source', { attributeQuery: ['id'] }], ['target', { attributeQuery: ['id', 'productId'] }]]
357
+ )
358
+ expect(relations.length).toBe(2)
359
+ expect(relations[0].target.productId).toBe('P1')
360
+ })
361
+
208
362
  test('Article.state transitions correctly', async () => {
209
363
  // Create article in draft state
210
364
  const result = await controller.callInteraction('CreateArticle', {
@@ -256,6 +410,40 @@ color: blue
256
410
 
257
411
  expect(relations.length).toBe(1)
258
412
  })
413
+
414
+ // Example: Testing Integration Event Entity computations
415
+ test('Order.paymentStatus computed from StripePaymentEvent', async () => {
416
+ /**
417
+ * Test Plan: Testing computation based on Integration Event Entity
418
+ * This tests computations triggered by external system events (StripePaymentEvent)
419
+ * Use storage.create() to simulate webhook data, NOT callInteraction()
420
+ */
421
+
422
+ // Create an order first (via interaction)
423
+ const orderResult = await controller.callInteraction('CreateOrder', {
424
+ user: testUser,
425
+ payload: { orderNumber: 'ORD001', amount: 100 }
426
+ })
427
+
428
+ // Simulate webhook from payment gateway by directly creating StripePaymentEvent
429
+ // This mimics external system (Stripe/Alipay) sending payment confirmation
430
+ await controller.system.storage.create(StripePaymentEvent.name, {
431
+ transactionId: 'txn_123456',
432
+ paymentStatus: 'success',
433
+ orderId: orderResult.data.id,
434
+ timestamp: Math.floor(Date.now() / 1000)
435
+ })
436
+
437
+ // Verify the computation triggered by StripePaymentEvent updated Order
438
+ const order = await system.storage.findOne(
439
+ 'Order',
440
+ MatchExp.atom({ key: 'id', value: ['=', orderResult.data.id] }),
441
+ undefined,
442
+ ['id', 'paymentStatus']
443
+ )
444
+
445
+ expect(order.paymentStatus).toBe('paid')
446
+ })
259
447
  ```
260
448
 
261
449
  5. **Type Check Test Code**
@@ -264,29 +452,30 @@ color: blue
264
452
  - Do NOT run actual tests until type checking passes
265
453
 
266
454
  6. **Run Test**
267
- - Run full test suite: `npm run test tests/basic.test.ts`
455
+ - Run full test suite: `npm run test tests/{module}.business.test.ts`
268
456
  - Must fix any failures (new tests or regressions) before proceeding
269
457
 
270
458
  **If test fails:**
271
459
  - Review test plan - are dependencies properly set up?
272
- - Verify against `requirements/interaction-matrix.md` and `docs/data-design.json`
460
+ - Verify against `docs/{module}.data-design.json`
273
461
  - Check if test data matches `expandedDependencies`
274
462
  - Common issues: missing dependencies, wrong operation order, incorrect expectations
275
463
 
276
464
  **Error handling:**
277
465
  - After 10 fix attempts, STOP IMMEDIATELY and wait for user guidance
278
- - Create error document in `docs/errors/` with test plan, code, error, and attempts
279
- - Update `lastError` field in computation-implementation-plan.json with error doc path
466
+ - Create error document in `docs/errors/{module}.{error-name}.md` with test plan, code, error, and attempts
467
+ - Update `lastError` field in `docs/{module}.computation-implementation-plan.json` with error doc path
280
468
  - Never skip tests or fake data to pass
281
469
 
282
470
  7. **Document Progress**
283
- - **🔴 CRITICAL: Update `docs/computation-implementation-plan.json` based on test results:**
284
- - **If ALL tests pass** (`npm run test tests/basic.test.ts` shows ALL tests passing):
285
- - Set `"completed": true`
471
+ - **🔴 CRITICAL: Update `docs/{module}.computation-implementation-plan.json` based on test results:**
472
+ - **If ALL tests pass** (`npm run test tests/{module}.business.test.ts` shows ALL tests passing):
473
+ - Set `"completed": true` for the CURRENT node being worked on
286
474
  - Remove `lastError` field if it exists
475
+ - **For split computation nodes**: Mark completed only after the specific interaction path is implemented and tested. The unified Transform implementation happens progressively across nodes, but each node tracks completion of its trigger path.
287
476
  - **If ANY test fails** (including regression tests):
288
477
  - Keep `"completed": false` - the computation is NOT done
289
- - Add/update `lastError` field with path to error document in `docs/errors/`
478
+ - Add/update `lastError` field with path to error document in `docs/errors/{module}.{error-name}.md`
290
479
  - The computation remains incomplete and needs fixing
291
480
 
292
481
  8. **Commit Changes (only if tests pass)**