Skip to main content
Version: v0.9.0a2
Spec

Federation Trust Components

12 min readSpec contributor · Node operatorSpec-04 + Spec-05 + Spec-06 + Spec-08

What this page is

Rendered compatibility entry point for the four federation trust component specs: Spec-04-Manifests, Spec-05-Federation-Trust, Spec-06-Capability-Tokens, and Spec-08-Quarantine-Garden. Org manifests, capability tokens, source-trust score, quarantine garden, recall-time sanitizer.

Authoritative source: spec/stigmem-spec-v0.9.0a1.md

Section body

Each subsection below shows the most recent normative text from the spec source.

This section is non-normative.

The active security policy — supported versions, vulnerability reporting instructions, scope definitions, and the coordinated disclosure timeline — is maintained in SECURITY.md at the root of the repository.

Reporting.

Do not open a public GitHub issue for security vulnerabilities. Report via the GitHub private advisory path. We acknowledge within 48 hours and target a patch within 14 days for critical vulnerabilities.

Disclosure timeline: 90 days from the report date before public disclosure, except for vulnerabilities already being actively exploited in the wild.

For the current security posture and Dependabot alert triage covering the pre-reset v1.0-rc snapshot, see the Security Posture section of SECURITY.md.

v1.0 — Stable. All sections normative. Apache-2.0.

§19.1 Org manifest

§19.1.1 Purpose

An org manifest is a signed document that declares the canonical public key for a Stigmem node or organisation and the set of entity URIs that the manifest is authoritative for. Peers MUST use the manifest public key to verify capability tokens (§19.3), provenance signatures (§19.6), and recall-time sanitizer trust decisions (§19.7).

§19.1.2 Manifest fields

The OrgManifest struct carries everything a verifier needs to validate tokens and provenance from a given node.

OrgManifest:
manifest_version: integer // MUST be 1 for this spec version
entity_uri: URI // root entity URI; MUST be a stigmem:// URI
public_key: base64url // Ed25519 public key (32 bytes, encoded)
key_id: hex // SHA-256 of the 32-byte raw Ed25519 public key
entities: [URI] // entity URIs this manifest is authoritative for; MUST include entity_uri
rotation_events: [RotationEvent] // ordered history of key rotations (§19.1.4); empty on first publish
issued_at: RFC3339 // issuance timestamp
expires_at: RFC3339 // expiry; MUST be at least 24h after issued_at
signature: base64url // Ed25519 sig over the canonical JSON encoding of all other fields

A manifest MUST be self-consistent.

The signature MUST verify under the public_key declared in the same manifest. The expires_at ceiling forces regular re-publication, limiting the window during which a compromised key remains trusted.

§19.1.3 Canonical encoding

Manifest signing and verification MUST use RFC 8785 (JSON Canonicalization Scheme, JCS) for deterministic byte ordering. Implementations MUST serialize the manifest body (all fields except signature) using JCS before signing. Implementations MUST reject manifests where JCS canonicalization of the non-signature fields does not reproduce the same bytes that were signed.

§19.1.4 Key rotation

Key rotation events allow a node to cycle its signing key while preserving a verifiable chain of custody back to its original published key.

RotationEvent:
rotated_at: RFC3339 // timestamp of rotation
old_key_id: hex // key_id of the previous key
new_key_id: hex // key_id of the new key (= current manifest's key_id)
rotation_sig: base64url // Ed25519 sig over canonical JSON of { entity_uri, old_key_id, new_key_id, rotated_at }
// signed by the OLD private key; entity_uri binds the event to its manifest

The rotation_sig MUST verify under the public key identified by old_key_id. This creates an unbroken chain: the previous key vouches for the new key.

Rotation chain invariants:

  1. rotation_events MUST be ordered chronologically ascending.
  2. Each event's old_key_id MUST equal the key_id of the preceding entry (or the original manifest's key_id for the first rotation).
  3. A valid rotation chain MUST terminate with the new_key_id matching the current manifest's key_id.
  4. The rotation_events count in a newly published manifest MUST be ≥ the count in the most recently submitted manifest for the same entity_uri. Peers MUST reject any manifest where the rotation event count regresses.

