Skip to content

Releases: jamescarr/ankusa

sdk-java-v0.1.0

Choose a tag to compare

@github-actions github-actions released this 22 Oct 04:23
692abae

Added

  • AdminClient.listAudit(ListAuditParams): GET /v1/audit, the audit trail
    (newest first, paged by cursor/limit, filtered by since, until,
    actor, action, tenant).

  • revision on Route: counts the definition's changes, from 1 at creation;
    re-creating a deleted route starts again at 1; null from a node older than
    the field.

  • scope on AdminRejectedError and RoutesRejectedError: the scope a 403 forbidden says the credential lacks (control planes require a credential
    since Ankusa 0.6).

  • verification on parsed headers, decoded messages and received deliveries:
    how Ankusa verified the hook at ingest ({status, flagged}, null from a
    node older than the field).

  • ParsedClaimRef.parse and ClaimCheckClient.redeem accept signed,
    expiring v2 refs
    (urn:ankusa:claim:v2:<tenant>:<claim_id>:<exp_unix>:<signature>): the
    parsed path ends in ?exp=&sig= and redeem requests it as is. The
    gateway's 400 bad_signature and 410 ref_expired are a
    ClaimRejectedError. The SDK never checks the signature or the expiry.

  • webhook/Receiver.receive(headers, body, Receiver.Options), the
    receiver-side decision on one HTTP-sink delivery. The sink signs every
    delivery unless configured allow_unsigned: true, so a receiver is closed
    by default: Options.signed(secrets) makes the delivery verify (as
    Signature.verify); Options.unsigned() is the explicit opt-out, and any
    other options with no secrets throw InvalidSignatureError
    (invalid_secret). The signature is judged before the hook id. Returns a
    Receiver.Received (headers, verified).

  • ClaimCheckClient: redeem(ref, sha256) fetches a claim's bytes from the
    claim-check gateway and verifies them against the expected digest before
    returning them, and health() is a liveness probe. ParsedClaimRef.parse
    (class claimcheck/ParsedClaimRef.java) exposes the ref parsing on its own.

  • RoutesClient: the route-management client — health, listRoutes,
    createRoute, getRoute, replaceRoute, updateRoute, deleteRoute,
    getIpRules, putIpRules, and testRoute. Every call is one request, no
    retries, and a redirect is never followed: a 3xx is a RoutesUnavailableError.

  • AdminClient: the DLQ and quarantine client — health, metrics, config,
    asyncApi (the GET /asyncapi.json AsyncAPI 3.0 document),
    listDeadLetters, listQuarantined, purgeQuarantined
    (DELETE /v1/quarantine, filtered by PurgeQuarantinedParams, answering a
    QuarantinePurge of deleted and bytes), and the replay jobs
    createReplay, getReplay, listReplays, and updateReplay (kinds dlq,
    archive, quarantine). A QuarantineEntry carries a nullable tenantId
    and size, both null for an entry held before the listener recorded them.

  • Message.decode, the queue-message decoder: it validates the v1 message
    (including the shipped idempotency_key, the idempotencyKey record
    component), decodes and verifies the body, and raises InvalidMessageError
    (code(), field()) on a bad message. Message.idempotencyKey(includeReplay)
    and HookHeaders.idempotencyKey(includeReplay) return the key a worker dedupes
    on: the key Ankusa shipped, or for a message or delivery that predates it
    tenant:source_id:dedupe_key (tenant default when there is none) when a
    dedupe key is set, else id.

  • HookHeaders.parse for receivers: the x-ankusa-id, x-ankusa-source,
    x-ankusa-tenant, content-type, x-ankusa-dedupe-key,
    x-ankusa-replay-id, and x-ankusa-idempotency-key headers of a delivery,
    and MissingHookIdError when x-ankusa-id is absent.

  • Signature.verify for receivers of a signed HTTP sink: the Standard Webhooks
    webhook-id/webhook-timestamp/webhook-signature check, compared with
    MessageDigest.isEqual, raising InvalidSignatureError (code(),
    field(), never retryable).

  • A gateway 408 or 429 on redeem is a ClaimCheckUnavailableError
    (retryable), not a ClaimRejectedError.

  • SourcesClient: the tenant-scoped source API — serverVersion, listSources,
    getSource, createSource, updateSource, and deleteSource. A client built
    with an expectedVersion fetches /health once, caches the reported version,
    and raises VersionMismatchError on every later call that disagrees.

  • The error hierarchy: one abstract AnkusaException per client family
    (ClaimCheckError, RoutesError, AdminError, SourcesError), with
    retryable() true only on the four *UnavailableError classes. Caller misuse
    is an IllegalArgumentException instead, raised before any request: a base URL
    that is not an absolute http(s) URL, a header the JDK client refuses to set, or
    a body that cannot be encoded as JSON.

  • ClientOptions, carrying the request headers, the timeout (10 s by default,
    bounding connect through the last body byte), and the Transport. The default
    transport is the JDK HttpClient; inject your own to test, or to drive a
    different stack.

  • The language-neutral conformance runner (JUnit Jupiter, ConformanceTest), passing
    every vector in conformance/cases.

