Documentation
¶
Overview ¶
Package modprotov2 is the wire representation of the frozen xibodev.module/v2 behavioural contract.
SEPARATE PACKAGE FROM modproto, DELIBERATELY. modproto is xibodev.module/v1: immutable legacy behaviour that nothing may be added to. A module speaks one wire or the other, never both, and putting v2 fields on v1 structs would be the "smuggling successor semantics into a v1 exchange" that final ruling 5 forbids -- tolerant decoding is not permission.
WHAT THIS PACKAGE IS AND IS NOT. It carries the frozen shape (docs/architecture/WIRE_V2_PROPOSAL.md, wire shape 031a89d07f6dfd12) and the mechanical checks the contract requires. It does NOT decide policy: whether a host gates on a given effect, how it renders an artifact, or what it does with a refusal, all live above this package. The contract says what may be said; the host says what to do about it.
EVERY FIELD TRACES TO A FROZEN CLAUSE. The proposal's own rule is that anything not traceable to a specific v1 gap should be cut, and it was applied against this author: `long_running` was in the draft, appeared zero times in the frozen RFC, and is cut (§1b). It remains in v1 on modproto.Capability.LongRunning, which is untouched.
Index ¶
- func RenderMediaType(d *Descriptor, a Artifact) string
- type Accepted
- type Approval
- type ApprovalRecord
- type Artifact
- type ArtifactKind
- type Capability
- type Decision
- type Descriptor
- type Effects
- type Envelope
- type Error
- type ExecProps
- type Execution
- type Finding
- type Grants
- type Kind
- type MayCharge
- type Operation
- type Overlay
- type Permissions
- type Request
- type Requirement
- type Resolution
- type Root
- type Skill
- type State
- type Strength
- type Validator
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RenderMediaType ¶
func RenderMediaType(d *Descriptor, a Artifact) string
RenderMediaType is the media type the host should use to pick a renderer.
It comes from the DECLARATION rather than from sniffing, which is the point of declaring it. A module observed declaring image/png for JPEG bytes is why the host still treats the value as a claim: this returns what was declared, and the caller's renderer degrades gracefully when a claim is wrong.
Returns "" for an undeclared kind rather than guessing. CheckArtifacts reports that as a finding; silently substituting a default here would make an undeclared kind render as though it had been declared.
Types ¶
type Accepted ¶
type Accepted struct {
// contains filtered or unexported fields
}
Accepted is a descriptor whose contract pin PASSED. It is the only way to obtain v2 semantics from this package.
THE ORDERING IS STRUCTURAL, NOT DOCUMENTED. The operator's requirement is that the contract gate execute before the host relies on any v2 semantic declaration. A comment saying "call CheckPin first" is exactly the kind of prose guarantee this contract exists to stop shipping: it holds until someone adds a call site that does not read it.
So the v2 descriptor is unexported inside this type and reachable only through a constructor that runs the pin. A caller CANNOT hold v2 Operations without having passed the gate, because there is no other way to build one.
func (Accepted) Descriptor ¶
func (a Accepted) Descriptor() *Descriptor
Descriptor returns the accepted v2 descriptor. Reaching this value at all proves the pin passed.
type Approval ¶
type Approval struct {
// RequiredWhen holds reasons from the legal set: "chargeable",
// "irreversible", "external_write", "product_checkpoint".
RequiredWhen []string `json:"required_when"`
}
Approval says WHY approval is required, not merely that it is (§6).
type ApprovalRecord ¶
type ApprovalRecord struct {
Granted bool `json:"granted"`
Reason string `json:"reason"`
// GrantedBy is never "model". An approval minted by a model is stripped by
// the host before this point: the agent cannot approve spending on the
// operator's behalf, and a field that could carry such a claim would make
// the gate bypassable by the thing it gates.
GrantedBy string `json:"granted_by"`
}
ApprovalRecord is the host's statement about an approval it obtained.
type Artifact ¶
type Artifact struct {
ID string `json:"id"`
Kind string `json:"kind"`
Root string `json:"root"`
Path string `json:"path"`
Bytes int64 `json:"bytes"`
Digest string `json:"digest"`
// Presentation names a rendering primitive the HOST owns. An unknown value
// is ignored rather than honoured.
Presentation string `json:"presentation,omitempty"`
}
Artifact is a produced file, referencing a declared kind rather than restating it.
type ArtifactKind ¶
type ArtifactKind struct {
Kind Kind `json:"kind"`
MediaType string `json:"media_type"`
Validator *Validator `json:"validator,omitempty"`
}
ArtifactKind declares one produced artifact type.
type Capability ¶
type Capability struct {
ID string `json:"id"`
Title string `json:"title"`
Summary string `json:"summary"`
RequestSchema string `json:"request_schema"`
ResultSchema string `json:"result_schema"`
// Projects names the Operations this capability surfaces. EMPTY IS LEGAL
// and is not a defect -- see the type doc.
Projects []string `json:"projects"`
// Effects are declared SEPARATELY from the projected Operations' effects,
// in the SAME SHAPE, so the no-weakening comparison is mechanical rather
// than a translation. A translation is where a weakening hides.
Effects Effects `json:"effects"`
}
Capability is an invocable surface.
type Decision ¶
type Decision struct {
Pin contractv2.PinResult
// V2 is set only when the pin matched exactly.
V2 *Accepted
// V1 is set only when NO contract version was declared. That module is
// served under v1 with v1 behaviour and NO v2 guarantee.
V1 *modproto.Descriptor
}
Decision is what a host may do with a counterparty, and it carries the three-outcome evaluation rather than a boolean.
The three states are EVALUATOR state, never transmitted (§4 of the wire proposal). The wire carries exactly one value -- contract_version -- and the outcome is what the host CONCLUDES. A transmitted "state" field would let a module assert its own evaluation, which is the thing the evaluation exists to decide.
func Evaluate ¶
Evaluate is THE entry point for a described module, and the only one.
It reads contract_version from the raw describe output, runs the pin, and returns a Decision whose shape makes the version matrix inescapable:
absent -> V1 set, V2 nil served, no v2 guarantee xibodev.module/v2 -> V2 set, V1 nil served, v2 semantics xibodev.module/v1 -> both nil REFUSED (wire identity) anything else -> both nil REFUSED
No negotiation, no downgrade, no fallback from a wrong explicit value. A caller that wants v2 must check V2 != nil; there is no way to obtain the v2 descriptor from a refusal because the field is simply not set.
func (Decision) MayRelyOnV2 ¶
MayRelyOnV2 reports whether v2 semantics are available. Delegates to the pin rather than re-deriving, so the two cannot disagree.
type Descriptor ¶
type Descriptor struct {
// Protocol is the WIRE format identity. ContractVersion is the BEHAVIOURAL
// contract. Conflating them is the axis error that made v1's version list
// look like negotiation, so both are carried and neither is derived from
// the other.
Protocol string `json:"protocol"`
ContractVersion string `json:"contract_version"`
Module string `json:"module"`
Name string `json:"name"`
Version string `json:"version"`
// Operations are LAYER 1: canonical semantic units. Their effects are the
// product truth a capability's declaration must not weaken (§2, §9).
//
// v1 has no equivalent at all -- Capability was the only unit -- so the
// no-weakening check had nothing to compare against. That absence is the
// gap this field closes.
Operations []Operation `json:"operations"`
// Capabilities are LAYER 2: what a host invokes. The gate reads THESE
// effects (§2a rule 2), which is why they must be no weaker than every
// Operation projected.
Capabilities []Capability `json:"capabilities"`
// ArtifactKinds declares produced artifacts BY KIND (§7). Not all JSON:
// v1's artifact_schemas implied a JSON Schema for everything and validated
// nothing, which meant an mp4 was undeclarable without implying a
// validator that cannot exist.
ArtifactKinds map[string]ArtifactKind `json:"artifact_kinds"`
RequestSchemas map[string]json.RawMessage `json:"request_schemas"`
ResultSchemas map[string]json.RawMessage `json:"result_schemas"`
Permissions Permissions `json:"permissions"`
AgentOverlays []Overlay `json:"agent_overlays"`
Skills []Skill `json:"skills"`
}
Descriptor is what `module describe --json` returns under v2.
TWO LAYERS, AND THE PROJECTION IS NOT 1:1 IN EITHER DIRECTION (§2). Operations are semantic units derived by contract; Capabilities are what a host actually invokes. One Operation may surface through several capabilities, and one capability may reach many Operations -- Facet's creative.tools.run reaches any of 35. A capability may also project NONE: a registry read transforms no product material and is still invocable, so it still declares effects.
type Effects ¶
type Effects struct {
Network bool `json:"network"`
ExternalWrites bool `json:"external_writes"`
Provider string `json:"provider"`
// MayCharge is chargeability, INDEPENDENT of whether an amount is known.
//
// THE GAP THIS CLOSES. v1 had only cost_known, which means "is a number
// known", and this host gated on it as a proxy for spending. The two are
// genuinely independent: one of Facet's tools declares cost_known TRUE and
// network TRUE and requires no consent, and Midden's estimate capability --
// whose entire purpose is checking cost BEFORE a paid run -- declared
// cost_known false while its own summary said it never bills. Gating the
// cost-checking tool as possibly-billing discourages the one behaviour that
// makes a cost gate work.
MayCharge MayCharge `json:"may_charge"`
// CostKnown declares whether a NUMBER is known. Never a proxy for
// spending; that is what MayCharge is for.
CostKnown bool `json:"cost_known"`
// Deterministic: same input, same bytes (§4).
//
// MUST be false if Network or MayCharge could be true. Enforced in
// Validate rather than documented, because a determinism claim that is
// merely asserted is the kind of guarantee this contract exists to stop
// shipping as prose.
Deterministic bool `json:"deterministic"`
}
Effects is the declared effect profile, used at BOTH layers in the SAME SHAPE (§2). Identical shape is what makes the no-weakening comparison mechanical; different shapes at the two layers would make it a translation, and a translation is where a weakening hides.
type Envelope ¶
type Envelope struct {
Protocol string `json:"protocol"`
ContractVersion string `json:"contract_version"`
Module string `json:"module"`
Operation string `json:"operation"`
RequestID string `json:"request_id"`
OK bool `json:"ok"`
Result json.RawMessage `json:"result,omitempty"`
Warnings []string `json:"warnings"`
Execution *Execution `json:"execution,omitempty"`
Error *Error `json:"error,omitempty"`
}
Envelope is one completed v2 invocation.
type Error ¶
type Error struct {
Code string `json:"code"`
Reason string `json:"reason"`
Remedy string `json:"remedy"`
Retryable bool `json:"retryable"`
Details map[string]any `json:"details,omitempty"`
}
Error carries a REASON and a REMEDY as separate fields.
THE ONE v1 SHAPE THAT CHANGES. v1 has Message only. §8 requires both, and the justification is concrete rather than stylistic: a refusal that says only what is wrong sends the reader to source, and that is precisely how two sibling lanes ended up inferring guarantees nobody had written. The remedy is where a reader forms an expectation, so it has to close the wrong ones explicitly.
type ExecProps ¶
type ExecProps struct {
// DeadlineMSDefault bounds the WHOLE invocation, wall-clock, from process
// start (§8).
DeadlineMSDefault int `json:"deadline_ms_default"`
}
ExecProps bounds an invocation.
`long_running` is NOT here. It was in the draft and is cut: it appears zero times in the frozen RFC, and it told a host THAT something takes a while while giving it nothing to do about it -- no handle, no poll verb, no resume, all out of scope. Facet's poll_scope gap is real (a host polling an expired in-memory handle gets unknown_job, indistinguishable from a job that never existed) and is recorded as the first successor item rather than smuggled in as a field for an object v2 cannot express.
type Execution ¶
type Execution struct {
Network bool `json:"network"`
ExternalWrites bool `json:"external_writes"`
Provider string `json:"provider"`
// Charged is what happened, distinct from may_charge which is what was
// declared possible.
Charged bool `json:"charged"`
// EstimatedCost and ActualCost are POINTERS on purpose. null means
// genuinely UNKNOWN; 0 means genuinely free. Collapsing them would let an
// unpriced provider call render as free and slip past a cost gate.
EstimatedCost *float64 `json:"estimated_cost"`
ActualCost *float64 `json:"actual_cost"`
// Resolution is requirements as evaluated at runtime, three-valued (§5).
Resolution []Resolution `json:"resolution"`
Artifacts []Artifact `json:"artifacts"`
}
Execution is what ACTUALLY happened, reported separately from what was declared -- which is the point of reporting it, since the host compares.
type Finding ¶
Finding is one conformance violation, carrying reason AND remedy for the same reason Error does: a refusal that says only what is wrong sends the reader to source.
func CheckArtifacts ¶
func CheckArtifacts(d *Descriptor, arts []Artifact, rootPath func(root string) (string, bool)) []Finding
CheckArtifacts verifies produced artifacts against what the descriptor declared, and is the "Value Contract" half of the frozen shape.
WHAT v1 COULD NOT DO. artifact_schemas mapped every artifact to a JSON Schema and validated NONE of them -- wrong in two directions at once: nothing was enforced, and an mp4 was undeclarable without implying a validator that cannot exist. So this is the first point where a produced artifact is checked against its own declaration at all.
VALIDATION DIFFERS BY KIND, and that is the whole point of section 7 rather than an implementation convenience:
media -- media type, size and digest. NEVER a JSON Schema.
text -- media type and digest. An honest "no validator exists".
document -- must name a validator, and that validator must check the
artifact's OWN CONTENT.
WHAT THIS DOES NOT DO, stated because a checker that silently covers less than its name suggests is the defect this contract keeps finding: it does not EXECUTE a document's validator. Running a JSON Schema needs a schema evaluator, and the frozen RFC's open question -- whether v2 enforces artifact validation at all -- is unruled. So a document artifact is checked for having a validator declared and for its bytes matching digest and size; whether the content SATISFIES the schema is not checked here and is not claimed.
func CheckNoWeakening ¶
func CheckNoWeakening(d *Descriptor) []Finding
CheckNoWeakening closes the triangle:
product semantic declaration -> v2 projection -> host interpretation
THIS IS THE ONE RULE WHOSE VIOLATION IS INVISIBLE AT THE LAYER BEING REVIEWED (§9). The Operation reads correct, while the gate -- which reads CAPABILITY effects -- fires on nothing. A reviewer looking at the Operation sees honest semantics and no problem. So a rule against a silent failure that was itself only checked by reading would be the same defect one level up, which is why this is mechanical.
It compares field by field in the SAME SHAPE at both layers. Different shapes would make this a translation, and a translation is where a weakening hides.
func Validate ¶
func Validate(d *Descriptor) []Finding
Validate checks a v2 descriptor against every frozen clause that is mechanically checkable, and returns findings rather than a single error so a module author sees ALL of them at once.
SCOPE, stated because a validator that silently covers less than its name suggests is the defect this contract keeps finding. This checks declarations against each other. It does NOT open artifact files, run validators, or probe requirements -- those need a real invocation and live in the conformance suite that runs against a binary.
type Kind ¶
type Kind string
Kind classifies an artifact by what CAN be checked about it (§7).
THE GAP THIS CLOSES. v1's artifact_schemas mapped every artifact to a JSON Schema and validated none of them. That is wrong in two directions at once: nothing was enforced, and an mp4 was undeclarable without implying a validator that cannot exist. Facet raised the second; it is what created §7.
const ( // KindDocument has a validator that validates the artifact's OWN CONTENT. KindDocument Kind = "document" // KindText has no validation contract beyond media type and digest. This // is an HONEST statement that no validator exists, not a gap -- Midden's // markdown artifacts are exactly this today. KindText Kind = "text" // KindMedia is validated by media type, size and digest. NEVER by JSON // Schema. Naming a validator here is an ERROR rather than an omission, // because a JSON Schema that "validates" an mp4 is a claim nothing can // honour. KindMedia Kind = "media" )
type MayCharge ¶
type MayCharge struct {
// Always is the plain boolean form. When Field is empty this is the whole
// declaration.
Always bool
// Field names a request field to evaluate. Empty means unconditional.
Field string
// WhenIn lists values of Field for which this charges.
WhenIn []string
// Default is the answer for a value not in WhenIn, or when the field
// cannot be resolved.
//
// §3a requires this to be TRUE for an unlisted or unresolvable value, and
// Validate enforces it. Midden verified the sequencing rather than
// assuming it: content.produce REFUSES a request with no kind, so an
// unevaluable condition resolves true, approval is sought, the request
// arrives, and the module refuses deterministically -- over-gate then
// refusal, which is annoying and strictly safe. The opposite default would
// send an UNAPPROVED request whose kind might have been chargeable.
Default bool
}
MayCharge is either a plain bool or an argument-conditional form (§3a).
WHY CONDITIONAL AT ALL. Midden's produce_content charges for 12 of 19 kinds, resolved from a runtime registry. Without this, expressing that honestly would force 19 Operations onto the wire -- a delivery detail becoming product semantics, which final ruling 10 forbids -- or permanently over-gate 7 free kinds.
AND WHY IT IS LEGAL AT THE CAPABILITY LAYER TOO (§2a). The first draft put the conditional on the Operation and a collapsed boolean on the capability. For kind=retrieval_pack that yields TWO DECLARED ANSWERS FOR ONE INVOCATION: the Operation evaluates false, the capability reads true, and the measured truth is false. Worse, since the gate reads capability effects, §3a became expressible where nothing reads it and unreachable where something does.
func (MayCharge) Evaluate ¶
Evaluate answers the chargeability question for ONE invocation, against the request the host is about to send (§2a rule 2).
An unevaluable condition is TRUE (§2a rule 5): a missing field, an unresolvable value, or a non-string value all mean charge. The safe direction is the one that asks a human about something free, never the one that spends without asking.
func (MayCharge) IsConditional ¶
IsConditional reports whether this declaration depends on a request field.
func (MayCharge) MarshalJSON ¶
MarshalJSON emits the plain boolean when unconditional and the object form otherwise, which is what the frozen shape shows.
func (MayCharge) NoWeakerThan ¶
NoWeakerThan reports whether m charges for at least every input that other charges for (§2a rule 3).
COMPARES CONDITIONALS, NOT THEIR COLLAPSES. A boolean true is the maximal condition and always satisfies this; a boolean false never does against a charging counterpart. Collapsing to true stays legal (§2a rule 4) -- Facet asked for that as a STAGING option, since a capability-layer conditional needs matching evaluation semantics on both sides and that is a thing to implement after freeze rather than assume during review.
func (*MayCharge) UnmarshalJSON ¶
UnmarshalJSON accepts both forms.
type Operation ¶
type Operation struct {
ID string `json:"id"`
Title string `json:"title"`
Summary string `json:"summary"`
Effects Effects `json:"effects"`
Execution ExecProps `json:"execution"`
// Requirements carry STRENGTH (§5). v1 could not say whether a missing
// dependency was fatal or merely degrading.
Requirements []Requirement `json:"requirements"`
// Approval carries a REASON (§6). v1 gated on one field, so a
// free-but-irreversible step was ungated entirely.
Approval Approval `json:"approval"`
// Produces names artifact kinds by key into Descriptor.ArtifactKinds.
Produces []string `json:"produces"`
}
Operation is a canonical semantic unit, NOT a delivery surface (final ruling 10). It is derived by contract from what the product means, so a module with one Operation reachable four ways declares one Operation and four capabilities.
type Overlay ¶
type Overlay struct {
ID string `json:"id"`
Title string `json:"title"`
Path string `json:"path"`
Digest string `json:"digest"`
}
Overlay and Skill are unchanged from v1.
type Permissions ¶
type Permissions struct {
FilesystemRead []string `json:"filesystem_read"`
FilesystemWrite []string `json:"filesystem_write"`
Network []string `json:"network"`
Credentials []string `json:"credentials"`
PaidProviders []string `json:"paid_providers"`
Publish bool `json:"publish"`
Subprocess []string `json:"subprocess"`
}
Permissions is unchanged from v1 in meaning: roots, grants and binaries work and §8 states them normatively, so v2 changes nothing about them.
type Request ¶
type Request struct {
Protocol string `json:"protocol"`
ContractVersion string `json:"contract_version"`
Capability string `json:"capability"`
RequestID string `json:"request_id"`
Input json.RawMessage `json:"input"`
Roots map[string]Root `json:"roots"`
Grants Grants `json:"grants"`
Binaries map[string]string `json:"binaries"`
DeadlineMS int `json:"deadline_ms"`
MaxOutputBytes int `json:"max_output_bytes"`
// Approval states WHETHER approval was obtained and FOR WHAT (§6).
//
// ABSENT MEANS NONE WAS OBTAINED, and a module MUST NOT infer approval
// from the field being missing. A pointer rather than a value type so that
// absence is representable at all: a zero-valued struct with Granted false
// says "asked and refused", which is a different fact from "never asked".
Approval *ApprovalRecord `json:"approval,omitempty"`
}
Request is one v2 invocation.
NO NEW AUTHORITY MECHANISM. Roots, grants and binaries are unchanged from v1 in mechanism: they are enforced, tested, and §8 states them normatively. Changing a working authority model while changing everything else would be the wrong risk, so v2 adds exactly two things -- the contract pin and the approval record.
type Requirement ¶
type Requirement struct {
// Kind is "binary", "version", "config", "env", or "credential".
Kind string `json:"kind"`
Name string `json:"name"`
Strength Strength `json:"strength"`
Detail string `json:"detail,omitempty"`
}
Requirement is a semantic precondition, declared with its strength.
type Resolution ¶
type Resolution struct {
Kind string `json:"kind"`
Name string `json:"name"`
State State `json:"state"`
Detail string `json:"detail,omitempty"`
}
Resolution is a requirement as EVALUATED at runtime.
type State ¶
type State string
State is a Resolution outcome. THREE states, never a bool (§5).
v1 had Requirement.Available bool -- two states for a three-state world. The missing one is UNKNOWN, and it is not a technicality: a credential that is present but never exercised is neither satisfied nor unsatisfied, and reporting it as either is a lie in a different direction. Facet has two providers in exactly this state.
`disabled` is NOT a Resolution state. It is USER INTENT ("do not use this even though it works"), and all three states here are statements about whether the thing WORKS. A lane with a four-state model would flatten disabled into unsatisfied and report a working provider as broken; Midden proposed keeping enablement product-side rather than widening the contract, and that is the right call.
func (State) Valid ¶
Valid reports whether s is one of the three legal states.
An unrecognised value is NOT quietly treated as unknown. "Unknown" is a deliberate claim a module makes about a probe it could not complete; an unparseable value is a module speaking a vocabulary this host does not have. Collapsing the second into the first would let a typo read as a considered answer.
type Strength ¶
type Strength string
Strength distinguishes a requirement that must hold from one that improves the result (§5).
v1 could not say this at all: Requirement had no strength field, so a host could not tell a missing dependency that blocks an Operation from one that degrades it. Midden's d2 is preferred (diagrams render without it, worse); an API key is mandatory.
type Validator ¶
Validator names how a document artifact's CONTENT is checked.
"Names a validator that RESOLVES" is not the bar, and that weaker wording shipped here first. Midden declared application/json as a document naming a real, present JSON Schema whose properties were kind, format and review -- it validated the artifact RECORD, never the bytes. A name-resolution check passes that; the intent fails. So conformance must check WHAT the validator validates, and the two failures are distinct: a validator that does not exist, and one that resolves but describes something else.