agent-dealer 1.2.13 → 1.2.14
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/bundle/server/dist/adapters/git-worktree.js +15 -0
- package/bundle/server/dist/adapters/git-worktree.test.js +30 -0
- package/bundle/server/dist/adapters/linear-inbox.js +97 -0
- package/bundle/server/dist/adapters/linear-inbox.test.js +299 -1
- package/bundle/server/dist/adapters/linear-sync.js +33 -10
- package/bundle/server/dist/coordinator/auto-merge.js +10 -0
- package/bundle/server/dist/coordinator/branch-progress.js +5 -4
- package/bundle/server/dist/coordinator/commands.js +37 -3
- package/bundle/server/dist/coordinator/developer-effect.js +33 -6
- package/bundle/server/dist/coordinator/linear-merge-verify.js +382 -0
- package/bundle/server/dist/coordinator/linear-merge-verify.test.js +376 -0
- package/bundle/server/dist/coordinator/prompts.js +9 -1
- package/bundle/server/dist/coordinator/prompts.test.js +93 -0
- package/bundle/server/dist/coordinator/source-attachments.js +473 -0
- package/bundle/server/dist/coordinator/source-attachments.test.js +284 -0
- package/bundle/server/dist/coordinator/source-reload.js +178 -0
- package/bundle/server/dist/db/schema.sql +28 -0
- package/bundle/server/dist/docs-linear-attachments.test.js +36 -0
- package/bundle/server/dist/paths.js +12 -0
- package/bundle/server/dist/repository/source-attachments.js +56 -0
- package/bundle/server/dist/routes/human-actions.js +17 -0
- package/bundle/server/dist/routes/issues-execution-contract.test.js +296 -0
- package/bundle/server/dist/routes/issues-source-attachments.test.js +411 -0
- package/bundle/server/dist/routes/issues.js +89 -1
- package/bundle/server/package.json +2 -2
- package/bundle/server/static-ui/assets/index-DDbJ9ZU7.js +64 -0
- package/bundle/server/static-ui/index.html +1 -1
- package/bundle/shared/dist/index.d.ts +108 -2
- package/bundle/shared/dist/index.js +12 -1
- package/bundle/shared/dist/issues.d.ts +43 -2
- package/bundle/shared/dist/issues.js +10 -0
- package/bundle/shared/dist/linear-intake.d.ts +85 -0
- package/bundle/shared/dist/linear-intake.js +56 -0
- package/bundle/shared/dist/linear-intake.test.js +55 -1
- package/bundle/shared/dist/workflow.d.ts +4 -4
- package/bundle/shared/dist/workflow.js +7 -0
- package/bundle/shared/package.json +1 -1
- package/package.json +1 -1
- package/bundle/server/static-ui/assets/index-BzLjMBI9.js +0 -64
|
@@ -274,6 +274,116 @@ test("an active workflow keeps its frozen contract — later edits are refused",
|
|
|
274
274
|
assert.deepStrictEqual(after, before);
|
|
275
275
|
await app.close();
|
|
276
276
|
});
|
|
277
|
+
// NOT-363: source-reload fixtures — a ready Linear-sourced issue pulls the
|
|
278
|
+
// latest ticket text through POST /api/issues/:id/reload-source.
|
|
279
|
+
const { listWorkflowEventsForIssue } = await import("../repository/workflow-events.js");
|
|
280
|
+
const { queueStatusForIssue } = await import("../coordinator/admission.js");
|
|
281
|
+
const { createWorkerSession, startSession } = await import("../repository/worker-sessions.js");
|
|
282
|
+
/** Refreshed Linear description: new exit predicate, wholly new criteria. */
|
|
283
|
+
const RELOADED_DESCRIPTION = [
|
|
284
|
+
"The Planner improved the ticket after import.",
|
|
285
|
+
"",
|
|
286
|
+
"## Builder execution mode",
|
|
287
|
+
"feature",
|
|
288
|
+
"",
|
|
289
|
+
"## Non-goals",
|
|
290
|
+
"- Selecting a playbook or reasoning about implementation architecture",
|
|
291
|
+
"- Adding a generic workflow/schema editor",
|
|
292
|
+
"",
|
|
293
|
+
"## Exit predicate",
|
|
294
|
+
"Reloading a Linear-sourced issue pulls the latest ticket text and recompiles the contract.",
|
|
295
|
+
"",
|
|
296
|
+
"## One-PR stopping point",
|
|
297
|
+
"Stop after the refreshed contract freezes and renders.",
|
|
298
|
+
"",
|
|
299
|
+
"## Acceptance criteria",
|
|
300
|
+
"- [ ] Reloaded ticket compiles into the refreshed structured schema",
|
|
301
|
+
" Evidence: reload fixtures | run the reload tests | every field asserted",
|
|
302
|
+
"- [ ] Old local criteria do not survive a source reload",
|
|
303
|
+
" Evidence: reload suite | run the reload tests | green",
|
|
304
|
+
"",
|
|
305
|
+
].join("\n");
|
|
306
|
+
const EXPECTED_RELOADED_CONTRACT = {
|
|
307
|
+
version: "v1",
|
|
308
|
+
executionMode: "feature",
|
|
309
|
+
nonGoals: [
|
|
310
|
+
"Selecting a playbook or reasoning about implementation architecture",
|
|
311
|
+
"Adding a generic workflow/schema editor",
|
|
312
|
+
],
|
|
313
|
+
exitPredicate: "Reloading a Linear-sourced issue pulls the latest ticket text and recompiles the contract.",
|
|
314
|
+
onePrStoppingPoint: "Stop after the refreshed contract freezes and renders.",
|
|
315
|
+
acceptanceCriteria: [
|
|
316
|
+
{
|
|
317
|
+
text: "Reloaded ticket compiles into the refreshed structured schema",
|
|
318
|
+
evidence: "reload fixtures | run the reload tests | every field asserted",
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
text: "Old local criteria do not survive a source reload",
|
|
322
|
+
evidence: "reload suite | run the reload tests | green",
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
function linearNodeForTest(description) {
|
|
327
|
+
return {
|
|
328
|
+
id: "linear-uuid-1",
|
|
329
|
+
identifier: "NOT-123",
|
|
330
|
+
title: "Refreshed title from Linear",
|
|
331
|
+
description,
|
|
332
|
+
url: "https://linear.app/not-so-fat/issue/NOT-123/refreshed",
|
|
333
|
+
state: { name: "Todo" },
|
|
334
|
+
team: { id: "team-1" },
|
|
335
|
+
labels: { nodes: [] },
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const realFetch = globalThis.fetch;
|
|
339
|
+
let linearIssueNodeForTest = null;
|
|
340
|
+
let linearHttpFailsForTest = false;
|
|
341
|
+
before(() => {
|
|
342
|
+
process.env.LINEAR_API_KEY = "test-key";
|
|
343
|
+
globalThis.fetch = (async (_url, init) => {
|
|
344
|
+
if (linearHttpFailsForTest) {
|
|
345
|
+
return { ok: false, status: 500, headers: new Headers(), text: async () => "boom" };
|
|
346
|
+
}
|
|
347
|
+
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
348
|
+
// Single-issue fetch (getLinearIssue) answers the reload; relation walks
|
|
349
|
+
// (fetchLinearBlockers, from queue wait-reason classification) answer no
|
|
350
|
+
// blockers so positions stay comparable.
|
|
351
|
+
const payload = typeof body.variables?.id === "string"
|
|
352
|
+
? { data: { issue: linearIssueNodeForTest } }
|
|
353
|
+
: { data: { issues: { nodes: [], pageInfo: { hasNextPage: false, endCursor: null } } } };
|
|
354
|
+
return {
|
|
355
|
+
ok: true,
|
|
356
|
+
status: 200,
|
|
357
|
+
headers: new Headers(),
|
|
358
|
+
text: async () => JSON.stringify(payload),
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
after(() => {
|
|
363
|
+
globalThis.fetch = realFetch;
|
|
364
|
+
delete process.env.LINEAR_API_KEY;
|
|
365
|
+
});
|
|
366
|
+
function resetLinearMock(node, httpFails = false) {
|
|
367
|
+
linearIssueNodeForTest = node;
|
|
368
|
+
linearHttpFailsForTest = httpFails;
|
|
369
|
+
}
|
|
370
|
+
const LINEAR_ISSUE_PAYLOAD = {
|
|
371
|
+
title: "NOT-123: stale imported title",
|
|
372
|
+
description: CONTRACT_DESCRIPTION,
|
|
373
|
+
// A Dealer-local clarification that a reload must wipe: the refreshed
|
|
374
|
+
// description's derived criteria replace it, they never merge with it.
|
|
375
|
+
acceptanceCriteria: "Operator-typed override",
|
|
376
|
+
source: "linear",
|
|
377
|
+
externalId: "linear-uuid-1",
|
|
378
|
+
externalLabel: "NOT-123",
|
|
379
|
+
externalUrl: "https://linear.app/not-so-fat/issue/NOT-123/stale",
|
|
380
|
+
...BASE_AGENTS,
|
|
381
|
+
};
|
|
382
|
+
async function createLinearIssue(app) {
|
|
383
|
+
const res = await app.inject({ method: "POST", url: "/api/issues", payload: LINEAR_ISSUE_PAYLOAD });
|
|
384
|
+
assert.equal(res.statusCode, 200, res.body);
|
|
385
|
+
return res.json();
|
|
386
|
+
}
|
|
277
387
|
test("NOT-185 + NOT-306: a parked re-freeze updates source and compiled contract atomically", async () => {
|
|
278
388
|
const app = await buildApp();
|
|
279
389
|
const created = (await app.inject({
|
|
@@ -319,3 +429,189 @@ test("NOT-185 + NOT-306: a parked re-freeze updates source and compiled contract
|
|
|
319
429
|
assert.deepStrictEqual(getIssue(created.id).executionContract, frozen.executionContract);
|
|
320
430
|
await app.close();
|
|
321
431
|
});
|
|
432
|
+
test("NOT-363: reload-source pulls the latest Linear text and recompiles the contract", async () => {
|
|
433
|
+
resetLinearMock(linearNodeForTest(RELOADED_DESCRIPTION));
|
|
434
|
+
const app = await buildApp();
|
|
435
|
+
const { id } = await createLinearIssue(app);
|
|
436
|
+
const res = await app.inject({ method: "POST", url: `/api/issues/${id}/reload-source` });
|
|
437
|
+
assert.equal(res.statusCode, 200, res.body);
|
|
438
|
+
const reloaded = res.json();
|
|
439
|
+
// Same title convention as import (`<identifier>: <title>`).
|
|
440
|
+
assert.equal(reloaded.title, "NOT-123: Refreshed title from Linear");
|
|
441
|
+
assert.equal(reloaded.description, RELOADED_DESCRIPTION);
|
|
442
|
+
assert.deepStrictEqual(reloaded.executionContract, EXPECTED_RELOADED_CONTRACT);
|
|
443
|
+
// The derived criteria replace the old local override — never merge with it.
|
|
444
|
+
assert.ok(reloaded.acceptanceCriteria.includes("- [ ] Reloaded ticket compiles into the refreshed structured schema"));
|
|
445
|
+
assert.ok(!reloaded.acceptanceCriteria.includes("Operator-typed override"));
|
|
446
|
+
assert.equal(reloaded.externalId, "linear-uuid-1");
|
|
447
|
+
assert.equal(reloaded.externalLabel, "NOT-123");
|
|
448
|
+
// One `issue.source_reloaded` event naming Linear with digests, never the text.
|
|
449
|
+
const reloadEvents = listWorkflowEventsForIssue(id).filter((e) => e.type === "issue.source_reloaded");
|
|
450
|
+
assert.equal(reloadEvents.length, 1);
|
|
451
|
+
const payload = JSON.parse(reloadEvents[0].payloadJson);
|
|
452
|
+
assert.equal(payload.source, "linear");
|
|
453
|
+
assert.equal(payload.externalId, "linear-uuid-1");
|
|
454
|
+
assert.equal(payload.externalLabel, "NOT-123");
|
|
455
|
+
for (const key of ["prevTitleDigest", "newTitleDigest", "prevDescriptionDigest", "newDescriptionDigest"]) {
|
|
456
|
+
assert.match(String(payload[key]), /^[0-9a-f]{16}$/, key);
|
|
457
|
+
}
|
|
458
|
+
assert.notEqual(payload.prevTitleDigest, payload.newTitleDigest);
|
|
459
|
+
assert.notEqual(payload.prevDescriptionDigest, payload.newDescriptionDigest);
|
|
460
|
+
assert.ok(!reloadEvents[0].payloadJson.includes("Refreshed title from Linear"));
|
|
461
|
+
assert.ok(!reloadEvents[0].payloadJson.includes("The Planner improved the ticket"));
|
|
462
|
+
await app.close();
|
|
463
|
+
});
|
|
464
|
+
test("NOT-363: reload preserves configuration and the exact queue position", async () => {
|
|
465
|
+
resetLinearMock(linearNodeForTest(RELOADED_DESCRIPTION));
|
|
466
|
+
const app = await buildApp();
|
|
467
|
+
const first = (await app.inject({
|
|
468
|
+
method: "POST",
|
|
469
|
+
url: "/api/issues",
|
|
470
|
+
payload: {
|
|
471
|
+
...LINEAR_ISSUE_PAYLOAD,
|
|
472
|
+
baseBranch: "develop",
|
|
473
|
+
maxReviewRounds: 5,
|
|
474
|
+
maxInfraAttempts: 1,
|
|
475
|
+
autoMerge: true,
|
|
476
|
+
},
|
|
477
|
+
})).json();
|
|
478
|
+
const second = (await app.inject({
|
|
479
|
+
method: "POST",
|
|
480
|
+
url: "/api/issues",
|
|
481
|
+
payload: { title: "Queue neighbor", description: "plain", ...BASE_AGENTS },
|
|
482
|
+
})).json();
|
|
483
|
+
const before = getIssue(first.id);
|
|
484
|
+
const positionOf = (issueId) => queueStatusForIssue(issueId)?.position ?? null;
|
|
485
|
+
const beforePositions = [positionOf(first.id), positionOf(second.id)];
|
|
486
|
+
const res = await app.inject({ method: "POST", url: `/api/issues/${first.id}/reload-source` });
|
|
487
|
+
assert.equal(res.statusCode, 200, res.body);
|
|
488
|
+
const after = getIssue(first.id);
|
|
489
|
+
assert.equal(after.repo, before.repo);
|
|
490
|
+
assert.equal(after.baseBranch, "develop");
|
|
491
|
+
assert.equal(after.developerAgentId, before.developerAgentId);
|
|
492
|
+
assert.equal(after.reviewerAgentId, before.reviewerAgentId);
|
|
493
|
+
assert.equal(after.maxReviewRounds, 5);
|
|
494
|
+
assert.equal(after.maxInfraAttempts, 1);
|
|
495
|
+
assert.equal(after.autoMerge, true);
|
|
496
|
+
assert.equal(after.source, "linear");
|
|
497
|
+
assert.equal(after.externalId, before.externalId);
|
|
498
|
+
assert.equal(after.externalLabel, before.externalLabel);
|
|
499
|
+
assert.equal(after.externalUrl, before.externalUrl);
|
|
500
|
+
assert.equal(after.status, "ready");
|
|
501
|
+
assert.deepStrictEqual([positionOf(first.id), positionOf(second.id)], beforePositions);
|
|
502
|
+
await app.close();
|
|
503
|
+
});
|
|
504
|
+
test("NOT-363: reload refuses a manual issue with 400 and writes nothing", async () => {
|
|
505
|
+
resetLinearMock(linearNodeForTest(RELOADED_DESCRIPTION));
|
|
506
|
+
const app = await buildApp();
|
|
507
|
+
const created = (await app.inject({
|
|
508
|
+
method: "POST",
|
|
509
|
+
url: "/api/issues",
|
|
510
|
+
payload: { title: "Manual", description: "plain", acceptanceCriteria: "It works", ...BASE_AGENTS },
|
|
511
|
+
})).json();
|
|
512
|
+
const res = await app.inject({ method: "POST", url: `/api/issues/${created.id}/reload-source` });
|
|
513
|
+
assert.equal(res.statusCode, 400, res.body);
|
|
514
|
+
const untouched = getIssue(created.id);
|
|
515
|
+
assert.equal(untouched.title, "Manual");
|
|
516
|
+
assert.equal(untouched.description, "plain");
|
|
517
|
+
assert.equal(untouched.acceptanceCriteria, "It works");
|
|
518
|
+
assert.ok(!listWorkflowEventsForIssue(created.id).some((e) => e.type === "issue.source_reloaded"));
|
|
519
|
+
await app.close();
|
|
520
|
+
});
|
|
521
|
+
test("NOT-363: reload refuses after admission with 409 and writes nothing", async () => {
|
|
522
|
+
resetLinearMock(linearNodeForTest(RELOADED_DESCRIPTION));
|
|
523
|
+
const app = await buildApp();
|
|
524
|
+
const { id } = await createLinearIssue(app);
|
|
525
|
+
// Force-admit: POST /start only queues when no slot is free, so admit
|
|
526
|
+
// through the coordinator core — status leaves `ready` and the workflow
|
|
527
|
+
// instance goes active, which is what the reload must refuse on.
|
|
528
|
+
const { startWorkflow } = await import("../coordinator/commands.js");
|
|
529
|
+
assert.equal(startWorkflow(id).ok, true);
|
|
530
|
+
const res = await app.inject({ method: "POST", url: `/api/issues/${id}/reload-source` });
|
|
531
|
+
assert.equal(res.statusCode, 409, res.body);
|
|
532
|
+
const untouched = getIssue(id);
|
|
533
|
+
assert.equal(untouched.title, LINEAR_ISSUE_PAYLOAD.title);
|
|
534
|
+
assert.equal(untouched.description, CONTRACT_DESCRIPTION);
|
|
535
|
+
assert.equal(untouched.acceptanceCriteria, LINEAR_ISSUE_PAYLOAD.acceptanceCriteria);
|
|
536
|
+
assert.ok(!listWorkflowEventsForIssue(id).some((e) => e.type === "issue.source_reloaded"));
|
|
537
|
+
await app.close();
|
|
538
|
+
});
|
|
539
|
+
test("NOT-363: reload refuses while a worker session runs with 409 and writes nothing", async () => {
|
|
540
|
+
resetLinearMock(linearNodeForTest(RELOADED_DESCRIPTION));
|
|
541
|
+
const app = await buildApp();
|
|
542
|
+
const { id } = await createLinearIssue(app);
|
|
543
|
+
const session = createWorkerSession({
|
|
544
|
+
issueId: id,
|
|
545
|
+
role: "developer",
|
|
546
|
+
round: 1,
|
|
547
|
+
agentId: BUILTIN_AGENT_CLAUDE_ID,
|
|
548
|
+
runtime: "claude_code",
|
|
549
|
+
});
|
|
550
|
+
startSession(session.id);
|
|
551
|
+
const res = await app.inject({ method: "POST", url: `/api/issues/${id}/reload-source` });
|
|
552
|
+
assert.equal(res.statusCode, 409, res.body);
|
|
553
|
+
assert.match(res.json().error, /running session/);
|
|
554
|
+
const untouched = getIssue(id);
|
|
555
|
+
assert.equal(untouched.title, LINEAR_ISSUE_PAYLOAD.title);
|
|
556
|
+
assert.equal(untouched.description, CONTRACT_DESCRIPTION);
|
|
557
|
+
assert.ok(!listWorkflowEventsForIssue(id).some((e) => e.type === "issue.source_reloaded"));
|
|
558
|
+
await app.close();
|
|
559
|
+
});
|
|
560
|
+
test("NOT-363: a Linear fetch failure leaves task fields and queue state unchanged", async () => {
|
|
561
|
+
resetLinearMock(null, true);
|
|
562
|
+
const app = await buildApp();
|
|
563
|
+
const { id } = await createLinearIssue(app);
|
|
564
|
+
const positionBefore = queueStatusForIssue(id);
|
|
565
|
+
const res = await app.inject({ method: "POST", url: `/api/issues/${id}/reload-source` });
|
|
566
|
+
assert.equal(res.statusCode, 502, res.body);
|
|
567
|
+
const untouched = getIssue(id);
|
|
568
|
+
assert.equal(untouched.title, LINEAR_ISSUE_PAYLOAD.title);
|
|
569
|
+
assert.equal(untouched.description, CONTRACT_DESCRIPTION);
|
|
570
|
+
assert.equal(untouched.acceptanceCriteria, LINEAR_ISSUE_PAYLOAD.acceptanceCriteria);
|
|
571
|
+
assert.equal(queueStatusForIssue(id)?.position, positionBefore?.position);
|
|
572
|
+
assert.ok(!listWorkflowEventsForIssue(id).some((e) => e.type === "issue.source_reloaded"));
|
|
573
|
+
await app.close();
|
|
574
|
+
});
|
|
575
|
+
test("NOT-363: a contract-validation failure on refreshed text leaves everything unchanged", async () => {
|
|
576
|
+
resetLinearMock(linearNodeForTest(CONTRACT_DESCRIPTION.replace("feature", "teleport")));
|
|
577
|
+
const app = await buildApp();
|
|
578
|
+
const { id } = await createLinearIssue(app);
|
|
579
|
+
const positionBefore = queueStatusForIssue(id);
|
|
580
|
+
const res = await app.inject({ method: "POST", url: `/api/issues/${id}/reload-source` });
|
|
581
|
+
assert.equal(res.statusCode, 400, res.body);
|
|
582
|
+
assert.match(res.json().error, /unknown execution mode/);
|
|
583
|
+
const untouched = getIssue(id);
|
|
584
|
+
assert.equal(untouched.title, LINEAR_ISSUE_PAYLOAD.title);
|
|
585
|
+
assert.equal(untouched.description, CONTRACT_DESCRIPTION);
|
|
586
|
+
assert.equal(untouched.acceptanceCriteria, LINEAR_ISSUE_PAYLOAD.acceptanceCriteria);
|
|
587
|
+
assert.equal(queueStatusForIssue(id)?.position, positionBefore?.position);
|
|
588
|
+
assert.ok(!listWorkflowEventsForIssue(id).some((e) => e.type === "issue.source_reloaded"));
|
|
589
|
+
await app.close();
|
|
590
|
+
});
|
|
591
|
+
test("NOT-363: the frozen snapshot after reload contains exactly the refreshed text", async () => {
|
|
592
|
+
resetLinearMock(linearNodeForTest(RELOADED_DESCRIPTION));
|
|
593
|
+
const app = await buildApp();
|
|
594
|
+
const { startWorkflow } = await import("../coordinator/commands.js");
|
|
595
|
+
const { id } = await createLinearIssue(app);
|
|
596
|
+
assert.equal((await app.inject({ method: "POST", url: `/api/issues/${id}/reload-source` })).statusCode, 200);
|
|
597
|
+
assert.equal(startWorkflow(id).ok, true);
|
|
598
|
+
const frozen = getTaskSnapshot(getIssue(id));
|
|
599
|
+
assert.equal(frozen.description, RELOADED_DESCRIPTION);
|
|
600
|
+
assert.ok(frozen.acceptanceCriteria.includes("- [ ] Reloaded ticket compiles into the refreshed structured schema"));
|
|
601
|
+
assert.ok(!frozen.acceptanceCriteria.includes("Operator-typed override"));
|
|
602
|
+
assert.deepStrictEqual(frozen.executionContract, EXPECTED_RELOADED_CONTRACT);
|
|
603
|
+
// And the local-edit path freezes exactly what was saved, too.
|
|
604
|
+
resetLinearMock(linearNodeForTest(RELOADED_DESCRIPTION));
|
|
605
|
+
const edited = (await app.inject({
|
|
606
|
+
method: "POST",
|
|
607
|
+
url: "/api/issues",
|
|
608
|
+
payload: { title: "Local edit path", description: "plain", acceptanceCriteria: "saved locally", ...BASE_AGENTS },
|
|
609
|
+
})).json();
|
|
610
|
+
assert.equal((await app.inject({ method: "PATCH", url: `/api/issues/${edited.id}`, payload: { description: RELOADED_DESCRIPTION } }))
|
|
611
|
+
.statusCode, 200);
|
|
612
|
+
assert.equal(startWorkflow(edited.id).ok, true);
|
|
613
|
+
const frozenEdited = getTaskSnapshot(getIssue(edited.id));
|
|
614
|
+
assert.equal(frozenEdited.description, RELOADED_DESCRIPTION);
|
|
615
|
+
assert.deepStrictEqual(frozenEdited.executionContract, EXPECTED_RELOADED_CONTRACT);
|
|
616
|
+
await app.close();
|
|
617
|
+
});
|