Commit Graph

2 Commits

Author SHA1 Message Date
CarterPerez-dev 0757d9f196 feat(canary): event + notify domain — contracts, senders, services
Lays the domain layer for Phase 10 notification dispatch.

- event/contract.go: NotifyInfo (token-shape DTO that breaks the would-be
  event→notify→token cycle), Notifier interface, TokenIncrementer interface,
  Store interface (Insert+UpdateNotifyStatus+PruneToLimit).
- token.Token.NotifyInfo() helper bridges *token.Token → event.NotifyInfo
  for the main.go adapter.
- notify/types.go: Sender + StatusWriter interfaces.
- notify.Service: per-channel sender registry, async fire-and-forget
  Notify with bounded sendTimeout, WaitGroup for graceful drain, status
  writeback (sent / failed) on the configured StatusWriter.
- telegram.Sender: POST /bot{TOKEN}/sendMessage with MarkdownV2-escaped
  body, cenkalti/backoff/v5 retry (3 tries / 30s window), permanent on
  4xx, 10s overall + 5s connect timeouts. Note: spec body said
  parse_mode=Markdown but escape table is the V2 set; using MarkdownV2
  keeps the escape rules consistent.
- webhook.Sender: POST user URL with versioned JSON envelope (§10.4),
  optional HMAC-SHA256 signing via X-Canary-Signature, URL validation
  at the call site (rejects non-http(s), missing host, userinfo). Same
  retry/timeout policy as telegram.
- event.Service: Record(ctx, info, evt) inserts → IncrementTriggerCount
  → Redis SetNX dedup gate (15m TTL, fail-open on Redis error). First
  trigger calls notifier; duplicates INCR + UpdateNotifyStatus(deduped).
- event.Service.RunRetentionLoop(ctx, interval, limit) tickered prune.

Adds cenkalti/backoff/v5 + miniredis/v2 to go.mod for backoff API and
unit-test Redis fakes.
2026-05-14 00:30:42 -04:00
CarterPerez-dev 019bfda437 feat(canary): token service + contract relocation
Phase 9 task 9.5.

Resolved a long-standing import-cycle constraint: the Generator
interface + Artifact + ArtifactKind + TriggerResponse types now live
at internal/token/contract.go (the token package, which both the
service and concrete generators can import). The
internal/token/generators/generator.go file now re-exports these
as Go type aliases for backwards compatibility — concrete generators
(webbug/slowredirect/docx/pdf/kubeconfig/envfile/mysql) keep their
existing `generators.Artifact` / `generators.KindFile` references
unchanged.

This unlocks token.Service.Create, which would otherwise be unable
to type-reference Artifact (since generators imports token for
token.Type/token.Token).

token.Service:
  - Create(ctx, req, fp, ip) (*Token, Artifact, error):
      - go-playground/validator on the request DTO
      - type-specific metadata validation:
        - slowredirect → metadata.destination_url required, must be
          http:// or https://
        - envfile → metadata.include_keys (if present) must be a
          subset of {aws,stripe,github,db}; malformed JSON rejected
        - others → no extra validation
      - registry.Get(type) → ErrUnknownGeneratorType on miss
      - generateTokenID: 12-char [a-z0-9] via crypto/rand
      - uuid.NewString for manage_id
      - call Generator.Generate (which may mutate t.Metadata for
        mysql's mysql_username persist)
      - repo.Insert; rollback semantics: if generator fails, no
        persistence happens
  - GetByID / GetByManageID: proxy to repo, return (nil, nil) on
    not-found (so handlers can distinguish 404 from real errors)
  - IncrementTriggerCount: proxy to repo
  - TriggerURL / ManageURL: URL builders so handlers don't duplicate
    path joining
  - Generator(t Type): registry lookup for handler dispatch

Tests (~15 cases): ID + manage_id format regexes, repo persistence,
generator call, unknown type, validation failure, slowredirect
metadata variants (missing/invalid-scheme/valid), envfile include_keys
(invalid/valid), generator error path skips persistence, distinct IDs
across 30 calls, GetByID nil-nil semantics, URL builders.
2026-05-13 15:13:43 -04:00