verdifax 1.4.0 → 1.5.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/dist/index.mjs CHANGED
@@ -48,6 +48,17 @@ var APIKeyRequiredError = class extends VerdifaxError {
48
48
  super("verdifax: apiKey is required for this endpoint");
49
49
  }
50
50
  };
51
+ var SignalFeedError = class extends VerdifaxError {
52
+ constructor(lastSequence, reason, options) {
53
+ super(
54
+ `verdifax: the signal feed stopped after sequence ${lastSequence}: ${reason}; follow again with after: ${lastSequence}`,
55
+ options
56
+ );
57
+ this.lastSequence = lastSequence;
58
+ }
59
+ lastSequence;
60
+ name = "VerdifaxSignalFeedError";
61
+ };
51
62
 
52
63
  // src/signing.ts
53
64
  import {
@@ -96,6 +107,18 @@ function validateJobName(value, fieldName) {
96
107
  );
97
108
  }
98
109
  }
110
+ function requirePositiveId(id, name) {
111
+ if (!Number.isSafeInteger(id) || id <= 0) {
112
+ throw new VerdifaxError(`verdifax: ${name} must be a whole number > 0`);
113
+ }
114
+ }
115
+ function asOwnerOf(options, method) {
116
+ const asOwner = options?.asOwner;
117
+ if (asOwner !== void 0 && typeof asOwner !== "boolean") {
118
+ throw new VerdifaxError(`verdifax: ${method} asOwner must be true or false`);
119
+ }
120
+ return asOwner === true;
121
+ }
99
122
 
100
123
  // src/helpers.ts
101
124
  function formatProviderPayload(provider, prompt, response) {
@@ -692,8 +715,324 @@ var VerdifaxJob = class {
692
715
  }
693
716
  };
694
717
 
