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.
Files changed (134) hide show
  1. package/README.md +705 -0
  2. package/cli/dist/commands/attest.d.ts +7 -0
  3. package/cli/dist/commands/attest.d.ts.map +1 -0
  4. package/cli/dist/commands/attest.js +26 -0
  5. package/cli/dist/commands/attest.js.map +1 -0
  6. package/cli/dist/commands/export.d.ts +6 -0
  7. package/cli/dist/commands/export.d.ts.map +1 -0
  8. package/cli/dist/commands/export.js +30 -0
  9. package/cli/dist/commands/export.js.map +1 -0
  10. package/cli/dist/commands/init.d.ts +4 -0
  11. package/cli/dist/commands/init.d.ts.map +1 -0
  12. package/cli/dist/commands/init.js +22 -0
  13. package/cli/dist/commands/init.js.map +1 -0
  14. package/cli/dist/commands/run-checks.d.ts +5 -0
  15. package/cli/dist/commands/run-checks.d.ts.map +1 -0
  16. package/cli/dist/commands/run-checks.js +53 -0
  17. package/cli/dist/commands/run-checks.js.map +1 -0
  18. package/cli/dist/config.d.ts +7 -0
  19. package/cli/dist/config.d.ts.map +1 -0
  20. package/cli/dist/config.js +28 -0
  21. package/cli/dist/config.js.map +1 -0
  22. package/cli/dist/index.d.ts +3 -0
  23. package/cli/dist/index.d.ts.map +1 -0
  24. package/cli/dist/index.js +39 -0
  25. package/cli/dist/index.js.map +1 -0
  26. package/dist/adapters/authfn.d.ts +43 -0
  27. package/dist/adapters/authfn.d.ts.map +1 -0
  28. package/dist/adapters/authfn.js +67 -0
  29. package/dist/adapters/authfn.js.map +1 -0
  30. package/dist/adapters/secfn.d.ts +44 -0
  31. package/dist/adapters/secfn.d.ts.map +1 -0
  32. package/dist/adapters/secfn.js +68 -0
  33. package/dist/adapters/secfn.js.map +1 -0
  34. package/dist/adapters/types.d.ts +46 -0
  35. package/dist/adapters/types.d.ts.map +1 -0
  36. package/dist/adapters/types.js +7 -0
  37. package/dist/adapters/types.js.map +1 -0
  38. package/dist/api.d.ts +72 -0
  39. package/dist/api.d.ts.map +1 -0
  40. package/dist/api.js +5 -0
  41. package/dist/api.js.map +1 -0
  42. package/dist/bundles/loader.d.ts +39 -0
  43. package/dist/bundles/loader.d.ts.map +1 -0
  44. package/dist/bundles/loader.js +80 -0
  45. package/dist/bundles/loader.js.map +1 -0
  46. package/dist/check.d.ts +23 -0
  47. package/dist/check.d.ts.map +1 -0
  48. package/dist/check.js +5 -0
  49. package/dist/check.js.map +1 -0
  50. package/dist/checks/service.d.ts +31 -0
  51. package/dist/checks/service.d.ts.map +1 -0
  52. package/dist/checks/service.js +286 -0
  53. package/dist/checks/service.js.map +1 -0
  54. package/dist/compfn.d.ts +9 -0
  55. package/dist/compfn.d.ts.map +1 -0
  56. package/dist/compfn.js +94 -0
  57. package/dist/compfn.js.map +1 -0
  58. package/dist/constants.d.ts +14 -0
  59. package/dist/constants.d.ts.map +1 -0
  60. package/dist/constants.js +14 -0
  61. package/dist/constants.js.map +1 -0
  62. package/dist/control.d.ts +25 -0
  63. package/dist/control.d.ts.map +1 -0
  64. package/dist/control.js +5 -0
  65. package/dist/control.js.map +1 -0
  66. package/dist/controls/service.d.ts +22 -0
  67. package/dist/controls/service.d.ts.map +1 -0
  68. package/dist/controls/service.js +248 -0
  69. package/dist/controls/service.js.map +1 -0
  70. package/dist/errors.d.ts +23 -0
  71. package/dist/errors.d.ts.map +1 -0
  72. package/dist/errors.js +5 -0
  73. package/dist/errors.js.map +1 -0
  74. package/dist/evidence/service.d.ts +34 -0
  75. package/dist/evidence/service.d.ts.map +1 -0
  76. package/dist/evidence/service.js +218 -0
  77. package/dist/evidence/service.js.map +1 -0
  78. package/dist/evidence.d.ts +30 -0
  79. package/dist/evidence.d.ts.map +1 -0
  80. package/dist/evidence.js +20 -0
  81. package/dist/evidence.js.map +1 -0
  82. package/dist/export/service.d.ts +39 -0
  83. package/dist/export/service.d.ts.map +1 -0
  84. package/dist/export/service.js +108 -0
  85. package/dist/export/service.js.map +1 -0
  86. package/dist/export-types.d.ts +18 -0
  87. package/dist/export-types.d.ts.map +1 -0
  88. package/dist/export-types.js +5 -0
  89. package/dist/export-types.js.map +1 -0
  90. package/dist/framework.d.ts +24 -0
  91. package/dist/framework.d.ts.map +1 -0
  92. package/dist/framework.js +5 -0
  93. package/dist/framework.js.map +1 -0
  94. package/dist/frameworks/service.d.ts +22 -0
  95. package/dist/frameworks/service.d.ts.map +1 -0
  96. package/dist/frameworks/service.js +255 -0
  97. package/dist/frameworks/service.js.map +1 -0
  98. package/dist/http/routes.d.ts +4 -0
  99. package/dist/http/routes.d.ts.map +1 -0
  100. package/dist/http/routes.js +256 -0
  101. package/dist/http/routes.js.map +1 -0
  102. package/dist/index.d.ts +22 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +15 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/logger.d.ts +19 -0
  107. package/dist/logger.d.ts.map +1 -0
  108. package/dist/logger.js +39 -0
  109. package/dist/logger.js.map +1 -0
  110. package/dist/readiness/service.d.ts +34 -0
  111. package/dist/readiness/service.d.ts.map +1 -0
  112. package/dist/readiness/service.js +160 -0
  113. package/dist/readiness/service.js.map +1 -0
  114. package/dist/readiness.d.ts +18 -0
  115. package/dist/readiness.d.ts.map +1 -0
  116. package/dist/readiness.js +5 -0
  117. package/dist/readiness.js.map +1 -0
  118. package/dist/storage/adapter.d.ts +21 -0
  119. package/dist/storage/adapter.d.ts.map +1 -0
  120. package/dist/storage/adapter.js +37 -0
  121. package/dist/storage/adapter.js.map +1 -0
  122. package/dist/storage/schema.d.ts +14 -0
  123. package/dist/storage/schema.d.ts.map +1 -0
  124. package/dist/storage/schema.js +92 -0
  125. package/dist/storage/schema.js.map +1 -0
  126. package/dist/types.d.ts +12 -0
  127. package/dist/types.d.ts.map +1 -0
  128. package/dist/types.js +5 -0
  129. package/dist/types.js.map +1 -0
  130. package/dist/validation.d.ts +52 -0
  131. package/dist/validation.d.ts.map +1 -0
  132. package/dist/validation.js +550 -0
  133. package/dist/validation.js.map +1 -0
  134. 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,7 @@
1
+ export declare function attestCommand(options: {
2
+ controlId: string;
3
+ actorId: string;
4
+ outcome?: "pass" | "fail";
5
+ config?: string;
6
+ }): Promise<void>;
7
+ //# sourceMappingURL=attest.d.ts.map
@@ -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"}