Documentation
¶
Overview ¶
Package projectprotocol defines the Project Context Protocol domain contract: skill and rule artifacts, immutable revisions, activation compare-and-swap (CAS) pointers, idempotency DTOs, canonical hashing and the deterministic effective protocol resolution with its approved limits.
Artifacts are NON-EXECUTABLE data. Kinds "skill" and "rule" are inert labels and the only accepted content type is text/markdown; nothing in this package interprets, evaluates or executes artifact content. Consumers (plugins, agents) MUST treat artifacts as documentation, never as code authority.
The package is deliberately free of persistence, HTTP/MCP and provider concerns: it depends on the Go standard library only. Store and transport layers validate through these same types and limits so local, HTTP and MCP paths cannot disagree (see the ProjectProtocolStore port in internal/domain/interfaces.go).
v1 retention policy (REQ-RET-001): revisions, activations and audit events are immutable and retained indefinitely. Deletion is exclusively the soft-delete state transition; this package defines NO hard-delete or purge operation.
Index ¶
- Constants
- Variables
- func CanonicalDigest(v any) (string, []byte, error)
- func CanonicalJSON(v any) ([]byte, error)
- func CanonicalizeMetadata(raw []byte) ([]byte, error)
- func CanonicalizeMetadataMap(m map[string]any) ([]byte, error)
- func DecodeCanonicalRaw(raw []byte) (any, error)
- func DigestHex(bytes []byte) string
- func ETag(canonical []byte) string
- func PageSizeBounds(requested int) int
- func ValidateContent(content string) error
- func ValidateContentType(contentType string) error
- func ValidateETagShape(etag string) error
- func ValidateIdempotencyKey(s string) error
- func ValidateKey(key string) error
- func ValidateProjectRef(scope Scope, project string) error
- func ValidateSaveArtifactInput(in SaveArtifactInput) error
- func ValidateTitle(title string) error
- type ActivateInput
- type Activation
- type Artifact
- type ArtifactEvent
- type ArtifactEventPage
- type ArtifactFilter
- type ArtifactPage
- type Bundle
- type Error
- type ErrorCode
- type EventType
- type IdempotencyKey
- type IdempotencyRecord
- type IdempotencyVerdict
- type KeyConflict
- type Kind
- type LimitWriter
- type PageInfo
- type PageRequest
- type Preconditions
- type Protocol
- type ProtocolArtifact
- type ProviderBinding
- type Resolution
- type ResolvableArtifact
- type Revision
- type RevisionInput
- type RevisionPage
- type RollbackInput
- type SaveArtifactInput
- type Scope
- type ShadowedArtifact
- type SoftDeleteInput
- type Status
Constants ¶
const ( // MaxKeyRunes bounds an artifact key (1..128 runes). MaxKeyRunes = 128 // MaxTitleRunes bounds an artifact title (1..200 runes). MaxTitleRunes = 200 // MaxMessageRunes bounds a revision message (0..1024 runes). MaxMessageRunes = 1024 // MaxReasonRunes bounds a soft-delete/rollback reason (1..1024 runes). MaxReasonRunes = 1024 // MaxIdempotencyKeyBytes bounds an idempotency key (1..128 bytes). MaxIdempotencyKeyBytes = 128 // MaxProjectRunes bounds a project reference (1..128 runes) when the // scope is project; the workspace default uses the empty reference. MaxProjectRunes = 128 // MaxArtifactIDBytes bounds store-assigned artifact identifiers. MaxArtifactIDBytes = 128 // MaxActorBytes bounds provenance actors (deleted_by, event actors): // 1..256 bytes of valid UTF-8. MaxActorBytes = 256 )
Field bounds for validated artifact fields.
const ( // MaxArtifactContentBytes bounds artifact content measured in UTF-8 // bytes after decode. Content is never normalized or truncated. MaxArtifactContentBytes = 1 << 20 // 1 MiB // MaxArtifactMetadataBytes bounds the canonical JSON encoding of the // artifact metadata object. The limit applies to canonical bytes, not // to the raw transport encoding. MaxArtifactMetadataBytes = 1 << 16 // 64 KiB // MaxEffectiveArtifacts bounds the number of active artifacts in the // effective protocol after project-over-workspace resolution. MaxEffectiveArtifacts = 2000 // MaxProtocolBundleBytes bounds the canonical JSON encoding of the full // effective protocol bundle. MaxProtocolBundleBytes = 1 << 22 // 4 MiB // OrdinaryRequestTransportBytes is the HTTP transport cap for ordinary // JSON request bodies (encoded, pre-decode). Informational for // transport layers; semantic limits above remain authoritative. OrdinaryRequestTransportBytes = 1 << 20 // 1 MiB // LargeMutationTransportBytes is the HTTP transport cap for artifact // create/revision envelopes so that a valid escaped 1 MiB content plus // 64 KiB metadata still reaches semantic validation. LargeMutationTransportBytes = 8 << 20 // 8 MiB // MCPAbsoluteRequestBytes is the absolute encoded request cap for the // MCP endpoint. MCPAbsoluteRequestBytes = 8 << 20 // 8 MiB // MCPProtocolResponseTargetBytes is the MCP response cap target: it // must accommodate the canonical 4 MiB structured bundle plus a // bounded envelope. MCPProtocolResponseTargetBytes = 5 << 20 // 5 MiB // DefaultPageSize is the default page size for list operations. DefaultPageSize = 20 // MaxPageSize is the maximum page size for list operations. MaxPageSize = 100 )
Approved v1 limits. These constants are the single source of truth shared by local CLI, HTTP, MCP and store validation; layers MUST NOT define their own divergent copies.
Exactly-at-limit values are accepted; limit+1 is rejected atomically with zero effects and no truncation (REQ-LIMIT-001..003).
const ( ProviderHealthHealthy = "healthy" ProviderHealthDegraded = "degraded" ProviderHealthUnhealthy = "unhealthy" )
Health values reuse the port health vocabulary.
const ContentTypeMarkdown = "text/markdown"
ContentTypeMarkdown is the only accepted artifact content type. Artifacts are markdown documents and never executable payloads.
Variables ¶
var ( ErrInvalidArtifact = &Error{Code: ErrCodeValidation, Message: "invalid artifact"} ErrInvalidUTF8 = &Error{Code: ErrCodeInvalidUTF8, Message: "content is not valid UTF-8"} ErrDuplicateMetadataKey = &Error{Code: ErrCodeDuplicateMetadataKey, Message: "metadata contains a duplicate object key"} ErrContentTooLarge = &Error{Code: ErrCodeContentTooLarge, Message: "artifact content exceeds the accepted size", Limit: MaxArtifactContentBytes} ErrMetadataTooLarge = &Error{Code: ErrCodeMetadataTooLarge, Message: "canonical metadata exceeds the accepted size", Limit: MaxArtifactMetadataBytes} ErrEffectiveLimit = &Error{Code: ErrCodeEffectiveLimitExceeded, Message: "effective artifact limit exceeded", Limit: MaxEffectiveArtifacts} ErrProtocolTooLarge = &Error{Code: ErrCodeProtocolTooLarge, Message: "canonical protocol bundle exceeds the accepted size", Limit: MaxProtocolBundleBytes} ErrRevisionConflict = &Error{Code: ErrCodeRevisionConflict, Message: "artifact changed since the expected revision"} ErrActivationConflict = &Error{Code: ErrCodeActivationConflict, Message: "activation pointer changed since the expected activation revision"} ErrIdempotencyConflict = &Error{Code: ErrCodeIdempotencyConflict, Message: "idempotency key was already used with a different payload"} )
Sentinel errors for the most common failure classes. Errors carrying a dynamic Limit (limit_exceeded, *_too_large) should be constructed with NewLimitError so the exact bound travels with the error.
Functions ¶
func CanonicalDigest ¶
CanonicalDigest returns the canonical bytes of v and their "sha256:<hex>" digest. Digests computed this way are stable across map iteration order.
func CanonicalJSON ¶
CanonicalJSON encodes v into its deterministic canonical JSON form. Accepted value types: nil, bool, string, json.Number, float32/float64, signed/unsigned integers, []any, map[string]any and json.RawMessage. Other types are rejected as unsupported to avoid encoding ambiguity.
func CanonicalizeMetadata ¶
CanonicalizeMetadata validates raw JSON metadata and returns its canonical bytes. The root must be a JSON object; duplicate keys, invalid UTF-8, unpaired surrogates and non-finite numbers are rejected. The canonical byte length must not exceed MaxArtifactMetadataBytes: exactly at the limit is accepted, one byte more is rejected without truncation.
func CanonicalizeMetadataMap ¶
CanonicalizeMetadataMap validates an in-memory metadata object and returns its canonical bytes under the same rules and limit as CanonicalizeMetadata.
func DecodeCanonicalRaw ¶
DecodeCanonicalRaw decodes raw JSON bytes into canonical-ready values: duplicate object keys are rejected, trailing data is rejected, numbers are preserved as json.Number, and invalid UTF-8 input is rejected before any decoding (Go's decoder would otherwise silently replace it with U+FFFD). Unpaired \uD800-\uDFFF escapes are rejected because they decode to U+FFFD and would silently alias distinct inputs.
func ETag ¶
ETag returns the opaque quoted ETag for canonical bytes (a quoted sha256 hex string), as used for artifact ETags and protocol conditional requests.
func PageSizeBounds ¶
PageSizeBounds validates and normalizes a requested page size. Non-positive values return DefaultPageSize; values above MaxPageSize are clamped to MaxPageSize.
func ValidateContent ¶
ValidateContent enforces the artifact content contract: valid UTF-8, non-empty, and at most MaxArtifactContentBytes bytes after decode. Exactly at the limit is accepted; one byte more is rejected without truncation.
func ValidateContentType ¶
ValidateContentType enforces the single non-executable content type.
func ValidateETagShape ¶
ValidateETagShape checks that etag is a well-formed canonical entity tag: a quoted string of exactly 64 lowercase hex characters, the shape produced by ETag() over canonical bytes. If-Match preconditions and artifact ETags share this grammar.
func ValidateIdempotencyKey ¶
ValidateIdempotencyKey enforces 1..128 printable ASCII bytes (no control characters, no invalid UTF-8).
func ValidateKey ¶
ValidateKey enforces the stable artifact key grammar: 1..128 runes matching [a-z0-9]a-z0-9._/-*.
func ValidateProjectRef ¶
ValidateProjectRef validates the project reference for a scope. The workspace default scope must use the empty reference; the project scope requires 1..128 valid-UTF-8 runes without control characters.
func ValidateSaveArtifactInput ¶
func ValidateSaveArtifactInput(in SaveArtifactInput) error
ValidateSaveArtifactInput checks the full creation contract, including the approved content and canonical metadata limits and the REQUIRED idempotency key: creation is idempotent per key + request digest and MUST NOT be dispatched without one. An empty project means the workspace default scope.
func ValidateTitle ¶
ValidateTitle enforces 1..200 valid-UTF-8 runes.
Types ¶
type ActivateInput ¶
type ActivateInput struct {
ArtifactID string `json:"artifact_id"`
Revision int64 `json:"revision"`
ExpectedActivationRevision int64 `json:"expected_activation_revision"`
}
ActivateInput activates one revision under activation CAS.
func (ActivateInput) Validate ¶
func (in ActivateInput) Validate() error
Validate checks the activation request invariants.
type Activation ¶
type Activation struct {
ArtifactID string `json:"artifact_id"`
Revision int64 `json:"revision"`
ActivationRevision int64 `json:"activation_revision"`
ActivatedBy string `json:"activated_by"`
ActivatedAt time.Time `json:"activated_at"`
Reason string `json:"reason,omitempty"`
}
Activation is the audited compare-and-swap pointer from an artifact to exactly one of its revisions within a scope. Rollback creates a NEW activation event pointing at a previous revision; it never rewrites history. ActivationRevision is the monotonic CAS token: concurrent activate/rollback operations must win exactly once by comparing ExpectedActivationRevision against the stored value.
func (Activation) Validate ¶
func (a Activation) Validate() error
Validate checks the activation invariants.
type Artifact ¶
type Artifact struct {
ID string `json:"id"`
Project string `json:"project"`
Kind Kind `json:"kind"`
Key string `json:"key"`
Title string `json:"title"`
Scope Scope `json:"scope"`
Status Status `json:"status"`
Precedence int32 `json:"precedence"`
LatestRevision int64 `json:"latest_revision"`
ActiveRevision *int64 `json:"active_revision"`
// ActivationRevision is the monotonic compare-and-swap token for the
// activation pointer. Every activate/rollback increments it exactly once.
ActivationRevision int64 `json:"activation_revision"`
ETag string `json:"etag"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Soft-delete provenance (REQ-RET-001). The three fields are set exactly
// when Status transitions to deleted and are retained indefinitely;
// artifacts in any other status MUST NOT carry them.
DeletedAt *time.Time `json:"deleted_at,omitempty"`
DeletedBy string `json:"deleted_by,omitempty"`
DeleteReason string `json:"delete_reason,omitempty"`
}
Artifact is the logical, stable artifact record. Content lives exclusively in immutable revisions; the artifact row carries identity and state.
func (Artifact) CanonicalETag ¶
CanonicalETag derives the artifact's canonical entity tag from its validated state: ETag() over the canonical JSON of etagPayload. Stores MUST assign exactly this value when persisting state transitions; the derivation is deterministic and content-stable across clock jitter.
func (Artifact) Validate ¶
Validate performs the full artifact invariant check for persisted or constructed artifacts.
func (Artifact) VerifyETag ¶
VerifyETag recomputes the canonical ETag and reports whether it still matches, detecting any post-assignment mutation of covered state.
type ArtifactEvent ¶
type ArtifactEvent struct {
ID string `json:"id"`
ArtifactID string `json:"artifact_id"`
Type EventType `json:"type"`
// Revision references the revision written, activated or rolled back to;
// 0 when not applicable (e.g. soft delete).
Revision int64 `json:"revision"`
// ActivationRevision references the activation CAS token produced by the
// event; 0 when not applicable.
ActivationRevision int64 `json:"activation_revision"`
Actor string `json:"actor,omitempty"`
Reason string `json:"reason,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
ArtifactEvent is one immutable audit record over an artifact. Revisions and activations carry their own detail; events provide the listable timeline surface (REQ-PAGE-001 applies: cursor pagination, limit default 20/max 100, stable ordering by created_at desc, id desc).
func (ArtifactEvent) Validate ¶
func (e ArtifactEvent) Validate() error
Validate checks the persisted-event invariants. Stores call this when appending or loading events.
type ArtifactEventPage ¶
type ArtifactEventPage struct {
Items []ArtifactEvent `json:"items"`
Page PageInfo `json:"page"`
}
ArtifactEventPage is one bounded page of audit events.
type ArtifactFilter ¶
type ArtifactFilter struct {
// Project filters by project reference; empty means the workspace
// default scope.
Project string `json:"project"`
Kind *Kind `json:"kind,omitempty"`
ActiveOnly bool `json:"active"`
IncludeDeleted bool `json:"include_deleted"`
Query string `json:"q,omitempty"`
}
ArtifactFilter selects artifacts for listing.
type ArtifactPage ¶
ArtifactPage is one bounded page of artifacts.
type Bundle ¶
type Bundle struct {
// Canonical is the canonical JSON encoding of the protocol (bounded by
// MaxProtocolBundleBytes) or nil when the bundle exceeded the limit.
Canonical []byte
// ETag is the opaque quoted entity tag of the canonical bytes.
ETag string
// Digest is the "sha256:<hex>" digest of the canonical bytes.
Digest string
// BytesLen is the canonical byte count (0 on abort).
BytesLen int
}
Bundle is the canonical bounded encoding result of a protocol snapshot.
type Error ¶
Error is the typed domain error. Limit carries the exact bound that was exceeded (0 when not applicable) so callers can report limits without echoing content.
func AsError ¶
AsError normalizes any error into a *Error, wrapping unknown errors as validation failures. The original message is dropped: transport-safe context can be attached via Detail by the caller.
func NewLimitError ¶
NewLimitError builds a typed limit failure carrying the exact bound.
type ErrorCode ¶
type ErrorCode string
ErrorCode is the stable, transport-neutral failure classification for the Project Context Protocol domain. Transport layers map these codes onto their HTTP statuses / MCP error codes; the domain itself never leaks content, keys or secrets in messages.
const ( ErrCodeValidation ErrorCode = "validation" ErrCodePayloadTooLarge ErrorCode = "payload_too_large" ErrCodeContentTooLarge ErrorCode = "content_too_large" ErrCodeMetadataTooLarge ErrorCode = "metadata_too_large" ErrCodeInvalidUTF8 ErrorCode = "invalid_utf8" ErrCodeDuplicateMetadataKey ErrorCode = "duplicate_metadata_key" ErrCodeEffectiveLimitExceeded ErrorCode = "effective_artifact_limit_exceeded" ErrCodeProtocolTooLarge ErrorCode = "protocol_too_large" ErrCodeLimitExceeded ErrorCode = "limit_exceeded" ErrCodeRevisionConflict ErrorCode = "revision_conflict" ErrCodeActivationConflict ErrorCode = "activation_conflict" ErrCodeIdempotencyConflict ErrorCode = "idempotency_conflict" ErrCodeNotFound ErrorCode = "not_found" ErrCodeUnsupportedType ErrorCode = "unsupported_type" )
type EventType ¶
type EventType string
EventType classifies an immutable audit event on an artifact's history. Events are append-only and retained indefinitely (REQ-RET-001/002); authorized history remains listable even for soft-deleted artifacts.
const ( // EventArtifactCreated records artifact creation with its first revision. EventArtifactCreated EventType = "artifact_created" // EventRevisionAppended records an immutable revision write. EventRevisionAppended EventType = "revision_appended" // EventActivated records an activation pointer transition. EventActivated EventType = "activated" // EventRolledBack records a rollback to a previous revision. EventRolledBack EventType = "rolled_back" // EventSoftDeleted records the soft-delete state transition. EventSoftDeleted EventType = "soft_deleted" )
type IdempotencyKey ¶
type IdempotencyKey string
IdempotencyKey is the caller-supplied idempotency identifier for artifact creation and revision writes. Keys are compared within the principal-derived workspace/project scope, never globally.
func NewIdempotencyKey ¶
func NewIdempotencyKey(s string) (IdempotencyKey, error)
NewIdempotencyKey validates and wraps a key.
type IdempotencyRecord ¶
type IdempotencyRecord struct {
Key IdempotencyKey `json:"key"`
RequestDigest string `json:"request_digest"` // sha256:<hex> of the canonical request payload
ArtifactID string `json:"artifact_id"`
Revision int64 `json:"revision"`
}
IdempotencyRecord is the durable evidence of a previous keyed write.
func (IdempotencyRecord) Validate ¶
func (r IdempotencyRecord) Validate() error
Validate checks the record invariants.
type IdempotencyVerdict ¶
type IdempotencyVerdict string
IdempotencyVerdict classifies a retry against a stored record.
const ( // IdempotencyNew means no record exists for the key: execute the write. IdempotencyNew IdempotencyVerdict = "new" // IdempotencyReplay means the same key and payload digest were seen: // return the original result without re-executing. IdempotencyReplay IdempotencyVerdict = "replay" // IdempotencyConflict means the key was reused with a different payload // digest: fail with idempotency_conflict and mutate nothing. IdempotencyConflict IdempotencyVerdict = "conflict" )
func ClassifyIdempotency ¶
func ClassifyIdempotency(stored *IdempotencyRecord, key IdempotencyKey, requestDigest string) IdempotencyVerdict
ClassifyIdempotency applies the verdict rules: same key+digest replays, same key with a different digest conflicts, absent key is new.
type KeyConflict ¶
type KeyConflict struct {
Kind Kind `json:"kind"`
Key string `json:"key"`
Scope Scope `json:"scope"`
ArtifactIDs []string `json:"artifact_ids"`
ResolvedByID string `json:"resolved_by_id"`
}
KeyConflict records two or more distinct artifact records sharing one (kind,key) within the same scope. The resolver still picks a deterministic winner (precedence descending, then artifact id ascending) so resolution stays total; consumers surface the conflict for operator review.
func (KeyConflict) Validate ¶
func (c KeyConflict) Validate() error
Validate checks the conflict-entry invariants: at least two distinct artifact ids, sorted and unique, with the deterministic winner among them. It does NOT perform the bundle-level crosscheck; Protocol.Validate does.
type Kind ¶
type Kind string
Kind is the artifact kind label. Artifacts are non-executable data; kinds exist to organize retrieval, not to select execution behavior.
type LimitWriter ¶
type LimitWriter struct {
// contains filtered or unexported fields
}
LimitWriter is the counting writer/hash used by every bounded canonical encoding in this package (metadata, revision digests, protocol bundles).
Contract (REQ-DOS-002, REQ-LIMIT-003):
- the total accepted byte count never exceeds the configured limit;
- a Write whose completion would reach limit+1 is rejected in full: it contributes zero bytes, the internal buffer is discarded (no partial output), and the writer enters a failed state;
- once failed, Bytes returns nil and every subsequent Write fails with the same typed limit error;
- the running SHA-256 covers exactly the accepted bytes, so a successful writer yields both the bounded canonical output and its digest/ETag.
func NewLimitWriter ¶
func NewLimitWriter(limit int64) *LimitWriter
NewLimitWriter returns a counting writer that accepts at most limit bytes. The limit must be non-negative.
func (*LimitWriter) Bytes ¶
func (w *LimitWriter) Bytes() []byte
Bytes returns the accepted bytes, or nil once the writer has aborted. A failed encoding therefore never exposes partial output.
func (*LimitWriter) Count ¶
func (w *LimitWriter) Count() int64
Count returns the number of accepted bytes; it never exceeds Limit.
func (*LimitWriter) Digest ¶
func (w *LimitWriter) Digest() string
Digest returns the "sha256:<hex>" digest of the accepted bytes.
func (*LimitWriter) ETag ¶
func (w *LimitWriter) ETag() string
ETag returns the opaque quoted ETag of the accepted bytes.
func (*LimitWriter) Failed ¶
func (w *LimitWriter) Failed() bool
Failed reports whether the writer aborted at the limit.
type PageInfo ¶
type PageInfo struct {
NextCursor string `json:"next_cursor"`
HasMore bool `json:"has_more"`
SnapshotRevision string `json:"snapshot_revision"`
}
PageInfo carries the pagination output contract.
type PageRequest ¶
PageRequest is the bounded cursor pagination input. Limit is normalized by PageSizeBounds; cursors are opaque, snapshot-bound tokens produced by the store layer.
func (PageRequest) Normalize ¶
func (p PageRequest) Normalize() PageRequest
Normalize clamps the request to the approved page bounds.
type Preconditions ¶
type Preconditions struct {
ExpectedRevision *int64 `json:"expected_revision,omitempty"`
IfMatchETag string `json:"if_match_etag,omitempty"`
}
Preconditions is the optimistic concurrency guard for artifact writes. Exactly one form may be set: ExpectedRevision compares against the stored latest revision number; IfMatchETag compares against the stored artifact ETag. A stale precondition MUST fail with revision_conflict and mutate nothing.
func (Preconditions) Validate ¶
func (p Preconditions) Validate() error
Validate enforces the exclusive union and the canonical ETag grammar.
type Protocol ¶
type Protocol struct {
Project string `json:"project"`
ProtocolRevision string `json:"protocol_revision"`
GeneratedAt time.Time `json:"generated_at"`
Artifacts []ProtocolArtifact `json:"artifacts"`
Shadowed []ShadowedArtifact `json:"shadowed"`
Conflicts []KeyConflict `json:"conflicts"`
// ProviderBinding is the sanitized project->provider reference summary,
// or nil when the project has no binding. It participates in bundle
// validation, hashing and size accounting (REQ-LIMIT-003).
ProviderBinding *ProviderBinding `json:"provider_binding"`
}
Protocol is the deterministic effective protocol snapshot for one project. ProtocolRevision is the store-supplied opaque monotonic snapshot identifier; GeneratedAt is its creation time.
func (*Protocol) EncodeBundle ¶
EncodeBundle canonicalizes the protocol through the counting writer and hashing pipeline (REQ-LIMIT-003): the snapshot is fully validated BEFORE any byte is emitted (per-artifact invariants, the effective count cap, the sanitized provider binding, and the sorted/unique/crosschecked shadowed+conflict provenance), then streamed into the 4 MiB bounded buffer. Exactly 4 MiB is accepted; one byte more aborts with protocol_too_large and no partial canonical bytes are returned.
The canonical bytes deliberately EXCLUDE generated_at: the ETag/digest are content-stable across generation time of the same snapshot (protocol_revision identifies the snapshot; conditional requests compare content, not wall clocks).
func (*Protocol) Validate ¶
Validate checks the snapshot invariants: the effective count cap, the sanitized provider binding, per-artifact semantics, strictly sorted and unique effective/(kind,key) ordering, and the FULL provenance contract on shadowed/conflicts — every entry semantically valid, sorted, unique, and crosschecked against the effective set so bundle bytes cannot carry provenance the snapshot state does not support (REQ-LIMIT-003).
type ProtocolArtifact ¶
type ProtocolArtifact struct {
ArtifactID string `json:"artifact_id"`
Kind Kind `json:"kind"`
Key string `json:"key"`
Title string `json:"title"`
Revision int64 `json:"revision"`
SourceScope Scope `json:"source_scope"`
ContentType string `json:"content_type"`
Content string `json:"content"`
Metadata map[string]any `json:"metadata"`
Digest string `json:"digest"`
Precedence int32 `json:"precedence"`
}
ProtocolArtifact is one fully materialized artifact of the effective protocol bundle, carrying content and canonical metadata.
func (ProtocolArtifact) Validate ¶
func (p ProtocolArtifact) Validate() error
Validate checks the bundle artifact invariants.
type ProviderBinding ¶
type ProviderBinding struct {
ProviderID string `json:"provider_id"`
Model string `json:"model"`
Dimension int `json:"dimension"`
BindingRevision int64 `json:"binding_revision"`
Generation int64 `json:"generation"`
ReindexState string `json:"reindex_state"`
Health string `json:"health"`
}
ProviderBinding is the sanitized, non-secret project->provider summary carried by the effective protocol (REQ-ART-004: "provider_binding summary no secreto"). It is a versioned reference into the operator provider catalog plus its reindex state; it MUST NEVER contain credentials, tokens or raw catalog configuration.
func (ProviderBinding) Validate ¶
func (b ProviderBinding) Validate() error
Validate checks the sanitized provider binding invariants. Every field is bounded and printable-ASCII; no secret material can be represented.
type Resolution ¶
type Resolution struct {
// Effective holds the winning artifact per (kind,key), ordered by kind
// then key. Its length never exceeds MaxEffectiveArtifacts.
Effective []ResolvableArtifact `json:"effective"`
// Shadowed records workspace-default artifacts overridden by a project
// artifact with the same (kind,key), ordered by kind then key.
Shadowed []ShadowedArtifact `json:"shadowed"`
// Conflicts records same-scope key collisions between distinct artifact
// records (an integrity signal; keys are unique per scope by contract).
Conflicts []KeyConflict `json:"conflicts"`
}
Resolution is the deterministic outcome of project-over-workspace resolution over active artifact summaries.
func Resolve ¶
func Resolve(candidates []ResolvableArtifact) (Resolution, error)
Resolve computes the effective protocol summary set from active artifact candidates (REQ-ART-004).
Determinism contract:
- for each (kind,key), a project-scope artifact wins over a workspace-default artifact; the loser is recorded in Shadowed;
- within one scope, distinct artifact records sharing a (kind,key) are recorded in Conflicts with the deterministic winner chosen by precedence descending then artifact id ascending;
- Effective is sorted by kind then key;
- the effective count is capped at MaxEffectiveArtifacts: reaching limit+1 aborts with effective_artifact_limit_exceeded BEFORE any content is fetched (candidates carry no content by construction).
Candidate identity integrity: an artifact id appears at most once per snapshot. Repeated candidates whose entire summary is identical are exact duplicate rows and collapse silently (the outcome is unchanged); a repeated id with ANY differing field (key, kind, scope, precedence, revision, digest) is inconsistent data and rejected outright. This guarantees the returned provenance — conflict id lists and shadowed references — is always internally sorted, unique and crosscheck-valid for Protocol.Validate.
type ResolvableArtifact ¶
type ResolvableArtifact struct {
ArtifactID string `json:"artifact_id"`
Kind Kind `json:"kind"`
Key string `json:"key"`
Scope Scope `json:"scope"`
Precedence int32 `json:"precedence"`
Revision int64 `json:"revision"`
Digest string `json:"digest"`
}
ResolvableArtifact is the manifest-level summary of one ACTIVE artifact consumed by the effective protocol resolver. It deliberately carries no content: resolution MUST count and select before any content fetch (REQ-LIMIT-002, REQ-DOS-002).
func (ResolvableArtifact) Validate ¶
func (r ResolvableArtifact) Validate() error
Validate checks the summary invariants.
type Revision ¶
type Revision struct {
ArtifactID string `json:"artifact_id"`
Revision int64 `json:"revision"` // 1-based monotonic
Title string `json:"title"`
Content string `json:"content"`
ContentType string `json:"content_type"` // always text/markdown
Metadata json.RawMessage `json:"metadata"` // canonical JSON object bytes
Message string `json:"message,omitempty"`
Digest string `json:"digest"` // sha256 of the canonical revision payload
CreatedBy string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
}
Revision is an immutable content snapshot of an artifact. Revisions are append-only: once constructed they are never mutated, and stores MUST NOT update or delete them (REQ-RET-001). The zero value is not valid; use NewRevision, the only constructor that computes the digest.
func NewRevision ¶
func NewRevision(artifactID string, revisionNumber int64, in RevisionInput, createdBy string, createdAt time.Time) (Revision, error)
NewRevision validates the input and constructs the immutable revision with its canonical digest. The digest covers artifact_id, revision, title, content, content_type, canonical metadata and message; provenance fields (created_by/created_at) are deliberately excluded so the same content always yields the same digest. Metadata is stored canonically, making the digest stable across key insertion order.
func (Revision) Validate ¶
Validate performs the persisted-revision invariant check (stores call this when loading or writing revisions).
func (Revision) VerifyDigest ¶
VerifyDigest recomputes the canonical digest and reports whether it still matches, detecting any post-construction mutation of digest-covered fields.
type RevisionInput ¶
type RevisionInput struct {
Title string `json:"title,omitempty"`
Content string `json:"content"`
ContentType string `json:"content_type,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
Message string `json:"message,omitempty"`
IdempotencyKey IdempotencyKey `json:"idempotency_key"`
}
RevisionInput is the transport-neutral payload for creating a revision. Derived fields (revision number, digest, actor, timestamp) are assigned by NewRevision at construction time, never taken from client input.
IdempotencyKey is REQUIRED on every revision write (REQ-ART-002): stores replay same key+digest requests and conflict on key reuse with a different payload. Title is optional per the API contract (title?); an empty title means "inherit the artifact title" and the store resolves it before the immutable revision is persisted.
func (RevisionInput) RequestDigest ¶
func (in RevisionInput) RequestDigest(artifactID string) (string, error)
RequestDigest returns the canonical "sha256:<hex>" digest of the revision request payload bound to artifactID. It deliberately excludes the idempotency key itself (the key selects the comparison slot; the digest is the compared value) and provenance fields. Stores persist it in the IdempotencyRecord for replay/conflict classification.
func (RevisionInput) Validate ¶
func (in RevisionInput) Validate() error
Validate checks the full revision input contract before construction: a required idempotency key, valid UTF-8 non-empty content within MaxArtifactContentBytes, an optional valid title, canonical metadata within MaxArtifactMetadataBytes, and the single non-executable content type.
type RevisionPage ¶
RevisionPage is one bounded page of revisions.
type RollbackInput ¶
type RollbackInput struct {
ArtifactID string `json:"artifact_id"`
ToRevision int64 `json:"to_revision"`
ExpectedActivationRevision int64 `json:"expected_activation_revision"`
Reason string `json:"reason"`
}
RollbackInput repoints the activation at a previous revision under activation CAS, recording an explicit reason for the audit trail.
func (RollbackInput) Validate ¶
func (in RollbackInput) Validate() error
Validate checks the rollback request invariants.
type SaveArtifactInput ¶
type SaveArtifactInput struct {
Project string `json:"project"`
Kind string `json:"kind"`
Key string `json:"key"`
Title string `json:"title"`
Content string `json:"content"`
ContentType string `json:"content_type,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
Precedence int32 `json:"precedence,omitempty"`
IdempotencyKey IdempotencyKey `json:"idempotency_key"`
}
SaveArtifactInput is the transport-neutral artifact creation payload. Project/Kind/Key/Title/Content are validated by ValidateSaveArtifactInput; derived identity, revision numbers and timestamps are store-assigned.
func (SaveArtifactInput) RequestDigest ¶
func (in SaveArtifactInput) RequestDigest() (string, error)
RequestDigest returns the canonical "sha256:<hex>" digest of the creation request payload, excluding the idempotency key itself. Stores persist it in the IdempotencyRecord so retries with the same key replay and key reuse with a different payload conflicts.
type ShadowedArtifact ¶
type ShadowedArtifact struct {
ArtifactID string `json:"artifact_id"`
Kind Kind `json:"kind"`
Key string `json:"key"`
Revision int64 `json:"revision"`
ShadowedByID string `json:"shadowed_by_id"`
}
ShadowedArtifact is a workspace-default artifact overridden by a project artifact for the same (kind,key).
func (ShadowedArtifact) Validate ¶
func (s ShadowedArtifact) Validate() error
Validate checks the shadowed-entry invariants. It does NOT perform the bundle-level crosscheck against the effective set; Protocol.Validate does.
type SoftDeleteInput ¶
type SoftDeleteInput struct {
ArtifactID string `json:"artifact_id"`
IfMatchETag string `json:"if_match_etag"`
DeletedBy string `json:"deleted_by"`
Reason string `json:"reason"` // required
}
SoftDeleteInput is the only deletion transition in v1: it marks the artifact deleted (excluded from default lists and the effective protocol) while revisions, activations and events are retained indefinitely. There is deliberately NO hard-delete or purge input.
IfMatchETag carries the REQUIRED and ONLY compare-and-swap guard for deletion (REQ-API-003: "delete requires If-Match"): the caller MUST present the artifact's current canonical ETag. There is deliberately NO expected_revision form — deletion is ETag-addressed, so a stale request can never delete state it has not seen. A stale ETag fails with revision_conflict and mutates nothing. DeletedBy (the acting principal) and Reason are mandatory; the store assigns DeletedAt.
func (SoftDeleteInput) Validate ¶
func (in SoftDeleteInput) Validate() error
Validate checks the soft-delete request invariants: the If-Match ETag is required (and is the only accepted precondition), the acting principal is mandatory, and the reason is required and bounded.