Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions docs/guides/regulated-agent-runtime-quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,38 @@ const gateway = new PolicyGateway({
The plugin runs after token validation and before normal tier assignment. If it
returns a deny or transform decision, the request stops there.

When a token carries `regulatedDataPolicy`, the plugin intersects those
token-bound allowlists with deployment defaults. The token can narrow processor,
model, region, purpose, data class, context-field, and fallback authority. It
cannot expand deployment policy.

```typescript
const tokenRequest = {
issuer,
subject: agentId,
resource: "fhir://fhir.example.org/Observation/*",
actions: ["read"],
constraints: {},
regulatedDataPolicy: {
allowedDataClasses: ["PHI"],
allowedPurposes: ["TREAT"],
approvedProcessors: ["in-region-model-router"],
approvedRegions: ["us-east-1"],
approvedModels: ["clinical-summary-local"],
allowedContextFields: ["resourceType", "interaction", "resourceId"],
allowFallback: true,
},
delegationChain: { parentTokenId: null, depth: 0, attenuated: false },
expiresAt,
revocable: true,
};
```

## Build Regulated Metadata

```typescript
import {
buildFHIRRegulatedDataPolicy,
buildFHIRRegulatedRuntimeMetadata,
mapFHIRToSint,
withRegulatedRuntimeParams,
Expand All @@ -74,6 +102,15 @@ const mapping = mapFHIRToSint({
patientId: "patient-456",
});

const regulatedDataPolicy = buildFHIRRegulatedDataPolicy(mapping, {
allowedPurposes: ["TREAT"],
approvedProcessors: ["in-region-model-router"],
approvedRegions: ["us-east-1"],
approvedModels: ["clinical-summary-local"],
allowedContextFields: ["resourceType", "interaction", "resourceId"],
allowFallback: true,
});

const regulatedData = buildFHIRRegulatedRuntimeMetadata(mapping, {
purposeOfUse: "TREAT",
processor: "in-region-model-router",
Expand All @@ -88,6 +125,30 @@ const params = withRegulatedRuntimeParams(
);
```

Use `regulatedDataPolicy` as `SintCapabilityTokenRequest.regulatedDataPolicy`
when issuing the token for this workflow.

Delegation can only narrow this policy. For example, a parent token can remove
`patientId` from the child token's context authority:

```typescript
import { delegateCapabilityToken } from "@pshkv/gate-capability-tokens";

const delegated = delegateCapabilityToken(
parentToken,
{
newSubject: childAgentId,
tightenRegulatedDataPolicy: {
allowedContextFields: ["resourceType", "interaction", "resourceId"],
},
},
parentAgentPrivateKey,
);
```

If the child request asks for fields outside that narrowed list, the gateway
returns a `transform` decision with `minimize_context` audit metadata.

The resulting request params include:

```json
Expand Down
13 changes: 13 additions & 0 deletions docs/specs/regulated-agent-runtime-profile-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ These checks are token-bound. Configuration may provide deployment defaults, but
the effective authority must come from the signed capability token and any valid
attenuated delegation chain.

The optional `regulatedDataPolicy` capability-token extension binds:

- allowed data classes
- allowed purposes of use
- approved processors
- approved models
- approved regions
- allowed downstream context fields
- fallback-routing permission

Deployment policy can be stricter than the token. It must not expand token
authority.

## Context Attenuation

Downstream agents inherit less authority than their parent, never more. If a
Expand Down
62 changes: 60 additions & 2 deletions examples/regulated-agent-runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ This example shows the intended integration shape for regulated-data workflows:

```typescript
import {
buildFHIRRegulatedDataPolicy,
buildFHIRRegulatedRuntimeMetadata,
mapFHIRToSint,
withRegulatedRuntimeParams,
} from "@pshkv/bridge-health";
import {
delegateCapabilityToken,
issueCapabilityToken,
} from "@pshkv/gate-capability-tokens";
import {
DefaultRegulatedDataPolicyPlugin,
PolicyGateway,
Expand All @@ -27,11 +32,64 @@ const mapping = mapFHIRToSint({
patientId: "patient-456",
});

const regulatedDataPolicy = buildFHIRRegulatedDataPolicy(mapping, {
allowedPurposes: ["TREAT"],
approvedProcessors: ["in-region-model-router"],
approvedRegions: ["us-east-1"],
approvedModels: ["clinical-summary-local"],
allowedContextFields: [
"resourceType",
"interaction",
"resourceId",
"patientId",
],
allowFallback: false,
});

const parentToken = issueCapabilityToken(
{
issuer: issuerPublicKey,
subject: parentAgentPublicKey,
resource: mapping.resource,
actions: [mapping.action],
constraints: {},
regulatedDataPolicy,
delegationChain: { parentTokenId: null, depth: 0, attenuated: false },
expiresAt,
revocable: true,
},
issuerPrivateKey,
);

const delegatedToken = parentToken.ok
? delegateCapabilityToken(
parentToken.value,
{
newSubject: childAgentPublicKey,
tightenRegulatedDataPolicy: {
allowedContextFields: ["resourceType", "interaction", "resourceId"],
},
},
parentAgentPrivateKey,
)
: parentToken;

if (parentToken.ok) tokenStore.set(parentToken.value.tokenId, parentToken.value);
if (delegatedToken.ok) {
tokenStore.set(delegatedToken.value.tokenId, delegatedToken.value);
}

const regulatedData = buildFHIRRegulatedRuntimeMetadata(mapping, {
purposeOfUse: "TREAT",
processor: "in-region-model-router",
region: "us-east-1",
model: "clinical-summary-local",
requestedContextFields: [
"resourceType",
"interaction",
"resourceId",
"patientId",
],
});

const gateway = new PolicyGateway({
Expand All @@ -47,8 +105,8 @@ const gateway = new PolicyGateway({
const decision = await gateway.intercept({
requestId,
timestamp,
agentId,
tokenId,
agentId: childAgentPublicKey,
tokenId: delegatedToken.ok ? delegatedToken.value.tokenId : tokenId,
resource: mapping.resource,
action: mapping.action,
params: withRegulatedRuntimeParams({ fhir: mapping.context }, regulatedData),
Expand Down
23 changes: 23 additions & 0 deletions packages/bridge-health/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,12 @@ purpose, fallback, and context-minimization rules inside `PolicyGateway.intercep

```typescript
import {
buildFHIRRegulatedDataPolicy,
buildFHIRRegulatedRuntimeMetadata,
mapFHIRToSint,
withRegulatedRuntimeParams,
} from "@pshkv/bridge-health";
import { delegateCapabilityToken } from "@pshkv/gate-capability-tokens";

const mapping = mapFHIRToSint({
serverUrl: "https://fhir.example.org",
Expand All @@ -270,6 +272,15 @@ const mapping = mapFHIRToSint({
patientId: "patient-456",
});

const regulatedDataPolicy = buildFHIRRegulatedDataPolicy(mapping, {
allowedPurposes: ["TREAT"],
approvedProcessors: ["in-region-model-router"],
approvedRegions: ["us-east-1"],
approvedModels: ["clinical-summary-local"],
allowedContextFields: ["resourceType", "interaction", "resourceId"],
allowFallback: true,
});

const regulatedData = buildFHIRRegulatedRuntimeMetadata(mapping, {
purposeOfUse: "TREAT",
processor: "in-region-model-router",
Expand All @@ -285,6 +296,18 @@ const params = withRegulatedRuntimeParams(

// The bridge forwards `resource`, `action`, and `params` to PolicyGateway.intercept().
// It does not authorize the request itself.
// Use `regulatedDataPolicy` as SintCapabilityTokenRequest.regulatedDataPolicy.

const delegatedToken = delegateCapabilityToken(
parentToken,
{
newSubject: childAgentPublicKey,
tightenRegulatedDataPolicy: {
allowedContextFields: ["resourceType", "interaction"],
},
},
parentAgentPrivateKey,
);
```

### HealthKit On-Device Access
Expand Down
Loading