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):
- Exact: line_start..line_end still contain quoted_text → status=ok
- Heading-scoped: search the 50 lines after heading_slug → status=drifted
- Fuzzy: context_before + quoted_text + context_after appears in the plan → status=drifted
- 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
- Variables
- func DecryptJSON(ciphertext, iv, key []byte, v any) error
- func EncryptJSON(v any, key []byte) (ciphertext, iv []byte, err error)
- func GenerateKey() ([]byte, error)
- func Slugify(text string) string
- func Snippet(plan string, r AnchorResolution) string
- type Anchor
- type AnchorResolution
- type AppendEventRequest
- type CreatePasteRequest
- type CreatePasteResponse
- type Event
- type GetPasteResponse
- type Handlers
- type Share
- type Storage
Constants ¶
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.
const KeySize = 32
KeySize is the AES-256-GCM key length in bytes used by all paste crypto.
Variables ¶
var ( 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 ¶
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 ¶
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 ¶
GenerateKey returns a fresh random 32-byte key suitable for paste encryption.
func Slugify ¶
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 CreatePasteRequest ¶
type CreatePasteResponse ¶
type GetPasteResponse ¶
type GetPasteResponse struct {
Events []Event `json:"events"`
}
type Handlers ¶
type Handlers struct {
// contains filtered or unexported fields
}
Handlers holds the paste HTTP handler dependencies.
func NewHandlers ¶
NewHandlers creates a new Handlers with the given storage backend.
type Storage ¶
type Storage interface {
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.