@xoxoai/checkmate 0.6.7 → 0.7.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/dist/checkmate.js +13375 -0
- package/package.json +14 -100
- package/plugin/.claude-plugin/plugin.json +10 -0
- package/plugin/bin/checkmate.ts +3 -0
- package/plugin/contracts/finding.schema.json +78 -0
- package/plugin/contracts/gap.schema.json +56 -0
- package/plugin/contracts/plan-candidate.schema.json +103 -0
- package/plugin/contracts/plan-delta.schema.json +63 -0
- package/plugin/contracts/plan-patch.schema.json +116 -0
- package/plugin/contracts/plan-request.schema.json +108 -0
- package/plugin/contracts/plan.schema.json +313 -0
- package/plugin/contracts/profile.schema.json +154 -0
- package/plugin/contracts/response.schema.json +25 -0
- package/plugin/contracts/result.schema.json +410 -0
- package/plugin/dist/checkmate.js +13012 -0
- package/plugin/package.json +4 -0
- package/plugin/references/browser.md +35 -0
- package/plugin/references/http.md +30 -0
- package/plugin/references/workflow.md +122 -0
- package/plugin/skills/execute/SKILL.md +92 -0
- package/plugin/skills/plan/SKILL.md +94 -0
- package/plugin/skills/report/SKILL.md +28 -0
- package/plugin/skills/revise/SKILL.md +43 -0
- package/plugin/skills/setup/SKILL.md +82 -0
- package/plugin/skills/status/SKILL.md +20 -0
- package/plugin/skills/ui/SKILL.md +26 -0
- package/plugin/src/actor.ts +21 -0
- package/plugin/src/cli.ts +1133 -0
- package/plugin/src/contracts.ts +342 -0
- package/plugin/src/index.ts +52 -0
- package/plugin/src/plans.ts +1028 -0
- package/plugin/src/render.ts +544 -0
- package/plugin/src/runs.ts +868 -0
- package/plugin/src/store.ts +384 -0
- package/plugin/src/targets.ts +205 -0
- package/plugin/src/types.ts +319 -0
- package/plugin/src/ui/assets/client.js +501 -0
- package/plugin/src/ui/assets/favicon.svg +1 -0
- package/plugin/src/ui/assets/fonts/inter-OFL.txt +93 -0
- package/plugin/src/ui/assets/fonts/inter.woff2 +0 -0
- package/plugin/src/ui/assets/fonts/jetbrains-mono-OFL.txt +93 -0
- package/plugin/src/ui/assets/fonts/jetbrains-mono.woff2 +0 -0
- package/plugin/src/ui/assets/fonts/space-grotesk-OFL.txt +93 -0
- package/plugin/src/ui/assets/fonts/space-grotesk.woff2 +0 -0
- package/plugin/src/ui/assets/style.css +1926 -0
- package/plugin/src/ui/edit.ts +305 -0
- package/plugin/src/ui/forms.ts +209 -0
- package/plugin/src/ui/html.ts +130 -0
- package/plugin/src/ui/layout.ts +198 -0
- package/plugin/src/ui/lists.ts +645 -0
- package/plugin/src/ui/plan.ts +577 -0
- package/plugin/src/ui/run.ts +489 -0
- package/plugin/src/ui/server.ts +684 -0
- package/plugin/src/validators.d.ts +13 -0
- package/plugin/src/validators.js +1 -0
- package/plugin/src/views.ts +839 -0
- package/LICENSE +0 -21
- package/README.md +0 -294
- package/bin/checkmate.js +0 -13
- package/dist/ai/client.d.ts +0 -64
- package/dist/ai/client.d.ts.map +0 -1
- package/dist/ai/client.js +0 -313
- package/dist/ai/client.js.map +0 -1
- package/dist/ai/message-handler.d.ts +0 -9
- package/dist/ai/message-handler.d.ts.map +0 -1
- package/dist/ai/message-handler.js +0 -50
- package/dist/ai/message-handler.js.map +0 -1
- package/dist/ai/message-history.d.ts +0 -17
- package/dist/ai/message-history.d.ts.map +0 -1
- package/dist/ai/message-history.js +0 -45
- package/dist/ai/message-history.js.map +0 -1
- package/dist/ai/prompts.d.ts +0 -4
- package/dist/ai/prompts.d.ts.map +0 -1
- package/dist/ai/prompts.js +0 -24
- package/dist/ai/prompts.js.map +0 -1
- package/dist/ai/rate-limit-policy.d.ts +0 -9
- package/dist/ai/rate-limit-policy.d.ts.map +0 -1
- package/dist/ai/rate-limit-policy.js +0 -34
- package/dist/ai/rate-limit-policy.js.map +0 -1
- package/dist/ai/tool-response-handler.d.ts +0 -16
- package/dist/ai/tool-response-handler.d.ts.map +0 -1
- package/dist/ai/tool-response-handler.js +0 -85
- package/dist/ai/tool-response-handler.js.map +0 -1
- package/dist/ai/turn-processor.d.ts +0 -34
- package/dist/ai/turn-processor.d.ts.map +0 -1
- package/dist/ai/turn-processor.js +0 -107
- package/dist/ai/turn-processor.js.map +0 -1
- package/dist/api/allocate-run-identity.d.ts +0 -8
- package/dist/api/allocate-run-identity.d.ts.map +0 -1
- package/dist/api/allocate-run-identity.js +0 -10
- package/dist/api/allocate-run-identity.js.map +0 -1
- package/dist/api/execute-prepared-run.d.ts +0 -22
- package/dist/api/execute-prepared-run.d.ts.map +0 -1
- package/dist/api/execute-prepared-run.js +0 -184
- package/dist/api/execute-prepared-run.js.map +0 -1
- package/dist/api/index.d.ts +0 -25
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/index.js +0 -69
- package/dist/api/index.js.map +0 -1
- package/dist/api/prepare-run.d.ts +0 -44
- package/dist/api/prepare-run.d.ts.map +0 -1
- package/dist/api/prepare-run.js +0 -261
- package/dist/api/prepare-run.js.map +0 -1
- package/dist/cli/diagnostic-relay.d.ts +0 -6
- package/dist/cli/diagnostic-relay.d.ts.map +0 -1
- package/dist/cli/diagnostic-relay.js +0 -6
- package/dist/cli/diagnostic-relay.js.map +0 -1
- package/dist/cli/main.d.ts +0 -21
- package/dist/cli/main.d.ts.map +0 -1
- package/dist/cli/main.js +0 -166
- package/dist/cli/main.js.map +0 -1
- package/dist/cli/protocol.d.ts +0 -57
- package/dist/cli/protocol.d.ts.map +0 -1
- package/dist/cli/protocol.js +0 -116
- package/dist/cli/protocol.js.map +0 -1
- package/dist/cli/reconcile-result.d.ts +0 -13
- package/dist/cli/reconcile-result.d.ts.map +0 -1
- package/dist/cli/reconcile-result.js +0 -300
- package/dist/cli/reconcile-result.js.map +0 -1
- package/dist/cli/run-parent.d.ts +0 -37
- package/dist/cli/run-parent.d.ts.map +0 -1
- package/dist/cli/run-parent.js +0 -491
- package/dist/cli/run-parent.js.map +0 -1
- package/dist/cli/signals.d.ts +0 -36
- package/dist/cli/signals.d.ts.map +0 -1
- package/dist/cli/signals.js +0 -85
- package/dist/cli/signals.js.map +0 -1
- package/dist/cli/static-commands.d.ts +0 -11
- package/dist/cli/static-commands.d.ts.map +0 -1
- package/dist/cli/static-commands.js +0 -83
- package/dist/cli/static-commands.js.map +0 -1
- package/dist/cli/worker-stream-guard.d.ts +0 -20
- package/dist/cli/worker-stream-guard.d.ts.map +0 -1
- package/dist/cli/worker-stream-guard.js +0 -41
- package/dist/cli/worker-stream-guard.js.map +0 -1
- package/dist/cli/worker.d.ts +0 -3
- package/dist/cli/worker.d.ts.map +0 -1
- package/dist/cli/worker.js +0 -111
- package/dist/cli/worker.js.map +0 -1
- package/dist/config/ingestion.d.ts +0 -58
- package/dist/config/ingestion.d.ts.map +0 -1
- package/dist/config/ingestion.js +0 -283
- package/dist/config/ingestion.js.map +0 -1
- package/dist/config/manifest.d.ts +0 -20
- package/dist/config/manifest.d.ts.map +0 -1
- package/dist/config/manifest.js +0 -91
- package/dist/config/manifest.js.map +0 -1
- package/dist/config/model-egress.d.ts +0 -10
- package/dist/config/model-egress.d.ts.map +0 -1
- package/dist/config/model-egress.js +0 -62
- package/dist/config/model-egress.js.map +0 -1
- package/dist/config/package-resolution.d.ts +0 -4
- package/dist/config/package-resolution.d.ts.map +0 -1
- package/dist/config/package-resolution.js +0 -24
- package/dist/config/package-resolution.js.map +0 -1
- package/dist/config/policy.d.ts +0 -44
- package/dist/config/policy.d.ts.map +0 -1
- package/dist/config/policy.js +0 -141
- package/dist/config/policy.js.map +0 -1
- package/dist/config/record.d.ts +0 -4
- package/dist/config/record.d.ts.map +0 -1
- package/dist/config/record.js +0 -10
- package/dist/config/record.js.map +0 -1
- package/dist/config/secrets.d.ts +0 -11
- package/dist/config/secrets.d.ts.map +0 -1
- package/dist/config/secrets.js +0 -42
- package/dist/config/secrets.js.map +0 -1
- package/dist/contracts/diagnostics.d.ts +0 -4
- package/dist/contracts/diagnostics.d.ts.map +0 -1
- package/dist/contracts/diagnostics.js +0 -31
- package/dist/contracts/diagnostics.js.map +0 -1
- package/dist/contracts/serialize.d.ts +0 -2
- package/dist/contracts/serialize.d.ts.map +0 -1
- package/dist/contracts/serialize.js +0 -90
- package/dist/contracts/serialize.js.map +0 -1
- package/dist/contracts/types.d.ts +0 -316
- package/dist/contracts/types.d.ts.map +0 -1
- package/dist/contracts/types.js +0 -74
- package/dist/contracts/types.js.map +0 -1
- package/dist/contracts/validator.d.ts +0 -8
- package/dist/contracts/validator.d.ts.map +0 -1
- package/dist/contracts/validator.js +0 -71
- package/dist/contracts/validator.js.map +0 -1
- package/dist/driver.d.ts +0 -97
- package/dist/driver.d.ts.map +0 -1
- package/dist/driver.js +0 -24
- package/dist/driver.js.map +0 -1
- package/dist/drivers/descriptor.d.ts +0 -15
- package/dist/drivers/descriptor.d.ts.map +0 -1
- package/dist/drivers/descriptor.js +0 -122
- package/dist/drivers/descriptor.js.map +0 -1
- package/dist/drivers/loader.d.ts +0 -18
- package/dist/drivers/loader.d.ts.map +0 -1
- package/dist/drivers/loader.js +0 -97
- package/dist/drivers/loader.js.map +0 -1
- package/dist/drivers/web/checkmate-driver.json +0 -47
- package/dist/drivers/web/index.d.ts +0 -6
- package/dist/drivers/web/index.d.ts.map +0 -1
- package/dist/drivers/web/index.js +0 -27
- package/dist/drivers/web/index.js.map +0 -1
- package/dist/drivers/web/session.d.ts +0 -25
- package/dist/drivers/web/session.d.ts.map +0 -1
- package/dist/drivers/web/session.js +0 -132
- package/dist/drivers/web/session.js.map +0 -1
- package/dist/drivers/web/tools/network-request-recorder.d.ts +0 -46
- package/dist/drivers/web/tools/network-request-recorder.d.ts.map +0 -1
- package/dist/drivers/web/tools/network-request-recorder.js +0 -164
- package/dist/drivers/web/tools/network-request-recorder.js.map +0 -1
- package/dist/drivers/web/tools/screenshot-service.d.ts +0 -10
- package/dist/drivers/web/tools/screenshot-service.d.ts.map +0 -1
- package/dist/drivers/web/tools/screenshot-service.js +0 -23
- package/dist/drivers/web/tools/screenshot-service.js.map +0 -1
- package/dist/drivers/web/tools/snapshot-filter/index.d.ts +0 -4
- package/dist/drivers/web/tools/snapshot-filter/index.d.ts.map +0 -1
- package/dist/drivers/web/tools/snapshot-filter/index.js +0 -4
- package/dist/drivers/web/tools/snapshot-filter/index.js.map +0 -1
- package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.d.ts +0 -15
- package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.d.ts.map +0 -1
- package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.js +0 -99
- package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.js.map +0 -1
- package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.d.ts +0 -5
- package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.d.ts.map +0 -1
- package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.js +0 -56
- package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.js.map +0 -1
- package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.d.ts +0 -3
- package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.d.ts.map +0 -1
- package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.js +0 -85
- package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.js.map +0 -1
- package/dist/drivers/web/tools/snapshot-service.d.ts +0 -31
- package/dist/drivers/web/tools/snapshot-service.d.ts.map +0 -1
- package/dist/drivers/web/tools/snapshot-service.js +0 -96
- package/dist/drivers/web/tools/snapshot-service.js.map +0 -1
- package/dist/drivers/web/tools/tool.d.ts +0 -76
- package/dist/drivers/web/tools/tool.d.ts.map +0 -1
- package/dist/drivers/web/tools/tool.js +0 -562
- package/dist/drivers/web/tools/tool.js.map +0 -1
- package/dist/drivers/web/tools/transient-state-tracker.d.ts +0 -26
- package/dist/drivers/web/tools/transient-state-tracker.d.ts.map +0 -1
- package/dist/drivers/web/tools/transient-state-tracker.js +0 -301
- package/dist/drivers/web/tools/transient-state-tracker.js.map +0 -1
- package/dist/drivers/web/tools/types.d.ts +0 -11
- package/dist/drivers/web/tools/types.d.ts.map +0 -1
- package/dist/drivers/web/tools/types.js +0 -2
- package/dist/drivers/web/tools/types.js.map +0 -1
- package/dist/evidence/atomic-file.d.ts +0 -23
- package/dist/evidence/atomic-file.d.ts.map +0 -1
- package/dist/evidence/atomic-file.js +0 -104
- package/dist/evidence/atomic-file.js.map +0 -1
- package/dist/evidence/layout.d.ts +0 -30
- package/dist/evidence/layout.d.ts.map +0 -1
- package/dist/evidence/layout.js +0 -152
- package/dist/evidence/layout.js.map +0 -1
- package/dist/evidence/retention.d.ts +0 -5
- package/dist/evidence/retention.d.ts.map +0 -1
- package/dist/evidence/retention.js +0 -10
- package/dist/evidence/retention.js.map +0 -1
- package/dist/evidence/store.d.ts +0 -101
- package/dist/evidence/store.d.ts.map +0 -1
- package/dist/evidence/store.js +0 -375
- package/dist/evidence/store.js.map +0 -1
- package/dist/evidence/terminal-finalizer.d.ts +0 -19
- package/dist/evidence/terminal-finalizer.d.ts.map +0 -1
- package/dist/evidence/terminal-finalizer.js +0 -56
- package/dist/evidence/terminal-finalizer.js.map +0 -1
- package/dist/index.d.ts +0 -9
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -12
- package/dist/index.js.map +0 -1
- package/dist/logging/invocation-logger.d.ts +0 -4
- package/dist/logging/invocation-logger.d.ts.map +0 -1
- package/dist/logging/invocation-logger.js +0 -25
- package/dist/logging/invocation-logger.js.map +0 -1
- package/dist/logging/level-logger.d.ts +0 -11
- package/dist/logging/level-logger.d.ts.map +0 -1
- package/dist/logging/level-logger.js +0 -82
- package/dist/logging/level-logger.js.map +0 -1
- package/dist/logging/types.d.ts +0 -8
- package/dist/logging/types.d.ts.map +0 -1
- package/dist/logging/types.js +0 -7
- package/dist/logging/types.js.map +0 -1
- package/dist/redaction/diagnostic-sanitizer.d.ts +0 -7
- package/dist/redaction/diagnostic-sanitizer.d.ts.map +0 -1
- package/dist/redaction/diagnostic-sanitizer.js +0 -42
- package/dist/redaction/diagnostic-sanitizer.js.map +0 -1
- package/dist/redaction/redactor.d.ts +0 -40
- package/dist/redaction/redactor.d.ts.map +0 -1
- package/dist/redaction/redactor.js +0 -176
- package/dist/redaction/redactor.js.map +0 -1
- package/dist/redaction/scrub.d.ts +0 -29
- package/dist/redaction/scrub.d.ts.map +0 -1
- package/dist/redaction/scrub.js +0 -54
- package/dist/redaction/scrub.js.map +0 -1
- package/dist/runtime/config.d.ts +0 -16
- package/dist/runtime/config.d.ts.map +0 -1
- package/dist/runtime/config.js +0 -2
- package/dist/runtime/config.js.map +0 -1
- package/dist/runtime/driver-boundary.d.ts +0 -16
- package/dist/runtime/driver-boundary.d.ts.map +0 -1
- package/dist/runtime/driver-boundary.js +0 -71
- package/dist/runtime/driver-boundary.js.map +0 -1
- package/dist/runtime/driver-session.d.ts +0 -9
- package/dist/runtime/driver-session.d.ts.map +0 -1
- package/dist/runtime/driver-session.js +0 -64
- package/dist/runtime/driver-session.js.map +0 -1
- package/dist/runtime/internal-step-evidence.d.ts +0 -30
- package/dist/runtime/internal-step-evidence.d.ts.map +0 -1
- package/dist/runtime/internal-step-evidence.js +0 -82
- package/dist/runtime/internal-step-evidence.js.map +0 -1
- package/dist/runtime/result-adapter.d.ts +0 -9
- package/dist/runtime/result-adapter.d.ts.map +0 -1
- package/dist/runtime/result-adapter.js +0 -77
- package/dist/runtime/result-adapter.js.map +0 -1
- package/dist/runtime/runner.d.ts +0 -43
- package/dist/runtime/runner.d.ts.map +0 -1
- package/dist/runtime/runner.js +0 -72
- package/dist/runtime/runner.js.map +0 -1
- package/dist/runtime/scenario-control.d.ts +0 -40
- package/dist/runtime/scenario-control.d.ts.map +0 -1
- package/dist/runtime/scenario-control.js +0 -94
- package/dist/runtime/scenario-control.js.map +0 -1
- package/dist/runtime/scenario-runner.d.ts +0 -41
- package/dist/runtime/scenario-runner.d.ts.map +0 -1
- package/dist/runtime/scenario-runner.js +0 -274
- package/dist/runtime/scenario-runner.js.map +0 -1
- package/dist/runtime/scenario-state.d.ts +0 -55
- package/dist/runtime/scenario-state.d.ts.map +0 -1
- package/dist/runtime/scenario-state.js +0 -128
- package/dist/runtime/scenario-state.js.map +0 -1
- package/dist/runtime/step-execution.d.ts +0 -27
- package/dist/runtime/step-execution.d.ts.map +0 -1
- package/dist/runtime/step-execution.js +0 -143
- package/dist/runtime/step-execution.js.map +0 -1
- package/dist/runtime/text.d.ts +0 -10
- package/dist/runtime/text.d.ts.map +0 -1
- package/dist/runtime/text.js +0 -25
- package/dist/runtime/text.js.map +0 -1
- package/dist/runtime/types.d.ts +0 -53
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/types.js +0 -2
- package/dist/runtime/types.js.map +0 -1
- package/dist/runtime/usage-tracker.d.ts +0 -37
- package/dist/runtime/usage-tracker.d.ts.map +0 -1
- package/dist/runtime/usage-tracker.js +0 -110
- package/dist/runtime/usage-tracker.js.map +0 -1
- package/dist/tools/define-agent-tool.d.ts +0 -20
- package/dist/tools/define-agent-tool.d.ts.map +0 -1
- package/dist/tools/define-agent-tool.js +0 -30
- package/dist/tools/define-agent-tool.js.map +0 -1
- package/dist/tools/dispatcher.d.ts +0 -19
- package/dist/tools/dispatcher.d.ts.map +0 -1
- package/dist/tools/dispatcher.js +0 -91
- package/dist/tools/dispatcher.js.map +0 -1
- package/dist/tools/loop-detector.d.ts +0 -27
- package/dist/tools/loop-detector.d.ts.map +0 -1
- package/dist/tools/loop-detector.js +0 -76
- package/dist/tools/loop-detector.js.map +0 -1
- package/dist/tools/registry.d.ts +0 -19
- package/dist/tools/registry.d.ts.map +0 -1
- package/dist/tools/registry.js +0 -54
- package/dist/tools/registry.js.map +0 -1
- package/dist/tools/step/result-tool.d.ts +0 -7
- package/dist/tools/step/result-tool.d.ts.map +0 -1
- package/dist/tools/step/result-tool.js +0 -34
- package/dist/tools/step/result-tool.js.map +0 -1
- package/dist/tools/tool-contract.d.ts +0 -3
- package/dist/tools/tool-contract.d.ts.map +0 -1
- package/dist/tools/tool-contract.js +0 -2
- package/dist/tools/tool-contract.js.map +0 -1
- package/dist/tools/types.d.ts +0 -210
- package/dist/tools/types.d.ts.map +0 -1
- package/dist/tools/types.js +0 -4
- package/dist/tools/types.js.map +0 -1
- package/docs/CLI.md +0 -59
- package/docs/CONFIGURATION.md +0 -105
- package/docs/DRIVERS.md +0 -79
- package/docs/EVIDENCE.md +0 -64
- package/schemas/checkmate-config.v1.json +0 -176
- package/schemas/describe-result.v1.json +0 -414
- package/schemas/driver-descriptor.v1.json +0 -66
- package/schemas/run-request.v1.json +0 -67
- package/schemas/run-result.v1.json +0 -333
- package/schemas/validation-result.v1.json +0 -86
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import type { Plan, Result } from './types.ts'
|
|
2
|
+
import type { Store } from './store.ts'
|
|
3
|
+
import type { Approval } from './plans.ts'
|
|
4
|
+
import type { ApprovalLabel } from './views.ts'
|
|
5
|
+
import { requireChoice, requireCondition, revisionReference } from './contracts.ts'
|
|
6
|
+
import { planRevision } from './plans.ts'
|
|
7
|
+
import { outcomeCounts, resultAcceptance, runResult, runState } from './runs.ts'
|
|
8
|
+
import { approvalLabel, evidenceBytes } from './views.ts'
|
|
9
|
+
|
|
10
|
+
type Block =
|
|
11
|
+
| { heading: string; level?: 2 | 3 }
|
|
12
|
+
| { text: string }
|
|
13
|
+
| { table: string[]; rows: string[][] }
|
|
14
|
+
| { list: string[]; numbered?: boolean }
|
|
15
|
+
| { stats: Array<{ label: string; value: string }> }
|
|
16
|
+
| { evidence: Array<EvidenceItem> }
|
|
17
|
+
| { record: unknown }
|
|
18
|
+
type Report = { title: string; note: string; blocks: Block[] }
|
|
19
|
+
type EvidenceItem = { label: string; name: string; mediaType: string; size: number; image?: string; text?: string }
|
|
20
|
+
export type Format = 'md' | 'html' | 'json'
|
|
21
|
+
export type RunReport = {
|
|
22
|
+
result: Result
|
|
23
|
+
sha256: string
|
|
24
|
+
acceptance: { state: ApprovalLabel; latest: Approval | null }
|
|
25
|
+
plan: Pick<Plan, 'planId' | 'revision' | 'objective' | 'requirements' | 'gaps' | 'decisions' | 'assumptions'>
|
|
26
|
+
inline: Record<string, { image?: string; text?: string }>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const formats = ['md', 'html', 'json'] as const
|
|
30
|
+
const maxInlineImage = 512 * 1024
|
|
31
|
+
const maxInlineText = 16 * 1024
|
|
32
|
+
const inlineImages = ['image/png', 'image/jpeg', 'image/webp', 'image/gif']
|
|
33
|
+
const inlineTexts = ['text/plain', 'application/json', 'text/markdown']
|
|
34
|
+
const tones: Record<string, string> = {
|
|
35
|
+
passed: 'pass',
|
|
36
|
+
approved: 'pass',
|
|
37
|
+
completed: 'pass',
|
|
38
|
+
complete: 'pass',
|
|
39
|
+
tested: 'pass',
|
|
40
|
+
resolved: 'pass',
|
|
41
|
+
failed: 'fail',
|
|
42
|
+
rejected: 'fail',
|
|
43
|
+
operational_error: 'fail',
|
|
44
|
+
error: 'warn',
|
|
45
|
+
inconclusive: 'warn',
|
|
46
|
+
interrupted: 'warn',
|
|
47
|
+
partial: 'warn',
|
|
48
|
+
pending: 'warn',
|
|
49
|
+
open: 'warn',
|
|
50
|
+
blocking: 'warn',
|
|
51
|
+
uncovered: 'warn',
|
|
52
|
+
blocked: 'muted',
|
|
53
|
+
not_run: 'muted',
|
|
54
|
+
not_checked: 'muted',
|
|
55
|
+
deferred: 'muted',
|
|
56
|
+
not_executed: 'muted',
|
|
57
|
+
}
|
|
58
|
+
const none = '—'
|
|
59
|
+
const join = (values: string[]) => (values.length ? values.join(', ') : none)
|
|
60
|
+
|
|
61
|
+
function markdownText(value: string) {
|
|
62
|
+
return value
|
|
63
|
+
.replace(/\\/g, '\\\\')
|
|
64
|
+
.replace(/([|[\]*_`])/g, '\\$1')
|
|
65
|
+
.replace(/</g, '<')
|
|
66
|
+
.replace(/>/g, '>')
|
|
67
|
+
.replace(/\r?\n/g, ' ')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fence(text: string, language = '') {
|
|
71
|
+
const ticks = '`'.repeat(Math.max(3, ...[...text.matchAll(/`+/g)].map((match) => match[0].length + 1)))
|
|
72
|
+
return `${ticks}${language}\n${text}\n${ticks}`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function jsonFence(value: unknown) {
|
|
76
|
+
return fence(JSON.stringify(value, null, 2), 'json')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function indent(text: string) {
|
|
80
|
+
return text
|
|
81
|
+
.split('\n')
|
|
82
|
+
.map((line) => ` ${line}`)
|
|
83
|
+
.join('\n')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function markdown(report: Report) {
|
|
87
|
+
const parts = [`# ${markdownText(report.title)}`, markdownText(report.note)]
|
|
88
|
+
for (const block of report.blocks) {
|
|
89
|
+
if ('heading' in block) parts.push(`${'#'.repeat(block.level ?? 2)} ${markdownText(block.heading)}`)
|
|
90
|
+
else if ('text' in block) parts.push(markdownText(block.text))
|
|
91
|
+
else if ('table' in block)
|
|
92
|
+
parts.push(
|
|
93
|
+
[
|
|
94
|
+
`| ${block.table.map(markdownText).join(' | ')} |`,
|
|
95
|
+
`| ${block.table.map(() => '---').join(' | ')} |`,
|
|
96
|
+
...block.rows.map((row) => `| ${row.map(markdownText).join(' | ')} |`),
|
|
97
|
+
].join('\n')
|
|
98
|
+
)
|
|
99
|
+
else if ('list' in block)
|
|
100
|
+
parts.push(
|
|
101
|
+
block.list.map((item, i) => `${block.numbered ? `${i + 1}.` : '-'} ${markdownText(item)}`).join('\n')
|
|
102
|
+
)
|
|
103
|
+
else if ('stats' in block)
|
|
104
|
+
parts.push(
|
|
105
|
+
block.stats.map((stat) => `- **${markdownText(stat.label)}:** ${markdownText(stat.value)}`).join('\n')
|
|
106
|
+
)
|
|
107
|
+
else if ('evidence' in block)
|
|
108
|
+
parts.push(
|
|
109
|
+
block.evidence
|
|
110
|
+
.map((item) =>
|
|
111
|
+
[
|
|
112
|
+
`- ${markdownText(item.label)}: ${markdownText(item.name)} (${item.mediaType}, ${item.size} bytes)`,
|
|
113
|
+
...(item.text === undefined ? [] : ['', indent(fence(item.text))]),
|
|
114
|
+
].join('\n')
|
|
115
|
+
)
|
|
116
|
+
.join('\n')
|
|
117
|
+
)
|
|
118
|
+
else
|
|
119
|
+
parts.push(`<details>\n<summary>Complete JSON record</summary>\n\n${jsonFence(block.record)}\n\n</details>`)
|
|
120
|
+
}
|
|
121
|
+
return parts.join('\n\n') + '\n'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function escapeHtml(value: string) {
|
|
125
|
+
return value.replace(
|
|
126
|
+
/[&<>"']/g,
|
|
127
|
+
(character) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[character]!
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const styles = `
|
|
132
|
+
:root{--ink:#1b2130;--muted:#5d6678;--line:#dde2ea;--soft:#f4f6f9;--accent:#3b4fd8;
|
|
133
|
+
--pass:#1f7a4d;--pass-bg:#e5f4ec;--fail:#b42318;--fail-bg:#fdecea;--warn:#9a5b00;--warn-bg:#fdf3e1;--muted-bg:#eef0f4}
|
|
134
|
+
*{box-sizing:border-box}
|
|
135
|
+
body{font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;color:var(--ink);background:#fff;margin:0}
|
|
136
|
+
main{max-width:72rem;margin:0 auto;padding:2.5rem 1.5rem 4rem}
|
|
137
|
+
header{border-bottom:3px solid var(--accent);padding-bottom:1rem;margin-bottom:1.5rem}
|
|
138
|
+
h1{font-size:1.8rem;line-height:1.2;margin:0 0 .4rem}
|
|
139
|
+
h2{font-size:1.2rem;margin:2.2rem 0 .7rem;padding-bottom:.3rem;border-bottom:1px solid var(--line)}
|
|
140
|
+
h3{font-size:1rem;margin:1.4rem 0 .5rem}
|
|
141
|
+
.note{color:var(--muted);margin:0}
|
|
142
|
+
table{border-collapse:collapse;width:100%;margin:.4rem 0 1.2rem;font-size:.92rem}
|
|
143
|
+
th,td{padding:.45rem .6rem;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);overflow-wrap:break-word}
|
|
144
|
+
.token{word-break:break-all}
|
|
145
|
+
th{font-weight:600;background:var(--soft);font-size:.82rem;text-transform:uppercase;letter-spacing:.03em;color:var(--muted)}
|
|
146
|
+
tbody tr:nth-child(even) td{background:#fafbfc}
|
|
147
|
+
.pill{display:inline-block;padding:.05rem .5rem;border-radius:999px;font-size:.8rem;font-weight:600;white-space:nowrap}
|
|
148
|
+
.pass{color:var(--pass);background:var(--pass-bg)}.fail{color:var(--fail);background:var(--fail-bg)}
|
|
149
|
+
.warn{color:var(--warn);background:var(--warn-bg)}.muted{color:var(--muted);background:var(--muted-bg)}
|
|
150
|
+
.stats{display:grid;grid-template-columns:repeat(auto-fill,minmax(9.5rem,1fr));gap:.6rem;margin:1rem 0 1.5rem}
|
|
151
|
+
.stat{border:1px solid var(--line);border-radius:.5rem;padding:.6rem .8rem}
|
|
152
|
+
.stat b{display:block;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:600}
|
|
153
|
+
.stat span{font-size:1.05rem;font-weight:600;overflow-wrap:anywhere}
|
|
154
|
+
.evidence{display:grid;grid-template-columns:repeat(auto-fill,minmax(16rem,1fr));gap:1rem;margin:.5rem 0 1.5rem}
|
|
155
|
+
figure{margin:0;border:1px solid var(--line);border-radius:.5rem;padding:.6rem;break-inside:avoid}
|
|
156
|
+
figure img{display:block;max-width:100%;height:auto;border-radius:.3rem}
|
|
157
|
+
figcaption{font-size:.82rem;color:var(--muted);margin-top:.4rem;overflow-wrap:anywhere}
|
|
158
|
+
pre{white-space:pre-wrap;overflow-wrap:anywhere;background:var(--soft);padding:.8rem;border-radius:.4rem;font-size:.82rem;margin:0}
|
|
159
|
+
details{margin-top:2.5rem}summary{cursor:pointer;font-weight:600}
|
|
160
|
+
@page{size:A4;margin:16mm 14mm}
|
|
161
|
+
@media print{
|
|
162
|
+
body{font-size:10.5pt}main{max-width:none;padding:0}
|
|
163
|
+
h2{break-after:avoid}h3{break-after:avoid}tr,figure,.stat{break-inside:avoid}thead{display:table-header-group}
|
|
164
|
+
tbody tr:nth-child(even) td{background:none}details{display:none}
|
|
165
|
+
*{-webkit-print-color-adjust:exact;print-color-adjust:exact}}
|
|
166
|
+
`
|
|
167
|
+
|
|
168
|
+
function cell(value: string) {
|
|
169
|
+
const tone = tones[value]
|
|
170
|
+
if (tone) return `<span class="pill ${tone}">${escapeHtml(value)}</span>`
|
|
171
|
+
return value
|
|
172
|
+
.split(/(\s+)/)
|
|
173
|
+
.map((part) => (part.length > 24 ? `<span class="token">${escapeHtml(part)}</span>` : escapeHtml(part)))
|
|
174
|
+
.join('')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function figure(item: EvidenceItem) {
|
|
178
|
+
const body = item.image
|
|
179
|
+
? `<img src="${item.image}" alt="${escapeHtml(item.name)}">`
|
|
180
|
+
: item.text !== undefined
|
|
181
|
+
? `<pre>${escapeHtml(item.text)}</pre>`
|
|
182
|
+
: ''
|
|
183
|
+
return `<figure>${body}<figcaption>${escapeHtml(item.label)} · ${escapeHtml(item.name)} · ${escapeHtml(item.mediaType)} · ${item.size} bytes</figcaption></figure>`
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function html(report: Report) {
|
|
187
|
+
const parts = [
|
|
188
|
+
`<header><h1>${escapeHtml(report.title)}</h1><p class="note">${escapeHtml(report.note)}</p></header>`,
|
|
189
|
+
]
|
|
190
|
+
for (const block of report.blocks) {
|
|
191
|
+
if ('heading' in block) parts.push(`<h${block.level ?? 2}>${escapeHtml(block.heading)}</h${block.level ?? 2}>`)
|
|
192
|
+
else if ('text' in block) parts.push(`<p>${escapeHtml(block.text)}</p>`)
|
|
193
|
+
else if ('table' in block)
|
|
194
|
+
parts.push(
|
|
195
|
+
`<table><thead><tr>${block.table.map((head) => `<th>${escapeHtml(head)}</th>`).join('')}</tr></thead><tbody>${block.rows
|
|
196
|
+
.map((row) => `<tr>${row.map((value) => `<td>${cell(value)}</td>`).join('')}</tr>`)
|
|
197
|
+
.join('')}</tbody></table>`
|
|
198
|
+
)
|
|
199
|
+
else if ('list' in block) {
|
|
200
|
+
const tag = block.numbered ? 'ol' : 'ul'
|
|
201
|
+
parts.push(`<${tag}>${block.list.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</${tag}>`)
|
|
202
|
+
} else if ('stats' in block)
|
|
203
|
+
parts.push(
|
|
204
|
+
`<div class="stats">${block.stats
|
|
205
|
+
.map(
|
|
206
|
+
(stat) =>
|
|
207
|
+
`<div class="stat"><b>${escapeHtml(stat.label)}</b><span>${cell(stat.value)}</span></div>`
|
|
208
|
+
)
|
|
209
|
+
.join('')}</div>`
|
|
210
|
+
)
|
|
211
|
+
else if ('evidence' in block) parts.push(`<div class="evidence">${block.evidence.map(figure).join('')}</div>`)
|
|
212
|
+
else
|
|
213
|
+
parts.push(
|
|
214
|
+
`<details><summary>Complete JSON record</summary><pre>${escapeHtml(JSON.stringify(block.record, null, 2))}</pre></details>`
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
return (
|
|
218
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">' +
|
|
219
|
+
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:">` +
|
|
220
|
+
`<title>${escapeHtml(report.title)}</title><style>${styles.trim()}</style></head><body><main>` +
|
|
221
|
+
parts.join('') +
|
|
222
|
+
'</main></body></html>\n'
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function runDocument(report: RunReport): Report {
|
|
227
|
+
const { result, sha256, acceptance, plan } = report
|
|
228
|
+
const evidence = new Map(result.evidence.map((entry) => [entry.id, entry]))
|
|
229
|
+
const files = (ids: string[]) => join(ids.map((id) => `${evidence.get(id)?.name ?? id} (${id})`))
|
|
230
|
+
const counts = outcomeCounts(result.tests.filter((test) => result.selectedTestIds.includes(test.testId)))
|
|
231
|
+
const assertions = result.tests.flatMap((test) => test.assertions)
|
|
232
|
+
const blocks: Block[] = [
|
|
233
|
+
{
|
|
234
|
+
stats: [
|
|
235
|
+
{ label: 'Completion', value: result.completion },
|
|
236
|
+
{ label: 'Coverage', value: result.coverage.status },
|
|
237
|
+
{ label: 'Acceptance', value: acceptance.state },
|
|
238
|
+
{ label: 'Selected tests', value: String(result.selectedTestIds.length) },
|
|
239
|
+
...Object.entries(counts).map(([outcome, count]) => ({ label: outcome, value: String(count) })),
|
|
240
|
+
{
|
|
241
|
+
label: 'Assertions passed',
|
|
242
|
+
value: `${assertions.filter((assertion) => assertion.result === 'passed').length} of ${assertions.length}`,
|
|
243
|
+
},
|
|
244
|
+
{ label: 'Findings', value: String(result.findings.length) },
|
|
245
|
+
{ label: 'New gaps', value: String(result.newGaps.length) },
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
{ heading: 'Summary' },
|
|
249
|
+
{
|
|
250
|
+
table: ['Field', 'Value'],
|
|
251
|
+
rows: [
|
|
252
|
+
['Run', result.runId],
|
|
253
|
+
['Objective', plan.objective],
|
|
254
|
+
['Application build', result.applicationBuild],
|
|
255
|
+
['Plan', `${result.plan.planId}@${result.plan.revision}`],
|
|
256
|
+
['Target profile', `${result.target.targetId}@${result.target.revision}`],
|
|
257
|
+
['Started', result.startedAt],
|
|
258
|
+
['Finished', result.finishedAt],
|
|
259
|
+
['Attempts', String(result.attempts)],
|
|
260
|
+
['Retest of', result.retestOf ?? none],
|
|
261
|
+
['Cleanup', `${result.cleanup.status}: ${result.cleanup.reason}`],
|
|
262
|
+
['Result digest', sha256],
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
{ heading: 'Acceptance' },
|
|
266
|
+
acceptance.latest
|
|
267
|
+
? {
|
|
268
|
+
table: ['Verdict', 'Actor', 'At', 'Rationale', 'Digest'],
|
|
269
|
+
rows: [
|
|
270
|
+
[
|
|
271
|
+
acceptance.latest.verdict,
|
|
272
|
+
acceptance.latest.actor,
|
|
273
|
+
acceptance.latest.at,
|
|
274
|
+
acceptance.latest.rationale,
|
|
275
|
+
acceptance.latest.sha256,
|
|
276
|
+
],
|
|
277
|
+
],
|
|
278
|
+
}
|
|
279
|
+
: {
|
|
280
|
+
text:
|
|
281
|
+
acceptance.state === 'not_required'
|
|
282
|
+
? 'The target policy does not require acceptance.'
|
|
283
|
+
: 'No decision yet.',
|
|
284
|
+
},
|
|
285
|
+
{ heading: 'Tests' },
|
|
286
|
+
{
|
|
287
|
+
table: ['Test', 'Outcome', 'Assertions', 'Reason'],
|
|
288
|
+
rows: result.tests
|
|
289
|
+
.filter((test) => result.selectedTestIds.includes(test.testId))
|
|
290
|
+
.map((test) => [
|
|
291
|
+
test.testId,
|
|
292
|
+
test.outcome,
|
|
293
|
+
`${test.assertions.filter((assertion) => assertion.result === 'passed').length} of ${test.assertions.length} passed`,
|
|
294
|
+
test.reason ?? none,
|
|
295
|
+
]),
|
|
296
|
+
},
|
|
297
|
+
{ heading: 'Assertions' },
|
|
298
|
+
{
|
|
299
|
+
table: ['Assertion', 'Result', 'Check kind', 'Check', 'Observed', 'Reason', 'Evidence'],
|
|
300
|
+
rows: result.tests.flatMap((test) =>
|
|
301
|
+
test.assertions.map((assertion) => [
|
|
302
|
+
`${test.testId}.${assertion.assertionId}`,
|
|
303
|
+
assertion.result,
|
|
304
|
+
assertion.checkKind,
|
|
305
|
+
assertion.check,
|
|
306
|
+
assertion.observed ?? none,
|
|
307
|
+
assertion.reason ?? none,
|
|
308
|
+
files(assertion.evidenceIds),
|
|
309
|
+
])
|
|
310
|
+
),
|
|
311
|
+
},
|
|
312
|
+
{ heading: 'Findings' },
|
|
313
|
+
]
|
|
314
|
+
if (!result.findings.length) blocks.push({ text: 'No findings.' })
|
|
315
|
+
for (const finding of result.findings) {
|
|
316
|
+
blocks.push({ heading: `${finding.id} · ${finding.type} · ${finding.severity}: ${finding.title}`, level: 3 })
|
|
317
|
+
blocks.push({
|
|
318
|
+
table: ['Field', 'Value'],
|
|
319
|
+
rows: [
|
|
320
|
+
['Assertions', join(finding.assertionIds)],
|
|
321
|
+
['Requirements', join(finding.requirementIds)],
|
|
322
|
+
['Expected', finding.expected],
|
|
323
|
+
['Observed', finding.observed],
|
|
324
|
+
['Evidence', files(finding.evidenceIds)],
|
|
325
|
+
],
|
|
326
|
+
})
|
|
327
|
+
blocks.push({ list: finding.reproduction, numbered: true })
|
|
328
|
+
}
|
|
329
|
+
blocks.push({ heading: 'New gaps' })
|
|
330
|
+
if (!result.newGaps.length) blocks.push({ text: 'No new gaps.' })
|
|
331
|
+
else
|
|
332
|
+
blocks.push({
|
|
333
|
+
table: ['Gap', 'Severity', 'Question', 'Consequence', 'Requirements', 'Tests'],
|
|
334
|
+
rows: result.newGaps.map((gap) => [
|
|
335
|
+
gap.id,
|
|
336
|
+
gap.severity,
|
|
337
|
+
gap.question,
|
|
338
|
+
gap.consequence,
|
|
339
|
+
join(gap.requirementIds),
|
|
340
|
+
join(gap.testIds),
|
|
341
|
+
]),
|
|
342
|
+
})
|
|
343
|
+
blocks.push({ heading: `Plan gaps and decisions (${plan.planId}@${plan.revision})` })
|
|
344
|
+
if (!plan.gaps.length) blocks.push({ text: 'The plan has no gaps.' })
|
|
345
|
+
else
|
|
346
|
+
blocks.push({
|
|
347
|
+
table: ['Gap', 'Severity', 'Status', 'Question', 'Decision', 'Value', 'Actor', 'Rationale'],
|
|
348
|
+
rows: plan.gaps.map((gap) => {
|
|
349
|
+
const decision = plan.decisions.find((item) => item.id === gap.decisionId)
|
|
350
|
+
return [
|
|
351
|
+
gap.id,
|
|
352
|
+
gap.severity,
|
|
353
|
+
gap.status,
|
|
354
|
+
gap.question,
|
|
355
|
+
decision?.kind ?? none,
|
|
356
|
+
decision?.value ?? none,
|
|
357
|
+
decision?.actor ?? none,
|
|
358
|
+
decision?.rationale ?? none,
|
|
359
|
+
]
|
|
360
|
+
}),
|
|
361
|
+
})
|
|
362
|
+
if (plan.assumptions.length)
|
|
363
|
+
blocks.push({
|
|
364
|
+
list: plan.assumptions.map((assumption) => `Assumption ${assumption.decisionId}: ${assumption.text}`),
|
|
365
|
+
})
|
|
366
|
+
blocks.push({ heading: 'Evidence' })
|
|
367
|
+
if (!result.evidence.length) blocks.push({ text: 'No evidence.' })
|
|
368
|
+
else {
|
|
369
|
+
blocks.push({
|
|
370
|
+
evidence: result.evidence.map((entry) => ({
|
|
371
|
+
label: entry.assertionId ? `${entry.testId}.${entry.assertionId}` : entry.testId,
|
|
372
|
+
name: entry.name,
|
|
373
|
+
mediaType: entry.mediaType,
|
|
374
|
+
size: entry.size,
|
|
375
|
+
...report.inline[entry.id],
|
|
376
|
+
})),
|
|
377
|
+
})
|
|
378
|
+
blocks.push({
|
|
379
|
+
table: ['Evidence', 'Assertion', 'Kind', 'Name', 'Media type', 'Size', 'SHA-256'],
|
|
380
|
+
rows: result.evidence.map((entry) => [
|
|
381
|
+
entry.id,
|
|
382
|
+
entry.assertionId ? `${entry.testId}.${entry.assertionId}` : entry.testId,
|
|
383
|
+
entry.kind,
|
|
384
|
+
entry.name,
|
|
385
|
+
entry.mediaType,
|
|
386
|
+
String(entry.size),
|
|
387
|
+
entry.sha256,
|
|
388
|
+
]),
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
blocks.push(
|
|
392
|
+
{ heading: 'Coverage' },
|
|
393
|
+
{
|
|
394
|
+
table: ['Requirement', 'Text', 'Status', 'Tests', 'Reason'],
|
|
395
|
+
rows: result.coverage.requirements.map((row) => [
|
|
396
|
+
row.requirementId,
|
|
397
|
+
plan.requirements.find((requirement) => requirement.id === row.requirementId)?.text ?? none,
|
|
398
|
+
row.status,
|
|
399
|
+
join(row.testIds),
|
|
400
|
+
row.reason ?? none,
|
|
401
|
+
]),
|
|
402
|
+
},
|
|
403
|
+
{ record: result }
|
|
404
|
+
)
|
|
405
|
+
return {
|
|
406
|
+
title: `Test report: ${result.runId}`,
|
|
407
|
+
note: `${result.plan.planId}@${result.plan.revision} on build ${result.applicationBuild}. Generated from the Checkmate database. Completion, test outcomes, and coverage are separate.`,
|
|
408
|
+
blocks,
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function planReport(plan: Plan, sha256: string): Report {
|
|
413
|
+
const coverage = new Map(plan.coverage.map((row) => [row.requirementId, row.status]))
|
|
414
|
+
const blocks: Block[] = [
|
|
415
|
+
{
|
|
416
|
+
table: ['Field', 'Value'],
|
|
417
|
+
rows: [
|
|
418
|
+
['Plan', plan.planId],
|
|
419
|
+
['Revision', String(plan.revision)],
|
|
420
|
+
['Previous revision', plan.previousRevision === null ? none : String(plan.previousRevision)],
|
|
421
|
+
['Target', `${plan.target.targetId}@${plan.target.revision}`],
|
|
422
|
+
['Objective', plan.objective],
|
|
423
|
+
['Plan digest', sha256],
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
{ heading: 'Requirements' },
|
|
427
|
+
{
|
|
428
|
+
table: ['Requirement', 'Text', 'Source', 'Coverage'],
|
|
429
|
+
rows: plan.requirements.map((requirement) => [
|
|
430
|
+
requirement.id,
|
|
431
|
+
requirement.text,
|
|
432
|
+
requirement.source,
|
|
433
|
+
coverage.get(requirement.id) ?? none,
|
|
434
|
+
]),
|
|
435
|
+
},
|
|
436
|
+
{ heading: 'Tests' },
|
|
437
|
+
{
|
|
438
|
+
table: ['Test', 'Title', 'Readiness', 'Requirements', 'Depends on', 'Preconditions', 'Action'],
|
|
439
|
+
rows: plan.tests.map((test) => [
|
|
440
|
+
test.id,
|
|
441
|
+
test.title,
|
|
442
|
+
test.readiness,
|
|
443
|
+
join(test.requirementIds),
|
|
444
|
+
join(test.dependsOn),
|
|
445
|
+
join(test.preconditions),
|
|
446
|
+
test.action,
|
|
447
|
+
]),
|
|
448
|
+
},
|
|
449
|
+
{ heading: 'Assertions' },
|
|
450
|
+
{
|
|
451
|
+
table: ['Assertion', 'Check kind', 'Check'],
|
|
452
|
+
rows: plan.tests.flatMap((test) =>
|
|
453
|
+
test.assertions.map((assertion) => [`${test.id}.${assertion.id}`, assertion.checkKind, assertion.check])
|
|
454
|
+
),
|
|
455
|
+
},
|
|
456
|
+
{ heading: 'Gaps' },
|
|
457
|
+
]
|
|
458
|
+
if (!plan.gaps.length) blocks.push({ text: 'No gaps.' })
|
|
459
|
+
else
|
|
460
|
+
blocks.push({
|
|
461
|
+
table: ['Gap', 'Severity', 'Status', 'Question', 'Consequence', 'Decision'],
|
|
462
|
+
rows: plan.gaps.map((gap) => [
|
|
463
|
+
gap.id,
|
|
464
|
+
gap.severity,
|
|
465
|
+
gap.status,
|
|
466
|
+
gap.question,
|
|
467
|
+
gap.consequence,
|
|
468
|
+
gap.decisionId ?? none,
|
|
469
|
+
]),
|
|
470
|
+
})
|
|
471
|
+
blocks.push({ heading: 'Decisions' })
|
|
472
|
+
if (!plan.decisions.length) blocks.push({ text: 'No decisions.' })
|
|
473
|
+
else
|
|
474
|
+
blocks.push({
|
|
475
|
+
table: ['Decision', 'Gap', 'Kind', 'Value', 'Actor', 'Rationale', 'Source'],
|
|
476
|
+
rows: plan.decisions.map((decision) => [
|
|
477
|
+
decision.id,
|
|
478
|
+
decision.gapId,
|
|
479
|
+
decision.kind,
|
|
480
|
+
decision.value,
|
|
481
|
+
decision.actor,
|
|
482
|
+
decision.rationale,
|
|
483
|
+
decision.source ?? none,
|
|
484
|
+
]),
|
|
485
|
+
})
|
|
486
|
+
blocks.push({ heading: 'Assumptions' })
|
|
487
|
+
if (!plan.assumptions.length) blocks.push({ text: 'No accepted assumptions.' })
|
|
488
|
+
else blocks.push({ list: plan.assumptions.map((assumption) => `${assumption.decisionId}: ${assumption.text}`) })
|
|
489
|
+
blocks.push({ record: plan })
|
|
490
|
+
return {
|
|
491
|
+
title: `Test plan: ${plan.planId} revision ${plan.revision}`,
|
|
492
|
+
note: 'Generated from the Checkmate database. Record a decision and revise the plan to change it.',
|
|
493
|
+
blocks,
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function rendered(report: Report, record: unknown, format: Format) {
|
|
498
|
+
if (format === 'json') return JSON.stringify(record, null, 2) + '\n'
|
|
499
|
+
return format === 'md' ? markdown(report) : html(report)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export function renderPlan(plan: Plan, sha256: string, format: Format) {
|
|
503
|
+
return rendered(planReport(plan, sha256), plan, format)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function renderRun(report: RunReport, format: Format) {
|
|
507
|
+
const { inline, ...record } = report
|
|
508
|
+
return rendered(runDocument(report), record, format)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function exportPlan(store: Store, reference: string, format: string) {
|
|
512
|
+
const { id, revision } = revisionReference(reference, 'plan reference')
|
|
513
|
+
const selected = planRevision(store, id, revision)
|
|
514
|
+
return renderPlan(selected.plan, selected.sha256, requireChoice(format, formats, 'format (--format)'))
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export function runReport(store: Store, runId: string): RunReport {
|
|
518
|
+
const run = runState(store, runId)
|
|
519
|
+
const finished = runResult(store, runId)
|
|
520
|
+
requireCondition(finished, `Run ${runId} has no result. Finish it before you export it.`)
|
|
521
|
+
const state = resultAcceptance(store, run.row, finished.sha256)
|
|
522
|
+
const { planId, revision, objective, requirements, gaps, decisions, assumptions } = run.plan
|
|
523
|
+
const inline: RunReport['inline'] = {}
|
|
524
|
+
for (const entry of finished.result.evidence) {
|
|
525
|
+
const image = inlineImages.includes(entry.mediaType) && entry.size <= maxInlineImage
|
|
526
|
+
const text = inlineTexts.includes(entry.mediaType) && entry.size <= maxInlineText
|
|
527
|
+
if (!image && !text) continue
|
|
528
|
+
const bytes = Buffer.from(evidenceBytes(store, entry.id).bytes)
|
|
529
|
+
inline[entry.id] = image
|
|
530
|
+
? { image: `data:${entry.mediaType};base64,${bytes.toString('base64')}` }
|
|
531
|
+
: { text: bytes.toString('utf8') }
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
result: finished.result,
|
|
535
|
+
sha256: finished.sha256,
|
|
536
|
+
acceptance: { state: approvalLabel(state), latest: state.latest },
|
|
537
|
+
plan: { planId, revision, objective, requirements, gaps, decisions, assumptions },
|
|
538
|
+
inline,
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export function exportRun(store: Store, runId: string, format: string) {
|
|
543
|
+
return renderRun(runReport(store, runId), requireChoice(format, formats, 'format (--format)'))
|
|
544
|
+
}
|