sdk-rust-v0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 06:03
29a89d4

Added

  • verify_signature(headers, body, secrets, VerifyOptions) verifies the
    Standard Webhooks signature an HTTP sink with a secret adds, comparing in
    constant time (hmac); failures are InvalidSignatureError (code,
    field, never retryable). New dependency: hmac.
  • decode_message and Message, for consumers of Ankusa's queue: the inline
    (body_base64) and claim (claim) body forms, sha256/size/tenant
    validation, forwarded headers, the shipped idempotency_key field, and
    Message::idempotency_key(include_replay), which returns it (computing
    tenant:source_id:dedupe_key, tenant default when none, else id, only for
    a message that predates the field).
  • HookHeaders gains dedupe_key, replay_id, idempotency_key (the shipped
    x-ankusa-idempotency-key) and idempotency_key(include_replay).
  • AdminClient gains create_replay, get_replay, list_replays, and
    update_replay, with the Replay, ReplayList, ReplaySpec, and
    ReplayPatch models. Kinds are dlq, archive and quarantine;
    ReplaySpec's id/since/until apply to quarantine too.

Changed

  • ClaimCheckClient::redeem returns ClaimCheckError::Unavailable
    (retryable) for a gateway 408 or 429 instead of Rejected.

Removed

  • AdminClient::replay_dead_letters and its ReplayFilter/Replayed models,
    replaced by the replay-job API above.

sdk-ruby-v0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 06:03
29a89d4

Added

  • Ankusa.verify_signature(headers, body, secrets, tolerance_seconds: 300, now: nil)
    verifies the Standard Webhooks signature an HTTP sink with a secret adds,
    comparing with OpenSSL.fixed_length_secure_compare; failures raise
    Ankusa::InvalidSignatureError (code, field, never retryable).
  • Ankusa::Message and Ankusa.decode_message(data), plus
    Ankusa::InvalidMessageError (with code, field and retryable? false):
    decode and validate the v1 queue message every sink delivers — the inline
    (body_base64) and claim-check (claim) body forms, sha256,
    dedupe_key, replay_id, and the forwarded headers, ignoring unknown
    keys.
  • Message#idempotency_key / HookHeaders#idempotency_key, the key to store
    in a processed-ids table: the key Ankusa shipped (the message's
    idempotency_key field, or the x-ankusa-idempotency-key header), and for a
    message or delivery that predates it tenant:source_id:dedupe_key (tenant
    default when there is none) when a dedupe_key is set, else id, with an
    optional #replay:<replay_id> suffix (include_replay: true). The decoded
    field stays in to_h[:idempotency_key].
  • HookHeaders#dedupe_key / #replay_id / #idempotency_key's field, parsed
    from x-ankusa-dedupe-key / x-ankusa-replay-id / x-ankusa-idempotency-key.
  • AdminClient#create_replay, #get_replay, #list_replays and
    #update_replay for the POST/GET/PATCH /v1/replays replay-job API (kinds
    dlq, archive, quarantine).

Changed

  • ClaimCheckClient#redeem raises ClaimCheckUnavailableError (retryable)
    for a gateway 408 or 429 instead of ClaimRejectedError.

Removed

  • AdminClient#replay_dead_letters, replaced by the replay-job API above.
    POST /v1/dlq/replay no longer exists.

sdk-python-v0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 06:03
29a89d4

Added

  • verify_signature(headers, body, secrets, *, tolerance_seconds=300, now=None)
    verifies the Standard Webhooks signature an HTTP sink with a secret adds,
    comparing in constant time; failures raise InvalidSignatureError (code,
    field, retryable=False).
  • message.Message, message.decode_message(), and message.InvalidMessageError
    (with code, field and retryable=False): decode and validate the v1
    queue message every sink delivers — the inline (body_base64) and
    claim-check (claim) body forms, sha256, dedupe_key, replay_id, and
    the forwarded headers, ignoring unknown keys.
  • message.idempotency_key(), the key to store in a processed-ids table: the
    key Ankusa shipped (the message's idempotency_key, or the
    x-ankusa-idempotency-key header), and for a message or delivery that
    predates the field tenant:source_id:dedupe_key (tenant default when there
    is none) when dedupe_key is set, else id, with an optional
    #replay:<replay_id> suffix (include_replay=True).
  • Message.idempotency_key, decoded like replay_id, and
    HookHeaders.dedupe_key / HookHeaders.replay_id /
    HookHeaders.idempotency_key, parsed from x-ankusa-dedupe-key /
    x-ankusa-replay-id / x-ankusa-idempotency-key.
  • AdminClient.create_replay(), get_replay(), list_replays() and
    update_replay() for the POST/GET/PATCH /v1/replays replay-job API (kinds
    dlq, archive, quarantine).

Changed

  • ClaimCheckClient.redeem() raises ClaimCheckUnavailableError (retryable)
    for a gateway 408 or 429 instead of ClaimRejectedError.

Removed

  • AdminClient.replay_dead_letters(), replaced by the replay-job API above.
    POST /v1/dlq/replay no longer exists.

sdk-php-v0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 06:04
29a89d4

Added

  • Webhook\Signature::verify($headers, $body, $secrets, $toleranceSeconds = 300, $now = null)
    verifies the Standard Webhooks signature an HTTP sink with a secret adds,
    comparing with hash_equals; failures throw
    Webhook\InvalidSignatureError ($errorCode, $field, never retryable).
  • Message::$idempotencyKey and HookHeaders::$idempotencyKey, the key Ankusa
    ships (the message's idempotency_key, the x-ankusa-idempotency-key
    header). idempotencyKey() on both returns it, and for a message or
    delivery that predates it computes tenant:source_id:dedupe_key (tenant
    default when there is none) when a dedupe key is set, else the id, plus
    #replay:<replay_id> with includeReplay: true.
  • AdminClient::createReplay(array $spec), getReplay(string $id),
    listReplays() and updateReplay(string $id, array $patch) for replay jobs
    (kind dlq, archive or quarantine).
  • Ankusa\Message\Message::decode(string $data) and InvalidMessageError
    ($errorCode, $field, isRetryable() always false) for the v1 queue
    message (inline or claim body, sha256, dedupeKey, replayId,
    headers).
  • HookHeaders::$dedupeKey and $replayId, from x-ankusa-dedupe-key and
    x-ankusa-replay-id.

Changed

  • ClaimCheckClient::redeem() throws ClaimCheckUnavailableError
    (retryable) for a gateway 408 or 429 instead of ClaimRejectedError.

Removed

  • AdminClient::replayDeadLetters(): POST /v1/dlq/replay no longer exists.

sdk-go-v0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 06:04
29a89d4

Added

  • VerifySignature(header, body, secrets, VerifyOptions) verifies the
    Standard Webhooks signature an HTTP sink with a secret adds, comparing
    with hmac.Equal; failures are *InvalidSignatureError (Code, Field,
    not retryable).
  • DecodeMessage and the Message type: decode a v1 queue message with the
    body-integrity checks (size_mismatch, integrity, tenant_mismatch) and
    the dedupe_key/replay_id/idempotency_key/headers fields, raising
    InvalidMessageError (Code, Field, Retryable() == false) on any
    malformed input. The wire's idempotency_key is Message.ShippedIdempotencyKey
    (the field name cannot be IdempotencyKey, which is the helper).
  • Message.IdempotencyKey(includeReplay) and
    HookHeaders.IdempotencyKey(includeReplay): the key a consumer dedupes a
    delivery on. It is the key Ankusa shipped (idempotency_key /
    x-ankusa-idempotency-key); for a message or delivery that predates it, the
    helper computes tenant:source_id:dedupe_key (tenant default when there is
    none) when a dedupe key is set, else the id.
  • HookHeaders.DedupeKey, HookHeaders.ReplayID, and
    HookHeaders.ShippedIdempotencyKey, read from x-ankusa-dedupe-key,
    x-ankusa-replay-id, and x-ankusa-idempotency-key by ParseHeaders.
  • AdminClient.CreateReplay, GetReplay, ListReplays, and UpdateReplay,
    with the Replay, ReplayList, ReplaySpec, and ReplayPatch types. Kinds
    are dlq, archive and quarantine; ReplaySpec.ID, Since and Until
    cover quarantine too.

