@things-factory/webhook 10.1.39 → 10.1.40

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.
@@ -6,7 +6,7 @@ export * from './webhook-destination/webhook-destination';
6
6
  export * from './webhook-destination/webhook-destination-resolver';
7
7
  export * from './webhook-outbox-event/webhook-outbox-event';
8
8
  export * from './webhook-sequence/webhook-sequence';
9
- export declare const entities: (typeof WebhookOutboxEvent | typeof WebhookDestination | typeof WebhookSequence)[];
9
+ export declare const entities: (typeof WebhookDestination | typeof WebhookOutboxEvent | typeof WebhookSequence)[];
10
10
  export declare const schema: {
11
11
  resolverClasses: (typeof WebhookDestinationResolver)[];
12
12
  };
@@ -14,7 +14,7 @@ function planStream(stream, destination, env, options) {
14
14
  stream,
15
15
  note: {
16
16
  key: `no-destination:${subdomain}:${peer}`,
17
- line: `[webhook] facts are piling up for ${where} but no destination is declared for that peer - set it on the webhook destinations screen. Nothing is lost.`
17
+ line: `[webhook] facts are piling up for ${where} but no destination is declared for that peer - add one with peer "${peer}" on the webhook destinations screen; a row under any other name does not carry them. Nothing is lost.`
18
18
  }
19
19
  };
