paste

package
v0.13.0-rc9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Anchor resolution for replaying reviewer annotations against a possibly- edited plan. Mirror of web/src/lib/paste/anchor.ts; the two sides MUST stay in sync since both consume the same encrypted Anchor payloads.

4-step fallback (same as the TS):

  1. Exact: line_start..line_end still contain quoted_text → status=ok
  2. Heading-scoped: search the 50 lines after heading_slug → status=drifted
  3. Fuzzy: context_before + quoted_text + context_after appears in the plan → status=drifted
  4. None of the above: status=orphaned, original line numbers preserved for display purposes.

Package paste provides zero-knowledge encrypted paste storage for arc plans and review comments. The server stores opaque ciphertext blobs; encryption and decryption happen exclusively on clients.

Index

Constants

View Source
const (
	// AnchorStatusOK means the original line numbers still contain the quoted
	// text — no relocation was needed.
	AnchorStatusOK = "ok"
	// AnchorStatusDrifted means the anchor was relocated via heading scope
	// or fuzzy context match; the new line numbers are best-effort.
	AnchorStatusDrifted = "drifted"
	// AnchorStatusOrphaned means we couldn't relocate the anchor at all; the
	// original line numbers are preserved for display only.
	AnchorStatusOrphaned = "orphaned"
)

AnchorStatus values for AnchorResolution.Status. Mirrored on the SPA side; adding/renaming a status here breaks the cross-language contract.

View Source
const KeySize = 32

KeySize is the AES-256-GCM key length in bytes used by all paste crypto.

Variables

View Source
var (
	// ErrShareNotFound is returned when no paste share exists with the given ID.
	ErrShareNotFound = errors.New("paste share not found")
	// ErrInvalidEditToken is returned when an edit/delete request's bearer
	// token doesn't match the share's stored edit token.
	ErrInvalidEditToken = errors.New("invalid edit token")
)

Sentinel errors returned by Storage implementations. Handlers translate these into HTTP status codes; CLI clients pattern-match on them too.

Functions

func DecryptJSON

func DecryptJSON(ciphertext, iv, key []byte, v any) error

DecryptJSON inverts EncryptJSON: it decrypts ciphertext under key with the given nonce iv and unmarshals the plaintext JSON into v. Returns an error if the GCM tag fails to verify, the key is wrong, or the plaintext is not valid JSON for the target type.

func EncryptJSON

func EncryptJSON(v any, key []byte) (ciphertext, iv []byte, err error)

EncryptJSON marshals v to JSON and encrypts it with AES-256-GCM under key, returning the ciphertext and the freshly generated nonce (iv). The nonce is drawn fresh from crypto/rand on every call — callers must NOT reuse a nonce with the same key, which would catastrophically break GCM's confidentiality.

func GenerateKey

func GenerateKey() ([]byte, error)

GenerateKey returns a fresh random 32-byte key suitable for paste encryption.

func Slugify

func Slugify(text string) string

Slugify mirrors web/src/lib/paste/anchor.ts:slugify so heading_slug values produced by the SPA match what we recompute here. Lowercase ASCII letters + digits + hyphens; whitespace becomes '-'.

func Snippet

func Snippet(plan string, r AnchorResolution) string

Snippet returns up to ~5 lines around the resolved anchor — handy for LLM consumers that want a small chunk of context without re-reading the whole plan. Returns "" if the resolution is orphaned (no reliable location to extract from).

Types

type Anchor

type Anchor struct {
	LineStart     int    `json:"line_start"`
	LineEnd       int    `json:"line_end"`
	CharStart     *int   `json:"char_start,omitempty"`
	CharEnd       *int   `json:"char_end,omitempty"`
	QuotedText    string `json:"quoted_text"`
	ContextBefore string `json:"context_before,omitempty"`
	ContextAfter  string `json:"context_after,omitempty"`
	HeadingSlug   string `json:"heading_slug,omitempty"`
}

Anchor mirrors the TS Anchor type (web/src/lib/paste/types.ts). Encoded as JSON inside encrypted comment events; decoded here only when the CLI needs to relocate a comment against a current plan.

type AnchorResolution

type AnchorResolution struct {
	LineStart int    `json:"line_start"`
	LineEnd   int    `json:"line_end"`
	Status    string `json:"status"` // one of AnchorStatus* constants
}

AnchorResolution is the result of running ResolveAnchor against a plan. Status disambiguates "found at original location" from "relocated" from "couldn't find at all" — agents care about this for deciding whether to trust the line numbers or fall back to a Grep on QuotedText.

func ResolveAnchor

func ResolveAnchor(plan string, a Anchor) AnchorResolution

ResolveAnchor finds the current location of an anchor in plan markdown. Returns the original line numbers + status="orphaned" if every fallback fails — never returns an error.

type AppendEventRequest

type AppendEventRequest struct {
	Blob []byte `json:"blob"`
	IV   []byte `json:"iv"`
}

type CreatePasteRequest

type CreatePasteRequest struct {
	PlanBlob  []byte     `json:"plan_blob"`
	PlanIV    []byte     `json:"plan_iv"`
	SchemaVer int        `json:"schema_ver"`
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
}

type CreatePasteResponse

type CreatePasteResponse struct {
	ID        string `json:"id"`
	EditToken string `json:"edit_token"`
}

type Event

type Event struct {
	ID        string    `json:"id"`
	ShareID   string    `json:"share_id"`
	Blob      []byte    `json:"blob"`
	IV        []byte    `json:"iv"`
	CreatedAt time.Time `json:"created_at"`
}

type GetPasteResponse

type GetPasteResponse struct {
	Share
	Events []Event `json:"events"`
}

type Handlers

type Handlers struct {
	// contains filtered or unexported fields
}

Handlers holds the paste HTTP handler dependencies.

func NewHandlers

func NewHandlers(s Storage) *Handlers

NewHandlers creates a new Handlers with the given storage backend.

func (*Handlers) Register

func (h *Handlers) Register(g *echo.Group)

Register mounts the paste endpoints on the provided echo.Group.

type Share

type Share struct {
	ID        string     `json:"id"`
	PlanBlob  []byte     `json:"plan_blob"`
	PlanIV    []byte     `json:"plan_iv"`
	SchemaVer int        `json:"schema_ver"`
	CreatedAt time.Time  `json:"created_at"`
	UpdatedAt time.Time  `json:"updated_at"`
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
}

type Storage

type Storage interface {
	CreateShare(ctx context.Context, s Share, editToken string) error
	GetShare(ctx context.Context, id string) (*Share, error)
	UpdateSharePlan(ctx context.Context, id string, planBlob, iv []byte, editToken string) error
	DeleteShare(ctx context.Context, id string, editToken string) error
	AppendEvent(ctx context.Context, e Event) error
	ListEvents(ctx context.Context, shareID string) ([]Event, error)
	VerifyEditToken(ctx context.Context, id, token string) (bool, error)
}

Storage is the persistence interface backing the paste service. It captures the full lifecycle of a share (create, read, update plan, delete) plus the append-only event log used for review comments.

Directories

Path Synopsis
cmd
genxlang command
genxlang generates testdata/xlang_fixtures.json with Go-encrypted blobs.
genxlang generates testdata/xlang_fixtures.json with Go-encrypted blobs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL