# W23 — Seed attachments survive the mint (Request #9)

**Scope:** one bug, one requirement. A Seed carrying a Discord attachment mints a Request
with no trace of the file, and the Seed is deleted in the same act — so the blob is not
merely unlinked, it is unrecoverable.

**Gate:** `cargo build` + `cargo test` green, `traceable-reqs check` exit 0, live proof on
the running daemon over the path the Operator actually drives (a `#` Seed with an image,
minted by a Type reaction).

## The defect

`src/daemon/run.rs:689` calls `capture::mint_plan(&msg.content, type_value, &msg.author.name)`.
The plan is composed from the Seed's **text only** — `msg.attachments` is never read. Then
`run.rs:729` does `msg.delete(&ctx.http)`, because CONTEXT specs the Bot post as *replacing*
the Seed.

Two consequences, and the second is what makes this worth fixing now:

1. The minted issue never mentions the file.
2. The Discord message that held it is gone, and Discord's CDN URLs are signed and expiring,
   so there is no later repair path. The loss is permanent at mint time.

Reported by `discord:reavo.` on Request #9, from live use.

## Design ruling (DRI, 2026-07-26)

CONTEXT.md line 94 specs attachments for the **shell** path only — `create`/`comment` ride
`spt shell send` with a `--file` payload. The Seed→mint path says nothing about them. This is
therefore an unspecced gap, not a violated property: it needs a ruling recorded in CONTEXT,
not just a patch.

1. **Transport: reuse ADR-0006 unchanged.** Fetch each Discord blob, upload it as an asset on
   the rolling `alchemy-attachments` draft release, link it from the issue body. The machinery
   already exists and is already proven (`Engine::upload_attachment`, `attachments_block`,
   `AttachmentLink`). No second transport is introduced, and a minted Request becomes
   byte-identical in shape to a `create --file` one. Discord CDN URLs are **not** an option:
   they expire, and the message holding them is deleted seconds later.
2. **Order: fetch and upload BEFORE `create_issue`.** The links must be in the body of the
   single write that creates the issue — no create-then-edit — and nothing may depend on the
   Seed still existing. The Seed deletion at `run.rs:729` stays where it is, after the Bot post.
3. **Failure announces, never silences.** If a fetch or an upload fails, the mint still
   proceeds and the body names the file that could not be carried. This is the
   announce-and-advance shape already ratified for `REQ-TAG-CURSOR-JUMP-ANNOUNCE`. Refusing
   the mint instead would strand the Seed in a half-minted state with no Operator repair path,
   and silence is precisely the defect under repair.

## Open design questions

- **Asset name collision.** The draft release is one flat namespace per repo, shared with
  `--file` uploads. Two Seeds carrying `image.png` must not collide. Resolve by prefixing the
  asset name with the minting issue's identity — but the issue number does not exist yet at
  upload time. Use the **Seed message id**, which is unique, already known, and stable.
  (`display_name` stays the original filename, so the body reads naturally.)
- **Size.** Discord caps a non-Nitro upload well under the 2GB release-asset ceiling, so no
  new limit is introduced. No clamp needed; a failed upload is already handled by ruling 3.

## Tasks

- **T1 — registry.** `REQ-SEED-ATTACHMENT-CARRY` in `traceable-reqs.toml`, activated
  `["doc","impl","unit"]`, W23 group. *(done)*
- **T2 — doc.** CONTEXT.md: state the carry-across property and the announce-on-failure rule
  under the Seed/Bot post section. `<!-- [doc->REQ-SEED-ATTACHMENT-CARRY] -->`
- **T3 — impl, capture.** `mint_plan` takes the carried links and any failed names, composing
  the attachments block and the loud note into the body. Keep it a pure function — it stays
  unit-testable with no network. `// [impl->REQ-SEED-ATTACHMENT-CARRY]`
- **T4 — impl, run.** Before `create_issue`: download each `msg.attachments` entry, upload via
  the engine to the attachments release, collect links and failures, then compose and create.
- **T5 — unit.** Red-first: a mint plan built from a Seed with attachments links them; one
  built with a failed carry names the file; one with neither is unchanged from today (proving
  no regression to the existing `mint_plan_matches_the_create_contract` contract).
- **T6 — live proof.** Post a `#` Seed with an image in the testbed channel, mint it, confirm
  the issue body links a working asset URL. Drive it the way the Operator does — not through
  a unit harness.

## Not in scope

- `#edit` on an already-minted Request, and the Edit modal — they are Request surfaces, and
  attachment editing there is a separate question nobody has asked for.
- Retro-repair of #9's own lost attachment. The blob is gone; there is nothing to recover.