718
+ // src/signals.ts
719
+ import { createHash as createHash2, createHmac, timingSafeEqual } from "crypto";
720
+ var SIGNAL_FORM = "verdifax.signal.v1";
721
+ var SIGNALS_PATH = "/signals";
722
+ var ACCOUNT_SIGNALS_PATH = "/account/signals";
723
+ var FEED_KEYS_PATH = "/account/feed-keys";
724
+ var WEBHOOKS_PATH = "/account/webhooks";
725
+ var MAX_SIGNAL_LIMIT = 500;
726
+ var MAX_SIGNAL_WAIT_SECONDS = 25;
727
+ var DEFAULT_FOLLOW_WAIT_SECONDS = 25;
728
+ var SIGNAL_TIMEOUT_MARGIN_MS = 3e4;
729
+ var EMPTY_PAGE_PAUSE_MS = 1e3;
730
+ var MAX_DELIVERY_LIMIT = 500;
731
+ var MAX_FEED_KEY_NAME_BYTES = 200;
732
+ var WEBHOOK_SECRET_PREFIX = "whsec_";
733
+ var WEBHOOK_SIGNATURE_VERSION = "v1";
734
+ var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
735
+ var HMAC_SHA256_BYTES = 32;
736
+ var SIGNAL_KINDS = Object.freeze([
737
+ "run.refused",
738
+ "run.halted",
739
+ "run.failed",
740
+ "policy.changed",
741
+ "signoff.recorded",
742
+ "approver.registered",
743
+ "approver.revoked",
744
+ "key.created",
745
+ "key.revoked"
746
+ ]);
747
+ function signalsRead(options, method, clientTimeoutMs) {
748
+ const asOwner = asOwnerOf(options, method);
749
+ const after = given(options?.after, 0);
750
+ const limit = options?.limit;
751
+ const wait = given(options?.wait, 0);
752
+ requireAfter(after, method);
753
+ if (limit !== void 0) requireLimit(limit, method, MAX_SIGNAL_LIMIT);
754
+ requireWait(wait, method);
755
+ const q = new URLSearchParams();
756
+ q.set("after", String(after));
757
+ if (limit !== void 0) q.set("limit", String(limit));
758
+ q.set("wait", String(wait));
759
+ return {
760
+ path: `${asOwner ? ACCOUNT_SIGNALS_PATH : SIGNALS_PATH}?${q.toString()}`,
761
+ timeoutMs: Math.max(clientTimeoutMs, wait * 1e3 + SIGNAL_TIMEOUT_MARGIN_MS)
762
+ };
763
+ }
764
+ function followStart(options) {
765
+ const method = "followSignals";
766
+ const asOwner = asOwnerOf(options, method);
767
+ const after = given(options?.after, 0);
768
+ const wait = given(options?.wait, DEFAULT_FOLLOW_WAIT_SECONDS);
769
+ requireAfter(after, method);
770
+ requireWait(wait, method);
771
+ return { after, wait, asOwner };
772
+ }
773
+ async function* followSignalFeed(read, start) {
774
+ let last = start.after;
775
+ for (; ; ) {
776
+ let page;
777
+ try {
778
+ page = await read(last);
779
+ } catch (err) {
780
+ throw new SignalFeedError(
781
+ last,
782
+ err instanceof Error ? err.message : String(err),
783
+ { cause: err }
784
+ );
785
+ }
786
+ const events = pageEvents(page, last);
787
+ for (const event of events) {
788
+ yield event;
789
+ last = event.data.sequence;
790
+ }
791
+ if (events.length === 0 && start.wait === 0) {
792
+ await new Promise((r) => setTimeout(r, EMPTY_PAGE_PAUSE_MS));
793
+ }
794
+ }
795
+ }
796
+ function pageEvents(page, after) {
797
+ const events = page?.events ?? [];
798
+ if (!Array.isArray(events)) {
799
+ throw new SignalFeedError(after, "the server's page has no events list");
800
+ }
801
+ let previous = after;
802
+ for (const event of events) {
803
+ const sequence = event?.data?.sequence;
804
+ if (!Number.isSafeInteger(sequence) || sequence <= previous) {
805
+ throw new SignalFeedError(
806
+ after,
807
+ `the server's page holds an event out of order (sequence ${String(sequence)} after ${previous})`
808
+ );
809
+ }
810
+ previous = sequence;
811
+ }
812
+ if (page?.next_after !== previous) {
813
+ throw new SignalFeedError(
814
+ after,
815
+ `the server's next_after ${String(page?.next_after)} is not the last sequence on its page (${previous})`
816
+ );
817
+ }
818
+ return events;
819
+ }
820
+ function feedKeyBody(name, expiresAt) {
821
+ if (typeof name !== "string" || name.trim() === "" || Buffer.byteLength(name.trim(), "utf8") > MAX_FEED_KEY_NAME_BYTES) {
822
+ throw new VerdifaxError(
823
+ "verdifax: createFeedKey needs a name of at most 200 bytes"
824
+ );
825
+ }
826
+ if (expiresAt !== void 0 && typeof expiresAt !== "string") {
827
+ throw new VerdifaxError(
828
+ "verdifax: createFeedKey expiresAt must be an RFC 3339 time or a YYYY-MM-DD date, as a string"
829
+ );
830
+ }
831
+ return expiresAt === void 0 ? { name } : { name, expires_at: expiresAt };
832
+ }
833
+ function webhookBody(url, options) {
834
+ if (typeof url !== "string" || !url.startsWith("https://") || url.length <= 8) {
835
+ throw new VerdifaxError(
836
+ "verdifax: createWebhook needs a url that starts with https://"
837
+ );
838
+ }
839
+ const types = options?.types;
840
+ if (types === void 0) return { url };
841
+ if (!Array.isArray(types)) {
842
+ throw new VerdifaxError(
843
+ "verdifax: createWebhook types must be a list of signal kinds"
844
+ );
845
+ }
846
+ const seen = /* @__PURE__ */ new Set();
847
+ for (const kind of types) {
848
+ if (!SIGNAL_KINDS.includes(kind) || seen.has(kind)) {
849
+ throw new VerdifaxError(
850
+ `verdifax: createWebhook types must be distinct signal kinds (${SIGNAL_KINDS.join(", ")}); an empty list takes every kind`
851
+ );
852
+ }
853
+ seen.add(kind);
854
+ }
855
+ return { url, types: [...types] };
856
+ }
857
+ function deliveriesPath(id, options) {
858
+ const limit = options?.limit;
859
+ if (limit === void 0) return `${WEBHOOKS_PATH}/${id}/deliveries`;
860
+ requireLimit(limit, "listWebhookDeliveries", MAX_DELIVERY_LIMIT);
861
+ return `${WEBHOOKS_PATH}/${id}/deliveries?limit=${limit}`;
862
+ }
863
+ function verifyWebhook(headers, body, secret, options = {}) {
864
+ const key = webhookSecretBytes(secret);
865
+ const tolerance = options?.toleranceSeconds ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
866
+ if (typeof tolerance !== "number" || !Number.isFinite(tolerance) || tolerance < 0) {
867
+ throw new VerdifaxError(
868
+ "verdifax: verifyWebhook toleranceSeconds must be a number of seconds of 0 or more"
869
+ );
870
+ }
871
+ const now = options?.now ?? /* @__PURE__ */ new Date();
872
+ if (!(now instanceof Date) || Number.isNaN(now.getTime())) {
873
+ throw new VerdifaxError("verdifax: verifyWebhook now must be a valid Date");
874
+ }
875
+ let raw;
876
+ if (typeof body === "string") {
877
+ raw = Buffer.from(body, "utf8");
878
+ } else if (body instanceof Uint8Array) {
879
+ raw = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
880
+ } else {
881
+ throw new VerdifaxError(
882
+ "verdifax: verifyWebhook needs the body as the raw bytes received (a Buffer, Uint8Array or string), not parsed JSON"
883
+ );
884
+ }
885
+ const id = singleHeader(headers, "webhook-id");
886
+ const timestamp = singleHeader(headers, "webhook-timestamp");
887
+ const signatures = headerValues(headers, "webhook-signature").join(" ");
888
+ if (!signatures) {
889
+ throw new VerdifaxError("verdifax: webhook check failed: the webhook-signature header is missing");
890
+ }
891
+ if (!/^[0-9]{1,15}$/.test(timestamp)) {
892
+ throw new VerdifaxError(
893
+ "verdifax: webhook check failed: webhook-timestamp is not a whole number of seconds"
894
+ );
895
+ }
896
+ const age = Math.abs(now.getTime() / 1e3 - Number(timestamp));
897
+ if (age > tolerance) {
898
+ throw new VerdifaxError(
899
+ `verdifax: webhook check failed: webhook-timestamp is more than ${tolerance} seconds from now`
900
+ );
901
+ }
902
+ const expected = createHmac("sha256", key).update(`${id}.${timestamp}.`, "utf8").update(raw).digest();
903
+ let v1Seen = false;
904
+ let matched = false;
905
+ for (const entry of signatures.split(" ")) {
906
+ const comma = entry.indexOf(",");
907
+ if (comma < 0 || entry.slice(0, comma) !== WEBHOOK_SIGNATURE_VERSION) continue;
908
+ v1Seen = true;
909
+ const given2 = Buffer.from(entry.slice(comma + 1), "base64");
910
+ if (given2.length === HMAC_SHA256_BYTES && timingSafeEqual(given2, expected)) {
911
+ matched = true;
912
+ }
913
+ }
914
+ if (!v1Seen) {
915
+ throw new VerdifaxError(
916
+ "verdifax: webhook check failed: webhook-signature holds no v1 signature"
917
+ );
918
+ }
919
+ if (!matched) {
920
+ throw new VerdifaxError(
921
+ "verdifax: webhook check failed: no v1 signature in webhook-signature matches the id, timestamp and body under this secret"
922
+ );
923
+ }
924
+ let event;
925
+ try {
926
+ event = JSON.parse(raw.toString("utf8"));
927
+ } catch {
928
+ throw new VerdifaxError(
929
+ "verdifax: webhook check failed: the signature matches but the body is not JSON"
930
+ );
931
+ }
932
+ if (typeof event !== "object" || event === null || Array.isArray(event)) {
933
+ throw new VerdifaxError(
934
+ "verdifax: webhook check failed: the signature matches but the body is not a CloudEvent object"
935
+ );
936
+ }
937
+ if (event.id !== id) {
938
+ throw new VerdifaxError(
939
+ "verdifax: webhook check failed: the event id in the body does not match webhook-id"
940
+ );
941
+ }
942
+ return event;
943
+ }
944
+ function signalEventHash(kind, entryHash, fields) {
945
+ const { account, sequence, previousSignalHash } = fields ?? {};
946
+ for (const [name, value] of [
947
+ ["kind", kind],
948
+ ["entryHash", entryHash],
949
+ ["account", account],
950
+ ["previousSignalHash", previousSignalHash]
951
+ ]) {
952
+ if (typeof value !== "string" || value.includes("\0")) {
953
+ throw new VerdifaxError(
954
+ `verdifax: signalEventHash ${name} must be text without a zero byte`
955
+ );
956
+ }
957
+ }
958
+ if (!Number.isSafeInteger(sequence) || sequence < 0) {
959
+ throw new VerdifaxError(
960
+ "verdifax: signalEventHash sequence must be a whole number of 0 or more"
961
+ );
962
+ }
963
+ return createHash2("sha256").update(
964
+ [SIGNAL_FORM, kind, entryHash, account, String(sequence), previousSignalHash].join("\0"),
965
+ "utf8"
966
+ ).digest("hex");
967
+ }
968
+ function given(value, fallback) {
969
+ return value === void 0 ? fallback : value;
970
+ }
971
+ function requireAfter(after, method) {
972
+ if (!Number.isSafeInteger(after) || after < 0) {
973
+ throw new VerdifaxError(
974
+ `verdifax: ${method} after must be a whole number of 0 or more, the last sequence read`
975
+ );
976
+ }
977
+ }
978
+ function requireLimit(limit, method, max) {
979
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > max) {
980
+ throw new VerdifaxError(
981
+ `verdifax: ${method} limit must be a whole number from 1 to ${max}`
982
+ );
983
+ }
984
+ }
985
+ function requireWait(wait, method) {
986
+ if (!Number.isSafeInteger(wait) || wait < 0 || wait > MAX_SIGNAL_WAIT_SECONDS) {
987
+ throw new VerdifaxError(
988
+ `verdifax: ${method} wait must be a whole number of seconds from 0 to ${MAX_SIGNAL_WAIT_SECONDS}`
989
+ );
990
+ }
991
+ }
992
+ function webhookSecretBytes(secret) {
993
+ if (typeof secret !== "string" || !secret.startsWith(WEBHOOK_SECRET_PREFIX)) {
994
+ throw new VerdifaxError(
995
+ "verdifax: verifyWebhook needs the subscription's secret, which starts with whsec_"
996
+ );
997
+ }
998
+ const encoded = secret.slice(WEBHOOK_SECRET_PREFIX.length);
999
+ const key = Buffer.from(encoded, "base64");
1000
+ if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded) || key.length === 0 || key.toString("base64").replace(/=+$/, "") !== encoded.replace(/=+$/, "")) {
1001
+ throw new VerdifaxError(
1002
+ "verdifax: verifyWebhook needs the subscription's secret: whsec_ and then base64"
1003
+ );
1004
+ }
1005
+ return key;
1006
+ }
1007
+ function headerValues(headers, name) {
1008
+ if (typeof headers !== "object" || headers === null) {
1009
+ throw new VerdifaxError("verdifax: verifyWebhook needs the delivery's headers");
1010
+ }
1011
+ if (typeof headers.get === "function") {
1012
+ const value = headers.get(name);
1013
+ return value === null || value === void 0 ? [] : [String(value)];
1014
+ }
1015
+ const out = [];
1016
+ for (const [key, value] of Object.entries(headers)) {
1017
+ if (key.toLowerCase() !== name) continue;
1018
+ if (Array.isArray(value)) out.push(...value.map(String));
1019
+ else if (value !== void 0) out.push(String(value));
1020
+ }
1021
+ return out;
1022
+ }
1023
+ function singleHeader(headers, name) {
1024
+ const values = headerValues(headers, name);
1025
+ if (values.length === 0 || values[0] === "") {
1026
+ throw new VerdifaxError(`verdifax: webhook check failed: the ${name} header is missing`);
1027
+ }
1028
+ if (values.length > 1) {
1029
+ throw new VerdifaxError(`verdifax: webhook check failed: the ${name} header appears more than once`);
1030
+ }
1031
+ return values[0];
1032
+ }
1033
+
695
1034
  // src/version.ts
