edl: 0.1
name: github-coding-agent
version: 0.1.0

agent:
  id: agent:coding-agent
  kind: llm_agent
  image: ghcr.io/acme/coding-agent@sha256:REPLACE_WITH_DIGEST
  command: ["agent", "run", "--repo", "/workspace/repo"]
  workdir: /workspace
  supply_chain:
    image_digest_required: true
    signed_image_required: true
    sbom_required: true

authority:
  principals:
    - id: human:repo-owner
      kind: human
      identity: passkey_or_oidc
    - id: agent:coding-agent
      kind: agent
      identity: cryptographic
      credential: short_lived
      attestation: required
  mandates:
    - id: mandate:repo-maintenance
      issued_by: human:repo-owner
      issued_to: agent:coding-agent
      purpose: "Prepare pull requests and request approved merges."
      valid_for: 8h
      revocable: true
  delegations:
    - id: delegation:repo-maintenance
      from: human:repo-owner
      to: agent:coding-agent
      mandate: mandate:repo-maintenance
      may_delegate: false
      expires_after: 8h
  capabilities:
    - id: cap:github-propose-pr
      principal: agent:coding-agent
      effects: [github.propose_pr]
      resources: [repo:acme/app]
      ttl: 10m
    - id: cap:github-merge-pr
      principal: agent:coding-agent
      effects: [github.merge_pr]
      resources: [repo:acme/app:branch:main]
      ttl: 2m
      single_use: true
      requires:
        approval: human:repo-owner
        liveness: passkey

enclosure:
  id: enc:github-coding-agent
  mode: enforce
  backend_preference: [docker, seatbelt]
  fail_closed: true
  filesystem:
    root: read_only
    workdir:
      path: /workspace
      access: read_write
    mounts:
      - host: ./repo
        guest: /workspace/repo
        access: read_write
    deny:
      - ~/.ssh
      - ~/.git-credentials
      - ~/.config/gh
      - /var/run/docker.sock
  network:
    default: deny
    allow:
      - id: doorkeeper
        url: https://doorkeeper.internal
        ports: [443]
    deny:
      - github.com
      - api.github.com
      - ssh.github.com
  process:
    user: non_root
    no_new_privileges: true
    drop_linux_capabilities: all
    seccomp: default_or_stricter
    pids_max: 256
    timeout: 30m
    cpu: 2
    memory: 4Gi
    disallow:
      - docker
      - gh auth
      - ssh-add
  secrets:
    ambient: deny
    host_keychain: deny
    ssh_agent: deny
    mount_tokens: deny
    allowed_runtime_tokens:
      - audience: doorkeeper
        ttl: 5m

gates:
  - id: gate:github
    kind: mcp_doorkeeper
    endpoint: https://doorkeeper.internal/mcp
    target: github
    credential_custody: server_side_only
    actions:
      - effect: github.propose_pr
        decision: allow
        parameters:
          require_schema: schemas/github.propose_pr.json
      - effect: github.merge_pr
        decision: escalate
        requires_capability: cap:github-merge-pr
        requires_receipt: true
        postcondition:
          webhook: github.pull_request.closed
          verify: merged_by == "github-app:wauth-doorkeeper"
      - effect: github.force_push
        decision: deny

policies:
  - id: policy:github
    default: deny
    rules:
      - when: effect == "github.propose_pr" and resource.repo == "acme/app"
        decision: allow
      - when: effect == "github.merge_pr" and resource.branch == "main"
        decision: escalate
        require: [approval, liveness, single_use_capability]
      - when: effect in ["github.force_push", "github.disable_branch_protection"]
        decision: deny

memory:
  default: isolated
  stores:
    - id: mem:session
      isolation: per_session
      ttl: 24h
      source_attribution: required
      hash_validation: on_read
    - id: mem:repo-summary
      isolation: per_repo
      ttl: 7d
      promote_to_fact_requires: review

telemetry:
  required: true
  trace: opentelemetry
  tamper_evident: true
  export: [ledger, siem]
  must_emit:
    - invocation.start
    - invocation.end
    - enclosure.attested
    - policy.evaluated
    - capability.issued
    - effect.intent
    - effect.denied
    - effect.escalated
    - effect.executed
    - receipt.emitted
  include:
    - trace_id
    - agent_id
    - principal_id
    - mandate_id
    - delegation_id
    - capability_jti
    - enclosure_id
    - enclosure_profile_digest
    - action_hash
    - policy_id
    - gate_id

receipts:
  action_bound: true
  single_use_for_high_risk: true
  postcondition_required_for:
    - github.merge_pr

reconciliation:
  - id: recon:github-merge
    source: github.webhook
    events: [push, pull_request.closed]
    match_receipt:
      event: github_action_executed
      fields: [repo, branch, commit_sha, actor]
    on_missing_receipt: violation
    on_non_gate_actor: violation

assertions:
  impossible:
    - agent_has_github_token
    - agent_can_reach_github_api_directly
    - agent_can_read_ssh_keys
    - github_merge_without_receipt
  negative_tests:
    - name: direct_github_api_blocked
      attempt: curl https://api.github.com
      expect: network_denied
    - name: ssh_key_unreadable
      attempt: cat ~/.ssh/id_rsa
      expect: file_denied

compile:
  targets:
    - capsulang
    - docker
    - seatbelt
    - doorkeeper
    - telemetry
    - negative_tests