Changed

  • Redeem returns *ClaimCheckUnavailableError (retryable) for a gateway
    408 or 429 instead of *ClaimRejectedError.

Removed

  • AdminClient.ReplayDeadLetters, ReplayFilter, and Replayed; replay is
    now the replay-job API above.

sdk-elixir-v0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 06:02
29a89d4

Added

  • Ankusa.SDK.Signature.verify/4 verifies the Standard Webhooks signature an
    HTTP sink with a secret adds, comparing with :crypto.hash_equals/2;
    failures are Ankusa.SDK.InvalidSignatureError (code, field, never
    retryable). Ankusa.SDK.Receiver takes :secret (one or a list) and
    :tolerance_seconds, and answers 401
    {"error":"invalid_signature","code":...} to a delivery that doesn't
    verify.
  • Ankusa.SDK.Message.decode/1 now carries dedupe_key, replay_id,
    idempotency_key and headers, verifies an inline body's length and sha256,
    checks a claim's tenant against tenant_id, and rejects a claim that isn't a
    well-formed ref. Ankusa.SDK.InvalidMessageError gains code and field.
  • Ankusa.SDK.Idempotency.key/2: the idempotency key for a message, parsed
    webhook headers, or a hook. It returns the key Ankusa shipped (the message's
    idempotency_key, the x-ankusa-idempotency-key header) and, for a message
    or delivery that predates it, computes tenant:source_id:dedupe_key (tenant
    default when there is none) when a provider event key is present, else the
    hook id; include_replay: true appends the replay id.
  • Ankusa.SDK.Hook carries dedupe_key, replay_id, idempotency_key and
    headers; Ankusa.SDK.Webhook.Headers parses x-ankusa-dedupe-key,
    x-ankusa-replay-id and x-ankusa-idempotency-key and keeps every request
    header.
  • Ankusa.SDK.Admin.create_replay/2, get_replay/2, list_replays/1 and
    update_replay/3 for the replay-job API (POST/GET/PATCH /v1/replays;
    kinds dlq, archive, quarantine).

Changed

  • Ankusa.SDK.ClaimCheck.redeem/3 returns ClaimCheckUnavailableError
    (retryable) for a gateway 408 or 429 instead of ClaimRejectedError.

Removed

  • The admin client's replay_dead_letters function; POST /v1/dlq/replay is
    gone in favour of replay jobs.

async_api_spex-v0.2.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 05:25
29a89d4

Added

  • use AsyncApiSpex.Schema, fields: [...] derives a schema from an existing
    defstruct; every struct key becomes a property and fields: refines it
    with a type, required, and a description. title: and description:
    describe the schema.
  • use AsyncApiSpex.Channel declares a channel, the server it lives on, and
    its operation on the module that publishes to it or consumes from it.
  • use AsyncApiSpex.Spec implements spec/0 by assembling a document from
    channel modules, listed with channels: or discovered with otp_app:.
  • AsyncApiSpex.Plug.Viewer serves an HTML page that renders a document in
    the browser with the AsyncAPI React component (pinned to 3.2.1 on unpkg;
    :bundle_url and :stylesheet_url point it at a self-hosted copy). Mount it
    next to AsyncApiSpex.Plug.RenderSpec with path: set to that mount.
  • CI scaffolds a fresh mix new app that depends on nothing but this library
    and plug, decorates a producer and a consumer, and serves, validates, and
    writes the document, so the library is proven outside the repository it
    lives in.

