@seasonkoh/webaz 0.1.31 → 0.1.32

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 (31) hide show
  1. package/README.md +3 -1
  2. package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -1
  3. package/dist/layer1-agent/L1-1-mcp-server/network-mode.js +4 -0
  4. package/dist/layer1-agent/L1-1-mcp-server/server.js +133 -2
  5. package/dist/layer2-business/L2-6-notifications/notification-engine.js +96 -0
  6. package/dist/layer3-trust/L3-1-dispute-engine/decline-contest-resolve.js +110 -0
  7. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +109 -0
  8. package/dist/pwa/agent-order-minimal-view.js +29 -0
  9. package/dist/pwa/order-action-exec.js +158 -0
  10. package/dist/pwa/order-action-request.js +57 -0
  11. package/dist/pwa/public/app-admin-disputes.js +1 -1
  12. package/dist/pwa/public/app-agent-approvals-order.js +18 -0
  13. package/dist/pwa/public/app-agent-approvals.js +6 -7
  14. package/dist/pwa/public/app-agent-pair.js +2 -2
  15. package/dist/pwa/public/app-arbitrator-admin.js +1 -1
  16. package/dist/pwa/public/app-decline-contest-ruling.js +53 -0
  17. package/dist/pwa/public/app-decline-contest-ui.js +44 -0
  18. package/dist/pwa/public/app-grant-duration.js +20 -0
  19. package/dist/pwa/public/app.js +9 -9
  20. package/dist/pwa/public/i18n.js +24 -1
  21. package/dist/pwa/public/index.html +4 -0
  22. package/dist/pwa/public/openapi.json +68 -23
  23. package/dist/pwa/routes/admin-reports.js +40 -3
  24. package/dist/pwa/routes/agent-grants.js +117 -12
  25. package/dist/pwa/routes/disputes-read.js +17 -1
  26. package/dist/pwa/routes/disputes-write.js +42 -73
  27. package/dist/pwa/routes/orders-action.js +46 -3
  28. package/dist/pwa/server.js +3 -3
  29. package/dist/runtime/agent-grant-scopes.js +26 -8
  30. package/dist/runtime/webaz-schema-helpers.js +28 -0
  31. package/package.json +13 -2
@@ -1,11 +1,12 @@
1
1
  import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js';
2
- import { createNotification } from '../../layer2-business/L2-6-notifications/notification-engine.js';
2
+ import { createNotification, notifyDeclineContestCase } from '../../layer2-business/L2-6-notifications/notification-engine.js';
3
+ import { executeSellerOrderAction } from '../order-action-exec.js'; // RFC-021 PR3 共享执行器(api_key 路径 strictTracking=false)
3
4
  import { releaseFeeStake } from '../../direct-pay-ledger.js'; // Rail1 直付:取消/超时释放任何遗留模拟质押(AR 订单无 stake → no-op)
4
5
  import { restorePreShipDirectPayStock } from '../../direct-pay-stock.js'; // D3 库存回补唯一入口(pre-ship 放行;已出库拒绝)
5
6
  import { requireBothDisclosuresAcked } from '../../direct-pay-disclosures.js'; // PR-4e: D1/D2 披露契约门
6
7
  import { requireDirectPayHumanPasskey } from '../direct-pay-guards.js'; // PR-4e: 现场真人 Passkey/gate-token 门
