@electric-ax/agents-server-conformance-tests 0.1.6 → 0.1.7

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/dist/index.cjs CHANGED
@@ -678,7 +678,13 @@ async function executeStep(ctx, step) {
678
678
  let entityUrl = ctx.currentEntityUrl;
679
679
  if (step.kind === `killUrl`) entityUrl = step.url;
680
680
  if (!entityUrl) throw new Error(`No current entity — did you spawn first?`);
681
- const res = await electricAgentsFetch$1(ctx.baseUrl, entityUrl, { method: `DELETE` });
681
+ const res = await electricAgentsFetch$1(ctx.baseUrl, `${entityUrl}/signal`, {
682
+ method: `POST`,
683
+ body: JSON.stringify({
684
+ signal: `SIGKILL`,
685
+ reason: `Killed by conformance test`
686
+ })
687
+ });
682
688
  (0, vitest.expect)(res.status).toBe(200);
683
689
  ctx.history.push({
684
690
  type: `entity_killed`,
@@ -1195,8 +1201,8 @@ function checkStreamPathsMatchEntityUrl(history) {
1195
1201
  /**
1196
1202
  * Spec S4 — Safety: entity status transitions must be valid.
1197
1203
  * spawning → running is valid (at spawn time)
1198
- * running/idle → stopped is valid (at kill time)
1199
- * stopped → running is NOT valid
1204
+ * running/idle → killed is valid (at kill time)
1205
+ * killed → running is NOT valid
1200
1206
  * Soundness: Sound | Completeness: Incomplete (only checks observed status reads)
1201
1207
  */
1202
1208
  function checkStatusTransitionsValid(history) {
@@ -1205,7 +1211,7 @@ function checkStatusTransitionsValid(history) {
1205
1211
  if (event.type === `entity_spawned`) lastStatus.set(event.entityUrl, event.status);
1206
1212
  if (event.type === `entity_status_checked`) {
1207
1213
  const prev = lastStatus.get(event.entityUrl);
1208
- if (prev === `stopped`) (0, vitest.expect)(event.status, `Safety: entity ${event.entityUrl} transitioned from stopped to ${event.status}`).toBe(`stopped`);
1214
+ if (prev === `killed`) (0, vitest.expect)(event.status, `Safety: entity ${event.entityUrl} transitioned from killed to ${event.status}`).toBe(`killed`);
1209
1215
  lastStatus.set(event.entityUrl, event.status);
1210
1216
  }
1211
1217
  }
@@ -1324,14 +1330,14 @@ function enabledElectricAgentsActions(model) {
1324
1330
  * Next relation — pure state transition for the model.
1325
1331
  * The real server execution happens separately in the property test.
1326
1332
  */
1327
- function applyElectricAgentsAction(model, action, targetIdx) {
1333
+ function applyElectricAgentsAction(model, action, targetIdx, opts) {
1328
1334
  switch (action) {
1329
1335
  case `register_type`: {
1330
1336
  const typeNum = model.entityTypes.length;
1331
1337
  return {
1332
1338
  ...model,
1333
1339
  entityTypes: [...model.entityTypes, {
1334
- name: `prop-type-${typeNum}`,
1340
+ name: opts?.typeName ?? `prop-type-${typeNum}`,
1335
1341
  hasCreationSchema: false,
1336
1342
  hasInputSchemas: false,
1337
1343
  hasOutputSchemas: false
@@ -1381,7 +1387,7 @@ function applyElectricAgentsAction(model, action, targetIdx) {
1381
1387
  const e = entities[targetIdx];
1382
1388
  entities[targetIdx] = {
1383
1389
  ...e,
1384
- status: `stopped`
1390
+ status: `killed`
1385
1391
  };
1386
1392
  return {
1387
1393
  ...model,
@@ -1902,8 +1908,8 @@ function runElectricAgentsConformanceTests(config) {
1902
1908
  name: `status-filter-agent`,
1903
1909
  description: `Test entity type for status filter`,
1904
1910
  creation_schema: { type: `object` }
1905
- }).spawn(`status-filter-agent`, `entity-1`).spawn(`status-filter-agent`, `entity-2`).kill().list({ status: `stopped` }).custom(async (ctx) => {
1906
- (0, vitest.expect)(ctx.lastListResult.every((e) => e.status === `stopped`)).toBe(true);
1911
+ }).spawn(`status-filter-agent`, `entity-1`).spawn(`status-filter-agent`, `entity-2`).kill().list({ status: `killed` }).custom(async (ctx) => {
1912
+ (0, vitest.expect)(ctx.lastListResult.every((e) => e.status === `killed`)).toBe(true);
1907
1913
  }).list({ status: `running` }).custom(async (ctx) => {
1908
1914
  (0, vitest.expect)(ctx.lastListResult.every((e) => [`running`, `idle`].includes(e.status))).toBe(true);
1909
1915
  }).run();
@@ -1918,7 +1924,7 @@ function runElectricAgentsConformanceTests(config) {
1918
1924
  name: `kill-test-agent`,
1919
1925
  description: `Test entity type for kill`,
1920
1926
  creation_schema: { type: `object` }
1921
- }).spawn(`kill-test-agent`, `entity-1`).kill().expectStatus(`stopped`).expectSendError(`NOT_RUNNING`, 409).run());
1927
+ }).spawn(`kill-test-agent`, `entity-1`).kill().expectStatus(`killed`).expectSendError(`NOT_RUNNING`, 409).run());
1922
1928
  (0, vitest.test)(`stream data persists after kill`, () => electricAgents(config.baseUrl).subscription(`/kill-persist-agent/**`, `kill-persist-sub`).registerType({
1923
1929
  name: `kill-persist-agent`,
1924
1930
  description: `Test entity type for kill persistence`,
@@ -1927,8 +1933,9 @@ function runElectricAgentsConformanceTests(config) {
1927
1933
  const msgs = ctx.lastStreamMessages;
1928
1934
  const msgReceived = msgs.find((m) => m.type === `inbox`);
1929
1935
  (0, vitest.expect)(msgReceived.value?.payload).toEqual({ before: `kill` });
1930
- const stopped = msgs.find((m) => m.type === `entity_stopped`);
1931
- (0, vitest.expect)(stopped).toBeDefined();
1936
+ const signal = msgs.find((m) => m.type === `signal`);
1937
+ (0, vitest.expect)(signal).toBeDefined();
1938
+ (0, vitest.expect)(signal.value?.signal).toBe(`SIGKILL`);
1932
1939
  }).run());
1933
1940
  vitest.test.skip(`multiple entities under same subscription`, () => {
1934
1941
  let firstEntityUrl = null;
@@ -1942,7 +1949,10 @@ function runElectricAgentsConformanceTests(config) {
1942
1949
  }).spawn(`multi-test-worker`, `entity-2`).custom(async (ctx) => {
1943
1950
  secondEntityUrl = ctx.currentEntityUrl;
1944
1951
  }).custom(async (ctx) => {
1945
- const res = await electricAgentsFetch(ctx.baseUrl, firstEntityUrl, { method: `DELETE` });
1952
+ const res = await electricAgentsFetch(ctx.baseUrl, `${firstEntityUrl}/signal`, {
1953
+ method: `POST`,
1954
+ body: JSON.stringify({ signal: `SIGKILL` })
1955
+ });
1946
1956
  (0, vitest.expect)(res.status).toBe(200);
1947
1957
  ctx.history.push({
1948
1958
  type: `entity_killed`,
@@ -1964,8 +1974,8 @@ function runElectricAgentsConformanceTests(config) {
1964
1974
  description: `Test entity type for E2E lifecycle`,
1965
1975
  creation_schema: { type: `object` }
1966
1976
  }).spawn(`e2e-test-agent`, `entity-1`).expectStatus(`running`).send({ task: `do-something` }).expectWebhook().expectEntityContext({ type: `e2e-test-agent` }).respondDone().readStream().expectStreamContains(`inbox`).kill().custom(async (ctx) => {
1967
- const entity = await pollEntityStatus(ctx.baseUrl, ctx.currentEntityUrl, [`stopped`]);
1968
- (0, vitest.expect)(entity.status).toBe(`stopped`);
1977
+ const entity = await pollEntityStatus(ctx.baseUrl, ctx.currentEntityUrl, [`killed`]);
1978
+ (0, vitest.expect)(entity.status).toBe(`killed`);
1969
1979
  }).expectSendError(`NOT_RUNNING`, 409).run());
1970
1980
  });
1971
1981
  (0, vitest.describe)(`Electric Agents Entity Type Registration`, () => {
@@ -2563,7 +2573,6 @@ function runElectricAgentsConformanceTests(config) {
2563
2573
  const runId = `prop-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2564
2574
  const baseUrl = config.baseUrl;
2565
2575
  const entityUrls = [];
2566
- const registeredTypeNames = [];
2567
2576
  const scenario = electricAgents(baseUrl);
2568
2577
  let model = {
2569
2578
  entityTypes: [],
@@ -2571,21 +2580,21 @@ function runElectricAgentsConformanceTests(config) {
2571
2580
  nextEntityNum: 0
2572
2581
  };
2573
2582
  let entityCounter = 0;
2583
+ let typeCounter = 0;
2574
2584
  for (const action of actions) {
2575
2585
  const valid = enabledElectricAgentsActions(model);
2576
2586
  if (!valid.includes(action)) continue;
2577
2587
  switch (action) {
2578
2588
  case `register_type`: {
2579
- const typeNum = model.entityTypes.length;
2589
+ const typeNum = typeCounter++;
2580
2590
  const typeName = `prop-type-${runId}-${typeNum}`;
2581
- registeredTypeNames.push(typeName);
2582
2591
  scenario.subscription(`/${typeName}/**`, `prop-sub-${typeName}`);
2583
2592
  scenario.registerType({
2584
2593
  name: typeName,
2585
2594
  description: `Property-based test type ${typeNum}`,
2586
2595
  creation_schema: { type: `object` }
2587
2596
  });
2588
- model = applyElectricAgentsAction(model, `register_type`);
2597
+ model = applyElectricAgentsAction(model, `register_type`, void 0, { typeName });
2589
2598
  break;
2590
2599
  }
2591
2600
  case `delete_type`: {
@@ -2593,14 +2602,13 @@ function runElectricAgentsConformanceTests(config) {
2593
2602
  const deletableIndices = model.entityTypes.map((t, i) => !runningModelTypeNames.has(t.name) ? i : -1).filter((i) => i >= 0);
2594
2603
  if (deletableIndices.length === 0) break;
2595
2604
  const deleteIdx = deletableIndices[0];
2596
- scenario.deleteType(registeredTypeNames[deleteIdx]);
2597
- registeredTypeNames.splice(deleteIdx, 1);
2605
+ scenario.deleteType(model.entityTypes[deleteIdx].name);
2598
2606
  model = applyElectricAgentsAction(model, `delete_type`);
2599
2607
  break;
2600
2608
  }
2601
2609
  case `spawn`: {
2602
- if (registeredTypeNames.length === 0) break;
2603
- const typeName = registeredTypeNames[0];
2610
+ if (model.entityTypes.length === 0) break;
2611
+ const typeName = model.entityTypes[0].name;
2604
2612
  const instanceId = `entity-${entityCounter++}`;
2605
2613
  scenario.spawn(typeName, instanceId);
2606
2614
  scenario.custom(async (ctx) => {
@@ -2645,7 +2653,10 @@ function runElectricAgentsConformanceTests(config) {
2645
2653
  scenario.custom(async (ctx) => {
2646
2654
  const url = entityUrls[targetIdx];
2647
2655
  if (!url) return;
2648
- const res = await electricAgentsFetch(ctx.baseUrl, url, { method: `DELETE` });
2656
+ const res = await electricAgentsFetch(ctx.baseUrl, `${url}/signal`, {
2657
+ method: `POST`,
2658
+ body: JSON.stringify({ signal: `SIGKILL` })
2659
+ });
2649
2660
  (0, vitest.expect)(res.status).toBe(200);
2650
2661
  ctx.history.push({
2651
2662
  type: `entity_killed`,
@@ -3019,7 +3030,10 @@ function runElectricAgentsConformanceTests(config) {
3019
3030
  description: `test`
3020
3031
  })
3021
3032
  });
3022
- const res = await electricAgentsFetch(config.baseUrl, `/${typeName}/nonexistent-id-12345`, { method: `DELETE` });
3033
+ const res = await electricAgentsFetch(config.baseUrl, `/${typeName}/nonexistent-id-12345/signal`, {
3034
+ method: `POST`,
3035
+ body: JSON.stringify({ signal: `SIGKILL` })
3036
+ });
3023
3037
  (0, vitest.expect)(res.status).toBe(404);
3024
3038
  const body = await res.json();
3025
3039
  (0, vitest.expect)(body.error.code).toBe(`NOT_FOUND`);
@@ -3452,8 +3466,8 @@ function runCliConformanceTests(config) {
3452
3466
  name: `cli-kill-type`,
3453
3467
  description: `Type for CLI kill test`
3454
3468
  }).setupSubscription(`/cli-kill-type/**`, `cli-kill-sub`).exec(`spawn`, `/cli-kill-type/${id}`).expectExitCode(0).exec(`kill`, `/cli-kill-type/${id}`).expectExitCode(0).expectStdout(/Killed/).verifyApi(async (baseUrl) => {
3455
- const entity = await pollEntityStatus(baseUrl, `/cli-kill-type/${id}`, [`stopped`]);
3456
- (0, vitest.expect)(entity.status).toBe(`stopped`);
3469
+ const entity = await pollEntityStatus(baseUrl, `/cli-kill-type/${id}`, [`killed`]);
3470
+ (0, vitest.expect)(entity.status).toBe(`killed`);
3457
3471
  }).run();
3458
3472
  }, 15e3);
3459
3473
  (0, vitest.test)(`kill nonexistent entity fails`, async () => {
@@ -3472,8 +3486,8 @@ function runCliConformanceTests(config) {
3472
3486
  const entity = await res.json();
3473
3487
  (0, vitest.expect)([`running`, `idle`]).toContain(entity.status);
3474
3488
  }).exec(`send`, `/cli-lifecycle-type/${id}`, `test message`).expectExitCode(0).expectStdout(/Message sent/).exec(`inspect`, `/cli-lifecycle-type/${id}`).expectExitCode(0).expectStdout(/running|idle/).exec(`kill`, `/cli-lifecycle-type/${id}`).expectExitCode(0).expectStdout(/Killed/).verifyApi(async (baseUrl) => {
3475
- const entity = await pollEntityStatus(baseUrl, `/cli-lifecycle-type/${id}`, [`stopped`]);
3476
- (0, vitest.expect)(entity.status).toBe(`stopped`);
3489
+ const entity = await pollEntityStatus(baseUrl, `/cli-lifecycle-type/${id}`, [`killed`]);
3490
+ (0, vitest.expect)(entity.status).toBe(`killed`);
3477
3491
  }).exec(`ps`, `--status`, `running`).expectExitCode(0).expectStdoutNot(new RegExp(id)).run();
3478
3492
  }, 15e3);
3479
3493
  (0, vitest.test)(`send to stopped entity fails`, async () => {
package/dist/index.d.cts CHANGED
@@ -49,7 +49,7 @@ declare function runMockAgentCliTests(config: MockAgentCliTestOptions): void; //
49
49
  * .respondDone()
50
50
  * .expectStatus('running')
51
51
  * .kill()
52
- * .expectStatus('stopped')
52
+ * .expectStatus('killed')
53
53
  * .run()
54
54
  */
55
55
  type HistoryEvent = {
@@ -410,7 +410,7 @@ interface EntityTypeModel {
410
410
  interface EntityModel {
411
411
  url: string;
412
412
  typeName: string;
413
- status: `running` | `stopped`;
413
+ status: `running` | `killed`;
414
414
  messageCount: number;
415
415
  }
416
416
  /**
@@ -438,7 +438,9 @@ declare function enabledElectricAgentsActions(model: ElectricAgentsWorldModel):
438
438
  * Next relation — pure state transition for the model.
439
439
  * The real server execution happens separately in the property test.
440
440
  */
441
- declare function applyElectricAgentsAction(model: ElectricAgentsWorldModel, action: ElectricAgentsAction, targetIdx?: number): ElectricAgentsWorldModel;
441
+ declare function applyElectricAgentsAction(model: ElectricAgentsWorldModel, action: ElectricAgentsAction, targetIdx?: number, opts?: {
442
+ typeName?: string;
443
+ }): ElectricAgentsWorldModel;
442
444
  declare function electricAgents(baseUrl: string): ElectricAgentsScenario;
443
445
  declare function checkStateProtocolInvariants(events: Array<Record<string, unknown>>): void; //#endregion
444
446
  //#region src/cli-dsl.d.ts
package/dist/index.d.ts CHANGED
@@ -49,7 +49,7 @@ declare function runMockAgentCliTests(config: MockAgentCliTestOptions): void; //
49
49
  * .respondDone()
50
50
  * .expectStatus('running')
51
51
  * .kill()
52
- * .expectStatus('stopped')
52
+ * .expectStatus('killed')
53
53
  * .run()
54
54
  */
55
55
  type HistoryEvent = {
@@ -410,7 +410,7 @@ interface EntityTypeModel {
410
410
  interface EntityModel {
411
411
  url: string;
412
412
  typeName: string;
413
- status: `running` | `stopped`;
413
+ status: `running` | `killed`;
414
414
  messageCount: number;
415
415
  }
416
416
  /**
@@ -438,7 +438,9 @@ declare function enabledElectricAgentsActions(model: ElectricAgentsWorldModel):
438
438
  * Next relation — pure state transition for the model.
439
439
  * The real server execution happens separately in the property test.
440
440
  */
441
- declare function applyElectricAgentsAction(model: ElectricAgentsWorldModel, action: ElectricAgentsAction, targetIdx?: number): ElectricAgentsWorldModel;
441
+ declare function applyElectricAgentsAction(model: ElectricAgentsWorldModel, action: ElectricAgentsAction, targetIdx?: number, opts?: {
442
+ typeName?: string;
443
+ }): ElectricAgentsWorldModel;
442
444
  declare function electricAgents(baseUrl: string): ElectricAgentsScenario;
443
445
  declare function checkStateProtocolInvariants(events: Array<Record<string, unknown>>): void; //#endregion
444
446
  //#region src/cli-dsl.d.ts
package/dist/index.js CHANGED
@@ -654,7 +654,13 @@ async function executeStep(ctx, step) {
654
654
  let entityUrl = ctx.currentEntityUrl;
655
655
  if (step.kind === `killUrl`) entityUrl = step.url;
656
656
  if (!entityUrl) throw new Error(`No current entity — did you spawn first?`);
657
- const res = await electricAgentsFetch$1(ctx.baseUrl, entityUrl, { method: `DELETE` });
657
+ const res = await electricAgentsFetch$1(ctx.baseUrl, `${entityUrl}/signal`, {
658
+ method: `POST`,
659
+ body: JSON.stringify({
660
+ signal: `SIGKILL`,
661
+ reason: `Killed by conformance test`
662
+ })
663
+ });
658
664
  expect(res.status).toBe(200);
659
665
  ctx.history.push({
660
666
  type: `entity_killed`,
@@ -1171,8 +1177,8 @@ function checkStreamPathsMatchEntityUrl(history) {
1171
1177
  /**
1172
1178
  * Spec S4 — Safety: entity status transitions must be valid.
1173
1179
  * spawning → running is valid (at spawn time)
1174
- * running/idle → stopped is valid (at kill time)
1175
- * stopped → running is NOT valid
1180
+ * running/idle → killed is valid (at kill time)
1181
+ * killed → running is NOT valid
1176
1182
  * Soundness: Sound | Completeness: Incomplete (only checks observed status reads)
1177
1183
  */
1178
1184
  function checkStatusTransitionsValid(history) {
@@ -1181,7 +1187,7 @@ function checkStatusTransitionsValid(history) {
1181
1187
  if (event.type === `entity_spawned`) lastStatus.set(event.entityUrl, event.status);
1182
1188
  if (event.type === `entity_status_checked`) {
1183
1189
  const prev = lastStatus.get(event.entityUrl);
1184
- if (prev === `stopped`) expect(event.status, `Safety: entity ${event.entityUrl} transitioned from stopped to ${event.status}`).toBe(`stopped`);
1190
+ if (prev === `killed`) expect(event.status, `Safety: entity ${event.entityUrl} transitioned from killed to ${event.status}`).toBe(`killed`);
1185
1191
  lastStatus.set(event.entityUrl, event.status);
1186
1192
  }
1187
1193
  }
@@ -1300,14 +1306,14 @@ function enabledElectricAgentsActions(model) {
1300
1306
  * Next relation — pure state transition for the model.
1301
1307
  * The real server execution happens separately in the property test.
1302
1308
  */
1303
- function applyElectricAgentsAction(model, action, targetIdx) {
1309
+ function applyElectricAgentsAction(model, action, targetIdx, opts) {
1304
1310
  switch (action) {
1305
1311
  case `register_type`: {
1306
1312
  const typeNum = model.entityTypes.length;
1307
1313
  return {
1308
1314
  ...model,
1309
1315
  entityTypes: [...model.entityTypes, {
1310
- name: `prop-type-${typeNum}`,
1316
+ name: opts?.typeName ?? `prop-type-${typeNum}`,
1311
1317
  hasCreationSchema: false,
1312
1318
  hasInputSchemas: false,
1313
1319
  hasOutputSchemas: false
@@ -1357,7 +1363,7 @@ function applyElectricAgentsAction(model, action, targetIdx) {
1357
1363
  const e = entities[targetIdx];
1358
1364
  entities[targetIdx] = {
1359
1365
  ...e,
1360
- status: `stopped`
1366
+ status: `killed`
1361
1367
  };
1362
1368
  return {
1363
1369
  ...model,
@@ -1878,8 +1884,8 @@ function runElectricAgentsConformanceTests(config) {
1878
1884
  name: `status-filter-agent`,
1879
1885
  description: `Test entity type for status filter`,
1880
1886
  creation_schema: { type: `object` }
1881
- }).spawn(`status-filter-agent`, `entity-1`).spawn(`status-filter-agent`, `entity-2`).kill().list({ status: `stopped` }).custom(async (ctx) => {
1882
- expect(ctx.lastListResult.every((e) => e.status === `stopped`)).toBe(true);
1887
+ }).spawn(`status-filter-agent`, `entity-1`).spawn(`status-filter-agent`, `entity-2`).kill().list({ status: `killed` }).custom(async (ctx) => {
1888
+ expect(ctx.lastListResult.every((e) => e.status === `killed`)).toBe(true);
1883
1889
  }).list({ status: `running` }).custom(async (ctx) => {
1884
1890
  expect(ctx.lastListResult.every((e) => [`running`, `idle`].includes(e.status))).toBe(true);
1885
1891
  }).run();
@@ -1894,7 +1900,7 @@ function runElectricAgentsConformanceTests(config) {
1894
1900
  name: `kill-test-agent`,
1895
1901
  description: `Test entity type for kill`,
1896
1902
  creation_schema: { type: `object` }
1897
- }).spawn(`kill-test-agent`, `entity-1`).kill().expectStatus(`stopped`).expectSendError(`NOT_RUNNING`, 409).run());
1903
+ }).spawn(`kill-test-agent`, `entity-1`).kill().expectStatus(`killed`).expectSendError(`NOT_RUNNING`, 409).run());
1898
1904
  test(`stream data persists after kill`, () => electricAgents(config.baseUrl).subscription(`/kill-persist-agent/**`, `kill-persist-sub`).registerType({
1899
1905
  name: `kill-persist-agent`,
1900
1906
  description: `Test entity type for kill persistence`,
@@ -1903,8 +1909,9 @@ function runElectricAgentsConformanceTests(config) {
1903
1909
  const msgs = ctx.lastStreamMessages;
1904
1910
  const msgReceived = msgs.find((m) => m.type === `inbox`);
1905
1911
  expect(msgReceived.value?.payload).toEqual({ before: `kill` });
1906
- const stopped = msgs.find((m) => m.type === `entity_stopped`);
1907
- expect(stopped).toBeDefined();
1912
+ const signal = msgs.find((m) => m.type === `signal`);
1913
+ expect(signal).toBeDefined();
1914
+ expect(signal.value?.signal).toBe(`SIGKILL`);
1908
1915
  }).run());
1909
1916
  test.skip(`multiple entities under same subscription`, () => {
1910
1917
  let firstEntityUrl = null;
@@ -1918,7 +1925,10 @@ function runElectricAgentsConformanceTests(config) {
1918
1925
  }).spawn(`multi-test-worker`, `entity-2`).custom(async (ctx) => {
1919
1926
  secondEntityUrl = ctx.currentEntityUrl;
1920
1927
  }).custom(async (ctx) => {
1921
- const res = await electricAgentsFetch(ctx.baseUrl, firstEntityUrl, { method: `DELETE` });
1928
+ const res = await electricAgentsFetch(ctx.baseUrl, `${firstEntityUrl}/signal`, {
1929
+ method: `POST`,
1930
+ body: JSON.stringify({ signal: `SIGKILL` })
1931
+ });
1922
1932
  expect(res.status).toBe(200);
1923
1933
  ctx.history.push({
1924
1934
  type: `entity_killed`,
@@ -1940,8 +1950,8 @@ function runElectricAgentsConformanceTests(config) {
1940
1950
  description: `Test entity type for E2E lifecycle`,
1941
1951
  creation_schema: { type: `object` }
1942
1952
  }).spawn(`e2e-test-agent`, `entity-1`).expectStatus(`running`).send({ task: `do-something` }).expectWebhook().expectEntityContext({ type: `e2e-test-agent` }).respondDone().readStream().expectStreamContains(`inbox`).kill().custom(async (ctx) => {
1943
- const entity = await pollEntityStatus(ctx.baseUrl, ctx.currentEntityUrl, [`stopped`]);
1944
- expect(entity.status).toBe(`stopped`);
1953
+ const entity = await pollEntityStatus(ctx.baseUrl, ctx.currentEntityUrl, [`killed`]);
1954
+ expect(entity.status).toBe(`killed`);
1945
1955
  }).expectSendError(`NOT_RUNNING`, 409).run());
1946
1956
  });
1947
1957
  describe(`Electric Agents Entity Type Registration`, () => {
@@ -2539,7 +2549,6 @@ function runElectricAgentsConformanceTests(config) {
2539
2549
  const runId = `prop-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2540
2550
  const baseUrl = config.baseUrl;
2541
2551
  const entityUrls = [];
2542
- const registeredTypeNames = [];
2543
2552
  const scenario = electricAgents(baseUrl);
2544
2553
  let model = {
2545
2554
  entityTypes: [],
@@ -2547,21 +2556,21 @@ function runElectricAgentsConformanceTests(config) {
2547
2556
  nextEntityNum: 0
2548
2557
  };
2549
2558
  let entityCounter = 0;
2559
+ let typeCounter = 0;
2550
2560
  for (const action of actions) {
2551
2561
  const valid = enabledElectricAgentsActions(model);
2552
2562
  if (!valid.includes(action)) continue;
2553
2563
  switch (action) {
2554
2564
  case `register_type`: {
2555
- const typeNum = model.entityTypes.length;
2565
+ const typeNum = typeCounter++;
2556
2566
  const typeName = `prop-type-${runId}-${typeNum}`;
2557
- registeredTypeNames.push(typeName);
2558
2567
  scenario.subscription(`/${typeName}/**`, `prop-sub-${typeName}`);
2559
2568
  scenario.registerType({
2560
2569
  name: typeName,
2561
2570
  description: `Property-based test type ${typeNum}`,
2562
2571
  creation_schema: { type: `object` }
2563
2572
  });
2564
- model = applyElectricAgentsAction(model, `register_type`);
2573
+ model = applyElectricAgentsAction(model, `register_type`, void 0, { typeName });
2565
2574
  break;
2566
2575
  }
2567
2576
  case `delete_type`: {
@@ -2569,14 +2578,13 @@ function runElectricAgentsConformanceTests(config) {
2569
2578
  const deletableIndices = model.entityTypes.map((t, i) => !runningModelTypeNames.has(t.name) ? i : -1).filter((i) => i >= 0);
2570
2579
  if (deletableIndices.length === 0) break;
2571
2580
  const deleteIdx = deletableIndices[0];
2572
- scenario.deleteType(registeredTypeNames[deleteIdx]);
2573
- registeredTypeNames.splice(deleteIdx, 1);
2581
+ scenario.deleteType(model.entityTypes[deleteIdx].name);
2574
2582
  model = applyElectricAgentsAction(model, `delete_type`);
2575
2583
  break;
2576
2584
  }
2577
2585
  case `spawn`: {
2578
- if (registeredTypeNames.length === 0) break;
2579
- const typeName = registeredTypeNames[0];
2586
+ if (model.entityTypes.length === 0) break;
2587
+ const typeName = model.entityTypes[0].name;
2580
2588
  const instanceId = `entity-${entityCounter++}`;
2581
2589
  scenario.spawn(typeName, instanceId);
2582
2590
  scenario.custom(async (ctx) => {
@@ -2621,7 +2629,10 @@ function runElectricAgentsConformanceTests(config) {
2621
2629
  scenario.custom(async (ctx) => {
2622
2630
  const url = entityUrls[targetIdx];
2623
2631
  if (!url) return;
2624
- const res = await electricAgentsFetch(ctx.baseUrl, url, { method: `DELETE` });
2632
+ const res = await electricAgentsFetch(ctx.baseUrl, `${url}/signal`, {
2633
+ method: `POST`,
2634
+ body: JSON.stringify({ signal: `SIGKILL` })
2635
+ });
2625
2636
  expect(res.status).toBe(200);
2626
2637
  ctx.history.push({
2627
2638
  type: `entity_killed`,
@@ -2995,7 +3006,10 @@ function runElectricAgentsConformanceTests(config) {
2995
3006
  description: `test`
2996
3007
  })
2997
3008
  });
2998
- const res = await electricAgentsFetch(config.baseUrl, `/${typeName}/nonexistent-id-12345`, { method: `DELETE` });
3009
+ const res = await electricAgentsFetch(config.baseUrl, `/${typeName}/nonexistent-id-12345/signal`, {
3010
+ method: `POST`,
3011
+ body: JSON.stringify({ signal: `SIGKILL` })
3012
+ });
2999
3013
  expect(res.status).toBe(404);
3000
3014
  const body = await res.json();
3001
3015
  expect(body.error.code).toBe(`NOT_FOUND`);
@@ -3428,8 +3442,8 @@ function runCliConformanceTests(config) {
3428
3442
  name: `cli-kill-type`,
3429
3443
  description: `Type for CLI kill test`
3430
3444
  }).setupSubscription(`/cli-kill-type/**`, `cli-kill-sub`).exec(`spawn`, `/cli-kill-type/${id}`).expectExitCode(0).exec(`kill`, `/cli-kill-type/${id}`).expectExitCode(0).expectStdout(/Killed/).verifyApi(async (baseUrl) => {
3431
- const entity = await pollEntityStatus(baseUrl, `/cli-kill-type/${id}`, [`stopped`]);
3432
- expect(entity.status).toBe(`stopped`);
3445
+ const entity = await pollEntityStatus(baseUrl, `/cli-kill-type/${id}`, [`killed`]);
3446
+ expect(entity.status).toBe(`killed`);
3433
3447
  }).run();
3434
3448
  }, 15e3);
3435
3449
  test(`kill nonexistent entity fails`, async () => {
@@ -3448,8 +3462,8 @@ function runCliConformanceTests(config) {
3448
3462
  const entity = await res.json();
3449
3463
  expect([`running`, `idle`]).toContain(entity.status);
3450
3464
  }).exec(`send`, `/cli-lifecycle-type/${id}`, `test message`).expectExitCode(0).expectStdout(/Message sent/).exec(`inspect`, `/cli-lifecycle-type/${id}`).expectExitCode(0).expectStdout(/running|idle/).exec(`kill`, `/cli-lifecycle-type/${id}`).expectExitCode(0).expectStdout(/Killed/).verifyApi(async (baseUrl) => {
3451
- const entity = await pollEntityStatus(baseUrl, `/cli-lifecycle-type/${id}`, [`stopped`]);
3452
- expect(entity.status).toBe(`stopped`);
3465
+ const entity = await pollEntityStatus(baseUrl, `/cli-lifecycle-type/${id}`, [`killed`]);
3466
+ expect(entity.status).toBe(`killed`);
3453
3467
  }).exec(`ps`, `--status`, `running`).expectExitCode(0).expectStdoutNot(new RegExp(id)).run();
3454
3468
  }, 15e3);
3455
3469
  test(`send to stopped entity fails`, async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electric-ax/agents-server-conformance-tests",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Conformance test suite for Electric Agents server implementations",
5
5
  "author": "Durable Stream contributors",
6
6
  "license": "Apache-2.0",
@@ -43,7 +43,7 @@
43
43
  "@mariozechner/pi-agent-core": "^0.70.2",
44
44
  "tsdown": "^0.9.0",
45
45
  "typescript": "^5.0.0",
46
- "@electric-ax/agents-server": "0.4.5"
46
+ "@electric-ax/agents-server": "0.4.7"
47
47
  },
48
48
  "engines": {
49
49
  "node": ">=18.0.0"