Changed

  • use AsyncApiSpex.Schema without :schema or :fields now raises
    "requires exactly one of :schema or :fields".
  • AsyncApiSpex.validate/1 is stricter. A document with a component
    schema that holds a module that does not use AsyncApiSpex.Schema now fails
    validate/1 and mix async_api_spex.gen.

Fixed

  • A schema that references itself no longer makes the resolver loop forever.

ankusa_server-v0.6.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 06:02
29a89d4

Added

  • Every delivery says how the hook was verified: Sink.Http sends
    x-ankusa-verification (ok, failed or skipped) and, for a hook a
    source accepted despite a failed check (on_verify_failure: accept_flag),
    x-ankusa-verification-flagged: true; a queue sink's message carries
    verification: {status, flagged}. The admin API's source routes show
    trust_url_tenant.
  • idle_timeout_ms on rabbitmq, kafka, nats and redis sinks: close a
    broker connection after that long with no delivery (default 10 minutes, 0
    never).
  • Source key trust_url_tenant (default false). With
    routing: tenant_path, a source with tenant: default and
    verify: {type: none} files hooks under whatever tenant the URL names:
    boot and check-config now refuse it (exit 78, naming the source) unless it
    sets trust_url_tenant: true, and an admin API write of one is
    400 invalid_source. One source_store: persistent/redis already held
    keeps serving, with a warning at boot. config-examples/multi-tenant.yml's
    demo source sets it.
  • source_store.type: redis (url, namespace, tick_ms; env
    ANKUSA_SOURCE_STORE_URL; a url with no type means redis): sources
    created through the admin API are shared by every node through
    ankusa_redis's Ankusa.SourceStore.Redis. The three keys are rejected on
    static/persistent.
  • YAML keys for everything core added: admin.gauge_interval_ms,
    batcher.max_queue_bytes, dispatch.sink_concurrency,
    dispatch.breaker_failures / breaker_open_ms / breaker_max_open_ms,
    storage.key_prefix (env ANKUSA_STORAGE_KEY_PREFIX), claim_check.store
    (a dedicated claim store, same shape as storage plus root),
    storage.s3.session_token, http sink secret and max_response_bytes,
    rabbitmq sink max_inflight, kafka sink max_record_bytes.
  • A backup section (enabled, interval_ms, keep, store — the
    claim_check.store shape; env ANKUSA_BACKUP_ENABLED) for core's store
    backup: with it on and segments (or backup.store) in a bucket, a container
    started on an empty volume restores the latest backup before it serves, and
    exits with store_restore_failed when the bucket can't be read.
    check-config runs Ankusa.Store.Backup.validate_config!/1.
  • docker-entrypoint remote (an IEx shell in the running node) and
    docker-entrypoint rpc EXPR. Distribution stays on, bound to loopback with
    epmd (rel/vm.args.eex, rel/env.sh.eex); RELEASE_DISTRIBUTION=none
    turns it off.
  • GCP metadata tokens (GCS and Pub/Sub) come from AnkusaServer.GcpToken,
    one cached token fetched single-flight and refreshed before expiry.
  • type: sqs sinks (Ankusa.Sink.SQS): queue_url, region, endpoint,
    message_group_id, inline_max_bytes, max_message_bytes, timeout_ms,
    and optional static access_key_id / secret_access_key /
    session_token (else the AWS_* env, IRSA, or the instance role).
    reference.yml documents it.
  • type: google_pubsub sinks (Ankusa.Sink.GooglePubSub): project,
    topic, endpoint, ordering_key, inline_max_bytes,
    max_message_bytes, timeout_ms, and auth (metadata | token |
    none, default metadata; token is required when auth: token), the
    same choice storage.gcs.auth offers. reference.yml documents it.
  • Every sink now sends the tenant-scoped idempotency key: the message gains
    idempotency_key (still v: 1), Sink.Http sends x-ankusa-idempotency-key,
    and RabbitMQ, Kafka and NATS send ankusa_idempotency_key. It is
    tenant:source_id:dedupe_key when the source has a dedupe key, else the hook
    id. Consumers should dedupe on it; the SDK helpers read it.
  • dispatch.attempt_timeout_ms (default 30000): a delivery attempt that has
    not returned after it is killed and counts as a failed attempt
    ({:attempt_timeout, ms} in the retry error or the DLQ reason), so a hung
    sink frees its slot. A value that is not a positive integer fails
    check-config.
  • A quarantine: section (burst, rate, max_bytes): one quarantine
    bucket per source, and a cap on the pen's bytes. A full pen answers
    503 quarantine_full; a source over its bucket answers
    429 quarantine_rate_limited.
  • verify.secret takes a list of strings (at most 8), newest first, for a
    secret rotation with no cut-over: secret: ["${NEW}", "${OLD}"].
  • Release held hooks with POST /v1/replays {"kind":"quarantine"}, and purge
    them with DELETE /v1/quarantine. See docs/delivery.md#quarantine.
  • ankusa_ingest_refused_total{instance,reason} on GET /metrics: requests
    the edge answered without ingesting them (unknown_source,
    payload_too_large, body_read_failed, invalid_header).
  • admin.ip, claim_check.ip and routes.admin.ip: the address each listener
    binds (a strict IPv4 or IPv6 literal, default 127.0.0.1), and the
    ANKUSA_ADMIN_IP and ANKUSA_CLAIM_CHECK_IP overrides. check-config
    rejects anything that is not an IP address, naming the key.
  • Replay jobs: POST /v1/replays (kind: dlq | archive | quarantine),
    GET /v1/replays and GET|PATCH /v1/replays/{id}.
  • Source key dedupe (a preset, or {preset|header|json, ttl_seconds}): a
    repeat of an event inside the TTL answers 201 with the original id and
    "duplicate": true. Source key forward_headers.
  • wal.publish_timeout_ms (default 8000): the deadline every sink must
    confirm under with wal.type: none.
  • Metrics ankusa_replay_moved_total, ankusa_replay_throttled_total and
    ankusa_quarantine_full_total.

