@foldrun/core 2.14.2 → 2.14.3

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.
@@ -116,7 +116,7 @@ const FLOW = [
116
116
  { key: "every", type: ["string", "number"], help: "trigger: watch — how often" },
117
117
  { key: "path", type: "string", help: "trigger: storage — the prefix under storage/" },
118
118
  { key: "filter", type: "object", validated: true, help: "webhook, email, flow or storage trigger: the fields a delivery must have to start a run — e.g. {action: [opened], sender: {type: {not_in: [Bot]}}}" },
119
- { key: "idempotency", type: "string", help: "a header name, or body:<field>, that marks a repeat delivery" },
119
+ { key: "idempotency", type: ["string", "boolean"], help: "a header name, or body:<field>, that marks a repeat delivery; none (or false) to turn off (signature: github defaults to x-github-delivery)" },
120
120
  { key: "debounce", type: ["string", "number"], help: "wait for a burst to end" },
121
121
  { key: "throttle", type: ["string", "number"], help: "at most one run per window" },
122
122
  { key: "sla", type: ["string", "number"], help: "tell someone when a run outlives it" },
package/dist/src/store.js CHANGED
@@ -1658,7 +1658,7 @@ export function parseFlow(file, raw) {
1658
1658
  // A header name is case-insensitive on the wire; a JSON field is not.
1659
1659
  // `body:eventId` lower-cased to `body:eventid` matched nothing, and a
1660
1660
  // dedupe that matches nothing admits everything.
1661
- idempotency: typeof data.idempotency === "string" ? idempotencyName(data.idempotency) : null,
1661
+ idempotency: idempotencyOf(data.idempotency, data.trigger, data.signature),
1662
1662
  debounce: durationOf(data.debounce),
1663
1663
  throttle: durationOf(data.throttle),
1664
1664
  catchup: data.catchup === "none" || data.catchup === "last" ? data.catchup : null,
@@ -1695,6 +1695,20 @@ export function parseSchemaText(text) {
1695
1695
  }
1696
1696
  return { schema: value };
1697
1697
  }
1698
+ /**
1699
+ * The flow's `idempotency:`, or its default. A GitHub-signed webhook dedupes
1700
+ * on `x-github-delivery` unless it says otherwise: GitHub's signature has no
1701
+ * timestamp, so a delivery captured once could be replayed for ever, and
1702
+ * every GitHub delivery carries that id (FOL-94). Any `idempotency:` the
1703
+ * file sets wins; `none` (or `false`, `off`) turns dedupe off.
1704
+ */
1705
+ function idempotencyOf(raw, trigger, signature) {
1706
+ if (raw === false || (typeof raw === "string" && /^(none|off|false)$/i.test(raw.trim())))
1707
+ return null;
1708
+ if (typeof raw === "string")
1709
+ return idempotencyName(raw);
1710
+ return trigger === "webhook" && String(signature) === "github" ? "x-github-delivery" : null;
1711
+ }
1698
1712
  function idempotencyName(raw) {
1699
1713
  const v = raw.trim();
1700
1714
  if (!v)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foldrun/core",
3
- "version": "2.14.2",
3
+ "version": "2.14.3",
4
4
  "description": "Agents are just folders. The format, the runner, the checks.",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "type": "module",
@@ -163,7 +163,7 @@ const FLOW: FieldSpec[] = [
163
163
  { key: "every", type: ["string", "number"], help: "trigger: watch — how often" },
164
164
  { key: "path", type: "string", help: "trigger: storage — the prefix under storage/" },
165
165
  { key: "filter", type: "object", validated: true, help: "webhook, email, flow or storage trigger: the fields a delivery must have to start a run — e.g. {action: [opened], sender: {type: {not_in: [Bot]}}}" },
166
- { key: "idempotency", type: "string", help: "a header name, or body:<field>, that marks a repeat delivery" },
166
+ { key: "idempotency", type: ["string", "boolean"], help: "a header name, or body:<field>, that marks a repeat delivery; none (or false) to turn off (signature: github defaults to x-github-delivery)" },
167
167
  { key: "debounce", type: ["string", "number"], help: "wait for a burst to end" },
168
168
  { key: "throttle", type: ["string", "number"], help: "at most one run per window" },
169
169
  { key: "sla", type: ["string", "number"], help: "tell someone when a run outlives it" },
package/src/store.ts CHANGED
@@ -2107,7 +2107,7 @@ export function parseFlow(file: string, raw: string): FlowInfo {
2107
2107
  // A header name is case-insensitive on the wire; a JSON field is not.
2108
2108
  // `body:eventId` lower-cased to `body:eventid` matched nothing, and a
2109
2109
  // dedupe that matches nothing admits everything.
2110
- idempotency: typeof data.idempotency === "string" ? idempotencyName(data.idempotency) : null,
2110
+ idempotency: idempotencyOf(data.idempotency, data.trigger, data.signature),
2111
2111
  debounce: durationOf(data.debounce),
2112
2112
  throttle: durationOf(data.throttle),
2113
2113
  catchup: data.catchup === "none" || data.catchup === "last" ? data.catchup : null,
@@ -2144,6 +2144,19 @@ export function parseSchemaText(text: string): { schema: Record<string, unknown>
2144
2144
  return { schema: value as Record<string, unknown> };
2145
2145
  }
2146
2146
 
2147
+ /**
2148
+ * The flow's `idempotency:`, or its default. A GitHub-signed webhook dedupes
2149
+ * on `x-github-delivery` unless it says otherwise: GitHub's signature has no
2150
+ * timestamp, so a delivery captured once could be replayed for ever, and
2151
+ * every GitHub delivery carries that id (FOL-94). Any `idempotency:` the
2152
+ * file sets wins; `none` (or `false`, `off`) turns dedupe off.
2153
+ */
2154
+ function idempotencyOf(raw: unknown, trigger: unknown, signature: unknown): string | null {
2155
+ if (raw === false || (typeof raw === "string" && /^(none|off|false)$/i.test(raw.trim()))) return null;
2156
+ if (typeof raw === "string") return idempotencyName(raw);
2157
+ return trigger === "webhook" && String(signature) === "github" ? "x-github-delivery" : null;
2158
+ }
2159
+
2147
2160
  function idempotencyName(raw: string): string | null {
2148
2161
  const v = raw.trim();
2149
2162
  if (!v) return null;