Documentation
¶
Overview ¶
Package mcpacceptance implements an operator-runnable, artifact-bound Observe Acceptance Harness for the Culvert MCP Gateway (QUAL-6).
The harness drives a BUILT culvert binary through its REAL production boundaries — a real TLS/mTLS listener, real OAuth authentication, the real Admin HTTP API, real /metrics, and the real on-disk encrypted telemetry spool and archive — and emits a deterministic, tamper-evident, secret-free evidence bundle bound to the exact tested artifact.
It is strictly an ACCEPTANCE TEST HARNESS: it never begins Observe, never calls BeginWindow, never creates a qualification-duration window, never promotes a Catalog tool, never enables the executor/upstream/credential broker, never materializes a credential, never alters rollout mode, and never unlocks Production. Every "live" criterion is proven at the production binary boundary, never by calling an internal Go constructor.
Index ¶
- Constants
- type ArtifactIdentity
- type ArtifactSpec
- type CriterionResult
- type Duration
- type EnvSpec
- type FinalizeError
- type Fixture
- type Harness
- type Manifest
- type ManifestEntry
- type Mode
- type Options
- type Process
- type ProvenanceSpec
- type RunControl
- type SecretScan
- type Spec
- type Status
- type Summary
- type SupervisionEnv
- type SupervisionInfo
- type TelemetryEnv
- type TelemetrySummary
- type TenantMatrixCell
- type Violation
Constants ¶
const EvidenceSchemaVersion = 2
EvidenceSchemaVersion is the schema version of the acceptance evidence bundle. Bump only on a breaking change to the on-disk bundle shape.
v2 (QUAL-6.1) adds the effective-environment proof fields to the summary: effective_bind_host, operator_policy_digest, telemetry ownership/node id, and the supervision descriptor. These are additive, safe (secret-free) fields; a v1 reader simply does not see them. The interpretation of the existing v1 fields is unchanged.
const HarnessVersion = "qual6-observe-acceptance/1"
HarnessVersion is the acceptance-harness contract version (independent of the tested artifact's version).
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ArtifactIdentity ¶
type ArtifactIdentity struct {
Path string `json:"path"`
Digest string `json:"digest"` // sha256:<hex> of the on-disk binary
Version string `json:"version"` // from GET /healthz
SourceCommit string `json:"source_commit"` // operator-supplied / provenance-bound
Verification string `json:"verification"` // "digest-match" | "provenance-bound" | "unverified"
Authoritative bool `json:"authoritative"`
}
ArtifactIdentity binds the acceptance result to the exact tested artifact.
type ArtifactSpec ¶
type ArtifactSpec struct {
BinaryPath string `json:"binary_path"`
ExpectedDigest string `json:"expected_digest,omitempty"` // sha256:<hex>
ExpectedVersion string `json:"expected_version,omitempty"`
ExpectedSourceCommit string `json:"expected_source_commit,omitempty"`
Provenance *ProvenanceSpec `json:"provenance,omitempty"`
}
ArtifactSpec identifies the binary under test and the authoritative-verification material. For an authoritative run, ExpectedDigest and Provenance are mandatory.
type CriterionResult ¶
type CriterionResult struct {
ID string `json:"id"`
Name string `json:"name"`
Group string `json:"group"`
Required bool `json:"required"`
Status Status `json:"status"`
Expected string `json:"expected"`
Observed string `json:"observed"`
Reason string `json:"reason,omitempty"`
Evidence []string `json:"evidence,omitempty"`
StartMS int64 `json:"start_ms"`
EndMS int64 `json:"end_ms"`
}
CriterionResult is the bounded, secret-free record of one acceptance criterion. Observed carries only a safe classification (a status code, a bounded reason code, a count) — never a token, key, raw argument, tool output, or path secret.
type Duration ¶
Duration is a JSON-friendly time.Duration ("30s", "2m").
func (Duration) MarshalJSON ¶
MarshalJSON renders the duration as its string form ("30s").
func (*Duration) UnmarshalJSON ¶
UnmarshalJSON accepts a duration string ("30s", "2m") or a bare number of seconds.
type EnvSpec ¶
type EnvSpec struct {
BindHost string `json:"bind_host"`
OAuthIssuer string `json:"oauth_issuer"`
CanonicalResource string `json:"canonical_resource"`
RequiredScopes []string `json:"required_scopes"`
AcceptedClientIDs []string `json:"accepted_client_ids"`
TenantA string `json:"tenant_a"`
TenantB string `json:"tenant_b"`
ServerA string `json:"server_a"`
ServerB string `json:"server_b"`
// Identity material by path (public + private references stay on disk, never
// enter the evidence bundle).
TLSCertFile string `json:"tls_cert_file"`
TLSKeyFile string `json:"tls_key_file"`
ServerCAFile string `json:"server_ca_file"` // CA the harness trusts for the listener TLS
ClientCAFile string `json:"client_ca_file"` // mTLS client CA the listener requires
ClientCertFile string `json:"client_cert_file"` // mTLS client cert (for the mTLS scenario)
ClientKeyFile string `json:"client_key_file"`
TrustedJWKS string `json:"trusted_jwks_file"`
// SigningKeyFile is the ES256 private key (PEM) the harness uses to mint the
// tenant-A/tenant-B bearer tokens for the run. It is a file reference; its bytes
// never appear in evidence.
SigningKeyFile string `json:"signing_key_file"`
SigningKID string `json:"signing_kid"`
// ── QUAL-6.1 authoritative controls ─────────────────────────────────────────
// GatewayPort is the operator-selected MCP Gateway listener port on the primary
// (proc A). The Gateway binds BindHost:GatewayPort; the harness (and any external
// supervisor) reaches the MCP boundary there. Auxiliary/negative-control processes
// bind ephemeral ports on the same host.
GatewayPort int `json:"gateway_port"`
// TLSServerName is the server-name the harness validates the listener TLS cert
// against. Optional; empty defaults to BindHost. The operator's server cert SAN
// must cover it, otherwise the TLS scenario fails truthfully.
TLSServerName string `json:"tls_server_name,omitempty"`
// QualificationPolicyFile is the operator-owned Culvert qualification policy file
// (the SAME production format the binary consumes at mcp.gateway
// .qualification_policy_file). The harness passes THIS file into the spawned
// config verbatim and never rewrites it; it never substitutes the dev fixture
// policy in authoritative mode. Only its digest and the resulting runtime
// revision/snapshot-hash enter evidence, never its source bytes.
QualificationPolicyFile string `json:"qualification_policy_file"`
// Telemetry is the operator-owned QUAL-3 durable-telemetry custody boundary. The
// harness consumes these production fields on the primary EXACTLY, never a
// temp-work-root equivalent, never generates or reads the KEK, and never deletes
// these paths on cleanup.
Telemetry *TelemetryEnv `json:"telemetry"`
// Supervision is the operator-accessible Admin + metrics boundary for live
// external supervision of the primary during the run. Credentials are file
// references only; their bytes never enter evidence.
Supervision *SupervisionEnv `json:"supervision"`
}
EnvSpec is the operator-provided qualification environment. Every field is an operator DECISION; the harness never invents hosts, issuers, tenants, or paths. In authoritative mode the harness consumes each of these values in the spawned artifact and PROVES it was consumed (QUAL-6.1); it never records a value it silently ignores.
type FinalizeError ¶
FinalizeError classifies why bundle finalization failed.
func (*FinalizeError) Error ¶
func (e *FinalizeError) Error() string
type Fixture ¶
type Fixture struct {
// contains filtered or unexported fields
}
Fixture is the ephemeral, harness-owned qualification environment used for dev / self-test runs. For an authoritative run the operator supplies this material externally (via EnvSpec); the fixture generator is the non-authoritative path.
func NewFixture ¶
func NewFixture(root string, secrets *SecretScan) (*Fixture, error)
NewFixture builds a complete ephemeral two-tenant fixture under root.
func NewFixtureFromEnv ¶
func NewFixtureFromEnv(root string, env *EnvSpec, secrets *SecretScan) (*Fixture, error)
NewFixtureFromEnv builds a fixture from operator-supplied material (authoritative mode). It reuses the SAME inventory/policy/config renderers as the ephemeral dev path — one strict code path — differing only in the source of the identity material. It never generates keys or trust roots; the operator supplies them.
type Harness ¶
type Harness struct {
// contains filtered or unexported fields
}
Harness drives a built artifact through the real MCP boundaries and produces the evidence bundle. It owns every child process, tripwire, and temporary secret.
func NewHarness ¶
NewHarness validates the spec and prepares a harness. The evidence directory is created; a separate temp work directory holds secrets and is never referenced by the evidence bundle.
func (*Harness) Run ¶
Run executes the full acceptance and returns the finalized summary. The overall status is FAIL if any required criterion fails, does not run, artifact identity is not authoritative when required, the secret scan trips, execution is detected, or cleanup fails. Run never begins Observe, never calls BeginWindow, and never changes rollout state.
type Manifest ¶
type Manifest struct {
SchemaVersion int `json:"schema_version"`
Entries []ManifestEntry `json:"entries"`
ManifestDigest string `json:"manifest_digest"`
}
Manifest is the per-file digest set plus the overall manifest digest. It makes accidental mutation of the bundle detectable. It is NOT a signature and confers no authorization — the bundle is test evidence, never a rollout receipt.
type ManifestEntry ¶
type ManifestEntry struct {
File string `json:"file"`
Digest string `json:"sha256"`
Bytes int64 `json:"bytes"`
}
ManifestEntry is one file's tamper-evidence record.
type Mode ¶
type Mode string
Mode selects the artifact-verification policy.
const ( // ModeAuthoritative requires a matching expected digest AND provenance binding // before any traffic. Its evidence is marked authoritative:true. ModeAuthoritative Mode = "authoritative" // ModeDev is the CI/self-test path against a locally built binary. Its evidence // is unmistakably marked authoritative:false and can never be accepted as // qualification evidence. Dev mode NEVER silently satisfies an authoritative run. ModeDev Mode = "dev" )
type Options ¶
type Options struct {
// SourceSHA is the harness's own source commit (for provenance of the run).
SourceSHA string
// Now injects a clock for deterministic tests. Defaults to time.Now.
Now func() time.Time
// RunHorizon is the requested overall bounded run timeout (the CLI -timeout).
// Authoritative pre-run PKI validation requires every qualification certificate
// chain to remain valid through now+RunHorizon, so material that would expire
// DURING the bounded run is rejected before any child process starts. Zero
// means no added horizon (chains are still required to be valid at `now`).
RunHorizon time.Duration
}
Options configure a harness run.
type Process ¶
type Process struct {
// contains filtered or unexported fields
}
Process is one running built-binary instance under harness ownership.
type ProvenanceSpec ¶
type ProvenanceSpec struct {
Verifier string `json:"verifier"` // e.g. "cosign-keyless"
Identity string `json:"identity"` // pinned issuer/SAN the operator verified against
VerifiedDigest string `json:"verified_digest"` // sha256:<hex> the operator verified; must equal the hashed binary
}
ProvenanceSpec carries the result of an out-of-band verification the operator performed with the ACCEPTED verifier (cosign keyless against the pinned identity). The harness does NOT re-implement signature verification; it binds the operator's verified digest to the exact hashed binary. A missing or mismatched provenance block fails an authoritative run — it is never a silent downgrade to non-authoritative.
type RunControl ¶
type RunControl struct {
StartupTimeout Duration `json:"startup_timeout"`
RequestTimeout Duration `json:"request_timeout"`
ShutdownTimeout Duration `json:"shutdown_timeout"`
RestartTimeout Duration `json:"restart_timeout"`
}
RunControl bounds every wait. Zero fields fall back to safe defaults; values above the strict maximum are clamped down (never up).
type SecretScan ¶
type SecretScan struct {
// contains filtered or unexported fields
}
SecretScan holds the set of exact sensitive values the harness generated so the finalized bundle can be proven not to contain any of them. It also applies the generic pattern scan. A hit fails the run; only a bounded classification is reported, never the offending value.
func (*SecretScan) Add ¶
func (s *SecretScan) Add(label, value string)
Add registers an exact sensitive value under a bounded classification label. Empty and very short values are ignored (they would false-positive on ordinary text and carry no secret entropy).
func (*SecretScan) Scan ¶
func (s *SecretScan) Scan(dir string) ([]Violation, error)
Scan reads every file under dir and reports bounded violations for any known secret value or generic secret pattern found. The offending value is never included — only its classification and the file it appeared in.
type Spec ¶
type Spec struct {
Mode Mode `json:"mode"`
Artifact ArtifactSpec `json:"artifact"`
Environment *EnvSpec `json:"environment,omitempty"`
Run RunControl `json:"run"`
EvidenceDir string `json:"evidence_dir"`
}
Spec is the operator-facing acceptance specification. It references identity material by file path (never inline secrets). In dev mode the environment may be omitted and the harness generates an ephemeral two-tenant fixture.
func (*Spec) ConfigHash ¶
ConfigHash is a deterministic sha256 over the canonical spec (which references secrets only by path — safe to hash and record).
type Status ¶
type Status string
Status is the outcome of a single acceptance criterion.
const ( // StatusPass — the criterion was proven at the required boundary. StatusPass Status = "PASS" // StatusFail — the criterion ran and did not meet its expected result. StatusFail Status = "FAIL" // StatusSkip — the criterion is explicitly out of Observe scope (never a // required criterion) and was deliberately not run. StatusSkip Status = "SKIP" )
type Summary ¶
type Summary struct {
SchemaVersion int `json:"schema_version"`
Authoritative bool `json:"authoritative"`
HarnessVersion string `json:"harness_version"`
HarnessSourceSHA string `json:"harness_source_sha,omitempty"`
Artifact ArtifactIdentity `json:"artifact"`
AcceptanceConfigHash string `json:"acceptance_config_hash"`
RunID string `json:"run_id"`
StartUTC string `json:"acceptance_run_start_utc"`
EndUTC string `json:"acceptance_run_end_utc"`
Overall Status `json:"overall"`
PolicyRevision uint64 `json:"policy_revision"`
PolicySnapshotHash string `json:"policy_snapshot_hash"`
InventoryIdentity string `json:"inventory_identity"`
InventoryRevision uint64 `json:"inventory_revision"`
TenantMatrix []TenantMatrixCell `json:"tenant_matrix"`
TelemetrySummary TelemetrySummary `json:"telemetry_summary"`
RestartResult Status `json:"restart_result"`
EmergencyDisable Status `json:"emergency_disable_result"`
NonExecution Status `json:"non_execution_result"`
// ── QUAL-6.1 effective-environment proof (v2; safe, secret-free) ──────────────
// EffectiveBindHost is the host the harness successfully connected the MCP
// listener on (the operator-selected bind host in authoritative mode). It is
// recorded only AFTER a successful TLS connection to that host, so it reflects the
// environment that actually ran, never merely a config value.
EffectiveBindHost string `json:"effective_bind_host,omitempty"`
// OperatorPolicyDigest is sha256:<hex> of the operator-supplied qualification
// policy FILE (authoritative mode only). It binds the operator's policy source to
// the runtime revision/snapshot-hash without copying the policy bytes into
// evidence.
OperatorPolicyDigest string `json:"operator_policy_digest,omitempty"`
// Supervision is the safe live-supervision descriptor: reachable Admin/metrics/
// Gateway URLs and credential PATH references (never secret values).
Supervision *SupervisionInfo `json:"supervision,omitempty"`
Criteria []CriterionResult `json:"criteria"`
// Notes carries bounded, non-authoritative operator notes (e.g. a documented
// known limitation such as the absent live user-rule ALLOW tools/call path).
Notes []string `json:"notes,omitempty"`
}
Summary is the top-level acceptance evidence summary (bundle.json).
type SupervisionEnv ¶ added in v1.0.200
type SupervisionEnv struct {
AdminPort int `json:"admin_port"`
MetricsPort int `json:"metrics_port"`
AdminUser string `json:"admin_user"`
AdminPasswordFile string `json:"admin_password_file"`
MetricsTokenFile string `json:"metrics_token_file"`
}
SupervisionEnv is the operator-accessible Admin + metrics listener + credential configuration for the primary. Ports are operator-selected so an external supervisor knows where to look; credentials are supplied by PATH only (never a raw secret field). The admin UI and proxy/metrics listeners bind all interfaces (existing product behavior) and stay protected by their own auth + optional IP allowlist.
type SupervisionInfo ¶ added in v1.0.200
type SupervisionInfo struct {
RunID string `json:"run_id"`
GatewayURL string `json:"gateway_url"`
AdminURL string `json:"admin_url"`
MetricsURL string `json:"metrics_url"`
AdminUser string `json:"admin_user"`
AdminCredentialRef string `json:"admin_credential_ref"` // file path only, never the password
MetricsCredentialRef string `json:"metrics_credential_ref"` // file path only, never the token
AdminReachable bool `json:"admin_reachable"`
MetricsReachable bool `json:"metrics_reachable"`
}
SupervisionInfo is the safe runtime-supervision descriptor for the operator. It carries reachable endpoint URLs, credential PATH references (never the values), and the run id — everything an external supervisor needs to observe the run, and nothing sensitive. It is also written standalone to supervision.json.
type TelemetryEnv ¶ added in v1.0.200
type TelemetryEnv struct {
NodeID string `json:"node_id"`
DataDir string `json:"data_dir"`
KEKFile string `json:"kek_file"`
ArchiveDir string `json:"archive_dir"`
}
TelemetryEnv is the operator-owned QUAL-3 telemetry configuration (production fields). The primary process consumes these paths verbatim; the restart scenario reuses the same data root + KEK + archive to prove durable persistence across a real process restart.
type TelemetrySummary ¶
type TelemetrySummary struct {
TelemetryReady bool `json:"telemetry_ready"`
DecisionTelemetry string `json:"decision_telemetry"`
EncryptionAvailable bool `json:"encryption_available"`
Committed bool `json:"decision_committed"`
DenialAggregated bool `json:"denial_aggregated"`
ExportedAfterRestart bool `json:"evidence_survived_restart"`
// Ownership classifies the primary's telemetry custody boundary: "operator" in an
// authoritative run (operator-owned data_dir/KEK/archive, preserved on cleanup),
// "harness" in dev (ephemeral, removable). NodeID is the safe logical node id.
Ownership string `json:"ownership,omitempty"`
NodeID string `json:"node_id,omitempty"`
}
TelemetrySummary is the bounded telemetry/spool/export health snapshot.
type TenantMatrixCell ¶
type TenantMatrixCell struct {
Token string `json:"token"` // "A" | "B" (which tenant minted the token)
Server string `json:"server"` // "A" | "B" (which tenant owns the addressed server)
Expected string `json:"expected"`
Observed string `json:"observed"`
CrossTenant bool `json:"cross_tenant"`
Status Status `json:"status"`
}
TenantMatrixCell records one direction of the two-tenant live matrix.