Skip to content
ARP / SPEC
VERSION v0.1 — DRAFT

Scope catalog v1

Purpose: the canonical set of ~50 scope templates that compile into Cedar policies. This is the UX surface humans interact with when approving an agent connection — they never write Cedar, they pick scopes from this catalog.

What ships today: @kybernesis/arp-scope-catalog (source: packages/scope-catalog/scopes/*.yaml) plus a small HTTP service that serves the compiled JSON at /.well-known/scope-catalog.json. The dashboard renders the human consent UI from this catalog at pairing time.

Related:


1. Scope-template data model

Every scope template is a structured record with this shape:

id: string                      # stable OAuth-style dotted ID
version: string                 # semver, pinned per scope
label: string                   # short human-readable name (UI button/row)
description: string             # longer explanation (UI tooltip / expanded view)
category: enum                  # for UI grouping (§3)
risk: enum                      # low | medium | high | critical
parameters:                     # optional fill-in-the-blanks
  - name: string
    type: enum                  # ProjectID | Integer | Duration | Timezone | etc.
    required: bool
    default: any
    validation: string          # regex or enum values
cedar_template: string          # handlebars-style template compiled per-connection
consent_text_template: string   # rendered in plain English for the consent UI
obligations_forced: [string]    # obligations always attached (e.g., require_fresh_consent)
implies: [string]               # scope IDs automatically included
conflicts_with: [string]        # scope IDs that cannot coexist
tier_gate: string               # optional minimum VC requirement (e.g., vc_provider.adult)
                                # **tier_gate** — a VC type identifier (opaque string). Example values use
                                # `vc_provider.*` prefixes for historical continuity; any VC provider's type
                                # strings work. See `ARP-policy-examples.md §6.9`.
step_up_required: bool          # does granting need fresh principal consent beyond pairing?

Full YAML catalog is the source of truth; compiled JSON is served at /.well-known/scope-catalog.json.


2. Risk tiers

TierMeaningUX treatment
lowRead-only metadata, no content, no side effectsOne-tap approve
mediumContent reads, content writes, scoped mutations, money under capsInline approval
highExternal sharing, autonomous sending, deletions, broad data classesExpanded consent screen with confirm-phrase
criticalKey rotation, re-delegation, identity-adjacent actionsBiometric confirmation + waiting period

Risk determines default obligations (e.g., high forces audit_level: verbose and require_fresh_consent every 7 days).


3. Categories

identity      — connection meta, credentials, introductions
calendar      — time, availability, meetings
messaging     — email, chat, relay
files         — projects, documents, folders
contacts      — address book, attributes, introductions
tasks         — todos, assignments, project mgmt
notes         — notes, knowledge base, RAG
payments      — quotes, authorizations, refunds, history
work          — status, reports, professional context
credentials   — VC presentation, proof requests, reputation
tools         — MCP / function invocation
delegation    — re-delegation, forwarding, multi-hop
location      — presence, geo, physical context (v0.2+)
health        — health data (v0.2+)

4. The full catalog (50 scopes)

The catalog also ships one special scope outside the human-facing categories: system.trusted.full_access — used by the bundle.trusted_full_access.v1 bundle to grant blanket access between agents under the same principal (intra-company / same-owner automation). Human consent UI calls this out explicitly so it can never be granted accidentally.

#Scope IDLabelCatRiskParams
1identity.card.readRead agent cardidentitylow
2identity.principal.verifyVerify owner bindingidentitylow
3identity.introduction.requestRequest introductionidentitymediumto_agent
4connection.extendExtend connection expiryidentitymediumdays
5connection.rescope.requestRequest new scopesidentitymedium
6calendar.availability.readCheck availability (free/busy only)calendarlowdays_ahead
7calendar.events.readRead event detailscalendarmediumwindow_days, include_private
8calendar.events.proposePropose a meetingcalendarmediummax_attendees, max_duration_min
9calendar.events.createCreate events directlycalendarhighmax_per_day
10calendar.events.modifyModify existing eventscalendarhigh
11calendar.events.cancelCancel eventscalendarhigh
12messaging.email.summaryEmail summaries onlymessagingmediumlabel_filter
13messaging.email.thread.readRead email threadsmessaginghighlabel_filter
14messaging.email.draft.composeCompose drafts (no send)messagingmedium
15messaging.email.send.reviewedSend after human reviewmessaginghighrecipient_allowlist
16messaging.relay.to_principalRelay message to ownermessaginglow
17messaging.chat.sendSend chat messagemessagingmediumchannel_allowlist
18files.projects.listList projectsfileslow
19files.project.metadata.readRead project metadatafileslowproject_id
20files.project.files.listList files in a projectfileslowproject_id
21files.project.files.readRead file contentsfilesmediumproject_id, max_size_mb
22files.project.files.summarizeSummaries only (derive)fileslowproject_id, max_output_words
23files.project.files.writeCreate/modify filesfileshighproject_id, max_size_mb
24files.project.files.deleteDelete filesfilescriticalproject_id
25files.share.externalShare files outside circlefilescriticalproject_id, recipient_allowlist
26contacts.searchLook up contactscontactsmediumattribute_allowlist
27contacts.attributes.readRead specific contact attributescontactsmediumattributes
28contacts.introduceRequest contact introductioncontactsmedium
29contacts.shareShare a contact cardcontactshighrecipient_allowlist
30tasks.listList taskstaskslowproject_id
31tasks.readRead task detailstaskslowproject_id
32tasks.createCreate taskstasksmediumproject_id, max_per_day
33tasks.status.updateUpdate task statustasksmediumproject_id
34tasks.assignAssign tasks to humanstaskshighproject_id
35notes.searchSearch notesnotesmediumcollection_id
36notes.readRead notesnotesmediumcollection_id
37notes.writeCreate/update notesnotesmediumcollection_id, max_per_day
38knowledge.queryQuery knowledge basenotesmediumkb_id, max_tokens
39payments.quote.requestRequest a price quotepaymentslow
40payments.authorize.cappedAuthorize payment under cappaymentshighmax_per_txn_usd, max_per_30d_usd
41payments.history.readRead past transactionspaymentsmediumdays_back
42payments.refund.requestRequest refundpaymentsmedium
43work.status.readCurrent work statusworklow
44work.projects.listCurrent active projectsworklow
45work.reports.summaryGenerate status summaryworkmediumperiod
46credentials.present.requestRequest specific VCscredentialsmediumrequired_vcs
47credentials.proof.zk.requestRequest ZK proof of attributecredentialsmediumattribute, predicate
48tools.invoke.readInvoke read-only toolstoolsmediumtool_allowlist
49tools.invoke.mutatingInvoke mutating toolstoolshightool_allowlist, max_per_day
50delegation.forward.taskForward task to another agentdelegationhighagent_allowlist, scope_attenuation

5. Detailed specs — representative examples

One per category, showing the full template shape and Cedar compilation.

5.1 identity.card.read

id: identity.card.read
version: 1.0.0
label: Read agent card
description: Allow the peer agent to fetch your agent card (name, supported protocols, public endpoints).
category: identity
risk: low
parameters: []
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action == Action::"read",
    resource == AgentCard::"self"
  );
consent_text_template: "See your public agent card."
obligations_forced: []
implies: []
conflicts_with: []
step_up_required: false

Consent UI: "See your public agent card."

5.2 calendar.availability.read

id: calendar.availability.read
version: 1.0.0
label: Check availability (free/busy only)
description: Peer can see when you're free or busy, but no event titles, attendees, or details.
category: calendar
risk: low
parameters:
  - name: days_ahead
    type: Integer
    required: true
    default: 14
    validation: "1..90"
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action == Action::"check_availability",
    resource == Calendar::"primary"
  ) when {
    context.query_window_days <= {{days_ahead}}
  };
  forbid (
    principal == Agent::"{{audience_did}}",
    action,
    resource == Calendar::"primary"
  ) when {
    action != Action::"check_availability"
  };
consent_text_template: "Check your free/busy (no details) up to {{days_ahead}} days ahead."
obligations_forced:
  - type: redact_fields
    params:
      fields: ["event.title", "event.attendees", "event.description", "event.location"]
implies: []
conflicts_with: []
step_up_required: false

Consent UI: "Check your free/busy (no details) up to 14 days ahead."

5.3 calendar.events.propose

id: calendar.events.propose
version: 1.0.0
label: Propose a meeting
description: Peer can propose a meeting. Creates a tentative event pending your confirmation.
category: calendar
risk: medium
parameters:
  - name: max_attendees
    type: Integer
    required: true
    default: 10
    validation: "1..50"
  - name: max_duration_min
    type: Integer
    required: true
    default: 60
    validation: "15..480"
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action == Action::"propose_meeting",
    resource == Calendar::"primary"
  ) when {
    context.proposed_attendee_count <= {{max_attendees}} &&
    context.proposed_duration_min <= {{max_duration_min}}
  };
consent_text_template: "Propose meetings (up to {{max_attendees}} people, {{max_duration_min}} minutes). You confirm before it's booked."
obligations_forced:
  - type: require_principal_confirmation
    params:
      max_age_seconds: 86400
implies:
  - calendar.availability.read
conflicts_with: []
step_up_required: false

Consent UI: "Propose meetings (up to 10 people, 60 minutes). You confirm before it's booked."

5.4 files.project.files.read

id: files.project.files.read
version: 1.0.0
label: Read file contents
description: Peer can read the contents of files in a specific project.
category: files
risk: medium
parameters:
  - name: project_id
    type: ProjectID
    required: true
  - name: max_size_mb
    type: Integer
    required: true
    default: 10
    validation: "1..100"
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action in [Action::"read", Action::"list"],
    resource in Project::"{{project_id}}"
  ) when {
    resource.size_bytes <= {{max_size_mb}} * 1048576 &&
    !resource.tags.contains("confidential") &&
    !resource.tags.contains("do-not-share")
  };
consent_text_template: "Read files in {{project.name}} (up to {{max_size_mb}} MB each; excludes items tagged confidential)."
obligations_forced:
  - type: audit_level
    params:
      level: verbose
implies:
  - files.project.files.list
  - files.project.metadata.read
conflicts_with: []
step_up_required: false

5.5 messaging.email.send.reviewed

id: messaging.email.send.reviewed
version: 1.0.0
label: Send email (after your review)
description: Peer drafts emails on your behalf; each send requires your one-tap approval before going out.
category: messaging
risk: high
parameters:
  - name: recipient_allowlist
    type: EmailList
    required: false
    default: []
    validation: "rfc5322-or-domain-glob"
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action == Action::"send_email",
    resource == Email::"outbox"
  ) when {
    {{#if recipient_allowlist}}
    context.recipient_matches_allowlist({{recipient_allowlist}})
    {{else}}
    true
    {{/if}}
  };
consent_text_template: "Draft and (with your approval) send emails{{#if recipient_allowlist}} to: {{recipient_allowlist_display}}{{/if}}."
obligations_forced:
  - type: require_principal_confirmation
    params:
      max_age_seconds: 0
  - type: audit_level
    params:
      level: verbose
implies:
  - messaging.email.draft.compose
conflicts_with: []
step_up_required: true

5.6 payments.authorize.capped

id: payments.authorize.capped
version: 1.0.0
label: Authorize payments up to a cap
description: Peer can trigger x402 payments up to the per-transaction and rolling 30-day caps you set.
category: payments
risk: high
parameters:
  - name: max_per_txn_usd
    type: Decimal
    required: true
    default: 5
    validation: "0.01..1000"
  - name: max_per_30d_usd
    type: Decimal
    required: true
    default: 50
    validation: "0.01..10000"
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action == Action::"authorize_payment",
    resource == Wallet::"primary"
  ) when {
    context.quoted_price_usd <= {{max_per_txn_usd}} &&
    context.spend_last_30d_usd + context.quoted_price_usd <= {{max_per_30d_usd}}
  };
consent_text_template: "Pay up to ${{max_per_txn_usd}} per request, ${{max_per_30d_usd}} total per 30 days."
obligations_forced:
  - type: notify_principal
    params: {}
  - type: audit_level
    params:
      level: verbose
implies: []
conflicts_with: []
tier_gate: vc_provider.verified_human
step_up_required: true

5.7 credentials.proof.zk.request

id: credentials.proof.zk.request
version: 1.0.0
label: Request ZK proof of an attribute
description: Peer can ask your agent to present a zero-knowledge proof (via any pluggable VC provider) of a single attribute without revealing the underlying credential.
category: credentials
risk: medium
parameters:
  - name: attribute
    type: Enum
    required: true
    validation: ["over_18", "over_21", "us_resident", "verified_human", "country"]
  - name: predicate
    type: Enum
    required: false
    default: "eq"
    validation: ["eq", "gte", "lte", "in"]
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action == Action::"request_zk_proof",
    resource == Credential::"{{attribute}}"
  ) when {
    context.predicate == "{{predicate}}"
  };
consent_text_template: "Prove to Peer, without revealing details, that you are {{attribute_human}}."
obligations_forced:
  - type: log_zk_disclosure
    params: {}
implies: []
conflicts_with: []
step_up_required: false

5.8 tools.invoke.mutating

id: tools.invoke.mutating
version: 1.0.0
label: Invoke tools with side effects
description: Peer can invoke specific tools on your MCP server that cause changes (not just reads).
category: tools
risk: high
parameters:
  - name: tool_allowlist
    type: ToolIDList
    required: true
    validation: "at-least-one"
  - name: max_per_day
    type: Integer
    required: true
    default: 20
    validation: "1..1000"
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action == Action::"invoke_tool",
    resource == Tool
  ) when {
    resource.id in {{tool_allowlist_json}} &&
    context.requests_last_day <= {{max_per_day}}
  };
consent_text_template: "Use these tools on your behalf (max {{max_per_day}}/day): {{tool_allowlist_display}}."
obligations_forced:
  - type: audit_level
    params:
      level: verbose
  - type: rate_limit
    params:
      window: day
      max: "{{max_per_day}}"
implies: []
conflicts_with: []
step_up_required: true

5.9 delegation.forward.task

id: delegation.forward.task
version: 1.0.0
label: Forward task to another agent
description: Peer can re-delegate a task to a third agent, with automatically attenuated scopes.
category: delegation
risk: high
parameters:
  - name: agent_allowlist
    type: AgentDIDList
    required: true
    validation: "at-least-one"
  - name: scope_attenuation
    type: Enum
    required: true
    default: "read_only"
    validation: ["read_only", "same_scopes", "custom"]
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action == Action::"redelegate",
    resource == Connection::"self"
  ) when {
    context.delegate_target in {{agent_allowlist_json}} &&
    context.attenuation_mode == "{{scope_attenuation}}"
  };
consent_text_template: "Allow Peer to forward tasks to: {{agent_allowlist_display}}. Forwarded tasks get {{scope_attenuation_human}} access."
obligations_forced:
  - type: audit_level
    params:
      level: verbose
  - type: notify_principal
    params: {}
implies: []
conflicts_with: []
tier_gate: vc_provider.verified_human
step_up_required: true

5.10 contacts.search

id: contacts.search
version: 1.0.0
label: Look up contacts
description: Peer can search your contacts and receive only the attributes you specify.
category: contacts
risk: medium
parameters:
  - name: attribute_allowlist
    type: AttributeList
    required: true
    default: ["name", "email"]
    validation: ["name","email","phone","title","company","linkedin","twitter","notes"]
cedar_template: |
  permit (
    principal == Agent::"{{audience_did}}",
    action == Action::"search_contacts",
    resource == Contact
  );
consent_text_template: "Search your contacts and see these fields: {{attribute_allowlist_display}}."
obligations_forced:
  - type: redact_fields_except
    params:
      allowlist: "{{attribute_allowlist}}"
implies: []
conflicts_with: []
step_up_required: false

6. Scope bundles (common multi-scope packages)

Bundles are named, versioned sets of scopes with pre-filled parameters. The UI offers them as one-tap presets before exposing individual scopes.

bundle.project_collaboration.v1

Typical "collaborate on a project" bundle.

files.projects.list
files.project.metadata.read       (project_id: <user-picks>)
files.project.files.read          (project_id: same, max_size_mb: 25)
files.project.files.summarize     (project_id: same, max_output_words: 2000)
tasks.list                        (project_id: same)
tasks.read                        (project_id: same)
tasks.status.update               (project_id: same)
notes.search                      (collection_id: project-scoped)
notes.read                        (collection_id: project-scoped)

bundle.scheduling_assistant.v1

For agents coordinating meetings.

calendar.availability.read        (days_ahead: 14)
calendar.events.propose           (max_attendees: 10, max_duration_min: 60)
contacts.search                   (attribute_allowlist: [name, email])
messaging.relay.to_principal

bundle.research_agent.v1

For an agent pulling research without writing.

files.projects.list
files.project.files.read          (project_id: <user-picks>)
files.project.files.summarize     (same)
notes.search                      (collection_id: <user-picks>)
notes.read                        (same)
knowledge.query                   (kb_id: same, max_tokens: 8000)
credentials.proof.zk.request      (attribute: verified_human)

bundle.procurement_agent.v1

For an agent that can buy things under tight caps.

payments.quote.request
payments.authorize.capped         (max_per_txn_usd: 25, max_per_30d_usd: 200)
payments.history.read             (days_back: 90)
messaging.relay.to_principal

bundle.executive_assistant.v1

Broad assistant with step-up consent on anything external-facing.

calendar.availability.read
calendar.events.propose
calendar.events.modify
messaging.email.summary
messaging.email.draft.compose
messaging.email.send.reviewed     (recipient_allowlist: [])
contacts.search
tasks.* (list/read/create/update_status)
notes.search/read/write
work.status.read
work.reports.summary

7. Implication graph (automatic includes)

Some scopes automatically pull in prerequisites so the consent UI doesn't ask the same thing twice.

files.project.files.read
  → files.project.files.list
  → files.project.metadata.read

files.project.files.summarize
  → files.project.files.read

calendar.events.propose
  → calendar.availability.read

tasks.status.update
  → tasks.read

messaging.email.send.reviewed
  → messaging.email.draft.compose

tools.invoke.mutating
  → tools.invoke.read        (if counterpart expects symmetric reads)

Implications are transitive; the PDP expands them when compiling Cedar.


8. Conflict rules

Certain scopes cannot coexist on the same Connection — the UI prevents selecting both.

files.project.files.delete          ⊥  files.share.external
  (can't grant both in the same connection — too much combined blast radius)

messaging.email.send.reviewed       ⊥  messaging.email.send.autonomous
  (pick one send model)

payments.authorize.capped           ⊥  payments.authorize.unlimited   (v0 has no unlimited; listed for future)

9. Parameter types & validation

TypeValidationExample
Integerrange1..90
Decimalrange, 2dp0.01..1000
DurationISO 8601 durationP14D, PT60M
ProjectIDexists in owner's project registryalpha, research-2026
AgentDIDvalid DID URIdid:web:ghost.agent
AgentDIDListarray of AgentDID, min-1[did:web:ghost.agent]
ToolIDListexists in agent's MCP registry["search","calc"]
AttributeListenum constrained["name","email"]
EmailListRFC5322 or *@domain.com glob["alice@example.com","*@corp.com"]
IANATimezonevalid IANA TZAmerica/New_York
Enumdefined per-scope"read_only"

10. Versioning & change management

  • Each scope is independently semver-versioned (e.g., calendar.events.propose@1.2.0)
  • Catalog has a top-level version (v1) pinned per Connection Token
  • Breaking changes to a scope require a new major version; old connections keep the pinned version
  • Adding a scope: minor catalog bump
  • Removing a scope: requires deprecation period (≥180 days) and migration mapping
  • Catalog source of truth lives in packages/scope-catalog/scopes/*.yaml in the monorepo

The scope catalog is a projection spec: each scope knows how to render itself into plain English. A realistic pairing request UI:

Ghost (ghost.agent) wants to connect with Samantha.

Purpose: Project Alpha collaboration

Ghost WILL be able to:
  ✓ Read files in Project Alpha (up to 25 MB each)
  ✓ Summarize files in Project Alpha
  ✓ Read and update task status in Project Alpha
  ✓ Check your availability up to 14 days ahead
  ✓ Propose meetings (up to 10 people, 60 min)
  ✓ Pay up to $5 per request, $50 per 30 days

Ghost WILL NOT be able to:
  ✗ Delete files or share Project Alpha outside this connection
  ✗ Send email on your behalf
  ✗ Access any other project, your contacts, or your calendar details

Ghost must prove:
  • Verified human (via attribute VC)

Conditions:
  • Weekdays 09:00–17:00 America/New_York
  • Every email-like action requires your one-tap approval
  • Connection expires October 22, 2026

[ Approve ]    [ Adjust scopes ]    [ Cancel ]

Every line above is generated from the scope catalog. No hand-written copy per connection.


12. What's out of scope for v1

Reserved for v0.2+ (call out explicitly so they're not mistakenly implemented now):

  • Location / physical presence scopes
  • Health data scopes
  • Financial account read/write (beyond x402 payments)
  • Multi-party connections (3+ agents sharing one scope)
  • Dynamic scopes (scopes whose parameters change over time)
  • ML-derived scopes (e.g., "all files semantically similar to X")

These are deferred because they raise regulatory, privacy, or technical design questions that would slow v1.


13. Governance

  • Source of truth: packages/scope-catalog/scopes/*.yaml in the monorepo
  • Additions: PR to the repo, minor catalog bump
  • Deprecations: RFC + 180-day notice
  • Breaking changes per scope: new major version; old versions frozen but still usable
  • Any implementer (the gateway, third-party runtimes, registrar templates) reads the compiled JSON from https://gateway.arp.run/.well-known/scope-catalog.json (managed) or your self-hosted equivalent — never hand-maintained copies

Source: docs/ARP-scope-catalog-v1.md · Ported 2026-04-23