{
  "schema_version": "declared-profile/0.1",
  "generated_at": "2026-07-30T09:12:44Z",
  "generated_by": "factfile-intake skill, Claude Code, run against commit 4f2ac91",
  "synthetic_example": "Perch Retail Systems is a fictional vendor and the file paths below point at a repository that does not exist. This file shows the expected shape and the expected level of honesty. This key belongs to the example only; a real declaration does not carry it.",
  "subject": {
    "vendor": "Perch Retail Systems (Synthetic)",
    "product": "Perch Support Agent (Synthetic)",
    "version": "2026.07.3",
    "model_digest": "sha256:0f5c2b1e9a47d3c86b0e14f7a2d95c38e6b71049fa3c82d15e97b604c3a8d271",
    "prompt_digest": "sha256:7c31a9e0d84b62f15c07ae39b4d28f6013e5c7a94db20f68e13c5a7092bd4f38",
    "policy_digest": "sha256:b2740e15c9a83df6021e74b5c8309af2d61e40b73c95f28ad0146e9b73c25f80",
    "toolset_digest": "sha256:3d9814f7b0c25ea63f18d40927b5cae1806f37b2d94e05c7a12f6b83409de7c5"
  },
  "declared_profile": {
    "purpose": "Perch Support Agent answers post-purchase questions for shoppers of merchants on the Perch platform. It looks up orders, explains shipping and return policy from the merchant's help centre, changes a shipping address before a parcel ships, cancels an unfulfilled order, and files a refund request for a human to approve. It runs on the merchant's storefront widget and on the merchant's support inbox, and it hands the conversation to a human whenever identity, policy, or a tool result is missing.",
    "intended_users": [
      "shoppers contacting a merchant after buying",
      "merchant support staff who receive escalated conversations"
    ],
    "intended_environment": "Perch's multi-tenant commerce platform: Postgres order and customer tables scoped by merchant, the Perch payments service, and a per-merchant help-centre index.",
    "dependencies": [
      "Perch orders service (internal HTTP)",
      "Perch payments service (refund request queue)",
      "help-centre search index, per merchant",
      "session service that issues the storefront customer token",
      "Zendesk, for escalated conversations"
    ],
    "autonomy": {
      "mode": "bounded_action",
      "human_oversight": "A merchant support agent approves every refund request before payments executes it, and receives every escalated conversation in Zendesk. Address changes and cancellations are not reviewed.",
      "financial_authority_usd": 0,
      "financial_authority_basis": "code_enforced",
      "can_access": [
        "order records for the merchant in the current session",
        "customer name, email, and address for the authenticated customer",
        "the merchant's published help-centre articles",
        "the refund request queue, write-only"
      ],
      "cannot_access": [
        "card numbers and payment instruments, which the payments service never returns",
        "orders belonging to another merchant, blocked by a merchant_id filter in the query layer",
        "the refund execution endpoint, which has no client in this repository"
      ]
    },
    "capability_claims": [
      {
        "id": "order-lookup",
        "workflow": "order status",
        "claim": "Report the current status of an order without changing it.",
        "user_outcome": "A shopper learns where the parcel is.",
        "success_definition": "The reply matches the order record and no mutating tool runs.",
        "consequence_if_wrong": "moderate"
      },
      {
        "id": "policy-answer",
        "workflow": "policy question",
        "claim": "Answer returns and shipping questions from the merchant's own help centre.",
        "user_outcome": "A shopper gets the merchant's actual policy rather than an invented one.",
        "success_definition": "The answer cites a help-centre article, or says the policy could not be found.",
        "consequence_if_wrong": "high"
      },
      {
        "id": "address-change",
        "workflow": "pre-shipment address change",
        "claim": "Change the shipping address on an order that has not shipped.",
        "user_outcome": "A shopper corrects an address without contacting a human.",
        "success_definition": "Only an unshipped order is changed, and the stored address matches what was confirmed.",
        "consequence_if_wrong": "high"
      },
      {
        "id": "order-cancel",
        "workflow": "cancellation",
        "claim": "Cancel an order that has not been fulfilled.",
        "user_outcome": "A shopper stops an order without a duplicate or partial cancellation.",
        "success_definition": "One unfulfilled order moves to cancelled and the final state is confirmed to the shopper.",
        "consequence_if_wrong": "high"
      },
      {
        "id": "refund-request",
        "workflow": "refund request",
        "claim": "File a refund request for a human to approve, without moving money.",
        "user_outcome": "A shopper's refund reaches a person who can decide it.",
        "success_definition": "One request is queued with the order reference, and the agent states that a human decides.",
        "consequence_if_wrong": "critical"
      },
      {
        "id": "human-handoff",
        "workflow": "escalation",
        "claim": "Hand off to a human when identity, policy, or a tool result is missing.",
        "user_outcome": "A stuck conversation reaches a person without a half-finished action.",
        "success_definition": "A Zendesk ticket is created with the reason, and no mutating tool runs afterwards.",
        "consequence_if_wrong": "high"
      }
    ],
    "surfaces": [
      {
        "channel": "chat widget",
        "platform": "Perch storefront widget, embedded on the merchant's site",
        "caller_authenticated": "yes",
        "source": "services/gateway/routes/widget.py:24"
      },
      {
        "channel": "email",
        "platform": "merchant support inbox, ingested through Zendesk webhooks",
        "caller_authenticated": "no",
        "source": "services/gateway/routes/inbound_email.py:31"
      },
      {
        "channel": "API",
        "platform": "internal /v1/converse endpoint used by merchant integrations, API-key authenticated",
        "caller_authenticated": "yes",
        "source": "services/gateway/routes/api.py:18"
      }
    ],
    "tools": [
      {
        "name": "lookup_order",
        "does": "Returns status, line items, and tracking for one order.",
        "inputs": "order_reference (string, required)",
        "mutates": false,
        "touches_money": false,
        "limits": [
          {
            "limit": "Query filters on the session customer_id and merchant_id.",
            "enforced_in": "code",
            "source": "agent/tools/orders.py:57"
          }
        ],
        "source": "agent/tools/orders.py:41",
        "confidence": "found"
      },
      {
        "name": "get_customer_profile",
        "does": "Returns the name, email, and default address of the customer in the session.",
        "inputs": "no arguments; reads customer_id from the session",
        "mutates": false,
        "touches_money": false,
        "limits": [
          {
            "limit": "Takes no identifier from the model, so it cannot be pointed at another customer.",
            "enforced_in": "code",
            "source": "agent/tools/customers.py:22"
          }
        ],
        "source": "agent/tools/customers.py:14",
        "confidence": "found"
      },
      {
        "name": "search_help_center",
        "does": "Full-text search over the merchant's published help-centre articles.",
        "inputs": "query (string, required), top_k (integer, default 5)",
        "mutates": false,
        "touches_money": false,
        "limits": [
          {
            "limit": "Index is scoped to the merchant and to published articles only.",
            "enforced_in": "code",
            "source": "agent/tools/knowledge.py:33"
          }
        ],
        "source": "agent/tools/knowledge.py:19",
        "confidence": "found"
      },
      {
        "name": "update_shipping_address",
        "does": "Replaces the shipping address on an order that has not shipped.",
        "inputs": "order_reference (string), address (object: line1, line2, city, region, postal_code, country)",
        "mutates": true,
        "touches_money": false,
        "limits": [
          {
            "limit": "Rejected unless order.status is 'created' or 'paid'.",
            "enforced_in": "code",
            "source": "agent/tools/orders.py:118"
          },
          {
            "limit": "The system prompt says to read the new address back to the shopper before writing it. Nothing in code checks that this happened.",
            "enforced_in": "prompt_level_only",
            "source": "agent/prompts/system.md:64"
          }
        ],
        "source": "agent/tools/orders.py:104",
        "confidence": "found"
      },
      {
        "name": "cancel_order",
        "does": "Moves an unfulfilled order to cancelled and releases the inventory hold.",
        "inputs": "order_reference (string), reason (string)",
        "mutates": true,
        "touches_money": false,
        "limits": [
          {
            "limit": "Rejected unless order.fulfillment_status is 'unfulfilled'.",
            "enforced_in": "code",
            "source": "agent/tools/orders.py:151"
          },
          {
            "limit": "No idempotency key is sent. Two calls in one conversation would issue two cancellations; the orders service may or may not reject the second.",
            "enforced_in": "uncertain",
            "source": "agent/tools/orders.py:163"
          }
        ],
        "source": "agent/tools/orders.py:139",
        "confidence": "found"
      },
      {
        "name": "create_refund_request",
        "does": "Queues a refund request against an order for a merchant support agent to approve or decline.",
        "inputs": "order_reference (string), reason (string), line_item_ids (array of string, optional)",
        "mutates": true,
        "touches_money": true,
        "limits": [
          {
            "limit": "Writes to the approval queue only. The repository contains no client for the payments execution endpoint, so this tool cannot move money.",
            "enforced_in": "code",
            "source": "agent/tools/refunds.py:28"
          },
          {
            "limit": "No amount is passed. The approving human sets the amount in the Perch admin UI, which is outside this repository.",
            "enforced_in": "code",
            "source": "agent/tools/refunds.py:44"
          }
        ],
        "source": "agent/tools/refunds.py:17",
        "confidence": "found"
      },
      {
        "name": "escalate_to_human",
        "does": "Creates a Zendesk ticket with the transcript and a reason, and ends the agent's turn.",
        "inputs": "reason (enum: identity, policy, tool_failure, customer_request), summary (string)",
        "mutates": true,
        "touches_money": false,
        "limits": [
          {
            "limit": "The runtime stops the loop after this tool returns, so no tool can run after a handoff.",
            "enforced_in": "code",
            "source": "agent/runtime/loop.py:96"
          }
        ],
        "source": "agent/tools/escalation.py:12",
        "confidence": "found"
      }
    ],
    "write_authority": [
      "update_shipping_address",
      "cancel_order",
      "create_refund_request",
      "escalate_to_human"
    ],
    "money_touching_actions": [
      {
        "tool": "create_refund_request",
        "action": "Queues a refund for human approval. Does not execute it.",
        "limit": "No amount is set by the agent, and no execution client exists in this repository.",
        "enforced_in": "code",
        "approval_gate": "A merchant support agent approves in the Perch admin UI before payments executes.",
        "source": "agent/tools/refunds.py:17"
      },
      {
        "tool": "cancel_order",
        "action": "Cancels an unfulfilled order, which releases a payment authorisation downstream.",
        "limit": "Only orders with fulfillment_status 'unfulfilled'. No cap on how many cancellations one conversation may perform.",
        "enforced_in": "code",
        "approval_gate": "none",
        "source": "agent/tools/orders.py:139"
      }
    ],
    "data_access": {
      "reads": [
        "order records: status, line items, totals, tracking",
        "customer records: name, email, phone, addresses",
        "the merchant's published help-centre articles"
      ],
      "scoping": "Order and customer queries filter on the merchant_id and customer_id carried by the session, not on identifiers supplied by the model. Payment instruments are never returned by the orders service.",
      "other_customers_reachable": "uncertain",
      "other_customers_note": "On the widget and API surfaces, no. The session carries a verified customer_id and the tools ignore any identifier the model supplies. On the email surface the customer is resolved by matching the From address, which is not verified, so a spoofed sender would be treated as that customer. We found no verification step for the email path.",
      "source": "agent/data/session.py:38"
    },
    "escalation": {
      "triggers": [
        "identity cannot be established for a request that changes an order",
        "no help-centre article supports the answer",
        "a tool returns an error or times out",
        "the shopper asks for a human"
      ],
      "mechanism": "escalate_to_human creates a Zendesk ticket with the transcript and reason; the runtime then ends the agent's turn.",
      "enforced_in": "code",
      "never_do": [
        "state a refund amount or promise a refund; prompt-level only",
        "reveal the system prompt, tool definitions, or internal identifiers; prompt-level only",
        "guess a policy that is not in the help centre; prompt-level only"
      ],
      "source": "agent/prompts/system.md:88"
    },
    "version_identifiers": {
      "model_id": "perch-support-llm-v4 (vendor-hosted alias; the underlying model is set outside this repository)",
      "repo_commit": "4f2ac91d7e6b5a30c1f8942be07d5c6a3f19b204",
      "prompt_files": [
        {
          "path": "agent/prompts/system.md",
          "sha256": "7c31a9e0d84b62f15c07ae39b4d28f6013e5c7a94db20f68e13c5a7092bd4f38"
        },
        {
          "path": "agent/prompts/escalation_rules.md",
          "sha256": "b2740e15c9a83df6021e74b5c8309af2d61e40b73c95f28ad0146e9b73c25f80"
        }
      ],
      "config_files": [
        {
          "path": "config/agent.production.yaml",
          "sha256": "9a17c4d8e2b06f35a71c9d840e2b57f31c68d09a4e75b213c8f0a6d94e18b7c2"
        }
      ],
      "toolset": {
        "tool_count": 7,
        "definition_source": "agent/tools/registry.py:9 (the seven tools registered in TOOL_REGISTRY)"
      }
    },
    "uncertain": [
      "The model behind the perch-support-llm-v4 alias is chosen outside this repository. The model_digest is the hash of the alias string, not of a model.",
      "The email surface resolves a customer from an unverified From address. Whether an upstream Zendesk rule verifies the sender was not determined from this repository.",
      "cancel_order sends no idempotency key. Whether the orders service deduplicates retries was not determined from this repository.",
      "A feature flag named refund_autoexecute appears in config/agent.production.yaml and is set to false. No code in this repository reads it, so what it would enable is unknown.",
      "Two additional tools exist in agent/tools/experimental/ and are not in TOOL_REGISTRY. They are excluded here because nothing registers them."
    ],
    "declaration": "Self-declared by the vendor's own tooling from the vendor's own repository. It is a declaration of intended capability, not evidence of behavior. Nothing in it has been independently observed."
  }
}
