From ab7f6a5b1d4e844c8364fde58ea3965b9db30a92 Mon Sep 17 00:00:00 2001
From: Mary Dickson <mary.dickson@virtru.com>
Date: Tue, 7 Apr 2026 09:33:29 -0700
Subject: [PATCH 1/6] chore(docs): restructure Discovery page and add
 Architecture Response Objects (#274)

Discovery:
- Add Setup section with client init for Go, Java, JS
- Add Signature/Parameters/Returns to all 5 methods
- Strip inline JS auth setup from examples
- Link Returns to Policy type references

Architecture:
- Add Response Objects section showing getter patterns across Go/Java/JS
  with four examples: GetAttributeValue, ListAttributeValues, GetDecision,
  GetEntitlements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 docs/sdks/discovery.mdx       | 436 ++++++++++++++++++++++++----------
 docs/sdks/platform-client.mdx | 105 ++++++++
 2 files changed, 414 insertions(+), 127 deletions(-)

diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx
index ec7a2c4b..305833ed 100644
--- a/docs/sdks/discovery.mdx
+++ b/docs/sdks/discovery.mdx
@@ -13,6 +13,57 @@ Before encrypting data with `CreateTDF`, it helps to verify that the attributes
 
 The Go, Java, and JavaScript SDKs provide five methods to discover and validate attributes without manual platform queries.
 
+## Setup
+
+All examples on this page assume you have created a platform client. See [Authentication](/sdks/authentication) for full details including DPoP key binding.
+
+<Tabs>
+<TabItem value="go" label="Go">
+
+```go
+client, err := sdk.New("http://localhost:8080",
+    sdk.WithClientCredentials("opentdf", "secret", nil),
+)
+if err != nil {
+    log.Fatal(err)
+}
+
+// All Go snippets below use `client` and `context.Background()`.
+```
+
+</TabItem>
+<TabItem value="java" label="Java">
+
+```java
+SDK sdk = SDKBuilder.newBuilder()
+    .platformEndpoint("http://localhost:8080")
+    .clientSecret("opentdf", "secret")
+    .useInsecurePlaintextConnection(true)
+    .build();
+
+// All Java snippets below use `sdk`.
+```
+
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
+```typescript
+import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk';
+
+const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
+    clientId: 'opentdf', clientSecret: 'secret',
+    oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
+}))] };
+
+const platformUrl = 'http://localhost:8080';
+
+// All JavaScript snippets below use `auth` and `platformUrl`.
+// Standalone functions like listAttributes() take these as arguments.
+```
+
+</TabItem>
+</Tabs>
+
 <JsAuthNote />
 
 ---
@@ -21,6 +72,41 @@ The Go, Java, and JavaScript SDKs provide five methods to discover and validate
 
 Returns all active attributes on the platform. Use this to see what's available before choosing which attributes to apply to a TDF.
 
+**Signature**
+
+<Tabs groupId="sdk">
+<TabItem value="go" label="Go">
+
+```go
+client.ListAttributes(ctx, ...namespaceFilter)
+```
+
+</TabItem>
+<TabItem value="java" label="Java">
+
+```java
+sdk.listAttributes()
+sdk.listAttributes(namespaceFilter)
+```
+
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
+```typescript
+await listAttributes(platformUrl, auth, namespaceFilter?)
+```
+
+</TabItem>
+</Tabs>
+
+**Parameters**
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `namespaceFilter` | `string` | No | When provided, returns only attributes in this namespace (e.g., `"opentdf.io"`). |
+
+**Example**
+
 <Tabs groupId="sdk">
 <TabItem value="go" label="Go">
 
@@ -67,13 +153,8 @@ List<Attribute> attrs = sdk.listAttributes("opentdf.io");
 </TabItem>
 <TabItem value="js" label="JavaScript">
 
-```ts
-import { authTokenInterceptor, clientCredentialsTokenProvider, listAttributes } from '@opentdf/sdk';
-
-const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
-    clientId: 'opentdf', clientSecret: 'secret',
-    oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
-}))] };
+```typescript
+import { listAttributes } from '@opentdf/sdk';
 
 const attrs = await listAttributes(platformUrl, auth);
 
@@ -87,27 +168,57 @@ for (const a of attrs) {
 
 To filter by namespace:
 
-```ts
+```typescript
 const attrs = await listAttributes(platformUrl, auth, 'opentdf.io');
 ```
 
 </TabItem>
 </Tabs>
 
-`listAttributes` / `ListAttributes` paginates automatically — you always get the full list back without needing to manage page tokens.
+**Returns**
+
+A list of [Attribute](/sdks/policy#attribute-object) objects, each with nested [values](/sdks/policy#attribute-value-object). Paginates automatically — you always get the full list back without managing page tokens.
 
 ---
 
 ## AttributeExists
 
-Reports whether an attribute definition exists on the platform. Returns `true`/`false` — useful for a quick existence check before adding values to an attribute or constructing an access policy.
+Reports whether an attribute definition exists on the platform. Returns `true`/`false` — useful for a quick existence check before adding values or constructing an access policy.
+
+**Signature**
+
+<Tabs groupId="sdk">
+<TabItem value="go" label="Go">
+
+```go
+client.AttributeExists(ctx, attributeFqn)
+```
 
-`attributeFqn` should be an **attribute-level** FQN (no `/value/` segment):
+</TabItem>
+<TabItem value="java" label="Java">
 
+```java
+sdk.attributeExists(attributeFqn)
 ```
-https://<namespace>/attr/<attribute_name>
+
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
+```typescript
+await attributeExists(platformUrl, auth, attributeFqn)
 ```
 
+</TabItem>
+</Tabs>
+
+**Parameters**
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `attributeFqn` | `string` | Yes | An attribute-level FQN (no `/value/` segment): `https://<namespace>/attr/<attribute_name>` |
+
+**Example**
+
 <Tabs groupId="sdk">
 <TabItem value="go" label="Go">
 
@@ -134,13 +245,8 @@ if (!exists) {
 </TabItem>
 <TabItem value="js" label="JavaScript">
 
-```ts
-import { authTokenInterceptor, clientCredentialsTokenProvider, attributeExists } from '@opentdf/sdk';
-
-const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
-    clientId: 'opentdf', clientSecret: 'secret',
-    oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
-}))] };
+```typescript
+import { attributeExists } from '@opentdf/sdk';
 
 const exists = await attributeExists(platformUrl, auth, 'https://opentdf.io/attr/department');
 if (!exists) {
@@ -151,18 +257,50 @@ if (!exists) {
 </TabItem>
 </Tabs>
 
+**Returns**
+
+`true` if the attribute exists, `false` otherwise.
+
 ---
 
 ## AttributeValueExists
 
 Reports whether a specific attribute value FQN exists on the platform. Returns `true`/`false` — useful for spot-checking one pre-registered value.
 
-`valueFqn` should be a **full attribute value** FQN (with `/value/` segment):
+**Signature**
+
+<Tabs groupId="sdk">
+<TabItem value="go" label="Go">
+
+```go
+client.AttributeValueExists(ctx, valueFqn)
+```
+
+</TabItem>
+<TabItem value="java" label="Java">
 
+```java
+sdk.attributeValueExists(valueFqn)
 ```
-https://<namespace>/attr/<attribute_name>/value/<value>
+
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
+```typescript
+await attributeValueExists(platformUrl, auth, valueFqn)
 ```
 
+</TabItem>
+</Tabs>
+
+**Parameters**
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `valueFqn` | `string` | Yes | A full attribute value FQN: `https://<namespace>/attr/<attribute_name>/value/<value>` |
+
+**Example**
+
 <Tabs groupId="sdk">
 <TabItem value="go" label="Go">
 
@@ -189,13 +327,8 @@ if (!exists) {
 </TabItem>
 <TabItem value="js" label="JavaScript">
 
-```ts
-import { authTokenInterceptor, clientCredentialsTokenProvider, attributeValueExists } from '@opentdf/sdk';
-
-const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
-    clientId: 'opentdf', clientSecret: 'secret',
-    oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
-}))] };
+```typescript
+import { attributeValueExists } from '@opentdf/sdk';
 
 const exists = await attributeValueExists(platformUrl, auth, 'https://opentdf.io/attr/department/value/finance');
 if (!exists) {
@@ -206,12 +339,50 @@ if (!exists) {
 </TabItem>
 </Tabs>
 
+**Returns**
+
+`true` if the value exists, `false` otherwise.
+
 ---
 
 ## ValidateAttributes
 
 Checks that a list of attribute value FQNs exist on the platform **before** calling `CreateTDF`. This catches misspellings and missing attributes at the point of use rather than at decryption time.
 
+**Signature**
+
+<Tabs groupId="sdk">
+<TabItem value="go" label="Go">
+
+```go
+client.ValidateAttributes(ctx, fqns...)
+```
+
+</TabItem>
+<TabItem value="java" label="Java">
+
+```java
+sdk.validateAttributes(fqns)
+```
+
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
+```typescript
+await validateAttributes(platformUrl, auth, fqns)
+```
+
+</TabItem>
+</Tabs>
+
+**Parameters**
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `fqns` | `[]string` | Yes | Attribute value FQNs to validate (up to 250 per call). |
+
+**Example**
+
 <Tabs groupId="sdk">
 <TabItem value="go" label="Go">
 
@@ -223,8 +394,7 @@ fqns := []string{
 
 if err := client.ValidateAttributes(ctx, fqns...); err != nil {
     log.Fatalf("attribute validation failed: %v", err)
-    // err will name the specific FQNs that are missing, e.g.:
-    // "attribute not found: https://opentdf.io/attr/clearance/value/executive"
+    // err will name the specific FQNs that are missing
 }
 
 // Safe to encrypt — all attributes confirmed present
@@ -247,8 +417,7 @@ try {
     sdk.validateAttributes(fqns);
 } catch (SDK.AttributeNotFoundException e) {
     System.err.println("attribute validation failed: " + e.getMessage());
-    // getMessage() names the specific FQNs that are missing, e.g.:
-    // "attribute not found: https://opentdf.io/attr/clearance/value/executive"
+    // getMessage() names the specific FQNs that are missing
 }
 
 // Safe to encrypt — all attributes confirmed present
@@ -262,13 +431,8 @@ sdk.createTDF(inputStream, outputStream, config);
 </TabItem>
 <TabItem value="js" label="JavaScript">
 
-```ts
-import { authTokenInterceptor, clientCredentialsTokenProvider, validateAttributes, AttributeNotFoundError } from '@opentdf/sdk';
-
-const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
-    clientId: 'opentdf', clientSecret: 'secret',
-    oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
-}))] };
+```typescript
+import { validateAttributes, AttributeNotFoundError } from '@opentdf/sdk';
 
 const fqns = [
     'https://opentdf.io/attr/department/value/marketing',
@@ -280,8 +444,7 @@ try {
 } catch (e) {
     if (e instanceof AttributeNotFoundError) {
         console.error('attribute validation failed:', e.message);
-        // e.message names the specific FQNs that are missing, e.g.:
-        // "attribute not found: https://opentdf.io/attr/clearance/value/executive"
+        // e.message names the specific FQNs that are missing
     }
 }
 
@@ -291,17 +454,57 @@ try {
 </TabItem>
 </Tabs>
 
-**Why this matters**: `CreateTDF` succeeds even when the attribute doesn't exist on the platform (it embeds the FQN string in the TDF policy). The failure only surfaces later when a decryption client attempts to resolve the attribute through the KAS and gets a `not_found` response. `ValidateAttributes` makes that failure happen immediately, at the point of encryption, with a clear message.
+**Returns**
+
+No return value on success. Throws/returns an error naming the specific FQNs that are missing.
+
+**Why this matters**: `CreateTDF` succeeds even when the attribute doesn't exist on the platform — it embeds the FQN string in the TDF policy. The failure only surfaces later when a decryption client attempts to resolve the attribute through the KAS. `ValidateAttributes` makes that failure happen immediately, with a clear message.
 
 :::tip
-`validateAttributes` / `ValidateAttributes` accepts up to 250 FQNs per call, matching the platform's limit. For larger sets, batch your FQNs across multiple calls.
+Accepts up to 250 FQNs per call, matching the platform's limit. For larger sets, batch across multiple calls.
 :::
 
 ---
 
 ## GetEntityAttributes
 
-Returns the attribute value FQNs assigned to a specific entity (person entity or non-person entity). Use this to inspect what a user, service account, or other principal has been granted — for example, to understand why someone can or cannot decrypt a TDF.
+Returns the attribute value FQNs assigned to a specific entity (person or non-person). Use this to inspect what a user or service account has been granted — for example, to understand why someone can or cannot decrypt a TDF.
+
+**Signature**
+
+<Tabs groupId="sdk">
+<TabItem value="go" label="Go">
+
+```go
+client.GetEntityAttributes(ctx, entity)
+```
+
+</TabItem>
+<TabItem value="java" label="Java">
+
+```java
+sdk.getEntityAttributes(entity)
+```
+
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
+```typescript
+await platformClient.v2.authorization.getEntitlements({ ... })
+```
+
+The JS SDK does not expose a standalone `getEntityAttributes` function. Use `getEntitlements` via `PlatformClient` in a Node.js server environment.
+
+</TabItem>
+</Tabs>
+
+**Parameters**
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `entity` | `Entity` | Yes | The entity to look up. Supports email, username, client ID, and UUID. |
+
+**Example**
 
 <Tabs groupId="sdk">
 <TabItem value="go" label="Go">
@@ -374,14 +577,11 @@ Entity.newBuilder().setId("e1").setUuid("550e8400-e29b-41d4-a716-446655440000").
 </TabItem>
 <TabItem value="js" label="JavaScript">
 
-The JavaScript SDK does not expose a `getEntityAttributes` convenience function because `GetEntitlements` is a server-side/PEP operation that should not be called from browser contexts. In a Node.js server environment, use the `PlatformClient` directly:
-
-```ts
+```typescript
 import { PlatformClient } from '@opentdf/sdk/platform';
 
 const platform = new PlatformClient({ ...auth, platformUrl });
 
-// By email address
 const resp = await platform.v2.authorization.getEntitlements({
   entityIdentifier: {
     identifier: {
@@ -406,7 +606,7 @@ if (resp.entitlements.length > 0) {
 
 Other supported entity types (placed inside the `entities` array):
 
-```ts
+```typescript
 // By username
 { ephemeralId: 'e1', entityType: { case: 'userName', value: 'alice' } }
 
@@ -417,8 +617,12 @@ Other supported entity types (placed inside the `entities` array):
 </TabItem>
 </Tabs>
 
+**Returns**
+
+A list of [attribute value](/sdks/policy#attribute-value-object) FQN strings that the entity is entitled to access.
+
 :::warning Authorization required
-Querying another entity's attributes requires your caller to have appropriate platform permissions on the `GetEntitlements` endpoint. Without this, the call will return a `permission_denied` error. Contact your platform administrator to confirm your service account has the necessary policy access.
+Querying another entity's attributes requires your caller to have appropriate platform permissions on the `GetEntitlements` endpoint. Without this, the call will return a `permission_denied` error.
 :::
 
 ---
@@ -431,100 +635,78 @@ A common pattern is to validate attributes at application startup and surface he
 <TabItem value="go" label="Go">
 
 ```go
-func main() {
-    client, err := sdk.New(platformEndpoint,
-        sdk.WithClientCredentials("opentdf", "secret", nil),
-        sdk.WithInsecureSkipVerifyConn(),
-    )
-    if err != nil {
-        log.Fatalf("sdk init failed: %v", err)
-    }
-
-    // 1. See what's available on the platform
-    attrs, err := client.ListAttributes(ctx)
-    if err != nil {
-        log.Fatalf("could not reach platform: %v", err)
-    }
-    log.Printf("platform has %d active attributes", len(attrs))
+// 1. See what's available on the platform
+attrs, err := client.ListAttributes(ctx)
+if err != nil {
+    log.Fatalf("could not reach platform: %v", err)
+}
+log.Printf("platform has %d active attributes", len(attrs))
 
-    // 2. Check a specific attribute exists before using it
-    exists, err := client.AttributeExists(ctx, "https://opentdf.io/attr/department")
-    if err != nil {
-        log.Fatalf("service error: %v", err)
-    }
-    if !exists {
-        log.Fatalf("attribute missing — create it first")
-    }
+// 2. Check a specific attribute exists before using it
+exists, err := client.AttributeExists(ctx, "https://opentdf.io/attr/department")
+if err != nil {
+    log.Fatalf("service error: %v", err)
+}
+if !exists {
+    log.Fatalf("attribute missing — create it first")
+}
 
-    // 3. Validate the specific values before encrypting
-    required := []string{
-        "https://opentdf.io/attr/department/value/marketing",
-    }
-    if err := client.ValidateAttributes(ctx, required...); err != nil {
-        log.Fatalf("required attributes missing — create them first: %v", err)
-    }
+// 3. Validate the specific values before encrypting
+required := []string{
+    "https://opentdf.io/attr/department/value/marketing",
+}
+if err := client.ValidateAttributes(ctx, required...); err != nil {
+    log.Fatalf("required attributes missing: %v", err)
+}
 
-    // 4. Encrypt with confidence
-    _, err = client.CreateTDF(encryptedBuffer, dataReader,
-        sdk.WithDataAttributes(required...),
-        sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
-    )
-    if err != nil {
-        log.Fatalf("encryption failed: %v", err)
-    }
-    log.Println("data encrypted successfully")
+// 4. Encrypt with confidence
+_, err = client.CreateTDF(encryptedBuffer, dataReader,
+    sdk.WithDataAttributes(required...),
+    sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}),
+)
+if err != nil {
+    log.Fatalf("encryption failed: %v", err)
 }
+log.Println("data encrypted successfully")
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-try (SDK sdk = new SDKBuilder()
-        .platformEndpoint(platformEndpoint)
-        .clientSecret("opentdf", "secret")
-        .build()) {
-
-    // 1. See what's available on the platform
-    List<Attribute> attrs = sdk.listAttributes();
-    System.out.printf("platform has %d active attributes%n", attrs.size());
-
-    // 2. Check a specific attribute exists before using it
-    if (!sdk.attributeExists("https://opentdf.io/attr/department")) {
-        System.err.println("attribute missing — create it first");
-        return;
-    }
+// 1. See what's available on the platform
+List<Attribute> attrs = sdk.listAttributes();
+System.out.printf("platform has %d active attributes%n", attrs.size());
 
-    // 3. Validate the specific values before encrypting
-    List<String> required = List.of("https://opentdf.io/attr/department/value/marketing");
-    try {
-        sdk.validateAttributes(required);
-    } catch (SDK.AttributeNotFoundException e) {
-        System.err.println("required attributes missing — create them first: " + e.getMessage());
-        return;
-    }
+// 2. Check a specific attribute exists before using it
+if (!sdk.attributeExists("https://opentdf.io/attr/department")) {
+    System.err.println("attribute missing — create it first");
+    return;
+}
 
-    // 4. Encrypt with confidence
-    Config.TDFConfig config = Config.newTDFConfig(
-        Config.withDataAttributes(required),
-        Config.withKasInformation(new Config.KASInfo(platformEndpoint, null))
-    );
-    sdk.createTDF(inputStream, outputStream, config);
-    System.out.println("data encrypted successfully");
+// 3. Validate the specific values before encrypting
+List<String> required = List.of("https://opentdf.io/attr/department/value/marketing");
+try {
+    sdk.validateAttributes(required);
+} catch (SDK.AttributeNotFoundException e) {
+    System.err.println("required attributes missing: " + e.getMessage());
+    return;
 }
+
+// 4. Encrypt with confidence
+Config.TDFConfig config = Config.newTDFConfig(
+    Config.withDataAttributes(required),
+    Config.withKasInformation(new Config.KASInfo(platformEndpoint, null))
+);
+sdk.createTDF(inputStream, outputStream, config);
+System.out.println("data encrypted successfully");
 ```
 
 </TabItem>
 <TabItem value="js" label="JavaScript">
 
-```ts
-import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF, listAttributes, attributeExists, validateAttributes, AttributeNotFoundError } from '@opentdf/sdk';
-
-// Define auth once — reuse for standalone functions and the OpenTDF client
-const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
-    clientId: 'opentdf', clientSecret: 'secret',
-    oidcOrigin: oidcEndpoint,
-}))] };
+```typescript
+import { listAttributes, attributeExists, validateAttributes, AttributeNotFoundError, OpenTDF } from '@opentdf/sdk';
 
 // 1. See what's available on the platform
 const attrs = await listAttributes(platformUrl, auth);
@@ -543,7 +725,7 @@ try {
     await validateAttributes(platformUrl, auth, required);
 } catch (e) {
     if (e instanceof AttributeNotFoundError) {
-        console.error('required attributes missing — create them first:', e.message);
+        console.error('required attributes missing:', e.message);
         process.exit(1);
     }
     throw e;
diff --git a/docs/sdks/platform-client.mdx b/docs/sdks/platform-client.mdx
index cfeaf4f7..f6457af8 100644
--- a/docs/sdks/platform-client.mdx
+++ b/docs/sdks/platform-client.mdx
@@ -89,3 +89,108 @@ const platform = new PlatformClient({ ...auth, platformUrl: 'http://localhost:80
 
 </TabItem>
 </Tabs>
+
+## Response Objects
+
+All platform API calls return protobuf response objects. The way you access fields differs by language: **Go** uses generated getter methods prefixed with `Get`, **Java** uses standard getters, and **JavaScript** uses direct property access with optional chaining.
+
+**GetAttributeValue** — accessing a policy attribute value:
+
+```go
+resp, err := client.Attributes.GetAttributeValue(ctx, req)
+v := resp.GetValue()
+v.GetValue()    // "confidential"
+v.GetFqn()      // "https://example.com/attr/classification/value/confidential"
+v.GetId()       // "a1b2c3d4-..."
+```
+
+```java
+var resp = sdk.getServices().attributes()
+    .getAttributeValueBlocking(req, Collections.emptyMap()).execute();
+var v = resp.getValue();
+v.getValue();   // "confidential"
+v.getFqn();     // "https://example.com/attr/classification/value/confidential"
+v.getId();      // "a1b2c3d4-..."
+```
+
+```typescript
+const resp = await platform.v1.attributes.getAttributeValue({ fqn: '...' });
+resp.value?.value;   // "confidential"
+resp.value?.fqn;     // "https://example.com/attr/classification/value/confidential"
+resp.value?.id;      // "a1b2c3d4-..."
+```
+
+**ListAttributeValues** — iterating over a list response:
+
+```go
+resp, err := client.Attributes.ListAttributeValues(ctx, req)
+for _, v := range resp.GetValues() {
+    v.GetValue()    // "confidential"
+    v.GetFqn()      // "https://example.com/attr/classification/value/confidential"
+}
+```
+
+```java
+var resp = sdk.getServices().attributes()
+    .listAttributeValuesBlocking(req, Collections.emptyMap()).execute();
+for (var v : resp.getValuesList()) {
+    v.getValue();   // "confidential"
+    v.getFqn();     // "https://example.com/attr/classification/value/confidential"
+}
+```
+
+```typescript
+const resp = await platform.v1.attributes.listAttributeValues({ attributeId: '...' });
+for (const v of resp.values) {
+  v.value;   // "confidential"
+  v.fqn;     // "https://example.com/attr/classification/value/confidential"
+}
+```
+
+**GetDecision** — accessing an authorization decision:
+
+```go
+resp, err := client.AuthorizationV2.GetDecision(ctx, req)
+d := resp.GetDecision()
+d.GetDecision()              // DECISION_PERMIT or DECISION_DENY
+d.GetRequiredObligations()   // []string of obligation value FQNs
+```
+
+```java
+var resp = sdk.getServices().authorization().getDecision(req).get();
+var d = resp.getDecision();
+d.getDecision();                // DECISION_PERMIT or DECISION_DENY
+d.getRequiredObligationsList(); // List<String> of obligation value FQNs
+```
+
+```typescript
+const resp = await platform.v2.authorization.getDecision({ ... });
+resp.decision?.decision;              // Decision.PERMIT or Decision.DENY
+resp.decision?.requiredObligations;   // string[] of obligation value FQNs
+```
+
+**GetEntitlements** — accessing entity entitlements:
+
+```go
+resp, err := client.AuthorizationV2.GetEntitlements(ctx, req)
+for _, e := range resp.GetEntitlements() {
+    e.GetActionsPerAttributeValueFqn()  // map of FQN → permitted actions
+}
+```
+
+```java
+var resp = sdk.getServices().authorization().getEntitlements(req).get();
+for (var e : resp.getEntitlementsList()) {
+    e.getActionsPerAttributeValueFqnMap();  // Map<String, Actions>
+}
+```
+
+```typescript
+const resp = await platform.v2.authorization.getEntitlements({ ... });
+for (const e of resp.entitlements) {
+  e.actionsPerAttributeValueFqn;  // Record<string, Actions>
+}
+```
+
+This pattern applies to all platform objects. Each SDK page includes type reference tables listing the available fields.
+

From 7a92f1e2cef8a14ad8bc43ec28449769f28a7486 Mon Sep 17 00:00:00 2001
From: Mary Dickson <mary.dickson@virtru.com>
Date: Tue, 7 Apr 2026 10:30:58 -0700
Subject: [PATCH 2/6] chore(docs): rename "Putting It Together" to "Typical
 Workflow"

Consistent naming with the Authorization page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 docs/sdks/discovery.mdx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx
index 305833ed..2ad18788 100644
--- a/docs/sdks/discovery.mdx
+++ b/docs/sdks/discovery.mdx
@@ -627,7 +627,7 @@ Querying another entity's attributes requires your caller to have appropriate pl
 
 ---
 
-## Putting It Together
+## Typical Workflow
 
 A common pattern is to validate attributes at application startup and surface helpful errors before any encryption happens:
 

From 85f6008fd474044ada4733981184cb044eaf962b Mon Sep 17 00:00:00 2001
From: Mary Dickson <mary.dickson@virtru.com>
Date: Tue, 7 Apr 2026 10:49:31 -0700
Subject: [PATCH 3/6] fix: Discovery page review feedback
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Use language-neutral "list of strings" instead of Go-specific []string
- Fix platformClient → platform variable name in GetEntityAttributes signature
- Clarify that JS returns EntityEntitlements, not plain FQN strings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 docs/sdks/discovery.mdx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx
index 2ad18788..60208db0 100644
--- a/docs/sdks/discovery.mdx
+++ b/docs/sdks/discovery.mdx
@@ -379,7 +379,7 @@ await validateAttributes(platformUrl, auth, fqns)
 
 | Parameter | Type | Required | Description |
 |-----------|------|----------|-------------|
-| `fqns` | `[]string` | Yes | Attribute value FQNs to validate (up to 250 per call). |
+| `fqns` | `list of strings` | Yes | Attribute value FQNs to validate (up to 250 per call). |
 
 **Example**
 
@@ -490,7 +490,7 @@ sdk.getEntityAttributes(entity)
 <TabItem value="js" label="JavaScript">
 
 ```typescript
-await platformClient.v2.authorization.getEntitlements({ ... })
+await platform.v2.authorization.getEntitlements({ ... })
 ```
 
 The JS SDK does not expose a standalone `getEntityAttributes` function. Use `getEntitlements` via `PlatformClient` in a Node.js server environment.
@@ -619,7 +619,7 @@ Other supported entity types (placed inside the `entities` array):
 
 **Returns**
 
-A list of [attribute value](/sdks/policy#attribute-value-object) FQN strings that the entity is entitled to access.
+**Go/Java:** A list of [attribute value](/sdks/policy#attribute-value-object) FQN strings that the entity is entitled to access. **JavaScript:** An [EntityEntitlements](/sdks/authorization#entityentitlements) response from `getEntitlements`, containing a map of FQNs to permitted actions.
 
 :::warning Authorization required
 Querying another entity's attributes requires your caller to have appropriate platform permissions on the `GetEntitlements` endpoint. Without this, the call will return a `permission_denied` error.

From 1e00f953c44e648744c7c097e0e939475480c99f Mon Sep 17 00:00:00 2001
From: Mary Dickson <mary.dickson@virtru.com>
Date: Tue, 7 Apr 2026 10:58:28 -0700
Subject: [PATCH 4/6] chore(docs): wrap Response Objects examples in Tabs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

All four examples already had Go/Java/JS — now wrapped in Tabs
components so the language selector is visible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 docs/sdks/platform-client.mdx | 48 +++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/docs/sdks/platform-client.mdx b/docs/sdks/platform-client.mdx
index f6457af8..6fdffb13 100644
--- a/docs/sdks/platform-client.mdx
+++ b/docs/sdks/platform-client.mdx
@@ -96,6 +96,9 @@ All platform API calls return protobuf response objects. The way you access fiel
 
 **GetAttributeValue** — accessing a policy attribute value:
 
+<Tabs>
+<TabItem value="go" label="Go">
+
 ```go
 resp, err := client.Attributes.GetAttributeValue(ctx, req)
 v := resp.GetValue()
@@ -104,6 +107,9 @@ v.GetFqn()      // "https://example.com/attr/classification/value/confidential"
 v.GetId()       // "a1b2c3d4-..."
 ```
 
+</TabItem>
+<TabItem value="java" label="Java">
+
 ```java
 var resp = sdk.getServices().attributes()
     .getAttributeValueBlocking(req, Collections.emptyMap()).execute();
@@ -113,6 +119,9 @@ v.getFqn();     // "https://example.com/attr/classification/value/confidential"
 v.getId();      // "a1b2c3d4-..."
 ```
 
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
 ```typescript
 const resp = await platform.v1.attributes.getAttributeValue({ fqn: '...' });
 resp.value?.value;   // "confidential"
@@ -120,8 +129,14 @@ resp.value?.fqn;     // "https://example.com/attr/classification/value/confident
 resp.value?.id;      // "a1b2c3d4-..."
 ```
 
+</TabItem>
+</Tabs>
+
 **ListAttributeValues** — iterating over a list response:
 
+<Tabs>
+<TabItem value="go" label="Go">
+
 ```go
 resp, err := client.Attributes.ListAttributeValues(ctx, req)
 for _, v := range resp.GetValues() {
@@ -130,6 +145,9 @@ for _, v := range resp.GetValues() {
 }
 ```
 
+</TabItem>
+<TabItem value="java" label="Java">
+
 ```java
 var resp = sdk.getServices().attributes()
     .listAttributeValuesBlocking(req, Collections.emptyMap()).execute();
@@ -139,6 +157,9 @@ for (var v : resp.getValuesList()) {
 }
 ```
 
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
 ```typescript
 const resp = await platform.v1.attributes.listAttributeValues({ attributeId: '...' });
 for (const v of resp.values) {
@@ -147,8 +168,14 @@ for (const v of resp.values) {
 }
 ```
 
+</TabItem>
+</Tabs>
+
 **GetDecision** — accessing an authorization decision:
 
+<Tabs>
+<TabItem value="go" label="Go">
+
 ```go
 resp, err := client.AuthorizationV2.GetDecision(ctx, req)
 d := resp.GetDecision()
@@ -156,6 +183,9 @@ d.GetDecision()              // DECISION_PERMIT or DECISION_DENY
 d.GetRequiredObligations()   // []string of obligation value FQNs
 ```
 
+</TabItem>
+<TabItem value="java" label="Java">
+
 ```java
 var resp = sdk.getServices().authorization().getDecision(req).get();
 var d = resp.getDecision();
@@ -163,14 +193,23 @@ d.getDecision();                // DECISION_PERMIT or DECISION_DENY
 d.getRequiredObligationsList(); // List<String> of obligation value FQNs
 ```
 
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
 ```typescript
 const resp = await platform.v2.authorization.getDecision({ ... });
 resp.decision?.decision;              // Decision.PERMIT or Decision.DENY
 resp.decision?.requiredObligations;   // string[] of obligation value FQNs
 ```
 
+</TabItem>
+</Tabs>
+
 **GetEntitlements** — accessing entity entitlements:
 
+<Tabs>
+<TabItem value="go" label="Go">
+
 ```go
 resp, err := client.AuthorizationV2.GetEntitlements(ctx, req)
 for _, e := range resp.GetEntitlements() {
@@ -178,6 +217,9 @@ for _, e := range resp.GetEntitlements() {
 }
 ```
 
+</TabItem>
+<TabItem value="java" label="Java">
+
 ```java
 var resp = sdk.getServices().authorization().getEntitlements(req).get();
 for (var e : resp.getEntitlementsList()) {
@@ -185,6 +227,9 @@ for (var e : resp.getEntitlementsList()) {
 }
 ```
 
+</TabItem>
+<TabItem value="js" label="JavaScript">
+
 ```typescript
 const resp = await platform.v2.authorization.getEntitlements({ ... });
 for (const e of resp.entitlements) {
@@ -192,5 +237,8 @@ for (const e of resp.entitlements) {
 }
 ```
 
+</TabItem>
+</Tabs>
+
 This pattern applies to all platform objects. Each SDK page includes type reference tables listing the available fields.
 

From e8da59833dbcc9308644510972ff9cc92f41cb52 Mon Sep 17 00:00:00 2001
From: Mary Dickson <mary.dickson@virtru.com>
Date: Tue, 7 Apr 2026 11:00:21 -0700
Subject: [PATCH 5/6] chore(docs): rename Architecture to SDK Overview
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Better reflects the page content — SDK structure, client setup, and
response patterns rather than system architecture.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 docs/sdks/platform-client.mdx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/sdks/platform-client.mdx b/docs/sdks/platform-client.mdx
index 6fdffb13..6995b5cc 100644
--- a/docs/sdks/platform-client.mdx
+++ b/docs/sdks/platform-client.mdx
@@ -1,13 +1,13 @@
 ---
 sidebar_position: 1
-title: Architecture
+title: Overview
 ---
 
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 import JsAuthNote from '../../code_samples/js_auth_note.mdx'
 
-# Architecture
+# Overview
 
 Some SDK functionality — including policy management and authorization decisions — is provided through a **platform service client** rather than through the core SDK. This page explains the difference and when you'll use each.
 

From 4ee7581fcd93d7ad2c76b0ec4e259c56095f3de3 Mon Sep 17 00:00:00 2001
From: Mary Dickson <mary.dickson@virtru.com>
Date: Wed, 8 Apr 2026 07:44:14 -0700
Subject: [PATCH 6/6] fix: clarify JS uses different parameter shape for
 GetEntityAttributes

JS uses getEntitlements with EntityIdentifier, not the same entity
parameter as Go/Java. Add a note linking to the EntityIdentifier docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 docs/sdks/discovery.mdx | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx
index 60208db0..04ba3ba3 100644
--- a/docs/sdks/discovery.mdx
+++ b/docs/sdks/discovery.mdx
@@ -500,10 +500,14 @@ The JS SDK does not expose a standalone `getEntityAttributes` function. Use `get
 
 **Parameters**
 
+**Go/Java:**
+
 | Parameter | Type | Required | Description |
 |-----------|------|----------|-------------|
 | `entity` | `Entity` | Yes | The entity to look up. Supports email, username, client ID, and UUID. |
 
+**JavaScript** uses `getEntitlements` instead (see [EntityIdentifier](/sdks/authorization#entityidentifier) for the request shape).
+
 **Example**
 
 <Tabs groupId="sdk">
