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 }