docs: reframe POLICY_SETUP as a Fusion de-id policy

Position /tokenize + /restore as the transform backend of an inline request/
response policy on the route to the AgentCore agent, with Fusion as the
non-bypassable enforcement + audit point — rather than a manual HTTP
orchestrator. Adds an honest Fusion-vs-/demo governance note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:26:18 +10:00
parent acaf145903
commit 2443e0e6f3

View File

@@ -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://<api-id>.execute-api.<region>.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 <secret>`
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 <secret>` 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": "<conv id>", "expires_at": <epoch+ttl>, "customer_id": "cust-0001" }
"session_id": "<session>", "expires_at": <epoch+ttl>, "customer_id": "cust-0001" }
{ "token": "TW_683250", "type": "TW_ROC_ID", "value": "A123456789",
"session_id": "<conv id>", "expires_at": <epoch+ttl> }
"session_id": "<session>", "expires_at": <epoch+ttl> }
```
(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.