cabloy 5.1.201 → 5.1.203

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 (55) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/CONTRIBUTING.md +18 -17
  4. package/README.md +2 -2
  5. package/package.json +2 -3
  6. package/repo-agent-governance/managed-assets.json +33 -33
  7. package/repo-agent-governance/policies/repository.md +1 -0
  8. package/repo-agent-governance/skills/cabloy-spec-execution/SKILL.md +3 -2
  9. package/repo-agent-governance/skills/cabloy-spec-execution/references/execution-protocol.md +1 -1
  10. package/repo-agent-governance/skills/cabloy-spec-execution/references/status-and-evidence.md +11 -10
  11. package/repo-agent-governance/skills/cabloy-spec-generation/SKILL.md +1 -1
  12. package/repo-agent-governance/skills/cabloy-spec-generation/references/canonical-spec-input.md +4 -2
  13. package/repo-agent-governance/skills/cabloy-spec-generation/references/repo-specs-document-set.md +16 -13
  14. package/repo-agent-governance/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +18 -11
  15. package/repo-agent-governance/skills/cabloy-workflow/SKILL.md +2 -2
  16. package/repo-agent-governance/skills/cabloy-workflow/evals/evals.json +1 -1
  17. package/repo-agent-governance/skills/cabloy-worktree-environment/SKILL.md +5 -5
  18. package/repo-agent-governance/tests/spec-audit.test.mjs +323 -0
  19. package/repo-agent-governance/tools/spec-audit/audit.mjs +129 -3
  20. package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.mjs +26 -7
  21. package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.test.mjs +82 -0
  22. package/repo-agent-governance/tools/spec-charts/spec-parser.mjs +40 -1
  23. package/repo-docs/ai/ai-spec-driven-development.md +1 -0
  24. package/repo-docs/ai/playbook-spec-execution.md +3 -2
  25. package/repo-docs/ai/playbook-spec-generation.md +1 -1
  26. package/repo-docs/assets/img/cabloy-admin-ssr-cover-en.png +0 -0
  27. package/repo-docs/assets/img/cabloy-admin-tabs-cover-en.png +0 -0
  28. package/repo-docs/assets/img/cabloy-ai-crud-cover-en.png +0 -0
  29. package/repo-docs/demo.md +14 -0
  30. package/repo-docs/frontend/scripts.md +10 -18
  31. package/repo-docs/fullstack/parallel-worktree-environment.md +2 -2
  32. package/repo-docs/index.md +2 -2
  33. package/repo-docs/reference/repo-scripts.md +15 -31
  34. package/repo-e2e/config/playwright.config.ts +14 -19
  35. package/repo-e2e/scripts/e2e.test.ts +17 -0
  36. package/repo-e2e/scripts/e2e.ts +5 -1
  37. package/repo-e2e/scripts/runE2e.ts +3 -15
  38. package/repo-e2e/scripts/runE2eArgs.test.ts +21 -17
  39. package/repo-e2e/scripts/runE2eArgs.ts +16 -29
  40. package/repo-e2e/scripts/startE2eVona.ts +20 -9
  41. package/repo-e2e/specs/a-commerce.spec.ts +232 -31
  42. package/repo-e2e/specs/cabloy-basic.spec.ts +39 -12
  43. package/repo-e2e/specs/home-user-account.spec.ts +9 -2
  44. package/scripts/upgrade.test.ts +147 -0
  45. package/scripts/upgrade.ts +49 -27
  46. package/vona/packages-cli/cli/package.json +1 -1
  47. package/vona/packages-cli/cli-set-api/package.json +1 -1
  48. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +51 -5
  49. package/vona/packages-cli/cli-set-api/src/lib/command/bin.dev.ts +6 -1
  50. package/vona/packages-cli/cli-set-api/test/binDevMode.test.ts +52 -0
  51. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +41 -0
  52. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/e2eFixture.ts +52 -0
  53. package/vona/src/suite/a-commerce/modules/commerce-trade/src/lib/e2eFixture.ts +224 -0
  54. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/e2eFixture.ts +694 -0
  55. package/vona/src/suite/a-commerce/modules/commerce-trade/test/e2eFixture.test.ts +434 -0
