@predicatesystems/temporal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/dist/index.d.mts +127 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +87 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Predicate Systems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# @predicatesystems/temporal
|
|
2
|
+
|
|
3
|
+
Temporal.io Worker Interceptor for Predicate Authority Zero-Trust authorization.
|
|
4
|
+
|
|
5
|
+
This package provides a pre-execution security gate for all Temporal Activities, enforcing cryptographic authorization mandates before any activity code runs.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
This package requires the **Predicate Authority Sidecar** daemon to be running. The sidecar is a lightweight Rust binary that handles policy evaluation and mandate signing.
|
|
10
|
+
|
|
11
|
+
| Resource | Link |
|
|
12
|
+
|----------|------|
|
|
13
|
+
| Sidecar Repository | [github.com/PredicateSystems/predicate-authority-sidecar](https://github.com/PredicateSystems/predicate-authority-sidecar) |
|
|
14
|
+
| Download Binaries | [Latest Releases](https://github.com/PredicateSystems/predicate-authority-sidecar/releases) |
|
|
15
|
+
| License | MIT / Apache 2.0 |
|
|
16
|
+
|
|
17
|
+
### Quick Sidecar Setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Download the latest release for your platform
|
|
21
|
+
# Linux x64, macOS x64/ARM64, Windows x64 available
|
|
22
|
+
|
|
23
|
+
# Extract and run
|
|
24
|
+
tar -xzf predicate-authorityd-*.tar.gz
|
|
25
|
+
chmod +x predicate-authorityd
|
|
26
|
+
|
|
27
|
+
# Start with a policy file
|
|
28
|
+
./predicate-authorityd --port 8787 --policy-file policy.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @predicatesystems/temporal
|
|
35
|
+
# or
|
|
36
|
+
yarn add @predicatesystems/temporal
|
|
37
|
+
# or
|
|
38
|
+
pnpm add @predicatesystems/temporal
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { Worker } from "@temporalio/worker";
|
|
45
|
+
import { AuthorityClient } from "@predicatesystems/authority";
|
|
46
|
+
import { createPredicateInterceptors } from "@predicatesystems/temporal";
|
|
47
|
+
|
|
48
|
+
// Initialize the Predicate Authority client
|
|
49
|
+
const authorityClient = new AuthorityClient({
|
|
50
|
+
baseUrl: "http://127.0.0.1:8787",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Create interceptors
|
|
54
|
+
const interceptors = createPredicateInterceptors({
|
|
55
|
+
authorityClient,
|
|
56
|
+
principal: "temporal-worker",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Create worker with the interceptors
|
|
60
|
+
const worker = await Worker.create({
|
|
61
|
+
connection,
|
|
62
|
+
namespace: "default",
|
|
63
|
+
taskQueue: "my-task-queue",
|
|
64
|
+
workflowsPath: require.resolve("./workflows"),
|
|
65
|
+
activities,
|
|
66
|
+
interceptors,
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## How It Works
|
|
71
|
+
|
|
72
|
+
The interceptor sits in the Temporal activity execution pipeline:
|
|
73
|
+
|
|
74
|
+
1. Temporal dispatches an activity to your worker
|
|
75
|
+
2. **Before** the activity code runs, the interceptor extracts:
|
|
76
|
+
- Activity type (action)
|
|
77
|
+
- Activity arguments (context)
|
|
78
|
+
3. The interceptor calls `AuthorityClient.authorize()` to request a mandate
|
|
79
|
+
4. If **denied**: throws `PredicateAuthorizationError` - activity never executes
|
|
80
|
+
5. If **approved**: activity proceeds normally
|
|
81
|
+
|
|
82
|
+
This ensures that no untrusted code or payload reaches your OS until it has been cryptographically authorized.
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
### Interceptor Options
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { createPredicateInterceptors } from "@predicatesystems/temporal";
|
|
90
|
+
|
|
91
|
+
const interceptors = createPredicateInterceptors({
|
|
92
|
+
// Required: The Predicate Authority client
|
|
93
|
+
authorityClient: new AuthorityClient({ baseUrl: "http://127.0.0.1:8787" }),
|
|
94
|
+
|
|
95
|
+
// Optional: Principal ID (default: "temporal-worker")
|
|
96
|
+
principal: "my-worker",
|
|
97
|
+
|
|
98
|
+
// Optional: Tenant ID for multi-tenant setups
|
|
99
|
+
tenantId: "tenant-123",
|
|
100
|
+
|
|
101
|
+
// Optional: Session ID for request correlation
|
|
102
|
+
sessionId: "session-456",
|
|
103
|
+
|
|
104
|
+
// Optional: Custom resource identifier (default: "temporal:activity")
|
|
105
|
+
resource: "temporal:my-queue",
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Policy File
|
|
110
|
+
|
|
111
|
+
Create a policy file for the Predicate Authority daemon:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"rules": [
|
|
116
|
+
{
|
|
117
|
+
"name": "allow-safe-activities",
|
|
118
|
+
"effect": "allow",
|
|
119
|
+
"principals": ["temporal-worker"],
|
|
120
|
+
"actions": ["processOrder", "sendNotification"],
|
|
121
|
+
"resources": ["*"]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "deny-dangerous-activities",
|
|
125
|
+
"effect": "deny",
|
|
126
|
+
"principals": ["*"],
|
|
127
|
+
"actions": ["delete*", "admin*"],
|
|
128
|
+
"resources": ["*"]
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## API Reference
|
|
135
|
+
|
|
136
|
+
### `createPredicateInterceptors(options)`
|
|
137
|
+
|
|
138
|
+
Creates the interceptor configuration object for `Worker.create()`.
|
|
139
|
+
|
|
140
|
+
**Parameters:**
|
|
141
|
+
|
|
142
|
+
- `options.authorityClient` (required): `AuthorityClient` - The Predicate Authority client instance
|
|
143
|
+
- `options.principal` (optional): `string` - Principal ID (default: `"temporal-worker"`)
|
|
144
|
+
- `options.tenantId` (optional): `string` - Tenant ID for multi-tenant setups
|
|
145
|
+
- `options.sessionId` (optional): `string` - Session ID for request correlation
|
|
146
|
+
- `options.resource` (optional): `string` - Resource identifier (default: `"temporal:activity"`)
|
|
147
|
+
|
|
148
|
+
**Returns:** `WorkerInterceptors` - The interceptor configuration for Temporal Worker
|
|
149
|
+
|
|
150
|
+
### `PredicateActivityInterceptor`
|
|
151
|
+
|
|
152
|
+
The activity interceptor class. Usually you don't need to instantiate this directly - use `createPredicateInterceptors()` instead.
|
|
153
|
+
|
|
154
|
+
### `PredicateAuthorizationError`
|
|
155
|
+
|
|
156
|
+
Custom error thrown when authorization is denied.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { PredicateAuthorizationError } from "@predicatesystems/temporal";
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await workflow.executeActivity("dangerousActivity", args);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error instanceof PredicateAuthorizationError) {
|
|
165
|
+
console.log(`Denied: ${error.reason}`);
|
|
166
|
+
console.log(`Violated rule: ${error.violatedRule}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Error Handling
|
|
172
|
+
|
|
173
|
+
When authorization is denied, the interceptor throws a `PredicateAuthorizationError`:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { ApplicationFailure } from "@temporalio/workflow";
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
await workflow.executeActivity("sensitiveActivity", args, {
|
|
180
|
+
startToCloseTimeout: "30s",
|
|
181
|
+
});
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (error instanceof ApplicationFailure) {
|
|
184
|
+
// Check if it's a Predicate denial
|
|
185
|
+
if (error.message.includes("Predicate Zero-Trust Denial")) {
|
|
186
|
+
// Handle authorization denial
|
|
187
|
+
console.log("Activity was blocked by security policy");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Install dependencies
|
|
197
|
+
npm install
|
|
198
|
+
|
|
199
|
+
# Build
|
|
200
|
+
npm run build
|
|
201
|
+
|
|
202
|
+
# Run tests
|
|
203
|
+
npm test
|
|
204
|
+
|
|
205
|
+
# Type checking
|
|
206
|
+
npm run typecheck
|
|
207
|
+
|
|
208
|
+
# Linting
|
|
209
|
+
npm run lint
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Context } from '@temporalio/activity';
|
|
2
|
+
import { ActivityInboundCallsInterceptor, ActivityExecuteInput, Next, WorkerInterceptors } from '@temporalio/worker';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Predicate Authority interceptor for Temporal.io activities.
|
|
6
|
+
*
|
|
7
|
+
* This module provides a pre-execution security gate for all Temporal Activities,
|
|
8
|
+
* enforcing cryptographic authorization mandates before any activity code runs.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Interface for the Predicate Authority client.
|
|
13
|
+
* This matches the AuthorityClient from @predicatesystems/authority.
|
|
14
|
+
*/
|
|
15
|
+
interface PredicateAuthorityClient {
|
|
16
|
+
authorize(request: PredicateAuthorizeRequest): Promise<PredicateAuthorizationResponse>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Authorization request sent to the Predicate Authority sidecar.
|
|
20
|
+
*/
|
|
21
|
+
interface PredicateAuthorizeRequest {
|
|
22
|
+
principal: string;
|
|
23
|
+
action: string;
|
|
24
|
+
resource: string;
|
|
25
|
+
intent_hash?: string;
|
|
26
|
+
context?: Record<string, unknown>;
|
|
27
|
+
labels?: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Authorization response from the Predicate Authority sidecar.
|
|
31
|
+
*/
|
|
32
|
+
interface PredicateAuthorizationResponse {
|
|
33
|
+
allowed: boolean;
|
|
34
|
+
reason: string;
|
|
35
|
+
mandate_id: string | null;
|
|
36
|
+
violated_rule: string | null;
|
|
37
|
+
missing_labels: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for creating Predicate interceptors.
|
|
41
|
+
*/
|
|
42
|
+
interface PredicateInterceptorOptions {
|
|
43
|
+
/** The Predicate Authority client for authorization */
|
|
44
|
+
authorityClient: PredicateAuthorityClient;
|
|
45
|
+
/** Principal ID used for authorization requests (default: "temporal-worker") */
|
|
46
|
+
principal?: string;
|
|
47
|
+
/** Optional tenant ID for multi-tenant setups */
|
|
48
|
+
tenantId?: string;
|
|
49
|
+
/** Optional session ID for request correlation */
|
|
50
|
+
sessionId?: string;
|
|
51
|
+
/** Optional resource identifier (default: "temporal:activity") */
|
|
52
|
+
resource?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Activity interceptor that enforces Predicate Authority authorization.
|
|
56
|
+
*
|
|
57
|
+
* This interceptor sits in the Temporal activity execution pipeline and ensures
|
|
58
|
+
* that every activity is authorized before execution. If authorization is denied,
|
|
59
|
+
* a PredicateAuthorizationError is thrown and the activity never executes.
|
|
60
|
+
*/
|
|
61
|
+
declare class PredicateActivityInterceptor implements ActivityInboundCallsInterceptor {
|
|
62
|
+
private readonly authorityClient;
|
|
63
|
+
private readonly principal;
|
|
64
|
+
private readonly tenantId;
|
|
65
|
+
private readonly sessionId;
|
|
66
|
+
private readonly resource;
|
|
67
|
+
private readonly activityType;
|
|
68
|
+
constructor(ctx: Context, options: PredicateInterceptorOptions);
|
|
69
|
+
/**
|
|
70
|
+
* Execute activity with Predicate Authority authorization check.
|
|
71
|
+
*
|
|
72
|
+
* This method intercepts the activity execution, extracts the activity type
|
|
73
|
+
* and arguments, and requests authorization from Predicate Authority.
|
|
74
|
+
* If denied, throws PredicateAuthorizationError. If approved, proceeds with execution.
|
|
75
|
+
*/
|
|
76
|
+
execute(input: ActivityExecuteInput, next: Next<ActivityInboundCallsInterceptor, "execute">): Promise<unknown>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Creates Temporal worker interceptors that enforce Predicate Authority authorization.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import { Worker } from "@temporalio/worker";
|
|
84
|
+
* import { AuthorityClient } from "@predicatesystems/authority";
|
|
85
|
+
* import { createPredicateInterceptors } from "@predicatesystems/temporal";
|
|
86
|
+
*
|
|
87
|
+
* const authorityClient = new AuthorityClient({
|
|
88
|
+
* baseUrl: "http://127.0.0.1:8787",
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* const interceptors = createPredicateInterceptors({
|
|
92
|
+
* authorityClient,
|
|
93
|
+
* principal: "temporal-worker",
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* const worker = await Worker.create({
|
|
97
|
+
* connection,
|
|
98
|
+
* taskQueue: "my-task-queue",
|
|
99
|
+
* workflowsPath: require.resolve("./workflows"),
|
|
100
|
+
* activities,
|
|
101
|
+
* interceptors,
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
declare function createPredicateInterceptors(options: PredicateInterceptorOptions): WorkerInterceptors;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Error thrown when Predicate Authority denies an activity execution.
|
|
109
|
+
*/
|
|
110
|
+
declare class PredicateAuthorizationError extends Error {
|
|
111
|
+
/** The authorization reason code */
|
|
112
|
+
readonly reason: string;
|
|
113
|
+
/** The policy rule that caused the denial, if any */
|
|
114
|
+
readonly violatedRule: string | undefined;
|
|
115
|
+
/** Labels that were required but missing */
|
|
116
|
+
readonly missingLabels: readonly string[];
|
|
117
|
+
/** The activity type that was denied */
|
|
118
|
+
readonly activityType: string;
|
|
119
|
+
constructor(options: {
|
|
120
|
+
activityType: string;
|
|
121
|
+
reason: string;
|
|
122
|
+
violatedRule?: string | undefined;
|
|
123
|
+
missingLabels?: readonly string[];
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { PredicateActivityInterceptor, PredicateAuthorizationError, type PredicateInterceptorOptions, createPredicateInterceptors };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Context } from '@temporalio/activity';
|
|
2
|
+
import { ActivityInboundCallsInterceptor, ActivityExecuteInput, Next, WorkerInterceptors } from '@temporalio/worker';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Predicate Authority interceptor for Temporal.io activities.
|
|
6
|
+
*
|
|
7
|
+
* This module provides a pre-execution security gate for all Temporal Activities,
|
|
8
|
+
* enforcing cryptographic authorization mandates before any activity code runs.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Interface for the Predicate Authority client.
|
|
13
|
+
* This matches the AuthorityClient from @predicatesystems/authority.
|
|
14
|
+
*/
|
|
15
|
+
interface PredicateAuthorityClient {
|
|
16
|
+
authorize(request: PredicateAuthorizeRequest): Promise<PredicateAuthorizationResponse>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Authorization request sent to the Predicate Authority sidecar.
|
|
20
|
+
*/
|
|
21
|
+
interface PredicateAuthorizeRequest {
|
|
22
|
+
principal: string;
|
|
23
|
+
action: string;
|
|
24
|
+
resource: string;
|
|
25
|
+
intent_hash?: string;
|
|
26
|
+
context?: Record<string, unknown>;
|
|
27
|
+
labels?: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Authorization response from the Predicate Authority sidecar.
|
|
31
|
+
*/
|
|
32
|
+
interface PredicateAuthorizationResponse {
|
|
33
|
+
allowed: boolean;
|
|
34
|
+
reason: string;
|
|
35
|
+
mandate_id: string | null;
|
|
36
|
+
violated_rule: string | null;
|
|
37
|
+
missing_labels: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for creating Predicate interceptors.
|
|
41
|
+
*/
|
|
42
|
+
interface PredicateInterceptorOptions {
|
|
43
|
+
/** The Predicate Authority client for authorization */
|
|
44
|
+
authorityClient: PredicateAuthorityClient;
|
|
45
|
+
/** Principal ID used for authorization requests (default: "temporal-worker") */
|
|
46
|
+
principal?: string;
|
|
47
|
+
/** Optional tenant ID for multi-tenant setups */
|
|
48
|
+
tenantId?: string;
|
|
49
|
+
/** Optional session ID for request correlation */
|
|
50
|
+
sessionId?: string;
|
|
51
|
+
/** Optional resource identifier (default: "temporal:activity") */
|
|
52
|
+
resource?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Activity interceptor that enforces Predicate Authority authorization.
|
|
56
|
+
*
|
|
57
|
+
* This interceptor sits in the Temporal activity execution pipeline and ensures
|
|
58
|
+
* that every activity is authorized before execution. If authorization is denied,
|
|
59
|
+
* a PredicateAuthorizationError is thrown and the activity never executes.
|
|
60
|
+
*/
|
|
61
|
+
declare class PredicateActivityInterceptor implements ActivityInboundCallsInterceptor {
|
|
62
|
+
private readonly authorityClient;
|
|
63
|
+
private readonly principal;
|
|
64
|
+
private readonly tenantId;
|
|
65
|
+
private readonly sessionId;
|
|
66
|
+
private readonly resource;
|
|
67
|
+
private readonly activityType;
|
|
68
|
+
constructor(ctx: Context, options: PredicateInterceptorOptions);
|
|
69
|
+
/**
|
|
70
|
+
* Execute activity with Predicate Authority authorization check.
|
|
71
|
+
*
|
|
72
|
+
* This method intercepts the activity execution, extracts the activity type
|
|
73
|
+
* and arguments, and requests authorization from Predicate Authority.
|
|
74
|
+
* If denied, throws PredicateAuthorizationError. If approved, proceeds with execution.
|
|
75
|
+
*/
|
|
76
|
+
execute(input: ActivityExecuteInput, next: Next<ActivityInboundCallsInterceptor, "execute">): Promise<unknown>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Creates Temporal worker interceptors that enforce Predicate Authority authorization.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import { Worker } from "@temporalio/worker";
|
|
84
|
+
* import { AuthorityClient } from "@predicatesystems/authority";
|
|
85
|
+
* import { createPredicateInterceptors } from "@predicatesystems/temporal";
|
|
86
|
+
*
|
|
87
|
+
* const authorityClient = new AuthorityClient({
|
|
88
|
+
* baseUrl: "http://127.0.0.1:8787",
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* const interceptors = createPredicateInterceptors({
|
|
92
|
+
* authorityClient,
|
|
93
|
+
* principal: "temporal-worker",
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* const worker = await Worker.create({
|
|
97
|
+
* connection,
|
|
98
|
+
* taskQueue: "my-task-queue",
|
|
99
|
+
* workflowsPath: require.resolve("./workflows"),
|
|
100
|
+
* activities,
|
|
101
|
+
* interceptors,
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
declare function createPredicateInterceptors(options: PredicateInterceptorOptions): WorkerInterceptors;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Error thrown when Predicate Authority denies an activity execution.
|
|
109
|
+
*/
|
|
110
|
+
declare class PredicateAuthorizationError extends Error {
|
|
111
|
+
/** The authorization reason code */
|
|
112
|
+
readonly reason: string;
|
|
113
|
+
/** The policy rule that caused the denial, if any */
|
|
114
|
+
readonly violatedRule: string | undefined;
|
|
115
|
+
/** Labels that were required but missing */
|
|
116
|
+
readonly missingLabels: readonly string[];
|
|
117
|
+
/** The activity type that was denied */
|
|
118
|
+
readonly activityType: string;
|
|
119
|
+
constructor(options: {
|
|
120
|
+
activityType: string;
|
|
121
|
+
reason: string;
|
|
122
|
+
violatedRule?: string | undefined;
|
|
123
|
+
missingLabels?: readonly string[];
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { PredicateActivityInterceptor, PredicateAuthorizationError, type PredicateInterceptorOptions, createPredicateInterceptors };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// src/interceptor.ts
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var PredicateAuthorizationError = class extends Error {
|
|
9
|
+
/** The authorization reason code */
|
|
10
|
+
reason;
|
|
11
|
+
/** The policy rule that caused the denial, if any */
|
|
12
|
+
violatedRule;
|
|
13
|
+
/** Labels that were required but missing */
|
|
14
|
+
missingLabels;
|
|
15
|
+
/** The activity type that was denied */
|
|
16
|
+
activityType;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
const message = `Predicate Zero-Trust Denial: Activity '${options.activityType}' not authorized. Reason: ${options.reason}${options.violatedRule ? `, violated rule: ${options.violatedRule}` : ""}`;
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "PredicateAuthorizationError";
|
|
21
|
+
this.reason = options.reason;
|
|
22
|
+
this.violatedRule = options.violatedRule;
|
|
23
|
+
this.missingLabels = options.missingLabels ?? [];
|
|
24
|
+
this.activityType = options.activityType;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/interceptor.ts
|
|
29
|
+
var PredicateActivityInterceptor = class {
|
|
30
|
+
authorityClient;
|
|
31
|
+
principal;
|
|
32
|
+
tenantId;
|
|
33
|
+
sessionId;
|
|
34
|
+
resource;
|
|
35
|
+
activityType;
|
|
36
|
+
constructor(ctx, options) {
|
|
37
|
+
this.authorityClient = options.authorityClient;
|
|
38
|
+
this.principal = options.principal ?? "temporal-worker";
|
|
39
|
+
this.tenantId = options.tenantId;
|
|
40
|
+
this.sessionId = options.sessionId;
|
|
41
|
+
this.resource = options.resource ?? "temporal:activity";
|
|
42
|
+
this.activityType = ctx.info.activityType;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Execute activity with Predicate Authority authorization check.
|
|
46
|
+
*
|
|
47
|
+
* This method intercepts the activity execution, extracts the activity type
|
|
48
|
+
* and arguments, and requests authorization from Predicate Authority.
|
|
49
|
+
* If denied, throws PredicateAuthorizationError. If approved, proceeds with execution.
|
|
50
|
+
*/
|
|
51
|
+
async execute(input, next) {
|
|
52
|
+
const activityArgs = input.args;
|
|
53
|
+
const argsJson = JSON.stringify(activityArgs);
|
|
54
|
+
const argsHash = crypto.createHash("sha256").update(argsJson).digest("hex");
|
|
55
|
+
const request = {
|
|
56
|
+
principal: this.principal,
|
|
57
|
+
action: this.activityType,
|
|
58
|
+
resource: this.resource,
|
|
59
|
+
intent_hash: `ih_execute_${this.activityType.toLowerCase()}`,
|
|
60
|
+
context: {
|
|
61
|
+
state_hash: argsHash,
|
|
62
|
+
tenant_id: this.tenantId,
|
|
63
|
+
session_id: this.sessionId
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const decision = await this.authorityClient.authorize(request);
|
|
67
|
+
if (!decision.allowed) {
|
|
68
|
+
throw new PredicateAuthorizationError({
|
|
69
|
+
activityType: this.activityType,
|
|
70
|
+
reason: decision.reason,
|
|
71
|
+
violatedRule: decision.violated_rule ?? void 0,
|
|
72
|
+
missingLabels: decision.missing_labels ?? []
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return next(input);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function createPredicateInterceptors(options) {
|
|
79
|
+
const activityInterceptorFactory = (ctx) => ({
|
|
80
|
+
inbound: new PredicateActivityInterceptor(ctx, options)
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
activity: [activityInterceptorFactory]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
exports.PredicateActivityInterceptor = PredicateActivityInterceptor;
|
|
88
|
+
exports.PredicateAuthorizationError = PredicateAuthorizationError;
|
|
89
|
+
exports.createPredicateInterceptors = createPredicateInterceptors;
|
|
90
|
+
//# sourceMappingURL=index.js.map
|
|
91
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/interceptor.ts"],"names":["createHash"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error thrown when Predicate Authority denies an activity execution.\n */\nexport class PredicateAuthorizationError extends Error {\n /** The authorization reason code */\n public readonly reason: string;\n\n /** The policy rule that caused the denial, if any */\n public readonly violatedRule: string | undefined;\n\n /** Labels that were required but missing */\n public readonly missingLabels: readonly string[];\n\n /** The activity type that was denied */\n public readonly activityType: string;\n\n constructor(options: {\n activityType: string;\n reason: string;\n violatedRule?: string | undefined;\n missingLabels?: readonly string[];\n }) {\n const message = `Predicate Zero-Trust Denial: Activity '${options.activityType}' not authorized. Reason: ${options.reason}${\n options.violatedRule ? `, violated rule: ${options.violatedRule}` : \"\"\n }`;\n super(message);\n this.name = \"PredicateAuthorizationError\";\n this.reason = options.reason;\n this.violatedRule = options.violatedRule;\n this.missingLabels = options.missingLabels ?? [];\n this.activityType = options.activityType;\n }\n}\n","/**\n * Predicate Authority interceptor for Temporal.io activities.\n *\n * This module provides a pre-execution security gate for all Temporal Activities,\n * enforcing cryptographic authorization mandates before any activity code runs.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { Context } from \"@temporalio/activity\";\nimport type {\n ActivityExecuteInput,\n ActivityInboundCallsInterceptor,\n ActivityInterceptorsFactory,\n Next,\n WorkerInterceptors,\n} from \"@temporalio/worker\";\nimport { PredicateAuthorizationError } from \"./errors.js\";\n\n/**\n * Interface for the Predicate Authority client.\n * This matches the AuthorityClient from @predicatesystems/authority.\n */\nexport interface PredicateAuthorityClient {\n authorize(request: PredicateAuthorizeRequest): Promise<PredicateAuthorizationResponse>;\n}\n\n/**\n * Authorization request sent to the Predicate Authority sidecar.\n */\nexport interface PredicateAuthorizeRequest {\n principal: string;\n action: string;\n resource: string;\n intent_hash?: string;\n context?: Record<string, unknown>;\n labels?: string[];\n}\n\n/**\n * Authorization response from the Predicate Authority sidecar.\n */\nexport interface PredicateAuthorizationResponse {\n allowed: boolean;\n reason: string;\n mandate_id: string | null;\n violated_rule: string | null;\n missing_labels: string[];\n}\n\n/**\n * Options for creating Predicate interceptors.\n */\nexport interface PredicateInterceptorOptions {\n /** The Predicate Authority client for authorization */\n authorityClient: PredicateAuthorityClient;\n\n /** Principal ID used for authorization requests (default: \"temporal-worker\") */\n principal?: string;\n\n /** Optional tenant ID for multi-tenant setups */\n tenantId?: string;\n\n /** Optional session ID for request correlation */\n sessionId?: string;\n\n /** Optional resource identifier (default: \"temporal:activity\") */\n resource?: string;\n}\n\n/**\n * Activity interceptor that enforces Predicate Authority authorization.\n *\n * This interceptor sits in the Temporal activity execution pipeline and ensures\n * that every activity is authorized before execution. If authorization is denied,\n * a PredicateAuthorizationError is thrown and the activity never executes.\n */\nexport class PredicateActivityInterceptor implements ActivityInboundCallsInterceptor {\n private readonly authorityClient: PredicateAuthorityClient;\n private readonly principal: string;\n private readonly tenantId: string | undefined;\n private readonly sessionId: string | undefined;\n private readonly resource: string;\n private readonly activityType: string;\n\n constructor(ctx: Context, options: PredicateInterceptorOptions) {\n this.authorityClient = options.authorityClient;\n this.principal = options.principal ?? \"temporal-worker\";\n this.tenantId = options.tenantId;\n this.sessionId = options.sessionId;\n this.resource = options.resource ?? \"temporal:activity\";\n this.activityType = ctx.info.activityType;\n }\n\n /**\n * Execute activity with Predicate Authority authorization check.\n *\n * This method intercepts the activity execution, extracts the activity type\n * and arguments, and requests authorization from Predicate Authority.\n * If denied, throws PredicateAuthorizationError. If approved, proceeds with execution.\n */\n async execute(\n input: ActivityExecuteInput,\n next: Next<ActivityInboundCallsInterceptor, \"execute\">\n ): Promise<unknown> {\n const activityArgs = input.args;\n\n // Hash the arguments for state evidence\n const argsJson = JSON.stringify(activityArgs);\n const argsHash = createHash(\"sha256\").update(argsJson).digest(\"hex\");\n\n const request: PredicateAuthorizeRequest = {\n principal: this.principal,\n action: this.activityType,\n resource: this.resource,\n intent_hash: `ih_execute_${this.activityType.toLowerCase()}`,\n context: {\n state_hash: argsHash,\n tenant_id: this.tenantId,\n session_id: this.sessionId,\n },\n };\n\n const decision = await this.authorityClient.authorize(request);\n\n if (!decision.allowed) {\n throw new PredicateAuthorizationError({\n activityType: this.activityType,\n reason: decision.reason,\n violatedRule: decision.violated_rule ?? undefined,\n missingLabels: decision.missing_labels ?? [],\n });\n }\n\n return next(input);\n }\n}\n\n/**\n * Creates Temporal worker interceptors that enforce Predicate Authority authorization.\n *\n * @example\n * ```typescript\n * import { Worker } from \"@temporalio/worker\";\n * import { AuthorityClient } from \"@predicatesystems/authority\";\n * import { createPredicateInterceptors } from \"@predicatesystems/temporal\";\n *\n * const authorityClient = new AuthorityClient({\n * baseUrl: \"http://127.0.0.1:8787\",\n * });\n *\n * const interceptors = createPredicateInterceptors({\n * authorityClient,\n * principal: \"temporal-worker\",\n * });\n *\n * const worker = await Worker.create({\n * connection,\n * taskQueue: \"my-task-queue\",\n * workflowsPath: require.resolve(\"./workflows\"),\n * activities,\n * interceptors,\n * });\n * ```\n */\nexport function createPredicateInterceptors(\n options: PredicateInterceptorOptions\n): WorkerInterceptors {\n const activityInterceptorFactory: ActivityInterceptorsFactory = (ctx) => ({\n inbound: new PredicateActivityInterceptor(ctx, options),\n });\n\n return {\n activity: [activityInterceptorFactory],\n };\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
|
|
3
|
+
// src/interceptor.ts
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var PredicateAuthorizationError = class extends Error {
|
|
7
|
+
/** The authorization reason code */
|
|
8
|
+
reason;
|
|
9
|
+
/** The policy rule that caused the denial, if any */
|
|
10
|
+
violatedRule;
|
|
11
|
+
/** Labels that were required but missing */
|
|
12
|
+
missingLabels;
|
|
13
|
+
/** The activity type that was denied */
|
|
14
|
+
activityType;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
const message = `Predicate Zero-Trust Denial: Activity '${options.activityType}' not authorized. Reason: ${options.reason}${options.violatedRule ? `, violated rule: ${options.violatedRule}` : ""}`;
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "PredicateAuthorizationError";
|
|
19
|
+
this.reason = options.reason;
|
|
20
|
+
this.violatedRule = options.violatedRule;
|
|
21
|
+
this.missingLabels = options.missingLabels ?? [];
|
|
22
|
+
this.activityType = options.activityType;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/interceptor.ts
|
|
27
|
+
var PredicateActivityInterceptor = class {
|
|
28
|
+
authorityClient;
|
|
29
|
+
principal;
|
|
30
|
+
tenantId;
|
|
31
|
+
sessionId;
|
|
32
|
+
resource;
|
|
33
|
+
activityType;
|
|
34
|
+
constructor(ctx, options) {
|
|
35
|
+
this.authorityClient = options.authorityClient;
|
|
36
|
+
this.principal = options.principal ?? "temporal-worker";
|
|
37
|
+
this.tenantId = options.tenantId;
|
|
38
|
+
this.sessionId = options.sessionId;
|
|
39
|
+
this.resource = options.resource ?? "temporal:activity";
|
|
40
|
+
this.activityType = ctx.info.activityType;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Execute activity with Predicate Authority authorization check.
|
|
44
|
+
*
|
|
45
|
+
* This method intercepts the activity execution, extracts the activity type
|
|
46
|
+
* and arguments, and requests authorization from Predicate Authority.
|
|
47
|
+
* If denied, throws PredicateAuthorizationError. If approved, proceeds with execution.
|
|
48
|
+
*/
|
|
49
|
+
async execute(input, next) {
|
|
50
|
+
const activityArgs = input.args;
|
|
51
|
+
const argsJson = JSON.stringify(activityArgs);
|
|
52
|
+
const argsHash = createHash("sha256").update(argsJson).digest("hex");
|
|
53
|
+
const request = {
|
|
54
|
+
principal: this.principal,
|
|
55
|
+
action: this.activityType,
|
|
56
|
+
resource: this.resource,
|
|
57
|
+
intent_hash: `ih_execute_${this.activityType.toLowerCase()}`,
|
|
58
|
+
context: {
|
|
59
|
+
state_hash: argsHash,
|
|
60
|
+
tenant_id: this.tenantId,
|
|
61
|
+
session_id: this.sessionId
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const decision = await this.authorityClient.authorize(request);
|
|
65
|
+
if (!decision.allowed) {
|
|
66
|
+
throw new PredicateAuthorizationError({
|
|
67
|
+
activityType: this.activityType,
|
|
68
|
+
reason: decision.reason,
|
|
69
|
+
violatedRule: decision.violated_rule ?? void 0,
|
|
70
|
+
missingLabels: decision.missing_labels ?? []
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return next(input);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
function createPredicateInterceptors(options) {
|
|
77
|
+
const activityInterceptorFactory = (ctx) => ({
|
|
78
|
+
inbound: new PredicateActivityInterceptor(ctx, options)
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
activity: [activityInterceptorFactory]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { PredicateActivityInterceptor, PredicateAuthorizationError, createPredicateInterceptors };
|
|
86
|
+
//# sourceMappingURL=index.mjs.map
|
|
87
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/interceptor.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Error thrown when Predicate Authority denies an activity execution.\n */\nexport class PredicateAuthorizationError extends Error {\n /** The authorization reason code */\n public readonly reason: string;\n\n /** The policy rule that caused the denial, if any */\n public readonly violatedRule: string | undefined;\n\n /** Labels that were required but missing */\n public readonly missingLabels: readonly string[];\n\n /** The activity type that was denied */\n public readonly activityType: string;\n\n constructor(options: {\n activityType: string;\n reason: string;\n violatedRule?: string | undefined;\n missingLabels?: readonly string[];\n }) {\n const message = `Predicate Zero-Trust Denial: Activity '${options.activityType}' not authorized. Reason: ${options.reason}${\n options.violatedRule ? `, violated rule: ${options.violatedRule}` : \"\"\n }`;\n super(message);\n this.name = \"PredicateAuthorizationError\";\n this.reason = options.reason;\n this.violatedRule = options.violatedRule;\n this.missingLabels = options.missingLabels ?? [];\n this.activityType = options.activityType;\n }\n}\n","/**\n * Predicate Authority interceptor for Temporal.io activities.\n *\n * This module provides a pre-execution security gate for all Temporal Activities,\n * enforcing cryptographic authorization mandates before any activity code runs.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { Context } from \"@temporalio/activity\";\nimport type {\n ActivityExecuteInput,\n ActivityInboundCallsInterceptor,\n ActivityInterceptorsFactory,\n Next,\n WorkerInterceptors,\n} from \"@temporalio/worker\";\nimport { PredicateAuthorizationError } from \"./errors.js\";\n\n/**\n * Interface for the Predicate Authority client.\n * This matches the AuthorityClient from @predicatesystems/authority.\n */\nexport interface PredicateAuthorityClient {\n authorize(request: PredicateAuthorizeRequest): Promise<PredicateAuthorizationResponse>;\n}\n\n/**\n * Authorization request sent to the Predicate Authority sidecar.\n */\nexport interface PredicateAuthorizeRequest {\n principal: string;\n action: string;\n resource: string;\n intent_hash?: string;\n context?: Record<string, unknown>;\n labels?: string[];\n}\n\n/**\n * Authorization response from the Predicate Authority sidecar.\n */\nexport interface PredicateAuthorizationResponse {\n allowed: boolean;\n reason: string;\n mandate_id: string | null;\n violated_rule: string | null;\n missing_labels: string[];\n}\n\n/**\n * Options for creating Predicate interceptors.\n */\nexport interface PredicateInterceptorOptions {\n /** The Predicate Authority client for authorization */\n authorityClient: PredicateAuthorityClient;\n\n /** Principal ID used for authorization requests (default: \"temporal-worker\") */\n principal?: string;\n\n /** Optional tenant ID for multi-tenant setups */\n tenantId?: string;\n\n /** Optional session ID for request correlation */\n sessionId?: string;\n\n /** Optional resource identifier (default: \"temporal:activity\") */\n resource?: string;\n}\n\n/**\n * Activity interceptor that enforces Predicate Authority authorization.\n *\n * This interceptor sits in the Temporal activity execution pipeline and ensures\n * that every activity is authorized before execution. If authorization is denied,\n * a PredicateAuthorizationError is thrown and the activity never executes.\n */\nexport class PredicateActivityInterceptor implements ActivityInboundCallsInterceptor {\n private readonly authorityClient: PredicateAuthorityClient;\n private readonly principal: string;\n private readonly tenantId: string | undefined;\n private readonly sessionId: string | undefined;\n private readonly resource: string;\n private readonly activityType: string;\n\n constructor(ctx: Context, options: PredicateInterceptorOptions) {\n this.authorityClient = options.authorityClient;\n this.principal = options.principal ?? \"temporal-worker\";\n this.tenantId = options.tenantId;\n this.sessionId = options.sessionId;\n this.resource = options.resource ?? \"temporal:activity\";\n this.activityType = ctx.info.activityType;\n }\n\n /**\n * Execute activity with Predicate Authority authorization check.\n *\n * This method intercepts the activity execution, extracts the activity type\n * and arguments, and requests authorization from Predicate Authority.\n * If denied, throws PredicateAuthorizationError. If approved, proceeds with execution.\n */\n async execute(\n input: ActivityExecuteInput,\n next: Next<ActivityInboundCallsInterceptor, \"execute\">\n ): Promise<unknown> {\n const activityArgs = input.args;\n\n // Hash the arguments for state evidence\n const argsJson = JSON.stringify(activityArgs);\n const argsHash = createHash(\"sha256\").update(argsJson).digest(\"hex\");\n\n const request: PredicateAuthorizeRequest = {\n principal: this.principal,\n action: this.activityType,\n resource: this.resource,\n intent_hash: `ih_execute_${this.activityType.toLowerCase()}`,\n context: {\n state_hash: argsHash,\n tenant_id: this.tenantId,\n session_id: this.sessionId,\n },\n };\n\n const decision = await this.authorityClient.authorize(request);\n\n if (!decision.allowed) {\n throw new PredicateAuthorizationError({\n activityType: this.activityType,\n reason: decision.reason,\n violatedRule: decision.violated_rule ?? undefined,\n missingLabels: decision.missing_labels ?? [],\n });\n }\n\n return next(input);\n }\n}\n\n/**\n * Creates Temporal worker interceptors that enforce Predicate Authority authorization.\n *\n * @example\n * ```typescript\n * import { Worker } from \"@temporalio/worker\";\n * import { AuthorityClient } from \"@predicatesystems/authority\";\n * import { createPredicateInterceptors } from \"@predicatesystems/temporal\";\n *\n * const authorityClient = new AuthorityClient({\n * baseUrl: \"http://127.0.0.1:8787\",\n * });\n *\n * const interceptors = createPredicateInterceptors({\n * authorityClient,\n * principal: \"temporal-worker\",\n * });\n *\n * const worker = await Worker.create({\n * connection,\n * taskQueue: \"my-task-queue\",\n * workflowsPath: require.resolve(\"./workflows\"),\n * activities,\n * interceptors,\n * });\n * ```\n */\nexport function createPredicateInterceptors(\n options: PredicateInterceptorOptions\n): WorkerInterceptors {\n const activityInterceptorFactory: ActivityInterceptorsFactory = (ctx) => ({\n inbound: new PredicateActivityInterceptor(ctx, options),\n });\n\n return {\n activity: [activityInterceptorFactory],\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@predicatesystems/temporal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Temporal.io Worker Interceptor for Predicate Authority Zero-Trust authorization",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"test:coverage": "vitest run --coverage",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"lint": "biome check src tests",
|
|
28
|
+
"lint:fix": "biome check --write src tests",
|
|
29
|
+
"format": "biome format --write src tests",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"temporal",
|
|
34
|
+
"temporalio",
|
|
35
|
+
"authorization",
|
|
36
|
+
"zero-trust",
|
|
37
|
+
"security",
|
|
38
|
+
"ai-agents",
|
|
39
|
+
"predicate-authority",
|
|
40
|
+
"interceptor"
|
|
41
|
+
],
|
|
42
|
+
"author": "Predicate Systems <hello@predicatesystems.dev>",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/PredicateSystems/temporal-predicate-typescript.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/PredicateSystems/temporal-predicate-typescript/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/PredicateSystems/temporal-predicate-typescript#readme",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@predicatesystems/authority": ">=0.1.0",
|
|
57
|
+
"@temporalio/worker": ">=1.9.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@biomejs/biome": "^1.5.0",
|
|
61
|
+
"@predicatesystems/authority": "^0.3.0",
|
|
62
|
+
"@temporalio/worker": "^1.11.0",
|
|
63
|
+
"@types/node": "^20.11.0",
|
|
64
|
+
"@vitest/coverage-v8": "^1.2.0",
|
|
65
|
+
"tsup": "^8.0.0",
|
|
66
|
+
"typescript": "^5.3.0",
|
|
67
|
+
"vitest": "^1.2.0"
|
|
68
|
+
}
|
|
69
|
+
}
|