@auto-engineer/server-generator-apollo-emmett 1.146.0 → 1.148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +6 -6
  3. package/.turbo/turbo-type-check.log +1 -1
  4. package/CHANGELOG.md +68 -0
  5. package/dist/src/codegen/extract/messages.d.ts.map +1 -1
  6. package/dist/src/codegen/extract/messages.js +4 -12
  7. package/dist/src/codegen/extract/messages.js.map +1 -1
  8. package/dist/src/codegen/scaffoldFromSchema.d.ts +1 -11
  9. package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
  10. package/dist/src/codegen/scaffoldFromSchema.js +7 -38
  11. package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
  12. package/dist/src/codegen/templateHelpers.d.ts +11 -0
  13. package/dist/src/codegen/templateHelpers.d.ts.map +1 -0
  14. package/dist/src/codegen/templateHelpers.js +47 -0
  15. package/dist/src/codegen/templateHelpers.js.map +1 -0
  16. package/dist/src/codegen/templates/command/decide.specs.specs.ts +662 -0
  17. package/dist/src/codegen/templates/command/decide.specs.ts +1 -10
  18. package/dist/src/codegen/templates/command/decide.specs.ts.ejs +3 -47
  19. package/dist/src/codegen/templates/command/decide.ts.ejs +31 -2
  20. package/dist/src/codegen/templates/command/handle.specs.ts +0 -201
  21. package/dist/src/codegen/templates/command/handle.ts.ejs +0 -34
  22. package/dist/src/codegen/templates/command/state.specs.ts +89 -0
  23. package/dist/src/codegen/templates/command/state.ts.ejs +20 -0
  24. package/dist/src/codegen/templates/query/events.specs.ts +133 -0
  25. package/dist/tsconfig.tsbuildinfo +1 -1
  26. package/ketchup-plan.md +5 -4
  27. package/package.json +4 -4
  28. package/src/codegen/extract/messages.specs.ts +9 -8
  29. package/src/codegen/extract/messages.ts +4 -12
  30. package/src/codegen/extract/states.specs.ts +53 -0
  31. package/src/codegen/scaffoldFromSchema.ts +7 -58
  32. package/src/codegen/templateHelpers.specs.ts +98 -0
  33. package/src/codegen/templateHelpers.ts +58 -0
  34. package/src/codegen/templates/command/decide.specs.specs.ts +662 -0
  35. package/src/codegen/templates/command/decide.specs.ts +1 -10
  36. package/src/codegen/templates/command/decide.specs.ts.ejs +3 -47
  37. package/src/codegen/templates/command/decide.ts.ejs +31 -2
  38. package/src/codegen/templates/command/handle.specs.ts +0 -201
  39. package/src/codegen/templates/command/handle.ts.ejs +0 -34
  40. package/src/codegen/templates/command/state.specs.ts +89 -0
  41. package/src/codegen/templates/command/state.ts.ejs +20 -0
  42. package/src/codegen/templates/query/events.specs.ts +133 -0
  43. package/src/codegen/buildCrossSceneGivens.specs.ts +0 -263
@@ -1388,6 +1388,668 @@ describe('spec.ts.ejs', () => {
1388
1388
  expect(decideFile?.contents).not.toContain('`NotFoundError`');
1389
1389
  });
1390
1390
 