696
- var SDK_VERSION = "1.4.0";
1035
+ var SDK_VERSION = "1.5.0";
697
1036
 
698
1037
  // src/client.ts
699
1038
  import { KeyObject as KeyObject2 } from "crypto";
@@ -1389,6 +1728,103 @@ var VerdifaxClient = class {
1389
1728
  `${ACCOUNT_SIGNOFFS_PATH}/history`
1390
1729
  ) ?? [];
1391
1730
  }
1731
+ // ── Signal feed ──────────────────────────────────────────────
1732
+ //
1733
+ // Every control signal Verdifax recorded for the account, in order, each
1734
+ // with its sealed entry. The feed cannot show a signal Verdifax never
1735
+ // recorded. A feed key reads it on GET /signals and does nothing else;
1736
+ // the owner key reads it with asOwner and manages feed keys and webhooks.
1737
+ // The feed and webhook logic is in signals.ts.
1738
+ /** One page of the account's feed: the events after `after`, oldest
1739
+ * first, with `next_after` to read from next. With `wait` the server
1740
+ * holds the request open up to that many seconds for the next event, and
1741
+ * this call's timeout is at least the wait plus 30 seconds. With
1742
+ * `asOwner` it reads GET /account/signals with the owner key. Bad
1743
+ * options are refused before sending. Save the events and check them
1744
+ * with `verdifax-verify --signals`. */
1745
+ async getSignals(options = {}) {
1746
+ this.requireAPIKey();
1747
+ const read = signalsRead(options, "getSignals", this.timeoutMs);
1748
+ const out = await this.request(
1749
+ "GET",
1750
+ read.path,
1751
+ void 0,
1752
+ false,
1753
+ read.timeoutMs
1754
+ );
1755
+ return { ...out, events: out?.events ?? [] };
1756
+ }
1757
+ /** Follows the feed from `after`, yielding each event once, in order,
1758
+ * until you stop with `break` or `return`. Each read starts at the page
1759
+ * before's `next_after`, waits up to `wait` seconds (default 25) when
1760
+ * there is no event, and is retried like any GET. A read that still
1761
+ * fails throws {@link SignalFeedError}, whose `lastSequence` is the last
1762
+ * sequence yielded: follow again from there and nothing is lost. Bad
1763
+ * options are refused here, before anything is sent. */
1764
+ followSignals(options = {}) {
1765
+ this.requireAPIKey();
1766
+ const start = followStart(options);
1767
+ return followSignalFeed(
1768
+ (after) => this.getSignals({ after, wait: start.wait, asOwner: start.asOwner }),
1769
+ start
1770
+ );
1771
+ }
1772
+ /** Creates a feed key in the owner's account. A feed key reads the
1773
+ * account's signal feed, with {@link getSignals} and
1774
+ * {@link followSignals}, and nothing else. The secret is in the result
1775
+ * this once. */
1776
+ async createFeedKey(name, options = {}) {
1777
+ this.requireAPIKey();
1778
+ return this.request(
1779
+ "POST",
1780
+ FEED_KEYS_PATH,
1781
+ feedKeyBody(name, options?.expiresAt)
1782
+ );
1783
+ }
1784
+ /** Revokes one of the account's feed keys. An id that is not a feed key
1785
+ * of the account throws {@link NotFoundError}. */
1786
+ async revokeFeedKey(id) {
1787
+ this.requireAPIKey();
1788
+ requirePositiveId(id, "id");
1789
+ return this.request("DELETE", `${FEED_KEYS_PATH}/${id}`);
1790
+ }
1791
+ /** Subscribes an https address to the account's signals. `types` are the
1792
+ * kinds to deliver, each once; empty or left out takes every kind. The
1793
+ * secret, `whsec_` and base64, is in the result this once: check each
1794
+ * delivery with {@link verifyWebhook}. A delivery is a copy: the pull
1795
+ * feed and the public log are the record. The server checks the address
1796
+ * too, and refuses an eleventh subscription with HTTP 409. */
1797
+ async createWebhook(url, options = {}) {
1798
+ this.requireAPIKey();
1799
+ return this.request(
1800
+ "POST",
1801
+ WEBHOOKS_PATH,
1802
+ webhookBody(url, options)
1803
+ );
1804
+ }
1805
+ /** The account's webhook subscriptions, without their secrets. */
1806
+ async listWebhooks() {
1807
+ this.requireAPIKey();
1808
+ const out = await this.request("GET", WEBHOOKS_PATH);
1809
+ return { ...out, webhooks: out?.webhooks ?? [] };
1810
+ }
1811
+ /** Ends a webhook subscription. Its events stay in the pull feed. */
1812
+ async deleteWebhook(id) {
1813
+ this.requireAPIKey();
1814
+ requirePositiveId(id, "id");
1815
+ return this.request("DELETE", `${WEBHOOKS_PATH}/${id}`);
1816
+ }
1817
+ /** A webhook's newest deliveries first, up to `limit` (1 to 500; the
1818
+ * server's default is 50). */
1819
+ async listWebhookDeliveries(id, options = {}) {
1820
+ this.requireAPIKey();
1821
+ requirePositiveId(id, "id");
1822
+ const out = await this.request(
1823
+ "GET",
1824
+ deliveriesPath(id, options)
1825
+ );
1826
+ return { ...out, deliveries: out?.deliveries ?? [] };
1827
+ }
1392
1828
  // ── Runs ──────────────────────────────────────────────────────
