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:
@@ -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
|
Fusion (Axway Amplify AI Gateway) is a **shared SaaS instance**, configured in the
|
||||||
console, not deployed by this repo. Because it's SaaS it **cannot reach private
|
console — not deployed by this repo. Here it is the **enforcement plane** in front
|
||||||
VPC resources or use local AWS creds**, so it does not call Presidio or DynamoDB
|
of the AgentCore agent: de-identification is a **policy on the route**, not
|
||||||
directly. Instead it calls two **public HTTPS endpoints** this repo exposes
|
application code. `/tokenize` and `/restore` are the policy's **transform backend**
|
||||||
(built in `gateway_api/`, tasks T1/T2), which do the detection, minting, vault
|
(they hold the detector + vault on-prem); Fusion applies them inline to traffic.
|
||||||
writes, and restore on the AWS side. Fusion owns the orchestration and routing.
|
|
||||||
|
|
||||||
## 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`
|
- `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`
|
||||||
`terraform -chdir=terraform output -raw tokenize_url`.
|
- `RESTORE_URL` = `https://<...>/restore` — `terraform ... output -raw restore_url`
|
||||||
- `RESTORE_URL` = `https://<...>/restore` (egress: vault lookup + re-attach identity — T2)
|
- `AGENT_RUNTIME_ARN` (the route's upstream) = printed by `scripts/agentcore_setup.sh`
|
||||||
- `AGENT_RUNTIME_ARN` (or its HTTPS invoke endpoint) = printed by `scripts/agentcore_setup.sh`
|
- **Auth:** public API Gateway HTTP API; send the shared secret in the **`x-api-key`**
|
||||||
- **Auth (T1, live):** the endpoint is a public API Gateway HTTP API; it requires a
|
header (`Authorization: Bearer <secret>` also works). The secret is the Terraform
|
||||||
shared secret in the **`x-api-key`** header (a `Authorization: Bearer <secret>`
|
`tokenize_api_key` var (gitignored `terraform/local.auto.tfvars`, never committed) —
|
||||||
header also works). Configure Fusion's outbound request to send it. The secret is
|
hand it to the console operator out of band.
|
||||||
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.
|
|
||||||
- *Note:* a Lambda Function URL was the first choice, but this account's SCP blocks
|
- *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)
|
## Request policy — ingress (prompt bound for the agent)
|
||||||
1. **Authenticate** the advisor; apply the RBAC / business-purpose check.
|
Applied to every request on the route; the app cannot skip it.
|
||||||
2. **Tokenize**: POST `{ query }` to `TOKENIZE_URL`. Receive
|
1. **AuthN / RBAC / business-purpose** check (Fusion native).
|
||||||
`{ deidentified_prompt, session_id }`. (The endpoint runs detect → mint →
|
2. **Tokenize transform:** `POST { query }` → `TOKENIZE_URL` → `{ deidentified_prompt,
|
||||||
vault-write → splice; detection returns typed findings, never a redacted blob.)
|
session_id }`. The endpoint runs detect → mint → vault-write → splice (typed
|
||||||
3. **Route**: invoke the agent with `{ "prompt": deidentified_prompt }`.
|
findings, never a redacted blob). **Replace** the outbound prompt with
|
||||||
4. **Trace**: log the **tokenized** payload only — never the raw query.
|
`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)
|
## Response policy — egress (agent's answer)
|
||||||
1. Receive the agent's de-identified result.
|
Applied to every response on the route.
|
||||||
2. **Restore**: POST `{ session_id, text }` to `RESTORE_URL`; receive `{ final }`.
|
1. **Restore transform:** `POST { session_id, text }` → `RESTORE_URL` → `{ final }`
|
||||||
3. Return `{ "final": ..., "deidentified_prompt": ... }` so the UI shows the split view.
|
(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)
|
## Vault item shape (DynamoDB, written by the tokenize transform)
|
||||||
One row per detected entity. `value` is the original PII (so `/restore` can put it
|
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
|
back); a resolvable PERSON also gets a `customer_id` so the RAG tool can turn the
|
||||||
token into a de-identified evidence package.
|
token into a de-identified evidence package.
|
||||||
```
|
```
|
||||||
{ "token": "CUST_863651", "type": "PERSON", "value": "王小明",
|
{ "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",
|
{ "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`
|
(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
|
## Fusion vs. the `/demo` orchestrator (be honest on camera)
|
||||||
Keeping detection, minting, and the vault behind `/tokenize` and `/restore` means
|
`gateway_api/orchestrator/` (`/demo`) runs the *identical* tokenize → agent →
|
||||||
the only things exposed to the SaaS gateway are two authenticated HTTPS endpoints —
|
restore mechanics over plain HTTP, so the Fusion-less demo works end to end. Show
|
||||||
no AWS creds or private resources leave the account, and Fusion stays a pure
|
that to prove the data-flow. Then make the governance point: only Fusion turns
|
||||||
orchestration/routing layer. That is also the cleanest story on camera: the
|
those mechanics into a **mandatory, audited policy** across all AI traffic — the
|
||||||
gateway owns the flow; the cloud only ever sees tokens.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user