Module 4: Building a VCP-Enabled Chat Application

Verify first, enforce locally, then provide bounded constitutional context to your chosen model provider.

DEV 40 min

Learning Objectives

By the end of this module, you will be able to:

  • Keep bundle acquisition, cryptographic verification, policy enforcement, and model invocation as separate gates
  • Fail closed when required VCP evidence is absent or invalid
  • Format a verified bundle for provider-neutral prompt injection
  • Record privacy-aware verification evidence

4.1 — The Trust Boundary

A VCP-enabled chat application has an explicit sequence:

Load bytes
  → validate schema and limits
  → verify hash, signatures, trust, time, revocation, budget, and scope
  → apply local enforcement policy
  → format verified constitutional content
  → call the selected model provider
  → retain a privacy-aware audit record

Do not send unverified bundle content to a model. Parsing and formatting are not substitutes for verification.

4.2 — Project Setup

# From the root of an immutable VCP-SDK checkout
python -m venv .venv
. .venv/bin/activate
python -m pip install ./python

SDK 4.2.0 is currently source-only. No registry release is claimed. Record the checkout commit before using it as deployment evidence.

Install the SDK for your selected model provider separately. VCP does not require one provider and does not hide provider credentials inside its package.

4.3 — Load Trust and Bundle Data

Your application must define where bundles and trust anchors come from. Keep those adapters narrow and reviewable:

bundle = bundle_repository.load("family.safe.guide", "1.2.0")
trust = trust_repository.load_for_environment("production")

# These two repositories are application-owned adapters.
# Treat fetched bundle bytes and registry responses as untrusted input.

Pin issuer and auditor keys through TrustConfig. Never infer trust merely because a bundle names a familiar issuer.

4.4 — Verify and Enforce

from vcp import (
    BundleExpiryPlugin,
    Orchestrator,
    PDPEnforcer,
    RefusalBoundaryPlugin,
    VerificationResult,
)

verification = Orchestrator(trust).verify(bundle)
if verification is not VerificationResult.VALID:
    raise PermissionError(
        "VCP bundle rejected: " + verification.name
    )

enforcer = PDPEnforcer(fail_closed=True)
enforcer.register(BundleExpiryPlugin())
enforcer.register(RefusalBoundaryPlugin())

policy = enforcer.evaluate(
    bundle,
    content=user_message,
    verification_result=verification,
)
if not policy.allowed:
    raise PermissionError("VCP policy blocked this request")

The built-in enforcer produces ALLOW, BLOCK, MODIFY, or ESCALATE decisions. In the current SDK, an unhandled escalation is promoted to a block. Applications that need human review must build and test that workflow explicitly.

4.5 — Format Only After Verification

from vcp import InjectionFormat, InjectionOptions, format_injection

vcp_prompt = format_injection(
    bundle,
    options=InjectionOptions(
        format=InjectionFormat.HEADER_DELIMITED,
        include_tokens=True,
        include_attestation=True,
    ),
)

messages = [
    {"role": "system", "content": vcp_prompt},
    {"role": "user", "content": user_message},
]

# Send messages through your provider adapter only after every gate above passes.
reply = await model_provider.respond(messages)

Important: format_injection formats a bundle. Its type signature cannot prove that your application called verify first. Preserve that ordering in one reviewed function and cover the negative path with tests.

4.6 — Add Adaptation Context

Encode contextual signals separately from constitutional bundle content:

from vcp.adaptation import ContextEncoder

context = ContextEncoder().encode(
    time="evening",
    space="home",
    company="children",
)

context_wire = context.encode()
# Your application decides whether and where this bounded wire value is shared.

Context does not acquire confidentiality merely because it is encoded. Apply consent, minimisation, platform filtering, retention limits, and logging redaction at the application boundary.

4.7 — Record Verification Evidence

from vcp import AuditLevel, AuditLogger

audit = AuditLogger(level=AuditLevel.STANDARD)
audit.log_verification(
    bundle,
    verification,
    session_id=session_id,
    request_id=request_id,
)

# Persist through a controlled callback or export path with your own
# access control and retention policy.

The SDK hashes session and request identifiers before storing them. That reduces exposure but does not remove your obligations around access, retention, deletion, and incident response.

4.8 — Test the Failure Paths

  • Missing bundle and missing trust anchor
  • Hash mismatch, invalid signature, and invalid attestation
  • Expired, not-yet-valid, revoked, and replayed bundles
  • Scope or audience mismatch
  • Enforcement plugin exception under fail-closed operation
  • Provider failure after verification, without leaking raw private context into logs

Exercise

Run the SDK's examples/python/05_full_pipeline.py. Then tamper with the bundle content and confirm that verification fails before formatting or model invocation.

See It in Action

The Gentian demo illustrates portable preferences. The demo is an explanatory application, not evidence that a third-party platform implements VCP.