Skip to content

Evidence & Reports

Developer Reference

This page covers internal implementation details. It is not included in the User Guide.

Evidence is a first-class, append-only, citable record. It attaches to Report Items, Findings, Plans, Bundles, and Runs. Every Evidence record carries native cloud-console deep links so any claim can be verified in one click in the source-of-truth system.

Design intent vs shipped

This page documents the target Evidence model — Option A from the design review: Evidence attaches to Report Items directly (not only to Findings). Mandatory console_links[] is part of the schema.

What's verified shipped today:

  • The v3 Appendix A canonical model says Evidence is first-class and attaches to Findings / Plans / Runs.
  • The v4 Analysis Agent semantic-payload spec references evidence_refs[] and evidence_id fields.

What's not yet implemented (in the running CE API per context_engine_api.py):

  • An Evidence collection or /resolve/evidence endpoint
  • The console-link generation module
  • Per-Report-Item Evidence linking
  • The full export pipeline described in the Export section below

Treat this page as the design contract. Engineering needs to confirm storage location, retention, and signing before any of this lands in code.


Where Evidence attaches

Report  →  Report Item  →  Evidence record(s)
                         + EstateView pin

Finding →  the Report Item it derives from
        +  additional Evidence captured during triage

Plan    →  one or more Findings
        +  Plan-time Evidence (assumptions, blast-radius reads)

Bundle  →  one or more Plan revisions
        +  Bundle-time Evidence (current state at materialisation)

Run     →  the Bundle
        +  Run-time Evidence (before / after state, API calls, approvals)

Every artifact at every stage has its own Evidence chain. Re-running a Report on a fresher EstateView produces a new Report version with a fresh evidence chain; old versions are retained immutably.


Evidence record schema

yaml
evidence:
  id: ev_01HX...                   # immutable ULID
  schema_version: 1

  type: cloudtrail_event           # enum, see below
  captured_at: 2026-05-13T09:14:22Z
  estate_view_id: ev_42            # which EstateView snapshot this belongs to

  source:
    system: aws.cloudtrail         # aws.* | azure.* | github.* | jira.* | linear.* | internal.*
    region: us-east-1              # nullable for global services
    account_id: "123456789012"     # AWS account / Azure subscription / GCP project
    tenant_id: "11111111-2222-..." # Azure-only (Microsoft tenant)
    native_id: "e-1234-..."        # provider-side ID (event ID, ARN, resource ID)
    api_call: "lookup-events"      # the call Escher made to capture this

  console_links:                   # MANDATORY: at least one entry
    - label: "View event in CloudTrail"
      href: "https://us-east-1.console.aws.amazon.com/cloudtrail/home?region=us-east-1#/events/e-1234-..."
    - label: "View bucket in S3 Console"
      href: "https://us-east-1.console.aws.amazon.com/s3/buckets/prod-customer-uploads"

  payload:                         # raw, type-specific content. JSON-serialisable. Immutable.
    # shape depends on `type` — examples below

  summary: "IAM user 'bot-deploy' rotated its access key at 09:13 UTC."

  attached_to:
    - report_item_id: ri_...
    - finding_id: fnd_...
    - plan_id: pln_...
    - run_id: run_...

  redaction_class: standard        # standard | sensitive | pii — controls export masking

Type enum (closed set)

Adding a new type requires a schema_version bump.

TypeCapturessource.system examples
cloudtrail_eventA single AWS CloudTrail eventaws.cloudtrail
azure_activity_logA single Azure Activity Log entryazure.activitylog
config_snapshotFull configuration of a specific resource at a momentaws.s3, aws.ec2, azure.storage
iam_policyA policy document (inline or managed)aws.iam, azure.rbac
billing_lineA line item from Cost Explorer / Cost Managementaws.ce, azure.costmanagement
metric_pointA single metric value at a timestampaws.cloudwatch, azure.monitor
log_lineA single log line from CloudWatch / Azure Monitor logsaws.cloudwatch.logs, azure.loganalytics
deployment_recordA deployment event (CI/CD trigger, commit, PR)github.*
pr_recordA pull request's metadata + diff summarygithub.pullrequest
commit_recordA specific commitgithub.commit
approval_recordA human approval action (in-app, Slack, webhook)internal.approval
api_callAn Escher-made API call with its request + responseinternal.api_log
manual_noteHuman-attached context, comment, or assumptioninternal.note

Cloud console URL patterns

Console links are generated at evidence-capture time by a pure function over (type, source, native_id). No extra API call needed.

AWS

ResourceURL pattern
S3 buckethttps://{region}.console.aws.amazon.com/s3/buckets/{bucket}?region={region}
S3 objecthttps://{region}.console.aws.amazon.com/s3/object/{bucket}?region={region}&prefix={key}
EC2 instancehttps://{region}.console.aws.amazon.com/ec2/home?region={region}#InstanceDetails:instanceId={id}
Security grouphttps://{region}.console.aws.amazon.com/ec2/home?region={region}#SecurityGroup:groupId={sg_id}
IAM userhttps://us-east-1.console.aws.amazon.com/iam/home#/users/{name}
IAM rolehttps://us-east-1.console.aws.amazon.com/iam/home#/roles/{name}
IAM policyhttps://us-east-1.console.aws.amazon.com/iam/home#/policies/{arn}$jsonEditor
RDS instancehttps://{region}.console.aws.amazon.com/rds/home?region={region}#database:id={id};is-cluster=false
Lambda functionhttps://{region}.console.aws.amazon.com/lambda/home?region={region}#/functions/{name}
CloudTrail eventhttps://{region}.console.aws.amazon.com/cloudtrail/home?region={region}#/events/{event_id}
Cost Explorerhttps://us-east-1.console.aws.amazon.com/cost-management/home#/cost-explorer
Config rulehttps://{region}.console.aws.amazon.com/config/home?region={region}#/rules/details?configRuleName={name}

