@rsconcept/rstool 0.10.3 → 1.0.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.
- package/README.md +61 -33
- package/dist/agent-workflow-Gk0Vfnv1.d.ts +64 -0
- package/dist/analysis-LLnPhmGa.d.ts +23 -0
- package/dist/{common-DxLg3eXX.d.ts → common-DHJalS-Q.d.ts} +6 -1
- package/dist/constituenta-DnGR6bnM.d.ts +54 -0
- package/dist/diagnostic-D9yl_mEL.d.ts +19 -0
- package/dist/evaluation-Cns8BFm4.d.ts +31 -0
- package/dist/index.d.ts +11 -11
- package/dist/index.js +1 -2
- package/dist/mappers/model-adapter.d.ts +3 -3
- package/dist/mappers/schema-adapter.d.ts +4 -4
- package/dist/mappers/types.d.ts +6 -2
- package/dist/mappers/types.js +2 -0
- package/dist/mappers/types.js.map +1 -1
- package/dist/{model-value-SFAVj0dw.d.ts → model-value-BbonPzMz.d.ts} +14 -3
- package/dist/models/agent-workflow.d.ts +2 -0
- package/dist/models/agent-workflow.js +1 -0
- package/dist/models/analysis.d.ts +1 -1
- package/dist/models/common.d.ts +1 -1
- package/dist/models/constituenta.d.ts +2 -2
- package/dist/models/diagnostic.d.ts +1 -1
- package/dist/models/evaluation.d.ts +2 -2
- package/dist/models/index.d.ts +11 -11
- package/dist/models/index.js +2 -2
- package/dist/models/model-value.d.ts +2 -2
- package/dist/models/rstool-agent.d.ts +1 -1
- package/dist/models/rstool-agent.js +1 -1
- package/dist/models/session.d.ts +1 -1
- package/dist/models/tool-contract.d.ts +2 -2
- package/dist/models/tool-contract.js +2 -1
- package/dist/models/tool-contract.js.map +1 -1
- package/dist/models-Bw6Uum8i.js +685 -0
- package/dist/models-Bw6Uum8i.js.map +1 -0
- package/dist/rstool-agent-D2cQze_b.d.ts +71 -0
- package/dist/session/session-store.d.ts +18 -5
- package/dist/session/session-store.js +1 -64
- package/dist/{session-BPgsE80c.d.ts → session-ChexW8i7.d.ts} +11 -8
- package/dist/tool-contract-0uRGhEfW.d.ts +164 -0
- package/dist/wrapper/client.d.ts +23 -0
- package/dist/wrapper/client.js +17 -0
- package/dist/wrapper/client.js.map +1 -1
- package/dist/wrapper/stdio-wrapper.js +75 -63
- package/dist/wrapper/stdio-wrapper.js.map +1 -1
- package/docs/CONSTITUENTA.md +2 -2
- package/docs/DIAGNOSTICS.md +6 -5
- package/docs/MODEL-TESTING.md +3 -3
- package/docs/PORTAL-API.md +24 -18
- package/examples/README.md +1 -1
- package/examples/agent-client.ts +11 -41
- package/examples/build-chocolate-nim-rsform.ts +21 -70
- package/examples/chocolate-nim/build-rsform.ts +23 -18
- package/examples/chocolate-nim/build-rsmodel.ts +10 -12
- package/examples/chocolate-nim/rsform-session.json +290 -290
- package/examples/chocolate-nim/rsmodel-session.json +291 -291
- package/examples/expression-bank/bank-constituents.ts +304 -53
- package/examples/expression-bank/build-rsform.ts +19 -16
- package/examples/expression-bank/rsform-session.json +1551 -1551
- package/examples/kinship/build-rsform.ts +23 -18
- package/examples/kinship/build-rsmodel.ts +16 -16
- package/examples/kinship/rsform-session.json +219 -219
- package/examples/kinship/rsmodel-session.json +221 -221
- package/examples/kinship/session.ts +19 -21
- package/examples/movd/build-rsform.ts +23 -18
- package/examples/movd/build-rsmodel.ts +18 -20
- package/examples/movd/rsform-session.json +262 -262
- package/examples/movd/rsmodel-session.json +264 -264
- package/examples/sample/build-rsform.ts +18 -51
- package/examples/sample/build-rsmodel.ts +25 -44
- package/examples/sample/rsform-session.json +10 -7
- package/examples/sample/rsmodel-session.json +36 -33
- package/examples/template-apply/build-rsform.ts +27 -24
- package/examples/template-apply/rsform-session.json +48 -48
- package/package.json +4 -2
- package/skills/rstool-helper/EXAMPLES.md +44 -116
- package/skills/rstool-helper/GUIDE.md +40 -25
- package/skills/rstool-helper/REFERENCE.md +40 -177
- package/src/index.ts +24 -17
- package/src/mappers/portal-adapter.ts +49 -0
- package/src/mappers/types.ts +4 -0
- package/src/models/agent-workflow.ts +66 -0
- package/src/models/analysis.ts +7 -0
- package/src/models/common.ts +7 -0
- package/src/models/constituenta.ts +24 -6
- package/src/models/diagnostic.ts +4 -0
- package/src/models/evaluation.ts +11 -0
- package/src/models/import-detect.test.ts +66 -0
- package/src/models/import-detect.ts +42 -0
- package/src/models/import-export.ts +24 -0
- package/src/models/index.ts +22 -14
- package/src/models/model-value.ts +12 -0
- package/src/models/portal-json.test.ts +38 -0
- package/src/models/portal-json.ts +54 -1
- package/src/models/rstool-agent.test.ts +698 -146
- package/src/models/rstool-agent.ts +392 -92
- package/src/models/session.ts +8 -5
- package/src/models/tool-contract.ts +81 -42
- package/src/session/batch-apply.test.ts +123 -0
- package/src/session/batch-apply.ts +82 -0
- package/src/session/persistence.test.ts +63 -0
- package/src/session/persistence.ts +69 -0
- package/src/session/session-store.ts +76 -6
- package/src/wrapper/client.test.ts +58 -0
- package/src/wrapper/client.ts +23 -0
- package/src/wrapper/stdio-handler.test.ts +101 -0
- package/src/wrapper/stdio-handler.ts +195 -0
- package/src/wrapper/stdio-wrapper.ts +4 -187
- package/dist/analysis-JiwOYDKx.d.ts +0 -16
- package/dist/constituenta-Dnd6iToB.d.ts +0 -36
- package/dist/diagnostic-BMYvciz8.d.ts +0 -15
- package/dist/evaluation-CCVYH0wA.d.ts +0 -21
- package/dist/index-uhkmwruf.d.ts +0 -46
- package/dist/rstool-agent-BZi5jO1y.js +0 -158
- package/dist/rstool-agent-BZi5jO1y.js.map +0 -1
- package/dist/rstool-agent-pRaPnZay.d.ts +0 -35
- package/dist/session/session-store.js.map +0 -1
- package/dist/tool-contract-n1ghUOrK.d.ts +0 -32
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdtempSync, rmSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
1
5
|
import { describe, expect, it } from 'vitest';
|
|
2
6
|
|
|
7
|
+
import { TUPLE_ID } from '@rsconcept/domain';
|
|
8
|
+
|
|
3
9
|
import { CstType, EvalStatus, RSErrorCode, RSToolAgent } from './index';
|
|
4
10
|
|
|
5
11
|
function buildSampleForm(tool: RSToolAgent, sessionId: string) {
|
|
6
|
-
tool.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
tool.applySchemaPatch(
|
|
13
|
+
{
|
|
14
|
+
items: [{ alias: 'X1' }, { alias: 'D1', definitionFormal: '1+2' }, { alias: 'A1', definitionFormal: '1=1' }]
|
|
15
|
+
},
|
|
16
|
+
sessionId
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function fullState(tool: RSToolAgent, sessionId?: string) {
|
|
21
|
+
return tool.getSessionState('full', sessionId) as import('./session').SessionState;
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
describe('RSToolAgent', () => {
|
|
@@ -26,10 +33,7 @@ describe('RSToolAgent', () => {
|
|
|
26
33
|
it('analyzes a valid expression', () => {
|
|
27
34
|
const tool = new RSToolAgent();
|
|
28
35
|
const session = tool.createSession();
|
|
29
|
-
const analysis = tool.analyzeExpression(session.sessionId
|
|
30
|
-
expression: '1+2',
|
|
31
|
-
cstType: CstType.TERM
|
|
32
|
-
});
|
|
36
|
+
const analysis = tool.analyzeExpression({ expression: '1+2', cstType: CstType.TERM }, session.sessionId);
|
|
33
37
|
expect(analysis.success).toBe(true);
|
|
34
38
|
expect(analysis.diagnostics.length).toBe(0);
|
|
35
39
|
});
|
|
@@ -37,10 +41,7 @@ describe('RSToolAgent', () => {
|
|
|
37
41
|
it('returns syntax diagnostics for invalid expression', () => {
|
|
38
42
|
const tool = new RSToolAgent();
|
|
39
43
|
const session = tool.createSession();
|
|
40
|
-
const analysis = tool.analyzeExpression(session.sessionId
|
|
41
|
-
expression: '(',
|
|
42
|
-
cstType: CstType.TERM
|
|
43
|
-
});
|
|
44
|
+
const analysis = tool.analyzeExpression({ expression: '(', cstType: CstType.TERM }, session.sessionId);
|
|
44
45
|
expect(analysis.success).toBe(false);
|
|
45
46
|
expect(analysis.diagnostics.length).toBeGreaterThan(0);
|
|
46
47
|
});
|
|
@@ -48,68 +49,64 @@ describe('RSToolAgent', () => {
|
|
|
48
49
|
it('rejects formal definition for constants', () => {
|
|
49
50
|
const tool = new RSToolAgent();
|
|
50
51
|
const session = tool.createSession();
|
|
51
|
-
const result = tool.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
expect(result.state.analysis.success).toBe(false);
|
|
60
|
-
expect(result.diagnostics[0]?.error.code).toBe(RSErrorCode.definitionNotAllowed);
|
|
52
|
+
const result = tool.applySchemaPatch(
|
|
53
|
+
{
|
|
54
|
+
items: [{ alias: 'C1', cstType: CstType.CONSTANT, definitionFormal: 'X1' }]
|
|
55
|
+
},
|
|
56
|
+
session.sessionId
|
|
57
|
+
);
|
|
58
|
+
expect(result.success).toBe(false);
|
|
59
|
+
expect(result.failed[0]?.diagnostics[0]?.error.code).toBe(RSErrorCode.definitionNotAllowed);
|
|
61
60
|
});
|
|
62
61
|
|
|
63
62
|
it('rejects formal definition for basic sets', () => {
|
|
64
63
|
const tool = new RSToolAgent();
|
|
65
64
|
const session = tool.createSession();
|
|
66
|
-
const result = tool.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
expect(result.state.analysis.success).toBe(false);
|
|
75
|
-
expect(result.diagnostics[0]?.error.code).toBe(RSErrorCode.definitionNotAllowed);
|
|
65
|
+
const result = tool.applySchemaPatch(
|
|
66
|
+
{
|
|
67
|
+
items: [{ alias: 'X1', cstType: CstType.BASE, definitionFormal: 'Z' }]
|
|
68
|
+
},
|
|
69
|
+
session.sessionId
|
|
70
|
+
);
|
|
71
|
+
expect(result.success).toBe(false);
|
|
72
|
+
expect(result.failed[0]?.diagnostics[0]?.error.code).toBe(RSErrorCode.definitionNotAllowed);
|
|
76
73
|
});
|
|
77
74
|
|
|
78
75
|
it('returns known analysis for empty base definition', () => {
|
|
79
76
|
const tool = new RSToolAgent();
|
|
80
77
|
const session = tool.createSession();
|
|
81
|
-
const result = tool.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
expect(
|
|
90
|
-
expect(
|
|
91
|
-
expect(result.state.analysis.valueClass).toBe('value');
|
|
78
|
+
const result = tool.applySchemaPatch(
|
|
79
|
+
{
|
|
80
|
+
items: [{ alias: 'X1', cstType: CstType.BASE, definitionFormal: '' }]
|
|
81
|
+
},
|
|
82
|
+
session.sessionId
|
|
83
|
+
);
|
|
84
|
+
expect(result.success).toBe(true);
|
|
85
|
+
const state = fullState(tool, session.sessionId);
|
|
86
|
+
expect(state.items[0]?.analysis.type).not.toBeNull();
|
|
87
|
+
expect(state.items[0]?.analysis.valueClass).toBe('value');
|
|
92
88
|
});
|
|
93
89
|
|
|
94
90
|
it('persists term, definitionText, and convention in session state', () => {
|
|
95
91
|
const tool = new RSToolAgent();
|
|
96
92
|
const session = tool.createSession();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
93
|
+
tool.applySchemaPatch(
|
|
94
|
+
{
|
|
95
|
+
items: [
|
|
96
|
+
{
|
|
97
|
+
alias: 'D2',
|
|
98
|
+
cstType: CstType.TERM,
|
|
99
|
+
definitionFormal: '1',
|
|
100
|
+
term: 'natural number',
|
|
101
|
+
definitionText: 'A positive integer',
|
|
102
|
+
convention: 'Standard arithmetic'
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
session.sessionId
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const form = fullState(tool, session.sessionId);
|
|
113
110
|
expect(form.items[0]).toMatchObject({
|
|
114
111
|
term: 'natural number',
|
|
115
112
|
definitionText: 'A positive integer',
|
|
@@ -117,8 +114,8 @@ describe('RSToolAgent', () => {
|
|
|
117
114
|
});
|
|
118
115
|
|
|
119
116
|
const exported = tool.exportSession(session.sessionId);
|
|
120
|
-
const imported = tool.
|
|
121
|
-
const restored = tool
|
|
117
|
+
const imported = tool.importData(exported, 'session');
|
|
118
|
+
const restored = fullState(tool, imported.sessionId);
|
|
122
119
|
expect(restored.items[0]).toMatchObject({
|
|
123
120
|
term: 'natural number',
|
|
124
121
|
definitionText: 'A positive integer',
|
|
@@ -134,13 +131,13 @@ describe('RSToolAgent', () => {
|
|
|
134
131
|
comment: 'Example schema'
|
|
135
132
|
});
|
|
136
133
|
|
|
137
|
-
expect(tool
|
|
134
|
+
expect(fullState(tool, session.sessionId)).toMatchObject({
|
|
138
135
|
alias: 'KIN',
|
|
139
136
|
title: 'Kinship',
|
|
140
137
|
comment: 'Example schema'
|
|
141
138
|
});
|
|
142
139
|
|
|
143
|
-
const exported = JSON.parse(tool.
|
|
140
|
+
const exported = JSON.parse(tool.exportPortal({ kind: 'schema' }, session.sessionId) as string) as {
|
|
144
141
|
title: string;
|
|
145
142
|
alias: string;
|
|
146
143
|
description: string;
|
|
@@ -155,19 +152,23 @@ describe('RSToolAgent', () => {
|
|
|
155
152
|
it('exports schema data for Portal JSON import', () => {
|
|
156
153
|
const tool = new RSToolAgent();
|
|
157
154
|
const session = tool.createSession();
|
|
158
|
-
tool.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
155
|
+
tool.applySchemaPatch(
|
|
156
|
+
{
|
|
157
|
+
items: [
|
|
158
|
+
{
|
|
159
|
+
alias: 'D2',
|
|
160
|
+
cstType: CstType.TERM,
|
|
161
|
+
definitionFormal: '1',
|
|
162
|
+
term: 'natural number',
|
|
163
|
+
definitionText: 'A positive integer',
|
|
164
|
+
convention: 'Standard arithmetic'
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
session.sessionId
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const exported = JSON.parse(tool.exportPortal({ kind: 'schema' }, session.sessionId) as string) as {
|
|
171
172
|
contract_version: string;
|
|
172
173
|
title: string;
|
|
173
174
|
alias: string;
|
|
@@ -182,7 +183,6 @@ describe('RSToolAgent', () => {
|
|
|
182
183
|
expect(exported.description).toBe('');
|
|
183
184
|
|
|
184
185
|
expect(exported.items[0]).toMatchObject({
|
|
185
|
-
id: 15,
|
|
186
186
|
alias: 'D2',
|
|
187
187
|
cst_type: CstType.TERM,
|
|
188
188
|
definition_formal: '1',
|
|
@@ -198,12 +198,9 @@ describe('RSToolAgent', () => {
|
|
|
198
198
|
const tool = new RSToolAgent();
|
|
199
199
|
const session = tool.createSession();
|
|
200
200
|
buildSampleForm(tool, session.sessionId);
|
|
201
|
-
await tool.
|
|
202
|
-
target: 1,
|
|
203
|
-
value: { 1: 'Alice' }
|
|
204
|
-
});
|
|
201
|
+
await tool.setModelValues({ set: [{ target: 1, value: { 1: 'Alice' } }] }, session.sessionId);
|
|
205
202
|
|
|
206
|
-
const exported = JSON.parse(tool.
|
|
203
|
+
const exported = JSON.parse(tool.exportPortal({ kind: 'model' }, session.sessionId) as string) as {
|
|
207
204
|
contract_version: string;
|
|
208
205
|
title: string;
|
|
209
206
|
alias: string;
|
|
@@ -227,33 +224,31 @@ describe('RSToolAgent', () => {
|
|
|
227
224
|
it('defaults missing text fields to empty strings', () => {
|
|
228
225
|
const tool = new RSToolAgent();
|
|
229
226
|
const session = tool.createSession();
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
expect(
|
|
239
|
-
expect(
|
|
240
|
-
expect(result.state.convention).toBe('');
|
|
227
|
+
tool.applySchemaPatch(
|
|
228
|
+
{
|
|
229
|
+
items: [{ alias: 'D3', cstType: CstType.TERM, definitionFormal: '2' }]
|
|
230
|
+
},
|
|
231
|
+
session.sessionId
|
|
232
|
+
);
|
|
233
|
+
const state = fullState(tool, session.sessionId);
|
|
234
|
+
expect(state.items[0]?.term).toBe('');
|
|
235
|
+
expect(state.items[0]?.definitionText).toBe('');
|
|
236
|
+
expect(state.items[0]?.convention).toBe('');
|
|
241
237
|
});
|
|
242
238
|
|
|
243
239
|
it('returns known analysis for empty constant definition', () => {
|
|
244
240
|
const tool = new RSToolAgent();
|
|
245
241
|
const session = tool.createSession();
|
|
246
|
-
const result = tool.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
expect(
|
|
255
|
-
expect(
|
|
256
|
-
expect(result.state.analysis.valueClass).toBe('value');
|
|
242
|
+
const result = tool.applySchemaPatch(
|
|
243
|
+
{
|
|
244
|
+
items: [{ alias: 'C1', cstType: CstType.CONSTANT, definitionFormal: '' }]
|
|
245
|
+
},
|
|
246
|
+
session.sessionId
|
|
247
|
+
);
|
|
248
|
+
expect(result.success).toBe(true);
|
|
249
|
+
const state = fullState(tool, session.sessionId);
|
|
250
|
+
expect(state.items[0]?.analysis.type).not.toBeNull();
|
|
251
|
+
expect(state.items[0]?.analysis.valueClass).toBe('value');
|
|
257
252
|
});
|
|
258
253
|
});
|
|
259
254
|
|
|
@@ -270,10 +265,10 @@ describe('RSToolAgent modeling and evaluation', () => {
|
|
|
270
265
|
const session = tool.createSession();
|
|
271
266
|
buildSampleForm(tool, session.sessionId);
|
|
272
267
|
|
|
273
|
-
const model = await tool.
|
|
274
|
-
target: 1,
|
|
275
|
-
|
|
276
|
-
|
|
268
|
+
const model = await tool.setModelValues(
|
|
269
|
+
{ set: [{ target: 1, value: { 0: 'zero', 1: 'one' } }] },
|
|
270
|
+
session.sessionId
|
|
271
|
+
);
|
|
277
272
|
expect(model.items).toHaveLength(1);
|
|
278
273
|
expect(model.items[0]).toMatchObject({
|
|
279
274
|
id: 1,
|
|
@@ -287,12 +282,40 @@ describe('RSToolAgent modeling and evaluation', () => {
|
|
|
287
282
|
const session = tool.createSession();
|
|
288
283
|
buildSampleForm(tool, session.sessionId);
|
|
289
284
|
|
|
285
|
+
await expect(tool.setModelValues({ set: [{ target: 2, value: 3 }] }, session.sessionId)).rejects.toThrow(
|
|
286
|
+
/inferrable/
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('restores session state when setModelValues fails mid-batch', async () => {
|
|
291
|
+
const tool = new RSToolAgent();
|
|
292
|
+
const session = tool.createSession();
|
|
293
|
+
buildSampleForm(tool, session.sessionId);
|
|
294
|
+
await tool.setModelValues({ set: [{ target: 1, value: { 0: 'a' } }] }, session.sessionId);
|
|
295
|
+
|
|
290
296
|
await expect(
|
|
291
|
-
tool.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
297
|
+
tool.setModelValues(
|
|
298
|
+
{
|
|
299
|
+
set: [
|
|
300
|
+
{ target: 1, value: { 0: 'b' } },
|
|
301
|
+
{ target: 999, value: { 0: 'x' } }
|
|
302
|
+
]
|
|
303
|
+
},
|
|
304
|
+
session.sessionId
|
|
305
|
+
)
|
|
306
|
+
).rejects.toThrow(/Unknown constituent/);
|
|
307
|
+
|
|
308
|
+
expect(tool.getModelState(session.sessionId).items).toEqual([
|
|
309
|
+
expect.objectContaining({ id: 1, value: { 0: 'a' } })
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
await expect(
|
|
313
|
+
tool.setModelValues({ clear: [1], set: [{ target: 999, value: { 0: 'x' } }] }, session.sessionId)
|
|
314
|
+
).rejects.toThrow(/Unknown constituent/);
|
|
315
|
+
|
|
316
|
+
expect(tool.getModelState(session.sessionId).items).toEqual([
|
|
317
|
+
expect.objectContaining({ id: 1, value: { 0: 'a' } })
|
|
318
|
+
]);
|
|
296
319
|
});
|
|
297
320
|
|
|
298
321
|
it('evaluates expression against session context', () => {
|
|
@@ -300,10 +323,7 @@ describe('RSToolAgent modeling and evaluation', () => {
|
|
|
300
323
|
const session = tool.createSession();
|
|
301
324
|
buildSampleForm(tool, session.sessionId);
|
|
302
325
|
|
|
303
|
-
const result = tool.
|
|
304
|
-
expression: '1+2',
|
|
305
|
-
cstType: CstType.TERM
|
|
306
|
-
});
|
|
326
|
+
const result = tool.evaluate({ expression: '1+2', cstType: CstType.TERM }, session.sessionId);
|
|
307
327
|
expect(result.success).toBe(true);
|
|
308
328
|
expect(result.value).toBe(3);
|
|
309
329
|
expect(result.status).toBe(EvalStatus.HAS_DATA);
|
|
@@ -315,7 +335,7 @@ describe('RSToolAgent modeling and evaluation', () => {
|
|
|
315
335
|
const session = tool.createSession();
|
|
316
336
|
buildSampleForm(tool, session.sessionId);
|
|
317
337
|
|
|
318
|
-
const result = tool.
|
|
338
|
+
const result = tool.evaluate({ constituentId: 2 }, session.sessionId);
|
|
319
339
|
expect(result.success).toBe(true);
|
|
320
340
|
expect(result.value).toBe(3);
|
|
321
341
|
expect(result.status).toBe(EvalStatus.HAS_DATA);
|
|
@@ -326,7 +346,7 @@ describe('RSToolAgent modeling and evaluation', () => {
|
|
|
326
346
|
const session = tool.createSession();
|
|
327
347
|
buildSampleForm(tool, session.sessionId);
|
|
328
348
|
|
|
329
|
-
const result = tool.
|
|
349
|
+
const result = tool.evaluate({ constituentId: 3 }, session.sessionId);
|
|
330
350
|
expect(result.success).toBe(true);
|
|
331
351
|
expect(result.value).toBe(1);
|
|
332
352
|
});
|
|
@@ -349,31 +369,31 @@ describe('RSToolAgent modeling and evaluation', () => {
|
|
|
349
369
|
const tool = new RSToolAgent();
|
|
350
370
|
const session = tool.createSession();
|
|
351
371
|
buildSampleForm(tool, session.sessionId);
|
|
352
|
-
await tool.
|
|
353
|
-
target: 1,
|
|
354
|
-
value: { 0: 'a' }
|
|
355
|
-
});
|
|
372
|
+
await tool.setModelValues({ set: [{ target: 1, value: { 0: 'a' } }] }, session.sessionId);
|
|
356
373
|
|
|
357
|
-
const model = await tool.
|
|
374
|
+
const model = await tool.setModelValues({ clear: [1] }, session.sessionId);
|
|
358
375
|
expect(model.items).toHaveLength(0);
|
|
359
376
|
});
|
|
360
377
|
|
|
361
378
|
it('batch sets model values', async () => {
|
|
362
379
|
const tool = new RSToolAgent();
|
|
363
380
|
const session = tool.createSession();
|
|
364
|
-
tool.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
381
|
+
tool.applySchemaPatch(
|
|
382
|
+
{
|
|
383
|
+
items: [{ alias: 'X1' }, { alias: 'C1', cstType: CstType.CONSTANT, definitionFormal: '' }]
|
|
384
|
+
},
|
|
385
|
+
session.sessionId
|
|
386
|
+
);
|
|
370
387
|
|
|
371
|
-
const model = await tool.
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
388
|
+
const model = await tool.setModelValues(
|
|
389
|
+
{
|
|
390
|
+
set: [
|
|
391
|
+
{ target: 1, value: { 0: 'a', 1: 'b' } },
|
|
392
|
+
{ target: 2, value: { 0: 'c' } }
|
|
393
|
+
]
|
|
394
|
+
},
|
|
395
|
+
session.sessionId
|
|
396
|
+
);
|
|
377
397
|
expect(model.items).toHaveLength(2);
|
|
378
398
|
});
|
|
379
399
|
|
|
@@ -381,18 +401,550 @@ describe('RSToolAgent modeling and evaluation', () => {
|
|
|
381
401
|
const tool = new RSToolAgent();
|
|
382
402
|
const session = tool.createSession();
|
|
383
403
|
buildSampleForm(tool, session.sessionId);
|
|
384
|
-
await tool.
|
|
385
|
-
target: 1,
|
|
386
|
-
value: { 0: 'zero' }
|
|
387
|
-
});
|
|
404
|
+
await tool.setModelValues({ set: [{ target: 1, value: { 0: 'zero' } }] }, session.sessionId);
|
|
388
405
|
|
|
389
406
|
const exported = tool.exportSession(session.sessionId);
|
|
390
407
|
expect(exported).toContain('"model"');
|
|
391
408
|
|
|
392
409
|
const newTool = new RSToolAgent();
|
|
393
|
-
const imported = newTool.
|
|
410
|
+
const imported = newTool.importData(exported, 'session');
|
|
394
411
|
const model = newTool.getModelState(imported.sessionId);
|
|
395
412
|
expect(model.items).toHaveLength(1);
|
|
396
413
|
expect(model.items[0]?.value).toEqual({ 0: 'zero' });
|
|
397
414
|
});
|
|
398
415
|
});
|
|
416
|
+
|
|
417
|
+
describe('RSToolAgent agent ergonomics', () => {
|
|
418
|
+
it('tracks current session and allows omitting sessionId', () => {
|
|
419
|
+
const tool = new RSToolAgent();
|
|
420
|
+
const session = tool.createSession({ title: 'Active' });
|
|
421
|
+
expect(tool.getCurrentSession()?.sessionId).toBe(session.sessionId);
|
|
422
|
+
expect(fullState(tool).title).toBe('Active');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('auto-creates a session when sessionId is omitted', () => {
|
|
426
|
+
const tool = new RSToolAgent();
|
|
427
|
+
const result = tool.applySchemaPatch({
|
|
428
|
+
items: [{ alias: 'X1' }]
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
expect(result.success).toBe(true);
|
|
432
|
+
expect(tool.getCurrentSession()).not.toBeNull();
|
|
433
|
+
expect(fullState(tool).items).toHaveLength(1);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('applies agent schema patches with inferred ids and cstType', () => {
|
|
437
|
+
const tool = new RSToolAgent();
|
|
438
|
+
|
|
439
|
+
const result = tool.applySchemaPatch({
|
|
440
|
+
initial: { title: 'Agent patch' },
|
|
441
|
+
commitMessage: 'initial schema',
|
|
442
|
+
items: [
|
|
443
|
+
{ alias: 'D1', definitionFormal: 'Pr1(S1)' },
|
|
444
|
+
{ alias: 'X1' },
|
|
445
|
+
{ alias: 'S1', definitionFormal: 'ℬ(X1×X1)' }
|
|
446
|
+
]
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
expect(result.success).toBe(true);
|
|
450
|
+
expect(result.summary.title).toBe('Agent patch');
|
|
451
|
+
expect(result.summary.itemCount).toBe(3);
|
|
452
|
+
expect(result.revision?.message).toBe('initial schema');
|
|
453
|
+
expect(fullState(tool).items.map(item => item.alias)).toEqual(['D1', 'X1', 'S1']);
|
|
454
|
+
expect(fullState(tool).items.map(item => item.cstType)).toEqual([CstType.TERM, CstType.BASE, CstType.STRUCTURED]);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('exports Portal payloads as structured objects', () => {
|
|
458
|
+
const tool = new RSToolAgent();
|
|
459
|
+
tool.applySchemaPatch({
|
|
460
|
+
items: [{ alias: 'D1', definitionFormal: '1+2' }]
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const schema = tool.exportPortal({ kind: 'schema', format: 'object' });
|
|
464
|
+
expect(schema).toMatchObject({ items: [{ alias: 'D1', cst_type: CstType.TERM }] });
|
|
465
|
+
expect(JSON.parse(tool.exportPortal({ kind: 'schema' }) as string).items[0]).toMatchObject(
|
|
466
|
+
(schema as { items: unknown[] }).items[0] as object
|
|
467
|
+
);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('replaces active diagnostics per constituent on upsert', () => {
|
|
471
|
+
const tool = new RSToolAgent();
|
|
472
|
+
const session = tool.createSession();
|
|
473
|
+
tool.applySchemaPatch(
|
|
474
|
+
{ mode: 'best_effort', items: [{ alias: 'X1', cstType: CstType.BASE, definitionFormal: 'Z' }] },
|
|
475
|
+
session.sessionId
|
|
476
|
+
);
|
|
477
|
+
expect(tool.listDiagnostics(undefined, session.sessionId)).toHaveLength(1);
|
|
478
|
+
|
|
479
|
+
tool.applySchemaPatch({ items: [{ alias: 'X1', cstType: CstType.BASE, definitionFormal: '' }] }, session.sessionId);
|
|
480
|
+
expect(tool.listDiagnostics(undefined, session.sessionId)).toHaveLength(0);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('does not record analyzeExpression diagnostics by default', () => {
|
|
484
|
+
const tool = new RSToolAgent();
|
|
485
|
+
const session = tool.createSession();
|
|
486
|
+
tool.analyzeExpression({ expression: '(', cstType: CstType.TERM }, session.sessionId);
|
|
487
|
+
expect(tool.listDiagnostics(undefined, session.sessionId)).toHaveLength(0);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('records analyzeExpression diagnostics when requested', () => {
|
|
491
|
+
const tool = new RSToolAgent();
|
|
492
|
+
const session = tool.createSession();
|
|
493
|
+
tool.analyzeExpression({ expression: '(', cstType: CstType.TERM, recordDiagnostics: true }, session.sessionId);
|
|
494
|
+
expect(tool.listDiagnostics(undefined, session.sessionId).length).toBeGreaterThan(0);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('applySchemaPatch rolls back in atomic mode', () => {
|
|
498
|
+
const tool = new RSToolAgent();
|
|
499
|
+
const session = tool.createSession();
|
|
500
|
+
tool.applySchemaPatch({ items: [{ alias: 'X1' }] }, session.sessionId);
|
|
501
|
+
|
|
502
|
+
const result = tool.applySchemaPatch(
|
|
503
|
+
{
|
|
504
|
+
mode: 'atomic',
|
|
505
|
+
items: [
|
|
506
|
+
{ alias: 'D1', definitionFormal: '1+2' },
|
|
507
|
+
{ alias: 'D2', definitionFormal: 'Pr1(MISSING)' }
|
|
508
|
+
]
|
|
509
|
+
},
|
|
510
|
+
session.sessionId
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
expect(result.success).toBe(false);
|
|
514
|
+
expect(result.applied).toHaveLength(0);
|
|
515
|
+
expect(fullState(tool, session.sessionId).items).toHaveLength(1);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('applySchemaPatch applies valid drafts in best_effort mode', () => {
|
|
519
|
+
const tool = new RSToolAgent();
|
|
520
|
+
const session = tool.createSession();
|
|
521
|
+
tool.applySchemaPatch(
|
|
522
|
+
{
|
|
523
|
+
items: [{ alias: 'X1' }, { alias: 'S1', definitionFormal: 'ℬ(X1×X1)' }]
|
|
524
|
+
},
|
|
525
|
+
session.sessionId
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
const result = tool.applySchemaPatch(
|
|
529
|
+
{
|
|
530
|
+
mode: 'best_effort',
|
|
531
|
+
items: [
|
|
532
|
+
{ alias: 'D1', definitionFormal: 'Pr1(S1)' },
|
|
533
|
+
{ alias: 'D2', definitionFormal: 'Pr1(MISSING)' }
|
|
534
|
+
]
|
|
535
|
+
},
|
|
536
|
+
session.sessionId
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
expect(result.applied).toHaveLength(1);
|
|
540
|
+
expect(result.failed).toHaveLength(1);
|
|
541
|
+
expect(fullState(tool, session.sessionId).items.map(item => item.alias)).toContain('D1');
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('imports portal details JSON', () => {
|
|
545
|
+
const tool = new RSToolAgent();
|
|
546
|
+
const payload = {
|
|
547
|
+
title: 'Kinship',
|
|
548
|
+
alias: 'KIN',
|
|
549
|
+
description: 'Example',
|
|
550
|
+
items: [
|
|
551
|
+
{ id: 1, alias: 'X1', cst_type: CstType.BASE, definition_formal: '' },
|
|
552
|
+
{ id: 2, alias: 'D1', cst_type: CstType.TERM, definition_formal: '1+2' }
|
|
553
|
+
]
|
|
554
|
+
};
|
|
555
|
+
const session = tool.importData(payload, 'portal-details');
|
|
556
|
+
const state = fullState(tool, session.sessionId);
|
|
557
|
+
expect(state.title).toBe('Kinship');
|
|
558
|
+
expect(state.items).toHaveLength(2);
|
|
559
|
+
expect(state.items.map(item => item.alias).sort()).toEqual(['D1', 'X1']);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('auto-detects portal details import kind', () => {
|
|
563
|
+
const tool = new RSToolAgent();
|
|
564
|
+
const session = tool.importData({
|
|
565
|
+
title: 'Auto',
|
|
566
|
+
items: [{ id: 1, alias: 'X1', cst_type: CstType.BASE, definition_formal: '' }]
|
|
567
|
+
});
|
|
568
|
+
expect(fullState(tool, session.sessionId).title).toBe('Auto');
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('persists sessions across agent restarts', () => {
|
|
572
|
+
const dir = mkdtempSync(join(tmpdir(), 'rstool-test-sessions-'));
|
|
573
|
+
try {
|
|
574
|
+
const tool = new RSToolAgent({ persistenceDir: dir });
|
|
575
|
+
const session = tool.createSession({ title: 'Persisted' });
|
|
576
|
+
tool.applySchemaPatch({ items: [{ alias: 'X1' }] }, session.sessionId);
|
|
577
|
+
|
|
578
|
+
const restored = new RSToolAgent({ persistenceDir: dir });
|
|
579
|
+
expect(restored.getCurrentSession()?.sessionId).toBe(session.sessionId);
|
|
580
|
+
expect(fullState(restored, session.sessionId).items).toHaveLength(1);
|
|
581
|
+
} finally {
|
|
582
|
+
rmSync(dir, { recursive: true, force: true });
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
describe('RSToolAgent session management', () => {
|
|
588
|
+
it('ensureSession reuses the current session', () => {
|
|
589
|
+
const tool = new RSToolAgent();
|
|
590
|
+
const session = tool.createSession({ title: 'First' });
|
|
591
|
+
const ensured = tool.ensureSession({ title: 'Ignored' });
|
|
592
|
+
expect(ensured.sessionId).toBe(session.sessionId);
|
|
593
|
+
expect(fullState(tool).title).toBe('First');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('ensureSession creates a session when none is active', () => {
|
|
597
|
+
const tool = new RSToolAgent();
|
|
598
|
+
expect(tool.getCurrentSession()).toBeNull();
|
|
599
|
+
const ensured = tool.ensureSession({ title: 'Created' });
|
|
600
|
+
expect(ensured.sessionId).toBeTruthy();
|
|
601
|
+
expect(fullState(tool).title).toBe('Created');
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('switches current session with setCurrentSession', () => {
|
|
605
|
+
const tool = new RSToolAgent();
|
|
606
|
+
const first = tool.createSession({ title: 'First' });
|
|
607
|
+
const second = tool.createSession({ title: 'Second' });
|
|
608
|
+
tool.setCurrentSession(first.sessionId);
|
|
609
|
+
expect(fullState(tool).title).toBe('First');
|
|
610
|
+
tool.setCurrentSession(second.sessionId);
|
|
611
|
+
expect(fullState(tool).title).toBe('Second');
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it('throws when setCurrentSession receives an unknown id', () => {
|
|
615
|
+
const tool = new RSToolAgent();
|
|
616
|
+
const unknownId = randomUUID();
|
|
617
|
+
expect(() => tool.setCurrentSession(unknownId)).toThrow(/Unknown session/);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('throws when an explicit unknown sessionId is passed to API methods', () => {
|
|
621
|
+
const tool = new RSToolAgent();
|
|
622
|
+
const unknownId = randomUUID();
|
|
623
|
+
expect(() => tool.getSessionState('full', unknownId)).toThrow(/Unknown session/);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('applySchemaPatch with explicit sessionId does not switch current session', () => {
|
|
627
|
+
const tool = new RSToolAgent();
|
|
628
|
+
const first = tool.createSession({ title: 'First' });
|
|
629
|
+
const second = tool.createSession({ title: 'Second' });
|
|
630
|
+
tool.setCurrentSession(first.sessionId);
|
|
631
|
+
|
|
632
|
+
tool.applySchemaPatch({ items: [{ alias: 'X1' }] }, second.sessionId);
|
|
633
|
+
|
|
634
|
+
expect(tool.getCurrentSession()?.sessionId).toBe(first.sessionId);
|
|
635
|
+
expect(tool.getSessionState('summary', second.sessionId)).toMatchObject({ itemCount: 1 });
|
|
636
|
+
expect(tool.getSessionState('summary', first.sessionId)).toMatchObject({ itemCount: 0 });
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('returns summary session state by default', () => {
|
|
640
|
+
const tool = new RSToolAgent();
|
|
641
|
+
const session = tool.createSession({ title: 'Summary', alias: 'SUM' });
|
|
642
|
+
tool.applySchemaPatch({ items: [{ alias: 'X1' }] }, session.sessionId);
|
|
643
|
+
|
|
644
|
+
const summary = tool.getSessionState('summary', session.sessionId);
|
|
645
|
+
expect(summary).toMatchObject({
|
|
646
|
+
sessionId: session.sessionId,
|
|
647
|
+
title: 'Summary',
|
|
648
|
+
alias: 'SUM',
|
|
649
|
+
itemCount: 1,
|
|
650
|
+
modelItemCount: 0
|
|
651
|
+
});
|
|
652
|
+
expect('items' in summary && summary.items[0]).toMatchObject({
|
|
653
|
+
alias: 'X1',
|
|
654
|
+
analysisSuccess: true
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it('records revisions via commitStep', () => {
|
|
659
|
+
const tool = new RSToolAgent();
|
|
660
|
+
const session = tool.createSession();
|
|
661
|
+
tool.applySchemaPatch({ items: [{ alias: 'X1' }] }, session.sessionId);
|
|
662
|
+
const revision = tool.commitStep('checkpoint', session.sessionId);
|
|
663
|
+
expect(revision.message).toBe('checkpoint');
|
|
664
|
+
expect(fullState(tool, session.sessionId).revisions).toHaveLength(1);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it('filters diagnostics by constituent id', () => {
|
|
668
|
+
const tool = new RSToolAgent();
|
|
669
|
+
const session = tool.createSession();
|
|
670
|
+
tool.applySchemaPatch(
|
|
671
|
+
{
|
|
672
|
+
mode: 'best_effort',
|
|
673
|
+
items: [
|
|
674
|
+
{ alias: 'X1', cstType: CstType.BASE, definitionFormal: 'Z' },
|
|
675
|
+
{ alias: 'X2', cstType: CstType.BASE, definitionFormal: 'Z' }
|
|
676
|
+
]
|
|
677
|
+
},
|
|
678
|
+
session.sessionId
|
|
679
|
+
);
|
|
680
|
+
const all = tool.listDiagnostics(undefined, session.sessionId);
|
|
681
|
+
const forX1 = tool.listDiagnostics({ constituentId: 1 }, session.sessionId);
|
|
682
|
+
expect(all.length).toBeGreaterThanOrEqual(2);
|
|
683
|
+
expect(forX1.every(record => record.constituentId === 1)).toBe(true);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it('filters diagnostics by constituent id 0', () => {
|
|
687
|
+
const tool = new RSToolAgent();
|
|
688
|
+
const session = tool.createSession();
|
|
689
|
+
tool.applySchemaPatch(
|
|
690
|
+
{
|
|
691
|
+
mode: 'best_effort',
|
|
692
|
+
items: [
|
|
693
|
+
{ id: 0, alias: 'X0', cstType: CstType.BASE, definitionFormal: 'Z' },
|
|
694
|
+
{ alias: 'X1', cstType: CstType.BASE, definitionFormal: 'Z' }
|
|
695
|
+
]
|
|
696
|
+
},
|
|
697
|
+
session.sessionId
|
|
698
|
+
);
|
|
699
|
+
const all = tool.listDiagnostics(undefined, session.sessionId);
|
|
700
|
+
const forX0 = tool.listDiagnostics({ constituentId: 0 }, session.sessionId);
|
|
701
|
+
expect(all.length).toBeGreaterThanOrEqual(2);
|
|
702
|
+
expect(forX0.length).toBeGreaterThan(0);
|
|
703
|
+
expect(forX0.length).toBeLessThan(all.length);
|
|
704
|
+
expect(forX0.every(record => record.constituentId === 0)).toBe(true);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it('applySchemaPatch avoids id collisions when explicit ids precede auto-assigned ones', () => {
|
|
708
|
+
const tool = new RSToolAgent();
|
|
709
|
+
const session = tool.createSession();
|
|
710
|
+
tool.applySchemaPatch(
|
|
711
|
+
{
|
|
712
|
+
items: [
|
|
713
|
+
{ id: 1, alias: 'X1', cstType: CstType.BASE, definitionFormal: '' },
|
|
714
|
+
{ alias: 'X2', cstType: CstType.BASE, definitionFormal: '' }
|
|
715
|
+
]
|
|
716
|
+
},
|
|
717
|
+
session.sessionId
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
const ids = fullState(tool, session.sessionId).items.map(item => item.id);
|
|
721
|
+
expect(ids).toEqual([1, 2]);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it('loads a persisted session from disk via setCurrentSession', () => {
|
|
725
|
+
const dir = mkdtempSync(join(tmpdir(), 'rstool-test-sessions-'));
|
|
726
|
+
try {
|
|
727
|
+
const writer = new RSToolAgent({ persistenceDir: dir });
|
|
728
|
+
const session = writer.createSession({ title: 'On disk' });
|
|
729
|
+
writer.applySchemaPatch({ items: [{ alias: 'X1' }] }, session.sessionId);
|
|
730
|
+
|
|
731
|
+
const reader = new RSToolAgent({ persistenceDir: dir });
|
|
732
|
+
reader.setCurrentSession(session.sessionId);
|
|
733
|
+
expect(fullState(reader, session.sessionId).title).toBe('On disk');
|
|
734
|
+
} finally {
|
|
735
|
+
rmSync(dir, { recursive: true, force: true });
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it('clears current session when persisted file is missing', () => {
|
|
740
|
+
const dir = mkdtempSync(join(tmpdir(), 'rstool-test-sessions-'));
|
|
741
|
+
try {
|
|
742
|
+
const writer = new RSToolAgent({ persistenceDir: dir });
|
|
743
|
+
const session = writer.createSession({ title: 'Gone' });
|
|
744
|
+
|
|
745
|
+
unlinkSync(join(dir, `${session.sessionId}.json`));
|
|
746
|
+
|
|
747
|
+
const reader = new RSToolAgent({ persistenceDir: dir });
|
|
748
|
+
expect(reader.getCurrentSession()).toBeNull();
|
|
749
|
+
} finally {
|
|
750
|
+
rmSync(dir, { recursive: true, force: true });
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
describe('RSToolAgent import and export', () => {
|
|
756
|
+
it('imports portal schema JSON', () => {
|
|
757
|
+
const tool = new RSToolAgent();
|
|
758
|
+
const payload = {
|
|
759
|
+
contract_version: '1.0.0',
|
|
760
|
+
title: 'Imported schema',
|
|
761
|
+
alias: 'IMP',
|
|
762
|
+
description: 'From portal',
|
|
763
|
+
items: [
|
|
764
|
+
{ id: 1, alias: 'X1', cst_type: CstType.BASE, definition_formal: '' },
|
|
765
|
+
{ id: 2, alias: 'D1', cst_type: CstType.TERM, definition_formal: '1+2' }
|
|
766
|
+
],
|
|
767
|
+
attribution: []
|
|
768
|
+
};
|
|
769
|
+
const session = tool.importData(payload, 'portal-schema');
|
|
770
|
+
const state = fullState(tool, session.sessionId);
|
|
771
|
+
expect(state).toMatchObject({ title: 'Imported schema', alias: 'IMP', comment: 'From portal' });
|
|
772
|
+
expect(state.items.map(item => item.alias)).toEqual(['X1', 'D1']);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('keeps declaration order in portal schema export', () => {
|
|
776
|
+
const tool = new RSToolAgent();
|
|
777
|
+
const session = tool.createSession({ title: 'Order', alias: 'ORD' });
|
|
778
|
+
tool.applySchemaPatch(
|
|
779
|
+
{
|
|
780
|
+
items: [
|
|
781
|
+
{ alias: 'D1', cstType: CstType.TERM, definitionFormal: 'Pr1(S1)' },
|
|
782
|
+
{ alias: 'S1', cstType: CstType.STRUCTURED, definitionFormal: 'ℬ(X1×X1)' },
|
|
783
|
+
{ alias: 'C1', cstType: CstType.CONSTANT, definitionFormal: '' },
|
|
784
|
+
{ alias: 'X1', cstType: CstType.BASE, definitionFormal: '' }
|
|
785
|
+
]
|
|
786
|
+
},
|
|
787
|
+
session.sessionId
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
const exported = tool.exportPortal({ kind: 'schema', format: 'object' }, session.sessionId) as {
|
|
791
|
+
items: Array<{ alias: string }>;
|
|
792
|
+
};
|
|
793
|
+
expect(exported.items.map(item => item.alias)).toEqual(['D1', 'S1', 'C1', 'X1']);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
it('round-trips portal schema export and import', () => {
|
|
797
|
+
const tool = new RSToolAgent();
|
|
798
|
+
const session = tool.createSession({ title: 'Round trip', alias: 'RT' });
|
|
799
|
+
tool.applySchemaPatch(
|
|
800
|
+
{
|
|
801
|
+
items: [{ alias: 'D1', cstType: CstType.TERM, definitionFormal: '1+2', term: 'sum' }]
|
|
802
|
+
},
|
|
803
|
+
session.sessionId
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
const exported = tool.exportPortal({ kind: 'schema', format: 'object' });
|
|
807
|
+
const imported = tool.importData(exported, 'portal-schema');
|
|
808
|
+
const state = fullState(tool, imported.sessionId);
|
|
809
|
+
expect(state.title).toBe('Round trip');
|
|
810
|
+
expect(state.items[0]).toMatchObject({
|
|
811
|
+
alias: 'D1',
|
|
812
|
+
definitionFormal: '1+2',
|
|
813
|
+
term: 'sum'
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('rejects invalid session export payloads', () => {
|
|
818
|
+
const tool = new RSToolAgent();
|
|
819
|
+
expect(() => tool.importData({ contractVersion: '2.0.0' }, 'session')).toThrow(/Invalid session export/);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it('rejects undetectable import payloads', () => {
|
|
823
|
+
const tool = new RSToolAgent();
|
|
824
|
+
expect(() => tool.importData({ title: 'orphan' })).toThrow(/Cannot detect import kind/);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it('infers cstType from N and T alias prefixes', () => {
|
|
828
|
+
const tool = new RSToolAgent();
|
|
829
|
+
const statementResult = tool.applySchemaPatch({ items: [{ alias: 'T1', definitionFormal: '1=1' }] });
|
|
830
|
+
expect(statementResult.success).toBe(true);
|
|
831
|
+
expect(fullState(tool).items[0]?.cstType).toBe(CstType.STATEMENT);
|
|
832
|
+
|
|
833
|
+
const nominalResult = tool.applySchemaPatch({ items: [{ alias: 'N1' }] });
|
|
834
|
+
expect(nominalResult.failed[0]?.draft.cstType).toBe(CstType.NOMINAL);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it('rejects aliases with unknown cstType prefix', () => {
|
|
838
|
+
const tool = new RSToolAgent();
|
|
839
|
+
expect(() =>
|
|
840
|
+
tool.applySchemaPatch({
|
|
841
|
+
items: [{ alias: 'Q1', definitionFormal: '1' }]
|
|
842
|
+
})
|
|
843
|
+
).toThrow(/Cannot infer cstType/);
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
describe('RSToolAgent modeling semantics', () => {
|
|
848
|
+
function buildKinshipScratch(tool: RSToolAgent, sessionId: string) {
|
|
849
|
+
tool.applySchemaPatch(
|
|
850
|
+
{
|
|
851
|
+
items: [
|
|
852
|
+
{ alias: 'X1' },
|
|
853
|
+
{ alias: 'S1', definitionFormal: 'ℬ(X1×X1)' },
|
|
854
|
+
{ alias: 'D1', definitionFormal: 'Pr1(S1)' },
|
|
855
|
+
{ alias: 'A1', cstType: CstType.AXIOM, definitionFormal: 'card(X1)≤1' }
|
|
856
|
+
]
|
|
857
|
+
},
|
|
858
|
+
sessionId
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
it('returns EMPTY when a basic set has no binding', () => {
|
|
863
|
+
const tool = new RSToolAgent();
|
|
864
|
+
const session = tool.createSession();
|
|
865
|
+
tool.applySchemaPatch({ items: [{ alias: 'X1' }] }, session.sessionId);
|
|
866
|
+
|
|
867
|
+
const result = tool.evaluate({ constituentId: 1 }, session.sessionId);
|
|
868
|
+
expect(result.status).toBe(EvalStatus.EMPTY);
|
|
869
|
+
expect(result.value).toBeNull();
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
it('sets structured set values and evaluates projections', async () => {
|
|
873
|
+
const tool = new RSToolAgent();
|
|
874
|
+
const session = tool.createSession();
|
|
875
|
+
buildKinshipScratch(tool, session.sessionId);
|
|
876
|
+
|
|
877
|
+
await tool.setModelValues(
|
|
878
|
+
{
|
|
879
|
+
set: [
|
|
880
|
+
{ target: 1, value: { 0: 'ann', 1: 'bob' } },
|
|
881
|
+
{
|
|
882
|
+
target: 2,
|
|
883
|
+
value: [
|
|
884
|
+
[TUPLE_ID, 0, 1],
|
|
885
|
+
[TUPLE_ID, 1, 0]
|
|
886
|
+
]
|
|
887
|
+
}
|
|
888
|
+
]
|
|
889
|
+
},
|
|
890
|
+
session.sessionId
|
|
891
|
+
);
|
|
892
|
+
|
|
893
|
+
const result = tool.evaluate({ constituentId: 3 }, session.sessionId);
|
|
894
|
+
expect(result.success).toBe(true);
|
|
895
|
+
expect(result.status).toBe(EvalStatus.HAS_DATA);
|
|
896
|
+
expect(Array.isArray(result.value)).toBe(true);
|
|
897
|
+
expect((result.value as unknown[]).length).toBeGreaterThan(0);
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it('reports AXIOM_FALSE when model data violates the axiom', async () => {
|
|
901
|
+
const tool = new RSToolAgent();
|
|
902
|
+
const session = tool.createSession();
|
|
903
|
+
buildKinshipScratch(tool, session.sessionId);
|
|
904
|
+
|
|
905
|
+
await tool.setModelValues({ set: [{ target: 1, value: { 0: 'ann', 1: 'bob' } }] }, session.sessionId);
|
|
906
|
+
|
|
907
|
+
const result = tool.evaluate({ constituentId: 4 }, session.sessionId);
|
|
908
|
+
expect(result.status).toBe(EvalStatus.AXIOM_FALSE);
|
|
909
|
+
expect(result.value).toBe(0);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
it('reports AXIOM_FALSE via recalculateModel', async () => {
|
|
913
|
+
const tool = new RSToolAgent();
|
|
914
|
+
const session = tool.createSession();
|
|
915
|
+
buildKinshipScratch(tool, session.sessionId);
|
|
916
|
+
await tool.setModelValues({ set: [{ target: 1, value: { 0: 'ann', 1: 'bob' } }] }, session.sessionId);
|
|
917
|
+
|
|
918
|
+
const recalculated = tool.recalculateModel(session.sessionId);
|
|
919
|
+
const a1 = recalculated.items.find(item => item.alias === 'A1');
|
|
920
|
+
expect(a1?.status).toBe(EvalStatus.AXIOM_FALSE);
|
|
921
|
+
expect(a1?.value).toBe(0);
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
it('throws when evaluate input is incomplete', () => {
|
|
925
|
+
const tool = new RSToolAgent();
|
|
926
|
+
const session = tool.createSession();
|
|
927
|
+
expect(() => tool.evaluate({}, session.sessionId)).toThrow(/requires constituentId or expression/);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
it('throws for unknown constituents on evaluate and setModelValues', async () => {
|
|
931
|
+
const tool = new RSToolAgent();
|
|
932
|
+
const session = tool.createSession();
|
|
933
|
+
buildSampleForm(tool, session.sessionId);
|
|
934
|
+
|
|
935
|
+
expect(() => tool.evaluate({ constituentId: 999 }, session.sessionId)).toThrow(/Unknown constituent/);
|
|
936
|
+
await expect(tool.setModelValues({ set: [{ target: 999, value: { 0: 'a' } }] }, session.sessionId)).rejects.toThrow(
|
|
937
|
+
/Unknown constituent/
|
|
938
|
+
);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it('rejects invalid basic binding data', async () => {
|
|
942
|
+
const tool = new RSToolAgent();
|
|
943
|
+
const session = tool.createSession();
|
|
944
|
+
tool.applySchemaPatch({ items: [{ alias: 'X1' }] }, session.sessionId);
|
|
945
|
+
|
|
946
|
+
await expect(
|
|
947
|
+
tool.setModelValues({ set: [{ target: 1, value: [1, 2, 3] as never }] }, session.sessionId)
|
|
948
|
+
).rejects.toThrow(/Invalid basic binding/);
|
|
949
|
+
});
|
|
950
|
+
});
|