modprotov2

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 16, 2026 License: MIT Imports: 10 Imported by: 0

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

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

func Evaluate(describeOutput []byte) (Decision, error)

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

func (d Decision) MayRelyOnV2() bool

MayRelyOnV2 reports whether v2 semantics are available. Delegates to the pin rather than re-deriving, so the two cannot disagree.

func (Decision) Served

func (d Decision) Served() bool

Served reports whether the host may talk to this module at all.

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

type Finding struct {
	Code   string
	Where  string
	Reason string
	Remedy string
}

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.

func (Finding) String

func (f Finding) String() string

type Grants

type Grants struct {
	Network       []string `json:"network"`
	Credentials   []string `json:"credentials"`
	PaidProviders []string `json:"paid_providers"`
	Publish       bool     `json:"publish"`
	Subprocess    []string `json:"subprocess"`
}

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

func (m MayCharge) Evaluate(input map[string]any) bool

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

func (m MayCharge) IsConditional() bool

IsConditional reports whether this declaration depends on a request field.

func (MayCharge) MarshalJSON

func (m MayCharge) MarshalJSON() ([]byte, error)

MarshalJSON emits the plain boolean when unconditional and the object form otherwise, which is what the frozen shape shows.

func (MayCharge) NoWeakerThan

func (m MayCharge) NoWeakerThan(other MayCharge) bool

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

func (m *MayCharge) UnmarshalJSON(b []byte) error

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 Root

type Root struct {
	Path string `json:"path"`
	Mode string `json:"mode"`
}

type Skill

type Skill struct {
	ID     string `json:"id"`
	Title  string `json:"title"`
	Path   string `json:"path"`
	Digest string `json:"digest"`
}

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.

const (
	StateSatisfied   State = "satisfied"
	StateUnsatisfied State = "unsatisfied"
	StateUnknown     State = "unknown"
)

func (State) Valid

func (s State) Valid() bool

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.

const (
	StrengthMandatory Strength = "mandatory"
	StrengthPreferred Strength = "preferred"
)

type Validator

type Validator struct {
	Type   string `json:"type"`
	Schema string `json:"schema"`
}

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.

Jump to

Keyboard shortcuts

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