1393
1829
  async getRun(runId) {
1394
1830
  this.requireAPIKey();
@@ -1534,6 +1970,14 @@ var VerdifaxClient = class {
1534
1970
  async getCESAuditRecord(runId) {
1535
1971
  return this.getRunArtifact(runId, "ces-audit-record");
1536
1972
  }
1973
+ /** A failed run's sealed failure receipt, in the form `verdifax-verify`
1974
+ * reads. A run that failed before failure receipts were sealed has none
1975
+ * and throws {@link NotFoundError}. */
1976
+ async getFailureReceipt(runId) {
1977
+ this.requireAPIKey();
1978
+ requirePositiveId(runId, "runId");
1979
+ return this.getRunArtifact(runId, "failure-receipt");
1980
+ }
1537
1981
  /** The audit report for a run, as JSON. */
1538
1982
  async getRunReport(runId) {
1539
1983
  const out = await this.getRunArtifact(runId, "report");
@@ -1731,7 +2175,7 @@ var VerdifaxClient = class {
1731
2175
  });
1732
2176
  }
1733
2177
  // ── HTTP plumbing, retry-aware ──────────────────────────────
1734
- async request(method, path, body, rawText = false) {
2178
+ async request(method, path, body, rawText = false, timeoutMs = this.timeoutMs) {
1735
2179
  const url = this.baseURL + path;
1736
2180
  const headers = {
1737
2181
  "User-Agent": this.userAgent
@@ -1748,7 +2192,7 @@ var VerdifaxClient = class {
1748
2192
  await new Promise((r) => setTimeout(r, sleep));
1749
2193
  }
1750
2194
  const controller = new AbortController();
1751
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
2195
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1752
2196
  let resp;
1753
2197
  try {
1754
2198
  resp = await fetch(url, {
@@ -1845,13 +2289,6 @@ var VerdifaxClient = class {
1845
2289
  function signOffsBase(options, method) {
1846
2290
  return asOwnerOf(options, method) ? ACCOUNT_SIGNOFFS_PATH : SIGNOFFS_PATH;
1847
2291
  }
1848
- function asOwnerOf(options, method) {
1849
- const asOwner = options?.asOwner;
1850
- if (asOwner !== void 0 && typeof asOwner !== "boolean") {
1851
- throw new VerdifaxError(`verdifax: ${method} asOwner must be true or false`);
1852
- }
1853
- return asOwner === true;
1854
- }
1855
2292
  function signOffTargetOf(target) {
1856
2293
  if (typeof target !== "object" || target === null || Array.isArray(target)) {
1857
2294
  throw new VerdifaxError("verdifax: signOff needs a target object");
@@ -1888,11 +2325,6 @@ function recordMatchesSent(record, target, decision, previous, signed) {
1888
2325
  const got = record?.target;
1889
2326
  return typeof record === "object" && record !== null && record.signature === signed.signature && record.approver_key === signed.approver_key && record.decision === decision && record.previous_signoff_hash === previous && typeof got === "object" && got !== null && got.kind === target.kind && got.run_id === target.run_id && got.digest === target.digest && (got.job_id ?? "") === (target.job_id ?? "");
1890
2327
  }
1891
- function requirePositiveId(id, name) {
1892
- if (!Number.isSafeInteger(id) || id <= 0) {
1893
- throw new VerdifaxError(`verdifax: ${name} must be a whole number > 0`);
1894
- }
1895
- }
1896
2328
  function withSignedText(result, sent) {
1897
2329
  return "signed_request" in sent ? { ...result, signed_request: sent.signed_request } : result;
1898
2330
  }
@@ -2018,13 +2450,13 @@ var RUN_STATUSES = [
2018
2450
  ];
2019
2451
 
2020
2452
  // src/section0.ts
2021
- import { createHash as createHash2 } from "crypto";
2453
+ import { createHash as createHash3 } from "crypto";
2022
2454
  var DSE_FORMULA_VERSION = "dse.tcu.dispatch.v1";
2023
2455
  var DCAE_PREIMAGE_VERSION = "verdifax.dcae.v1";
2024
2456
  var CRES_PREIMAGE_VERSION = "verdifax.cres.v1";
2025
2457
  var CRES_ENGINE_VERSION = "verdifax-cres/1.0.0";
2026
2458
  function sha256Hex2(preimage) {
2027
- return createHash2("sha256").update(Buffer.from(preimage, "utf8")).digest("hex");
2459
+ return createHash3("sha256").update(Buffer.from(preimage, "utf8")).digest("hex");
2028
2460
  }
2029
2461
  function requireHex64(value, name) {
2030
2462
  try {
@@ -2090,6 +2522,7 @@ export {
2090
2522
  MAX_BATCH_ROWS,
2091
2523
  NotFoundError,
2092
2524
  RUN_STATUSES,
2525
+ SignalFeedError,
2093
2526
  VerdifaxClient,
2094
2527
  VerdifaxError,
2095
2528
  VerdifaxJob,
@@ -2111,7 +2544,9 @@ export {
2111
2544
  signOffPreimage,
2112
2545
  signRequest,
2113
2546
  signRequestV2,
2547
+ signalEventHash,
2114
2548
  validateHex64,
2115
2549
  validateJobName,
2116
- validateRouteId
2550
+ validateRouteId,
2551
+ verifyWebhook
2117
2552
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verdifax",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Official Node.js / TypeScript SDK for the Verdifax orchestrator REST API.",
5
5
  "license": "MIT",
6
6
  "author": "Verdifax <team@verdifax.com>",