7
8
  export function registerOrdersActionRoutes(app, deps) {
8
- const { db, auth, isTrustedRole, generateId, transition, notifyTransition, settleOrder, settleFault, detectFraud, createDispute, checkTimeouts, recordViolationReputation, broadcastSystemEvent, consumeGateToken } = deps;
9
+ const { db, auth, isTrustedRole, generateId, transition, notifyTransition, settleOrder, settleFault, detectFraud, createDispute, createDeclineContestDispute, checkTimeouts, recordViolationReputation, broadcastSystemEvent, consumeGateToken } = deps;
9
10
  // PR-4e: direct_p2p 风险动作门 —— ① D1/D2 两次披露都 ack(缺则 DISCLOSURE_NOT_ACKED);② 现场真人 Passkey + 一次性
10
11
  // WebAuthn gate token(purpose 固定 direct_pay_order_action,order+action 走 purpose_data + validate)。
11
12
  // 纯前置门:返回 ok 才允许后续写入。【先 disclosure(只读)再 Passkey(消费 token)】→ 缺 ack 不浪费 token。
@@ -378,8 +379,22 @@ export function registerOrdersActionRoutes(app, deps) {
378
379
  evIds.push(eid);
379
380
  }
380
381
  db.prepare("UPDATE orders SET decline_contested = 1 WHERE id = ?").run(req.params.id);
382
+ // 统一仲裁台:并入 disputes(不改 order.status)。幂等——重复举证不会建第二条。失败不阻断举证本身
383
+ // (标志位已置;backfill 脚本可事后补建),但记录以便排查。
384
+ const dc = createDeclineContestDispute(db, req.params.id);
385
+ if (!dc.success)
386
+ console.error(`[contest_decline] createDeclineContestDispute failed for ${req.params.id}: ${dc.error}`);
387
+ // PR2:通知全体 active 仲裁员 + admin 有新案(去重,只对新建行发;既有行不重复轰炸)。
388
+ else if (!dc.existing && dc.disputeId) {
389
+ try {
390
+ notifyDeclineContestCase(db, req.params.id, dc.disputeId);
391
+ }
392
+ catch (e) {
393
+ console.error(`[contest_decline] notify failed: ${e.message}`);
394
+ }
395
+ }
381
396
  return void res.json({
382
- success: true, outcome: 'contested', evidence_ids: evIds,
397
+ success: true, outcome: 'contested', evidence_ids: evIds, dispute_id: dc.disputeId,
383
398
  note: '已就客观无责拒单发起人工仲裁举证。自动终结已暂停,等待仲裁员裁决:维持→免责全退+退质押,驳回→违约结算。',
384
399
  });
385
400
  }
@@ -456,6 +471,21 @@ export function registerOrdersActionRoutes(app, deps) {
456
471
  catch { }
457
472
  return void res.json({ success: true, status: 'completed', dispute_dismissed: true });
458
473
  }
