Documentation
¶
Overview ¶
Package mutation implements the taskman-mutation/0 envelope of SPEC §3.3, its operation-specific closed payloads, the taskman-outcome/0 result, the pure post-record computation of every §3.3 operation (TM-V0-003, TM-V0-005), the request-ID replay rule of TM-V0-006 against an explicit index, and the §3.3 ADOPT_FILE composition (R2 F3, AS-35).
The package is pure: it depends on internal/wire, internal/ticket and internal/intent only. It reads no file, takes no lock, consults no clock and draws no randomness. Every fact it needs (the canonical record, the queue manifest, the policy, the inventory, attempt liveness, the request index, the logical timestamp and the trusted actor binding) is an explicit input, and every result is an explicit output: a Plan carrying the outcome, the planned post record and, for CREATE, the planned queue manifest. A Plan is never a receipt. Nothing here is durable, and a Plan with outcome COMPLETED means "this mutation would commit as the following post record"; the journal commit (receipt, request index entry, intent-file projection) is TCP-02/TCP-02b work and is the only thing that makes a mutation real.
Security boundary (owner-requested expert panel). The envelope's actor.role and actor.id are untrusted data supplied by whoever wrote the envelope; they are not authentication. Apply and Adopt therefore require a separate trusted Binding in the Context that names the role and id of the invoking principal, and refuse UNAUTHORIZED when the binding is absent or the envelope actor differs from it in either field. The binding is never derived from the envelope, from ticket text, from the policy or from any registration; it must be furnished by the caller's authority layer. This library can validate a binding it is given; it cannot authenticate the process that supplies it, and in particular it cannot distinguish two processes running under the same UID. There is no default OWNER binding and no privileged entry point: an empty Binding fails closed. The runtime enforcement profile that decides who may furnish which binding remains qualification work outside this package.
Index ¶
- Constants
- Variables
- func AdoptDigest(b Binding, queueID wire.QueueID, targetID wire.TicketID, requestID string, ...) wire.Digest
- func ParseRequestID(where, s string) (string, error)
- func PayloadValue(p Payload) wire.Value
- type Actor
- type Binding
- type CompleteManualPayload
- type Context
- type CreatePayload
- type Envelope
- type GrantApprovalPayload
- type HoldPayload
- type IndexEntry
- type MemoryIndex
- type Outcome
- type Payload
- type Plan
- type PrioritizePayload
- type ReasonPayload
- type RefinePayload
- type ReleaseHoldPayload
- type RequestIndex
- type RevokeApprovalPayload
- type SetDependenciesPayload
- type SetEffectsPayload
- type SetGatesPayload
Constants ¶
const ( OpCreate = "CREATE" OpRefine = "REFINE" OpPrioritize = "PRIORITIZE" OpSetDependencies = "SET_DEPENDENCIES" OpSetGates = "SET_GATES" OpSetEffects = "SET_EFFECTS" OpHold = "HOLD" OpReleaseHold = "RELEASE_HOLD" OpReopen = "REOPEN" OpArchive = "ARCHIVE" OpRestore = "RESTORE" OpCompleteManual = "COMPLETE_MANUAL" OpGrantApproval = "GRANT_APPROVAL" OpRevokeApproval = "REVOKE_APPROVAL" )
Operation names (§3.3).
const ( OutcomeCompleted = "COMPLETED" OutcomeRevisionConflict = "REVISION_CONFLICT" OutcomeValidationFailed = "VALIDATION_FAILED" OutcomeBlocked = "BLOCKED" OutcomeUnsupported = "UNSUPPORTED" OutcomeCapacityExhausted = "CAPACITY_EXHAUSTED" OutcomeUncertainEffect = "UNCERTAIN_EFFECT" OutcomeRequestIDConflict = "REQUEST_ID_CONFLICT" OutcomeStorageFailed = "STORAGE_FAILED" )
Outcome values of taskman-outcome/0 (§3.3), closed.
const AdoptOperation = "ADOPT_FILE"
AdoptOperation names the reconcile operation in the adoption request digest preimage (§3.3 "ADOPT_FILE request digest").
const OutcomeProfile = "taskman-outcome/0"
OutcomeProfile is the taskman-outcome/0 profile (§3.3).
const Profile = "taskman-mutation/0"
Profile is the mutation envelope profile (§3.3).
Variables ¶
var AdoptProtectedFields = []string{
"status", "archivedFrom", "completion", "approvals", "revision", "acceptanceRevision",
"previousRecordSha256", "source", "shadowOverlay", "supersededBy", "createdAt", "updatedBy",
}
AdoptProtectedFields are the record fields a diverged intent file may never change through ADOPT_FILE (§3.3). A difference in any of them refuses VALIDATION_FAILED/ADOPT_UNSUPPORTED_FIELD and leaves the ticket diverged. `status` is listed but is derived, never adopted: a difference is tolerated only between the live statuses DRAFT, OPEN and HELD, and the composed operations (REFINE opening a DRAFT, HOLD/RELEASE_HOLD) decide the post status; see adoptStatusCovered. COMPLETED and ARCHIVED can neither be entered nor left by adoption.
var Outcomes = []string{ OutcomeCompleted, OutcomeRevisionConflict, OutcomeValidationFailed, OutcomeUnauthorized, OutcomeBlocked, OutcomeUnsupported, OutcomeCapacityExhausted, OutcomeUncertainEffect, OutcomeRequestIDConflict, OutcomeStorageFailed, }
Outcomes is the closed outcome vocabulary.
var PayloadKeys = map[string][]string{ OpCreate: createKeys, OpRefine: RefineFields, OpPrioritize: {"priority", "order"}, OpSetDependencies: {"dependencies"}, OpSetGates: {"requiredGates"}, OpSetEffects: {"effects", "capabilities", "executionClass"}, OpHold: {"holdId", "reason"}, OpReleaseHold: {"holdId"}, OpReopen: {"reason"}, OpArchive: {"reason"}, OpRestore: {"reason"}, OpCompleteManual: {"reason", "evidence"}, OpGrantApproval: {"grantId", "actor", "operation", "targetRevision", "scope"}, OpRevokeApproval: {"grantId", "reason"}, }
PayloadKeys is each operation's closed payload key set, which decodePayload enforces and help prints. CREATE also takes an optional localToken, and REFINE takes a non-empty subset of its keys.
var RefineFields = []string{
"acceptanceCriteria", "body", "dueDate", "estimateMinutes", "kind", "labels",
"milestone", "owner", "requirementRefs", "supersedes", "title",
}
RefineFields are the keys a REFINE payload may carry (§3.3), sorted.
Functions ¶
func AdoptDigest ¶
func AdoptDigest(b Binding, queueID wire.QueueID, targetID wire.TicketID, requestID string, file []byte) wire.Digest
AdoptDigest is the TM-V0-006 request digest of an ADOPT_FILE request (§3.3 "ADOPT_FILE request digest"): the SHA-256 of the canonical encoding, trailing LF included, of the closed object
{actor:{id,role}, fileSha256, operation:"ADOPT_FILE", queueId, requestId, targetId}
where actor is the trusted Binding (never the file's updatedBy or any claim), fileSha256 is the SHA-256 of the exact file bytes offered, queueId is the context queue and targetId the canonical record's ticketId. The preimage binds the operation, the request, the reconciling principal, the queue, the target and the bytes, and nothing that moves between an original and its retry (no timestamp, no current revision): an identical retry after commit replays, while the same request ID reused by another actor or role, for another target or queue, or with other bytes conflicts.
func ParseRequestID ¶
ParseRequestID validates a request ID supplied outside an envelope (the ADOPT_FILE request).
func PayloadValue ¶
PayloadValue renders a payload as its closed wire object.
Types ¶
type Actor ¶
Actor is the envelope's untrusted actor claim. It is compared against the trusted Binding of the Context and never used on its own.
type Binding ¶
Binding is the trusted invocation actor: the role and id of the principal that invoked the tool, as established by the caller's authority layer. It is deliberately a separate type from Actor so that it can never be filled from an envelope by accident. An empty Binding is not "anonymous" and not "OWNER"; it fails closed with UNAUTHORIZED.
Limit: this package validates the binding it is handed (well-formed label, known role, equal to the envelope claim). It cannot authenticate the process that handed it over, and it cannot tell two processes under the same UID apart. Deciding who may furnish which binding is the runtime enforcement profile's job (qualification work), not this library's.
type CompleteManualPayload ¶
CompleteManualPayload is {reason, evidence}.
type Context ¶
type Context struct {
// Binding is the trusted actor (see Binding). Required.
Binding Binding
// Queue is the current queue manifest (canonical record). Required.
Queue *intent.Queue
// Policy is the current policy (roles row removal, gate ids). Required.
Policy *intent.Policy
// Inventory holds every current ticket record of the queue, including
// tombstones, so that ID membership, dependency existence and cycles
// are checked against the full set (TM-V0-005). Required.
Inventory *ticket.Inventory
// Attempts answers attempt liveness (§3.2). A nil oracle or a
// NOT_OBSERVED answer is treated as "possibly live": an acceptance-
// relevant mutation is then refused BLOCKED/ATTEMPT_LIVE (fail closed).
Attempts ticket.AttemptOracle
// Requests is the request-ID index consulted for TM-V0-006 replay.
// Required: a nil index is refused VALIDATION_FAILED/MALFORMED before any
// computation, never treated as "no prior request" (a Context built
// without its index would otherwise commit every retry twice).
Requests RequestIndex
// Now is the logical timestamp recorded as updatedAt, createdAt,
// placedAt, grantedAt and recordedAt. Advisory only (§2).
Now wire.Timestamp
}
Context carries every fact a mutation depends on. All of it is explicit input; nothing is read from disk, a clock or the environment.
type CreatePayload ¶
type CreatePayload struct {
LocalToken *string
Title string
Body *string
Kind string
Owner *string
Milestone *string
Priority string
Order wire.Count
Labels []string
Dependencies []ticket.Dependency
AcceptanceCriteria []string
RequirementRefs []string
Source ticket.Source
Effects ticket.Effects
Capabilities []string
RequiredGates []string
ExecutionClass string
DueDate *string
EstimateMinutes *wire.Count
Supersedes *wire.TicketID
SupersededBy *wire.TicketID
}
CreatePayload is the CREATE payload: the full record minus the fields the tool derives (ticketId, revision, acceptanceRevision, previousRecordSha256, status, archivedFrom, completion, holds, approvals, createdAt, updatedAt, updatedBy, shadowOverlay), plus an optional localToken.
type Envelope ¶
type Envelope struct {
RequestID string
Actor Actor
QueueID wire.QueueID
TargetID *wire.TicketID // nil only for CREATE
ExpectedRevision *wire.Count // nil only for CREATE
Operation string
Payload Payload
IssuedAt wire.Timestamp
// Raw is the exact envelope bytes as decoded (canonical body plus LF).
// The TM-V0-006 request digest is the SHA-256 of these bytes.
Raw []byte
}
Envelope is a decoded taskman-mutation/0.
func Decode ¶
Decode parses and validates one mutation envelope (canonical bytes with trailing LF, ≤256 KiB). Every key is checked against the closed schema of §3.3, the payload against the closed table for its operation, and the nullity rule for targetId/expectedRevision. Decoding trusts nothing: the actor claim is carried through for comparison with the Binding by Apply.
type GrantApprovalPayload ¶
type GrantApprovalPayload struct {
GrantID string
Actor string
Operation string
TargetRevision wire.Count
Scope []string
}
GrantApprovalPayload is a grant minus revoked and grantedAt. The grant's actor must equal the trusted Binding id.
type HoldPayload ¶
HoldPayload is {holdId, reason}. The hold's actor and placedAt are never taken from the payload: they come from the trusted Binding and the logical timestamp of the Context.
type IndexEntry ¶
IndexEntry is one recorded request: the request ID, the SHA-256 of the canonical mutation bytes and the outcome recorded with it (TM-V0-006).
type MemoryIndex ¶
type MemoryIndex struct {
// contains filtered or unexported fields
}
MemoryIndex is the explicit in-memory fake used by TCP-01 tests to exercise the TM-V0-006 replay and conflict rules (AS-03). It is never durable, never shared and never evidence of a commit: a test that records an entry here is simulating the transaction the journal would perform, nothing more.
func NewMemoryIndex ¶
func NewMemoryIndex() *MemoryIndex
NewMemoryIndex returns an empty fake index.
func (*MemoryIndex) Len ¶
func (m *MemoryIndex) Len() int
Len returns the number of recorded requests.
func (*MemoryIndex) Lookup ¶
func (m *MemoryIndex) Lookup(requestID string) (IndexEntry, bool, error)
Lookup implements RequestIndex.
func (*MemoryIndex) Record ¶
func (m *MemoryIndex) Record(e IndexEntry) error
Record stores an entry as the journal would in the same transaction as its receipt. A second record under the same request ID is refused: the index is append-only.
type Outcome ¶
type Outcome struct {
RequestID string
Outcome string
Replayed bool
ResultingRevision *wire.Count
ResultingAcceptanceRevision *wire.Count
ReleaseID *string
ResultingReleaseRevision *wire.Count
ReceiptSeq *wire.Size
Codes []string
}
Outcome is a taskman-outcome/0 document. ReceiptSeq is nil for every outcome this package produces: a planned result has no receipt. Only the journal commit of TCP-02b fills it, and a Replayed outcome carries whatever the recorded original carried.
func DecodeOutcome ¶
DecodeOutcome parses and validates a taskman-outcome/0 (≤64 KiB).
func (*Outcome) Encode ¶
Encode validates the outcome against its closed schema and the §1 bound and returns the transport bytes.
type Payload ¶
type Payload interface {
// contains filtered or unexported methods
}
Payload is one operation's closed payload (§3.3 table).
type Plan ¶
type Plan struct {
Outcome Outcome
MutationSha256 wire.Digest // SHA-256 of the canonical mutation bytes (TM-V0-006)
Detail string // human explanation of a refusal; never queue prose
Pre *ticket.Record
Post *ticket.Record // non-nil iff Outcome is a fresh COMPLETED
QueuePost *intent.Queue // non-nil iff CREATE allocated a serial
Composed []string // ADOPT_FILE: the composed operations in order
}
Plan is the pure result of validating and computing one mutation. It is explicitly uncommitted: Outcome.ReceiptSeq is nil, no file changed, no receipt exists, and the request index was only read. A Plan with outcome COMPLETED says what the post record would be; the transaction writer of TCP-02b decides whether it becomes real.
func Adopt ¶
Adopt computes the §3.3 ADOPT_FILE composition (R2 F3): the per-field difference between a diverged intent file and the canonical record is mapped, in the fixed order of the contract table, onto composed operations that are each validated by the same step rules as if the reconciling actor had issued them, and the whole composition is applied as one revision. The result is a Plan whose Post is the single RECONCILE post record: revision +1 exactly once, acceptanceRevision +1 iff an acceptance-relevant field changed, previousRecordSha256 chained from the canonical record and never from the file. Nothing is written; the ticket stays diverged until the transaction writer commits the plan.
The reconciling actor is the trusted Binding (OWNER or OPERATOR); the file's `updatedBy` and each hold's `actor`/`placedAt` are never adopted. The request digest is AdoptDigest, computed from the immutable request identity (binding, queue, canonical ticketId, requestId, file bytes) before the replay question is asked. A tombstone accepts only RESTORE (§3.2), so an ARCHIVED canonical record refuses BLOCKED/TICKET_STATE before anything is composed: not even an empty or updatedAt-only adoption may mint a revision on it.
type PrioritizePayload ¶
PrioritizePayload is {priority, order}.
type ReasonPayload ¶
ReasonPayload is {reason} for REOPEN, ARCHIVE and RESTORE.
type RefinePayload ¶
type RefinePayload struct {
Present map[string]bool
Title string
Body *string
Kind string
Owner *string
Milestone *string
Labels []string
AcceptanceCriteria []string
RequirementRefs []string
DueDate *string
EstimateMinutes *wire.Count
Supersedes *wire.TicketID
}
RefinePayload is a non-empty subset of RefineFields. Present names the keys carried; a typed field is meaningful only when its key is present.
func (*RefinePayload) Has ¶
func (p *RefinePayload) Has(key string) bool
Has reports whether the payload carries the key.
func (*RefinePayload) Keys ¶
func (p *RefinePayload) Keys() []string
Keys returns the present keys in sorted order.
type ReleaseHoldPayload ¶
type ReleaseHoldPayload struct {
HoldID string
}
ReleaseHoldPayload is {holdId}.
type RequestIndex ¶
type RequestIndex interface {
Lookup(requestID string) (IndexEntry, bool, error)
}
RequestIndex answers whether a request ID has already been recorded. The durable implementation is the journal's requests/ index (TCP-02); this package only consults whatever it is given. Only false,nil proves absence; a lookup error refuses before a fresh plan is computed.
type RevokeApprovalPayload ¶
RevokeApprovalPayload is {grantId, reason}.
type SetDependenciesPayload ¶
type SetDependenciesPayload struct {
Dependencies []ticket.Dependency
}
SetDependenciesPayload is the full replacement of dependencies.
type SetEffectsPayload ¶
type SetEffectsPayload struct {
Effects ticket.Effects
Capabilities []string
ExecutionClass string
}
SetEffectsPayload is the full replacement of effects, capabilities and executionClass.
type SetGatesPayload ¶
type SetGatesPayload struct {
RequiredGates []string
}
SetGatesPayload is the full replacement of requiredGates.