n8n-nodes-dingtalk-card 0.6.0 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0 (stable)
4
+
5
+ Promotes the production-validated v0.7.0-beta.1 Event Handling & Reliability behavior without runtime changes. Connected-instance acceptance covered Create & Deliver, Trigger deactivation/reactivation, a real callback after reactivation, normalized event correlation, and card Update. Exact-message duplicate suppression remains validated by deterministic tests; live duplicate redelivery was not forced. The npm `beta` channel remains on `0.7.0-beta.1`. See [release verification](docs/VERIFICATION.md).
6
+
7
+ ## 0.7.0-beta.1 (published prerelease)
8
+
9
+ - Suppress duplicate Stream callbacks by `messageId` within one active Trigger lifecycle; duplicates still receive the immediate receipt ACK. Cache is bounded (10,000 entries), TTL-based (10 minutes), and explicitly not durable exactly-once across restart or instances.
10
+ - Preserve the existing native WebSocket reconnect/backoff and shutdown lifecycle; no competing SDK/transport was introduced.
11
+ - Normalize outbound error operation, phase, attempts, ambiguity and correlation fields. Only explicit invalid-token rejection is automatically retried for writes; network/timeout/5xx outcomes remain ambiguous and non-retryable, preventing blind Create replay.
12
+ - Published to npm under `beta`; `latest` remains on stable `0.6.0`. Release evidence, provenance, and canonical tarball identity are recorded in [verification](docs/VERIFICATION.md#v070-beta1-public-prerelease--2026-10-25).
13
+
3
14
  ## 0.6.0 (stable)
4
15
 
5
16
  Promotes the production-accepted v0.6.0-beta.3 A2UI callback params normalization without runtime changes. Connected-instance acceptance passed for delivery, callback normalization, and the original card A2UI Update. The npm `beta` channel remains on `0.6.0-beta.3`.
package/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  Focused n8n community nodes for the **DingTalk Card Platform lifecycle and Stream card callbacks**.
4
4
 
5
- **v0.6.0 is the stable release.** It promotes the production-accepted v0.6.0-beta.3 A2UI action callback params normalization without runtime changes. Connected-instance acceptance covered card delivery, the real callback, normalized params, and a successful A2UI Update. This does not certify v1.0 or untested platform/runtime behavior. This project is not n8n-verified and is not affiliated with DingTalk or n8n. See [release verification](docs/VERIFICATION.md).
5
+ **v0.7.0 is the stable release.** It promotes the production-validated v0.7.0-beta.1 Event Handling & Reliability behavior without runtime changes. Connected-instance acceptance covered Create & Deliver, Trigger deactivation/reactivation, a real callback after reactivation, normalized event correlation, and card Update. Exact-message duplicate suppression is covered by deterministic tests; live duplicate redelivery was not forced. This does not certify v1.0 or untested platform/runtime behavior. This project is not n8n-verified and is not affiliated with DingTalk or n8n. See [release verification](docs/VERIFICATION.md).
6
6
 
7
7
  The published **v0.4.0-beta.2** established the functional baseline for stable v0.4.0. A true Presentation-mode status card rendered correctly in Mac DingTalk without workflow-supplied `flowStatus` or a JSON request body; the final live Create & Deliver regression passed for alert, status, digest, action, and report. The package ships no DingTalk templates. See the [presentation contract](docs/PRESENTATION.md) and [release verification](docs/VERIFICATION.md).
8
8
 
9
9
  The published **v0.5.0-beta.1** remains available on the beta channel and is the functional baseline for stable v0.5.0. Its optional normalized Trigger envelope passed production acceptance for one real request-button/action-callback path, with existing downstream expressions continuing to work. Form and Selection remain unverified and deferred. See [release verification](docs/VERIFICATION.md#v050-beta1-production-acceptance--2026-10-25).
10
10
 
11
- The npm `latest` dist-tag resolves to `0.6.0`; `beta` remains on `0.6.0-beta.3`. Releases use GitHub Actions Trusted Publisher/OIDC with provenance. Beta publication leaves `latest` unchanged now that a stable version exists.
11
+ The npm `latest` dist-tag resolves to `0.7.0`; `beta` remains on `0.7.0-beta.1`. Releases use GitHub Actions Trusted Publisher/OIDC with provenance. Beta publication leaves `latest` unchanged now that a stable version exists.
12
12
 
13
13
  It provides **DingTalk Card**, **DingTalk Card Trigger**, and the shared **DingTalk Card API** enterprise app credential. It does not implement OA, todo, tables or general robot messaging, and does not depend on `@cryozerolabs/n8n-nodes-dingtalk`. Robot delivery fields here exist only to deliver Card Platform instances.
14
14
 
@@ -57,13 +57,13 @@ For a reusable stable-ID Create & Deliver node recipe and success/error branch c
57
57
 
58
58
  DingTalk requires **one Stream service per Client ID** for card callbacks. The module-local lock rejects a second active Card Trigger with the same Client ID in this n8n process, including a manual listener competing with an active workflow. It cannot detect another n8n process, worker, replica, package copy or external Stream service. Use separate Client IDs for development and production, and a single listener service for each. See [DingTalk’s callback troubleshooting](https://github.com/open-dingtalk/dingtalk-card-examples#注意事项).
59
59
 
60
- The trigger subscribes **only** to `CALLBACK /v1.0/card/instances/callback`. It immediately queues `200 / OK` with empty JSON-object data (`"{}"`) **before** emitting one item; it never waits for downstream nodes. This avoids delaying acknowledgement toward the reported ~60-second card retry window. Downstream updates are asynchronous. **No synchronous local-update ACK support in this beta.** An ACK is receipt acknowledgement, not proof of downstream success. Network retries/duplicate deliveries remain possible; make business actions idempotent. There is no persistent deduplication or replay queue; an acknowledged event can be lost if the workflow subsequently fails.
60
+ The trigger subscribes **only** to `CALLBACK /v1.0/card/instances/callback`. It immediately queues `200 / OK` with empty JSON-object data (`"{}"`) **before** emitting one item; it never waits for downstream nodes. Duplicate `messageId` callbacks in the same active Trigger lifecycle are ACKed and ignored after the first emission. The cache is process-local, bounded to 10,000 IDs and expires entries after 10 minutes; eviction can also allow an earlier ID to emit again. This is not durable exactly-once delivery across restart, multiple processes or instances. An ACK is receipt acknowledgement, not proof of downstream success; acknowledged work may be lost if workflow processing subsequently fails. Make consequential business actions idempotent in n8n.
61
61
 
62
62
  Items include `type`, `specVersion`, `headers`, `topic`, `messageId`, ISO `receivedAt`, parsed `data`, and unchanged `rawData`. JSON `content` is exposed if it is an object; `cardPrivateData`, `params` (including `content.cardPrivateData.params`), and supplied `outTrackId`, `userId`, `userIdType`, `openId`, `unionId`, `operatorId`, `operatorUserId` retain source names/values. Missing optional fields stay absent; no identifier types are inferred. Invalid data JSON still receives an ACK and is emitted as a raw string. Invalid outer envelopes, missing message IDs and other topics are ignored. Callback payloads can contain private form/user data: control execution-data access and retention.
63
63
 
64
64
  The trigger adds a versioned `normalized` object while retaining all existing top-level fields; existing workflows need no migration. Raw callback serialization remains enabled by default and can be omitted with **Include Raw Callback Data**. This reduces duplicate raw serialization but does not remove sensitive callback content. See [the v1 interaction contract](docs/INTERACTIONS.md). Only the existing request-button/action callback path is live-verified; form and selection inputs remain deferred.
65
65
 
66
- The self-contained transport deliberately avoids the upstream `dingtalk-stream` SDK following verified ticket-bearing URL logging risk on SDK error paths. It uses Node 24+ native WebSocket, n8n’s HTTP helper, validated `wss:` endpoints and `URL.searchParams`; no SDK copy, runtime dependency or global console patch. Gateway bodies, tickets, secrets, URLs and original connection errors never enter its diagnostics. It answers SYSTEM ping with the supplied opaque value, reconnects on SYSTEM disconnect/unexpected close/errors, and watches opening (30s) and idle connections (120s). Reconnect delays are bounded at 1, 2, 4, 8, 16, 30 seconds, then 30 seconds until traffic resumes; each attempt gets a fresh ticket. Lookup/connect failures log generic diagnostics and retry while the workflow stays active. Deactivation aborts gateway lookup, closes pending/active sockets, cancels timers and releases the local lock.
66
+ The self-contained transport deliberately avoids the upstream `dingtalk-stream` SDK following verified ticket-bearing URL logging risk on SDK error paths. It uses Node 24+ native WebSocket, n8n’s HTTP helper, validated `wss:` endpoints and `URL.searchParams`; no SDK copy, runtime dependency or global console patch. Gateway bodies, tickets, secrets, URLs and original connection errors never enter its diagnostics. It answers SYSTEM ping with the supplied opaque value, reconnects on SYSTEM disconnect/unexpected close/errors, and watches opening (30s) and idle connections (120s). Reconnect delays are bounded at 1, 2, 4, 8, 16, 30 seconds, then 30 seconds until traffic resumes; each attempt gets a fresh ticket. This is the existing transport reconnect policy, not SDK-managed reconnect. Lookup/connect failures log generic diagnostics and retry while the workflow stays active. Deactivation aborts gateway lookup, closes pending/active sockets, cancels timers and releases the local lock.
67
67
 
68
68
  The real button callback E2E on 2026-10-24 used the same enterprise-app Client ID for creation and listening. DingTalk Card Trigger activated successfully, and a request-button card with `callbackType=STREAM` was delivered. One real button click produced a `CALLBACK /v1.0/card/instances/callback` trigger execution; normalized `params` contained the expected button id/text. Downstream asynchronous Update returned success, the card visibly changed, and execution completed successfully. This validates the tested button path and immediate receipt ACK transport with asynchronous downstream execution in practice. It does not certify every template, form submission, reconnect or failure mode; synchronous local-update ACKs remain unsupported.
69
69
 
@@ -93,7 +93,7 @@ The corresponding [API payload](examples/error-notification.payload.json) is usa
93
93
 
94
94
  - Credentials belong exclusively in n8n's encrypted credential store. Do not commit `.env` files, tokens, exported credentials or private destination IDs.
95
95
  - Requests use a fixed HTTPS origin, a 30-second timeout and disabled redirects. This package has no runtime dependencies beyond the n8n peer API. It never logs request/response credentials.
96
- - A recognized explicit invalid/expired-token API rejection refreshes auth and retries once with the **same payload/identifiers**. There are no other automatic write retries. If `errorDetails.mayHaveSucceeded` is true, inspect/verify the card and delivery before any workflow/operator replay; use the same caller-controlled identifiers. A stable `outTrackId` still does not guarantee Create deduplication or upsert.
96
+ - A recognized explicit invalid/expired-token API rejection refreshes auth and retries once with the **same payload/identifiers**. This is the only automatic write retry. Timeouts, network failures, HTTP 5xx, partial delivery, and incomplete write responses are non-retryable and may have an ambiguous outcome; in particular, a Create timeout can occur after card creation. `errorDetails` reports operation, phase, attempt count, `retryable`, `ambiguousOutcome`, and stable identifiers. Verify before any operator replay; reuse the same caller-controlled `outTrackId`. A stable `outTrackId` still does not guarantee Create deduplication or upsert.
97
97
  - n8n's own Retry On Fail reruns the node; an auto-generated `outTrackId` may change because it is stable only within one execution. Use an ID derived from a stable business/event key when replay correlation matters. Multiple items execute sequentially; partial success is possible before a later item fails.
98
98
  - Card content is sent to DingTalk and successful raw responses are retained in n8n execution data. Apply suitable execution-data retention and access controls.
99
99
 
@@ -109,7 +109,7 @@ npm run verify:package
109
109
  npm audit --omit=dev --audit-level=high
110
110
  ```
111
111
 
112
- Uses the official [n8n node CLI](https://docs.n8n.io/connect/create-nodes/build-your-node/using-the-n8n-node-tool.md) and current starter conventions, established in place without example nodes. The CLI is pinned to **0.54.2 beta** because the registry's stable 0.53.4 pins vulnerable Handlebars 4.7.9; the beta includes the patched upstream dependency without overrides. No CLI release script is provided: `n8n-node release` would publish to npm, which this beta process must not do. Tests use Node's built-in runner, mocked HTTP/sockets and real local TLS WebSocket fixtures (OpenSSL is required for tests). No live credentials are needed.
112
+ Uses the official [n8n node CLI](https://docs.n8n.io/connect/create-nodes/build-your-node/using-the-n8n-node-tool.md) and current starter conventions, established in place without example nodes. The CLI is pinned to **0.54.2 beta** because the registry's stable 0.53.4 pins vulnerable Handlebars 4.7.9; the beta includes the patched upstream dependency without overrides. Releases use the repository's Trusted Publisher workflow; `n8n-node release` is not used. Tests use Node's built-in runner, mocked HTTP/sockets and real local TLS WebSocket fixtures (OpenSSL is required for tests). No live credentials are needed.
113
113
 
114
114
  Programmatic execution is intentional: shared token handling, strict 2xx/partial-delivery failure checks, generated identifier propagation and sanitized item-level errors are implemented once, separated from pure payload builders. The current custom credential-test context in `n8n-workflow` exposes only the legacy `request` helper; that single call has a documented lint exception and sanitized error boundary. Main execution uses `httpRequest`. Pure helper errors are converted to `NodeOperationError` at the execution boundary.
115
115
 
@@ -310,7 +310,7 @@ class DingTalkCard {
310
310
  };
311
311
  }
312
312
  async execute() {
313
- var _a;
313
+ var _a, _b, _c;
314
314
  const items = this.getInputData();
315
315
  const output = [];
316
316
  const credential = await this.getCredentials('dingTalkCardApi');
@@ -386,6 +386,7 @@ class DingTalkCard {
386
386
  outTrackId: body.outTrackId,
387
387
  ...(body.guid ? { guid: body.guid } : {}),
388
388
  success: true,
389
+ correlation: { outTrackId: body.outTrackId, ...(body.guid ? { guid: body.guid } : {}) },
389
390
  raw: raw,
390
391
  },
391
392
  pairedItem: { item: i },
@@ -401,12 +402,20 @@ class DingTalkCard {
401
402
  retryable: cardError.retryable,
402
403
  mayHaveSucceeded: cardError.mayHaveSucceeded,
403
404
  ...(cardError.httpStatus === undefined ? {} : { httpStatus: cardError.httpStatus }),
405
+ operation,
406
+ phase: (_b = cardError.phase) !== null && _b !== void 0 ? _b : 'request',
407
+ attemptCount: cardError.attemptCount,
408
+ ambiguousOutcome: cardError.ambiguousOutcome,
404
409
  }
405
410
  : {
406
411
  code: 'DINGTALK_ERROR',
407
412
  category: 'internal',
408
413
  retryable: false,
409
414
  mayHaveSucceeded: false,
415
+ operation,
416
+ phase: 'validation',
417
+ attemptCount: 0,
418
+ ambiguousOutcome: false,
410
419
  };
411
420
  const outTrackId = typeof body.outTrackId === 'string' ? body.outTrackId : '';
412
421
  if (this.continueOnFail()) {
@@ -415,6 +424,7 @@ class DingTalkCard {
415
424
  operation,
416
425
  outTrackId,
417
426
  ...(body.guid ? { guid: body.guid } : {}),
427
+ correlation: { outTrackId, ...(body.guid ? { guid: body.guid } : {}) },
418
428
  success: false,
419
429
  error: message,
420
430
  errorDetails,
@@ -425,9 +435,9 @@ class DingTalkCard {
425
435
  else
426
436
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), message, {
427
437
  itemIndex: i,
428
- description: `Operation: ${operation}. Error code: ${errorDetails.code}. Category: ${errorDetails.category}. Retryable: ${errorDetails.retryable}. May have succeeded: ${errorDetails.mayHaveSucceeded}.${errorDetails.httpStatus === undefined
438
+ description: `Operation: ${operation}. Phase: ${(_c = errorDetails.phase) !== null && _c !== void 0 ? _c : 'request'}. Attempt count: ${errorDetails.attemptCount}. Error code: ${errorDetails.code}. Category: ${errorDetails.category}. Retryable: ${errorDetails.retryable}. Ambiguous outcome: ${errorDetails.ambiguousOutcome}. May have succeeded: ${errorDetails.mayHaveSucceeded}.${errorDetails.httpStatus === undefined
429
439
  ? ''
430
- : ` HTTP status: ${errorDetails.httpStatus}.`} Out Track ID: ${outTrackId || '(not assigned)'}. Preserve this identifier when investigating partial delivery.`,
440
+ : ` HTTP status: ${errorDetails.httpStatus}.`} Out Track ID: ${outTrackId || '(not assigned)'}.${body.guid ? ` GUID: ${String(body.guid)}.` : ''} Preserve these identifiers when investigating partial delivery.`,
431
441
  });
432
442
  }
433
443
  }
@@ -23,13 +23,17 @@ function validHttpStatus(value) {
23
23
  }
24
24
  class CardError extends Error {
25
25
  constructor(message, code = 'DINGTALK_ERROR', metadata = {}) {
26
- var _a, _b, _c;
26
+ var _a, _b, _c, _d;
27
27
  super(message);
28
28
  this.code = safeCode(code);
29
29
  this.category = (_a = metadata.category) !== null && _a !== void 0 ? _a : 'validation';
30
30
  this.retryable = (_b = metadata.retryable) !== null && _b !== void 0 ? _b : false;
31
31
  this.mayHaveSucceeded = (_c = metadata.mayHaveSucceeded) !== null && _c !== void 0 ? _c : false;
32
32
  this.httpStatus = validHttpStatus(metadata.httpStatus);
33
+ this.operation = typeof metadata.operation === 'string' ? metadata.operation : undefined;
34
+ this.phase = typeof metadata.phase === 'string' ? metadata.phase : undefined;
35
+ this.attemptCount = typeof metadata.attemptCount === 'number' ? metadata.attemptCount : 1;
36
+ this.ambiguousOutcome = (_d = metadata.ambiguousOutcome) !== null && _d !== void 0 ? _d : this.mayHaveSucceeded;
33
37
  }
34
38
  }
35
39
  exports.CardError = CardError;
@@ -129,11 +133,9 @@ async function send(request, options) {
129
133
  : 'network/timeout failure';
130
134
  throw new CardError(`DingTalk ${label} (${code}). Check connectivity, app credentials, API permissions and payload. ${tokenRequest ? 'Token acquisition failed before the card write.' : 'A write may have succeeded; verify before retrying.'}`, code, {
131
135
  category,
132
- retryable: (!tokenRequest && invalidToken) ||
133
- (tokenRequest &&
134
- !invalidToken &&
135
- (status === undefined || status === 408 || status === 429 || status >= 500)),
136
+ retryable: (!tokenRequest && invalidToken) || (tokenRequest && !invalidToken && (status === undefined || status === 408 || status === 429 || status >= 500)),
136
137
  mayHaveSucceeded: !tokenRequest && !invalidToken && !authenticationStatus,
138
+ ambiguousOutcome: !tokenRequest && !invalidToken && !authenticationStatus,
137
139
  httpStatus: status,
138
140
  });
139
141
  }
@@ -190,8 +192,25 @@ function createClient(request, appKey, appSecret, now = Date.now) {
190
192
  }
191
193
  }
192
194
  const call = async (method, path, body) => {
195
+ const operation = path.endsWith('/createAndDeliver')
196
+ ? 'createAndDeliver'
197
+ : path.endsWith('/streaming')
198
+ ? (body.isFinalize === true ? 'finalize' : 'stream')
199
+ : 'update';
193
200
  for (let attempt = 0;; attempt++) {
194
- const auth = await token();
201
+ let auth;
202
+ try {
203
+ auth = await token();
204
+ }
205
+ catch (error) {
206
+ if (error instanceof CardError)
207
+ throw new CardError(error.message, error.code, {
208
+ category: error.category, retryable: error.retryable, mayHaveSucceeded: false,
209
+ httpStatus: error.httpStatus, operation,
210
+ phase: 'token', attemptCount: 1, ambiguousOutcome: false,
211
+ });
212
+ throw error;
213
+ }
195
214
  try {
196
215
  const response = checkBusinessResponse(await send(request, {
197
216
  method,
@@ -216,6 +235,13 @@ function createClient(request, appKey, appSecret, now = Date.now) {
216
235
  tokens.delete(cacheKey);
217
236
  continue;
218
237
  }
238
+ if (error instanceof CardError) {
239
+ throw new CardError(error.message, error.code, {
240
+ category: error.category, retryable: error.retryable, mayHaveSucceeded: error.mayHaveSucceeded,
241
+ httpStatus: error.httpStatus, operation,
242
+ phase: 'write', attemptCount: attempt + 1, ambiguousOutcome: error.ambiguousOutcome,
243
+ });
244
+ }
219
245
  throw error;
220
246
  }
221
247
  }
@@ -6,6 +6,8 @@ exports.createCardStream = createCardStream;
6
6
  const transport_1 = require("../DingTalkCard/transport");
7
7
  exports.CARD_TOPIC = '/v1.0/card/instances/callback';
8
8
  const activeClients = new Set();
9
+ const DEDUPE_TTL_MS = 10 * 60 * 1000;
10
+ const DEDUPE_MAX_ENTRIES = 10000;
9
11
  function parse(value) {
10
12
  if (typeof value !== 'string')
11
13
  return value;
@@ -115,7 +117,7 @@ function normalizeCallback(frame, receivedAt = new Date().toISOString(), include
115
117
  return result;
116
118
  }
117
119
  function createCardStream(clientId, clientSecret, deps) {
118
- var _a, _b;
120
+ var _a, _b, _c;
119
121
  if (!clientId || !clientSecret)
120
122
  throw new transport_1.CardError('Configure Client ID and Client Secret in the DingTalk Card API credential.');
121
123
  if (activeClients.has(clientId))
@@ -130,6 +132,22 @@ function createCardStream(clientId, clientSecret, deps) {
130
132
  let reconnect;
131
133
  let watchdog;
132
134
  let attempts = 0;
135
+ const seenMessages = new Map();
136
+ const now = (_c = deps.now) !== null && _c !== void 0 ? _c : Date.now;
137
+ function duplicate(messageId) {
138
+ const time = now();
139
+ for (const [id, expiresAt] of seenMessages) {
140
+ if (expiresAt > time)
141
+ break;
142
+ seenMessages.delete(id);
143
+ }
144
+ if (seenMessages.has(messageId))
145
+ return true;
146
+ while (seenMessages.size >= DEDUPE_MAX_ENTRIES)
147
+ seenMessages.delete(seenMessages.keys().next().value);
148
+ seenMessages.set(messageId, time + DEDUPE_TTL_MS);
149
+ return false;
150
+ }
133
151
  function clearWatchdog() {
134
152
  if (watchdog !== undefined)
135
153
  clearTimer(watchdog);
@@ -242,6 +260,8 @@ function createCardStream(clientId, clientSecret, deps) {
242
260
  else if (frame.type === 'CALLBACK' && topic === exports.CARD_TOPIC) {
243
261
  acknowledge(current, messageId, {});
244
262
  attempts = 0;
263
+ if (duplicate(messageId))
264
+ return;
245
265
  deps.emit(normalizeCallback(frame, new Date().toISOString(), deps.includeRawData !== false));
246
266
  }
247
267
  }
package/docs/DESIGN.md CHANGED
@@ -39,6 +39,12 @@ Each layer owns a distinct responsibility:
39
39
  6. **Reusable automation patterns:** provide workflow-layer examples and assets that users can adapt without coupling the generic plugin to one owner's business systems.
40
40
  7. **Verifiable claims:** specific platform features, templates, callback behaviors, and compatibility claims require current documentation and suitable acceptance evidence.
41
41
 
42
+ ## Event and write reliability boundary
43
+
44
+ The Stream Trigger acknowledges valid callbacks promptly and suppresses repeated `messageId` values only within its active process lifecycle using bounded TTL memory. This reduces duplicate executions but is not durable exactly-once delivery; restart, eviction, another process/instance, and downstream failure remain outside that guarantee. Workflows own durable business idempotency and state.
45
+
46
+ The existing native WebSocket transport owns reconnect, backoff, and deactivation. Outbound calls have bounded timeouts. The card client retries a write only after an explicit invalid-token rejection, preserving its payload and identifiers. A timeout or other uncertain write result is reported as potentially ambiguous and is never automatically replayed; `outTrackId` is a correlation key, not a Create deduplication/upsert contract.
47
+
42
48
  ## Controlled presentation and action contract
43
49
 
44
50
  The high-level contract is a versioned descriptor, not arbitrary UI. Validation must be deterministic and reject unknown versions, unknown fields where unsafe, unapproved components, and actions outside the allowlist. Rendering must use only verified template/component mappings and safe primitives. Invalid input needs a documented safe failure/fallback. Inputs and resulting actions should remain auditable and bounded by the contract.
package/docs/ROADMAP.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  This roadmap is subordinate to the project's [design baseline](DESIGN.md), which governs architecture, scope, non-goals, and acceptance boundaries. Roadmap items may sequence work within those boundaries but cannot override them; resolve conflicts by updating the proposal to fit the baseline or deliberately revising the baseline first.
4
4
 
5
- This roadmap starts from the published **v0.4.0** stable state. It describes intended milestones, not released capabilities. The current release and its tested limits remain documented in the [README](../README.md), [API contracts](API.md), and [verification record](VERIFICATION.md). Dates are omitted; milestones are semantic versions, not calendar commitments.
5
+ This roadmap records the v0.4.0 onward milestones, including later releases documented below. It describes intended milestones, not released capabilities. The current release and its tested limits remain documented in the [README](../README.md), [API contracts](API.md), and [verification record](VERIFICATION.md). Dates are omitted; milestones are semantic versions, not calendar commitments.
6
6
 
7
- The repository candidate version is **0.6.0-beta.1**. npm `latest` remains `0.5.0`; this candidate has not been published. The v0.4.0 Presentation Contract & Template Kit is implemented and production acceptance is complete. v0.5.0 promotes the beta.1 baseline without runtime changes. Its production acceptance covers only the request-button/action-callback path; Form and Selection remain unverified and deferred. See [verification](VERIFICATION.md).
7
+ v0.7.0 is the current stable release; beta.1 production and publication evidence is in the [verification record](VERIFICATION.md). v0.6.0 and earlier milestones remain documented below. The v0.4.0 Presentation Contract & Template Kit is implemented and production acceptance is complete. v0.5.0 promotes its beta.1 baseline without runtime changes. Its production acceptance covers only the request-button/action-callback path; Form and Selection remain unverified and deferred. See [verification](VERIFICATION.md).
8
8
 
9
9
  ## Vision and product principle
10
10
 
@@ -103,23 +103,22 @@ The beta.1 implementation is specified in [INTERACTIONS.md](INTERACTIONS.md), wi
103
103
 
104
104
  **Compatibility and migration:** Additive `a2ui` Card Data Mode for Create & Deliver and Update. Preserve Form/JSON/Presentation behavior and the low-level raw JSON expert path. Update does not enable callbacks.
105
105
 
106
- ### v0.7.0 — n8n Action & Workflow Kit
106
+ ### v0.7.0 — Event Handling & Reliability (stable release)
107
107
 
108
- **Objective:** Make common card-to-automation loops reusable while keeping business logic in n8n.
108
+ **Objective:** Make callback handling, reconnect lifecycle, outbound retry decisions, timeouts, and correlation predictable while keeping business logic in n8n.
109
109
 
110
110
  **Main deliverables:**
111
111
 
112
- - Reusable example/subworkflow contracts for Notification Hub, Report Hub, and Action Router.
113
- - Action classes: **Direct** (deterministic, no LLM), **Parameterized** (collect/validate needed inputs), and **Intelligent** (optional ChatGPT Worker).
114
- - Standard `outTrackId`, correlation, idempotency, and state model; acknowledgement, retry, refresh, run, ignore, and open patterns.
115
- - Explicit confirmation and authorization patterns for destructive actions.
116
- - Documented paths from callback to deterministic subworkflow or optional AI worker.
112
+ - Bounded process-local callback duplicate suppression keyed by DingTalk `messageId`.
113
+ - Predictable Stream reconnect/deactivation lifecycle using the existing native WebSocket transport.
114
+ - Bounded external request timeouts and sanitized operation/phase/retry/ambiguity/correlation error details.
115
+ - Automatic retry only for an explicit invalid-token write rejection; ambiguous writes, including Create timeouts, are never retried by the node.
117
116
 
118
- **Acceptance gates:** Examples make deterministic routing the default, show bounded retry and duplicate/replay handling, and avoid blind Create replay. Destructive examples require explicit confirmation/authorization. Keep the kit portable as reusable n8n assets/examples.
117
+ **Acceptance gates:** Unit coverage for duplicate ACK/emit, TTL and bounded eviction, reconnect lifecycle, retry safety, timeout/error shape and correlation passed. Connected-instance validation passed for Create & Deliver, Trigger deactivation/reactivation, a real callback after reactivation, normalized correlation, and card Update. Exact-message duplicate suppression is unit-tested; live duplicate redelivery was not forced.
119
118
 
120
- **Non-goals / deferrals:** Do not hard-code business automation into the node. The kit does not claim delivery guarantees beyond the underlying node/platform behavior, and does not require ChatGPT for Direct actions.
119
+ **Non-goals / deferrals:** No durable or distributed exactly-once guarantee, no persistent deduplication store, and no hard-coded business automation. Workflows own durable business idempotency.
121
120
 
122
- **Compatibility and migration:** Track state and idempotency explicitly in the workflow layer; do not imply the current plugin has a persistent deduplication store. Existing node identifiers and fields remain usable.
121
+ **Compatibility and migration:** Additive event/result/error fields only. Dedupe is bounded in-process and expires; it is not durable exactly-once across restart or multiple instances. Existing node identifiers and fields remain usable.
123
122
 
124
123
  ### v0.8.0 — Automation Presentation Kit
125
124
 
@@ -136,7 +135,7 @@ The beta.1 implementation is specified in [INTERACTIONS.md](INTERACTIONS.md), wi
136
135
 
137
136
  **Non-goals / deferrals:** These are patterns and examples, not built-in external-service integrations or a generic content ingestion suite.
138
137
 
139
- **Compatibility and migration:** Build on the v0.4 presentation contract and v0.7 workflow kit. Keep existing raw JSON recipes available.
138
+ **Compatibility and migration:** Build on the v0.4 presentation contract and v0.7 reliability primitives. Keep existing raw JSON recipes available.
140
139
 
141
140
  ### v0.9.0 — Production Hardening
142
141
 
@@ -1,5 +1,23 @@
1
1
  # Release verification record — 2026-10-24
2
2
 
3
+ ## v0.7.0-beta.1 public prerelease — 2026-10-25
4
+
5
+ The annotated tag `v0.7.0-beta.1` (tag object `2b0fafa12e406b8cd829c4851ac81d8d9f87b287`) points to release commit `d598fc289c871d46f183692ae91eaee9ce4901f7`. Main CI [40173686130](https://github.com/NTLx/n8n-nodes-dingtalk-card/actions/runs/40173686130) passed on that exact commit. The repository’s manually dispatched [Trusted Publisher/OIDC publish workflow](https://github.com/NTLx/n8n-nodes-dingtalk-card/actions/runs/40173797932), dispatched on the release tag, reran npm ci, lint, typecheck, build, all 74 tests, package verification, production audit and release-ref validation before publication; all steps passed. The stable publish step was skipped. The tag push did not produce a separate CI workflow run; the same commit passed main CI and the tag-based publish run repeated the complete CI quality gates.
6
+
7
+ Public npm dist-tags are `beta -> 0.7.0-beta.1` and `latest -> 0.6.0`. Registry metadata reports `gitHead=d598fc289c871d46f183692ae91eaee9ce4901f7`, 34 files, unpacked size 257,412 bytes, tarball `https://registry.npmjs.org/n8n-nodes-dingtalk-card/-/n8n-nodes-dingtalk-card-0.7.0-beta.1.tgz`, SHA-1 `bfe29ec865425ae361c22348030848b85de3b371`, and integrity `sha512-rQFSeFGnwTC5b50nRBfKm3PcGxVQWBDVsCExImPBjiyaE6Pb1YRYwBT+N9d0PXlmqqlOJomw1XO2usjdeG67uQ==`. An isolated install verified 94 registry signatures and 6 attestations with `npm audit signatures`; no invalid or missing signatures were reported. The verified SLSA provenance subject SHA-512 is `ad01527851a7c130b96f9d274417ca9b73dc1b15505810d5b021312263c18e2c9a13a3dbd58458c014fe37d7743d7966aaa94e2689b0d573b6bac8dd786ebbb9`. Its predicate identifies `.github/workflows/publish.yml`, `refs/tags/v0.7.0-beta.1`, the release commit above, and publish run 40173797932 attempt 1. The transparency-log entry is [3454929240](https://search.sigstore.dev/?logIndex=3454929240).
8
+
9
+ The [GitHub prerelease](https://github.com/NTLx/n8n-nodes-dingtalk-card/releases/tag/v0.7.0-beta.1) (release ID `423226951`) has `draft=false` and `prerelease=true`. Its sole canonical asset `n8n-nodes-dingtalk-card-0.7.0-beta.1.tgz` is byte-identical to the public npm tarball: 77,300 bytes, SHA-256 `8a3c6d92a297e60001e5c829251dd2637396fa5b999909af092bf4d9039f827a`, SHA-1 `bfe29ec865425ae361c22348030848b85de3b371`, and npm integrity as above. GitHub reports the matching SHA-256 asset digest. Use the exact package spec `n8n-nodes-dingtalk-card@0.7.0-beta.1` for connected-instance validation. No production n8n instance was changed for this release.
10
+
11
+ ## v0.7.0-beta.1 connected-instance acceptance — 2026-10-25
12
+
13
+ The maintainer reports that beta.1 was installed in production n8n. Manual Create & Deliver execution **5702** succeeded and returned `correlation.outTrackId`. The workflow was deactivated and reactivated successfully on the same active package version. After reactivation, real Trigger callback execution **5703** succeeded with `normalized.interaction.params = {"gate":"event-reliability","source":"v0.7.0-beta.1"}` and `normalized.correlation = {"messageId":"be36c1a_1572_1a125150ef8_ee37cc","topic":"/v1.0/card/instances/callback"}`. The original card update succeeded with `correlation.outTrackId = "6d7b686c-848b-440c-84b9-87cb121ad97b"`; the temporary listener was then unpublished.
14
+
15
+ The execution 5702/5703 data, workflow deactivation/reactivation state, normalized params/correlation, and update result were independently queried by the parent ChatGPT through the production n8n MCP on n8n-VPS. The screenshot was user-supplied client-side corroboration. This verifies Create & Deliver, Trigger deactivation/reactivation, a real callback after reactivation, normalized correlation, and the original card update on the connected instance. Exact-message duplicate suppression is covered by deterministic unit tests; live duplicate redelivery was not forced.
16
+
17
+ ## v0.7.0 stable release preparation — 2026-10-25
18
+
19
+ Stable promotion changes package version metadata, release workflow/ref policy, and release-status documentation only. No runtime source, dependencies, or beta behavior changed. Package and lockfile versions are promoted together from `0.7.0-beta.1` to `0.7.0`; beta remains `0.7.0-beta.1` after stable publication. The connected-instance acceptance above completes the v0.7 stable gate for the tested path; duplicate redelivery remains covered only by deterministic tests.
20
+
3
21
  ## v0.6.0-beta.3 candidate: serialized A2UI action params normalization — 2026-10-25
4
22
 
5
23
  The production callback received on beta.2 exposed descriptor params as `cardPrivateData.params = {"params":"{\"source\":\"v0.6-beta.2\"}"}`, so workflows had to parse the nested string. Beta.3 changes only `normalized.interaction.params` for this exact one-key wrapper when its value parses as a JSON object. Local tests cover the observed shape, malformed JSON, JSON non-objects, additional wrapper keys, ordinary params, and preservation of top-level params and raw callback data. The change passed lint, typecheck, build, all 71 unit tests, packed-package standalone loading/registration (34 allowlisted files), and `git diff --check` at package version `0.6.0-beta.2`. No production n8n was changed. Live confirmation of the normalized result remains part of release validation.
@@ -24,6 +42,16 @@ Stable promotion preserves the beta.3 production-accepted runtime. Release-only
24
42
 
25
43
  On Node **26.12.1 / npm 12.2.1**, the release gates passed: clean `npm ci`; lint; typecheck; build; all **71 tests**; package verification with standalone packed class loading/registration (**34 allowlisted files**, 74,569 bytes); production audit (**0 vulnerabilities**); release-ref self-test and `0.6.0 -> latest` check; `npm pack --dry-run` inspection (**28 package entries**); and `git diff --check`. The first parallel test attempt raced the concurrent build and failed while `dist` was being rewritten; the complete test suite passed after the build completed. GitHub CI for the pushed release commit is required before tagging. The existing publish workflow repeats all gates and publishes the stable tag through npm Trusted Publisher/OIDC with provenance.
26
44
 
45
+ ## v0.6.0 stable public release — 2026-10-25
46
+
47
+ Annotated tag `v0.6.0` (tag object `7c860a9997bbb895b96489402c5d906083ff2825`) points to release commit `de2c79496e0e2a699b312448eff0c4de9cca8ed6`. Main CI run [40171338211](https://github.com/NTLx/n8n-nodes-dingtalk-card/actions/runs/40171338211) and tag CI run [40171408700](https://github.com/NTLx/n8n-nodes-dingtalk-card/actions/runs/40171408700) passed. The existing Trusted Publisher/OIDC publish workflow run [40171484374](https://github.com/NTLx/n8n-nodes-dingtalk-card/actions/runs/40171484374), attempt 1, passed the repeated quality gates, tag/package check, stable npm publication, dist-tag verification and published metadata/tarball verification.
48
+
49
+ Public npm dist-tags are `latest -> 0.6.0` and `beta -> 0.6.0-beta.3`. Registry metadata reports `gitHead=de2c79496e0e2a699b312448eff0c4de9cca8ed6`, 34 files, unpacked size 248,072 bytes, tarball SHA-1 `26dff4c3d9f6bce9718780c0fd9116bce81c7212`, and integrity `sha512-5ik4E4QdCVHnBp2DcSJgxHLPLqs7EZxY07QadtIIYVKJAc/90MBGHLaRFZWkQ468N4yFYj9fxQNH3GeUpLy3hw==`. An isolated install of this exact package passed `npm audit signatures` with no invalid or missing signatures/attestations; the command verified 94 registry signatures and 6 attestations across the install. The package SLSA provenance subject SHA-512 is `e6293813841d0951e7069d83712260c472cf2eab3b119c58d3b41a76d20861528901cffdd0c0461cb6911595a4438ebc378c85623f5fc50347dc6794a4bcb787`. Its verified predicate identifies `.github/workflows/publish.yml`, `refs/tags/v0.6.0`, release commit `de2c79496e0e2a699b312448eff0c4de9cca8ed6`, and publish run 40171484374 attempt 1; the transparency log entry is `3453987183`.
50
+
51
+ The [GitHub stable release](https://github.com/NTLx/n8n-nodes-dingtalk-card/releases/tag/v0.6.0) (release ID `423205382`) is published with `draft=false` and `prerelease=false`. Its only asset, `n8n-nodes-dingtalk-card-0.6.0.tgz`, is byte-identical to the public npm tarball: 74,770 bytes, SHA-256 `6d20e4f788db3c4762f80852e52f9fbaa820c1f1e6173d43b72db4563d176a11`, and SHA-1 `26dff4c3d9f6bce9718780c0fd9116bce81c7212`; GitHub reports the matching SHA-256 digest.
52
+
53
+ The connected-instance acceptance evidence and its scope are recorded above. This release did not modify production n8n or change the validated A2UI runtime behavior.
54
+
27
55
  ## v0.5.0-beta.1 local verification scope (historical, before production acceptance)
28
56
 
29
57
  This candidate adds normalized callback fields beside the existing output contract. Local verification exercises the existing request-button/action callback path, normalization precedence, optional fields, timestamps, raw-data control, privacy-safe diagnostics, and ACK-before-emit ordering. Form and selection interactions have not been live-verified and remain deferred. No production n8n was touched for this candidate. Stable release still requires scoped live acceptance before expanding interaction claims.
@@ -393,3 +421,10 @@ Public npm dist-tags are `beta -> 0.6.0-beta.1` and `latest -> 0.5.0`. Registry
393
421
  The [GitHub prerelease](https://github.com/NTLx/n8n-nodes-dingtalk-card/releases/tag/v0.6.0-beta.1) is published with `draft=false` and `prerelease=true`. Its only asset, `n8n-nodes-dingtalk-card-0.6.0-beta.1.tgz`, was downloaded from the public npm registry and verified byte-identical to the npm tarball: 69,986 bytes, 34 files, SHA-256 `00bfde616fb3cfccdb0de12adcee9e55858a9bb09b22b1171d87272b62c72605`, SHA-1 `02c18b3ad02461b28b4e9c687fb59d3a0adc9c19`, and npm SHA-512 integrity `sha512-hPEIfESbEweehI+UwC8cQ69m81RJK67dVrtHqbFDhbVpp8ZcMTovgsL2ct/2ZxK53YOib8h1VdCXGXX12S1OoQ==`.
394
422
 
395
423
  The official NORMAL schemaVersion 3.0.0 loop example is reference evidence only; it is not an accepted `universal-v1` template asset. No transformed `universal-v1` template import asset is shipped. Live template import, client rendering, Create, Update, and an action click through the callback path remain pending production beta acceptance. Production n8n was not touched for this release.
424
+ # v0.7 Event Handling & Reliability candidate
425
+
426
+ Local candidate scope: process-local duplicate callback suppression by `messageId` (10-minute TTL, 10,000-entry FIFO bound), existing native WebSocket reconnect lifecycle, 30-second bounded DingTalk HTTP requests, conservative write retry classification, and additive operation/error correlation metadata. Duplicates are acknowledged and ignored while their ID remains cached. This does not provide durable exactly-once delivery across process restart, cache eviction, multiple workers/instances, or independent package copies. Acknowledged events can still be lost after downstream failure.
427
+
428
+ Automatic write replay remains limited to one retry after DingTalk explicitly rejects the access token; the same payload and identifiers are retained. A timeout, network failure, HTTP 5xx, partial delivery or incomplete write response is not automatically retried and may have an ambiguous outcome. For Create & Deliver, verify before replay because the card may already exist; stable `outTrackId` is correlation, not a dedupe/upsert guarantee. Stream connection diagnostics remain generic and credential-safe.
429
+
430
+ Connected-instance stable gates still required: validate callback duplicate delivery/ACK behavior against a live DingTalk Stream; exercise disconnect/reconnect and Trigger deactivation on the supported n8n runtime; confirm workflow execution data retains the additive correlation/error shape and legacy top-level fields. No production instance was modified for this candidate.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-dingtalk-card",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "DingTalk Card Platform lifecycle nodes for n8n",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/NTLx/n8n-nodes-dingtalk-card#readme",