20
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pump-round.js","sourceRoot":"","sources":["../../../server/service/outbox/pump-round.ts"],"names":[],"mappings":"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","sourcesContent":["import { webhookSecretCandidates, webhookSecretFrom, webhookSecretMissingNote } from '@operato/ops-contract'\n\n/**\n * **What one round of the pump does with each stream** — a pure judgement: no database, no network, and\n * the environment arrives as an argument (ADR-0078 decision 5 as re-ruled on 2026-09-24).\n *\n * ═════════════════════════════════════════════════════════════════════════\n * ## Silence is the failure this guards\n *\n * Facts can pile up for three reasons, and none of them fails anything: no destination is declared for\n * that peer, a person turned the destination off, or nothing can sign. Each sends the administrator\n * somewhere different - declare it, turn it back on, set a key - so each is its own sentence, and each\n * comes with a key the pump says it once by. Said every round, the line buries everything else.\n *\n * ## The key's name belongs to the contract\n *\n * `webhookSecretFrom` walks the contract's ladder - per peer, then per domain - and a product passes no\n * names of its own. Pinned to the peer, two products on one installation never share a key. A product that\n * ran before this ladder passes `legacyBases`; a key found there is used and said once, because a quiet\n * success means nobody ever moves to the new name.\n *\n * The missing-key sentence names the variables. That is for the process log only - the contract says so\n * (`webhookSecretMissingNote`) - and this sentence never goes into a response.\n * ═════════════════════════════════════════════════════════════════════════\n */\n\n/** One stream with facts waiting. The pump reads these off the pending rows. */\nexport interface PendingStream {\n domainId: string\n /** The tenant's name - what the key's name is built from and what a person reads in the log. */\n subdomain: string\n peer: string\n scope: string\n}\n\n/** What the destination row says, read by the caller. */\nexport interface DestinationReading {\n endpoint: string\n active: boolean\n params?: { receivingDomain?: string } & Record<string, unknown>\n}\n\nexport interface RoundNote {\n /** The same cause on the same stream carries the same key, so it is said once. */\n key: string\n line: string\n}\n\nexport type RoundStep =\n | {\n kind: 'deliver'\n stream: PendingStream\n endpoint: string\n secret: string\n headers: Record<string, string>\n /** Clock readings are kept per peer - two peers' clocks are not one clock. */\n clockKey: string\n note?: RoundNote\n }\n | { kind: 'wait'; stream: PendingStream; note: RoundNote }\n\nexport interface RoundOptions {\n /** Names this product's installations used before the ladder. Absent for a product that never had one. */\n legacyBases?: readonly string[]\n}\n\nexport function planRound(\n streams: readonly PendingStream[],\n destinationOf: (domainId: string, peer: string) => DestinationReading | undefined,\n env: Record<string, string | undefined>,\n options: RoundOptions = {}\n): RoundStep[] {\n return streams.map(stream => planStream(stream, destinationOf(stream.domainId, stream.peer), env, options))\n}\n\nfunction planStream(stream: PendingStream, destination: DestinationReading | undefined, env: Record<string, string | undefined>, options: RoundOptions): RoundStep {\n const { subdomain, peer } = stream\n const where = `\"${subdomain}\" → ${peer}`\n\n if (!destination?.endpoint) {\n return {\n kind: 'wait',\n stream,\n note: {\n key: `no-destination:${subdomain}:${peer}`,\n line: `[webhook] facts are piling up for ${where} but no destination is declared for that peer - set it on the webhook destinations screen. Nothing is lost.`\n }\n }\n }\n\n if (!destination.active) {\n return {\n kind: 'wait',\n stream,\n note: {\n key: `turned-off:${subdomain}:${peer}`,\n line: `[webhook] facts are piling up for ${where} - its destination is turned off. Nothing is lost; turn it back on when the receiver is ready.`\n }\n }\n }\n\n const lookup = { purpose: 'signing' as const, domain: subdomain, peer, ...(options.legacyBases?.length ? { legacyBases: options.legacyBases } : {}) }\n const found = webhookSecretFrom(env, lookup)\n\n /*\n * The answer is a union. This package compiles without `strictNullChecks`, and there a check on `secret`\n * does not narrow it - so ask which branch it is by the field only that branch has.\n */\n if (!('opened' in found)) {\n return {\n kind: 'wait',\n stream,\n note: {\n key: `no-secret:${subdomain}:${peer}`,\n line: `[webhook] facts are piling up for ${where} but nothing can sign them - ${webhookSecretMissingNote(found.tried)} Nothing is lost; they go as soon as a key is there.`\n }\n }\n }\n\n const { secret, opened } = found\n const receivingDomain = destination.params?.receivingDomain\n\n return {\n kind: 'deliver',\n stream,\n endpoint: destination.endpoint,\n secret,\n /* The receiver's own tenant - never assumed to be ours. */\n headers: typeof receivingDomain === 'string' && receivingDomain ? { 'x-things-factory-domain': receivingDomain } : {},\n clockKey: `${subdomain}:${peer}`,\n ...(opened.compatibility\n ? {\n note: {\n key: `legacy-secret:${subdomain}:${peer}`,\n line: `[webhook] ${where} is signed with ${opened.name}, a name kept for installations that ran before - move the key to ${webhookSecretCandidates({ purpose: 'signing', domain: subdomain, peer })[0].name}.`\n }\n }\n : {})\n }\n}\n"]}
1
+ {"version":3,"file":"pump-round.js","sourceRoot":"","sources":["../../../server/service/outbox/pump-round.ts"],"names":[],"mappings":";;AAkEA,8BAOC;AAzED,wDAA4G;AAkE5G,SAAgB,SAAS,CACvB,OAAiC,EACjC,aAAiF,EACjF,GAAuC,EACvC,UAAwB,EAAE;IAE1B,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;AAC7G,CAAC;AAED,SAAS,UAAU,CAAC,MAAqB,EAAE,WAA2C,EAAE,GAAuC,EAAE,OAAqB;IACpJ,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,MAAM,CAAA;IAClC,MAAM,KAAK,GAAG,IAAI,SAAS,OAAO,IAAI,EAAE,CAAA;IAExC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,CAAC;QAC3B,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,MAAM;YACN,IAAI,EAAE;gBACJ,GAAG,EAAE,kBAAkB,SAAS,IAAI,IAAI,EAAE;gBAC1C,IAAI,EAAE,qCAAqC,KAAK,sEAAsE,IAAI,wGAAwG;aACnO;SACF,CAAA;IACH,CAAC;IAED,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;QACxB,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,MAAM;YACN,IAAI,EAAE;gBACJ,GAAG,EAAE,cAAc,SAAS,IAAI,IAAI,EAAE;gBACtC,IAAI,EAAE,qCAAqC,KAAK,gGAAgG;aACjJ;SACF,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,SAAkB,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;IACrJ,MAAM,KAAK,GAAG,IAAA,gCAAiB,EAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAE5C;;;OAGG;IACH,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,MAAM;YACN,IAAI,EAAE;gBACJ,GAAG,EAAE,aAAa,SAAS,IAAI,IAAI,EAAE;gBACrC,IAAI,EAAE,qCAAqC,KAAK,gCAAgC,IAAA,uCAAwB,EAAC,KAAK,CAAC,KAAK,CAAC,sDAAsD;aAC5K;SACF,CAAA;IACH,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAChC,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,EAAE,eAAe,CAAA;IAE3D,OAAO;QACL,IAAI,EAAE,SAAS;QACf,MAAM;QACN,QAAQ,EAAE,WAAW,CAAC,QAAQ;QAC9B,MAAM;QACN,2DAA2D;QAC3D,OAAO,EAAE,OAAO,eAAe,KAAK,QAAQ,IAAI,eAAe,CAAC,CAAC,CAAC,EAAE,yBAAyB,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE;QACrH,QAAQ,EAAE,GAAG,SAAS,IAAI,IAAI,EAAE;QAChC,GAAG,CAAC,MAAM,CAAC,aAAa;YACtB,CAAC,CAAC;gBACE,IAAI,EAAE;oBACJ,GAAG,EAAE,iBAAiB,SAAS,IAAI,IAAI,EAAE;oBACzC,IAAI,EAAE,aAAa,KAAK,mBAAmB,MAAM,CAAC,IAAI,qEAAqE,IAAA,sCAAuB,EAAC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG;iBAC/M;aACF;YACH,CAAC,CAAC,EAAE,CAAC;KACR,CAAA;AACH,CAAC","sourcesContent":["import { webhookSecretCandidates, webhookSecretFrom, webhookSecretMissingNote } from '@operato/ops-contract'\n\n/**\n * **What one round of the pump does with each stream** — a pure judgement: no database, no network, and\n * the environment arrives as an argument (ADR-0078 decision 5 as re-ruled on 2026-09-24).\n *\n * ═════════════════════════════════════════════════════════════════════════\n * ## Silence is the failure this guards\n *\n * Facts can pile up for three reasons, and none of them fails anything: no destination is declared for\n * that peer, a person turned the destination off, or nothing can sign. Each sends the administrator\n * somewhere different - declare it, turn it back on, set a key - so each is its own sentence, and each\n * comes with a key the pump says it once by. Said every round, the line buries everything else.\n *\n * ## The key's name belongs to the contract\n *\n * `webhookSecretFrom` walks the contract's ladder - per peer, then per domain - and a product passes no\n * names of its own. Pinned to the peer, two products on one installation never share a key. A product that\n * ran before this ladder passes `legacyBases`; a key found there is used and said once, because a quiet\n * success means nobody ever moves to the new name.\n *\n * The missing-key sentence names the variables. That is for the process log only - the contract says so\n * (`webhookSecretMissingNote`) - and this sentence never goes into a response.\n * ═════════════════════════════════════════════════════════════════════════\n */\n\n/** One stream with facts waiting. The pump reads these off the pending rows. */\nexport interface PendingStream {\n domainId: string\n /** The tenant's name - what the key's name is built from and what a person reads in the log. */\n subdomain: string\n peer: string\n scope: string\n}\n\n/** What the destination row says, read by the caller. */\nexport interface DestinationReading {\n endpoint: string\n active: boolean\n params?: { receivingDomain?: string } & Record<string, unknown>\n}\n\nexport interface RoundNote {\n /** The same cause on the same stream carries the same key, so it is said once. */\n key: string\n line: string\n}\n\nexport type RoundStep =\n | {\n kind: 'deliver'\n stream: PendingStream\n endpoint: string\n secret: string\n headers: Record<string, string>\n /** Clock readings are kept per peer - two peers' clocks are not one clock. */\n clockKey: string\n note?: RoundNote\n }\n | { kind: 'wait'; stream: PendingStream; note: RoundNote }\n\nexport interface RoundOptions {\n /** Names this product's installations used before the ladder. Absent for a product that never had one. */\n legacyBases?: readonly string[]\n}\n\nexport function planRound(\n streams: readonly PendingStream[],\n destinationOf: (domainId: string, peer: string) => DestinationReading | undefined,\n env: Record<string, string | undefined>,\n options: RoundOptions = {}\n): RoundStep[] {\n return streams.map(stream => planStream(stream, destinationOf(stream.domainId, stream.peer), env, options))\n}\n\nfunction planStream(stream: PendingStream, destination: DestinationReading | undefined, env: Record<string, string | undefined>, options: RoundOptions): RoundStep {\n const { subdomain, peer } = stream\n const where = `\"${subdomain}\" → ${peer}`\n\n if (!destination?.endpoint) {\n return {\n kind: 'wait',\n stream,\n note: {\n key: `no-destination:${subdomain}:${peer}`,\n line: `[webhook] facts are piling up for ${where} but no destination is declared for that peer - add one with peer \"${peer}\" on the webhook destinations screen; a row under any other name does not carry them. Nothing is lost.`\n }\n }\n }\n\n if (!destination.active) {\n return {\n kind: 'wait',\n stream,\n note: {\n key: `turned-off:${subdomain}:${peer}`,\n line: `[webhook] facts are piling up for ${where} - its destination is turned off. Nothing is lost; turn it back on when the receiver is ready.`\n }\n }\n }\n\n const lookup = { purpose: 'signing' as const, domain: subdomain, peer, ...(options.legacyBases?.length ? { legacyBases: options.legacyBases } : {}) }\n const found = webhookSecretFrom(env, lookup)\n\n /*\n * The answer is a union. This package compiles without `strictNullChecks`, and there a check on `secret`\n * does not narrow it - so ask which branch it is by the field only that branch has.\n */\n if (!('opened' in found)) {\n return {\n kind: 'wait',\n stream,\n note: {\n key: `no-secret:${subdomain}:${peer}`,\n line: `[webhook] facts are piling up for ${where} but nothing can sign them - ${webhookSecretMissingNote(found.tried)} Nothing is lost; they go as soon as a key is there.`\n }\n }\n }\n\n const { secret, opened } = found\n const receivingDomain = destination.params?.receivingDomain\n\n return {\n kind: 'deliver',\n stream,\n endpoint: destination.endpoint,\n secret,\n /* The receiver's own tenant - never assumed to be ours. */\n headers: typeof receivingDomain === 'string' && receivingDomain ? { 'x-things-factory-domain': receivingDomain } : {},\n clockKey: `${subdomain}:${peer}`,\n ...(opened.compatibility\n ? {\n note: {\n key: `legacy-secret:${subdomain}:${peer}`,\n line: `[webhook] ${where} is signed with ${opened.name}, a name kept for installations that ran before - move the key to ${webhookSecretCandidates({ purpose: 'signing', domain: subdomain, peer })[0].name}.`\n }\n }\n : {})\n }\n}\n"]}