diff --git a/fusion/POLICY_SETUP.md b/fusion/POLICY_SETUP.md index 2b689be..14e1763 100644 --- a/fusion/POLICY_SETUP.md +++ b/fusion/POLICY_SETUP.md @@ -1,55 +1,89 @@ -# Fusion (shared SaaS) policy setup (steps 2, 3-route, 8) +# Fusion (shared SaaS) policy setup — de-identification as a gateway policy -Fusion is a **shared SaaS instance** — configured in the Amplify AI Gateway -console, not deployed by this repo. Because it's SaaS it **cannot reach private -VPC resources or use local AWS creds**, so it does not call Presidio or DynamoDB -directly. Instead it calls two **public HTTPS endpoints** this repo exposes -(built in `gateway_api/`, tasks T1/T2), which do the detection, minting, vault -writes, and restore on the AWS side. Fusion owns the orchestration and routing. +Fusion (Axway Amplify AI Gateway) is a **shared SaaS instance**, configured in the +console — not deployed by this repo. Here it is the **enforcement plane** in front +of the AgentCore agent: de-identification is a **policy on the route**, not +application code. `/tokenize` and `/restore` are the policy's **transform backend** +(they hold the detector + vault on-prem); Fusion applies them inline to traffic. -## Endpoints Fusion calls +> **Why put Fusion in the path at all?** Functionally, everything below is an HTTP +> call — the `/demo` orchestrator proves a plain client can do the same mechanics. +> That is the point: the mechanics are **not** the differentiator. Fusion adds what +> a plain call cannot: +> - **Non-bypassable enforcement** — de-id is applied at the gateway to *all* AI +> traffic, not trusted to each app to call `/tokenize` correctly. +> - **Central audit** — the gateway's own trace is the compliance artifact proving +> only tokens ever reached the model. (See the money-shot check in the README.) +> - **Reuse** — one policy governs every model/agent/app behind Fusion; routing, +> rate-limits, and guardrails come with it. +> +> Drop Fusion and you lose *enforcement and audit*, not capability. + +## The policy model +Fusion fronts the AgentCore Runtime as a governed route. Two policies wrap it: + +``` +advisor ─▶ [ Fusion route to the agent ] + │ request policy (ingress) ── tokenize transform ─▶ /tokenize + ▼ + AgentCore Runtime (sees TOKENS only) + │ response policy (egress) ── restore transform ──▶ /restore + ▼ +advisor ◀────┘ +``` + +The transforms run **inside the policy boundary**; the raw name never crosses it. + +## Backend the policy calls - `TOKENIZE_URL` = `https://.execute-api..amazonaws.com/tokenize` - (ingress: detect + mint + vault-write + splice). Get the live value with - `terraform -chdir=terraform output -raw tokenize_url`. -- `RESTORE_URL` = `https://<...>/restore` (egress: vault lookup + re-attach identity — T2) -- `AGENT_RUNTIME_ARN` (or its HTTPS invoke endpoint) = printed by `scripts/agentcore_setup.sh` -- **Auth (T1, live):** the endpoint is a public API Gateway HTTP API; it requires a - shared secret in the **`x-api-key`** header (a `Authorization: Bearer ` - header also works). Configure Fusion's outbound request to send it. The secret is - the Terraform `tokenize_api_key` var (kept in gitignored `terraform/local.auto.tfvars`, - never committed) — hand it to the Fusion console operator out of band. + — `terraform -chdir=terraform output -raw tokenize_url` +- `RESTORE_URL` = `https://<...>/restore` — `terraform ... output -raw restore_url` +- `AGENT_RUNTIME_ARN` (the route's upstream) = printed by `scripts/agentcore_setup.sh` +- **Auth:** public API Gateway HTTP API; send the shared secret in the **`x-api-key`** + header (`Authorization: Bearer ` also works). The secret is the Terraform + `tokenize_api_key` var (gitignored `terraform/local.auto.tfvars`, never committed) — + hand it to the console operator out of band. - *Note:* a Lambda Function URL was the first choice, but this account's SCP blocks - unauthenticated Function URLs, so the public front door is API Gateway instead. + unauthenticated Function URLs, so the public front door is API Gateway. -## Ingress policy (advisor request → cloud) -1. **Authenticate** the advisor; apply the RBAC / business-purpose check. -2. **Tokenize**: POST `{ query }` to `TOKENIZE_URL`. Receive - `{ deidentified_prompt, session_id }`. (The endpoint runs detect → mint → - vault-write → splice; detection returns typed findings, never a redacted blob.) -3. **Route**: invoke the agent with `{ "prompt": deidentified_prompt }`. -4. **Trace**: log the **tokenized** payload only — never the raw query. +## Request policy — ingress (prompt bound for the agent) +Applied to every request on the route; the app cannot skip it. +1. **AuthN / RBAC / business-purpose** check (Fusion native). +2. **Tokenize transform:** `POST { query }` → `TOKENIZE_URL` → `{ deidentified_prompt, + session_id }`. The endpoint runs detect → mint → vault-write → splice (typed + findings, never a redacted blob). **Replace** the outbound prompt with + `deidentified_prompt`; carry `session_id` as policy/session context. +3. **Forward** to the AgentCore upstream with `{ "prompt": deidentified_prompt }` — + tokens only. +4. **Audit:** record the **tokenized** payload; the raw query never leaves the policy. -## Egress policy (cloud response → advisor) -1. Receive the agent's de-identified result. -2. **Restore**: POST `{ session_id, text }` to `RESTORE_URL`; receive `{ final }`. -3. Return `{ "final": ..., "deidentified_prompt": ... }` so the UI shows the split view. +## Response policy — egress (agent's answer) +Applied to every response on the route. +1. **Restore transform:** `POST { session_id, text }` → `RESTORE_URL` → `{ final }` + (vault lookup scoped to this session; re-attaches identity at the envelope and + swaps any inline tokens). +2. **Return** `{ "final": ..., "deidentified_prompt": ... }` so the UI renders the + restored-vs-tokenized split view. -## Vault item shape (DynamoDB, written by /tokenize) -One row per detected entity. `value` is the original PII (so `/restore` can put it +## Vault item shape (DynamoDB, written by the tokenize transform) +One row per detected entity. `value` is the original PII (so restore can put it back); a resolvable PERSON also gets a `customer_id` so the RAG tool can turn the token into a de-identified evidence package. ``` { "token": "CUST_863651", "type": "PERSON", "value": "王小明", - "session_id": "", "expires_at": , "customer_id": "cust-0001" } + "session_id": "", "expires_at": , "customer_id": "cust-0001" } { "token": "TW_683250", "type": "TW_ROC_ID", "value": "A123456789", - "session_id": "", "expires_at": } + "session_id": "", "expires_at": } ``` (The seed's `--with-demo-token` writes a different `type=CUSTOMER, value=cust-0001` -row; that's only a standalone RAG test aid, not what `/tokenize` mints.) +row; that's only a standalone RAG test aid, not what the tokenize transform mints.) -## Why this split -Keeping detection, minting, and the vault behind `/tokenize` and `/restore` means -the only things exposed to the SaaS gateway are two authenticated HTTPS endpoints — -no AWS creds or private resources leave the account, and Fusion stays a pure -orchestration/routing layer. That is also the cleanest story on camera: the -gateway owns the flow; the cloud only ever sees tokens. +## Fusion vs. the `/demo` orchestrator (be honest on camera) +`gateway_api/orchestrator/` (`/demo`) runs the *identical* tokenize → agent → +restore mechanics over plain HTTP, so the Fusion-less demo works end to end. Show +that to prove the data-flow. Then make the governance point: only Fusion turns +those mechanics into a **mandatory, audited policy** across all AI traffic — the +enforcement boundary an app-level HTTP call can't guarantee. + +> Not automated here: Fusion is shared SaaS and configured by a human in the +> console. This file is the spec for that configuration.