Notes:

  • IAM, Cost Explorer, and Organizations are global services — region segment is always us-east-1 for these URLs (AWS convention).
  • The console requires the user to already be signed into the right AWS account in their browser. Multi-account environments can prepend a sign-in URL: https://signin.aws.amazon.com/switchrole?account={account_id}&roleName={role}&destination={encoded_url}.

Azure

Base portal URL: https://portal.azure.com/#@{tenant_id}

ResourceURL pattern
Subscription overviewhttps://portal.azure.com/#@{tenant_id}/resource/subscriptions/{sub_id}/overview
Resource (generic)https://portal.azure.com/#@{tenant_id}/resource/subscriptions/{sub_id}/resourceGroups/{rg}/providers/{provider}/{type}/{name}
Storage accounthttps://portal.azure.com/#@{tenant_id}/resource/subscriptions/{sub_id}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{name}/overview
Virtual machinehttps://portal.azure.com/#@{tenant_id}/resource/subscriptions/{sub_id}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/{name}/overview
NSGhttps://portal.azure.com/#@{tenant_id}/resource/subscriptions/{sub_id}/resourceGroups/{rg}/providers/Microsoft.Network/networkSecurityGroups/{name}/overview
Activity loghttps://portal.azure.com/#@{tenant_id}/resource/subscriptions/{sub_id}/activityLog
Cost managementhttps://portal.azure.com/#@{tenant_id}/resource/subscriptions/{sub_id}/costmanagement

Notes:

  • Tenant ID is required for any Azure portal link. Captured during Connect Azure (the az login step) and stored on the profile.
  • Subscription-level pages don't need a resource group.
  • For multi-tenant users, the @{tenant_id} segment routes to the correct tenant on portal load.

GCP (roadmap)

GCP support is not shipped today (see Connect GCP). When it ships, console URLs will follow https://console.cloud.google.com/{service}/{resource}?project={project_id} patterns.


SurfaceRendering
Canvas — inline claimCitation chip on the claim. Click → drawer opens with summary, payload preview, and console_links rendered as buttons at the top of the drawer (e.g. "View bucket in S3 Console ↗")
Canvas — PDF exportEach Evidence record renders as a footnote at the bottom of the page. console_links render as labelled URLs (printable + clickable in PDF readers)
JSON exportconsole_links[] array preserved in full
Markdown exportInline link under each cited claim: [View bucket in S3 Console](https://...)
Slack notificationUp to 2 console links surfaced as Block Kit buttons; "+N more" overflow
Jira / Linear ticketAll console links listed in the ticket description under "Verify in cloud:"

Export modes

Three formats. Redaction class is chosen at export time.

FormatBest forWhat's in it
PDF (audit pack)External auditors, board readoutsCover page (tenant, EstateView pin, generated_at, scope, redaction class) → claims with footnoted citations → appendix with full evidence records. Console links printed + clickable.
JSON (SIEM / GRC ingest)Splunk, Datadog, custom GRC toolsTop-level Findings array with evidence_refs[]. Separate evidence[] array so duplicate references are stored once. Bulk audit pack = ZIP of manifest.json + evidence/{id}.json + findings/{id}.json + report-summary.pdf.
Markdown (tickets / wikis)Drop into Jira description, Notion page, GitHub IssueClaims as bullets, evidence as expandable <details> blocks. Useful for collaboration; less suitable for formal audit.

Redaction model

ClassWhat's masked
standard (default)Nothing — full evidence with native IDs and console links
sensitiveAccount IDs, IPs, IAM principal names, billing amounts. Console links rewritten to the generic service home (e.g. https://console.aws.amazon.com/s3/) — external auditor can't pivot from the link
piiEverything in sensitive + masks email-looking strings, phone-number-looking strings, and tag values flagged as containing PII

Redaction is applied at export time. The underlying Evidence record is always stored at standard class — redaction is a view, not a destructive transform.


Reports vs Evidence — clean boundary

ReportEvidence
What it isSnapshot of observed data — a list of Report Items against a pinned EstateViewImmutable proof for a specific claim — typed, source-attributed, console-linked
MutabilityNew versions can be generated; old versions retainedAppend-only, never modified
CitationsCites its EstateView pin + per-Report-Item Evidence recordsn/a — Evidence is the citation
LifecycleGenerated by scans or by user request; versioned over timeCreated at the moment the cited fact is observed; never deleted

Reports cite Evidence. Findings, Plans, Bundles, Runs cite Evidence. Evidence cites the cloud.


Open items requiring engineering sign-off

  1. Where Evidence records live — new CE collection (escher_evidence_<tenant_id>) or a separate write-optimised store? Volume profile is millions of immutable records per tenant per quarter.
  2. Signing. Per-record HMAC, per-export PGP, or per-bundle X.509? "Signed evidence bundles" was previously claimed as Enterprise — what's the actual implementation path?
  3. Retention windows. Defaults per tier — legal sign-off needed.
  4. Console-link generator module location. Lives next to the type enum so they evolve together.
  5. Azure tenant-ID capture. Captured at az login time during Connect Azure and stored on the profile.

Next steps

Escher — Agentic CloudOps by Tessell