1391
+ it('should omit state-based Given refs from given() array', async () => {
1392
+ const spec: SpecsSchema = {
1393
+ variant: 'specs',
1394
+ scenes: [
1395
+ {
1396
+ name: 'Fitness scene',
1397
+ moments: [
1398
+ {
1399
+ type: 'command',
1400
+ name: 'Log workout',
1401
+ stream: 'workout',
1402
+ client: { specs: [] },
1403
+ server: {
1404
+ description: '',
1405
+ specs: [
1406
+ {
1407
+ type: 'gherkin',
1408
+ feature: 'Log workout',
1409
+ rules: [
1410
+ {
1411
+ name: 'Should log workout',
1412
+ examples: [
1413
+ {
1414
+ name: 'Workout logged from draft state',
1415
+ steps: [
1416
+ {
1417
+ keyword: 'Given',
1418
+ text: 'WorkoutDraft',
1419
+ docString: { memberId: 'mem_001', date: '2024-01-15' },
1420
+ },
1421
+ {
1422
+ keyword: 'When',
1423
+ text: 'LogWorkout',
1424
+ docString: { memberId: 'mem_001', date: '2024-01-15' },
1425
+ },
1426
+ {
1427
+ keyword: 'Then',
1428
+ text: 'WorkoutLogged',
1429
+ docString: { memberId: 'mem_001', date: '2024-01-15' },
1430
+ },
1431
+ ],
1432
+ },
1433
+ ],
1434
+ },
1435
+ ],
1436
+ },
1437
+ ],
1438
+ },
1439
+ },
1440
+ ],
1441
+ },
1442
+ ],
1443
+ messages: [
1444
+ {
1445
+ type: 'state',
1446
+ name: 'WorkoutDraft',
1447
+ fields: [
1448
+ { name: 'memberId', type: 'string', required: true },
1449
+ { name: 'date', type: 'string', required: true },
1450
+ ],
1451
+ },
1452
+ {
1453
+ type: 'command',
1454
+ name: 'LogWorkout',
1455
+ fields: [
1456
+ { name: 'memberId', type: 'string', required: true },
1457
+ { name: 'date', type: 'string', required: true },
1458
+ ],
1459
+ },
1460
+ {
1461
+ type: 'event',
1462
+ name: 'WorkoutLogged',
1463
+ source: 'internal',
1464
+ fields: [
1465
+ { name: 'memberId', type: 'string', required: true },
1466
+ { name: 'date', type: 'string', required: true },
1467
+ ],
1468
+ },
1469
+ ],
1470
+ };
1471
+
1472
+ const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
1473
+ const specFile = plans.find((p) => p.outputPath.endsWith('specs.ts'));
1474
+ const contents = specFile?.contents ?? '';
1475
+
1476
+ expect(contents).toContain('given([])');
1477
+ expect(contents).not.toContain('WorkoutDraft');
1478
+ });
1479
+
1480
+ it('should keep event Given refs and omit state Given refs when mixed', async () => {
1481
+ const spec: SpecsSchema = {
1482
+ variant: 'specs',
1483
+ scenes: [
1484
+ {
1485
+ name: 'Fitness scene',
1486
+ moments: [
1487
+ {
1488
+ type: 'command',
1489
+ name: 'Log workout',
1490
+ stream: 'workout',
1491
+ client: { specs: [] },
1492
+ server: {
1493
+ description: '',
1494
+ specs: [
1495
+ {
1496
+ type: 'gherkin',
1497
+ feature: 'Log workout',
1498
+ rules: [
1499
+ {
1500
+ name: 'Should log workout',
1501
+ examples: [
1502
+ {
1503
+ name: 'Workout logged with prior event and draft state',
1504
+ steps: [
1505
+ {
1506
+ keyword: 'Given',
1507
+ text: 'OrderPlaced',
1508
+ docString: { orderId: 'o1' },
1509
+ },
1510
+ {
1511
+ keyword: 'Given',
1512
+ text: 'WorkoutDraft',
1513
+ docString: { memberId: 'mem_001', date: '2024-01-15' },
1514
+ },
1515
+ {
1516
+ keyword: 'When',
1517
+ text: 'LogWorkout',
1518
+ docString: { memberId: 'mem_001', date: '2024-01-15' },
1519
+ },
1520
+ {
1521
+ keyword: 'Then',
1522
+ text: 'WorkoutLogged',
1523
+ docString: { memberId: 'mem_001', date: '2024-01-15' },
1524
+ },
1525
+ ],
1526
+ },
1527
+ ],
1528
+ },
1529
+ ],
1530
+ },
1531
+ ],
1532
+ },
1533
+ },
1534
+ ],
1535
+ },
1536
+ ],
1537
+ messages: [
1538
+ {
1539
+ type: 'event',
1540
+ name: 'OrderPlaced',
1541
+ source: 'internal',
1542
+ fields: [{ name: 'orderId', type: 'string', required: true }],
1543
+ },
1544
+ {
1545
+ type: 'state',
1546
+ name: 'WorkoutDraft',
1547
+ fields: [
1548
+ { name: 'memberId', type: 'string', required: true },
1549
+ { name: 'date', type: 'string', required: true },
1550
+ ],
1551
+ },
1552
+ {
1553
+ type: 'command',
1554
+ name: 'LogWorkout',
1555
+ fields: [
1556
+ { name: 'memberId', type: 'string', required: true },
1557
+ { name: 'date', type: 'string', required: true },
1558
+ ],
1559
+ },
1560
+ {
1561
+ type: 'event',
1562
+ name: 'WorkoutLogged',
1563
+ source: 'internal',
1564
+ fields: [
1565
+ { name: 'memberId', type: 'string', required: true },
1566
+ { name: 'date', type: 'string', required: true },
1567
+ ],
1568
+ },
1569
+ ],
1570
+ };
1571
+
1572
+ const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
1573
+ const specFile = plans.find((p) => p.outputPath.endsWith('specs.ts'));
1574
+ const contents = specFile?.contents ?? '';
1575
+
1576
+ expect(contents).toContain("type: 'OrderPlaced'");
1577
+ expect(contents).not.toContain('WorkoutDraft');
1578
+ });
1579
+
1580
+ it('should generate "directly emit" instructions in decide.ts when Given has only state refs', async () => {
1581
+ const spec: SpecsSchema = {
1582
+ variant: 'specs',
1583
+ scenes: [
1584
+ {
1585
+ name: 'Fitness scene',
1586
+ moments: [
1587
+ {
1588
+ type: 'command',
1589
+ name: 'Log workout',
1590
+ stream: 'workout',
1591
+ client: { specs: [] },
1592
+ server: {
1593
+ description: '',
1594
+ specs: [
1595
+ {
1596
+ type: 'gherkin',
1597
+ feature: 'Log workout',
1598
+ rules: [
1599
+ {
1600
+ name: 'Should log workout',
1601
+ examples: [
1602
+ {
1603
+ name: 'Workout logged from draft state',
1604
+ steps: [
1605
+ {
1606
+ keyword: 'Given',
1607
+ text: 'WorkoutDraft',
1608
+ docString: { memberId: 'mem_001', date: '2024-01-15' },
1609
+ },
1610
+ {
1611
+ keyword: 'When',
1612
+ text: 'LogWorkout',
1613
+ docString: { memberId: 'mem_001', date: '2024-01-15' },
1614
+ },
1615
+ {
1616
+ keyword: 'Then',
1617
+ text: 'WorkoutLogged',
1618
+ docString: { memberId: 'mem_001', date: '2024-01-15' },
1619
+ },
1620
+ ],
1621
+ },
1622
+ ],
1623
+ },
1624
+ ],
1625
+ },
1626
+ ],
1627
+ },
1628
+ },
1629
+ ],
1630
+ },
1631
+ ],
1632
+ messages: [
1633
+ {
1634
+ type: 'state',
1635
+ name: 'WorkoutDraft',
1636
+ fields: [
1637
+ { name: 'memberId', type: 'string', required: true },
1638
+ { name: 'date', type: 'string', required: true },
1639
+ ],
1640
+ },
1641
+ {
1642
+ type: 'command',
1643
+ name: 'LogWorkout',
1644
+ fields: [
1645
+ { name: 'memberId', type: 'string', required: true },
1646
+ { name: 'date', type: 'string', required: true },
1647
+ ],
1648
+ },
1649
+ {
1650
+ type: 'event',
1651
+ name: 'WorkoutLogged',
1652
+ source: 'internal',
1653
+ fields: [
1654
+ { name: 'memberId', type: 'string', required: true },
1655
+ { name: 'date', type: 'string', required: true },
1656
+ ],
1657
+ },
1658
+ ],
1659
+ };
1660
+
1661
+ const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
1662
+ const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
1663
+ const contents = decideFile?.contents ?? '';
1664
+
1665
+ expect(contents).toContain('can directly emit');
1666
+ expect(contents).not.toContain('requires evaluating prior state');
1667
+ expect(contents).not.toContain('Inspect the current domain');
1668
+ expect(contents).not.toContain('If State is a discriminated union');
1669
+ });
1670
+
1671
+ it('should generate state-narrowing instructions in decide.ts when Given has event refs', async () => {
1672
+ const spec: SpecsSchema = {
1673
+ variant: 'specs',
1674
+ scenes: [
1675
+ {
1676
+ name: 'Guest removes a listing',
1677
+ moments: [
1678
+ {
1679
+ type: 'command',
1680
+ name: 'Remove listing',
1681
+ client: { specs: [] },
1682
+ server: {
1683
+ description: '',
1684
+ specs: [
1685
+ {
1686
+ type: 'gherkin',
1687
+ feature: 'Remove listing spec',
1688
+ rules: [
1689
+ {
1690
+ name: 'Should remove existing listing',
1691
+ examples: [
1692
+ {
1693
+ name: 'Existing listing can be removed',
1694
+ steps: [
1695
+ {
1696
+ keyword: 'Given',
1697
+ text: 'ListingCreated',
1698
+ docString: {
1699
+ propertyId: 'listing_123',
1700
+ listedAt: '2024-01-15T10:00:00Z',
1701
+ rating: 4.8,
1702
+ metadata: { foo: 'bar' },
1703
+ },
1704
+ },
1705
+ {
1706
+ keyword: 'When',
1707
+ text: 'RemoveListing',
1708
+ docString: { propertyId: 'listing_123' },
1709
+ },
1710
+ {
1711
+ keyword: 'Then',
1712
+ text: 'ListingRemoved',
1713
+ docString: {
1714
+ propertyId: 'listing_123',
1715
+ removedAt: '2024-01-16T10:00:00Z',
1716
+ },
1717
+ },
1718
+ ],
1719
+ },
1720
+ ],
1721
+ },
1722
+ ],
1723
+ },
1724
+ ],
1725
+ },
1726
+ },
1727
+ ],
1728
+ },
1729
+ ],
1730
+ messages: [
1731
+ {
1732
+ type: 'command',
1733
+ name: 'RemoveListing',
1734
+ fields: [{ name: 'propertyId', type: 'string', required: true }],
1735
+ },
1736
+ {
1737
+ type: 'event',
1738
+ name: 'ListingCreated',
1739
+ source: 'internal',
1740
+ fields: [
1741
+ { name: 'propertyId', type: 'string', required: true },
1742
+ { name: 'listedAt', type: 'Date', required: true },
1743
+ { name: 'rating', type: 'number', required: true },
1744
+ { name: 'metadata', type: 'object', required: true },
1745
+ ],
1746
+ },
1747
+ {
1748
+ type: 'event',
1749
+ name: 'ListingRemoved',
1750
+ source: 'internal',
1751
+ fields: [
1752
+ { name: 'propertyId', type: 'string', required: true },
1753
+ { name: 'removedAt', type: 'Date', required: true },
1754
+ ],
1755
+ },
1756
+ ],
1757
+ };
1758
+
1759
+ const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
1760
+ const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
1761
+ const contents = decideFile?.contents ?? '';
1762
+
1763
+ expect(contents).toContain('requires evaluating prior state');
1764
+ expect(contents).toContain('Inspect the current domain');
1765
+ expect(contents).toContain('If State is a discriminated union');
1766
+ expect(contents).not.toContain('can directly emit');
1767
+ });
1768
+
1769
+ it('should include "do not narrow state" instruction when Given has only state refs', async () => {
1770
+ const spec: SpecsSchema = {
1771
+ variant: 'specs',
1772
+ scenes: [
1773
+ {
1774
+ name: 'Profile scene',
1775
+ moments: [
1776
+ {
1777
+ type: 'command',
1778
+ name: 'Update profile',
1779
+ stream: 'profile',
1780
+ client: { specs: [] },
1781
+ server: {
1782
+ description: '',
1783
+ specs: [
1784
+ {
1785
+ type: 'gherkin',
1786
+ feature: 'Update profile',
1787
+ rules: [
1788
+ {
1789
+ name: 'Should update profile',
1790
+ examples: [
1791
+ {
1792
+ name: 'Profile updated from created state',
1793
+ steps: [
1794
+ {
1795
+ keyword: 'Given',
1796
+ text: 'CreatedProfile',
1797
+ docString: { userId: 'u1', status: 'created' },
1798
+ },
1799
+ {
1800
+ keyword: 'When',
1801
+ text: 'UpdateProfile',
1802
+ docString: { userId: 'u1', displayName: 'Alice' },
1803
+ },
1804
+ {
1805
+ keyword: 'Then',
1806
+ text: 'ProfileUpdated',
1807
+ docString: { userId: 'u1', displayName: 'Alice' },
1808
+ },
1809
+ ],
1810
+ },
1811
+ ],
1812
+ },
1813
+ ],
1814
+ },
1815
+ ],
1816
+ },
1817
+ },
1818
+ ],
1819
+ },
1820
+ ],
1821
+ messages: [
1822
+ {
1823
+ type: 'state',
1824
+ name: 'CreatedProfile',
1825
+ fields: [
1826
+ { name: 'userId', type: 'string', required: true },
1827
+ { name: 'status', type: 'string', required: true },
1828
+ ],
1829
+ },
1830
+ {
1831
+ type: 'command',
1832
+ name: 'UpdateProfile',
1833
+ fields: [
1834
+ { name: 'userId', type: 'string', required: true },
1835
+ { name: 'displayName', type: 'string', required: true },
1836
+ ],
1837
+ },
1838
+ {
1839
+ type: 'event',
1840
+ name: 'ProfileUpdated',
1841
+ source: 'internal',
1842
+ fields: [
1843
+ { name: 'userId', type: 'string', required: true },
1844
+ { name: 'displayName', type: 'string', required: true },
1845
+ ],
1846
+ },
1847
+ ],
1848
+ };
1849
+
1850
+ const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
1851
+ const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
1852
+ const contents = decideFile?.contents ?? '';
1853
+
1854
+ expect(contents).toContain('Do not narrow or guard on _state');
1855
+ expect(contents).not.toContain('Inspect the current domain');
1856
+ expect(contents).not.toContain('If State is a discriminated union');
1857
+ });
1858
+
1859
+ it('should generate date and untested-field instructions for nonCommandFields with state-only Given', async () => {
1860
+ const spec: SpecsSchema = {
1861
+ variant: 'specs',
1862
+ scenes: [
1863
+ {
1864
+ name: 'Session scene',
1865
+ moments: [
1866
+ {
1867
+ type: 'command',
1868
+ name: 'Complete session',
1869
+ stream: 'session',
1870
+ client: { specs: [] },
1871
+ server: {
1872
+ description: '',
1873
+ specs: [
1874
+ {
1875
+ type: 'gherkin',
1876
+ feature: 'Complete session',
1877
+ rules: [
1878
+ {
1879
+ name: 'Should complete session',
1880
+ examples: [
1881
+ {
1882
+ name: 'Session completed',
1883
+ steps: [
1884
+ {
1885
+ keyword: 'Given',
1886
+ text: 'ActiveSession',
1887
+ docString: { sessionId: 's1', status: 'active' },
1888
+ },
1889
+ {
1890
+ keyword: 'When',
1891
+ text: 'CompleteSession',
1892
+ docString: { sessionId: 's1', duration: 30 },
1893
+ },
1894
+ {
1895
+ keyword: 'Then',
1896
+ text: 'SessionCompleted',
1897
+ docString: {
1898
+ sessionId: 's1',
1899
+ userId: 'u1',
1900
+ date: '2024-01-01',
1901
+ type: 'workout',
1902
+ duration: 30,
1903
+ },
1904
+ },
1905
+ ],
1906
+ },
1907
+ ],
1908
+ },
1909
+ ],
1910
+ },
1911
+ ],
1912
+ },
1913
+ },
1914
+ ],
1915
+ },
1916
+ ],
1917
+ messages: [
1918
+ {
1919
+ type: 'state',
1920
+ name: 'ActiveSession',
1921
+ fields: [
1922
+ { name: 'sessionId', type: 'string', required: true },
1923
+ { name: 'status', type: 'string', required: true },
1924
+ ],
1925
+ },
1926
+ {
1927
+ type: 'command',
1928
+ name: 'CompleteSession',
1929
+ fields: [
1930
+ { name: 'sessionId', type: 'string', required: true },
1931
+ { name: 'duration', type: 'number', required: true },
1932
+ ],
1933
+ },
1934
+ {
1935
+ type: 'event',
1936
+ name: 'SessionCompleted',
1937
+ source: 'internal',
1938
+ fields: [
1939
+ { name: 'sessionId', type: 'string', required: true },
1940
+ { name: 'userId', type: 'string', required: true },
1941
+ { name: 'date', type: 'string', required: true },
1942
+ { name: 'type', type: 'string', required: true },
1943
+ { name: 'duration', type: 'number', required: true },
1944
+ ],
1945
+ },
1946
+ ],
1947
+ };
1948
+
1949
+ const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
1950
+ const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
1951
+ const contents = decideFile?.contents ?? '';
1952
+
1953
+ expect(contents).toContain('date: string — derive from command.metadata?.now');
1954
+ expect(contents).toContain('userId: string — not asserted by test');
1955
+ expect(contents).toContain('type: string — not asserted by test');
1956
+ });
1957
+
1958
+ it('should keep generic _state instruction for nonCommandFields when Given has event refs', async () => {
1959
+ const spec: SpecsSchema = {
1960
+ variant: 'specs',
1961
+ scenes: [
1962
+ {
1963
+ name: 'Host removes a listing',
1964
+ moments: [
1965
+ {
1966
+ type: 'command',
1967
+ name: 'Remove listing',
1968
+ client: { specs: [] },
1969
+ server: {
1970
+ description: '',
1971
+ specs: [
1972
+ {
1973
+ type: 'gherkin',
1974
+ feature: 'Remove listing command',
1975
+ rules: [
1976
+ {
1977
+ name: 'Should remove existing listing',
1978
+ examples: [
1979
+ {
1980
+ name: 'Existing listing can be removed',
1981
+ steps: [
1982
+ {
1983
+ keyword: 'Given',
1984
+ text: 'ListingCreated',
1985
+ docString: {
1986
+ propertyId: 'listing_123',
1987
+ listedAt: '2024-01-15T10:00:00Z',
1988
+ },
1989
+ },
1990
+ {
1991
+ keyword: 'When',
1992
+ text: 'RemoveListing',
1993
+ docString: { propertyId: 'listing_123' },
1994
+ },
1995
+ {
1996
+ keyword: 'Then',
1997
+ text: 'ListingRemoved',
1998
+ docString: {
1999
+ propertyId: 'listing_123',
2000
+ removedAt: '2024-01-16',
2001
+ removedBy: 'host_abc',
2002
+ },
2003
+ },
2004
+ ],
2005
+ },
2006
+ ],
2007
+ },
2008
+ ],
2009
+ },
2010
+ ],
2011
+ },
2012
+ },
2013
+ ],
2014
+ },
2015
+ ],
2016
+ messages: [
2017
+ {
2018
+ type: 'command',
2019
+ name: 'RemoveListing',
2020
+ fields: [{ name: 'propertyId', type: 'string', required: true }],
2021
+ },
2022
+ {
2023
+ type: 'event',
2024
+ name: 'ListingCreated',
2025
+ source: 'internal',
2026
+ fields: [
2027
+ { name: 'propertyId', type: 'string', required: true },
2028
+ { name: 'listedAt', type: 'Date', required: true },
2029
+ ],
2030
+ },
2031
+ {
2032
+ type: 'event',
2033
+ name: 'ListingRemoved',
2034
+ source: 'internal',
2035
+ fields: [
2036
+ { name: 'propertyId', type: 'string', required: true },
2037
+ { name: 'removedAt', type: 'string', required: true },
2038
+ { name: 'removedBy', type: 'string', required: true },
2039
+ ],
2040
+ },
2041
+ ],
2042
+ };
2043
+
2044
+ const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
2045
+ const decideFile = plans.find((p) => p.outputPath.endsWith('decide.ts'));
2046
+ const contents = decideFile?.contents ?? '';
2047
+
2048
+ expect(contents).toContain('removedAt: string — derive from command.metadata?.now');
2049
+ expect(contents).toContain('removedBy: string — derive from _state');
2050
+ expect(contents).not.toContain('Do not narrow');
2051
+ });
2052
+
1391
2053
  it('should not include "valid" qualifier in fallback test description', async () => {
1392
2054
  const spec: SpecsSchema = {
1393
2055
  variant: 'specs',