feat(plugin): add governance release gates
Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
Docker Image / docker (push) Has been cancelled

This commit is contained in:
2026-06-26 12:08:27 +08:00
parent b822993489
commit 629d6d5dbc
17 changed files with 2976 additions and 110 deletions

View File

@@ -43,5 +43,7 @@ func newAdminAPIHandler() http.HandlerFunc {
PluginRollback: handleAdminPluginRollback,
PluginDraining: handleAdminPluginDraining,
PluginDispatch: handleAdminPluginDispatchPlan,
PluginGovernance: handleAdminPluginGovernance,
PluginAdvisories: handleAdminPluginAdvisories,
})
}

View File

@@ -314,6 +314,105 @@ func TestAdminPluginPhase4API(t *testing.T) {
}
}
func TestAdminPluginPhase5GovernanceAPI(t *testing.T) {
handler := newAdminTestHandlerWithAdmin(t)
pluginsManager = pluginmanager.New(pluginmanager.Options{
DB: adminDB,
ArtifactRoot: filepath.Join(filepath.Dir(adminDBPath), "plugins", "artifacts"),
Adapter: gatewayTestPluginAdapter{},
})
adminToken := adminTestLogin(t, handler, "admin", "secret")
resp := adminTestRequest(t, handler, http.MethodPost, "/admin/api/users", adminToken, map[string]any{
"username": "member",
"role": "member",
"password": "member-secret",
})
if resp.Code != http.StatusCreated {
t.Fatalf("create member status = %d, body=%s", resp.Code, resp.Body.String())
}
memberToken := adminTestLogin(t, handler, "member", "member-secret")
artifact := uploadGatewayPhase5ProtocolProxyArtifact(t, "phase5-proxy")
if _, err := pluginsManager.SetDesired(context.Background(), "admin", "phase5-proxy", artifact.ID, pluginmanager.DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired() error = %v", err)
}
resp = adminTestRequest(t, handler, http.MethodGet, "/admin/api/plugins/phase5-proxy/governance?profile=prod", memberToken, nil)
if resp.Code != http.StatusOK {
t.Fatalf("member governance status = %d, body=%s", resp.Code, resp.Body.String())
}
if !strings.Contains(resp.Body.String(), "review_required") {
t.Fatalf("governance status body = %s, want review_required", resp.Body.String())
}
resp = adminTestRequest(t, handler, http.MethodPost, "/admin/api/plugins/phase5-proxy/governance/review", memberToken, map[string]any{
"artifact_id": artifact.ID,
"profile": pluginmanager.PolicyProfileProd,
})
if resp.Code != http.StatusForbidden {
t.Fatalf("member review write status = %d, want forbidden; body=%s", resp.Code, resp.Body.String())
}
resp = adminTestRequest(t, handler, http.MethodPost, "/admin/api/plugins/phase5-proxy/governance/review", adminToken, map[string]any{
"artifact_id": artifact.ID,
"profile": pluginmanager.PolicyProfileProd,
"decision": pluginmanager.ReviewDecisionApproved,
"notes": "phase 5 approval",
})
if resp.Code != http.StatusOK {
t.Fatalf("admin review write status = %d, body=%s", resp.Code, resp.Body.String())
}
resp = adminTestRequest(t, handler, http.MethodPost, "/admin/api/plugins/phase5-proxy/governance/preflight", adminToken, map[string]any{
"artifact_id": artifact.ID,
"profile": pluginmanager.PolicyProfileProd,
"action": pluginmanager.GovernanceActionEnable,
})
if resp.Code != http.StatusOK {
t.Fatalf("preflight status = %d, body=%s", resp.Code, resp.Body.String())
}
resp = adminTestRequest(t, handler, http.MethodPost, "/admin/api/plugins/phase5-proxy/governance/benchmark", adminToken, map[string]any{
"artifact_id": artifact.ID,
"profile": pluginmanager.PolicyProfileProd,
"benchmark_profile": "release",
"p95_ms": 10,
"p99_ms": 20,
"baseline_diff": 0.25,
"active_proxy_capacity": 100,
})
if resp.Code != http.StatusOK {
t.Fatalf("benchmark status = %d, body=%s", resp.Code, resp.Body.String())
}
resp = adminTestRequest(t, handler, http.MethodPost, "/admin/api/plugins/phase5-proxy/governance/override", adminToken, map[string]any{
"artifact_id": artifact.ID,
"profile": pluginmanager.PolicyProfileProd,
"action": pluginmanager.GovernanceActionEnable,
"reason": "accepted warning for rollout",
"ttl_seconds": 3600,
})
if resp.Code != http.StatusOK {
t.Fatalf("override status = %d, body=%s", resp.Code, resp.Body.String())
}
resp = adminTestRequest(t, handler, http.MethodPost, "/admin/api/plugin-advisories", adminToken, map[string]any{
"advisory_id": "MCG-2026-ADMIN",
"status": pluginmanager.AdvisoryStatusRevoked,
"action": pluginmanager.AdvisoryActionRevoke,
"artifact_sha256": artifact.SHA256,
})
if resp.Code != http.StatusOK {
t.Fatalf("advisory status = %d, body=%s", resp.Code, resp.Body.String())
}
resp = adminTestRequest(t, handler, http.MethodGet, "/admin/api/plugin-advisories?plugin_id=phase5-proxy", memberToken, nil)
if resp.Code != http.StatusOK || !strings.Contains(resp.Body.String(), "MCG-2026-ADMIN") {
t.Fatalf("member advisory read status = %d, body=%s", resp.Code, resp.Body.String())
}
resp = adminTestRequest(t, handler, http.MethodGet, "/admin/api/audit-logs", adminToken, nil)
if resp.Code != http.StatusOK {
t.Fatalf("audit status = %d, body=%s", resp.Code, resp.Body.String())
}
for _, action := range []string{"plugin_governance_review", "plugin_governance_override", "plugin_governance_advisory"} {
if !strings.Contains(resp.Body.String(), action) {
t.Fatalf("audit body missing %s: %s", action, resp.Body.String())
}
}
}
func uploadGatewayPhase4Artifact(t *testing.T, pluginID string) pluginmanager.ArtifactRecord {
t.Helper()
var manifest pluginmanager.Manifest
@@ -347,6 +446,31 @@ func uploadGatewayPhase4Artifact(t *testing.T, pluginID string) pluginmanager.Ar
return artifact
}
func uploadGatewayPhase5ProtocolProxyArtifact(t *testing.T, pluginID string) pluginmanager.ArtifactRecord {
t.Helper()
var manifest pluginmanager.Manifest
if err := json.Unmarshal(gatewayTestManifestWithCapabilities(t, pluginID, gatewayProtocolProxyCapabilities()), &manifest); err != nil {
t.Fatalf("Unmarshal manifest error = %v", err)
}
manifest.RuntimeLimits = pluginmanager.RuntimeLimits{HandlerTimeoutMS: 3000}
manifestBytes, err := json.Marshal(manifest)
if err != nil {
t.Fatalf("Marshal manifest error = %v", err)
}
artifact, err := pluginsManager.UploadArtifact(context.Background(), pluginmanager.ArtifactUpload{
SourcePath: writeGatewayTestMCGPEntries(t, map[string][]byte{
"manifest.json": manifestBytes,
"plugin.so": []byte("fake plugin bytes " + pluginID),
}),
FileName: pluginID + ".mcgp",
Actor: "admin",
})
if err != nil {
t.Fatalf("UploadArtifact() error = %v", err)
}
return artifact
}
func newAdminTestHandler(t *testing.T) http.Handler {
t.Helper()
t.Cleanup(saveGatewayState(t))

View File

@@ -117,6 +117,42 @@ export interface PluginProxyConnection {
draining: boolean;
}
export interface GovernanceIssue {
code: string;
severity: string;
message: string;
plugin_id?: string;
artifact_id?: string;
details?: Record<string, unknown>;
}
export interface GovernanceDecision {
ok: boolean;
action: string;
profile: string;
risk_level: string;
policy_hash: string;
review_required: boolean;
warning_override_used: boolean;
issues?: GovernanceIssue[];
checks?: GovernanceIssue[];
}
export interface GovernanceStatus {
decision?: GovernanceDecision;
policy?: Record<string, unknown>;
reviews?: Record<string, unknown>[];
warning_overrides?: Record<string, unknown>[];
preflights?: Record<string, unknown>[];
benchmarks?: Record<string, unknown>[];
advisories?: Record<string, unknown>[];
conflicts?: {
ok: boolean;
issues?: GovernanceIssue[];
plan?: unknown;
};
}
export interface PluginView {
id: string;
name?: string;
@@ -151,6 +187,8 @@ export interface PluginView {
manifest?: Record<string, unknown>;
active_proxy_connections?: number;
proxy_connections?: PluginProxyConnection[];
governance?: GovernanceStatus;
governance_error?: string;
updated_at?: number;
}

View File

@@ -167,6 +167,10 @@ export function renderPluginDetail(plugin: PluginView | null = selectedPlugin())
<dt>Minecraft</dt><dd>${escapeHTML(formatJSON(plugin.minecraft))}</dd>
</dl>
</section>
<section class="panel">
<h3>Governance</h3>
${governancePanel(plugin, canWrite)}
</section>
<section class="panel">
<h3>Config</h3>
<textarea id="pluginConfigEditor" ${canWrite ? "" : "readonly"}>${escapeHTML(prettyJSON(plugin.config_json || "{}"))}</textarea>
@@ -262,6 +266,12 @@ function bindPluginDetailEvents(plugin: PluginView): void {
document.querySelectorAll<HTMLButtonElement>("[data-snapshot-diff]").forEach((button) => {
button.addEventListener("click", () => showSnapshotDiff(plugin.id, Number(button.dataset.snapshotDiff || "0")));
});
document.getElementById("pluginGovernanceReviewBtn")?.addEventListener("click", () => createGovernanceReview(plugin));
document.getElementById("pluginGovernanceOverrideBtn")?.addEventListener("click", () => createGovernanceOverride(plugin));
document.getElementById("pluginGovernancePreflightBtn")?.addEventListener("click", () => runGovernancePreflight(plugin));
document.getElementById("pluginGovernanceSelfTestBtn")?.addEventListener("click", () => runGovernanceSelfTest(plugin));
document.getElementById("pluginGovernanceBenchmarkBtn")?.addEventListener("click", () => recordGovernanceBenchmark(plugin));
document.getElementById("pluginGovernanceAdvisoryBtn")?.addEventListener("click", () => createArtifactRevokeAdvisory(plugin));
}
async function dryRunConfig(plugin: PluginView): Promise<void> {
@@ -413,6 +423,117 @@ async function showSnapshotDiff(pluginID: string, snapshotID: number): Promise<v
}
}
async function createGovernanceReview(plugin: PluginView): Promise<void> {
try {
await api(`/plugins/${encodeURIComponent(plugin.id)}/governance/review`, {
method: "POST",
body: { artifact_id: plugin.desired_artifact_id, profile: "prod", decision: "approved" },
});
await loadPluginDetail(plugin.id);
showAlert("");
} catch (err) {
showAlert((err as Error).message);
}
}
async function createGovernanceOverride(plugin: PluginView): Promise<void> {
const reason = window.prompt("Reason");
if (!reason) {
return;
}
try {
await api(`/plugins/${encodeURIComponent(plugin.id)}/governance/override`, {
method: "POST",
body: { artifact_id: plugin.desired_artifact_id, profile: "prod", action: "enable", reason, ttl_seconds: 3600 },
});
await loadPluginDetail(plugin.id);
showAlert("");
} catch (err) {
showAlert((err as Error).message);
}
}
async function runGovernancePreflight(plugin: PluginView): Promise<void> {
try {
const data = await api<Record<string, unknown>>(`/plugins/${encodeURIComponent(plugin.id)}/governance/preflight`, {
method: "POST",
body: { artifact_id: plugin.desired_artifact_id, config_json: configEditorValue() },
});
el("pluginDryRunResult").textContent = formatJSON(data);
await loadPluginDetail(plugin.id);
showAlert("");
} catch (err) {
showAlert((err as Error).message);
}
}
async function runGovernanceSelfTest(plugin: PluginView): Promise<void> {
try {
const data = await api<Record<string, unknown>>(`/plugins/${encodeURIComponent(plugin.id)}/governance/self-test`, {
method: "POST",
body: { artifact_id: plugin.desired_artifact_id },
});
el("pluginDryRunResult").textContent = formatJSON(data);
await loadPluginDetail(plugin.id);
showAlert("");
} catch (err) {
showAlert((err as Error).message);
}
}
async function recordGovernanceBenchmark(plugin: PluginView): Promise<void> {
const diff = Number(window.prompt("Baseline diff, e.g. 0.25", "0.25"));
if (!Number.isFinite(diff)) {
return;
}
try {
await api(`/plugins/${encodeURIComponent(plugin.id)}/governance/benchmark`, {
method: "POST",
body: {
artifact_id: plugin.desired_artifact_id,
profile: "prod",
benchmark_profile: "manual",
p95_ms: 0,
p99_ms: 0,
error_rate: 0,
active_proxy_capacity: 0,
baseline_diff: diff,
},
});
await loadPluginDetail(plugin.id);
showAlert("");
} catch (err) {
showAlert((err as Error).message);
}
}
async function createArtifactRevokeAdvisory(plugin: PluginView): Promise<void> {
const artifact = plugin.desired_artifact;
if (!artifact) {
return;
}
const advisoryID = window.prompt("Advisory ID", `local-${shortID(artifact.sha256)}`);
if (!advisoryID) {
return;
}
try {
await api("/plugin-advisories", {
method: "POST",
body: {
advisory_id: advisoryID,
status: "revoked",
action: "revoke",
artifact_sha256: artifact.sha256,
recommended_action: "rollback or upgrade",
},
});
await loadPluginDetail(plugin.id);
showAlert("");
} catch (err) {
showAlert((err as Error).message);
}
}
function uploadInventoryDetail(): string {
const artifact = selectedArtifact();
if (!artifact) {
@@ -530,6 +651,50 @@ function pluginActionButtons(plugin: PluginView): string {
`;
}
function governancePanel(plugin: PluginView, canWrite: boolean): string {
if (plugin.governance_error) {
return `<div class="alert inline-alert">${escapeHTML(plugin.governance_error)}</div>`;
}
const governance = plugin.governance;
const decision = governance?.decision;
const issues = decision?.issues || [];
return `
<dl class="kv">
<dt>Profile</dt><dd>${escapeHTML(decision?.profile || "")}</dd>
<dt>Risk</dt><dd>${escapeHTML(decision?.risk_level || "")}</dd>
<dt>Policy</dt><dd>${escapeHTML(shortID(decision?.policy_hash || ""))}</dd>
<dt>Decision</dt><dd>${badge(decision?.ok ? "allowed" : "blocked", !decision?.ok)}</dd>
<dt>Review</dt><dd>${badge(decision?.review_required ? "required" : "not required", Boolean(decision?.review_required))}</dd>
<dt>Override</dt><dd>${badge(decision?.warning_override_used ? "used" : "not used", Boolean(decision?.warning_override_used))}</dd>
</dl>
${issues.length ? `<div class="mini-list">${issues.map((issue) => `
<div class="mini-row">
<span>${badge(issue.severity, issue.severity !== "info")}</span>
<span>${escapeHTML(issue.code)}</span>
<span>${escapeHTML(issue.message)}</span>
</div>
`).join("")}</div>` : `<div class="empty">No governance issues</div>`}
${canWrite ? `
<div class="row-actions">
<button class="secondary" type="button" id="pluginGovernanceReviewBtn">Review</button>
<button class="secondary" type="button" id="pluginGovernanceOverrideBtn">Override</button>
<button class="secondary" type="button" id="pluginGovernancePreflightBtn">Preflight</button>
<button class="secondary" type="button" id="pluginGovernanceSelfTestBtn">Self-test</button>
<button class="secondary" type="button" id="pluginGovernanceBenchmarkBtn">Benchmark</button>
<button class="danger" type="button" id="pluginGovernanceAdvisoryBtn">Revoke artifact</button>
</div>
` : ""}
<pre class="log-output">${escapeHTML(formatJSON({
conflicts: governance?.conflicts,
reviews: governance?.reviews || [],
warning_overrides: governance?.warning_overrides || [],
preflights: governance?.preflights || [],
benchmarks: governance?.benchmarks || [],
advisories: governance?.advisories || [],
}))}</pre>
`;
}
function artifactList(artifacts: PluginArtifact[], plugin: PluginView): string {
if (artifacts.length === 0) {
return `<div class="empty">No artifacts</div>`;

View File

@@ -671,6 +671,175 @@ func handleAdminPluginDispatchPlan(w http.ResponseWriter, r *http.Request) {
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"dispatch_plan": pluginsManager.DispatchPlan(r.Context())})
}
func handleAdminPluginGovernance(w http.ResponseWriter, r *http.Request, rawSegment string) {
session, ok := requireRole(w, r, adminRoleMember)
if !ok {
return
}
if pluginsManager == nil {
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
return
}
pluginID, action, ok := splitPluginSubresource(w, rawSegment, "governance")
if !ok {
return
}
switch {
case r.Method == http.MethodGet && action == "":
status, err := pluginsManager.GovernanceStatus(r.Context(), pluginID, r.URL.Query().Get("artifact_id"), r.URL.Query().Get("profile"))
if err != nil {
writePluginManagerError(w, err)
return
}
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"governance": status})
case r.Method == http.MethodPost && action == "review":
if session.Role != adminRoleAdmin {
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
return
}
var req pluginmanager.GovernanceReviewRequest
if !adminhttp.DecodeJSONRequest(w, r, &req) {
return
}
review, err := pluginsManager.CreateReview(r.Context(), session.Username, pluginID, req)
if err != nil {
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_review", "plugin", pluginID, false, err.Error())
writePluginManagerError(w, err)
return
}
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_review", "plugin", pluginID, true, "governance review recorded", map[string]any{
"artifact_id": review.ArtifactID,
"profile": review.Profile,
"policy_hash": review.PolicyHash,
"decision": review.Decision,
})
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"review": review})
case r.Method == http.MethodPost && action == "override":
if session.Role != adminRoleAdmin {
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
return
}
var req pluginmanager.WarningOverrideRequest
if !adminhttp.DecodeJSONRequest(w, r, &req) {
return
}
override, err := pluginsManager.CreateWarningOverride(r.Context(), session.Username, pluginID, req)
if err != nil {
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_override", "plugin", pluginID, false, err.Error())
writePluginManagerError(w, err)
return
}
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_override", "plugin", pluginID, true, "governance warning override recorded", map[string]any{
"artifact_id": override.ArtifactID,
"profile": override.Profile,
"action": override.Action,
"expires_at": override.ExpiresAt,
})
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"override": override})
case r.Method == http.MethodPost && action == "preflight":
if session.Role != adminRoleAdmin {
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
return
}
var req pluginmanager.PreflightRequest
if !adminhttp.DecodeJSONRequest(w, r, &req) {
return
}
result, err := pluginsManager.RunPreflight(r.Context(), session.Username, pluginID, req)
if err != nil {
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_preflight", "plugin", pluginID, false, err.Error())
writePluginManagerError(w, err)
return
}
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_preflight", "plugin", pluginID, result.OK, "governance preflight completed", map[string]any{"profile": result.Profile})
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"preflight": result})
case r.Method == http.MethodPost && action == "self-test":
if session.Role != adminRoleAdmin {
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
return
}
var req pluginmanager.SelfTestRequest
if !adminhttp.DecodeJSONRequest(w, r, &req) {
return
}
result, err := pluginsManager.RunSelfTest(r.Context(), session.Username, pluginID, req)
if err != nil {
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_self_test", "plugin", pluginID, false, err.Error())
writePluginManagerError(w, err)
return
}
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_self_test", "plugin", pluginID, result.OK, "governance self-test completed", map[string]any{"profile": result.Profile})
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"self_test": result})
case r.Method == http.MethodPost && action == "benchmark":
if session.Role != adminRoleAdmin {
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
return
}
var req pluginmanager.BenchmarkRequest
if !adminhttp.DecodeJSONRequest(w, r, &req) {
return
}
benchmark, err := pluginsManager.SaveBenchmark(r.Context(), session.Username, req)
if err != nil {
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_benchmark", "plugin", pluginID, false, err.Error())
writePluginManagerError(w, err)
return
}
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_benchmark", "plugin", pluginID, true, "governance benchmark recorded", map[string]any{
"artifact_id": benchmark.ArtifactID,
"profile": benchmark.Profile,
"baseline_diff": benchmark.BaselineDiff,
})
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"benchmark": benchmark})
default:
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
}
}
func handleAdminPluginAdvisories(w http.ResponseWriter, r *http.Request) {
session, ok := requireRole(w, r, adminRoleMember)
if !ok {
return
}
if pluginsManager == nil {
adminhttp.WriteAPIError(w, http.StatusServiceUnavailable, "plugin manager is not initialized")
return
}
switch r.Method {
case http.MethodGet:
advisories, err := pluginsManager.ListAdvisories(r.Context(), r.URL.Query().Get("plugin_id"))
if err != nil {
adminhttp.WriteAPIError(w, http.StatusInternalServerError, err.Error())
return
}
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"advisories": advisories})
case http.MethodPost:
if session.Role != adminRoleAdmin {
adminhttp.WriteAPIError(w, http.StatusForbidden, "forbidden")
return
}
var req pluginmanager.AdvisoryRequest
if !adminhttp.DecodeJSONRequest(w, r, &req) {
return
}
advisory, err := pluginsManager.UpsertAdvisory(r.Context(), session.Username, req)
if err != nil {
recordAudit(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_advisory", "plugin_advisory", req.AdvisoryID, false, err.Error())
writePluginManagerError(w, err)
return
}
recordAuditMetadata(r.Context(), session.Username, adminhttp.RequestSourceIP(r), "plugin_governance_advisory", "plugin_advisory", advisory.AdvisoryID, true, "plugin advisory upserted", map[string]any{
"action": advisory.Action,
"status": advisory.Status,
"artifact_sha256": advisory.ArtifactSHA256,
"plugin_id": advisory.PluginID,
})
adminhttp.WriteJSON(w, http.StatusOK, map[string]any{"advisory": advisory})
default:
adminhttp.WriteAPIError(w, http.StatusMethodNotAllowed, "method not allowed")
}
}
func receivePluginArtifact(r *http.Request, actor string) (pluginmanager.ArtifactRecord, error) {
if err := r.ParseMultipartForm(64 << 20); err != nil {
return pluginmanager.ArtifactRecord{}, err
@@ -870,6 +1039,12 @@ func pluginView(r *http.Request, plugin pluginmanager.PluginRecord, detail bool)
if detail {
view["manifest"] = manifest
view["operations_path"] = "/plugin-artifacts?plugin_id=" + plugin.ID
governance, err := pluginsManager.GovernanceStatus(r.Context(), plugin.ID, plugin.DesiredArtifactID, pluginsManager.PolicyProfile())
if err != nil {
view["governance_error"] = err.Error()
} else {
view["governance"] = governance
}
view["active_proxy_connections"] = activeProxyConnections(plugin)
connections, err := pluginsManager.ActiveProxyConnections(r.Context(), plugin.ID)
if err != nil {

View File

@@ -90,10 +90,11 @@ func TestHandleRequestProtocolProxyReplaysInitialDataOnce(t *testing.T) {
return gatewayEnd, nil
}},
})
artifact := uploadGatewayTestArtifactWithCapabilities(t, pluginsManager, "proxy-plugin", `{"upstream_connect":{"mode":"protocol-proxy"}}`)
artifact := uploadGatewayTestArtifactWithCapabilities(t, pluginsManager, "proxy-plugin", gatewayProtocolProxyCapabilities())
if _, err := pluginsManager.SetDesired(context.Background(), "admin", "proxy-plugin", artifact.ID, pluginmanager.DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired() error = %v", err)
}
approveGatewayPluginGovernanceForTest(t, "proxy-plugin", artifact.ID)
if _, err := pluginsManager.Enable(context.Background(), "admin", "proxy-plugin"); err != nil {
t.Fatalf("Enable() error = %v", err)
}
@@ -216,10 +217,11 @@ func TestHandleRequestProtocolProxyDisableSkipsNewConnections(t *testing.T) {
return gatewayEnd, nil
}},
})
artifact := uploadGatewayTestArtifactWithCapabilities(t, pluginsManager, "proxy-plugin", `{"upstream_connect":{"mode":"protocol-proxy"}}`)
artifact := uploadGatewayTestArtifactWithCapabilities(t, pluginsManager, "proxy-plugin", gatewayProtocolProxyCapabilities())
if _, err := pluginsManager.SetDesired(context.Background(), "admin", "proxy-plugin", artifact.ID, pluginmanager.DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired() error = %v", err)
}
approveGatewayPluginGovernanceForTest(t, "proxy-plugin", artifact.ID)
if _, err := pluginsManager.Enable(context.Background(), "admin", "proxy-plugin"); err != nil {
t.Fatalf("Enable() error = %v", err)
}

View File

@@ -279,6 +279,18 @@ func uploadGatewayTestArtifactWithCapabilities(t *testing.T, manager *pluginmana
return artifact
}
func approveGatewayPluginGovernanceForTest(t *testing.T, pluginID, artifactID string) {
t.Helper()
if _, err := pluginsManager.CreateReview(context.Background(), "admin", pluginID, pluginmanager.GovernanceReviewRequest{
ArtifactID: artifactID,
Profile: pluginmanager.PolicyProfileProd,
Decision: pluginmanager.ReviewDecisionApproved,
Notes: "test approval",
}); err != nil {
t.Fatalf("CreateReview(%s) error = %v", pluginID, err)
}
}
func writeGatewayTestMCGP(t *testing.T, pluginID string) string {
return writeGatewayTestMCGPWithCapabilities(t, pluginID, "")
}
@@ -322,6 +334,18 @@ func gatewayTestManifest(t *testing.T, pluginID string) []byte {
return gatewayTestManifestWithCapabilities(t, pluginID, "")
}
func gatewayProtocolProxyCapabilities() string {
return `{
"upstream_connect":{"mode":"protocol-proxy"},
"scope":{"type":"host","values":["play.example"]},
"rollout":{"mode":"canary"},
"minecraft":{
"protocol_versions":{"tested":[767]},
"forwarding":{"supported":["none"],"default":"none"}
}
}`
}
func gatewayTestManifestWithCapabilities(t *testing.T, pluginID string, capabilities string) []byte {
t.Helper()
if capabilities == "" {

View File

@@ -209,6 +209,81 @@ CREATE TABLE IF NOT EXISTS plugin_secrets (
PRIMARY KEY(plugin_id, name)
);
CREATE TABLE IF NOT EXISTS plugin_reviews (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plugin_id TEXT NOT NULL,
artifact_id TEXT NOT NULL,
profile TEXT NOT NULL DEFAULT 'dev',
risk_level TEXT NOT NULL DEFAULT 'low',
config_hash TEXT NOT NULL DEFAULT '',
scope_hash TEXT NOT NULL DEFAULT '',
rollout_hash TEXT NOT NULL DEFAULT '',
runtime_limits_hash TEXT NOT NULL DEFAULT '',
features_hash TEXT NOT NULL DEFAULT '',
policy_hash TEXT NOT NULL DEFAULT '',
decision TEXT NOT NULL DEFAULT 'approved',
notes TEXT NOT NULL DEFAULT '',
reviewed_by TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS plugin_warning_overrides (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plugin_id TEXT NOT NULL,
artifact_id TEXT NOT NULL,
profile TEXT NOT NULL DEFAULT 'dev',
action TEXT NOT NULL DEFAULT '',
policy_hash TEXT NOT NULL DEFAULT '',
reason TEXT NOT NULL DEFAULT '',
created_by TEXT NOT NULL DEFAULT '',
expires_at INTEGER NOT NULL,
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS plugin_advisories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
advisory_id TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
action TEXT NOT NULL DEFAULT 'denylist',
artifact_sha256 TEXT NOT NULL DEFAULT '',
plugin_id TEXT NOT NULL DEFAULT '',
version_range TEXT NOT NULL DEFAULT '',
dependency_name TEXT NOT NULL DEFAULT '',
dependency_range TEXT NOT NULL DEFAULT '',
recommended_action TEXT NOT NULL DEFAULT '',
fixed_version TEXT NOT NULL DEFAULT '',
mitigation TEXT NOT NULL DEFAULT '',
created_by TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS plugin_preflight_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plugin_id TEXT NOT NULL,
artifact_id TEXT NOT NULL,
profile TEXT NOT NULL DEFAULT 'dev',
status TEXT NOT NULL DEFAULT '',
result_json TEXT NOT NULL DEFAULT '{}',
created_by TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS plugin_benchmarks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plugin_id TEXT NOT NULL,
artifact_id TEXT NOT NULL,
profile TEXT NOT NULL DEFAULT 'dev',
benchmark_profile TEXT NOT NULL DEFAULT '',
p95_ms REAL NOT NULL DEFAULT 0,
p99_ms REAL NOT NULL DEFAULT 0,
error_rate REAL NOT NULL DEFAULT 0,
active_proxy_capacity INTEGER NOT NULL DEFAULT 0,
baseline_diff REAL NOT NULL DEFAULT 0,
created_by TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_routes_enabled ON routes(enabled);
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);
CREATE INDEX IF NOT EXISTS idx_plugin_artifacts_plugin_id ON plugin_artifacts(plugin_id, created_at);
@@ -218,6 +293,12 @@ CREATE INDEX IF NOT EXISTS idx_plugin_builds_plugin_id ON plugin_builds(plugin_i
CREATE INDEX IF NOT EXISTS idx_plugin_builds_source_id ON plugin_builds(source_id, created_at);
CREATE INDEX IF NOT EXISTS idx_plugin_config_snapshots_plugin_id ON plugin_config_snapshots(plugin_id, created_at);
CREATE INDEX IF NOT EXISTS idx_plugin_secrets_plugin_id ON plugin_secrets(plugin_id, updated_at);
CREATE INDEX IF NOT EXISTS idx_plugin_reviews_lookup ON plugin_reviews(plugin_id, artifact_id, profile, policy_hash, created_at);
CREATE INDEX IF NOT EXISTS idx_plugin_warning_overrides_lookup ON plugin_warning_overrides(plugin_id, artifact_id, profile, action, expires_at);
CREATE INDEX IF NOT EXISTS idx_plugin_advisories_artifact ON plugin_advisories(artifact_sha256, status);
CREATE INDEX IF NOT EXISTS idx_plugin_advisories_plugin ON plugin_advisories(plugin_id, status);
CREATE INDEX IF NOT EXISTS idx_plugin_preflight_lookup ON plugin_preflight_results(plugin_id, artifact_id, profile, created_at);
CREATE INDEX IF NOT EXISTS idx_plugin_benchmarks_lookup ON plugin_benchmarks(plugin_id, artifact_id, profile, created_at);
INSERT OR IGNORE INTO schema_migrations(version, applied_at) VALUES (1, strftime('%s','now'));
`
if _, err := db.Exec(schema); err != nil {

View File

@@ -43,6 +43,8 @@ type APIHandlers struct {
PluginRollback SegmentHandlerFunc
PluginDraining SegmentHandlerFunc
PluginDispatch http.HandlerFunc
PluginGovernance SegmentHandlerFunc
PluginAdvisories http.HandlerFunc
}
func NewAPIHandler(prefix string, handlers APIHandlers) http.HandlerFunc {
@@ -100,8 +102,14 @@ func NewAPIHandler(prefix string, handlers APIHandlers) http.HandlerFunc {
callHandler(w, r, handlers.PluginsList)
case path == "/plugins/dispatch-plan" && r.Method == http.MethodGet:
callHandler(w, r, handlers.PluginDispatch)
case path == "/plugin-advisories" && (r.Method == http.MethodGet || r.Method == http.MethodPost):
callHandler(w, r, handlers.PluginAdvisories)
case strings.HasPrefix(path, "/plugins/"):
pluginPath := strings.TrimPrefix(path, "/plugins/")
if strings.Contains(pluginPath, "/governance/") || strings.HasSuffix(pluginPath, "/governance") {
callSegmentHandler(w, r, handlers.PluginGovernance, pluginPath)
return
}
if strings.HasSuffix(pluginPath, "/draining/force-close") {
callSegmentHandler(w, r, handlers.PluginDraining, strings.TrimSuffix(pluginPath, "/draining/force-close"))
return

View File

@@ -44,8 +44,11 @@ func TestNewAPIHandlerRoutesRequests(t *testing.T) {
{name: "plugin config dry-run", method: http.MethodPost, path: "/admin/api/plugins/upstream-rewrite/config/dry-run", wantCall: "plugin_config", wantSegment: "upstream-rewrite/config/dry-run"},
{name: "plugin secrets", method: http.MethodGet, path: "/admin/api/plugins/upstream-rewrite/secrets", wantCall: "plugin_secrets", wantSegment: "upstream-rewrite/secrets"},
{name: "plugin rollback artifact", method: http.MethodPost, path: "/admin/api/plugins/upstream-rewrite/rollback/artifact", wantCall: "plugin_rollback", wantSegment: "upstream-rewrite/rollback/artifact"},
{name: "plugin governance", method: http.MethodGet, path: "/admin/api/plugins/upstream-rewrite/governance", wantCall: "plugin_governance", wantSegment: "upstream-rewrite/governance"},
{name: "plugin governance review", method: http.MethodPost, path: "/admin/api/plugins/upstream-rewrite/governance/review", wantCall: "plugin_governance", wantSegment: "upstream-rewrite/governance/review"},
{name: "plugin draining force close", method: http.MethodPost, path: "/admin/api/plugins/mc-auth-proxy/draining/force-close", wantCall: "plugin_draining", wantSegment: "mc-auth-proxy"},
{name: "plugin dispatch", method: http.MethodGet, path: "/admin/api/plugins/dispatch-plan", wantCall: "plugin_dispatch"},
{name: "plugin advisories", method: http.MethodGet, path: "/admin/api/plugin-advisories", wantCall: "plugin_advisories"},
}
for _, tt := range tests {
@@ -87,6 +90,8 @@ func TestNewAPIHandlerRoutesRequests(t *testing.T) {
PluginRollback: recordSegmentCall(&gotCall, &gotSegment, "plugin_rollback"),
PluginDraining: recordSegmentCall(&gotCall, &gotSegment, "plugin_draining"),
PluginDispatch: recordCall(&gotCall, "plugin_dispatch"),
PluginGovernance: recordSegmentCall(&gotCall, &gotSegment, "plugin_governance"),
PluginAdvisories: recordCall(&gotCall, "plugin_advisories"),
})
resp := httptest.NewRecorder()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,174 @@
package pluginmanager
import (
"context"
"encoding/json"
"strings"
"testing"
"time"
)
func TestGovernanceHighRiskProtocolProxyRequiresReview(t *testing.T) {
manager := newManagerForTest(t, &fakeAdapter{})
artifact := uploadTestArtifactWithCapabilities(t, manager, "proxy-review", testProtocolProxyCapabilities())
if _, err := manager.SetDesired(context.Background(), "admin", "proxy-review", artifact.ID, DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired() error = %v", err)
}
_, err := manager.Enable(context.Background(), "admin", "proxy-review")
if err == nil || !strings.Contains(err.Error(), "review_required") {
t.Fatalf("Enable() error = %v, want review_required", err)
}
if _, err := manager.CreateReview(context.Background(), "admin", "proxy-review", GovernanceReviewRequest{
ArtifactID: artifact.ID,
Profile: PolicyProfileProd,
Decision: ReviewDecisionApproved,
}); err != nil {
t.Fatalf("CreateReview() error = %v", err)
}
if _, err := manager.Enable(context.Background(), "admin", "proxy-review"); err != nil {
t.Fatalf("Enable(after review) error = %v", err)
}
}
func TestGovernanceBlocksProtocolProxyScopeOverlap(t *testing.T) {
manager := newManagerForTest(t, &fakeAdapter{})
first := enableProtocolProxyTestPlugin(t, manager, "proxy-a")
if first.ID == "" {
t.Fatal("first protocol proxy artifact id is empty")
}
second := uploadTestArtifactWithCapabilities(t, manager, "proxy-b", testProtocolProxyCapabilities())
if _, err := manager.SetDesired(context.Background(), "admin", "proxy-b", second.ID, DesiredEnabled, `{}`, 20); err != nil {
t.Fatalf("SetDesired(second) error = %v", err)
}
approveGovernanceForTest(t, manager, "proxy-b", second.ID)
_, err := manager.Enable(context.Background(), "admin", "proxy-b")
if err == nil || !strings.Contains(err.Error(), "scope_overlap") {
t.Fatalf("Enable(second) error = %v, want scope_overlap", err)
}
}
func TestGovernanceBlocksMissingFeatureAndSecret(t *testing.T) {
manager := newManagerForTest(t, &fakeAdapter{})
artifact := uploadTestArtifactWithManifest(t, manager, "feature-secret", func(manifest *Manifest) {
manifest.Capabilities = json.RawMessage(`{"required_features":["wasm-sandbox"]}`)
manifest.Secrets = []SecretSpec{{Name: "api_token", Required: true}}
})
if _, err := manager.SetDesired(context.Background(), "admin", "feature-secret", artifact.ID, DesiredEnabled, `{}`, 10); err == nil {
t.Fatal("SetDesired() error = nil, want missing secret from dry-run")
}
if _, err := manager.UpsertSecret(context.Background(), "admin", "feature-secret", artifact.ID, "api_token", "secret", true, false); err != nil {
t.Fatalf("UpsertSecret() error = %v", err)
}
if _, err := manager.SetDesired(context.Background(), "admin", "feature-secret", artifact.ID, DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired(after secret) error = %v", err)
}
_, err := manager.Enable(context.Background(), "admin", "feature-secret")
if err == nil || !strings.Contains(err.Error(), "feature_missing") {
t.Fatalf("Enable() error = %v, want feature_missing", err)
}
}
func TestGovernanceAdvisoryRevokeBlocksRollback(t *testing.T) {
manager := newManagerForTest(t, &fakeAdapter{})
oldArtifact := uploadTestArtifact(t, manager, "revoke-plugin")
if _, err := manager.SetDesired(context.Background(), "admin", "revoke-plugin", oldArtifact.ID, DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired(old) error = %v", err)
}
if _, err := manager.Enable(context.Background(), "admin", "revoke-plugin"); err != nil {
t.Fatalf("Enable(old) error = %v", err)
}
newArtifact := uploadTestArtifactWithManifest(t, manager, "revoke-plugin", func(manifest *Manifest) {
manifest.Version = "0.2.0"
})
if _, err := manager.SetDesired(context.Background(), "admin", "revoke-plugin", newArtifact.ID, DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired(new) error = %v", err)
}
if _, err := manager.Enable(context.Background(), "admin", "revoke-plugin"); err != nil {
t.Fatalf("Enable(new) error = %v", err)
}
if _, err := manager.UpsertAdvisory(context.Background(), "admin", AdvisoryRequest{
AdvisoryID: "MCG-2026-0001",
Status: AdvisoryStatusRevoked,
Action: AdvisoryActionRevoke,
ArtifactSHA256: oldArtifact.SHA256,
}); err != nil {
t.Fatalf("UpsertAdvisory() error = %v", err)
}
_, err := manager.RollbackArtifact(context.Background(), "admin", "revoke-plugin", oldArtifact.ID)
if err == nil || !strings.Contains(err.Error(), "advisory_revoke") {
t.Fatalf("RollbackArtifact() error = %v, want advisory_revoke", err)
}
}
func TestGovernanceWarningOverrideTTL(t *testing.T) {
now := time.Unix(1000, 0)
db := openPluginManagerTestDB(t)
manager := New(Options{
DB: db,
ArtifactRoot: t.TempDir(),
Adapter: &fakeAdapter{},
})
manager.repo.now = func() time.Time { return now }
artifact := uploadTestArtifact(t, manager, "bench-plugin")
if _, err := manager.SetDesired(context.Background(), "admin", "bench-plugin", artifact.ID, DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired() error = %v", err)
}
if _, err := manager.SaveBenchmark(context.Background(), "admin", BenchmarkRequest{
ArtifactID: artifact.ID,
Profile: PolicyProfileProd,
BenchmarkProfile: "release",
P95MS: 10,
P99MS: 20,
BaselineDiff: 0.30,
}); err != nil {
t.Fatalf("SaveBenchmark() error = %v", err)
}
_, err := manager.Enable(context.Background(), "admin", "bench-plugin")
if err == nil || !strings.Contains(err.Error(), "benchmark_regression_warning") {
t.Fatalf("Enable() error = %v, want benchmark warning", err)
}
if _, err := manager.CreateWarningOverride(context.Background(), "admin", "bench-plugin", WarningOverrideRequest{
ArtifactID: artifact.ID,
Profile: PolicyProfileProd,
Action: GovernanceActionEnable,
Reason: "accepted for canary",
TTLSeconds: 60,
}); err != nil {
t.Fatalf("CreateWarningOverride() error = %v", err)
}
if _, err := manager.Enable(context.Background(), "admin", "bench-plugin"); err != nil {
t.Fatalf("Enable(with override) error = %v", err)
}
if _, err := manager.Disable(context.Background(), "admin", "bench-plugin"); err != nil {
t.Fatalf("Disable() error = %v", err)
}
now = now.Add(2 * time.Minute)
_, err = manager.Enable(context.Background(), "admin", "bench-plugin")
if err == nil || !strings.Contains(err.Error(), "benchmark_regression_warning") {
t.Fatalf("Enable(after override expiry) error = %v, want benchmark warning", err)
}
}
func TestGovernanceBenchmarkBlocking(t *testing.T) {
manager := newManagerForTest(t, &fakeAdapter{})
artifact := uploadTestArtifactWithManifest(t, manager, "bench-block", func(manifest *Manifest) {
manifest.RuntimeLimits.HandlerTimeoutMS = 100
})
if _, err := manager.SetDesired(context.Background(), "admin", "bench-block", artifact.ID, DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired() error = %v", err)
}
if _, err := manager.SaveBenchmark(context.Background(), "admin", BenchmarkRequest{
ArtifactID: artifact.ID,
Profile: PolicyProfileProd,
BenchmarkProfile: "release",
P95MS: 80,
P99MS: 200,
BaselineDiff: 0.60,
}); err != nil {
t.Fatalf("SaveBenchmark() error = %v", err)
}
_, err := manager.Enable(context.Background(), "admin", "bench-block")
if err == nil || !strings.Contains(err.Error(), "benchmark_regression_blocking") {
t.Fatalf("Enable() error = %v, want benchmark_regression_blocking", err)
}
}

View File

@@ -31,6 +31,52 @@ type ConfigDryRunAdapter interface {
type GoPluginAdapter struct{}
func (a GoPluginAdapter) Load(ctx context.Context, artifact ArtifactRecord, pluginRecord PluginRecord, gateway *Gateway) (api.Plugin, error) {
return a.instantiate(ctx, artifact, pluginRecord, gateway, true)
}
func (a GoPluginAdapter) DryRunConfig(ctx context.Context, artifact ArtifactRecord, pluginRecord PluginRecord) error {
_ = ctx
_, err := a.instantiate(ctx, artifact, pluginRecord, nil, false)
return err
}
func (a GoPluginAdapter) RunPreflight(ctx context.Context, artifact ArtifactRecord, pluginRecord PluginRecord, profile, action string) (api.PreflightResult, error) {
instance, err := a.instantiate(ctx, artifact, pluginRecord, nil, false)
if err != nil {
return api.PreflightResult{}, err
}
checker, ok := instance.(api.PreflightChecker)
if !ok {
return api.PreflightResult{}, nil
}
var config map[string]any
_ = json.Unmarshal([]byte(defaultJSONObject(pluginRecord.ConfigJSON)), &config)
return checker.Preflight(api.PreflightContext{
PluginID: pluginRecord.ID,
ArtifactID: artifact.ID,
Profile: profile,
Action: action,
Config: config,
Scope: jsonObjectFromRaw(manifestCapabilitiesRaw(artifact), "scope"),
Rollout: jsonObjectFromRaw(manifestCapabilitiesRaw(artifact), "rollout"),
RuntimeLimits: jsonObjectFromRaw(artifact.MetadataJSON, "runtime_limits"),
Features: stringSlice(jsonObjectFromRaw(manifestCapabilitiesRaw(artifact), "required_features")),
})
}
func (a GoPluginAdapter) RunSelfTest(ctx context.Context, artifact ArtifactRecord, pluginRecord PluginRecord, profile string) (api.SelfTestResult, error) {
instance, err := a.instantiate(ctx, artifact, pluginRecord, nil, false)
if err != nil {
return api.SelfTestResult{}, err
}
tester, ok := instance.(api.SelfTester)
if !ok {
return api.SelfTestResult{}, nil
}
return tester.SelfTest(api.SelfTestProfile{Name: profile})
}
func (a GoPluginAdapter) instantiate(ctx context.Context, artifact ArtifactRecord, pluginRecord PluginRecord, gateway *Gateway, init bool) (api.Plugin, error) {
_ = ctx
opened, err := stdplugin.Open(artifact.FilePath)
if err != nil {
@@ -59,44 +105,14 @@ func (a GoPluginAdapter) Load(ctx context.Context, artifact ArtifactRecord, plug
if err := instance.ReloadConfig(cfg); err != nil {
return nil, err
}
if init {
if err := instance.Init(gateway); err != nil {
return nil, err
}
}
return instance, nil
}
func (a GoPluginAdapter) DryRunConfig(ctx context.Context, artifact ArtifactRecord, pluginRecord PluginRecord) error {
_ = ctx
opened, err := stdplugin.Open(artifact.FilePath)
if err != nil {
return err
}
symbolName := "Plugin"
var manifest Manifest
if err := json.Unmarshal([]byte(artifact.MetadataJSON), &manifest); err == nil && manifest.Runtime.EntrySymbol != "" {
symbolName = manifest.Runtime.EntrySymbol
}
symbol, err := opened.Lookup(symbolName)
if err != nil {
return err
}
factory, ok := symbol.(func() api.Plugin)
if !ok {
return fmt.Errorf("plugin symbol %q has invalid signature", symbolName)
}
instance := factory()
cfg := instance.NewConfigObj()
if cfg != nil && pluginRecord.ConfigJSON != "" && canUnmarshalInto(cfg) {
if err := json.Unmarshal([]byte(pluginRecord.ConfigJSON), cfg); err != nil {
return fmt.Errorf("decode plugin config: %w", err)
}
}
if err := instance.ReloadConfig(cfg); err != nil {
return err
}
return nil
}
func canUnmarshalInto(value any) bool {
if value == nil {
return false
@@ -112,6 +128,7 @@ type Manager struct {
builders map[string]SourceBuilder
handleConn func(net.Conn)
wg *sync.WaitGroup
policyProfile string
mu sync.Mutex
loaded map[string]*loadedPlugin
@@ -187,6 +204,7 @@ type Options struct {
WaitGroup *sync.WaitGroup
Adapter RuntimeAdapter
Builders map[string]SourceBuilder
PolicyProfile string
}
func New(options Options) *Manager {
@@ -201,6 +219,7 @@ func New(options Options) *Manager {
builders: options.Builders,
handleConn: options.HandleConn,
wg: options.WaitGroup,
policyProfile: options.PolicyProfile,
loaded: make(map[string]*loadedPlugin),
proxyConns: make(map[uint64]*proxyConnection),
drainingIDs: make(map[string]bool),
@@ -539,6 +558,17 @@ func (m *Manager) RollbackArtifact(ctx context.Context, actor, pluginID, artifac
if err != nil {
return PluginRecord{}, err
}
decision, err := m.EvaluateGovernance(ctx, pluginID, artifactID, GovernanceActionRollback, m.currentPolicyProfile(), current.ConfigJSON)
if err == nil && !decision.OK {
err = governanceBlockedError(decision)
}
if err != nil {
_ = m.repo.RecordOperation(ctx, pluginID, artifactID, "artifact_rollback_gate", "failed", actor, err.Error(), map[string]any{
"active_changed": false,
"governance_decision": decision,
})
return PluginRecord{}, err
}
if _, err := m.DryRunConfig(ctx, pluginID, artifactID, current.ConfigJSON); err != nil {
_ = m.repo.RecordOperation(ctx, pluginID, artifactID, "artifact_rollback", "failed", actor, err.Error(), map[string]any{
"active_changed": false,
@@ -555,6 +585,7 @@ func (m *Manager) RollbackArtifact(ctx context.Context, actor, pluginID, artifac
_ = m.repo.RecordOperation(ctx, pluginID, artifactID, "artifact_rollback", "succeeded", actor, "artifact rollback desired state updated", map[string]any{
"desired_generation": plugin.DesiredGeneration,
"active_changed": false,
"governance_decision": decision,
})
return plugin, nil
}
@@ -576,6 +607,19 @@ func (m *Manager) RollbackConfigSnapshot(ctx context.Context, actor string, snap
desiredState = snapshot.DesiredState
priority = snapshot.Priority
}
decision, err := m.EvaluateGovernance(ctx, snapshot.PluginID, artifactID, GovernanceActionRollback, m.currentPolicyProfile(), snapshot.ConfigJSON)
if err == nil && !decision.OK {
err = governanceBlockedError(decision)
}
if err != nil {
_ = m.repo.RecordOperation(ctx, snapshot.PluginID, artifactID, "config_rollback_gate", "failed", actor, err.Error(), map[string]any{
"snapshot_id": snapshot.ID,
"full_desired": fullDesired,
"active_changed": false,
"governance_decision": decision,
})
return PluginRecord{}, err
}
if _, err := m.DryRunConfig(ctx, snapshot.PluginID, artifactID, snapshot.ConfigJSON); err != nil {
_ = m.repo.RecordOperation(ctx, snapshot.PluginID, artifactID, "config_rollback", "failed", actor, err.Error(), map[string]any{
"snapshot_id": snapshot.ID,
@@ -598,6 +642,7 @@ func (m *Manager) RollbackConfigSnapshot(ctx context.Context, actor string, snap
"full_desired": fullDesired,
"desired_generation": next.DesiredGeneration,
"active_changed": false,
"governance_decision": decision,
})
return next, nil
}
@@ -630,6 +675,17 @@ func (m *Manager) Enable(ctx context.Context, actor, pluginID string) (PluginRec
return PluginRecord{}, err
}
}
decision, err := m.EvaluateGovernance(ctx, pluginID, pluginRecord.DesiredArtifactID, GovernanceActionEnable, m.currentPolicyProfile(), pluginRecord.ConfigJSON)
if err == nil && !decision.OK {
err = governanceBlockedError(decision)
}
if err != nil {
_ = m.repo.MarkRuntime(ctx, pluginID, RuntimeFailed, "", "", pluginRecord.AppliedGeneration, err.Error(), map[string]any{"governance": decision}, nil)
_ = m.repo.RecordOperation(ctx, pluginID, pluginRecord.DesiredArtifactID, "enable_gate", "failed", actor, err.Error(), map[string]any{
"decision": decision,
})
return PluginRecord{}, err
}
m.mu.Lock()
defer m.mu.Unlock()
@@ -658,6 +714,7 @@ func (m *Manager) Enable(ctx context.Context, actor, pluginID string) (PluginRec
_ = m.repo.RecordOperation(ctx, pluginID, loaded.artifact.ID, "enable", "succeeded", actor, "plugin enabled", map[string]any{
"desired_generation": loaded.record.DesiredGeneration,
"handler_count": len(loaded.handlers),
"governance_decision": decision,
})
return m.repo.Plugin(ctx, pluginID)
}
@@ -728,6 +785,15 @@ func (m *Manager) Reconcile(ctx context.Context) error {
}
nextByPlugin := make(map[string][]*upstreamHandler)
for _, pluginRecord := range desired {
decision, err := m.EvaluateGovernance(ctx, pluginRecord.ID, pluginRecord.DesiredArtifactID, GovernanceActionEnable, m.currentPolicyProfile(), pluginRecord.ConfigJSON)
if err == nil && !decision.OK {
err = governanceBlockedError(decision)
}
if err != nil {
_ = m.repo.MarkRuntime(ctx, pluginRecord.ID, RuntimeFailed, "", "", pluginRecord.AppliedGeneration, err.Error(), map[string]any{"governance": decision}, nil)
_ = m.repo.RecordOperation(ctx, pluginRecord.ID, pluginRecord.DesiredArtifactID, "reconcile_gate", "failed", "system", err.Error(), map[string]any{"decision": decision})
continue
}
loaded, err := m.loadLocked(ctx, pluginRecord)
if err != nil {
_ = m.repo.MarkRuntime(ctx, pluginRecord.ID, RuntimeFailed, "", "", pluginRecord.AppliedGeneration, err.Error(), map[string]any{"error": err.Error()}, nil)
@@ -901,6 +967,38 @@ func (m *Manager) ListSecrets(ctx context.Context, pluginID string) ([]SecretRec
return m.repo.ListSecrets(ctx, pluginID)
}
func (m *Manager) quarantineAffected(ctx context.Context, advisory AdvisoryRecord) {
plugins, err := m.repo.ListPlugins(ctx)
if err != nil {
return
}
var manifests = make(map[string]Manifest)
m.mu.Lock()
defer m.mu.Unlock()
for _, plugin := range plugins {
if plugin.RuntimeState != RuntimeEnabled || plugin.ActiveArtifactID == "" {
continue
}
artifact, err := m.repo.Artifact(ctx, plugin.ActiveArtifactID)
if err != nil {
continue
}
manifest := manifests[artifact.ID]
if manifest.ID == "" {
_ = json.Unmarshal([]byte(artifact.MetadataJSON), &manifest)
manifests[artifact.ID] = manifest
}
if advisoryMatches(advisory, artifact, manifest) {
m.removeFromDispatchLocked(plugin.ID)
m.markDrainingLocked(plugin.ID)
_ = m.repo.MarkRuntime(ctx, plugin.ID, RuntimeDraining, artifact.ID, artifact.ID, plugin.AppliedGeneration, "plugin quarantined by advisory "+advisory.AdvisoryID, map[string]any{
"quarantine": true,
"advisory_id": advisory.AdvisoryID,
}, nil)
}
}
}
func (m *Manager) UpsertSecret(ctx context.Context, actor, pluginID, artifactID, name, value string, reloadRequired, hotReload bool) (SecretRecord, error) {
if artifactID == "" {
plugin, err := m.repo.Plugin(ctx, pluginID)

View File

@@ -510,10 +510,11 @@ func TestProtocolProxyTrackDrainAndForceClose(t *testing.T) {
},
}
manager := newManagerForTest(t, adapter)
artifact := uploadTestArtifactWithCapabilities(t, manager, "plugin-a", json.RawMessage(`{"upstream_connect":{"mode":"protocol-proxy"}}`))
artifact := uploadTestArtifactWithCapabilities(t, manager, "plugin-a", testProtocolProxyCapabilities())
if _, err := manager.SetDesired(context.Background(), "admin", "plugin-a", artifact.ID, DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired() error = %v", err)
}
approveGovernanceForTest(t, manager, "plugin-a", artifact.ID)
if _, err := manager.Enable(context.Background(), "admin", "plugin-a"); err != nil {
t.Fatalf("Enable() error = %v", err)
}
@@ -616,10 +617,11 @@ func TestProtocolProxyInitialWriteTimeoutClosesUnreadableConn(t *testing.T) {
},
}
manager := newManagerForTest(t, adapter)
artifact := uploadTestArtifactWithCapabilities(t, manager, "plugin-a", json.RawMessage(`{"upstream_connect":{"mode":"protocol-proxy"}}`))
artifact := uploadTestArtifactWithCapabilities(t, manager, "plugin-a", testProtocolProxyCapabilities())
if _, err := manager.SetDesired(context.Background(), "admin", "plugin-a", artifact.ID, DesiredEnabled, `{"unused":true}`, 10); err != nil {
t.Fatalf("SetDesired() error = %v", err)
}
approveGovernanceForTest(t, manager, "plugin-a", artifact.ID)
if _, err := manager.Enable(context.Background(), "admin", "plugin-a"); err != nil {
t.Fatalf("Enable() error = %v", err)
}
@@ -648,21 +650,46 @@ func TestProtocolProxyInitialWriteTimeoutClosesUnreadableConn(t *testing.T) {
func enableProtocolProxyTestPlugin(t *testing.T, manager *Manager, pluginID string) ArtifactRecord {
t.Helper()
artifact := uploadTestArtifactWithCapabilities(t, manager, pluginID, json.RawMessage(`{"upstream_connect":{"mode":"protocol-proxy"}}`))
artifact := uploadTestArtifactWithCapabilities(t, manager, pluginID, testProtocolProxyCapabilities())
if _, err := manager.SetDesired(context.Background(), "admin", pluginID, artifact.ID, DesiredEnabled, `{}`, 10); err != nil {
t.Fatalf("SetDesired() error = %v", err)
}
approveGovernanceForTest(t, manager, pluginID, artifact.ID)
if _, err := manager.Enable(context.Background(), "admin", pluginID); err != nil {
t.Fatalf("Enable() error = %v", err)
}
return artifact
}
func approveGovernanceForTest(t *testing.T, manager *Manager, pluginID, artifactID string) {
t.Helper()
if _, err := manager.CreateReview(context.Background(), "admin", pluginID, GovernanceReviewRequest{
ArtifactID: artifactID,
Profile: PolicyProfileProd,
Decision: ReviewDecisionApproved,
Notes: "test approval",
}); err != nil {
t.Fatalf("CreateReview(%s) error = %v", pluginID, err)
}
}
func newManagerForTest(t *testing.T, adapter RuntimeAdapter) *Manager {
t.Helper()
return newManagerForTestWithBuilders(t, adapter, nil)
}
func testProtocolProxyCapabilities() json.RawMessage {
return json.RawMessage(`{
"upstream_connect":{"mode":"protocol-proxy"},
"scope":{"type":"host","values":["play.example"]},
"rollout":{"mode":"canary"},
"minecraft":{
"protocol_versions":{"tested":[767]},
"forwarding":{"supported":["none"],"default":"none"}
}
}`)
}
func newManagerForTestWithBuilders(t *testing.T, adapter RuntimeAdapter, builders map[string]SourceBuilder) *Manager {
t.Helper()
db := openPluginManagerTestDB(t)

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
)
@@ -599,6 +600,359 @@ FROM plugin_secrets`
return secrets, rows.Err()
}
func (r Repository) SaveReview(ctx context.Context, review ReviewRecord) (ReviewRecord, error) {
now := r.now().Unix()
if review.CreatedAt == 0 {
review.CreatedAt = now
}
if review.Profile == "" {
review.Profile = PolicyProfileDev
}
if review.RiskLevel == "" {
review.RiskLevel = RiskLow
}
if review.Decision == "" {
review.Decision = ReviewDecisionApproved
}
_, err := r.db.ExecContext(ctx, `
INSERT INTO plugin_reviews(
plugin_id, artifact_id, profile, risk_level, config_hash, scope_hash, rollout_hash,
runtime_limits_hash, features_hash, policy_hash, decision, notes, reviewed_by, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
review.PluginID, review.ArtifactID, review.Profile, review.RiskLevel, review.ConfigHash, review.ScopeHash, review.RolloutHash,
review.RuntimeLimitsHash, review.FeaturesHash, review.PolicyHash, review.Decision, review.Notes, review.ReviewedBy, review.CreatedAt)
if err != nil {
return ReviewRecord{}, err
}
id, err := lastInsertID(ctx, r.db)
if err != nil {
return ReviewRecord{}, err
}
review.ID = id
return review, nil
}
func (r Repository) ListReviews(ctx context.Context, pluginID string) ([]ReviewRecord, error) {
query := `
SELECT id, plugin_id, artifact_id, profile, risk_level, config_hash, scope_hash, rollout_hash,
runtime_limits_hash, features_hash, policy_hash, decision, notes, reviewed_by, created_at
FROM plugin_reviews`
var args []any
if pluginID != "" {
query += ` WHERE plugin_id = ?`
args = append(args, pluginID)
}
query += ` ORDER BY created_at DESC, id DESC`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var reviews []ReviewRecord
for rows.Next() {
review, err := scanReview(rows)
if err != nil {
return nil, err
}
reviews = append(reviews, review)
}
return reviews, rows.Err()
}
func (r Repository) ApprovedReview(ctx context.Context, pluginID, artifactID, profile, policyHash, configHash, scopeHash, rolloutHash, runtimeHash, featuresHash string) (ReviewRecord, error) {
row := r.db.QueryRowContext(ctx, `
SELECT id, plugin_id, artifact_id, profile, risk_level, config_hash, scope_hash, rollout_hash,
runtime_limits_hash, features_hash, policy_hash, decision, notes, reviewed_by, created_at
FROM plugin_reviews
WHERE plugin_id = ? AND artifact_id = ? AND profile = ? AND policy_hash = ?
AND config_hash = ? AND scope_hash = ? AND rollout_hash = ? AND runtime_limits_hash = ? AND features_hash = ?
AND decision = ?
ORDER BY created_at DESC, id DESC
LIMIT 1`, pluginID, artifactID, profile, policyHash, configHash, scopeHash, rolloutHash, runtimeHash, featuresHash, ReviewDecisionApproved)
review, err := scanReview(row)
if errors.Is(err, sql.ErrNoRows) {
return ReviewRecord{}, nil
}
return review, err
}
func (r Repository) SaveWarningOverride(ctx context.Context, override WarningOverrideRecord) (WarningOverrideRecord, error) {
now := r.now().Unix()
if override.CreatedAt == 0 {
override.CreatedAt = now
}
if override.Profile == "" {
override.Profile = PolicyProfileDev
}
_, err := r.db.ExecContext(ctx, `
INSERT INTO plugin_warning_overrides(plugin_id, artifact_id, profile, action, policy_hash, reason, created_by, expires_at, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
override.PluginID, override.ArtifactID, override.Profile, override.Action, override.PolicyHash, override.Reason, override.CreatedBy, override.ExpiresAt, override.CreatedAt)
if err != nil {
return WarningOverrideRecord{}, err
}
id, err := lastInsertID(ctx, r.db)
if err != nil {
return WarningOverrideRecord{}, err
}
override.ID = id
return override, nil
}
func (r Repository) ActiveWarningOverride(ctx context.Context, pluginID, artifactID, profile, action, policyHash string) (WarningOverrideRecord, bool, error) {
row := r.db.QueryRowContext(ctx, `
SELECT id, plugin_id, artifact_id, profile, action, policy_hash, reason, created_by, expires_at, created_at
FROM plugin_warning_overrides
WHERE plugin_id = ? AND artifact_id = ? AND profile = ? AND action = ? AND policy_hash = ? AND expires_at > ?
ORDER BY expires_at DESC, id DESC
LIMIT 1`, pluginID, artifactID, profile, action, policyHash, r.now().Unix())
override, err := scanWarningOverride(row)
if errors.Is(err, sql.ErrNoRows) {
return WarningOverrideRecord{}, false, nil
}
if err != nil {
return WarningOverrideRecord{}, false, err
}
return override, true, nil
}
func (r Repository) ListWarningOverrides(ctx context.Context, pluginID string) ([]WarningOverrideRecord, error) {
query := `
SELECT id, plugin_id, artifact_id, profile, action, policy_hash, reason, created_by, expires_at, created_at
FROM plugin_warning_overrides`
var args []any
if pluginID != "" {
query += ` WHERE plugin_id = ?`
args = append(args, pluginID)
}
query += ` ORDER BY created_at DESC, id DESC`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var overrides []WarningOverrideRecord
for rows.Next() {
override, err := scanWarningOverride(rows)
if err != nil {
return nil, err
}
overrides = append(overrides, override)
}
return overrides, rows.Err()
}
func (r Repository) UpsertAdvisory(ctx context.Context, actor string, req AdvisoryRequest) (AdvisoryRecord, error) {
now := r.now().Unix()
if req.Status == "" {
req.Status = AdvisoryStatusActive
}
if req.Action == "" {
req.Action = AdvisoryActionDenylist
}
if strings.TrimSpace(req.AdvisoryID) == "" {
return AdvisoryRecord{}, errors.New("advisory_id is required")
}
res, err := r.db.ExecContext(ctx, `
UPDATE plugin_advisories
SET status = ?, action = ?, artifact_sha256 = ?, plugin_id = ?, version_range = ?,
dependency_name = ?, dependency_range = ?, recommended_action = ?, fixed_version = ?,
mitigation = ?, created_by = ?, updated_at = ?
WHERE advisory_id = ?`,
req.Status, req.Action, req.ArtifactSHA256, req.PluginID, req.VersionRange,
req.DependencyName, req.DependencyRange, req.RecommendedAction, req.FixedVersion,
req.Mitigation, actor, now, req.AdvisoryID)
if err != nil {
return AdvisoryRecord{}, err
}
if rows, _ := res.RowsAffected(); rows == 0 {
_, err = r.db.ExecContext(ctx, `
INSERT INTO plugin_advisories(
advisory_id, status, action, artifact_sha256, plugin_id, version_range, dependency_name,
dependency_range, recommended_action, fixed_version, mitigation, created_by, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
req.AdvisoryID, req.Status, req.Action, req.ArtifactSHA256, req.PluginID, req.VersionRange, req.DependencyName,
req.DependencyRange, req.RecommendedAction, req.FixedVersion, req.Mitigation, actor, now, now)
if err != nil {
return AdvisoryRecord{}, err
}
}
return r.AdvisoryByID(ctx, req.AdvisoryID)
}
func (r Repository) AdvisoryByID(ctx context.Context, advisoryID string) (AdvisoryRecord, error) {
row := r.db.QueryRowContext(ctx, `
SELECT id, advisory_id, status, action, artifact_sha256, plugin_id, version_range, dependency_name,
dependency_range, recommended_action, fixed_version, mitigation, created_by, created_at, updated_at
FROM plugin_advisories
WHERE advisory_id = ?
ORDER BY id DESC
LIMIT 1`, advisoryID)
return scanAdvisory(row)
}
func (r Repository) ListAdvisories(ctx context.Context, pluginID string) ([]AdvisoryRecord, error) {
query := `
SELECT id, advisory_id, status, action, artifact_sha256, plugin_id, version_range, dependency_name,
dependency_range, recommended_action, fixed_version, mitigation, created_by, created_at, updated_at
FROM plugin_advisories`
var args []any
if pluginID != "" {
query += ` WHERE plugin_id = ? OR plugin_id = ''`
args = append(args, pluginID)
}
query += ` ORDER BY updated_at DESC, id DESC`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var advisories []AdvisoryRecord
for rows.Next() {
advisory, err := scanAdvisory(rows)
if err != nil {
return nil, err
}
advisories = append(advisories, advisory)
}
return advisories, rows.Err()
}
func (r Repository) SavePreflight(ctx context.Context, record PreflightRecord) (PreflightRecord, error) {
now := r.now().Unix()
if record.CreatedAt == 0 {
record.CreatedAt = now
}
if record.Profile == "" {
record.Profile = PolicyProfileDev
}
if record.ResultJSON == "" || !json.Valid([]byte(record.ResultJSON)) {
record.ResultJSON = "{}"
}
_, err := r.db.ExecContext(ctx, `
INSERT INTO plugin_preflight_results(plugin_id, artifact_id, profile, status, result_json, created_by, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
record.PluginID, record.ArtifactID, record.Profile, record.Status, record.ResultJSON, record.CreatedBy, record.CreatedAt)
if err != nil {
return PreflightRecord{}, err
}
id, err := lastInsertID(ctx, r.db)
if err != nil {
return PreflightRecord{}, err
}
record.ID = id
return record, nil
}
func (r Repository) ListPreflights(ctx context.Context, pluginID string) ([]PreflightRecord, error) {
query := `
SELECT id, plugin_id, artifact_id, profile, status, result_json, created_by, created_at
FROM plugin_preflight_results`
var args []any
if pluginID != "" {
query += ` WHERE plugin_id = ?`
args = append(args, pluginID)
}
query += ` ORDER BY created_at DESC, id DESC`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var records []PreflightRecord
for rows.Next() {
record, err := scanPreflight(rows)
if err != nil {
return nil, err
}
records = append(records, record)
}
return records, rows.Err()
}
func (r Repository) SaveBenchmark(ctx context.Context, actor string, req BenchmarkRequest) (BenchmarkRecord, error) {
now := r.now().Unix()
if req.Profile == "" {
req.Profile = PolicyProfileDev
}
record := BenchmarkRecord{
PluginID: "",
ArtifactID: req.ArtifactID,
Profile: req.Profile,
BenchmarkProfile: req.BenchmarkProfile,
P95MS: req.P95MS,
P99MS: req.P99MS,
ErrorRate: req.ErrorRate,
ActiveProxyCapacity: req.ActiveProxyCapacity,
BaselineDiff: req.BaselineDiff,
CreatedBy: actor,
CreatedAt: now,
}
artifact, err := r.Artifact(ctx, req.ArtifactID)
if err != nil {
return BenchmarkRecord{}, err
}
record.PluginID = artifact.PluginID
_, err = r.db.ExecContext(ctx, `
INSERT INTO plugin_benchmarks(
plugin_id, artifact_id, profile, benchmark_profile, p95_ms, p99_ms, error_rate,
active_proxy_capacity, baseline_diff, created_by, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
record.PluginID, record.ArtifactID, record.Profile, record.BenchmarkProfile, record.P95MS, record.P99MS, record.ErrorRate,
record.ActiveProxyCapacity, record.BaselineDiff, record.CreatedBy, record.CreatedAt)
if err != nil {
return BenchmarkRecord{}, err
}
id, err := lastInsertID(ctx, r.db)
if err != nil {
return BenchmarkRecord{}, err
}
record.ID = id
return record, nil
}
func (r Repository) ListBenchmarks(ctx context.Context, pluginID string) ([]BenchmarkRecord, error) {
query := `
SELECT id, plugin_id, artifact_id, profile, benchmark_profile, p95_ms, p99_ms, error_rate,
active_proxy_capacity, baseline_diff, created_by, created_at
FROM plugin_benchmarks`
var args []any
if pluginID != "" {
query += ` WHERE plugin_id = ?`
args = append(args, pluginID)
}
query += ` ORDER BY created_at DESC, id DESC`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var records []BenchmarkRecord
for rows.Next() {
record, err := scanBenchmark(rows)
if err != nil {
return nil, err
}
records = append(records, record)
}
return records, rows.Err()
}
func (r Repository) LatestBenchmark(ctx context.Context, pluginID, artifactID, profile string) (BenchmarkRecord, error) {
row := r.db.QueryRowContext(ctx, `
SELECT id, plugin_id, artifact_id, profile, benchmark_profile, p95_ms, p99_ms, error_rate,
active_proxy_capacity, baseline_diff, created_by, created_at
FROM plugin_benchmarks
WHERE plugin_id = ? AND artifact_id = ? AND profile = ?
ORDER BY created_at DESC, id DESC
LIMIT 1`, pluginID, artifactID, profile)
benchmark, err := scanBenchmark(row)
if errors.Is(err, sql.ErrNoRows) {
return BenchmarkRecord{}, nil
}
return benchmark, err
}
func (r Repository) RecordOperation(ctx context.Context, pluginID, artifactID, operation, status, actor, message string, metadata any) error {
metadataJSON, err := marshalDefaultObject(metadata)
if err != nil {
@@ -691,6 +1045,55 @@ func scanBuild(row rowScanner) (BuildRecord, error) {
return build, err
}
func scanReview(row rowScanner) (ReviewRecord, error) {
var review ReviewRecord
err := row.Scan(
&review.ID, &review.PluginID, &review.ArtifactID, &review.Profile, &review.RiskLevel,
&review.ConfigHash, &review.ScopeHash, &review.RolloutHash, &review.RuntimeLimitsHash,
&review.FeaturesHash, &review.PolicyHash, &review.Decision, &review.Notes, &review.ReviewedBy, &review.CreatedAt,
)
return review, err
}
func scanWarningOverride(row rowScanner) (WarningOverrideRecord, error) {
var override WarningOverrideRecord
err := row.Scan(
&override.ID, &override.PluginID, &override.ArtifactID, &override.Profile, &override.Action,
&override.PolicyHash, &override.Reason, &override.CreatedBy, &override.ExpiresAt, &override.CreatedAt,
)
return override, err
}
func scanAdvisory(row rowScanner) (AdvisoryRecord, error) {
var advisory AdvisoryRecord
err := row.Scan(
&advisory.ID, &advisory.AdvisoryID, &advisory.Status, &advisory.Action, &advisory.ArtifactSHA256,
&advisory.PluginID, &advisory.VersionRange, &advisory.DependencyName, &advisory.DependencyRange,
&advisory.RecommendedAction, &advisory.FixedVersion, &advisory.Mitigation, &advisory.CreatedBy,
&advisory.CreatedAt, &advisory.UpdatedAt,
)
return advisory, err
}
func scanPreflight(row rowScanner) (PreflightRecord, error) {
var record PreflightRecord
err := row.Scan(
&record.ID, &record.PluginID, &record.ArtifactID, &record.Profile, &record.Status,
&record.ResultJSON, &record.CreatedBy, &record.CreatedAt,
)
return record, err
}
func scanBenchmark(row rowScanner) (BenchmarkRecord, error) {
var record BenchmarkRecord
err := row.Scan(
&record.ID, &record.PluginID, &record.ArtifactID, &record.Profile, &record.BenchmarkProfile,
&record.P95MS, &record.P99MS, &record.ErrorRate, &record.ActiveProxyCapacity,
&record.BaselineDiff, &record.CreatedBy, &record.CreatedAt,
)
return record, err
}
func marshalDefaultObject(value any) (string, error) {
if value == nil {
return "{}", nil

View File

@@ -53,6 +53,34 @@ const (
RuntimeDisabled = "disabled"
RuntimeDraining = "draining"
PolicyProfileDev = "dev"
PolicyProfileStaging = "staging"
PolicyProfileProd = "prod"
RiskLow = "low"
RiskMedium = "medium"
RiskHigh = "high"
GateSeverityWarning = "warning"
GateSeverityBlocking = "blocking"
GateSeverityInfo = "info"
GovernanceActionEnable = "enable"
GovernanceActionRollback = "rollback"
GovernanceActionPromotion = "promotion_apply"
AdvisoryActionDenylist = "denylist"
AdvisoryActionQuarantine = "quarantine"
AdvisoryActionRevoke = "revoke"
AdvisoryActionMitigate = "mitigate"
ReviewDecisionApproved = "approved"
ReviewDecisionRejected = "rejected"
AdvisoryStatusActive = "active"
AdvisoryStatusRevoked = "revoked"
AdvisoryStatusAcked = "acknowledged"
DefaultPriority = 100
DefaultHandlerTimeout = 3 * time.Second
DefaultManifestMaxBytes = 256 * 1024
@@ -258,6 +286,196 @@ type SecretRecord struct {
UpdatedAt int64 `json:"updated_at"`
}
type PolicySnapshot struct {
Profile string `json:"profile"`
WarningOverrideTTLSeconds int64 `json:"warning_override_ttl_seconds"`
ReviewRequiredRisk string `json:"review_required_risk"`
WarnBenchmarkRegression float64 `json:"warn_benchmark_regression"`
BlockBenchmarkRegression float64 `json:"block_benchmark_regression"`
CreatedAt int64 `json:"created_at"`
}
type GovernanceIssue struct {
Code string `json:"code"`
Severity string `json:"severity"`
Message string `json:"message"`
PluginID string `json:"plugin_id,omitempty"`
ArtifactID string `json:"artifact_id,omitempty"`
Details map[string]any `json:"details,omitempty"`
}
type GovernanceDecision struct {
OK bool `json:"ok"`
Action string `json:"action"`
Profile string `json:"profile"`
RiskLevel string `json:"risk_level"`
PolicyHash string `json:"policy_hash"`
ReviewRequired bool `json:"review_required"`
WarningOverrideUsed bool `json:"warning_override_used"`
Issues []GovernanceIssue `json:"issues"`
Checks []GovernanceIssue `json:"checks"`
CreatedAt int64 `json:"created_at"`
}
type ConflictAnalysis struct {
OK bool `json:"ok"`
Issues []GovernanceIssue `json:"issues"`
Plan DispatchPlan `json:"plan"`
CreatedAt int64 `json:"created_at"`
}
type PreflightCheck struct {
Code string `json:"code"`
Severity string `json:"severity"`
Message string `json:"message"`
Details map[string]any `json:"details,omitempty"`
}
type PreflightResult struct {
OK bool `json:"ok"`
Profile string `json:"profile"`
Checks []PreflightCheck `json:"checks"`
CreatedAt int64 `json:"created_at"`
}
type GovernanceStatus struct {
Decision GovernanceDecision `json:"decision"`
Policy PolicySnapshot `json:"policy"`
Reviews []ReviewRecord `json:"reviews"`
WarningOverrides []WarningOverrideRecord `json:"warning_overrides"`
Preflights []PreflightRecord `json:"preflights"`
Benchmarks []BenchmarkRecord `json:"benchmarks"`
Advisories []AdvisoryRecord `json:"advisories"`
Conflicts ConflictAnalysis `json:"conflicts"`
}
type ReviewRecord struct {
ID int64 `json:"id"`
PluginID string `json:"plugin_id"`
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
RiskLevel string `json:"risk_level"`
ConfigHash string `json:"config_hash"`
ScopeHash string `json:"scope_hash"`
RolloutHash string `json:"rollout_hash"`
RuntimeLimitsHash string `json:"runtime_limits_hash"`
FeaturesHash string `json:"features_hash"`
PolicyHash string `json:"policy_hash"`
Decision string `json:"decision"`
Notes string `json:"notes"`
ReviewedBy string `json:"reviewed_by"`
CreatedAt int64 `json:"created_at"`
}
type WarningOverrideRecord struct {
ID int64 `json:"id"`
PluginID string `json:"plugin_id"`
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
Action string `json:"action"`
PolicyHash string `json:"policy_hash"`
Reason string `json:"reason"`
CreatedBy string `json:"created_by"`
ExpiresAt int64 `json:"expires_at"`
CreatedAt int64 `json:"created_at"`
}
type AdvisoryRecord struct {
ID int64 `json:"id"`
AdvisoryID string `json:"advisory_id"`
Status string `json:"status"`
Action string `json:"action"`
ArtifactSHA256 string `json:"artifact_sha256"`
PluginID string `json:"plugin_id"`
VersionRange string `json:"version_range"`
DependencyName string `json:"dependency_name"`
DependencyRange string `json:"dependency_range"`
RecommendedAction string `json:"recommended_action"`
FixedVersion string `json:"fixed_version"`
Mitigation string `json:"mitigation"`
CreatedBy string `json:"created_by"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
type PreflightRecord struct {
ID int64 `json:"id"`
PluginID string `json:"plugin_id"`
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
Status string `json:"status"`
ResultJSON string `json:"result_json"`
CreatedBy string `json:"created_by"`
CreatedAt int64 `json:"created_at"`
}
type BenchmarkRecord struct {
ID int64 `json:"id"`
PluginID string `json:"plugin_id"`
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
BenchmarkProfile string `json:"benchmark_profile"`
P95MS float64 `json:"p95_ms"`
P99MS float64 `json:"p99_ms"`
ErrorRate float64 `json:"error_rate"`
ActiveProxyCapacity int64 `json:"active_proxy_capacity"`
BaselineDiff float64 `json:"baseline_diff"`
CreatedBy string `json:"created_by"`
CreatedAt int64 `json:"created_at"`
}
type GovernanceReviewRequest struct {
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
Decision string `json:"decision"`
Notes string `json:"notes"`
}
type WarningOverrideRequest struct {
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
Action string `json:"action"`
Reason string `json:"reason"`
TTLSeconds int64 `json:"ttl_seconds"`
}
type PreflightRequest struct {
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
Action string `json:"action"`
ConfigJSON string `json:"config_json"`
}
type SelfTestRequest struct {
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
}
type AdvisoryRequest struct {
AdvisoryID string `json:"advisory_id"`
Status string `json:"status"`
Action string `json:"action"`
ArtifactSHA256 string `json:"artifact_sha256"`
PluginID string `json:"plugin_id"`
VersionRange string `json:"version_range"`
DependencyName string `json:"dependency_name"`
DependencyRange string `json:"dependency_range"`
RecommendedAction string `json:"recommended_action"`
FixedVersion string `json:"fixed_version"`
Mitigation string `json:"mitigation"`
}
type BenchmarkRequest struct {
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
BenchmarkProfile string `json:"benchmark_profile"`
P95MS float64 `json:"p95_ms"`
P99MS float64 `json:"p99_ms"`
ErrorRate float64 `json:"error_rate"`
ActiveProxyCapacity int64 `json:"active_proxy_capacity"`
BaselineDiff float64 `json:"baseline_diff"`
}
type ProxyConnectionSummary struct {
ID uint64 `json:"id"`
PluginID string `json:"plugin_id"`

View File

@@ -23,6 +23,44 @@ type (
ReloadConfig(config any) error
}
PreflightCheck struct {
Code string `json:"code"`
Severity string `json:"severity"`
Message string `json:"message"`
}
PreflightContext struct {
PluginID string `json:"plugin_id"`
ArtifactID string `json:"artifact_id"`
Profile string `json:"profile"`
Action string `json:"action"`
Config map[string]any `json:"config,omitempty"`
Scope any `json:"scope,omitempty"`
Rollout any `json:"rollout,omitempty"`
RuntimeLimits any `json:"runtime_limits,omitempty"`
Features []string `json:"features,omitempty"`
}
PreflightResult struct {
Checks []PreflightCheck `json:"checks"`
}
SelfTestProfile struct {
Name string `json:"name"`
}
SelfTestResult struct {
Checks []PreflightCheck `json:"checks"`
}
PreflightChecker interface {
Preflight(context any) (PreflightResult, error)
}
SelfTester interface {
SelfTest(profile SelfTestProfile) (SelfTestResult, error)
}
Gateway interface {
HandleConn(conn net.Conn)