compfn 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/README.md +705 -0
- package/cli/dist/commands/attest.d.ts +7 -0
- package/cli/dist/commands/attest.d.ts.map +1 -0
- package/cli/dist/commands/attest.js +26 -0
- package/cli/dist/commands/attest.js.map +1 -0
- package/cli/dist/commands/export.d.ts +6 -0
- package/cli/dist/commands/export.d.ts.map +1 -0
- package/cli/dist/commands/export.js +30 -0
- package/cli/dist/commands/export.js.map +1 -0
- package/cli/dist/commands/init.d.ts +4 -0
- package/cli/dist/commands/init.d.ts.map +1 -0
- package/cli/dist/commands/init.js +22 -0
- package/cli/dist/commands/init.js.map +1 -0
- package/cli/dist/commands/run-checks.d.ts +5 -0
- package/cli/dist/commands/run-checks.d.ts.map +1 -0
- package/cli/dist/commands/run-checks.js +53 -0
- package/cli/dist/commands/run-checks.js.map +1 -0
- package/cli/dist/config.d.ts +7 -0
- package/cli/dist/config.d.ts.map +1 -0
- package/cli/dist/config.js +28 -0
- package/cli/dist/config.js.map +1 -0
- package/cli/dist/index.d.ts +3 -0
- package/cli/dist/index.d.ts.map +1 -0
- package/cli/dist/index.js +39 -0
- package/cli/dist/index.js.map +1 -0
- package/dist/adapters/authfn.d.ts +43 -0
- package/dist/adapters/authfn.d.ts.map +1 -0
- package/dist/adapters/authfn.js +67 -0
- package/dist/adapters/authfn.js.map +1 -0
- package/dist/adapters/secfn.d.ts +44 -0
- package/dist/adapters/secfn.d.ts.map +1 -0
- package/dist/adapters/secfn.js +68 -0
- package/dist/adapters/secfn.js.map +1 -0
- package/dist/adapters/types.d.ts +46 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +7 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/api.d.ts +72 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +5 -0
- package/dist/api.js.map +1 -0
- package/dist/bundles/loader.d.ts +39 -0
- package/dist/bundles/loader.d.ts.map +1 -0
- package/dist/bundles/loader.js +80 -0
- package/dist/bundles/loader.js.map +1 -0
- package/dist/check.d.ts +23 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +5 -0
- package/dist/check.js.map +1 -0
- package/dist/checks/service.d.ts +31 -0
- package/dist/checks/service.d.ts.map +1 -0
- package/dist/checks/service.js +286 -0
- package/dist/checks/service.js.map +1 -0
- package/dist/compfn.d.ts +9 -0
- package/dist/compfn.d.ts.map +1 -0
- package/dist/compfn.js +94 -0
- package/dist/compfn.js.map +1 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/control.d.ts +25 -0
- package/dist/control.d.ts.map +1 -0
- package/dist/control.js +5 -0
- package/dist/control.js.map +1 -0
- package/dist/controls/service.d.ts +22 -0
- package/dist/controls/service.d.ts.map +1 -0
- package/dist/controls/service.js +248 -0
- package/dist/controls/service.js.map +1 -0
- package/dist/errors.d.ts +23 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence/service.d.ts +34 -0
- package/dist/evidence/service.d.ts.map +1 -0
- package/dist/evidence/service.js +218 -0
- package/dist/evidence/service.js.map +1 -0
- package/dist/evidence.d.ts +30 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +20 -0
- package/dist/evidence.js.map +1 -0
- package/dist/export/service.d.ts +39 -0
- package/dist/export/service.d.ts.map +1 -0
- package/dist/export/service.js +108 -0
- package/dist/export/service.js.map +1 -0
- package/dist/export-types.d.ts +18 -0
- package/dist/export-types.d.ts.map +1 -0
- package/dist/export-types.js +5 -0
- package/dist/export-types.js.map +1 -0
- package/dist/framework.d.ts +24 -0
- package/dist/framework.d.ts.map +1 -0
- package/dist/framework.js +5 -0
- package/dist/framework.js.map +1 -0
- package/dist/frameworks/service.d.ts +22 -0
- package/dist/frameworks/service.d.ts.map +1 -0
- package/dist/frameworks/service.js +255 -0
- package/dist/frameworks/service.js.map +1 -0
- package/dist/http/routes.d.ts +4 -0
- package/dist/http/routes.d.ts.map +1 -0
- package/dist/http/routes.js +256 -0
- package/dist/http/routes.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +19 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +39 -0
- package/dist/logger.js.map +1 -0
- package/dist/readiness/service.d.ts +34 -0
- package/dist/readiness/service.d.ts.map +1 -0
- package/dist/readiness/service.js +160 -0
- package/dist/readiness/service.js.map +1 -0
- package/dist/readiness.d.ts +18 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +5 -0
- package/dist/readiness.js.map +1 -0
- package/dist/storage/adapter.d.ts +21 -0
- package/dist/storage/adapter.d.ts.map +1 -0
- package/dist/storage/adapter.js +37 -0
- package/dist/storage/adapter.js.map +1 -0
- package/dist/storage/schema.d.ts +14 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +92 -0
- package/dist/storage/schema.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +52 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +550 -0
- package/dist/validation.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
# @superfunctions/compfn
|
|
2
|
+
|
|
3
|
+
Self-hosted compliance function for managing controls, frameworks, evidence, checks, and audit-ready exports.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
CompFn is a compliance automation tool that:
|
|
8
|
+
- Maps **controls** to **frameworks** (SOC 2, ISO 27001, HIPAA, GDPR, etc.)
|
|
9
|
+
- Collects **evidence** from superfunctions and manual sources
|
|
10
|
+
- Runs **continuous checks** to verify compliance
|
|
11
|
+
- Produces **audit-ready reports**
|
|
12
|
+
- Keeps all evidence on your own infrastructure (no mandatory SaaS)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @superfunctions/compfn
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { compFn } from "@superfunctions/compfn";
|
|
24
|
+
import { memoryAdapter } from "@superfunctions/db/adapters";
|
|
25
|
+
|
|
26
|
+
const db = memoryAdapter();
|
|
27
|
+
await db.initialize();
|
|
28
|
+
|
|
29
|
+
const api = compFn({
|
|
30
|
+
database: db,
|
|
31
|
+
namespace: "compfn",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const control = await api.controls.create({
|
|
35
|
+
name: "Access reviews",
|
|
36
|
+
description: "Quarterly access reviews",
|
|
37
|
+
category: "access",
|
|
38
|
+
tags: ["soc2"],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log(control);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Core API
|
|
45
|
+
|
|
46
|
+
The `compFn()` function returns a `CompFnAPI` instance with the following namespaces:
|
|
47
|
+
|
|
48
|
+
- `controls` - Create, read, update, delete controls
|
|
49
|
+
- `frameworks` - Manage compliance frameworks and requirement mappings
|
|
50
|
+
- `evidence` - Create and query evidence (immutable)
|
|
51
|
+
- `checks` - Define and run compliance checks
|
|
52
|
+
- `readiness` - Get compliance status for controls and frameworks
|
|
53
|
+
- `export` - Generate auditor packs
|
|
54
|
+
|
|
55
|
+
All methods return a `CompfnEnvelope<T>`:
|
|
56
|
+
- Success: `{ ok: true, result: T }`
|
|
57
|
+
- Failure: `{ ok: false, error: { code, message, details } }`
|
|
58
|
+
|
|
59
|
+
### Error Codes
|
|
60
|
+
|
|
61
|
+
- `CONTROL_NOT_FOUND` - Control does not exist
|
|
62
|
+
- `FRAMEWORK_NOT_FOUND` - Framework does not exist
|
|
63
|
+
- `EVIDENCE_NOT_FOUND` - Evidence does not exist
|
|
64
|
+
- `CHECK_NOT_FOUND` - Check does not exist
|
|
65
|
+
- `VALIDATION_FAILED` - Input validation failed
|
|
66
|
+
- `ADAPTER_NOT_FOUND` - Evidence adapter not configured
|
|
67
|
+
- `ADAPTER_ERROR` - Evidence adapter threw an error
|
|
68
|
+
- `STORAGE_ERROR` - Database operation failed
|
|
69
|
+
- `EXPORT_FAILED` - Export size exceeded limit
|
|
70
|
+
- `PAYLOAD_TOO_LARGE` - Evidence payload exceeds size limit
|
|
71
|
+
- `RATE_LIMITED` - Rate limit exceeded
|
|
72
|
+
|
|
73
|
+
## HTTP API
|
|
74
|
+
|
|
75
|
+
CompFn provides an optional HTTP server that exposes all core functionality via REST endpoints.
|
|
76
|
+
|
|
77
|
+
### Setup
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { compFn, createCompFnRouter } from "@superfunctions/compfn";
|
|
81
|
+
import { serve } from "@hono/node-server";
|
|
82
|
+
|
|
83
|
+
const api = compFn({ database: db, namespace: "compfn" });
|
|
84
|
+
const app = createCompFnRouter(api);
|
|
85
|
+
|
|
86
|
+
serve({ fetch: app.fetch, port: 3000 });
|
|
87
|
+
console.log("CompFn HTTP API running on http://localhost:3000");
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Response Envelope
|
|
91
|
+
|
|
92
|
+
All HTTP endpoints return a JSON envelope:
|
|
93
|
+
|
|
94
|
+
**Success (2xx):**
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"ok": true,
|
|
98
|
+
"result": { ... }
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Failure (4xx/5xx):**
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"ok": false,
|
|
106
|
+
"error": {
|
|
107
|
+
"code": "CONTROL_NOT_FOUND",
|
|
108
|
+
"message": "Control not found",
|
|
109
|
+
"details": { "id": "ctrl_123" }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Status Codes
|
|
115
|
+
|
|
116
|
+
- `200` - Successful GET, PATCH, DELETE, or POST (non-create)
|
|
117
|
+
- `201` - Successful POST (create)
|
|
118
|
+
- `400` - Validation failed (`VALIDATION_FAILED`)
|
|
119
|
+
- `404` - Resource not found (e.g., `CONTROL_NOT_FOUND`)
|
|
120
|
+
- `413` - Payload too large (`PAYLOAD_TOO_LARGE`)
|
|
121
|
+
- `429` - Rate limited (`RATE_LIMITED`)
|
|
122
|
+
- `500` - Server error (e.g., `STORAGE_ERROR`, `ADAPTER_ERROR`)
|
|
123
|
+
|
|
124
|
+
### Endpoints
|
|
125
|
+
|
|
126
|
+
#### Controls
|
|
127
|
+
|
|
128
|
+
**POST /controls**
|
|
129
|
+
Create a new control.
|
|
130
|
+
|
|
131
|
+
Request body:
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"name": "Access reviews",
|
|
135
|
+
"description": "Quarterly access reviews",
|
|
136
|
+
"category": "access",
|
|
137
|
+
"tags": ["soc2"]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Response: `201` with `{ ok: true, result: Control }`
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
**GET /controls**
|
|
146
|
+
List all controls. Supports optional query parameters:
|
|
147
|
+
- `category` - Filter by category
|
|
148
|
+
- `tags` - Comma-separated list of tags
|
|
149
|
+
|
|
150
|
+
Response: `200` with `{ ok: true, result: Control[] }`
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
**GET /controls/:id**
|
|
155
|
+
Get a control by ID.
|
|
156
|
+
|
|
157
|
+
Response: `200` with `{ ok: true, result: Control }` or `404` with `CONTROL_NOT_FOUND`
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
**PATCH /controls/:id**
|
|
162
|
+
Update a control.
|
|
163
|
+
|
|
164
|
+
Request body (all fields optional):
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"name": "Updated name",
|
|
168
|
+
"description": "Updated description",
|
|
169
|
+
"category": "updated_category",
|
|
170
|
+
"tags": ["tag1", "tag2"]
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Response: `200` with `{ ok: true, result: Control }`
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
**DELETE /controls/:id**
|
|
179
|
+
Delete a control.
|
|
180
|
+
|
|
181
|
+
Response: `200` with `{ ok: true, result: undefined }`
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
#### Frameworks
|
|
186
|
+
|
|
187
|
+
**POST /frameworks**
|
|
188
|
+
Create a new framework.
|
|
189
|
+
|
|
190
|
+
Request body:
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"name": "SOC 2 Type II",
|
|
194
|
+
"version": "2022",
|
|
195
|
+
"description": "SOC 2 Type II compliance",
|
|
196
|
+
"requirements": [
|
|
197
|
+
{
|
|
198
|
+
"requirementId": "CC6.1",
|
|
199
|
+
"controlIds": ["ctrl_123", "ctrl_456"],
|
|
200
|
+
"name": "Logical Access Controls"
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Response: `201` with `{ ok: true, result: Framework }`
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
**GET /frameworks**
|
|
211
|
+
List all frameworks.
|
|
212
|
+
|
|
213
|
+
Response: `200` with `{ ok: true, result: Framework[] }`
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
**GET /frameworks/:id**
|
|
218
|
+
Get a framework by ID.
|
|
219
|
+
|
|
220
|
+
Response: `200` with `{ ok: true, result: Framework }` or `404` with `FRAMEWORK_NOT_FOUND`
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
**PATCH /frameworks/:id**
|
|
225
|
+
Update a framework.
|
|
226
|
+
|
|
227
|
+
Request body (all fields optional):
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"name": "Updated name",
|
|
231
|
+
"version": "2023",
|
|
232
|
+
"description": "Updated description",
|
|
233
|
+
"requirements": [ ... ]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Response: `200` with `{ ok: true, result: Framework }`
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
**DELETE /frameworks/:id**
|
|
242
|
+
Delete a framework.
|
|
243
|
+
|
|
244
|
+
Response: `200` with `{ ok: true, result: undefined }`
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
#### Evidence
|
|
249
|
+
|
|
250
|
+
**POST /evidence**
|
|
251
|
+
Create evidence for a control.
|
|
252
|
+
|
|
253
|
+
Request body:
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"controlId": "ctrl_123",
|
|
257
|
+
"type": "manual_attestation",
|
|
258
|
+
"actorId": "user_456",
|
|
259
|
+
"outcome": "pass",
|
|
260
|
+
"payload": { "note": "Access review completed" },
|
|
261
|
+
"timestamp": 1234567890000
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Response: `201` with `{ ok: true, result: Evidence }`
|
|
266
|
+
|
|
267
|
+
Evidence types:
|
|
268
|
+
- `automated_secfn`, `automated_authfn`, `automated_logfn`, `automated_watchfn`
|
|
269
|
+
- `automated_hostfn`, `automated_flowfn`, `automated_filefn`, `automated_plugfn`
|
|
270
|
+
- `manual_attestation`, `file_upload`, `questionnaire`, `custom_check`
|
|
271
|
+
- `scoping_decision`, `external_webhook`
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
**GET /evidence**
|
|
276
|
+
List evidence. Supports optional query parameters:
|
|
277
|
+
- `controlId` - Filter by control ID
|
|
278
|
+
- `frameworkId` - Filter by framework ID
|
|
279
|
+
- `type` - Filter by evidence type
|
|
280
|
+
- `since` - Filter by timestamp (Unix milliseconds)
|
|
281
|
+
|
|
282
|
+
Response: `200` with `{ ok: true, result: Evidence[] }`
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
**GET /evidence/:id**
|
|
287
|
+
Get evidence by ID.
|
|
288
|
+
|
|
289
|
+
Response: `200` with `{ ok: true, result: Evidence }` or `404` with `EVIDENCE_NOT_FOUND`
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
#### Checks
|
|
294
|
+
|
|
295
|
+
**POST /checks**
|
|
296
|
+
Create a check definition.
|
|
297
|
+
|
|
298
|
+
Request body:
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"name": "RBAC Check",
|
|
302
|
+
"controlId": "ctrl_123",
|
|
303
|
+
"schedule": "daily",
|
|
304
|
+
"adapterName": "secfn",
|
|
305
|
+
"adapterMethod": "getRbacStatus"
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Response: `201` with `{ ok: true, result: CheckDefinition }`
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
**GET /checks**
|
|
314
|
+
List all checks.
|
|
315
|
+
|
|
316
|
+
Response: `200` with `{ ok: true, result: CheckDefinition[] }`
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
**GET /checks/:id**
|
|
321
|
+
Get a check by ID.
|
|
322
|
+
|
|
323
|
+
Response: `200` with `{ ok: true, result: CheckDefinition }` or `404` with `CHECK_NOT_FOUND`
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
**POST /checks/:id/run**
|
|
328
|
+
Run a check immediately.
|
|
329
|
+
|
|
330
|
+
Response: `200` with `{ ok: true, result: RunCheckResult }`
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"checkId": "chk_123",
|
|
335
|
+
"controlId": "ctrl_456",
|
|
336
|
+
"evidenceId": "evid_789",
|
|
337
|
+
"outcome": "pass",
|
|
338
|
+
"timestamp": 1234567890000
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
**DELETE /checks/:id**
|
|
345
|
+
Delete a check.
|
|
346
|
+
|
|
347
|
+
Response: `200` with `{ ok: true, result: undefined }`
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
#### Readiness
|
|
352
|
+
|
|
353
|
+
**GET /readiness/control/:controlId**
|
|
354
|
+
Get readiness status for a control. Supports optional query parameter:
|
|
355
|
+
- `frameworkId` - Scope readiness to a specific framework
|
|
356
|
+
|
|
357
|
+
Response: `200` with `{ ok: true, result: ControlReadiness }`
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
{
|
|
361
|
+
"controlId": "ctrl_123",
|
|
362
|
+
"status": "compliant",
|
|
363
|
+
"lastEvidenceAt": 1234567890000,
|
|
364
|
+
"lastEvidenceId": "evid_456",
|
|
365
|
+
"requirementIds": ["CC6.1", "CC6.2"]
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Status values: `compliant`, `not_compliant`, `not_applicable`
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
**GET /readiness/framework/:frameworkId**
|
|
374
|
+
Get readiness status for a framework.
|
|
375
|
+
|
|
376
|
+
Response: `200` with `{ ok: true, result: FrameworkReadiness }`
|
|
377
|
+
|
|
378
|
+
```json
|
|
379
|
+
{
|
|
380
|
+
"frameworkId": "fw_123",
|
|
381
|
+
"status": "compliant",
|
|
382
|
+
"controlReadiness": [ ... ],
|
|
383
|
+
"lastUpdated": 1234567890000
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
#### Export
|
|
390
|
+
|
|
391
|
+
**POST /export/auditor-pack**
|
|
392
|
+
Export an auditor pack.
|
|
393
|
+
|
|
394
|
+
Request body (all fields optional):
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"frameworkId": "fw_123",
|
|
398
|
+
"since": 1234567890000
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Response: `200` with `{ ok: true, result: AuditorPack }`
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
{
|
|
406
|
+
"exportedAt": 1234567890000,
|
|
407
|
+
"frameworkId": "fw_123",
|
|
408
|
+
"controls": [ ... ],
|
|
409
|
+
"frameworks": [ ... ],
|
|
410
|
+
"evidence": [ ... ],
|
|
411
|
+
"mapping": [
|
|
412
|
+
{ "requirementId": "CC6.1", "controlIds": ["ctrl_123"] }
|
|
413
|
+
]
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## CLI
|
|
420
|
+
|
|
421
|
+
CompFn provides a command-line interface for common operations.
|
|
422
|
+
|
|
423
|
+
### Configuration
|
|
424
|
+
|
|
425
|
+
Create a `compfn.config.json` file:
|
|
426
|
+
|
|
427
|
+
```json
|
|
428
|
+
{
|
|
429
|
+
"database": {
|
|
430
|
+
"type": "memory"
|
|
431
|
+
},
|
|
432
|
+
"namespace": "compfn",
|
|
433
|
+
"systemActorId": "system"
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Or set the `COMPFN_CONFIG` environment variable to point to your config file.
|
|
438
|
+
|
|
439
|
+
### Commands
|
|
440
|
+
|
|
441
|
+
#### `compfn init`
|
|
442
|
+
|
|
443
|
+
Initialize the database schema.
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
compfn init
|
|
447
|
+
compfn init --config /path/to/config.json
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Options:
|
|
451
|
+
- `-c, --config <path>` - Path to config file (default: `compfn.config.json` or `$COMPFN_CONFIG`)
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
#### `compfn run-checks`
|
|
456
|
+
|
|
457
|
+
Run all compliance checks or a specific check.
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
compfn run-checks
|
|
461
|
+
compfn run-checks --check-id chk_123
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Options:
|
|
465
|
+
- `--check-id <id>` - Run a specific check by ID
|
|
466
|
+
- `-c, --config <path>` - Path to config file
|
|
467
|
+
|
|
468
|
+
Output:
|
|
469
|
+
```
|
|
470
|
+
Ran 3 checks: 2 pass, 1 fail
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
#### `compfn export`
|
|
476
|
+
|
|
477
|
+
Export an auditor pack to a file or stdout.
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
compfn export --output report.json
|
|
481
|
+
compfn export --framework-id fw_soc2 --output soc2-report.json
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Options:
|
|
485
|
+
- `--framework-id <id>` - Export for a specific framework (optional)
|
|
486
|
+
- `--output <path>` - Output file path (default: stdout)
|
|
487
|
+
- `-c, --config <path>` - Path to config file
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
#### `compfn attest`
|
|
492
|
+
|
|
493
|
+
Create a manual attestation evidence record.
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
compfn attest --control-id ctrl_123 --actor-id user_456
|
|
497
|
+
compfn attest --control-id ctrl_123 --actor-id user_456 --outcome pass
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Options:
|
|
501
|
+
- `--control-id <id>` - Control ID (required)
|
|
502
|
+
- `--actor-id <id>` - Actor ID (required)
|
|
503
|
+
- `--outcome <pass|fail>` - Outcome (default: `pass`)
|
|
504
|
+
- `-c, --config <path>` - Path to config file
|
|
505
|
+
|
|
506
|
+
Output:
|
|
507
|
+
```
|
|
508
|
+
Attestation created: evid_789
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## Evidence Adapters
|
|
514
|
+
|
|
515
|
+
CompFn supports pluggable evidence adapters for integrating with superfunctions:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
import { compFn, createSecfnAdapter } from "@superfunctions/compfn";
|
|
519
|
+
|
|
520
|
+
const api = compFn({
|
|
521
|
+
database: db,
|
|
522
|
+
adapters: {
|
|
523
|
+
secfn: createSecfnAdapter({ secfnClient }),
|
|
524
|
+
authfn: createAuthfnAdapter({ authfnClient }),
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## Framework Bundles
|
|
530
|
+
|
|
531
|
+
Load pre-defined framework bundles (SOC 2, ISO 27001, etc.):
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
import { loadBundle } from "@superfunctions/compfn";
|
|
535
|
+
|
|
536
|
+
const bundle = await loadBundle("soc2-type2");
|
|
537
|
+
const framework = await api.frameworks.create(bundle.framework);
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Configuration
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
const api = compFn({
|
|
544
|
+
database: adapter, // Required: @superfunctions/db adapter
|
|
545
|
+
namespace: "compfn", // Optional: table prefix
|
|
546
|
+
systemActorId: "system", // Optional: actor ID for automated checks
|
|
547
|
+
readinessWindowDays: 90, // Optional: freshness window for compliance
|
|
548
|
+
evidencePayloadMaxBytes: 524288, // Optional: 512 KiB default
|
|
549
|
+
retentionDays: 2555, // Optional: 7 years default
|
|
550
|
+
exportMaxBytes: 52428800, // Optional: 50 MiB default
|
|
551
|
+
adapters: { ... }, // Optional: evidence adapters
|
|
552
|
+
logger: customLogger, // Optional: custom logger
|
|
553
|
+
});
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
## Provider API: Consuming CompFn
|
|
557
|
+
|
|
558
|
+
CompFn can be consumed by other superfunctions (productFn, userfn, flowfn) to display compliance status and evidence-due tasks. The `readiness` and `evidence` APIs provide all the data needed to implement:
|
|
559
|
+
|
|
560
|
+
- **Compliance status widget**: Show framework readiness (compliant/not compliant/not applicable)
|
|
561
|
+
- **Evidence-due tasks**: List controls that need attention (no recent evidence or old evidence)
|
|
562
|
+
- **Attestation workflows**: Prompt users to submit manual attestations
|
|
563
|
+
|
|
564
|
+
### Getting Framework Readiness
|
|
565
|
+
|
|
566
|
+
Use `readiness.forFramework(frameworkId)` to get overall compliance status:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
const readinessRes = await api.readiness.forFramework("fw_soc2");
|
|
570
|
+
if (!readinessRes.ok) {
|
|
571
|
+
console.error("Failed to get readiness:", readinessRes.error);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const { status, controlReadiness, lastUpdated } = readinessRes.result;
|
|
576
|
+
console.log(`Framework status: ${status}`);
|
|
577
|
+
console.log(`Last updated: ${new Date(lastUpdated).toISOString()}`);
|
|
578
|
+
|
|
579
|
+
controlReadiness.forEach((cr) => {
|
|
580
|
+
console.log(`Control ${cr.controlId}: ${cr.status}`);
|
|
581
|
+
if (cr.lastEvidenceAt) {
|
|
582
|
+
console.log(` Last evidence: ${new Date(cr.lastEvidenceAt).toISOString()}`);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### Computing Evidence-Due Controls
|
|
588
|
+
|
|
589
|
+
To find controls that need attention ("evidence due"), filter `controlReadiness` by:
|
|
590
|
+
- Controls with no evidence (`lastEvidenceAt` is undefined)
|
|
591
|
+
- Controls with old evidence (`lastEvidenceAt < threshold`)
|
|
592
|
+
- Exclude controls marked as `not_applicable`
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
const readinessRes = await api.readiness.forFramework("fw_soc2");
|
|
596
|
+
if (!readinessRes.ok) return;
|
|
597
|
+
|
|
598
|
+
const threshold = Date.now() - 90 * 24 * 60 * 60 * 1000;
|
|
599
|
+
|
|
600
|
+
const evidenceDue = readinessRes.result.controlReadiness.filter((cr) => {
|
|
601
|
+
if (cr.status === "not_applicable") return false;
|
|
602
|
+
if (!cr.lastEvidenceAt) return true;
|
|
603
|
+
return cr.lastEvidenceAt < threshold;
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
console.log(`${evidenceDue.length} controls need evidence`);
|
|
607
|
+
evidenceDue.forEach((cr) => {
|
|
608
|
+
console.log(`- Control ${cr.controlId} (last evidence: ${cr.lastEvidenceAt ? new Date(cr.lastEvidenceAt).toISOString() : "never"})`);
|
|
609
|
+
});
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### Listing Recent Evidence
|
|
613
|
+
|
|
614
|
+
Use `evidence.list({ since })` to get all recent evidence across controls:
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
const ninetyDaysAgo = Date.now() - 90 * 24 * 60 * 60 * 1000;
|
|
618
|
+
const evidenceRes = await api.evidence.list({ since: ninetyDaysAgo });
|
|
619
|
+
if (!evidenceRes.ok) return;
|
|
620
|
+
|
|
621
|
+
console.log(`${evidenceRes.result.length} evidence records in last 90 days`);
|
|
622
|
+
evidenceRes.result.forEach((e) => {
|
|
623
|
+
console.log(`- ${e.type} for control ${e.controlId}: ${e.outcome}`);
|
|
624
|
+
});
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
You can also filter by control or framework:
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
const evidenceRes = await api.evidence.list({
|
|
631
|
+
controlId: "ctrl_123",
|
|
632
|
+
since: Date.now() - 30 * 24 * 60 * 60 * 1000,
|
|
633
|
+
});
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### Example: Building a "Tasks Due" View
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
async function getComplianceTasks(frameworkId: string): Promise<Array<{ controlId: string; task: string }>> {
|
|
640
|
+
const readinessRes = await api.readiness.forFramework(frameworkId);
|
|
641
|
+
if (!readinessRes.ok) return [];
|
|
642
|
+
|
|
643
|
+
const threshold = Date.now() - 90 * 24 * 60 * 60 * 1000;
|
|
644
|
+
const tasks: Array<{ controlId: string; task: string }> = [];
|
|
645
|
+
|
|
646
|
+
for (const cr of readinessRes.result.controlReadiness) {
|
|
647
|
+
if (cr.status === "not_applicable") continue;
|
|
648
|
+
|
|
649
|
+
if (!cr.lastEvidenceAt) {
|
|
650
|
+
tasks.push({
|
|
651
|
+
controlId: cr.controlId,
|
|
652
|
+
task: "No evidence submitted. Submit attestation or run check.",
|
|
653
|
+
});
|
|
654
|
+
} else if (cr.lastEvidenceAt < threshold) {
|
|
655
|
+
const daysOld = Math.floor((Date.now() - cr.lastEvidenceAt) / (24 * 60 * 60 * 1000));
|
|
656
|
+
tasks.push({
|
|
657
|
+
controlId: cr.controlId,
|
|
658
|
+
task: `Evidence is ${daysOld} days old. Submit new attestation or run check.",
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return tasks;
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Creating Attestation Tasks
|
|
668
|
+
|
|
669
|
+
When a user needs to attest, create evidence via `evidence.create`:
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
const attestationRes = await api.evidence.create({
|
|
673
|
+
controlId: "ctrl_access_review",
|
|
674
|
+
type: "manual_attestation",
|
|
675
|
+
actorId: userId,
|
|
676
|
+
outcome: "pass",
|
|
677
|
+
payload: {
|
|
678
|
+
note: "Q4 access review completed. All users reviewed and approved.",
|
|
679
|
+
reviewDate: "2024-01-15",
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
if (attestationRes.ok) {
|
|
684
|
+
console.log("Attestation recorded:", attestationRes.result.id);
|
|
685
|
+
}
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### Summary
|
|
689
|
+
|
|
690
|
+
The Provider API pattern is:
|
|
691
|
+
|
|
692
|
+
1. **Get readiness**: `readiness.forFramework(frameworkId)` → overall status + per-control readiness
|
|
693
|
+
2. **Filter evidence-due**: Find controls with `lastEvidenceAt < threshold` or no evidence
|
|
694
|
+
3. **List evidence**: `evidence.list({ controlId?, since? })` → recent evidence for display
|
|
695
|
+
4. **Create attestations**: `evidence.create({ type: "manual_attestation", ... })` → record user actions
|
|
696
|
+
|
|
697
|
+
This allows productFn, userfn, and flowfn to build:
|
|
698
|
+
- Compliance dashboards (readiness status)
|
|
699
|
+
- Task lists (evidence-due controls)
|
|
700
|
+
- Attestation forms (manual evidence creation)
|
|
701
|
+
- Scheduled reminders (flowfn triggers based on evidence age)
|
|
702
|
+
|
|
703
|
+
## License
|
|
704
|
+
|
|
705
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attest.d.ts","sourceRoot":"","sources":["../../src/commands/attest.ts"],"names":[],"mappings":"AAGA,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhB"}
|