Files
steve ca243a2d50
executus CI / test (push) Failing after 24s
P0: stand up executus harness module above majordomo
Batteries-included agent-harness base, extracted from mort's agent layer.
This first cut establishes the module + the zero-coupling core primitives:

- lane, dispatchguard, pendingattach, run/progress.go: moved verbatim from mort
- config: host config Source seam + env-var default (nil-safe helpers)
- deliver: output-egress seam + Discard/Stdout defaults
- identity: AdminPolicy + MemberResolver seams (nil-safe)
- fanout: programmatic N×M swarm (bounded global + per-key concurrency)
- README/CLAUDE.md with the vibe-coded banner; CI with Go gates +
  the "core stays majordomo+stdlib only" invariant

Core builds with stdlib only today; majordomo enters at P1 (model/structured).
go build/vet/test -race all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:18:37 -04:00

55 lines
1.7 KiB
Go

// Package pendingattach holds the canonical pending-attachment row type
// and its dedupe helper. It imports nothing from the rest of mort so
// every consumer (the skills storage layer, the send_attachments tool,
// and all delivery drainers) can depend on it without import cycles.
//
// CONTRACT: enqueue (Storage.AddPendingAttachment) is NOT idempotent — a
// model that calls send_attachments more than once for the same file
// leaves multiple rows. Every consumer that drains pending attachments
// MUST call Dedupe before delivery, or the artifact double-posts.
package pendingattach
import "time"
// Attachment is one deferred-attachment row. Field-for-field the shape
// the skills storage layer persists.
type Attachment struct {
ID string
RunID string
SkillID string
FileID string
Filename string
Mime string
SizeBytes int64
MessageText string
HostedURL string
Ord int
// CreatedAt is the enqueue time. Used only by the retention sweeper
// (PurgePendingAttachments) and storage round-trip tests; delivery
// drainers ignore it. Zero on the enqueue path — the storage layer
// defaults it to time.Now().
CreatedAt time.Time
}
// Dedupe removes rows whose FileID has already been seen (first
// occurrence wins, preserving input order). Rows with an empty FileID
// are never collapsed.
func Dedupe(rows []Attachment) []Attachment {
if len(rows) < 2 {
return rows
}
seen := make(map[string]struct{}, len(rows))
out := make([]Attachment, 0, len(rows))
for _, row := range rows {
if row.FileID != "" {
if _, dup := seen[row.FileID]; dup {
continue
}
seen[row.FileID] = struct{}{}
}
out = append(out, row)
}
return out
}