474
+ // RFC-021 PR3:accept/ship 经【共享执行器】(与 Passkey-approve 路径同一真相源;守卫全内置)。
475
+ // api_key 路径 = seller 本人直发 → strictTracking=false(保持现状:无单号/占位单号发货不破坏;tracking 仍为 hint)。
476
+ // evidence_description 现状语义保留(缺则执行器内 transition 因证据空而拒);logistics 绑定已在上方完成。
477
+ if (action === 'accept' || action === 'ship') {
478
+ const exec = executeSellerOrderAction(db, { orderId: req.params.id, action, actorId: user.id, nowIso: new Date().toISOString(), strictTracking: false, tracking: req.body?.tracking, evidenceDescription: evidence_description, generateId, path: 'api_key' });
479
+ if (!exec.ok)
480
+ return void res.status(exec.http || 409).json({ error: exec.error, error_code: exec.error_code });
481
+ try {
482
+ notifyTransition(db, req.params.id, exec.fromStatus, exec.toStatus);
483
+ }
484
+ catch (e) {
485
+ console.warn('[order-action notify]', e.message);
486
+ }
487
+ return void res.json({ success: true, status: exec.toStatus });
488
+ }
459
489
  const actionMap = {
460
490
  accept: 'accepted', ship: 'shipped', pickup: 'picked_up',
461
491
  transit: 'in_transit', deliver: 'delivered', confirm: 'confirmed', dispute: 'disputed',
@@ -530,6 +560,19 @@ export function registerOrdersActionRoutes(app, deps) {
530
560
  const result = transition(db, req.params.id, toStatus, user.id, evidenceIds, notes);
531
561
  if (!result.success)
532
562
  return void res.json({ error: result.error });
563
+ // P1-c(b):direct_p2p 首次进 accepted(mark_paid / confirm_received)才生成 ship_deadline —— direct-pay-create 建单不设该列,
564
+ // 缺 ship_deadline 会被共享执行器 SLA【fail-closed】卡住发货。WHERE ship_deadline IS NULL 兜死:仅 null→值 那一次写,
565
+ // 绝不覆盖已有值(守 I3:deadline 绝对列不重写)。判责钟从付款确认(now)起。
566
+ if (toStatus === 'accepted' && order.payment_rail === 'direct_p2p') {
567
+ let sh = 72;
568
+ try {
569
+ const p = await dbOne("SELECT value FROM protocol_params WHERE key = 'direct_pay.ship_window_hours'");
570
+ if (p)
571
+ sh = Math.max(1, Number(p.value) || 72);
572
+ }
573
+ catch { /* 用默认 72h */ }
574
+ await dbRun("UPDATE orders SET ship_deadline = datetime('now', ?) WHERE id = ? AND ship_deadline IS NULL", [`+${sh} hours`, req.params.id]);
575
+ }
533
576
  notifyTransition(db, req.params.id, fromStatus, toStatus);
534
577
  // 审计项 B(N2):买家标记付款 → 通知卖家核款发货(direct_pay_window→accepted 不在 notifyTransition RULES,
535
578
  // 此前卖家全程收不到"已付款"信号只能手动刷)。notes 即 D1/D2/E 的权威对账串(参考号+应付+多单预警),直接复用。
@@ -40,7 +40,7 @@ import { initExternalAnchorSchema } from '../layer1-agent/L1-2-external-anchor/a
40
40
  import { ensureEvidenceColumns, uploadEvidence, listEvidence as listEvidenceFiles, markEvidenceExpiry, cleanupExpiredEvidence, EVIDENCE_MAX_BYTES, EVIDENCE_ALLOWED_MIME, } from '../layer3-trust/L3-1-dispute-engine/evidence-storage.js';
41
41
  import { cleanupOrphanNotePhotos, } from '../layer2-business/L2-notes/note-photo-storage.js';
42
42
  import { initAnchorRegistrySchema, retireAnchorsByTarget, reclaimRetiredAnchors, retireIdleAnchors, userReferralVolume, computeTierLetter, } from '../layer2-business/L2-anchor-registry/anchor-registry.js';
43
- import { initDisputeSchema, createDispute, respondToDispute, arbitrateDispute, getOrderDispute, getDisputeDetails, getOpenDisputes, checkDisputeTimeouts, initEvidenceRequestSchema, requestEvidence, getEvidenceRequests, addPartyEvidence, } from '../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
43
+ import { initDisputeSchema, createDispute, createDeclineContestDispute, respondToDispute, arbitrateDispute, getOrderDispute, getDisputeDetails, getOpenDisputes, checkDisputeTimeouts, initEvidenceRequestSchema, requestEvidence, getEvidenceRequests, addPartyEvidence, } from '../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
44
44
  import { initMutualCancelSchema } from '../layer3-trust/L3-1-dispute-engine/mutual-cancel.js';
45
45
  import { registerMutualCancelRoutes } from './routes/mutual-cancel.js';
46
46
  import { initDirectPayCancelRefundSchema } from '../direct-pay-cancel-refund.js';
@@ -5401,7 +5401,7 @@ function computeLightTags(user, mod, vWhite, vAppPending) {
5401
5401
  }
5402
5402
  // #1013 Phase 101: admin/orders + admin/disputes + admin/verify-tasks + admin/audit-log 已迁出
5403
5403
  registerAdminReportsRoutes(app, {
5404
- db,
5404
+ db, requireHumanPresence,
5405
5405
  requireContentAdmin: (req, res) => requireAdminPermission(req, res, 'content'),
5406
5406
  requireArbitrationAdmin: (req, res) => requireAdminPermission(req, res, 'arbitration'),
5407
5407
  requireProtocolAdmin: (req, res) => requireAdminPermission(req, res, 'protocol'),
@@ -6053,7 +6053,7 @@ registerOrdersReadRoutes(app, { db, auth, getOrderStatus, getOrderChain, verifyO
6053
6053
  // 注意:settleOrder 是函数声明(hoisted)但绑了 db 闭包,无需注入 db
6054
6054
  registerOrdersActionRoutes(app, {
6055
6055
  db, auth, isTrustedRole, generateId, transition, notifyTransition,
6056
- settleOrder, settleFault, detectFraud, createDispute, checkTimeouts, recordViolationReputation,
6056
+ settleOrder, settleFault, detectFraud, createDispute, createDeclineContestDispute, checkTimeouts, recordViolationReputation,
6057
6057
  broadcastSystemEvent, consumeGateToken,
6058
6058
  });
6059
6059
  // #1013 Phase 85: POST /api/orders 巨型事务已迁出
@@ -34,6 +34,12 @@ export const SAFE_SCOPES = [
34
34
  'seller_inventory_read',
35
35
  'seller_product_draft',
36
36
  'seller_pricing_suggestion',
37
+ // RFC-021 Fulfillment Agent (order 侧 申请→人工 Passkey 批准). BOTH safe:
38
+ // seller_orders_read_minimal = 最小化订单读(无买家地址/联系);
39
+ // order_action_request = SUBMIT-only —— 只能把 {accept|ship} 请求塞进人的审批队列,【绝不执行】;
40
+ // 执行始终由人 Passkey 逐笔批准后服务端跑(RFC-021 §5/§6b)。RISK 档 order_accept/order_ship 不动、仍硬拒。
41
+ 'seller_orders_read_minimal',
42
+ 'order_action_request',
37
43
  ];
38
44
  /**
39
45
  * Real actions that WILL require a live Passkey each time — but are NOT yet gated
@@ -141,6 +147,14 @@ export const PERMISSION_BUNDLES = {
141
147
  human_summary: '选品、商品草稿、库存字段检查、价格建议、上架请求。它不能发布商品、不能接单、不能发货、不能动用资金。',
142
148
  human_summary_en: 'Sourcing, product drafts, inventory-field checks, pricing suggestions, and publish REQUESTS. It cannot publish products, accept orders, ship, or move funds.',
143
149
  },
150
+ // RFC-021:独立于 catalog_agent 授予/撤销/TTL(§15.1)。仅含两个 SAFE scope,均不直接执行订单动作。
151
+ fulfillment_agent: {
152
+ key: 'fulfillment_agent',
153
+ label: 'Fulfillment Agent',
154
+ scopes: ['seller_orders_read_minimal', 'order_action_request'],
155
+ human_summary: '读你订单的最小信息(订单号/状态/下一步责任方/截止/金额/商品),再把"接单/发货"请求提交到你的审批队列等你逐笔 Passkey 批准。它【不能】直接接单或发货、看不到买家收货地址与联系方式(接单获批前)、不动任何资金。',
156
+ human_summary_en: 'Reads a minimal view of your orders (id / status / next actor / deadline / amount / item) and SUBMITS "accept / ship" requests into your approval queue for you to approve one-by-one with Passkey. It CANNOT accept or ship directly, cannot see the buyer\'s shipping address or contact (before an accept is approved), and moves no funds.',
157
+ },
144
158
  };
145
159
  /** Resolve a bundle key → its safe scopes + human summary, or null if unknown. */
146
160
  export function resolveBundle(key) {
@@ -159,27 +173,31 @@ for (const b of Object.values(PERMISSION_BUNDLES)) {
159
173
  export const DURATION_SECONDS = { '1h': 3600, '24h': 86400, '7d': 604800, '30d': 2592000 };
160
174
  /**
161
175
  * Which durations a set of scopes may be granted for, capped by the HIGHEST risk tier present:
162
- * - never-delegable / unknown → [] (cannot be granted at all)
163
- * - risk (high) → ['once'] (single Passkey each time; NEVER long-term)
164
- * - safe (low/medium) → once…30d (long-term ok)
176
+ * - never-delegable / unknown → [] (cannot be granted at all)
177
+ * - risk (high) → [] (not delegable to a persistent grant)
178
+ * - safe (low/medium) → 1h / 24h / 7d / 30d (long-term ok)
179
+ * NOTE: 'once' is intentionally NOT offered. It's a real single-use concept, but no single-use grant
180
+ * CONSUMPTION mechanism exists yet — durationToSeconds('once') would fall back to a 1h *reusable* bearer,
181
+ * which misleads. Until a true single-use grant is built, 'once' stays out of the allowed set / UI / i18n.
165
182
  */
166
183
  export function allowedDurationsForScopes(scopes) {
167
184
  const tiers = scopes.map(classifyScope);
168
185
  if (tiers.some(t => t === 'never_delegable' || t === 'unknown'))
169
186
  return [];
170
187
  if (tiers.some(t => t === 'risk'))
171
- return ['once'];
172
- return ['once', '1h', '24h', '7d', '30d'];
188
+ return [];
189
+ return ['1h', '24h', '7d', '30d'];
173
190
  }
174
191
  export function durationAllowedForScopes(scopes, duration) {
175
192
  return typeof duration === 'string' && allowedDurationsForScopes(scopes).includes(duration);
176
193
  }
177
- /** Suggested default: 7d for long-term-eligible safe scopes; else the safest available (once). */
194
+ /** Suggested default: 7d for long-term-eligible safe scopes; else the safest available. */
178
195
  export function suggestedDurationForScopes(scopes) {
179
196
  const a = allowedDurationsForScopes(scopes);
180
- return a.includes('7d') ? '7d' : (a[0] ?? 'once');
197
+ return a.includes('7d') ? '7d' : (a[0] ?? '7d');
181
198
  }
182
- /** Grant lifetime in seconds for an approved (non-'once') duration. 'once' handled separately (single-use). */
199
+ /** Grant lifetime in seconds. 'once' is RESERVED for a future single-use grant (no consumption path yet) and
200
+ * is never offered; if it somehow arrives it maps to 0 → callers fall back to a short window. */
183
201
  export function durationToSeconds(duration) {
184
202
  return duration === 'once' ? 0 : DURATION_SECONDS[duration];
185
203
  }
@@ -1844,6 +1844,28 @@ export function initAgentPermissionRequestsSchema(db) {
1844
1844
  `);
1845
1845
  db.exec(`CREATE INDEX IF NOT EXISTS idx_apr_human ON agent_permission_requests(human_id, status)`);
1846
1846
  db.exec(`CREATE INDEX IF NOT EXISTS idx_apr_status ON agent_permission_requests(status, expires_at)`);
1847
+ // RFC-021 PR2:order-action 请求复用本队列(I9)。新列均可空;kind 默认 'scope_grant' → 旧行行为不变,零迁移。
1848
+ // executed_at / execution_result 本 PR 只建列【永不写】(PR3 才写);ALTER-AFTER-CREATE 幂等。
1849
+ for (const stmt of [
1850
+ "ALTER TABLE agent_permission_requests ADD COLUMN kind TEXT DEFAULT 'scope_grant'", // scope_grant | order_action
1851
+ "ALTER TABLE agent_permission_requests ADD COLUMN order_id TEXT",
1852
+ "ALTER TABLE agent_permission_requests ADD COLUMN order_action TEXT", // accept | ship(v1)
1853
+ "ALTER TABLE agent_permission_requests ADD COLUMN params_hash TEXT", // I2 绑定键 SHA-256(order_id,action,params)
1854
+ "ALTER TABLE agent_permission_requests ADD COLUMN action_params TEXT", // JSON:ship 的 {tracking,evidence_ref};accept '{}'
1855
+ "ALTER TABLE agent_permission_requests ADD COLUMN executed_at TEXT", // I5 幂等 CAS 键 —— PR3 写,PR2 恒 NULL
1856
+ "ALTER TABLE agent_permission_requests ADD COLUMN execution_result TEXT", // I7 执行结果 —— PR3 写
1857
+ ]) {
1858
+ try {
1859
+ db.exec(stmt);
1860
+ }
1861
+ catch { /* 列已存在 */ }
1862
+ }
1863
+ // 防双 pending + I5 幂等根基:同一 (order_id, order_action) 至多一条未终结(pending/approved)的 order_action 请求。
1864
+ // rejected/expired/executed 为终态,不占索引 → 允许事后重新提交。
1865
+ try {
1866
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS ux_apr_order_action_active ON agent_permission_requests(order_id, order_action) WHERE kind='order_action' AND status IN ('pending','approved')");
1867
+ }
1868
+ catch { /* */ }
1847
1869
  }
1848
1870
  /**
1849
1871
  * RFC-020 PR-C1 — agent pairing sessions (OAuth device-flow + PKCE shape).
@@ -1877,6 +1899,12 @@ export function initAgentPairingSchema(db) {
1877
1899
  `);
1878
1900
  db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_aps_user_code ON agent_pairing_sessions(user_code)`);
1879
1901
  db.exec(`CREATE INDEX IF NOT EXISTS idx_aps_status ON agent_pairing_sessions(status, expires_at)`);
1902
+ // RFC-020 duration-choice: the agent's SUGGESTED grant lifetime (once/1h/24h/7d/30d) shown in consent so the
1903
+ // human can accept or override it at approve. Safe-scope only; the human's choice wins. ALTER after CREATE.
1904
+ try {
1905
+ db.exec("ALTER TABLE agent_pairing_sessions ADD COLUMN grant_duration TEXT");
1906
+ }
1907
+ catch { /* 已有 */ }
1880
1908
  }
1881
1909
  /**
1882
1910
  * RFC-020 PR-C2a — per-request audit of delegation-grant authorizations (RFC-020 §3.7).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seasonkoh/webaz",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "[PRE-LAUNCH] Agent-native decentralized commerce protocol. Humans and AI agents trade on the same protocol via MCP tools. ⚠️ Repository currently private until W8 public launch — GitHub links may return 404. See https://webaz.xyz for status.",
5
5
  "main": "dist/mcp.js",
6
6
  "bin": {
@@ -89,7 +89,18 @@
89
89
  "test:agent-permission-consume": "tsx scripts/test-agent-permission-consume.ts",
90
90
  "test:agent-approvals-ui": "tsx scripts/test-agent-approvals-ui.ts",
91
91
  "test:mcp-place-order-rail": "tsx scripts/test-mcp-place-order-rail.ts",
92
+ "test:mcp-agent-order-tools": "tsx scripts/test-mcp-agent-order-tools.ts",
92
93
  "test:agent-product-draft": "tsx scripts/test-agent-product-draft.ts",
94
+ "test:decline-contest-unify-p1": "tsx scripts/test-decline-contest-unify-pr1.ts",
95
+ "test:decline-contest-unify-p2": "tsx scripts/test-decline-contest-unify-pr2.ts",
96
+ "test:decline-contest-unify-p3": "tsx scripts/test-decline-contest-unify-pr3.ts",
97
+ "test:arb-resolved-history": "tsx scripts/test-arb-resolved-history.ts",
98
+ "test:order-action-request-p1": "tsx scripts/test-order-action-request-pr1.ts",
99
+ "test:order-action-request-p2": "tsx scripts/test-order-action-request-pr2.ts",
100
+ "test:order-action-request-p3": "tsx scripts/test-order-action-request-pr3.ts",
101
+ "backfill:decline-contest": "tsx scripts/backfill-decline-contest-disputes.ts",
102
+ "backfill:direct-pay-ship-deadline": "tsx scripts/backfill-direct-pay-ship-deadline.ts",
103
+ "test:mcp-version-nudge": "tsx scripts/test-mcp-version-nudge.ts",
93
104
  "test:agent-grant-verify": "tsx scripts/test-agent-grant-verify.ts",
94
105
  "test:agent-grant-mcp-consume": "tsx scripts/test-agent-grant-mcp-consume.ts",
95
106
  "test:connected-agents-read": "tsx scripts/test-connected-agents-read.ts",
@@ -107,7 +118,7 @@
107
118
  "test:currency-display": "tsx scripts/test-currency-display.ts",
108
119
  "test:currency-schema-flip": "tsx scripts/test-currency-schema-flip.ts",
109
120
  "test:proposal-admin-ui": "tsx scripts/test-proposal-admin-ui.ts",
110
- "check:pwa-syntax": "node --check src/pwa/public/app-notif-templates.js && node --check src/pwa/public/app-notif-templates-orders.js && node --check src/pwa/public/app.js && node --check src/pwa/public/app-admin.js && node --check src/pwa/public/app-admin-disputes.js && node --check src/pwa/public/app-contribution.js && node --check src/pwa/public/app-ai.js && node --check src/pwa/public/app-discover.js && node --check src/pwa/public/app-profile.js && node --check src/pwa/public/app-account.js && node --check src/pwa/public/app-shop.js && node --check src/pwa/public/app-listings.js && node --check src/pwa/public/app-external-links.js && node --check src/pwa/public/app-product-media.js && node --check src/pwa/public/app-create-kinds.js && node --check src/pwa/public/app-price.js && node --check src/pwa/public/app-order-labels.js && node --check src/pwa/public/app-seller.js && node --check src/pwa/public/app-agents.js && node --check src/pwa/public/app-agent-pair.js && node --check src/pwa/public/app-agent-approvals.js && node --check src/pwa/public/app-agent-appeal.js && node --check src/pwa/public/app-direct-pay.js && node --check src/pwa/public/app-direct-pay-readiness.js && node --check src/pwa/public/app-direct-pay-deferral.js && node --check src/pwa/public/app-direct-pay-deferral-admin.js && node --check src/pwa/public/app-direct-pay-product-verify.js && node --check src/pwa/public/app-direct-pay-store-verify.js && node --check src/pwa/public/app-direct-pay-compliance.js && node --check src/pwa/public/app-direct-pay-fee-ops.js && node --check src/pwa/public/app-direct-pay-fee-center.js && node --check src/pwa/public/app-direct-pay-sales-report.js && node --check src/pwa/public/app-gmv-rail-split.js && node --check src/pwa/public/app-direct-pay-accounts.js && node --check src/pwa/public/app-direct-pay-buyer.js && node --check src/pwa/public/app-direct-pay-pay.js && node --check src/pwa/public/app-direct-pay-reveal.js && node --check src/pwa/public/app-direct-pay-copy.js && node --check src/pwa/public/app-direct-pay-paymodal.js && node --check src/pwa/public/app-direct-pay-memo.js && node --check src/pwa/public/app-direct-pay-negotiation.js && node --check src/pwa/public/app-direct-pay-cancel-refund.js && node --check src/pwa/public/app-direct-pay-returns.js && node --check src/pwa/public/app-order-accept-ui.js && node --check src/pwa/public/app-order-rail-filter.js && node --check src/pwa/public/app-sale-regions-ui.js && node --check src/pwa/public/app-free-shipping-ui.js && node --check src/pwa/public/app-trade-tax-ui.js && node --check src/pwa/public/app-purchase-terms-ui.js && node --check src/pwa/public/app-listing-commerce-ui.js && node --check src/pwa/public/app-bond-terms-ui.js && node --check src/pwa/public/app-bond-ui.js && node --check src/pwa/public/app-bond-refund-ui.js && node --check src/pwa/public/app-bond-slash-ui.js && node --check src/pwa/public/app-bond-deferral-ui.js && node --check src/pwa/public/app-direct-pay-reconcile.js && node --check src/pwa/public/app-mutual-cancel.js && node --check src/pwa/public/app-dispute-close-ui.js && node --check src/pwa/public/app-order-errors.js && node --check src/pwa/public/app-arbitrator-entry.js && node --check src/pwa/public/app-arbitrator-admin.js && node --check src/pwa/public/app-contribution-hub.js && node --check src/pwa/public/app-platform-receive-accounts.js && node --check src/pwa/public/app-direct-pay-fee-request.js && node --check src/pwa/public/app-direct-pay-fee-requests-admin.js && node --check src/pwa/public/app-direct-pay-fee-history.js && node --check src/pwa/public/app-prelaunch-waz.js && node --check src/pwa/public/app-chat-poll.js && node --check src/pwa/public/app-poll-governor.js && node --check src/pwa/public/i18n.js && node --check src/pwa/public/sw.js",
121
+ "check:pwa-syntax": "node --check src/pwa/public/app-notif-templates.js && node --check src/pwa/public/app-notif-templates-orders.js && node --check src/pwa/public/app.js && node --check src/pwa/public/app-admin.js && node --check src/pwa/public/app-admin-disputes.js && node --check src/pwa/public/app-contribution.js && node --check src/pwa/public/app-ai.js && node --check src/pwa/public/app-discover.js && node --check src/pwa/public/app-profile.js && node --check src/pwa/public/app-account.js && node --check src/pwa/public/app-shop.js && node --check src/pwa/public/app-listings.js && node --check src/pwa/public/app-external-links.js && node --check src/pwa/public/app-product-media.js && node --check src/pwa/public/app-create-kinds.js && node --check src/pwa/public/app-price.js && node --check src/pwa/public/app-order-labels.js && node --check src/pwa/public/app-seller.js && node --check src/pwa/public/app-grant-duration.js && node --check src/pwa/public/app-agents.js && node --check src/pwa/public/app-agent-pair.js && node --check src/pwa/public/app-agent-approvals.js && node --check src/pwa/public/app-agent-approvals-order.js && node --check src/pwa/public/app-agent-appeal.js && node --check src/pwa/public/app-direct-pay.js && node --check src/pwa/public/app-direct-pay-readiness.js && node --check src/pwa/public/app-direct-pay-deferral.js && node --check src/pwa/public/app-direct-pay-deferral-admin.js && node --check src/pwa/public/app-direct-pay-product-verify.js && node --check src/pwa/public/app-direct-pay-store-verify.js && node --check src/pwa/public/app-direct-pay-compliance.js && node --check src/pwa/public/app-direct-pay-fee-ops.js && node --check src/pwa/public/app-direct-pay-fee-center.js && node --check src/pwa/public/app-direct-pay-sales-report.js && node --check src/pwa/public/app-gmv-rail-split.js && node --check src/pwa/public/app-direct-pay-accounts.js && node --check src/pwa/public/app-direct-pay-buyer.js && node --check src/pwa/public/app-direct-pay-pay.js && node --check src/pwa/public/app-direct-pay-reveal.js && node --check src/pwa/public/app-direct-pay-copy.js && node --check src/pwa/public/app-direct-pay-paymodal.js && node --check src/pwa/public/app-direct-pay-memo.js && node --check src/pwa/public/app-direct-pay-negotiation.js && node --check src/pwa/public/app-direct-pay-cancel-refund.js && node --check src/pwa/public/app-direct-pay-returns.js && node --check src/pwa/public/app-order-accept-ui.js && node --check src/pwa/public/app-order-rail-filter.js && node --check src/pwa/public/app-sale-regions-ui.js && node --check src/pwa/public/app-free-shipping-ui.js && node --check src/pwa/public/app-trade-tax-ui.js && node --check src/pwa/public/app-purchase-terms-ui.js && node --check src/pwa/public/app-listing-commerce-ui.js && node --check src/pwa/public/app-bond-terms-ui.js && node --check src/pwa/public/app-bond-ui.js && node --check src/pwa/public/app-bond-refund-ui.js && node --check src/pwa/public/app-bond-slash-ui.js && node --check src/pwa/public/app-bond-deferral-ui.js && node --check src/pwa/public/app-direct-pay-reconcile.js && node --check src/pwa/public/app-mutual-cancel.js && node --check src/pwa/public/app-dispute-close-ui.js && node --check src/pwa/public/app-order-errors.js && node --check src/pwa/public/app-arbitrator-entry.js && node --check src/pwa/public/app-arbitrator-admin.js && node --check src/pwa/public/app-decline-contest-ui.js && node --check src/pwa/public/app-decline-contest-ruling.js && node --check src/pwa/public/app-contribution-hub.js && node --check src/pwa/public/app-platform-receive-accounts.js && node --check src/pwa/public/app-direct-pay-fee-request.js && node --check src/pwa/public/app-direct-pay-fee-requests-admin.js && node --check src/pwa/public/app-direct-pay-fee-history.js && node --check src/pwa/public/app-prelaunch-waz.js && node --check src/pwa/public/app-chat-poll.js && node --check src/pwa/public/app-poll-governor.js && node --check src/pwa/public/i18n.js && node --check src/pwa/public/sw.js",
111
122
  "test:create-kinds": "tsx scripts/test-create-kinds.ts",
112
123
  "test:secondhand-photo-authenticity": "tsx scripts/test-secondhand-photo-authenticity.ts",
113
124
  "test:secondhand-detail-alignment": "tsx scripts/test-secondhand-detail-alignment.ts",