Changed

  • The image's HEALTHCHECK asks GET /ready (docker-entrypoint healthcheck, ingest port first, then admin), so a node whose store refuses
    writes reports unhealthy.
  • An unresolved ${…} in the file is a load error naming the key (a
    substituted value that itself contains ${ is fine); a static source whose
    verifier secret is missing or undecodable fails check-config and boot.
  • AnkusaServer.GcpToken answers :error to every caller for a second after
    a failed metadata fetch, so a queue of callers behind a dead metadata server
    costs one request.
  • :os_mon runs (disk gauges); only disksup is configured. The image adds
    coreutils: disksup runs df -lk -x squashfs, which busybox's df
    rejects.
  • GET /asyncapi.json: SinkMessageV1 now requires sha256, dedupe_key,
    replay_id, idempotency_key and headers, and SinkMessageHeadersV1
    requires ankusa_idempotency_key. A consumer that validates messages
    against the previous document rejects the new fields only if it also
    forbids additional properties.
  • Breaking: an empty verify.secret is a load error. An empty string, or
    an empty element of a secret list (an unset ${OLD:-} included), fails
    check-config and the boot, naming the key (sources.a.verify.secret[1]: must not be empty). It used to load and verify with the empty HMAC key,
    which accepts anything signed with it.
  • Breaking: a source over its quarantine bucket answers 429 quarantine_rate_limited with Retry-After, not 401 verification_failed;
    a full pen answers 503 quarantine_full with Retry-After: 60.
  • The default retry policy retries for about 6 hours: dispatch.retry
    defaults to max_ms: 300000 and max_attempts: 84 (they were 30000 and
    12, about 83 s). A deployment that relied on the old default to
    dead-letter within minutes should set max_attempts explicitly. Errors are
    not classified yet, so a permanent failure (an HTTP 400, say) also takes
    every attempt before it reaches the DLQ.
  • Breaking: the image's admin API (4002) and claim gateway (4001) listen on
    127.0.0.1 inside the container.
    -p 127.0.0.1:4002:4002 alone no longer
    reaches the admin API, and the claim gateway is unreachable from other
    containers. Set ANKUSA_ADMIN_IP=0.0.0.0 and ANKUSA_CLAIM_CHECK_IP=0.0.0.0
    (or admin.ip and claim_check.ip) to listen on the container's interface;
    the Docker HEALTHCHECK keeps working. The compose files, the smoke script
    and the examples set it where they publish or share a port.
  • Breaking: a request with a header name or value outside visible ASCII (and
    space and tab in a value) is 400 invalid_header.
    It used to be acked and
    then dead-lettered when a sink could not carry it; see the core changelog.
  • Breaking for anything that reads GET /v1/config: adapter options are
    shown only under known non-secret keys, and every other value (a
    sas_token, a private_key, an NATS jwt) is "[REDACTED]"; URL query
    values are redacted too. print-config uses the same view.
  • Breaking for dashboards: refused requests are no longer on
    ankusa_ingest_requests_total.
    Requests for sources that do not exist no
    longer count under ankusa_ingest_requests_total{outcome="unknown_source"};
    a panel that sums that counter for all traffic must add
    ankusa_ingest_refused_total.
  • Breaking: POST /v1/dlq/replay is removed. Use
    POST /v1/replays {"kind":"dlq"}.
  • Sink.Http forwards the provider's request headers by default (per the
    source's forward_headers) and adds x-ankusa-dedupe-key and
    x-ankusa-replay-id when present.
  • wal.type: none publishes to all of a source's sinks concurrently under
    wal.publish_timeout_ms; any failure or timeout is a 503.
  • Breaking: every RabbitMQ publish is mandatory. A publish to an
    exchange with no bound queue now retries and then dead-letters instead of
    silently answering 201. RabbitMQ also sets AMQP message_id to the hook
    id.
  • NATS sets Nats-Msg-Id on every publish (the hook id, or
    id:replay:<replay_id> on a replay).

Fixed

  • GET /metrics no longer grows with unauthenticated traffic: requests for
    sources that do not exist are one ankusa_ingest_refused_total series, not
    a source_id series per URL.
  • A Content-Length above http.max_body_bytes, or a source th...
Read more

ankusa_redis-v0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 05:56
29a89d4

Added

  • Ankusa.SourceStore.Redis: API-managed, tenant-scoped sources kept in Redis
    and shared by every node configured with the same namespace (a hash of
    sources and a version counter; Lua-scripted writes; pub/sub invalidation with
    a tick_ms safety net). Reads go to an ETS mirror that survives a restart of
    the store's state process. Seeds stay config-only. A first boot against an
    unreachable Redis fails; a write during an outage answers
    {:error, :store_unavailable} (the admin API's 503 store_unavailable).
  • Ankusa.Sink.Redis: :idle_timeout_ms (default 600_000, 0 never, at
    least :publish_timeout_ms plus 1 s): a connection with no delivery for that
    long is closed (Ankusa.Sink.Reaper, requires core's) and the next delivery
    opens a new one.

Changed

  • Requires ankusa ~> 0.6 (the ETS route snapshot API, and the source table
    SourceStore.Redis shares with SourceStore.Persistent); publish core first.
  • The Redis route store keeps serving its last known table when the
    namespace's version key disappears (a flushed or evicted Redis) instead of
    publishing an empty table that rejects every webhook; it logs a warning
    once and reloads when the namespace is written again. It publishes through
    core's ETS route snapshot and drops retired generations on request.
  • The Redis sink's connection dials on its own (sync_connect: false), so a
    dead Redis never blocks the supervisor that starts it.
  • Ankusa.Routes.Store.Redis and Ankusa.SourceStore.Redis start with
    [instance: instance] (core's new store contract) and read their options
    from Ankusa.config(instance): no child spec of theirs carries the config,
    the seeds or the decoder. Their Redix connections get the URL's password as
    Redix's {m, f, a} form, so neither a child spec nor Redix's state holds it.
  • SourceStore.Redis refuses a write of a shared source with no verifier
    under a resolver that takes the tenant from the request unless it sets
    trust_url_tenant: true ({:error, :invalid, message}), and names one
    already in Redis in a warning at first boot.
  • A route store restarted after the instance has published a table resumes from
    that table without waiting on Redis (its connections dial in the background),
    so a store that crashes during a Redis outage does not hold the edge down. It
    loads once the subscription is confirmed and on every tick. A first boot
    still fails loudly.
  • Ankusa.Sink.Redis registers its connection under a digest of the URL
    instead of the URL, and starts Redix with the URL's password as an
    {m, f, a} over a closure: the password is in no registry key, child spec
    or Redix state.