Lacuna/Labs
btn-upload
white
#128 / 601

NAME

btn-upload — White read-cart in the 'Buttons (write ops, mint-flavor) (15)' group. Operator-commanded data surface in the Analysis tab.

SYNOPSIS

id btn-upload tier white section §6. White — atomic tools (142 baseline, 149 post-matrix) trigger operator-commanded tools cortex_query, list_listings / list_orders source carry-forward

DESCRIPTION

The upload button. Destructive write; eight-star consent gate fires before bytes leave the device. Operator commands it.

White read-cart in the 'Buttons (write ops, mint-flavor) (15)' group. Operator-commanded data surface in the Analysis tab.

MECHANISM

PREfetchGUARDtier: whiteACTperformRESULTrenderON-ERRretry / esc
;;~ title    "Upload a draft listing"
;;~ author   "lacuna"
;;~ version  1
;;~ mode     automation
;;~ flavor   gray
;;~ id       btn-upload
;;~ touches  (etsy-listings etsy-images)
;;~ summary  "Generated when the operator presses the Upload button. Creates a draft from a payload, attaches photos, lands in Etsy. Architect-shape: every on-result is a NAMED state fn, no inline lambdas. OAuth liveness is a framework precondition; not a cart state. Escalate carries a closed KIND symbol only — surface decides recovery."

(cart 'btn-upload
  '((author    . "lacuna")
    (version   . 1)
    (touches   . (etsy-listings etsy-images))
    (read-only . #f)))

;; ── start ─────────────────────────────────────────────────────────
;; Guard the payload, jump into create-draft.
(define (start ctx)
  (let ((payload (ctx-get 'payload ctx)))
    (if (null? payload)
        (escalate 'missing-payload null)
        (act 'etsy/create-draft (list payload) 'check-draft-result))))

;; ── check-draft-result ───────────────────────────────────────────
;; Named result handler — branches on what came back from the draft
;; creation. No inline lambda; nothing in this branch decides the
;; on-result of any further act. Serialisable.
(define (check-draft-result ctx)
  (let ((r (ctx-result ctx)))
    (cond
      ((eq? r 'rate-limited)
       (after 30 'start ctx))
      ((eq? r 'auth-stale)
       (escalate 'assistant-oauth-relogin null))
      (else
       (next 'attach-images (ctx-set 'draft r ctx))))))

;; ── attach-images ───────────────────────────────────────────────
;; Pop the head image off the list, fire upload-image, branch on the
;; response in a named state fn. The cart recurses through itself by
;; jumping back here from check-image-result.
(define (attach-images ctx)
  (let ((images (ctx-get 'images ctx)))
    (if (or (null? images) (zero? (length images)))
        (next 'announce ctx)
        (let ((draft (ctx-get 'draft ctx)))
          (act 'etsy/upload-image
               (list (draft-id draft) (car images))
               'check-image-result)))))

;; ── check-image-result ──────────────────────────────────────────
;; Result handler for the image upload. Success → drop head + recurse;
;; failure → saga compensation (rollback-draft). The compensation is
;; its own state fn with its own retry/backoff (rollback-retry).
(define (check-image-result ctx)
  (let ((r (ctx-result ctx))
        (images (ctx-get 'images ctx)))
    (cond
      ((eq? r 'ok)
       (next 'attach-images (ctx-set 'images (cdr images) ctx)))
      ((eq? r 'rate-limited)
       (after 30 'attach-images ctx))
      (else
       (next 'rollback-draft (ctx-set 'failure r ctx))))))

;; ── rollback-draft (saga compensation) ──────────────────────────
;; Roll back the half-uploaded draft so the operator's shop isn't
;; left in a half-published state.
(define (rollback-draft ctx)
  (let ((draft (ctx-get 'draft ctx)))
    (act 'etsy/delete-listing (list (draft-id draft)) 'check-rollback)))

;; ── check-rollback ──────────────────────────────────────────────
;; The compensation can itself fail — that's a real path. Retry once
;; with backoff, then escalate with the orphan draft id so a human
;; can clean it up.
(define (check-rollback ctx)
  (let ((r (ctx-result ctx)))
    (cond
      ((eq? r 'ok)
       (escalate 'image-upload-failed (ctx-get 'failure ctx)))
      ((eq? r 'rate-limited)
       (after 30 'rollback-draft ctx))
      (else
       (escalate 'rollback-failed (ctx-get 'draft ctx))))))

;; ── announce ────────────────────────────────────────────────────
;; Clean exit. The driver emits cart-end {outcome: done}; the surface
;; reads that off the bus and pops a calm "uploaded" pill.
(define (announce ctx) (done))

source: curator-web/src/scheme/carts/etsy/btn-upload.sks · 90 lines

EXAMPLES

Example forthcoming — docs/automations/examples.json entry editable in place.

TIER

white
atomic · free · operator-commanded

FITNESS

Ready

Fits any shop today — this one has no sample-size floor.

SEE ALSO

HISTORY

canonical source: curator/docs/AUTOMATIONS-CANONICAL.md · §6-white-atomic-tools-142-baseline-149-post-matrix
last regenerated: 2026-06-05 20:31 UTC (mechanical — edits land in the MD or sidecars, not here)

KNOWN BUGS

(none recorded — flag in AUTOMATIONS-CANONICAL.md or open an issue against the cart file)