- Add PolicySourceStore for tracking where policies come from - Implement claim extraction skill and API endpoints - Add community UI text selection extractor component - Create Go SDK aphoria client for policy operations - Document patent specifications and legal disclosures - Add guides: golden path loop, policy audit trails, pre-flight checks - Expand Unreal Engine config extractor with source tracking - Add UAT reports for policy source tracking validation - Refactor tests.rs into modular test files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
350 lines
10 KiB
Go
350 lines
10 KiB
Go
package steme
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
)
|
|
|
|
// AphoriaClient provides access to Aphoria code-level truth linting endpoints.
|
|
//
|
|
// Aphoria scans codebases, extracts decisions embedded in config and code,
|
|
// and checks them against authoritative sources (RFCs, OWASP, etc.).
|
|
type AphoriaClient struct {
|
|
client *Client
|
|
}
|
|
|
|
// Aphoria returns a client for Aphoria operations.
|
|
//
|
|
// Example:
|
|
//
|
|
// result, err := client.Aphoria().Scan(ctx, ScanParams{
|
|
// TargetPath: "/path/to/project",
|
|
// })
|
|
func (c *Client) Aphoria() *AphoriaClient {
|
|
return &AphoriaClient{client: c}
|
|
}
|
|
|
|
// BlessParams defines parameters for blessing a code pattern.
|
|
type BlessParams struct {
|
|
// ConceptPath to bless (e.g., "code://rust/grpc/tls").
|
|
ConceptPath string `json:"concept_path"`
|
|
|
|
// Predicate being defined (e.g., "enabled", "min_version").
|
|
Predicate string `json:"predicate"`
|
|
|
|
// Value for this standard (e.g., "true", "1.2").
|
|
Value string `json:"value"`
|
|
|
|
// Reason/description for why this is the standard.
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
// BlessResult represents the response from blessing a pattern.
|
|
type BlessResult struct {
|
|
Success bool `json:"success"`
|
|
Message string `json:"message"`
|
|
ConceptPath string `json:"concept_path"`
|
|
}
|
|
|
|
// Bless marks a code pattern as the authoritative standard.
|
|
//
|
|
// Unlike acknowledge (which creates a suppression), bless creates an
|
|
// assertion with the actual predicate and value that becomes the
|
|
// authoritative standard for future scans.
|
|
//
|
|
// Example:
|
|
//
|
|
// result, err := client.Aphoria().Bless(ctx, BlessParams{
|
|
// ConceptPath: "code://rust/grpc/tls",
|
|
// Predicate: "enabled",
|
|
// Value: "true",
|
|
// Reason: "All services MUST use mTLS",
|
|
// })
|
|
func (a *AphoriaClient) Bless(ctx context.Context, params BlessParams) (*BlessResult, error) {
|
|
var result BlessResult
|
|
if err := a.client.doJSON(ctx, "POST", "/v1/aphoria/bless", params, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// ExportPolicyParams defines parameters for exporting a Trust Pack.
|
|
type ExportPolicyParams struct {
|
|
// Name for the Trust Pack (e.g., "acme-security-policy").
|
|
Name string `json:"name"`
|
|
|
|
// Description for the pack (optional).
|
|
Description string `json:"description,omitempty"`
|
|
|
|
// OutputPath where the pack should be saved (optional).
|
|
// If not provided, uses default name based on pack name.
|
|
OutputPath string `json:"output_path,omitempty"`
|
|
}
|
|
|
|
// ExportPolicyResult represents the response from exporting a policy.
|
|
type ExportPolicyResult struct {
|
|
// PackPath where the pack was saved.
|
|
PackPath string `json:"pack_path,omitempty"`
|
|
|
|
// Number of assertions exported.
|
|
AssertionsCount int `json:"assertions_count"`
|
|
|
|
// Number of aliases exported.
|
|
AliasesCount int `json:"aliases_count"`
|
|
|
|
// Hex-encoded issuer public key (first 8 chars).
|
|
IssuerHex string `json:"issuer_hex"`
|
|
|
|
// Name of the exported pack.
|
|
PackName string `json:"pack_name"`
|
|
|
|
// Version of the exported pack.
|
|
PackVersion string `json:"pack_version"`
|
|
}
|
|
|
|
// ExportPolicy exports policy assertions as a Trust Pack.
|
|
//
|
|
// Collects all acknowledged conflicts and manual aliases into a signed
|
|
// Trust Pack that can be shared with other projects.
|
|
//
|
|
// Example:
|
|
//
|
|
// result, err := client.Aphoria().ExportPolicy(ctx, ExportPolicyParams{
|
|
// Name: "acme-security",
|
|
// Description: "Acme Corp security standards",
|
|
// OutputPath: "./acme-security.pack",
|
|
// })
|
|
func (a *AphoriaClient) ExportPolicy(ctx context.Context, params ExportPolicyParams) (*ExportPolicyResult, error) {
|
|
var result ExportPolicyResult
|
|
if err := a.client.doJSON(ctx, "POST", "/v1/aphoria/policy/export", params, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// ImportPolicyParams defines parameters for importing a Trust Pack.
|
|
type ImportPolicyParams struct {
|
|
// PackPath to the Trust Pack file.
|
|
PackPath string `json:"pack_path"`
|
|
}
|
|
|
|
// ImportPolicyResult represents the response from importing a policy.
|
|
type ImportPolicyResult struct {
|
|
Success bool `json:"success"`
|
|
|
|
// Number of assertions imported.
|
|
AssertionsImported int `json:"assertions_imported"`
|
|
|
|
// Number of aliases imported.
|
|
AliasesImported int `json:"aliases_imported"`
|
|
|
|
// Name of the imported pack.
|
|
PackName string `json:"pack_name"`
|
|
|
|
// Version of the imported pack.
|
|
PackVersion string `json:"pack_version"`
|
|
|
|
// Hex-encoded issuer public key (first 8 chars).
|
|
IssuerHex string `json:"issuer_hex"`
|
|
}
|
|
|
|
// ImportPolicy imports a Trust Pack into the local Episteme.
|
|
//
|
|
// Loads and verifies the pack's signature, then imports assertions
|
|
// and aliases into the local storage.
|
|
//
|
|
// Example:
|
|
//
|
|
// result, err := client.Aphoria().ImportPolicy(ctx, ImportPolicyParams{
|
|
// PackPath: "./acme-security.pack",
|
|
// })
|
|
func (a *AphoriaClient) ImportPolicy(ctx context.Context, params ImportPolicyParams) (*ImportPolicyResult, error) {
|
|
var result ImportPolicyResult
|
|
if err := a.client.doJSON(ctx, "POST", "/v1/aphoria/policy/import", params, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// ScanParams defines parameters for scanning a project.
|
|
type ScanParams struct {
|
|
// TargetPath to the project root to scan.
|
|
TargetPath string `json:"target_path"`
|
|
|
|
// Format for output: "table", "json", "sarif", "markdown".
|
|
// Defaults to "json".
|
|
Format string `json:"format,omitempty"`
|
|
|
|
// FailOnFlag returns 422 status code if BLOCK findings exist.
|
|
FailOnFlag bool `json:"fail_on_flag,omitempty"`
|
|
|
|
// MinSeverity to report: "pass", "flag", "block".
|
|
MinSeverity string `json:"min_severity,omitempty"`
|
|
|
|
// Debug enables conflict resolution traces in the output.
|
|
Debug bool `json:"debug,omitempty"`
|
|
}
|
|
|
|
// ScanResult represents the response from a scan operation.
|
|
type ScanResult struct {
|
|
// Project name.
|
|
Project string `json:"project"`
|
|
|
|
// Unique scan ID.
|
|
ScanID string `json:"scan_id"`
|
|
|
|
// Number of files scanned.
|
|
FilesScanned int `json:"files_scanned"`
|
|
|
|
// Number of claims extracted.
|
|
ClaimsExtracted int `json:"claims_extracted"`
|
|
|
|
// Findings (conflicts detected).
|
|
Findings []Finding `json:"findings"`
|
|
|
|
// Summary counts by verdict.
|
|
Summary ScanSummary `json:"summary"`
|
|
}
|
|
|
|
// HasBlocks returns true if any findings have BLOCK verdict.
|
|
func (r *ScanResult) HasBlocks() bool {
|
|
return r.Summary.Blocked > 0
|
|
}
|
|
|
|
// HasFlags returns true if any findings have FLAG verdict.
|
|
func (r *ScanResult) HasFlags() bool {
|
|
return r.Summary.Flagged > 0
|
|
}
|
|
|
|
// Finding represents a single finding from the scan.
|
|
type Finding struct {
|
|
// ConceptPath of the claim.
|
|
ConceptPath string `json:"concept_path"`
|
|
|
|
// Predicate being claimed.
|
|
Predicate string `json:"predicate"`
|
|
|
|
// Value found in code.
|
|
CodeValue string `json:"code_value"`
|
|
|
|
// Source file path.
|
|
File string `json:"file"`
|
|
|
|
// Line number in the source file.
|
|
Line int `json:"line"`
|
|
|
|
// ConflictScore (0.0 to 1.0).
|
|
ConflictScore float64 `json:"conflict_score"`
|
|
|
|
// Verdict: "BLOCK", "FLAG", "PASS", "ACK".
|
|
Verdict string `json:"verdict"`
|
|
|
|
// Conflicts with authoritative sources.
|
|
Conflicts []ConflictingSource `json:"conflicts"`
|
|
|
|
// Acknowledgment info if this conflict was acknowledged.
|
|
Acknowledgment *Acknowledgment `json:"acknowledgment,omitempty"`
|
|
|
|
// Debug trace (if debug mode enabled).
|
|
Trace *ConflictTrace `json:"trace,omitempty"`
|
|
}
|
|
|
|
// IsBlocked returns true if this finding has BLOCK verdict.
|
|
func (f *Finding) IsBlocked() bool {
|
|
return f.Verdict == "BLOCK"
|
|
}
|
|
|
|
// IsFlagged returns true if this finding has FLAG verdict.
|
|
func (f *Finding) IsFlagged() bool {
|
|
return f.Verdict == "FLAG"
|
|
}
|
|
|
|
// ConflictingSource represents a source that conflicts with a code claim.
|
|
type ConflictingSource struct {
|
|
// Path of the authoritative source.
|
|
Path string `json:"path"`
|
|
|
|
// SourceClass (tier name).
|
|
SourceClass string `json:"source_class"`
|
|
|
|
// Authoritative value.
|
|
Value string `json:"value"`
|
|
|
|
// RFC/OWASP citation if applicable.
|
|
Citation string `json:"citation,omitempty"`
|
|
|
|
// PolicySource info if from a Trust Pack.
|
|
PolicySource *PolicySource `json:"policy_source,omitempty"`
|
|
}
|
|
|
|
// PolicySource contains information about a Trust Pack.
|
|
type PolicySource struct {
|
|
PackName string `json:"pack_name"`
|
|
PackVersion string `json:"pack_version"`
|
|
IssuerHex string `json:"issuer_hex"`
|
|
}
|
|
|
|
// Acknowledgment contains information about a conflict acknowledgment.
|
|
type Acknowledgment struct {
|
|
Timestamp string `json:"timestamp"`
|
|
By string `json:"by"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
// ConflictTrace contains debug information about conflict resolution.
|
|
type ConflictTrace struct {
|
|
CodeClaim string `json:"code_claim"`
|
|
AuthorityMatch string `json:"authority_match"`
|
|
AuthorityTier string `json:"authority_tier"`
|
|
Resolution string `json:"resolution"`
|
|
}
|
|
|
|
// ScanSummary contains counts by verdict.
|
|
type ScanSummary struct {
|
|
Total int `json:"total"`
|
|
Blocked int `json:"blocked"`
|
|
Flagged int `json:"flagged"`
|
|
Passed int `json:"passed"`
|
|
Acknowledged int `json:"acknowledged"`
|
|
}
|
|
|
|
// Scan runs a scan on a project for conflicts.
|
|
//
|
|
// Scans the specified project directory, extracts claims from code/config,
|
|
// and checks them against authoritative sources.
|
|
//
|
|
// When FailOnFlag is true and BLOCK findings exist, the API returns 422
|
|
// and this method returns an APIError with StatusCode 422. The result
|
|
// is still accessible via the error.
|
|
//
|
|
// Example:
|
|
//
|
|
// result, err := client.Aphoria().Scan(ctx, ScanParams{
|
|
// TargetPath: "/path/to/project",
|
|
// Format: "json",
|
|
// FailOnFlag: true,
|
|
// })
|
|
// if err != nil {
|
|
// if apiErr, ok := err.(*APIError); ok && apiErr.StatusCode == 422 {
|
|
// // Scan succeeded but found blocking issues
|
|
// fmt.Printf("Found %d blocking issues\n", result.Summary.Blocked)
|
|
// }
|
|
// }
|
|
func (a *AphoriaClient) Scan(ctx context.Context, params ScanParams) (*ScanResult, error) {
|
|
if params.Format == "" {
|
|
params.Format = "json"
|
|
}
|
|
|
|
var result ScanResult
|
|
if err := a.client.doJSON(ctx, "POST", "/v1/aphoria/scan", params, &result); err != nil {
|
|
// For 422, we still want to return the result if possible
|
|
if apiErr, ok := err.(*APIError); ok && apiErr.StatusCode == 422 {
|
|
// The response body may contain the scan result
|
|
return &result, fmt.Errorf("scan found blocking issues: %w", err)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|