§19.1.5 Entity URI list

The entities array declares which entity URIs this manifest speaks for. An entity URI MUST appear in at most one valid (non-expired) manifest per transparency log epoch. Nodes MUST reject capability tokens and provenance signatures claiming to be from an entity URI that does not appear in the signer's manifest.

§19.1.6 Manifest publication

Nodes MUST publish their manifest at /.well-known/stigmem-manifest.json. Nodes SHOULD also submit each new or rotated manifest to the transparency log (§19.2) for independent auditability. A manifest MUST be re-submitted on key rotation.

§19.2 Transparency log integration

§19.2.1 Purpose

A transparency log provides tamper-evident, append-only evidence that a manifest was published at a given time and has not been backdated or silently revoked. It is the audit anchor for the federation trust model.

§19.2.2 Recommended integration

Implementations SHOULD integrate with Rekor (Sigstore's transparency log) or an equivalent OSS log offering:

Append-only, tamper-evident

Backed by a Merkle tree.

Public inclusion proofs

Using signed tree heads (STH).

HTTP API

For entry submission and proof retrieval.

Implementations MAY operate a self-hosted Rekor instance. A self-hosted log is acceptable for private deployments but SHOULD be independently accessible to all federation peers.

§19.2.3 What we depend on vs. require

Capability
Requirement
Notes
Inclusion proof for submitted manifests
MUST
Be supported by the chosen log.
Consistency proof between log checkpoints
SHOULD
Be supported.
Log entry search by key fingerprint
SHOULD
Be supported.
Public verifiability (no auth)
SHOULD
Hold for public federation deployments.
Specific log implementation (Rekor)
MAY
Alternative logs acceptable if they satisfy the above.

Nodes MUST NOT trust a peer's manifest without a valid inclusion proof when operating in trust_mode: strict (see §19.4.3). Nodes operating in trust_mode: relaxed MAY accept peer manifests without log verification, but SHOULD log a warning.

§19.2.4 Inclusion proof format

When submitting a manifest to the log, the node receives a LogEntry:

LogEntry:
log_id: hex // transparency log's identity
log_index: integer // position in the log
integrated_time: RFC3339 // when the entry was appended
entry_hash: hex // SHA-256 of the log entry body
signed_entry_ts: base64url // signed timestamp from the log (Rekor: SignedEntryTimestamp)
inclusion_proof: {
log_index: integer,
root_hash: hex, // Merkle root of the log at time of proof
tree_size: integer,
hashes: [hex], // sibling hashes for Merkle path
checkpoint: string // signed tree head
}

Nodes SHOULD store the LogEntry alongside the manifest and serve it at /.well-known/stigmem-manifest-proof.json. Peers MUST be able to verify the inclusion proof independently using only the log's public key and the proof data.

§19.2.5 Revocation events

Capability token revocations (§19.3.4) MUST be submitted to the transparency log as a distinct log entry type. This makes revocations independently auditable: a peer can verify a token is revoked by checking the log without trusting the issuing node's runtime state.

§19.2.6 Checkpoint verification

The checkpoint field in LogEntry.inclusion_proof is a signed note in the transparency-dev/formats checkpoint format.

  1. Key discovery. Obtain the log's public key from the log's key discovery endpoint. For Rekor-compatible logs, issue GET /api/v1/log against the log instance; the response includes the ECDSA public key (PEM-encoded in the publicKey.content field, base64-encoded).
  2. Verification. For Rekor-compatible logs, implementations MUST verify the checkpoint using the log's published public key. A checkpoint that fails signature verification MUST cause the enclosing inclusion proof to be rejected.
  3. Failure-closed behavior. If the transparency log is unreachable when an inclusion proof is required (i.e., the node operates in trust_mode: strict), the manifest MUST be rejected. Implementations MUST NOT fall back to accepting an unverified manifest.
  4. Reference implementation. The sigstore-python library (sigstore.verify) is the reference for checkpoint and inclusion-proof verification.
  5. Error codes. See §19.9 for inclusion_proof_invalid (HTTP 400) and transparency_log_unavailable (HTTP 503).

§19.3 Capability tokens

§19.3.1 Purpose

A capability token is a signed, short-lived credential that grants a specific named permission to a specific subject from a specific issuer. Tokens replace ad-hoc per-peer trust agreements with a verifiable, revocable, auditable delegation primitive.

§19.3.2 Token shape

CapabilityToken:
token_version: integer // MUST be 1 for this spec version
token_id: UUID // unique identifier; used for revocation lookup
issuer: URI // entity URI of the issuing node/org (MUST be in issuer's manifest)
subject: URI // entity URI of the token bearer
verb: string // one of: "read" | "write" | "admin" | "federate" | "subscribe" | "tombstone:read"
object: URI // resource the verb applies to (scope URI, garden URI, or "*" for any)
issued_at: RFC3339
expiry: RFC3339 // MUST be set; MUST NOT exceed 90 days from issued_at
nonce: hex // 32 bytes cryptographically random; prevents replay
signature: base64url // Ed25519 sig over canonical JSON of all other fields, signed by issuer key

The verb values are:

Verb
Bearer may
Notes
read
read facts from object
write
assert facts to object
admin
manage keys and settings on object
federate
replicate facts bidirectionally
Via the federation protocol (§6).
subscribe
register a standing event subscription
On object (scope URI or entity URI).
tombstone:read
poll the tombstone federation route
Compound verb namespaces may be introduced by extension sections. Token validation implementations MUST accept any verb that appears in this enumeration.

§19.3.3 Signing and verification

The issuer MUST sign the token using the private key corresponding to the public_key in their current org manifest (§19.1).

Verifiers MUST:

  1. Resolve the issuer's org manifest.
  2. Check manifest.expires_at > now; if expired, attempt refresh from the issuer's /.well-known/stigmem-manifest.json; reject the token if the manifest is still expired after refresh.
  3. Verify the manifest's self-signature.
  4. Verify the token's signature under the manifest's public_key.
  5. Check that subject appears in the issuer's entities list. External-entity subjects are not permitted; cross-org delegation requires the delegatee to obtain their own org manifest and capability tokens.
  6. Check expiry > now.
  7. Check expiry ≤ issued_at + 90 days.
  8. Check the token is not revoked (§19.3.4).

A token that fails any of these steps MUST be rejected.

§19.3.4 Revocation

Issuers MAY revoke a token before its expiry by submitting a revocation event to the transparency log (§19.2.5) and calling the local revocation API (§5.24).

RevocationEvent:
event_type: "token_revocation"
token_id: UUID // the token being revoked
issuer: URI
revoked_at: RFC3339
reason: string // human-readable; SHOULD be informative
signature: base64url // Ed25519 sig over canonical JSON of other fields

Nodes that receive a token MUST check for a revocation event before honoring it. Nodes SHOULD cache revocation events with a TTL of no less than 60 seconds. A revoked token MUST be rejected even if it has not yet expired.

Revocation transparency log entries are for auditability, not real-time validation.

Implementations MUST NOT attempt an inline transparency log query as part of per-request token validation; doing so would introduce a synchronous dependency on an external service in the hot path. Real-time revocation checks MUST use the local revocation cache (populated by background sync) and the issuer's revocation API (§5.24).

§19.3.5 Token nonce and replay prevention

The nonce field MUST be 32 bytes of cryptographically random data (e.g., from /dev/urandom). Receivers MUST maintain a nonce cache in trust_mode: strict; receivers SHOULD maintain a nonce cache in trust_mode: relaxed. The nonce cache MUST be global (keyed on the nonce value alone, not per-issuer or per-subject) and MUST cover the token's full validity window (from issued_at to expiry). A nonce that appears in the cache MUST cause the token to be rejected with a token_replay error.

§19.4 Source-trust score

§19.4.1 Purpose

The source-trust score t is a scalar in [0.0, 1.0] that expresses how much confidence a node should place in facts asserted by a given source URI. It modulates the effective confidence of recalled facts: effective_confidence = fact.confidence × t.

§19.4.2 Derivation formula

t = clamp(
w_i × identity_strength(source)
+ w_p × peer_history(source)
+ w_s × scope_authority(source, fact.scope)
+ w_a × attestation_mode_factor(node.attestation_mode),
0.0, 1.0
)

Default weights: w_i = 0.35, w_p = 0.30, w_s = 0.25, w_a = 0.10. These MUST be documented in the node's /.well-known/stigmem response as trust_weights if non-default values are used.

Component definitions:

identity_strength(source) — a float in [0.0, 1.0] measuring how strongly the source is identified:

Condition
Value
Notes
Valid manifest + log proof
1.0
Valid manifest, no log proof
0.7
Valid capability token from trusted issuer
0.5
Known entity_uri registered on local API key
0.4
Unrecognized but syntactically valid
0.1
Absent or syntactically invalid
0.0

peer_history(source) — derived from the node's interaction history:

Condition
Value
Notes
≥ 100 facts with 0 attestation failures in 30 days
1.0
≥ 10 facts with < 5% attestation failures
0.7
New source (< 10 facts or no history)
0.5
Default when no peer history exists.
Recent attestation failure rate ≥ 5%
0.3
Explicitly blocklisted by admin
0.0

scope_authority(source, scope):

Condition
Value
Notes
Valid capability token for this scope with verb: write
1.0
Admin API key holder for this node
0.9
Source's entity_uri prefix matches the node's authority
0.7
External entity with a federate token
0.5
No explicit scope authority
0.2

attestation_mode_factor(mode):

Mode
Value
enforce
1.0
warn
0.6
off
0.2

§19.4.3 Trust mode configuration

Configure via STIGMEM_TRUST_MODE=strict|relaxed|off.

Mode
Posture
Behavior
strict
enforced
Nodes MUST verify log inclusion proofs for all peer manifests; source_trust is computed for all inbound facts; facts from sources with t < 0.2 are quarantined (§19.5).
relaxed (default)
observed
Nodes SHOULD compute source_trust but MUST NOT quarantine facts based solely on a low score; attestation failures are logged.
off
disabled
source_trust is not computed; source_trust field is null on all stored facts.

§19.4.4 Recall-time multiplier

At recall time, the effective_confidence of a fact is computed as:

effective_confidence = fact.confidence × t(fact.source)

Where t(fact.source) is recomputed live at recall time using current peer state (not the stored source_trust snapshot). Implementations SHOULD cache per-source trust scores with a TTL of no less than 60 seconds.

Recall results SHOULD include effective_confidence alongside confidence when trust_mode is strict or relaxed. Callers MUST NOT rely on effective_confidence being identical to confidence.

Multi-worker deployments

The default source-trust cache is per-worker and in-memory. In multi-worker deployments (gunicorn, uvicorn --workers), each worker holds an independent cache. Cache misses and stale entries can cause non-deterministic trust scoring. For production multi-worker deployments, configure STIGMEM_TRUST_CACHE_BACKEND=redis and provide STIGMEM_REDIS_URL. This will be required in a future release.

§19.4.5 Bounds and defaults

Always clamped

t is always clamped to [0.0, 1.0].

No-component default

A source with no computable score (all components unavailable) MUST default to t = 0.5.

Blocklist override

Admin blocklisted sources MUST return t = 0.0 regardless of other components.

§19.5 Quarantine garden

§19.5.1 Purpose

A quarantine garden is a special-purpose Memory Garden (§17) that holds facts pending human or automated review before they are integrated into production scope.

§19.5.2 Relationship to §17 garden machinery

A quarantine garden is a Garden record (§17) with an additional quarantine: true flag set at creation time. It inherits all §17 mechanics: it is scope-bound, ACL-controlled, facts within it are isolated from federation, and standard garden CRUD applies.

Differences from a standard garden:

  1. A quarantine garden adds a quarantine:moderator role (§19.5.3) not present in standard gardens.
  2. Facts in a quarantine garden MAY NOT be asserted directly — they are only populated by the node's automatic quarantine policy (§19.5.4) or by explicit operator action.
  3. A quarantine garden MUST NOT be deleted while it holds unreviewed facts (status pending). Attempting deletion returns HTTP 409 quarantine_has_pending_facts.

§19.5.3 Roles

Role
Permissions
Notes
admin
full
All §17 admin permissions; can promote/reject; can add/remove moderators.
quarantine:moderator
moderation
Can promote and reject pending facts; read-only otherwise.
writer
standard write
Same as §17; cannot promote/reject quarantined facts.
reader
read-only
Can see quarantined facts and review metadata.

The node MUST automatically add the garden's creating principal as admin. A quarantine garden MUST have at least one admin or quarantine:moderator at all times.

§19.5.4 Automatic quarantine policy

When trust_mode: strict is set (§19.4.3), the node MUST route inbound federated facts to the node's designated quarantine garden when:

Low trust score

The fact's source has source_trust t < 0.2.

Missing manifest

The fact's source lacks a valid org manifest.

Provenance failure

The fact fails provenance chain verification (§19.6.3).

When no quarantine garden has been designated, the node MUST reject these facts with HTTP 403 trust_below_threshold rather than silently dropping them.

The node MAY also quarantine facts that trigger the recall-time sanitizer (§19.7) in quarantine enforcement mode.

Pre-flight check: configure a quarantine garden before enabling strict mode

Enabling trust_mode=strict without configuring a quarantine garden will cause all low-trust facts (score < 0.2) to be permanently rejected with 403 trust_below_threshold. Before setting trust_mode=strict, create a quarantine garden and set quarantine_garden_id in your federation config. A future release will add a startup check that enforces this.

§19.5.5 Promote and reject mechanics

Promote. A quarantine:moderator or admin calls POST /v1/gardens/:id/promote (§5.25) with a target_garden_id. The node:

  1. Moves the fact's garden_id to the target_garden_id (or clears it for no-garden).
  2. Removes the quarantine:pending status marker from the fact.
  3. Logs a quarantine_promote event to the attestation audit log.

Reject. A quarantine:moderator or admin calls POST /v1/gardens/:id/reject (§5.25). The node:

  1. Sets confidence = 0.0 on the fact (logical retraction).
  2. Sets a quarantine:rejected marker.
  3. Logs a quarantine_reject event to the attestation audit log.

A rejected fact is retained in the quarantine garden for audit purposes. It MUST NOT be served in normal recall results. It MUST be visible to garden admins and moderators via the garden-filtered fact query.

§19.5.6 Auditability

All promote and reject events MUST be written to the attestation audit log (§18.10) with the additional fields:

quarantine_action: "promote" | "reject"
quarantine_garden_id: <garden_id>
target_garden_id: <garden_id> | null // promote only
reason: string
acted_by: <entity_uri>
acted_at: RFC3339

The audit log MUST be queryable by quarantine_garden_id and quarantine_action.

§19.6 Provenance chain

§19.6.1 Purpose

The provenance chain allows a fact to declare its intellectual antecedents (derived_from) and carry cryptographic attestations from intermediate processors (attestation_chain). Together they create a verifiable lineage from source to recall, enabling detection of data tampering or unexplained derivation gaps.

§19.6.2 Fact hash computation

A fact hash is the hex-encoded SHA-256 digest of the fact's canonical JSON representation. The canonical form MUST be JCS (RFC 8785) of:

{
"entity": "<entity>",
"relation": "<relation>",
"value": <FactValue>,
"scope": "<scope>",
"source": "<source>",
"confidence": <float>,
"ts": "<RFC3339>"
}

The following fields MUST be excluded from the hash input: id, garden_id, attested, source_trust, derived_from, attestation_chain. This ensures the hash is stable regardless of storage metadata or trust annotations.

§19.6.3 Provenance field shapes

derived_from: [FactHash]

64-char lowercase hex

Each FactHash MUST be a 64-character lowercase hex string.

Logical derivation order

Array ordered by logical derivation precedence (first = most direct antecedent).

Empty = null

An empty array is equivalent to null (no declared provenance).

DAG required

The reference graph MUST be a DAG. Implementations MUST detect circular references using a visited-set during traversal and MUST reject any fact that would create a cycle with HTTP 400 provenance_cycle_detected.

Resolvable references

The referenced facts MUST either exist in the same node or be resolvable via federation. Nodes SHOULD validate at write time in trust_mode: strict; in relaxed, MAY log a warning for dangling references.

attestation_chain: [Signature]

Ed25519 sigs over canonical hash

Each Signature is a base64url-encoded Ed25519 signature over the canonical fact hash.

Innermost-to-outermost order

Signatures are ordered from innermost processor to outermost (first processor = index 0).

Max 16 entries

attestation_chain MUST NOT exceed 16 entries. Nodes MUST reject facts with longer chains with HTTP 400 attestation_chain_too_long.

Parallel issuer URIs

Each signature is bound to a specific entity URI: the signing entity MUST include their entity URI in a parallel attestation_chain_issuers: [URI] field (same indexing). Implementations MUST include both fields together or neither.

Verify each signature

Verifiers MUST verify each signature in the chain using the corresponding issuer's manifest public key. A chain with an invalid signature at any position MUST be rejected entirely.

§19.6.4 Verification rules

A provenance chain is valid if all of the following hold.

  1. Every FactHash in derived_from references a fact that exists and whose stored hash matches the declared value.
  2. Every signature in attestation_chain verifies under the corresponding issuer's current manifest public key.
  3. No issuer in attestation_chain_issuers appears more than once (no circular attestation).
  4. The issuers' entity URIs each appear in their respective manifest's entities list.
  5. If any field included in the fact hash is modified after attestation, the node MUST either: (a) clear attestation_chain and attestation_chain_issuers (treating the updated fact as unattested), or (b) reject the update with HTTP 409 fact_is_attested. Nodes MUST NOT retain stale attestation chains after hash-relevant field changes.

Nodes MUST reject facts with invalid provenance chains in trust_mode: strict. Nodes SHOULD log warnings for invalid chains in trust_mode: relaxed.

§19.7 Recall-time content sanitizer

§19.7.1 Purpose

The recall-time content sanitizer prevents prompt-injection payloads and malformed values from reaching the recall layer's consumers. It is a defense-in-depth control applied at recall time, not at write time, so that future policy changes can be retroactively applied to already-stored facts.

The sanitizer is NOT applied at write time (storage is a transparent record layer). It is applied immediately before facts are serialized into the API response for GET /v1/facts and GET /v1/recall endpoints.

§19.7.2 Default-deny sentinel patterns

Before applying sentinel patterns, implementations MUST normalize all string input to Unicode NFKC form. Implementations MUST also strip or reject the following Unicode categories: bidirectional control characters (U+200F, U+200E, U+202A–U+202E, U+2066–U+2069) and invisible formatting characters (U+200B–U+200D, U+FEFF).

The following patterns are checked against all string-typed FactValue fields:

// Instruction-injection sentinels
\bignore\s+(all\s+)?previous\s+instructions?\b (case-insensitive)
\bdisregard\s+(all\s+)?previous\s+(prompt|instructions?)\b
\byou\s+are\s+now\s+(?:in\s+)?(?:a\s+)?(?:different|new)\s+mode\b
\bact\s+as\s+(?:an?\s+)?(?:evil|unfiltered|uncensored|dan\b)
\bsystem\s+prompt\s*:\s*
\<\|im_start\|\>
\<\|im_end\|\>
\[INST\]
\[\/INST\]
\bHuman:\s* // common chat-template leak
\bAssistant:\s* // ditto

// Schema evasion sentinels
\{\s*"__proto__"\s*: // prototype pollution attempt
\{\s*"constructor"\s*:

This list is the default. Operators MAY add patterns via node configuration (STIGMEM_SANITIZER_EXTRA_PATTERNS as a newline-delimited regex file). Operators MUST NOT remove default patterns in trust_mode: strict.

§19.7.3 Schema enforcement for typed FactValues

FactValue type
Enforcement
Behavior
string
UTF-8 valid
Control characters (U+0000–U+001F except U+0009, U+000A, U+000D) MUST be removed before return.
number
finite IEEE 754
NaN, +Inf, -Inf MUST be replaced with null.
bool
true/false only
Coercion from 0/1 is NOT performed; unexpected values MUST be returned as null.
ref
syntactically valid URI
Malformed refs MUST be replaced with null.
json
valid JSON
Malformed values MUST be replaced with null.
text
string + sentinel matching
Same as string, plus sentinel pattern matching.

Facts where type enforcement produces a null substitution MUST include a sanitizer_redacted: true marker in the API response alongside the null value.

§19.7.4 Enforcement modes

Configured via STIGMEM_SANITIZER_MODE.

Mode
Default in
Behavior on match
block
trust_mode: strict
Entire fact excluded; placeholder { "fact_id": "...", "sanitized": true } returned in its position.
quarantine
Fact moved to the node's quarantine garden (§19.5) and excluded from the current recall result.
warn
trust_mode: relaxed
Fact returned with sanitizer_warnings: ["<matched pattern>"]; content unmodified.
off
implied by trust_mode: off
No sanitizer check.

Operators MAY configure a more restrictive mode than the trust_mode default. Operators MUST NOT configure a less restrictive mode in trust_mode: strict.

§19.7.5 Placement and ordering

The sanitizer is the final processing step before serialization in the recall pipeline:

Storage layer
→ Scope/garden ACL filter (§17.3)
→ Source-trust multiplier applied to effective_confidence (§19.4.4)
→ Provenance chain verification (§19.6.4, strict mode only)
→ Content sanitizer ← HERE
→ API response serializer

The sanitizer MUST run after trust scoring and provenance verification so that trust metadata is available to the enforcement decision.

§19.7.6 Audit logging

Every sanitizer action MUST be logged to the attestation audit log (§18.10):

sanitizer_action: "block" | "quarantine" | "warn"
fact_id: <uuid>
matched_pattern: string // the regex that triggered; "schema_enforcement" for type failures
recall_endpoint: string // "/v1/facts" or "/v1/recall"
ts: RFC3339

§19.8 Schema migration (migration 006)

Migration 006 adds three tables to support the federation trust layer. federation_manifests stores org manifests indexed by entity_uri and key_id for fast lookup during token verification. capability_tokens records every token the node has issued or accepted. source_trust_scores caches computed trust scores per source URI and scope pair.

-- Org manifest storage
CREATE TABLE IF NOT EXISTS federation_manifests (
id TEXT PRIMARY KEY,
entity_uri TEXT NOT NULL UNIQUE,
manifest_json TEXT NOT NULL, -- JCS-canonical manifest body
signature TEXT NOT NULL, -- base64url Ed25519 sig
key_id TEXT NOT NULL,
issued_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
log_entry_json TEXT, -- NULL if not yet submitted to transparency log
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_federation_manifests_entity_uri ON federation_manifests(entity_uri);
CREATE INDEX IF NOT EXISTS idx_federation_manifests_key_id ON federation_manifests(key_id);

-- Capability token storage
CREATE TABLE IF NOT EXISTS capability_tokens (
id TEXT PRIMARY KEY, -- token_id UUID
token_json TEXT NOT NULL, -- full signed token body (JCS-canonical)
issuer TEXT NOT NULL,
subject TEXT NOT NULL,
verb TEXT NOT NULL,
object TEXT NOT NULL,
issued_at TEXT NOT NULL,
expiry TEXT NOT NULL,
nonce TEXT NOT NULL UNIQUE,
revoked_at TEXT, -- NULL if active
revoke_log TEXT, -- JSON of RevocationEvent if revoked
created_at TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_capability_tokens_subject ON capability_tokens(subject);
CREATE INDEX IF NOT EXISTS idx_capability_tokens_issuer ON capability_tokens(issuer);
CREATE INDEX IF NOT EXISTS idx_capability_tokens_nonce ON capability_tokens(nonce);
CREATE INDEX IF NOT EXISTS idx_capability_tokens_expiry ON capability_tokens(expiry);

-- Quarantine metadata extension on facts table
ALTER TABLE facts ADD COLUMN IF NOT EXISTS quarantine_status TEXT;
-- NULL = not quarantined; "pending" = awaiting review; "promoted" = approved; "rejected" = rejected
ALTER TABLE facts ADD COLUMN IF NOT EXISTS quarantine_garden_id TEXT REFERENCES gardens(id);
ALTER TABLE facts ADD COLUMN IF NOT EXISTS quarantine_acted_by TEXT;
ALTER TABLE facts ADD COLUMN IF NOT EXISTS quarantine_acted_at TEXT;
ALTER TABLE facts ADD COLUMN IF NOT EXISTS quarantine_reason TEXT;

-- Provenance chain fields on facts table
ALTER TABLE facts ADD COLUMN IF NOT EXISTS derived_from TEXT; -- JSON array of FactHash
ALTER TABLE facts ADD COLUMN IF NOT EXISTS attestation_chain TEXT; -- JSON array of base64url sigs
ALTER TABLE facts ADD COLUMN IF NOT EXISTS attestation_chain_issuers TEXT; -- JSON array of URI

-- Source trust snapshot
ALTER TABLE facts ADD COLUMN IF NOT EXISTS source_trust REAL;

CREATE INDEX IF NOT EXISTS idx_facts_quarantine_status ON facts(quarantine_status) WHERE quarantine_status IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_facts_quarantine_garden ON facts(quarantine_garden_id) WHERE quarantine_garden_id IS NOT NULL;

§19.9 Error reference

HTTP · Code
Class
Condition
400 · manifest_signature_invalid
bad request
Manifest signature does not verify under public_key.
400 · manifest_rotation_chain_invalid
bad request
Rotation chain verification fails.
400 · token_nonce_invalid
bad request
Nonce is not 32 bytes or is malformed.
400 · provenance_hash_invalid
bad request
derived_from entry is not a 64-char lowercase hex string.
400 · provenance_cycle_detected
bad request
derived_from graph contains a cycle.
400 · attestation_chain_too_long
bad request
attestation_chain exceeds 16 entries.
400 · inclusion_proof_invalid
bad request
Checkpoint signature or Merkle path fails verification.
403 · trust_below_threshold
forbidden
Fact source t < 0.2 in trust_mode: strict and no quarantine garden configured.
403 · token_expired
forbidden
Capability token expiry has passed.
403 · token_revoked
forbidden
Token found in revocation log.
403 · token_replay
forbidden
Token nonce already seen within its validity window.
403 · insufficient_capability
forbidden
Bearer's capability token does not cover the requested verb/object.
403 · entity_not_in_manifest
forbidden
Source entity_uri not in the issuer's manifest entities list.
409 · quarantine_has_pending_facts
conflict
Attempted deletion of quarantine garden with pending facts.
409 · fact_not_quarantine_pending
conflict
Promote/reject attempted on a fact not in pending quarantine state.
409 · fact_is_attested
conflict
Update to a hash-relevant field rejected because attestation_chain is present and node configured to reject rather than clear.
422 · attestation_chain_mismatch
unprocessable
attestation_chain and attestation_chain_issuers array lengths differ.
503 · transparency_log_unavailable
unavailable
Transparency log was unreachable; in trust_mode: strict, the manifest MUST be rejected.

§19.10 Well-known advertisement

Nodes MUST extend their /.well-known/stigmem response to include federation trust configuration:

{
...existing fields...,
"federation_trust": {
"trust_mode": "strict" | "relaxed" | "off",
"sanitizer_mode": "block" | "quarantine" | "warn" | "off",
"manifest_url": "https://node.example.com/.well-known/stigmem-manifest.json",
"manifest_proof_url": "https://node.example.com/.well-known/stigmem-manifest-proof.json",
"trust_weights": {
"identity_strength": 0.35,
"peer_history": 0.30,
"scope_authority": 0.25,
"attestation_mode": 0.10
}
}
}

trust_weights MUST be included when non-default values are configured.