@@ -0,0 +1,224 @@
1
+ import type { IPaymentOutcomeEvent, IRefundOutcomeEvent } from 'vona-module-a-pay';
2
+ import type { EntityOutboxEvent, EntityWebhookInbox } from 'vona-module-a-pay';
3
+
4
+ import { randomUUID } from 'node:crypto';
5
+
6
+ import type { TypeOrderState } from '../entity/order.tsx';
7
+ import type { EntityOrderAudit, TypeOrderAuditOperation } from '../entity/orderAudit.tsx';
8
+ import type { EntityStockBalance } from '../entity/stockBalance.tsx';
9
+ import type { EntityStockReservation } from '../entity/stockReservation.tsx';
10
+
11
+ export const fixturePrefix = 'e2e-fixture-';
12
+
13
+ export function createOwnedFixtureName(userId: string | number) {
14
+ return `${fixturePrefix}${userId}-${randomUUID().replaceAll('-', '').slice(0, 12)}`;
15
+ }
16
+ const mockProvider = 'pay-mock:mock';
17
+
18
+ export function isOwnedFixtureName(name: string, userId: string | number) {
19
+ return new RegExp(`^${fixturePrefix}${userId}-[0-9a-f]{12}$`).test(name);
20
+ }
21
+
22
+ export function isFixtureCustomer<T extends { name: string; anonymous?: boolean }>(
23
+ user: T | undefined,
24
+ ): user is T {
25
+ return !!user && !user.anonymous && user.name.startsWith(fixturePrefix);
26
+ }
27
+
28
+ export function fixtureReservationMatchesLine(
29
+ reservation: Pick<
30
+ EntityStockReservation,
31
+ 'skuId' | 'orderLineId' | 'stockBalanceId' | 'quantity'
32
+ >,
33
+ line: { id: string | number; skuId: string | number; quantity: number },
34
+ balance: Pick<EntityStockBalance, 'id' | 'skuId'>,
35
+ ) {
36
+ return (
37
+ String(reservation.orderLineId) === String(line.id) &&
38
+ String(reservation.skuId) === String(line.skuId) &&
39
+ String(reservation.stockBalanceId) === String(balance.id) &&
40
+ String(balance.skuId) === String(line.skuId) &&
41
+ reservation.quantity === line.quantity
42
+ );
43
+ }
44
+
45
+ export function fixtureAdvancedPaymentAuditMatches(
46
+ audits: Pick<
47
+ EntityOrderAudit,
48
+ 'id' | 'orderId' | 'operation' | 'fromState' | 'toState' | 'correlationId'
49
+ >[],
50
+ orderId: string | number,
51
+ state: Extract<TypeOrderState, 'shipped' | 'refunded'>,
52
+ paymentCorrelationId: string,
53
+ shipmentCorrelationId?: string,
54
+ refundCorrelationId?: string,
55
+ ) {
56
+ const expected: Array<{
57
+ operation: TypeOrderAuditOperation;
58
+ fromState: TypeOrderState;
59
+ toState: TypeOrderState;
60
+ }> = [
61
+ { operation: 'paid', fromState: 'awaiting_payment', toState: 'paid' },
62
+ ...(state === 'shipped'
63
+ ? [{ operation: 'shipped' as const, fromState: 'paid' as const, toState: 'shipped' as const }]
64
+ : [
65
+ {
66
+ operation: 'refund_requested' as const,
67
+ fromState: 'paid' as const,
68
+ toState: 'refund_requested' as const,
69
+ },
70
+ {
71
+ operation: 'refund_approved' as const,
72
+ fromState: 'refund_requested' as const,
73
+ toState: 'refund_approved' as const,
74
+ },
75
+ {
76
+ operation: 'refunded' as const,
77
+ fromState: 'refund_approved' as const,
78
+ toState: 'refunded' as const,
79
+ },
80
+ ]),
81
+ ];
82
+ const steps = audits.filter(audit => audit.operation !== 'created');
83
+ return (
84
+ steps.length === expected.length &&
85
+ steps.every(
86
+ (audit, index) =>
87
+ String(audit.orderId) === String(orderId) &&
88
+ audit.operation === expected[index].operation &&
89
+ audit.fromState === expected[index].fromState &&
90
+ audit.toState === expected[index].toState &&
91
+ (index !== 0 || audit.correlationId === paymentCorrelationId) &&
92
+ (state !== 'shipped' || index !== 1 || audit.correlationId === shipmentCorrelationId) &&
93
+ (state !== 'refunded' || index !== 3 || audit.correlationId === refundCorrelationId),
94
+ )
95
+ );
96
+ }
97
+
98
+ export function fixtureEventNeedsDispatch(event: EntityOutboxEvent, now = new Date()) {
99
+ if (event.state === 'dispatched') {
100
+ if (
101
+ !event.dispatchedAt ||
102
+ event.claimToken ||
103
+ event.claimedAt ||
104
+ event.claimExpiresAt ||
105
+ event.errorSummary ||
106
+ event.attemptCount < 1
107
+ ) {
108
+ throw new Error('fixture event has inconsistent dispatched state');
109
+ }
110
+ return false;
111
+ }
112
+ if (
113
+ event.state !== 'pending' ||
114
+ event.attemptCount !== 0 ||
115
+ event.claimToken ||
116
+ event.claimedAt ||
117
+ event.claimExpiresAt ||
118
+ event.dispatchedAt ||
119
+ event.errorSummary ||
120
+ (event.nextAttemptAt && event.nextAttemptAt > now)
121
+ ) {
122
+ throw new Error('fixture event is not eligible for dispatch');
123
+ }
124
+ return true;
125
+ }
126
+
127
+ export function selectFixtureEvent(
128
+ events: EntityOutboxEvent[],
129
+ inboxes: EntityWebhookInbox[],
130
+ expected: {
131
+ kind: 'payment' | 'refund';
132
+ paymentSessionId: string | number;
133
+ businessReference: string;
134
+ amountMinor: number;
135
+ currency: string;
136
+ refundOperationId?: string | number;
137
+ clientName: string;
138
+ environment: 'sandbox' | 'live';
139
+ },
140
+ ) {
141
+ const eventType = `${expected.kind}.outcome.v1`;
142
+ const candidates = events.filter(
143
+ event =>
144
+ event.eventType === eventType &&
145
+ String(event.paymentSessionId) === String(expected.paymentSessionId) &&
146
+ (expected.kind === 'payment'
147
+ ? !event.refundOperationId
148
+ : String(event.refundOperationId) === String(expected.refundOperationId)),
149
+ );
150
+ if (candidates.length !== 1) throw new Error('fixture outbox event is missing or ambiguous');
151
+ const event = candidates[0];
152
+ const payload = event.payload as IPaymentOutcomeEvent | IRefundOutcomeEvent;
153
+ if (
154
+ typeof payload?.eventId !== 'string' ||
155
+ !payload.eventId.startsWith(`mock-${expected.kind}-`) ||
156
+ String(event.paymentSessionId) !== String(payload.paymentSessionId) ||
157
+ (expected.kind === 'payment'
158
+ ? !!event.refundOperationId
159
+ : String(event.refundOperationId) !==
160
+ String((payload as IRefundOutcomeEvent).refundOperationId)) ||
161
+ payload.providerName !== mockProvider ||
162
+ payload.businessReference !== expected.businessReference ||
163
+ String(payload.paymentSessionId) !== String(expected.paymentSessionId) ||
164
+ payload.amountMinor !== expected.amountMinor ||
165
+ payload.currency !== expected.currency ||
166
+ !['succeeded', 'failed', 'cancelled'].includes(payload.state) ||
167
+ (expected.kind === 'refund' &&
168
+ String((payload as IRefundOutcomeEvent).refundOperationId) !==
169
+ String(expected.refundOperationId))
170
+ ) {
171
+ throw new Error('fixture outbox payload conflicts with the mock operation');
172
+ }
173
+ const matchingInboxes = inboxes.filter(
174
+ inbox =>
175
+ inbox.providerEventId === payload.eventId &&
176
+ inbox.providerName === mockProvider &&
177
+ inbox.clientName === expected.clientName &&
178
+ inbox.environment === expected.environment,
179
+ );
180
+ if (matchingInboxes.length !== 1)
181
+ throw new Error('fixture webhook inbox is missing or ambiguous');
182
+ const inbox = matchingInboxes[0];
183
+ if (
184
+ inbox.state !== 'processed' ||
185
+ !inbox.processedAt ||
186
+ inbox.errorSummary ||
187
+ inbox.eventType !== `${expected.kind}.${payload.state}` ||
188
+ String(inbox.paymentSessionId) !== String(expected.paymentSessionId) ||
189
+ (expected.kind === 'payment'
190
+ ? !!inbox.refundOperationId || inbox.paymentState !== payload.state
191
+ : String(inbox.refundOperationId) !== String(expected.refundOperationId) ||
192
+ inbox.refundState !== payload.state) ||
193
+ inbox.amountMinor !== expected.amountMinor ||
194
+ inbox.currency !== expected.currency ||
195
+ (expected.kind === 'payment' &&
196
+ (inbox.providerCaptureId ?? undefined) !==
197
+ ((payload as IPaymentOutcomeEvent).providerCaptureId ?? undefined)) ||
198
+ (expected.kind === 'refund' &&
199
+ inbox.providerRefundId !== (payload as IRefundOutcomeEvent).providerRefundId)
200
+ ) {
201
+ throw new Error('fixture webhook inbox is not a processed matching terminal outcome');
202
+ }
203
+ return { event, payload };
204
+ }
205
+
206
+ export function selectPaymentFixtureEvent(
207
+ events: EntityOutboxEvent[],
208
+ inboxes: EntityWebhookInbox[],
209
+ expected: Omit<Parameters<typeof selectFixtureEvent>[2], 'kind' | 'refundOperationId'>,
210
+ ) {
211
+ const result = selectFixtureEvent(events, inboxes, { ...expected, kind: 'payment' });
212
+ return { ...result, payload: result.payload as IPaymentOutcomeEvent };
213
+ }
214
+
215
+ export function selectRefundFixtureEvent(
216
+ events: EntityOutboxEvent[],
217
+ inboxes: EntityWebhookInbox[],
218
+ expected: Omit<Parameters<typeof selectFixtureEvent>[2], 'kind' | 'refundOperationId'> & {
219
+ refundOperationId: string | number;
220
+ },
221
+ ) {
222
+ const result = selectFixtureEvent(events, inboxes, { ...expected, kind: 'refund' });
223
+ return { ...result, payload: result.payload as IRefundOutcomeEvent };
224
+ }