Shared predicate vocabulary
Termivar's evidence producers and deterministic reasoners share one typed
predicate vocabulary from termivar-core. The vocabulary prevents a producer and
consumer from silently assigning different meanings to duplicated string
literals while retaining the existing KnowledgePredicate wire format.
Descriptor contract
PredicateDescriptor is a static, validated description of a predicate. It
contains the namespace, name, and dotted diagnostic form and converts to the
canonical owned KnowledgePredicate through into_knowledge() or Into.
Descriptors deliberately do not implement Serde. Persisted evidence, facts, hypotheses, expressions, and rules continue to encode predicates as:
{
"namespace": "http.response",
"name": "status"
}
The vocabulary is therefore a source-level contract, not a second storage
schema. Custom KnowledgePredicate values remain supported.
Predicate families
HttpEvidencePredicate
This family describes raw observations made by HTTP evidence producers. Its fixed descriptors cover request method and URL, response status and metadata, bounded body observations, timing, cookie names, common response headers, and normalized rate-limit signals.
API reasoning consumes normalized protocol observations instead of searching raw header or URL strings:
| Predicate | Producer guarantee |
|---|---|
http.response.media-type |
One syntactically valid, lowercase media-type essence with parameters removed |
http.response.media-type-json-compatible |
Boolean indicating the validated json subtype or a +json structured suffix |
http.request.path-segment |
One bounded, non-empty URL path segment |
Invalid or ambiguous media types do not produce normalized media-type
evidence. The API profile compares these values exactly; it does not infer
GraphQL from a substring in raw Content-Type or request-URL evidence.
HttpEvidencePolicy::with_reliability() also rejects
ConfidenceScore::NONE: the current deterministic rules use configured
likelihoods and must not turn a zero-confidence producer signal into Bayesian
support.
The opt-in GraphQL surface review uses these same normalized media/path observations when ranking one exact-origin endpoint. Its operation correlation and bounded response-envelope classifier remain runtime contracts rather than new stringly predicates. Generic JSON therefore still cannot become GraphQL merely because the active review was enabled.
The response-header namespace remains open. Producers that validate and
lowercase a header name may use
HttpEvidencePredicate::response_header(name) instead of adding a constant for
every possible header. Common contracts such as Content-Type, Server,
Allow, WWW-Authenticate, and X-Powered-By have fixed descriptors.
WebKnowledgePredicate
This family names conclusions produced by standard web reasoning:
technology.web-server
technology.language
technology.framework
technology.ui-framework
authentication.mechanism
Evidence predicates and knowledge predicates are intentionally separate. For
example, http.header.server is an immutable observation, while
technology.web-server is an inferred claim.
ApiEvidencePredicate
API visibility evidence is classified by one of eight atomic predicates:
| Surface | Compared views | Result | Dotted predicate |
|---|---|---|---|
| JSON HTTP | UI/API | Different | api.visibility.json.ui-api.difference |
| JSON HTTP | UI/API | Equivalent | api.visibility.json.ui-api.equivalent |
| JSON HTTP | Authorization contexts | Different | api.visibility.json.authorization-context.difference |
| JSON HTTP | Authorization contexts | Equivalent | api.visibility.json.authorization-context.equivalent |
| GraphQL | UI/API | Different | api.visibility.graphql.ui-api.difference |
| GraphQL | UI/API | Equivalent | api.visibility.graphql.ui-api.equivalent |
| GraphQL | Authorization contexts | Different | api.visibility.graphql.authorization-context.difference |
| GraphQL | Authorization contexts | Equivalent | api.visibility.graphql.authorization-context.equivalent |
ApiEvidencePredicate::visibility(surface, pair, result) selects the complete
classification without reconstructing a predicate string.
ApiKnowledgePredicate
Standard API reasoning writes only these claim predicates:
| Predicate | Meaning |
|---|---|
api.response-format |
Observed representation, currently json |
api.surface.kind |
Inferred API family: json-http-api or graphql-api |
api.visibility.boundary |
Reviewable UI/API or authorization-context visibility boundary |
The typed values ApiResponseFormat, ApiSurfaceKind, and
ApiVisibilityBoundaryKind provide their stable values. A response format is
not an API-family conclusion: JSON content alone does not establish GraphQL or
even prove that a generic JSON response is an API.
Atomic visibility comparisons
ApiVisibilityComparison represents one comparison already performed by an
authorized host component. It records:
- an opaque comparison ID;
- the JSON HTTP or GraphQL surface;
- UI/API or authorization-context pairing;
DifferentorEquivalentresult;- resources, fields, or status dimension;
- opaque baseline, candidate, and resource-scope handles.
- the host observation timestamp used for exact replay.
The constructor rejects empty handles and bounds every opaque value to 256 bytes. These fields are handles, not storage for credentials, tokens, URLs, principal names, response values, or resource names.
to_observation() is the normal bundled-storage path. It returns an
ApiVisibilityObservation containing exactly one immutable evidence record
and one stable evidence-backed graph relation. The result is encoded in the
evidence predicate, the measured dimension is its value, and its kind is
api.visibility-comparison.
The relation starts at the comparison subject, ends at the host's opaque
resource-scope EntityId, and uses the custom kind
api.visibility.resource-scope. Its deterministic
api-comparison-scope:<digest> identity and sole provenance ID are derived
from the same comparison evidence. ApiVisibilityObservation::into_parts()
returns those two records for storage.
A length-prefixed SHA-256 digest of the semantic identity fields becomes both
the api-comparison:<digest> subject and the source correlation ID, and the
same digest also produces deterministic evidence and relation IDs. Those
fields are comparison ID, surface, pair, result, dimension, baseline context,
candidate context, and resource scope.
Observation time, producer provenance, and reliability are deliberately not
part of that digest. Exact replay must nevertheless preserve them because they
remain part of the immutable Evidence record. Reconstructing the same
semantic identity with a different observed_at_ms, component, or reliability
reuses the deterministic evidence ID with different contents and the knowledge
base rejects it as an identity conflict. Changing a context, scope, surface,
result, or dimension instead creates a new semantic identity and isolated
subject.
KnowledgeBase::insert_evidence_with_relation() writes the observation bundle
atomically and idempotently. It requires the relation to originate at the
evidence subject and to cite exactly that evidence ID. Evidence and relation
identity conflicts are checked before either record or index changes, so the
knowledge base cannot retain an evidence-only or relation-only half after a
failed bundle write.
The destination KnowledgeEntity may be registered before or after this
bundle. Entity referential integrity is intentionally eventual because
independent discovery producers may write in either order. The graph edge is
present immediately in the supplied KnowledgeBase, so the comparison claim
still has a stable resource-scope association even while the destination entity
record has not yet arrived. Crash persistence remains a host responsibility.
to_evidence() remains an advanced detached path. It emits only the immutable
evidence; callers choosing it must persist an equivalent comparison-to-resource
mapping themselves. Integrations that need the built-in association should
prefer to_observation() and atomic knowledge-base bundle insertion, then
persist the result through their host-owned storage boundary.
Both conversion paths reject ConfidenceScore::NONE. A nonzero reliability is
preserved as source metadata on the emitted evidence and relation. The current
Bayesian rule engine applies the profile's declared likelihoods directly; it
does not scale them by Evidence::reliability(). Reliability-aware calibration
is a separate future policy, not an implied behavior of this vocabulary.
This one-record rule is a correctness boundary. The reasoning engine must not
join independent UI observations, API observations, or principal responses:
it has no authority to prove that they describe the same resource or
authorization context. The host must establish that equivalence before it
constructs ApiVisibilityComparison.
termivar-scanner::ApiVisibilityComparator is an optional pure producer for hosts
that already hold two authorized serde_json::Value views. It canonicalizes
them under explicit hard ceilings and emits this same comparison contract. It
does not authorize, fetch, retain, or independently pair responses. See
API visibility evidence.
Trust boundary
Typed descriptors and ApiVisibilityComparison provide canonical names,
validation, raw-value-free evidence payloads, pseudonymous comparison
identity, and one-record comparison semantics. They are not cryptographic
attestations. Predicate names in the public knowledge base can be constructed
directly, so the system assumes that only authorized producers have
evidence-write authority.
The SHA-256 digest is not a signature or HMAC and does not authenticate its inputs. It also is not a confidentiality boundary for predictable, low-entropy handles: an observer may be able to test guesses. Hosts should use suitably opaque, non-secret handles and must not treat the digest as proof of producer identity or authorization.
A malicious or compromised writer could forge a standardized API predicate or
bypass the comparison constructor by creating Evidence directly. Hosts must
enforce producer identity, authorization, and write access outside this
vocabulary. Reasoning treats an accepted record as coming from that trusted
write boundary; it does not authenticate the observation merely because its
predicate has a standard name.
Ownership boundary
The shared vocabulary lives in termivar-core because both producers and
reasoners depend inward on it. It defines names, typed values, validation, and
raw-value-free comparison evidence. It does not contain an HTTP client, parser,
planner, verifier, credential provider, or execution policy.
GraphQL review keeps request dispatch and response classification in the feature-gated scanner runtime. The predicate vocabulary neither selects an endpoint nor authorizes a request.