Spec-20-Lint-Semantics
What this page is
Rendered compatibility entry point for
Spec-20-Lint-Semantics.
The lint operation is a first-class Stigmem protocol operation
that performs health-check sweeps over a bounded scope or entity.
Lint is strictly read-only.
It observes and reports issues without writing facts or modifying
node state. Lint bridges the decay engine (§15) and the current
production node — running lint_scope against a live
node reveals knowledge-base health degradation before it affects
query results or agent behavior.
Authoritative source:
spec/stigmem-spec-v0.9.0a1.md
Each subsection below shows the most recent normative text from the spec source.
§14.1 Lint checks
Four normative checks, each independently selectable.
contradiction(entity, relation, scope) tuple where both have confidence > 0.0 and the conflict is unresolved (status "unresolved" in the conflicts table).stalevalid_until < now and confidence > 0.0; optionally, facts whose valid_until < now + stale_lookahead_s (approaching expiry).orphanconfidence = 0.0) or expired (valid_until < now). No live facts remain for the entity.broken_refvalue.type = "ref" whose value.v targets an entity or fact ID that has no live (non-retracted, non-expired) facts on this node.Default behavior: If checks is omitted or empty, all four
checks run.
Scope of search: Each check operates within the scope specified
in the lint request. Checks never cross scope boundaries — a
broken_ref finding in company scope is only reported if the
broken ref also falls within company scope.
§14.2 LintFinding shape
LintFinding {
check: "contradiction" | "stale" | "orphan" | "broken_ref"
severity: "error" | "warning" | "info"
entity: URI // entity under examination
relation: string | null // relevant relation; null for the orphan check
fact_ids: UUID[] // IDs of the fact(s) involved in the finding
detail: string // human-readable explanation suitable for display
}
§14.3 Severity mapping
contradiction · unresolved conflict between live factsstale · valid_until < nowstale · within stale_lookahead_sorphan · entity has no live factsbroken_ref · target has no live factsbroken_ref · on intent:handoff_to or intent:context_ref§14.4 Wire format
Request
POST /v1/lint
Authorization: Bearer <api-key>
Content-Type: application/json
{
"scope": FactScope, // required: which scope to sweep
"checks": string[], // optional; default: ["contradiction","stale","orphan","broken_ref"]
"entity": URI?, // optional: restrict sweep to one entity
"relation": string?, // optional: restrict sweep to one relation
"stale_lookahead_s": integer? // optional: also flag facts expiring within N seconds; default 0
}
Response
200 OK
{
"findings": LintFinding[], // zero or more findings; empty array = clean
"checked_at": string, // ISO 8601 UTC timestamp of when the sweep ran
"scope": FactScope, // echoed from the request
"checks_run": string[], // which checks actually ran (echoed or defaulted)
"fact_count": integer // number of facts scanned (not findings)
}
Authorization
The caller's API key must have read access to the requested scope
(§3.5). Nodes MUST return HTTP 403 if the key's allowed_scopes
does not include the requested scope.
Error responses
scope field missing or invalid; unknown checks value.§14.5 Performance contract
Sync ≤ 30s for <100k facts
POST /v1/lint MUST respond synchronously within 30 seconds for scopes with fewer than 100,000 facts.
Async for larger scopes
For scopes exceeding 100,000 facts, nodes MAY respond with HTTP 202 and a job_id. The caller polls GET /v1/lint/jobs/:job_id. Async job API is specified but deferred to the pre-reset substrate work.
Read-only invariant
The sweep MUST be read-only. Nodes MUST NOT assert, retract, or update any fact as a side effect of a lint call. This invariant applies even to internal bookkeeping.
202 Accepted
{ "job_id": "<uuid>", "status": "pending", "estimated_s": integer }
§14.6 Relationship to other operations
Lint is diagnostic, not prescriptive.
contradictionPOST /v1/conflicts/:id/resolve (§5.10).stalePOST /v1/decay/sweep with mode="retract" (§15) or POST /v1/facts with confidence=0.0.orphanbroken_ref§14.7 MCP tool: lint_scope
The lint_scope MCP tool exposes POST /v1/lint to any MCP-aware
agent without SDK installation.
Tool definition
{
"name": "lint_scope",
"description": "Sweep a Stigmem scope for knowledge-base health issues. Checks for: unresolved contradictions, stale or expiring facts, orphaned entities with no live facts, and broken cross-references. Read-only — reports findings without modifying any facts. Use resolve_contradiction to fix contradictions found here.",
"inputSchema": {
"type": "object",
"properties": {
"scope": {
"type": "string",
"enum": ["local", "team", "company", "public"],
"description": "The fact scope to sweep."
},
"checks": {
"type": "array",
"items": {
"type": "string",
"enum": ["contradiction", "stale", "orphan", "broken_ref"]
},
"description": "Which checks to run. Omit to run all four."
},
"entity": {
"type": "string",
"description": "Optional. Restrict sweep to facts about a single entity URI."
},
"relation": {
"type": "string",
"description": "Optional. Restrict sweep to a single relation."
},
"stale_lookahead_s": {
"type": "integer",
"description": "Optional. Also flag facts expiring within this many seconds. Default 0 (expired-only)."
}
},
"required": ["scope"]
}
}
Output shape
{
"findings": [
{
"check": "contradiction",
"severity": "error",
"entity": "stigmem://company.example/user/alice",
"relation": "memory:role",
"fact_ids": ["fact-uuid-1", "fact-uuid-2"],
"detail": "Two live facts with different values for (entity, relation, scope)"
}
],
"checked_at": "2026-05-02T14:00:00Z",
"scope": "company",
"checks_run": ["contradiction", "stale", "orphan", "broken_ref"],
"fact_count": 1024
}
§14.8 Conformance test vectors
Normative lint vectors are defined in
sdks/stigmem-py/tests/conformance_vectors.py under LINT_VECTORS.
Each vector includes a setup list of fact assertions to run before
the lint sweep, so results are deterministic.
Required vectors for conformance — all eight MUST pass against a reference Stigmem node.
findingslint-contradictioncontradictioncontradiction, severity=error.lint-stalestalevalid_until in the past → ≥1 finding, check=stale, severity=warning.lint-stale-lookaheadstalevalid_until within lookahead window but not yet elapsed → ≥1 finding, severity=info.lint-orphanorphaninfo.lint-broken-refbroken_refwarning.lint-broken-ref-intentbroken_refintent:handoff_to relation → ≥1 finding, severity=error.lint-clean[].lint-scope-filtercontradictioncompany scope; lint request on local scope → findings = [] (scope isolation).