@crvouga/atlas 0.3.0 → 0.5.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/CHANGELOG.md +12 -0
- package/README.md +178 -604
- package/dist/bun/src/driver.d.ts +41 -7
- package/dist/bun/src/driver.js +47 -6
- package/dist/bun/src/index.d.ts +8 -0
- package/dist/bun/src/index.js +8 -0
- package/dist/core/src/cli.d.ts +14 -1
- package/dist/core/src/cli.js +30 -1
- package/dist/core/src/config.d.ts +70 -4
- package/dist/core/src/config.js +62 -3
- package/dist/core/src/events/cloudevents.d.ts +54 -9
- package/dist/core/src/events/cloudevents.js +60 -9
- package/dist/core/src/graph.d.ts +21 -0
- package/dist/core/src/graph.js +26 -0
- package/dist/core/src/index.d.ts +11 -0
- package/dist/core/src/index.js +11 -0
- package/dist/core/src/lint.d.ts +30 -0
- package/dist/core/src/lint.js +13 -0
- package/dist/core/src/plan.d.ts +39 -1
- package/dist/core/src/plan.js +28 -1
- package/dist/core/src/privacy.d.ts +29 -2
- package/dist/core/src/privacy.js +19 -2
- package/dist/core/src/report/formats.d.ts +34 -4
- package/dist/core/src/report/formats.js +24 -4
- package/dist/core/src/report/markdown.d.ts +15 -0
- package/dist/core/src/report/markdown.js +8 -0
- package/dist/core/src/run/clients.d.ts +44 -5
- package/dist/core/src/run/clients.js +46 -2
- package/dist/core/src/run/execute.d.ts +69 -16
- package/dist/core/src/run/execute.js +67 -16
- package/dist/core/src/run/frames.d.ts +13 -4
- package/dist/core/src/run/frames.js +19 -4
- package/dist/core/src/run/http.d.ts +34 -1
- package/dist/core/src/run/http.js +14 -1
- package/dist/core/src/run/types.d.ts +76 -3
- package/dist/core/src/run/types.js +19 -3
- package/dist/core/src/signals.d.ts +49 -5
- package/dist/core/src/signals.js +22 -5
- package/dist/core/src/spec/compose.d.ts +23 -1
- package/dist/core/src/spec/compose.js +10 -1
- package/dist/core/src/spec/index.d.ts +9 -1
- package/dist/core/src/spec/index.js +9 -1
- package/dist/core/src/spec/load.d.ts +22 -1
- package/dist/core/src/spec/load.js +31 -1
- package/dist/core/src/spec/parse.d.ts +16 -2
- package/dist/core/src/spec/parse.js +25 -2
- package/dist/core/src/spec/scxml.d.ts +25 -4
- package/dist/core/src/spec/scxml.js +36 -4
- package/dist/core/src/spec/types.d.ts +52 -1
- package/dist/core/src/spec/types.js +24 -0
- package/dist/detox/src/index.d.ts +54 -11
- package/dist/detox/src/index.js +45 -7
- package/dist/playwright/src/driver.d.ts +39 -3
- package/dist/playwright/src/driver.js +27 -3
- package/dist/playwright/src/index.d.ts +10 -0
- package/dist/playwright/src/index.js +10 -0
- package/dist/playwright/src/media.d.ts +50 -4
- package/dist/playwright/src/media.js +55 -4
- package/dist/playwright/src/signals.d.ts +44 -5
- package/dist/playwright/src/signals.js +29 -5
- package/dist/reports/src/attachment-files.d.ts +44 -0
- package/dist/reports/src/attachment-files.js +121 -0
- package/dist/reports/src/attachment-response.d.ts +31 -0
- package/dist/reports/src/attachment-response.js +55 -0
- package/dist/reports/src/aws-credentials.d.ts +48 -0
- package/dist/reports/src/aws-credentials.js +221 -0
- package/dist/reports/src/browser-navigation.d.ts +14 -0
- package/dist/reports/src/browser-navigation.js +7 -0
- package/dist/reports/src/bundle.d.ts +34 -2
- package/dist/reports/src/bundle.js +38 -2
- package/dist/reports/src/catalog.d.ts +34 -3
- package/dist/reports/src/catalog.js +17 -3
- package/dist/reports/src/cli-stores.d.ts +36 -0
- package/dist/reports/src/cli-stores.js +73 -0
- package/dist/reports/src/clock.d.ts +7 -2
- package/dist/reports/src/clock.js +5 -2
- package/dist/reports/src/completion-order.d.ts +18 -5
- package/dist/reports/src/completion-order.js +18 -5
- package/dist/reports/src/compose.d.ts +80 -0
- package/dist/reports/src/compose.js +117 -0
- package/dist/reports/src/copy-store.d.ts +29 -0
- package/dist/reports/src/copy-store.js +52 -0
- package/dist/reports/src/directory-outline.d.ts +38 -0
- package/dist/reports/src/directory-outline.js +103 -0
- package/dist/reports/src/directory-store.d.ts +20 -0
- package/dist/reports/src/directory-store.js +166 -0
- package/dist/reports/src/doctor.d.ts +29 -0
- package/dist/reports/src/doctor.js +83 -0
- package/dist/reports/src/effective-delta.d.ts +34 -0
- package/dist/reports/src/effective-delta.js +25 -0
- package/dist/reports/src/evidence-detail.d.ts +64 -0
- package/dist/reports/src/evidence-detail.js +92 -0
- package/dist/reports/src/evidence-live.d.ts +40 -4
- package/dist/reports/src/evidence-live.js +52 -4
- package/dist/reports/src/evidence-types.d.ts +70 -1
- package/dist/reports/src/evidence-types.js +5 -1
- package/dist/reports/src/evidence-view.d.ts +58 -6
- package/dist/reports/src/evidence-view.js +44 -5
- package/dist/reports/src/explorer-types.d.ts +40 -3
- package/dist/reports/src/explorer-types.js +8 -0
- package/dist/reports/src/explorer-versions.d.ts +9 -1
- package/dist/reports/src/explorer-versions.js +18 -1
- package/dist/reports/src/export-evidence.d.ts +16 -1
- package/dist/reports/src/export-evidence.js +52 -1
- package/dist/reports/src/fetcher.d.ts +27 -3
- package/dist/reports/src/fetcher.js +11 -3
- package/dist/reports/src/format-date.d.ts +6 -0
- package/dist/reports/src/format-date.js +6 -0
- package/dist/reports/src/format.d.ts +16 -0
- package/dist/reports/src/format.js +10 -0
- package/dist/reports/src/frames.d.ts +36 -3
- package/dist/reports/src/frames.js +22 -3
- package/dist/reports/src/hash.d.ts +18 -0
- package/dist/reports/src/hash.js +13 -0
- package/dist/reports/src/host.d.ts +29 -0
- package/dist/reports/src/host.js +64 -0
- package/dist/reports/src/html-frame.d.ts +5 -6
- package/dist/reports/src/html-frame.js +12 -1
- package/dist/reports/src/http-store.d.ts +52 -0
- package/dist/reports/src/http-store.js +145 -0
- package/dist/reports/src/http.d.ts +7 -0
- package/dist/reports/src/http.js +6 -0
- package/dist/reports/src/ingest.d.ts +54 -0
- package/dist/reports/src/ingest.js +210 -0
- package/dist/reports/src/init.d.ts +40 -0
- package/dist/reports/src/init.js +364 -0
- package/dist/reports/src/junit.d.ts +33 -6
- package/dist/reports/src/junit.js +87 -21
- package/dist/reports/src/keyboard.d.ts +20 -0
- package/dist/reports/src/keyboard.js +11 -0
- package/dist/reports/src/keys.d.ts +21 -3
- package/dist/reports/src/keys.js +21 -3
- package/dist/reports/src/layer-axes.d.ts +48 -0
- package/dist/reports/src/layer-axes.js +71 -0
- package/dist/reports/src/layer-catalog.d.ts +28 -1
- package/dist/reports/src/layer-catalog.js +19 -2
- package/dist/reports/src/layer-merge.d.ts +98 -9
- package/dist/reports/src/layer-merge.js +154 -33
- package/dist/reports/src/layer-source.d.ts +18 -0
- package/dist/reports/src/layer-source.js +14 -0
- package/dist/reports/src/layer-store.d.ts +42 -0
- package/dist/reports/src/layer-store.js +63 -5
- package/dist/reports/src/layer-types.d.ts +34 -1
- package/dist/reports/src/layers-route.d.ts +47 -4
- package/dist/reports/src/layers-route.js +88 -12
- package/dist/reports/src/media-types.d.ts +11 -0
- package/dist/reports/src/media-types.js +22 -0
- package/dist/reports/src/namespace-guard.d.ts +10 -1
- package/dist/reports/src/namespace-guard.js +10 -1
- package/dist/reports/src/navigation.d.ts +12 -0
- package/dist/reports/src/navigation.js +7 -0
- package/dist/reports/src/object-io.d.ts +11 -4
- package/dist/reports/src/object-io.js +4 -4
- package/dist/reports/src/object-store-conformance.d.ts +27 -0
- package/dist/reports/src/object-store-conformance.js +87 -0
- package/dist/reports/src/object-store.d.ts +33 -24
- package/dist/reports/src/object-store.js +17 -24
- package/dist/reports/src/parse-with.d.ts +16 -0
- package/dist/reports/src/parse-with.js +14 -0
- package/dist/reports/src/performance-part.d.ts +25 -1
- package/dist/reports/src/performance-part.js +15 -1
- package/dist/reports/src/performance.d.ts +22 -2
- package/dist/reports/src/performance.js +12 -2
- package/dist/reports/src/persist.d.ts +12 -3
- package/dist/reports/src/persist.js +12 -3
- package/dist/reports/src/phone.d.ts +18 -1
- package/dist/reports/src/phone.js +18 -1
- package/dist/reports/src/protocol.d.ts +30 -4
- package/dist/reports/src/protocol.js +15 -4
- package/dist/reports/src/publication.d.ts +21 -1
- package/dist/reports/src/publication.js +21 -1
- package/dist/reports/src/publish-checks.d.ts +35 -0
- package/dist/reports/src/publish-checks.js +122 -0
- package/dist/reports/src/publish.d.ts +44 -9
- package/dist/reports/src/publish.js +40 -7
- package/dist/reports/src/read.d.ts +46 -16
- package/dist/reports/src/read.js +38 -14
- package/dist/reports/src/recording-bundle.d.ts +13 -2
- package/dist/reports/src/recording-bundle.js +4 -2
- package/dist/reports/src/recording-keys.d.ts +15 -1
- package/dist/reports/src/recording-keys.js +15 -1
- package/dist/reports/src/remote-report.d.ts +28 -7
- package/dist/reports/src/remote-report.js +15 -7
- package/dist/reports/src/report-cli.d.ts +51 -0
- package/dist/reports/src/report-cli.js +485 -0
- package/dist/reports/src/report-index.d.ts +99 -0
- package/dist/reports/src/report-index.js +337 -0
- package/dist/reports/src/report-loader.d.ts +10 -0
- package/dist/reports/src/report-loader.js +6 -0
- package/dist/reports/src/report-parts.d.ts +29 -3
- package/dist/reports/src/report-parts.js +40 -3
- package/dist/reports/src/report-status.d.ts +48 -0
- package/dist/reports/src/report-status.js +156 -0
- package/dist/reports/src/report-types.d.ts +73 -0
- package/dist/reports/src/report-types.js +3 -0
- package/dist/reports/src/reporter.d.ts +67 -0
- package/dist/reports/src/reporter.js +128 -0
- package/dist/reports/src/reports-explorer-route.d.ts +541 -0
- package/dist/reports/src/reports-explorer-route.js +11 -0
- package/dist/reports/src/reports-list-route.d.ts +9 -0
- package/dist/reports/src/reports-list-route.js +9 -0
- package/dist/reports/src/reports-route.d.ts +561 -22
- package/dist/reports/src/reports-route.js +11 -3
- package/dist/reports/src/reports-store.d.ts +19 -2
- package/dist/reports/src/reports-store.js +10 -2
- package/dist/reports/src/resilient-cache.d.ts +14 -1
- package/dist/reports/src/resilient-cache.js +8 -1
- package/dist/reports/src/retention.d.ts +17 -1
- package/dist/reports/src/retention.js +5 -1
- package/dist/reports/src/run-bundle.d.ts +63 -0
- package/dist/reports/src/run-bundle.js +159 -0
- package/dist/reports/src/s3-store.d.ts +79 -0
- package/dist/reports/src/s3-store.js +295 -0
- package/dist/reports/src/scenario.d.ts +14 -4
- package/dist/reports/src/scenario.js +5 -4
- package/dist/reports/src/sigv4.d.ts +72 -0
- package/dist/reports/src/sigv4.js +96 -0
- package/dist/reports/src/snapshot-bundle.d.ts +24 -1
- package/dist/reports/src/snapshot-bundle.js +27 -2
- package/dist/reports/src/snapshot-surface.d.ts +20 -9
- package/dist/reports/src/snapshot-surface.js +32 -19
- package/dist/reports/src/source-content.d.ts +23 -0
- package/dist/reports/src/source-content.js +30 -1
- package/dist/reports/src/source-entries.d.ts +13 -0
- package/dist/reports/src/source-entries.js +6 -0
- package/dist/reports/src/source-part.d.ts +15 -0
- package/dist/reports/src/source-part.js +8 -0
- package/dist/reports/src/source-reader.d.ts +11 -0
- package/dist/reports/src/source-reader.js +5 -0
- package/dist/reports/src/spec-evidence.d.ts +12 -2
- package/dist/reports/src/spec-evidence.js +6 -2
- package/dist/reports/src/spec-inventory.d.ts +15 -1
- package/dist/reports/src/spec-inventory.js +22 -1
- package/dist/reports/src/spec-link.d.ts +28 -0
- package/dist/reports/src/spec-link.js +70 -0
- package/dist/reports/src/spec-set.d.ts +63 -0
- package/dist/reports/src/spec-set.js +177 -0
- package/dist/reports/src/status.d.ts +10 -0
- package/dist/reports/src/status.js +5 -0
- package/dist/reports/src/store-prefix.d.ts +28 -0
- package/dist/reports/src/store-prefix.js +22 -0
- package/dist/reports/src/store-url.d.ts +89 -0
- package/dist/reports/src/store-url.js +160 -0
- package/dist/reports/src/test-catalog-entity.d.ts +11 -0
- package/dist/reports/src/test-catalog-entity.js +6 -0
- package/dist/reports/src/types.d.ts +6 -1
- package/dist/reports/src/use-report-nav.d.ts +35 -0
- package/dist/reports/src/use-report-nav.js +12 -0
- package/dist/schema/src/api.d.ts +799 -0
- package/dist/schema/src/api.js +201 -0
- package/dist/schema/src/cli-results.d.ts +210 -0
- package/dist/schema/src/cli-results.js +124 -0
- package/dist/schema/src/common.d.ts +53 -0
- package/dist/schema/src/common.js +42 -0
- package/dist/schema/src/index.d.ts +14 -0
- package/dist/schema/src/index.js +14 -0
- package/dist/schema/src/layer.d.ts +2273 -0
- package/dist/schema/src/layer.js +462 -0
- package/dist/schema/src/manifest.d.ts +95 -0
- package/dist/schema/src/manifest.js +84 -0
- package/dist/schema/src/report-sources.d.ts +28 -0
- package/dist/schema/src/report-sources.js +25 -0
- package/dist/schema/src/runs-index.d.ts +16 -0
- package/dist/schema/src/runs-index.js +15 -0
- package/dist/schema/src/server-config.d.ts +93 -0
- package/dist/schema/src/server-config.js +73 -0
- package/dist/schema/src/spec.d.ts +69 -0
- package/dist/schema/src/spec.js +58 -0
- package/dist/server/src/app.d.ts +62 -0
- package/dist/server/src/app.js +419 -0
- package/dist/server/src/auth.d.ts +41 -0
- package/dist/server/src/auth.js +59 -0
- package/dist/server/src/config.d.ts +83 -0
- package/dist/server/src/config.js +187 -0
- package/dist/server/src/dev.d.ts +12 -0
- package/dist/server/src/dev.js +80 -0
- package/dist/server/src/events.d.ts +29 -0
- package/dist/server/src/events.js +101 -0
- package/dist/server/src/host.d.ts +30 -0
- package/dist/server/src/host.js +115 -0
- package/dist/server/src/index.d.ts +16 -0
- package/dist/server/src/index.js +16 -0
- package/dist/server/src/llms.d.ts +19 -0
- package/dist/server/src/llms.js +63 -0
- package/dist/server/src/main.d.ts +30 -0
- package/dist/server/src/main.js +112 -0
- package/dist/server/src/openapi.d.ts +59 -0
- package/dist/server/src/openapi.js +374 -0
- package/dist/server/src/problem.d.ts +8 -0
- package/dist/server/src/problem.js +39 -0
- package/dist/server/src/site.d.ts +13 -0
- package/dist/server/src/site.js +43 -0
- package/dist/server/src/store-api.d.ts +28 -0
- package/dist/server/src/store-api.js +159 -0
- package/dist/server/src/validate.d.ts +71 -0
- package/dist/server/src/validate.js +197 -0
- package/dist/vitest/src/index.d.ts +27 -5
- package/dist/vitest/src/index.js +18 -5
- package/dist/webdriver/src/driver.d.ts +63 -11
- package/dist/webdriver/src/driver.js +57 -6
- package/dist/webdriver/src/index.d.ts +8 -0
- package/dist/webdriver/src/index.js +1 -0
- package/dist/webdriver/src/protocol.d.ts +75 -6
- package/dist/webdriver/src/protocol.js +79 -6
- package/docs/agents.md +137 -0
- package/docs/deploying.md +345 -0
- package/docs/dogfooding.md +175 -0
- package/docs/extending.md +242 -0
- package/docs/integrating.md +485 -0
- package/docs/reference/bun.md +141 -0
- package/docs/reference/cli.md +146 -0
- package/docs/reference/core.md +2426 -0
- package/docs/reference/detox.md +150 -0
- package/docs/reference/http-api.md +303 -0
- package/docs/reference/playwright.md +303 -0
- package/docs/reference/reports.md +7098 -0
- package/docs/reference/schema.md +1651 -0
- package/docs/reference/server.md +685 -0
- package/docs/reference/vitest.md +45 -0
- package/docs/reference/webdriver.md +330 -0
- package/docs/report-backends.md +144 -0
- package/docs/report-contract.md +400 -0
- package/docs/reporting-issues.md +160 -0
- package/docs/server.md +285 -0
- package/docs/statecharts.md +641 -0
- package/llms-full.txt +3506 -0
- package/llms.txt +47 -0
- package/package.json +33 -5
- package/packages/core/README.md +2 -1
- package/packages/reports/README.md +69 -2
- package/packages/reports/STORAGE.md +5 -1
- package/packages/schema/README.md +14 -0
- package/packages/schema/schemas/check-detail.schema.json +1032 -0
- package/packages/schema/schemas/checks-result.schema.json +26 -0
- package/packages/schema/schemas/command-error.schema.json +35 -0
- package/packages/schema/schemas/doctor-result.schema.json +60 -0
- package/packages/schema/schemas/effective-delta.schema.json +490 -0
- package/packages/schema/schemas/effective-evidence.schema.json +471 -0
- package/packages/schema/schemas/evidence-page.schema.json +271 -0
- package/packages/schema/schemas/hello-event.schema.json +22 -0
- package/packages/schema/schemas/init-plan.schema.json +347 -0
- package/packages/schema/schemas/layer-rows.schema.json +199 -0
- package/packages/schema/schemas/layer.schema.json +2888 -0
- package/packages/schema/schemas/merge-result.schema.json +40 -0
- package/packages/schema/schemas/outline.schema.json +62 -0
- package/packages/schema/schemas/problem.schema.json +37 -0
- package/packages/schema/schemas/publish-checks.schema.json +1413 -0
- package/packages/schema/schemas/publish-result.schema.json +168 -0
- package/packages/schema/schemas/ready.schema.json +68 -0
- package/packages/schema/schemas/report-event.schema.json +100 -0
- package/packages/schema/schemas/report-status.schema.json +370 -0
- package/packages/schema/schemas/server-config.schema.json +194 -0
- package/packages/schema/schemas/server-info.schema.json +114 -0
- package/packages/schema/schemas/spec-set.schema.json +103 -0
- package/packages/schema/schemas/store-listing.schema.json +29 -0
- package/packages/schema/schemas/store-write-result.schema.json +28 -0
- package/packages/schema/schemas/stream-result.schema.json +59 -0
- package/packages/server/README.md +43 -0
- package/packages/server/openapi.json +6699 -0
- package/packages/visualizer/README.md +27 -5
- package/packages/visualizer/example/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/example/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/example/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/example/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/example/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/example/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/scripts/make-fixtures.ts +2 -0
- package/packages/visualizer/scripts/publish-data.ts +1 -2
- package/packages/visualizer/src/app/AppShell.tsx +9 -2
- package/packages/visualizer/src/app/report-sources.tsx +6 -4
- package/packages/visualizer/src/app/router.tsx +3 -3
- package/packages/visualizer/src/components/Header.module.css +35 -0
- package/packages/visualizer/src/components/Header.tsx +41 -4
- package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
- package/packages/visualizer/src/data/adapters/index.ts +10 -1
- package/packages/visualizer/src/evidence/api.ts +14 -2
- package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
- package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
- package/packages/visualizer/src/evidence/check-detail.tsx +38 -4
- package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
- package/packages/visualizer/src/evidence/evidence.module.css +20 -0
- package/packages/visualizer/src/evidence/marks.tsx +2 -0
- package/packages/visualizer/src/evidence/overview.tsx +3 -3
- package/packages/visualizer/src/evidence/sources-panel.tsx +88 -34
- package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
- package/packages/visualizer/src/find/Finder.tsx +1 -7
- package/packages/visualizer/src/find/open.ts +4 -0
- package/packages/visualizer/src/live/live-stream.ts +204 -0
- package/packages/visualizer/src/runtime.ts +19 -0
- package/packages/visualizer/src/state/ui-store.ts +6 -2
- package/packages/visualizer/vite/atlas-server-proxy.ts +36 -0
- package/packages/visualizer/vite/example-reports.ts +475 -0
- package/packages/visualizer/vite.config.ts +18 -2
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Business event sources
|
|
3
|
+
*
|
|
4
|
+
* Where a run learns which business events actually happened: an HTTP endpoint that accepts
|
|
5
|
+
* CloudEvents (`cloudEventsHttpSource`), or the lines a log file gains (`logFileSource`), plus the pure
|
|
6
|
+
* functions that compare what a state expects (`meta.events`) with what was collected. Every source
|
|
7
|
+
* implements `EventSource`: `mark()` records a position before a step and `collect(mark)` returns only
|
|
8
|
+
* what arrived after it. An event with no way to be observed is reported as "no signal", never as
|
|
9
|
+
* missing, so absence of evidence is not read as failure.
|
|
10
|
+
*
|
|
11
|
+
* @see docs/statecharts.md
|
|
12
|
+
*/
|
|
1
13
|
import { closeSync, openSync, readSync, statSync } from 'node:fs';
|
|
2
14
|
import { createServer } from 'node:http';
|
|
3
15
|
import { randomUUID } from 'node:crypto';
|
|
@@ -10,7 +22,17 @@ function readBody(req) {
|
|
|
10
22
|
req.on('error', reject);
|
|
11
23
|
});
|
|
12
24
|
}
|
|
13
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Parses an HTTP delivery in any CloudEvents content mode: structured (`application/cloudevents+json`),
|
|
27
|
+
* batch (`application/cloudevents-batch+json`) or binary (`ce-*` headers, with the body as `data`).
|
|
28
|
+
* A request with none of those (no `ce-type` header and a different content type) yields no events.
|
|
29
|
+
* In binary mode a missing `ce-id` becomes a random UUID and a missing `ce-source` becomes `unknown`;
|
|
30
|
+
* other `ce-*` headers become extension attributes; the body is parsed as JSON when the content type
|
|
31
|
+
* says JSON and it parses, else kept as text.
|
|
32
|
+
*
|
|
33
|
+
* @param headers lower-cased header names, as Node's `IncomingMessage.headers`.
|
|
34
|
+
* @throws SyntaxError when a structured or batch body is not valid JSON.
|
|
35
|
+
*/
|
|
14
36
|
export function cloudEventsFromHttp(headers, body) {
|
|
15
37
|
const header = (name) => {
|
|
16
38
|
const value = headers[name];
|
|
@@ -53,8 +75,19 @@ export function cloudEventsFromHttp(headers, body) {
|
|
|
53
75
|
];
|
|
54
76
|
}
|
|
55
77
|
/**
|
|
56
|
-
*
|
|
57
|
-
* product's event bus, webhook relay or OpenTelemetry collector's CloudEvents exporter at
|
|
78
|
+
* Starts an HTTP endpoint the system under test sends CloudEvents to (any content mode). Point the
|
|
79
|
+
* product's event bus, webhook relay or OpenTelemetry collector's CloudEvents exporter at its `url`.
|
|
80
|
+
*
|
|
81
|
+
* It answers every valid POST with 202 and an unparseable delivery with 400, and keeps events in
|
|
82
|
+
* memory. It does no authentication and listens on `127.0.0.1` unless `host` says otherwise, so
|
|
83
|
+
* only the local machine can reach it by default. `mark()` is the number of events received so far.
|
|
84
|
+
* Call `close()` to stop the server; `runPaths` does that for the sources it is given.
|
|
85
|
+
*
|
|
86
|
+
* @param options.port the port to listen on; an unused port when omitted (read the real one from `url`).
|
|
87
|
+
* @param options.host the interface to listen on; `127.0.0.1` when omitted.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* eventSources: async () => [await cloudEventsHttpSource({ port: 4318 })]
|
|
58
91
|
*/
|
|
59
92
|
export async function cloudEventsHttpSource(options = {}) {
|
|
60
93
|
const received = [];
|
|
@@ -79,7 +112,18 @@ export async function cloudEventsHttpSource(options = {}) {
|
|
|
79
112
|
close: () => new Promise((resolve) => server.close(() => resolve()))
|
|
80
113
|
};
|
|
81
114
|
}
|
|
82
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* An event source for products whose only event stream is a log file: `collect` returns the text
|
|
117
|
+
* appended to the file since `mark` (match it with an `{ pattern }` matcher), and no CloudEvents.
|
|
118
|
+
*
|
|
119
|
+
* `mark()` is the file's size in bytes (0 when it does not exist). `collect` first waits `settleMs`
|
|
120
|
+
* for the product to write, then reads from the mark to the end of the file; a missing or
|
|
121
|
+
* unreadable file gives empty text, and a file that was truncated or rotated since the mark is not
|
|
122
|
+
* detected. The source's name starts with `log:`, which is how the runner tells it from a CloudEvents source.
|
|
123
|
+
*
|
|
124
|
+
* @param file the log file path.
|
|
125
|
+
* @param options.settleMs how long `collect` waits before reading, in milliseconds; 1000 when omitted.
|
|
126
|
+
*/
|
|
83
127
|
export function logFileSource(file, options = {}) {
|
|
84
128
|
return {
|
|
85
129
|
name: `log:${file}`,
|
|
@@ -110,8 +154,9 @@ export function logFileSource(file, options = {}) {
|
|
|
110
154
|
};
|
|
111
155
|
}
|
|
112
156
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
157
|
+
* Keeps what belongs to one attempt when attempts run side by side: events (whose JSON mentions an
|
|
158
|
+
* id) and log lines that contain one of its correlation ids. Empty ids are ignored; with no ids,
|
|
159
|
+
* everything is kept.
|
|
115
160
|
*/
|
|
116
161
|
export function correlated(collected, ids) {
|
|
117
162
|
const wanted = ids.filter(Boolean);
|
|
@@ -124,9 +169,15 @@ export function correlated(collected, ids) {
|
|
|
124
169
|
};
|
|
125
170
|
}
|
|
126
171
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
172
|
+
* Compares the business events a state expects with what the sources saw, sorting each expected
|
|
173
|
+
* name into `observed`, `missing` or `noSignal`. A business event is observed when its matcher
|
|
174
|
+
* matches (a function gets the events and log text; `{ type }` compares CloudEvent types;
|
|
175
|
+
* `{ pattern }` tests the log text); with no matcher, when a CloudEvent's `type` equals the name.
|
|
176
|
+
* With neither a matcher nor any CloudEvents source (`hasCloudEvents` false) it is "no signal",
|
|
177
|
+
* never "missing". Pure.
|
|
178
|
+
*
|
|
179
|
+
* @param matchers matchers by business event name.
|
|
180
|
+
* @param hasCloudEvents whether any non-log source was configured.
|
|
130
181
|
*/
|
|
131
182
|
export function compareBusinessEvents(expected, collected, matchers, hasCloudEvents) {
|
|
132
183
|
const result = { expected, observed: [], missing: [], noSignal: [], events: collected.events };
|
package/dist/core/src/graph.d.ts
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import type { HandOff } from './spec/compose.js';
|
|
2
2
|
import type { MachineConfig, StateConfig } from './spec/types.js';
|
|
3
|
+
/** An XState state value: a leaf state's name, or an object mapping each active child to its own value (parallel regions have several keys). */
|
|
3
4
|
export type StateValue = string | {
|
|
4
5
|
[key: string]: StateValue;
|
|
5
6
|
};
|
|
7
|
+
/** One event taken during a replay. */
|
|
6
8
|
export type Step = {
|
|
7
9
|
event: string;
|
|
10
|
+
/** Ids of the transitions that fired (see `transitionId`). */
|
|
8
11
|
transitions: string[];
|
|
12
|
+
/** True when the event was inserted automatically because a child chart's final state became active. */
|
|
9
13
|
handOff: boolean;
|
|
10
14
|
};
|
|
15
|
+
/** A configuration of the chart: where it is right now. */
|
|
11
16
|
export type Config = {
|
|
12
17
|
value: StateValue;
|
|
18
|
+
/** Names of every active state, ancestors included (see `activeKeys`). */
|
|
13
19
|
active: string[];
|
|
14
20
|
};
|
|
15
21
|
/** A configuration's identity: its state value with keys sorted, so equal configurations compare equal. */
|
|
16
22
|
export declare function configKey(value: StateValue): string;
|
|
23
|
+
/** Names of every state in a state value, each parent before its children. */
|
|
17
24
|
export declare function activeKeys(value: StateValue): string[];
|
|
18
25
|
/**
|
|
19
26
|
* A pure simulator over a composed machine: XState computes the next configuration, and this
|
|
@@ -21,16 +28,26 @@ export declare function activeKeys(value: StateValue): string[];
|
|
|
21
28
|
*/
|
|
22
29
|
export declare class ChartGraph {
|
|
23
30
|
readonly config: MachineConfig;
|
|
31
|
+
/** The XState machine built from the composed config, used for transition semantics. */
|
|
24
32
|
readonly machine: import("xstate").StateMachine<import("xstate").MachineContext, import("xstate").AnyEventObject, Record<string, import("xstate").AnyActorRef>, import("xstate").ProvidedActor, import("xstate").ParameterizedObject, import("xstate").ParameterizedObject, string, import("xstate").StateValue, string, unknown, {} | null | undefined, import("xstate").EventObject, import("xstate").MetaObject, any, import("xstate").MetaObject>;
|
|
25
33
|
private readonly nodes;
|
|
26
34
|
private readonly parentOf;
|
|
35
|
+
/** Hand-off state name to the event it passes to the parent chart. */
|
|
27
36
|
readonly handOffStates: Map<string, string>;
|
|
37
|
+
/**
|
|
38
|
+
* @param config the composed machine (`composeCharts(bundle).machine`).
|
|
39
|
+
* @param handOffs the composition's hand-offs; without them `replay` inserts none.
|
|
40
|
+
*/
|
|
28
41
|
constructor(config: MachineConfig, handOffs?: HandOff[]);
|
|
42
|
+
/** The config of the state called `name`, or `undefined` when there is none. */
|
|
29
43
|
node(name: string): StateConfig | undefined;
|
|
30
44
|
/** The state a transition leads to, by name. */
|
|
31
45
|
target(source: string, event: string): string | undefined;
|
|
46
|
+
/** Whether the state has no child states. */
|
|
32
47
|
isLeaf(name: string): boolean;
|
|
48
|
+
/** The name of the enclosing state, or `null` for a top-level state (or an unknown name). */
|
|
33
49
|
parent(name: string): string | null;
|
|
50
|
+
/** Every state name in the composed chart, parents before children. */
|
|
34
51
|
stateNames(): string[];
|
|
35
52
|
/** The parallel regions a state sits in, outermost first: each the child of a parallel ancestor. */
|
|
36
53
|
regionsOf(state: string): string[];
|
|
@@ -43,14 +60,18 @@ export declare class ChartGraph {
|
|
|
43
60
|
actsIn(event: string, regions: string[]): boolean;
|
|
44
61
|
/** Every distinct set of regions a state sits in, the empty set (outside every parallel) first. */
|
|
45
62
|
regionSets(): string[][];
|
|
63
|
+
/** The configuration the chart starts in. */
|
|
46
64
|
initial(): Config;
|
|
47
65
|
/** The state value that names `state`: its ancestors down to it, defaults filled in by `resolve`. */
|
|
48
66
|
valueOf(state: string): StateValue;
|
|
67
|
+
/** Fills a partial state value in with each parent's initial child, returning a complete configuration. */
|
|
49
68
|
resolve(value: StateValue): Config;
|
|
69
|
+
/** Names of the events some active state handles (a state's own or an ancestor's), without duplicates. Whether the event moves anything is for `step` to say. */
|
|
50
70
|
enabledEvents(config: Config): string[];
|
|
51
71
|
/** The transitions `event` fires from `config`: in each region, the deepest active state that handles it. */
|
|
52
72
|
firedBy(config: Config, event: string): string[];
|
|
53
73
|
private isDescendant;
|
|
74
|
+
/** Takes `event` from `config`: the next configuration and the transition ids that fired, or `null` when no active state handles the event. */
|
|
54
75
|
step(config: Config, event: string): {
|
|
55
76
|
config: Config;
|
|
56
77
|
fired: string[];
|
package/dist/core/src/graph.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Chart graph simulator
|
|
3
|
+
*
|
|
4
|
+
* A pure, deterministic simulator over a composed machine (see `composeCharts`): given a
|
|
5
|
+
* configuration and an event it computes the next configuration and names the transitions that
|
|
6
|
+
* fired. The planner, linter and runner all use it to reason about a chart without running
|
|
7
|
+
* the product. It never executes guards or actions (the linter rejects charts that have them), and
|
|
8
|
+
* it identifies states by name alone, so names must be unique across the composed product.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/statecharts.md
|
|
11
|
+
*/
|
|
1
12
|
import { createMachine, initialTransition, transition } from 'xstate';
|
|
2
13
|
import { targetOf, transitionId, walkStates } from './spec/types.js';
|
|
3
14
|
/** A configuration's identity: its state value with keys sorted, so equal configurations compare equal. */
|
|
@@ -5,6 +16,7 @@ export function configKey(value) {
|
|
|
5
16
|
const canonical = (v) => typeof v === 'string' ? v : Object.fromEntries(Object.keys(v).sort().map((k) => [k, canonical(v[k])]));
|
|
6
17
|
return JSON.stringify(canonical(value));
|
|
7
18
|
}
|
|
19
|
+
/** Names of every state in a state value, each parent before its children. */
|
|
8
20
|
export function activeKeys(value) {
|
|
9
21
|
if (typeof value === 'string')
|
|
10
22
|
return [value];
|
|
@@ -16,10 +28,16 @@ export function activeKeys(value) {
|
|
|
16
28
|
*/
|
|
17
29
|
export class ChartGraph {
|
|
18
30
|
config;
|
|
31
|
+
/** The XState machine built from the composed config, used for transition semantics. */
|
|
19
32
|
machine;
|
|
20
33
|
nodes = new Map();
|
|
21
34
|
parentOf = new Map();
|
|
35
|
+
/** Hand-off state name to the event it passes to the parent chart. */
|
|
22
36
|
handOffStates;
|
|
37
|
+
/**
|
|
38
|
+
* @param config the composed machine (`composeCharts(bundle).machine`).
|
|
39
|
+
* @param handOffs the composition's hand-offs; without them `replay` inserts none.
|
|
40
|
+
*/
|
|
23
41
|
constructor(config, handOffs = []) {
|
|
24
42
|
this.config = config;
|
|
25
43
|
this.machine = createMachine(config);
|
|
@@ -29,6 +47,7 @@ export class ChartGraph {
|
|
|
29
47
|
}
|
|
30
48
|
this.handOffStates = new Map(handOffs.map((h) => [h.finalState, h.event]));
|
|
31
49
|
}
|
|
50
|
+
/** The config of the state called `name`, or `undefined` when there is none. */
|
|
32
51
|
node(name) {
|
|
33
52
|
return this.nodes.get(name);
|
|
34
53
|
}
|
|
@@ -36,12 +55,15 @@ export class ChartGraph {
|
|
|
36
55
|
target(source, event) {
|
|
37
56
|
return targetOf(this.nodes.get(source)?.on?.[event]);
|
|
38
57
|
}
|
|
58
|
+
/** Whether the state has no child states. */
|
|
39
59
|
isLeaf(name) {
|
|
40
60
|
return !this.nodes.get(name)?.states;
|
|
41
61
|
}
|
|
62
|
+
/** The name of the enclosing state, or `null` for a top-level state (or an unknown name). */
|
|
42
63
|
parent(name) {
|
|
43
64
|
return this.parentOf.get(name) ?? null;
|
|
44
65
|
}
|
|
66
|
+
/** Every state name in the composed chart, parents before children. */
|
|
45
67
|
stateNames() {
|
|
46
68
|
return [...this.nodes.keys()];
|
|
47
69
|
}
|
|
@@ -81,6 +103,7 @@ export class ChartGraph {
|
|
|
81
103
|
}
|
|
82
104
|
return [...keyed.values()];
|
|
83
105
|
}
|
|
106
|
+
/** The configuration the chart starts in. */
|
|
84
107
|
initial() {
|
|
85
108
|
const [snapshot] = initialTransition(this.machine);
|
|
86
109
|
return {
|
|
@@ -97,6 +120,7 @@ export class ChartGraph {
|
|
|
97
120
|
trail.unshift(n);
|
|
98
121
|
return trail.reduceRight((inner, name) => (inner === null ? name : { [name]: inner }), null);
|
|
99
122
|
}
|
|
123
|
+
/** Fills a partial state value in with each parent's initial child, returning a complete configuration. */
|
|
100
124
|
resolve(value) {
|
|
101
125
|
const snapshot = this.machine.resolveState({ value });
|
|
102
126
|
return {
|
|
@@ -104,6 +128,7 @@ export class ChartGraph {
|
|
|
104
128
|
active: activeKeys(snapshot.value)
|
|
105
129
|
};
|
|
106
130
|
}
|
|
131
|
+
/** Names of the events some active state handles (a state's own or an ancestor's), without duplicates. Whether the event moves anything is for `step` to say. */
|
|
107
132
|
enabledEvents(config) {
|
|
108
133
|
const events = new Set();
|
|
109
134
|
for (const name of config.active) {
|
|
@@ -127,6 +152,7 @@ export class ChartGraph {
|
|
|
127
152
|
}
|
|
128
153
|
return false;
|
|
129
154
|
}
|
|
155
|
+
/** Takes `event` from `config`: the next configuration and the transition ids that fired, or `null` when no active state handles the event. */
|
|
130
156
|
step(config, event) {
|
|
131
157
|
const fired = this.firedBy(config, event);
|
|
132
158
|
if (fired.length === 0)
|
package/dist/core/src/index.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Atlas core
|
|
3
|
+
*
|
|
4
|
+
* The public entry point of `@crvouga/atlas`: define a config (`defineConfig`), then load, lint, plan
|
|
5
|
+
* and run executable statecharts against a real product through a driver, and write a manifest of the
|
|
6
|
+
* results. This file only re-exports, grouped by stage: spec (types, load, SCXML, composition), graph,
|
|
7
|
+
* lint, plan, run (drivers, implementations, execution), business events, privacy, report formats and
|
|
8
|
+
* config. It does no work at import time. Browser-safe parts are also available from `./spec`.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/statecharts.md
|
|
11
|
+
*/
|
|
1
12
|
export * from './spec/types.js';
|
|
2
13
|
export { parseChart, parseJourneys, loadSpecDirectory, bundleOf, childChartIds } from './spec/load.js';
|
|
3
14
|
export { scxmlToMachine, machineToScxml, scxmlToken, ATLAS_NS } from './spec/scxml.js';
|
package/dist/core/src/index.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Atlas core
|
|
3
|
+
*
|
|
4
|
+
* The public entry point of `@crvouga/atlas`: define a config (`defineConfig`), then load, lint, plan
|
|
5
|
+
* and run executable statecharts against a real product through a driver, and write a manifest of the
|
|
6
|
+
* results. This file only re-exports, grouped by stage: spec (types, load, SCXML, composition), graph,
|
|
7
|
+
* lint, plan, run (drivers, implementations, execution), business events, privacy, report formats and
|
|
8
|
+
* config. It does no work at import time. Browser-safe parts are also available from `./spec`.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/statecharts.md
|
|
11
|
+
*/
|
|
1
12
|
export * from './spec/types.js';
|
|
2
13
|
export { parseChart, parseJourneys, loadSpecDirectory, bundleOf, childChartIds } from './spec/load.js';
|
|
3
14
|
export { scxmlToMachine, machineToScxml, scxmlToken, ATLAS_NS } from './spec/scxml.js';
|
package/dist/core/src/lint.d.ts
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Spec linter
|
|
3
|
+
*
|
|
4
|
+
* Decides whether a spec bundle is executable before anything runs: pure structure, reachable states,
|
|
5
|
+
* no silent dead ends, journeys that replay, and (optionally) an implementation for everything in scope.
|
|
6
|
+
* It reports problems as findings rather than throwing, except when the charts cannot be composed at
|
|
7
|
+
* all. The `rule` of a finding is a stable short name (`pure`, `naming`, `meta`, `reachable`,
|
|
8
|
+
* `dead-end`, `journey`, `implemented`). `runConfig` refuses to run when any finding other than
|
|
9
|
+
* `implemented` exists.
|
|
10
|
+
*
|
|
11
|
+
* @see docs/statecharts.md
|
|
12
|
+
*/
|
|
1
13
|
import type { SpecBundle } from './spec/types.js';
|
|
2
14
|
import { ChartGraph } from './graph.js';
|
|
15
|
+
/** One problem found by `lintBundle`. */
|
|
3
16
|
export type LintFinding = {
|
|
17
|
+
/** Which check found it: `pure`, `naming`, `meta`, `reachable`, `dead-end`, `journey` or `implemented`. */
|
|
4
18
|
rule: string;
|
|
19
|
+
/** A sentence for a person, naming the state, event or journey concerned. */
|
|
5
20
|
message: string;
|
|
6
21
|
};
|
|
22
|
+
/** Options for `lintBundle`. */
|
|
7
23
|
export type LintOptions = {
|
|
8
24
|
/** Meta fields every state must carry; `description` by default. */
|
|
9
25
|
requiredMeta?: readonly string[];
|
|
@@ -12,6 +28,7 @@ export type LintOptions = {
|
|
|
12
28
|
states: ReadonlySet<string>;
|
|
13
29
|
events: ReadonlySet<string>;
|
|
14
30
|
};
|
|
31
|
+
/** The event and state names the implementation provides. When given, anything in scope without one is an `implemented` finding. */
|
|
15
32
|
implementations?: {
|
|
16
33
|
events: ReadonlySet<string>;
|
|
17
34
|
states: ReadonlySet<string>;
|
|
@@ -21,6 +38,19 @@ export type LintOptions = {
|
|
|
21
38
|
* Checks a spec bundle is executable: pure (no guards, actions, context or delays), every state
|
|
22
39
|
* reachable, no dead ends without `meta.deadEnd`, every journey replays to its `endsIn` states, and
|
|
23
40
|
* (when implementations are given) every event and state in scope has one.
|
|
41
|
+
*
|
|
42
|
+
* Findings are returned, never thrown. Each leaf state counts as having an exit when it or any
|
|
43
|
+
* ancestor handles an event; final states are exempt from the dead-end rule. `requiredMeta` applies
|
|
44
|
+
* to states, not to the chart's root. Only leaf states need recognisers.
|
|
45
|
+
*
|
|
46
|
+
* @returns `findings`; the `composition` and `graph` it built (reuse them rather than composing
|
|
47
|
+
* again); each journey with its `replay`; and the names in `unimplemented`.
|
|
48
|
+
* @throws Error when the charts cannot be composed (a missing or cyclic child chart, a child final
|
|
49
|
+
* with nowhere to go); the message lists the findings gathered so far.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const { findings } = lintBundle(loadSpecDirectory('./specs'));
|
|
53
|
+
* if (findings.length) throw new Error(findings.map((f) => f.message).join('\n'));
|
|
24
54
|
*/
|
|
25
55
|
export declare function lintBundle(bundle: SpecBundle, options?: LintOptions): {
|
|
26
56
|
findings: LintFinding[];
|
package/dist/core/src/lint.js
CHANGED
|
@@ -59,6 +59,19 @@ function sanitized(bundle) {
|
|
|
59
59
|
* Checks a spec bundle is executable: pure (no guards, actions, context or delays), every state
|
|
60
60
|
* reachable, no dead ends without `meta.deadEnd`, every journey replays to its `endsIn` states, and
|
|
61
61
|
* (when implementations are given) every event and state in scope has one.
|
|
62
|
+
*
|
|
63
|
+
* Findings are returned, never thrown. Each leaf state counts as having an exit when it or any
|
|
64
|
+
* ancestor handles an event; final states are exempt from the dead-end rule. `requiredMeta` applies
|
|
65
|
+
* to states, not to the chart's root. Only leaf states need recognisers.
|
|
66
|
+
*
|
|
67
|
+
* @returns `findings`; the `composition` and `graph` it built (reuse them rather than composing
|
|
68
|
+
* again); each journey with its `replay`; and the names in `unimplemented`.
|
|
69
|
+
* @throws Error when the charts cannot be composed (a missing or cyclic child chart, a child final
|
|
70
|
+
* with nowhere to go); the message lists the findings gathered so far.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const { findings } = lintBundle(loadSpecDirectory('./specs'));
|
|
74
|
+
* if (findings.length) throw new Error(findings.map((f) => f.message).join('\n'));
|
|
62
75
|
*/
|
|
63
76
|
export function lintBundle(bundle, options = {}) {
|
|
64
77
|
const findings = [];
|
package/dist/core/src/plan.d.ts
CHANGED
|
@@ -2,30 +2,43 @@ import type { Config, StateValue } from './graph.js';
|
|
|
2
2
|
import type { ChartGraph } from './graph.js';
|
|
3
3
|
import type { Composition } from './spec/compose.js';
|
|
4
4
|
import type { SpecBundle } from './spec/types.js';
|
|
5
|
+
/** The part of the composed product one run covers, as computed by `chartScope`. */
|
|
5
6
|
export type ChartScope = {
|
|
7
|
+
/** The id of the chart being run. */
|
|
6
8
|
chart: string;
|
|
7
9
|
/** Set when the run covers one state of the chart and what is inside it. */
|
|
8
10
|
state?: string;
|
|
9
11
|
/** The configuration each generated path starts from. */
|
|
10
12
|
start: StateValue;
|
|
13
|
+
/** A phrase describing the start for reports. */
|
|
11
14
|
startLabel: string;
|
|
15
|
+
/** Names of the states in scope. */
|
|
12
16
|
states: Set<string>;
|
|
17
|
+
/** Ids of the transitions in scope (see `transitionId`): the ones the run must cover. */
|
|
13
18
|
transitions: Set<string>;
|
|
19
|
+
/** Names of the events of those transitions. */
|
|
14
20
|
events: Set<string>;
|
|
15
21
|
/** Transitions that enter the chart from outside it. */
|
|
16
22
|
entries: Set<string>;
|
|
17
23
|
};
|
|
24
|
+
/** One event of a planned path, with the transitions it fires and the active states before and after. A hand-off step is inserted automatically and is not driven by the implementation's events. */
|
|
18
25
|
export type PlannedStep = {
|
|
19
26
|
event: string;
|
|
27
|
+
/** Ids of the transitions the event fires (see `transitionId`). */
|
|
20
28
|
transitions: string[];
|
|
21
29
|
handOff: boolean;
|
|
30
|
+
/** Names of the active states before the step. */
|
|
22
31
|
from: string[];
|
|
32
|
+
/** Names of the active states after the step. */
|
|
23
33
|
to: string[];
|
|
24
34
|
};
|
|
35
|
+
/** One sequence of events to run against the product from a start point. */
|
|
25
36
|
export type PlannedPath = {
|
|
37
|
+
/** `journey-<n>` or `generated-<n>`, numbered in plan order. Stable for a given plan; use `key` to match across plans. */
|
|
26
38
|
id: string;
|
|
27
39
|
/** The same start and events give the same key on any machine and in any run. */
|
|
28
40
|
key: string;
|
|
41
|
+
/** The journey's name, or `Covers: <state> → <event>` for a generated path. */
|
|
29
42
|
name: string;
|
|
30
43
|
kind: 'journey' | 'generated';
|
|
31
44
|
description: string;
|
|
@@ -36,15 +49,18 @@ export type PlannedPath = {
|
|
|
36
49
|
seed?: string;
|
|
37
50
|
/** The journeys this stretch belongs to: a stretch several journeys share is run once. */
|
|
38
51
|
journeys?: string[];
|
|
52
|
+
/** Why the path cannot fully run: `Start: <reason>` when its start cannot be set up, then the blocked events on it. Empty when it can run in full. */
|
|
39
53
|
blockedBy: string[];
|
|
40
54
|
/** A blocked path whose runnable part other paths already cover: reported, not run. */
|
|
41
55
|
skipRun?: boolean;
|
|
42
56
|
};
|
|
57
|
+
/** What to narrow a run to; all fields are optional and combine in `chartScope`. */
|
|
43
58
|
export type ScopeOptions = {
|
|
44
59
|
/** The chart to run; the root chart (the whole product) when omitted. */
|
|
45
60
|
chart?: string;
|
|
46
61
|
/** Where generated paths start; the root's initial configuration when omitted. */
|
|
47
62
|
start?: StateValue;
|
|
63
|
+
/** A phrase describing the start for reports; `The start of the chart` when omitted. */
|
|
48
64
|
startLabel?: string;
|
|
49
65
|
/** Only these events enter the chart from outside it; every entering transition when omitted. */
|
|
50
66
|
entryEvents?: string[];
|
|
@@ -56,23 +72,32 @@ export type ScopeOptions = {
|
|
|
56
72
|
};
|
|
57
73
|
/**
|
|
58
74
|
* The part of the composed product a run covers: every state of `chart`, the transitions that
|
|
59
|
-
* enter it from outside, and the hand-offs back to its parent.
|
|
75
|
+
* enter it from outside, and the hand-offs back to its parent. With `options.state`, the scope is
|
|
76
|
+
* that state, everything inside it, and the transitions into and out of it.
|
|
77
|
+
*
|
|
78
|
+
* @throws Error when `options.state` names a state that does not exist.
|
|
60
79
|
*/
|
|
61
80
|
export declare function chartScope(bundle: SpecBundle, composition: Composition, graph: ChartGraph, options?: ScopeOptions): ChartScope;
|
|
62
81
|
/** A named seed: a configuration the implementation can put the system in directly. */
|
|
63
82
|
export type SeedPoint = {
|
|
64
83
|
name: string;
|
|
84
|
+
/** The configuration the seed produces, as a state name or a full state value. */
|
|
65
85
|
at: StateValue;
|
|
86
|
+
/** The reason the seed cannot be used yet. A blocked seed starts no path. */
|
|
66
87
|
blocked?: string;
|
|
88
|
+
/** Events this seed is a variant for: it starts only paths that take one of them, and they start only from it. */
|
|
67
89
|
for?: string[];
|
|
90
|
+
/** The state the seed leaves on screen; in a parallel state it picks the region the first event must act in. */
|
|
68
91
|
shows?: string;
|
|
69
92
|
};
|
|
70
93
|
/** A journey as the paths it is cut into, one per seeded stretch, in order. */
|
|
71
94
|
export type PlannedJourney = {
|
|
72
95
|
name: string;
|
|
73
96
|
description: string;
|
|
97
|
+
/** Ids of the paths the journey is cut into, in order. */
|
|
74
98
|
paths: string[];
|
|
75
99
|
};
|
|
100
|
+
/** Inputs to `planChart` that come from the implementation rather than the charts. */
|
|
76
101
|
export type PlanOptions = {
|
|
77
102
|
/** Events that cannot run yet; paths stop before them. */
|
|
78
103
|
isBlocked?: (event: string) => boolean;
|
|
@@ -88,6 +113,10 @@ export type PlanOptions = {
|
|
|
88
113
|
};
|
|
89
114
|
/** The steps a path can actually run: everything before its first blocked event. */
|
|
90
115
|
export declare function runnableSteps(steps: PlannedStep[], isBlocked: (event: string) => boolean): PlannedStep[];
|
|
116
|
+
/**
|
|
117
|
+
* A 16-hex-character key for a path: a SHA-256 of its seed name, start configuration and event names.
|
|
118
|
+
* Identical on every machine and run; it is how reruns, merges and layers match a path across runs.
|
|
119
|
+
*/
|
|
91
120
|
export declare function pathKey(seed: string | undefined, start: Config, events: string[]): string;
|
|
92
121
|
/**
|
|
93
122
|
* Paths for one chart. Every path starts at a start point: the scope's start (through `setup`)
|
|
@@ -96,6 +125,15 @@ export declare function pathKey(seed: string | undefined, start: Config, events:
|
|
|
96
125
|
* journeys become one path. Then the shortest extra path from the nearest start point is added
|
|
97
126
|
* for every transition still uncovered, and extra paths that are a prefix of another path are
|
|
98
127
|
* dropped. Everything is ordered by the chart and the journeys, so the plan is deterministic.
|
|
128
|
+
*
|
|
129
|
+
* Paths stop before blocked events. A path whose start cannot be set up gets a `Start:` entry in
|
|
130
|
+
* `blockedBy`, and a blocked path whose runnable steps add no coverage gets `skipRun`. The generated
|
|
131
|
+
* paths are searched first avoiding blocked events, then (for what remains) allowing them.
|
|
132
|
+
*
|
|
133
|
+
* @returns `paths` (journey paths first, then generated, with ids assigned), `unreachable` (ids of
|
|
134
|
+
* in-scope transitions no startable path takes), `journeys` (the paths each journey is cut into),
|
|
135
|
+
* and `seeds` (each seed's name and active states, with `blocked` when it cannot be seeded yet).
|
|
136
|
+
* @see docs/statecharts.md
|
|
99
137
|
*/
|
|
100
138
|
export declare function planChart(bundle: SpecBundle, graph: ChartGraph, scope: ChartScope, options?: PlanOptions): {
|
|
101
139
|
paths: PlannedPath[];
|
package/dist/core/src/plan.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Run planner
|
|
3
|
+
*
|
|
4
|
+
* Decides, from the charts alone, which paths a run executes: the curated journeys (cut at seeded
|
|
5
|
+
* configurations), plus the shortest generated path to every transition the journeys leave uncovered.
|
|
6
|
+
* It also computes the scope of a run (all of the product, one chart, or one state). Pure and
|
|
7
|
+
* deterministic: the same bundle, scope, seeds and blocked events give the same paths, ids and keys on
|
|
8
|
+
* any machine, which is what lets shards and reruns be composed. It never runs the product.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/statecharts.md
|
|
11
|
+
*/
|
|
1
12
|
import { createHash } from 'node:crypto';
|
|
2
13
|
import { configKey } from './graph.js';
|
|
3
14
|
import { splitTransitionId, transitionId, walkStates } from './spec/types.js';
|
|
4
15
|
/**
|
|
5
16
|
* The part of the composed product a run covers: every state of `chart`, the transitions that
|
|
6
|
-
* enter it from outside, and the hand-offs back to its parent.
|
|
17
|
+
* enter it from outside, and the hand-offs back to its parent. With `options.state`, the scope is
|
|
18
|
+
* that state, everything inside it, and the transitions into and out of it.
|
|
19
|
+
*
|
|
20
|
+
* @throws Error when `options.state` names a state that does not exist.
|
|
7
21
|
*/
|
|
8
22
|
export function chartScope(bundle, composition, graph, options = {}) {
|
|
9
23
|
const chart = options.chart ?? bundle.root;
|
|
@@ -105,6 +119,10 @@ export function runnableSteps(steps, isBlocked) {
|
|
|
105
119
|
const stop = steps.findIndex((s) => isBlocked(s.event));
|
|
106
120
|
return stop < 0 ? steps : steps.slice(0, stop);
|
|
107
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* A 16-hex-character key for a path: a SHA-256 of its seed name, start configuration and event names.
|
|
124
|
+
* Identical on every machine and run; it is how reruns, merges and layers match a path across runs.
|
|
125
|
+
*/
|
|
108
126
|
export function pathKey(seed, start, events) {
|
|
109
127
|
return createHash('sha256')
|
|
110
128
|
.update(JSON.stringify([seed ?? null, configKey(start.value), events]))
|
|
@@ -119,6 +137,15 @@ const leaves = (graph, active) => active.filter((n) => graph.isLeaf(n));
|
|
|
119
137
|
* journeys become one path. Then the shortest extra path from the nearest start point is added
|
|
120
138
|
* for every transition still uncovered, and extra paths that are a prefix of another path are
|
|
121
139
|
* dropped. Everything is ordered by the chart and the journeys, so the plan is deterministic.
|
|
140
|
+
*
|
|
141
|
+
* Paths stop before blocked events. A path whose start cannot be set up gets a `Start:` entry in
|
|
142
|
+
* `blockedBy`, and a blocked path whose runnable steps add no coverage gets `skipRun`. The generated
|
|
143
|
+
* paths are searched first avoiding blocked events, then (for what remains) allowing them.
|
|
144
|
+
*
|
|
145
|
+
* @returns `paths` (journey paths first, then generated, with ids assigned), `unreachable` (ids of
|
|
146
|
+
* in-scope transitions no startable path takes), `journeys` (the paths each journey is cut into),
|
|
147
|
+
* and `seeds` (each seed's name and active states, with `blocked` when it cannot be seeded yet).
|
|
148
|
+
* @see docs/statecharts.md
|
|
122
149
|
*/
|
|
123
150
|
export function planChart(bundle, graph, scope, options = {}) {
|
|
124
151
|
const isBlocked = options.isBlocked ?? (() => false);
|
|
@@ -1,8 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Privacy scanning
|
|
3
|
+
*
|
|
4
|
+
* Keeps real personal data out of the media and manifests a run produces and shares: patterns for
|
|
5
|
+
* data that must never appear, an allow-list for synthetic emails, and an allow-list of hosts a run may
|
|
6
|
+
* target. Findings describe what matched without repeating it. These are pure functions of their input;
|
|
7
|
+
* the runner applies them to the text a driver reports after each step and to the finished manifest.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/statecharts.md
|
|
10
|
+
*/
|
|
1
11
|
import type { PrivacyRules } from './run/types.js';
|
|
2
12
|
/** Defaults that fit any product: no real-looking emails, SSNs or card numbers in media or manifests. */
|
|
3
13
|
export declare const DEFAULT_PRIVACY: Required<PrivacyRules>;
|
|
14
|
+
/**
|
|
15
|
+
* Combines a project's `PrivacyRules` with `DEFAULT_PRIVACY`: its `deny` and `allowEmails` are added
|
|
16
|
+
* to the defaults, while its `allowHosts`, when given, replaces the default hosts (so list loopback
|
|
17
|
+
* again if you still need it).
|
|
18
|
+
*/
|
|
4
19
|
export declare function mergePrivacy(rules?: PrivacyRules): Required<PrivacyRules>;
|
|
5
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Scans `text` for real-looking personal data and returns one description per finding, each
|
|
22
|
+
* prefixed by `where` and never containing the matched text (an email is shortened to its first
|
|
23
|
+
* letter and domain). Flags emails not matching `rules.allowEmails` and anything matching a
|
|
24
|
+
* `rules.deny` pattern. Returns an empty array when the text is clean.
|
|
25
|
+
*
|
|
26
|
+
* @param rules the merged rules (`mergePrivacy`).
|
|
27
|
+
*/
|
|
6
28
|
export declare function privacyFindings(text: string, where: string, rules: Required<PrivacyRules>): string[];
|
|
7
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Throws unless every URL's hostname is in `rules.allowHosts` (loopback by default), so a run
|
|
31
|
+
* cannot touch a real environment by accident.
|
|
32
|
+
*
|
|
33
|
+
* @throws Error naming the first refused host, or a `TypeError` for a URL that does not parse.
|
|
34
|
+
*/
|
|
8
35
|
export declare function assertAllowedTarget(urls: string[], rules: Required<PrivacyRules>): void;
|
package/dist/core/src/privacy.js
CHANGED
|
@@ -11,6 +11,11 @@ export const DEFAULT_PRIVACY = {
|
|
|
11
11
|
allowEmails: [/@example\.(com|org|net)$/i, /\.(test|example|invalid|localhost|local)$/i],
|
|
12
12
|
allowHosts: ['127.0.0.1', 'localhost', '::1', '[::1]']
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Combines a project's `PrivacyRules` with `DEFAULT_PRIVACY`: its `deny` and `allowEmails` are added
|
|
16
|
+
* to the defaults, while its `allowHosts`, when given, replaces the default hosts (so list loopback
|
|
17
|
+
* again if you still need it).
|
|
18
|
+
*/
|
|
14
19
|
export function mergePrivacy(rules = {}) {
|
|
15
20
|
return {
|
|
16
21
|
deny: [...DEFAULT_PRIVACY.deny, ...(rules.deny ?? [])],
|
|
@@ -18,7 +23,14 @@ export function mergePrivacy(rules = {}) {
|
|
|
18
23
|
allowHosts: rules.allowHosts ?? DEFAULT_PRIVACY.allowHosts
|
|
19
24
|
};
|
|
20
25
|
}
|
|
21
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Scans `text` for real-looking personal data and returns one description per finding, each
|
|
28
|
+
* prefixed by `where` and never containing the matched text (an email is shortened to its first
|
|
29
|
+
* letter and domain). Flags emails not matching `rules.allowEmails` and anything matching a
|
|
30
|
+
* `rules.deny` pattern. Returns an empty array when the text is clean.
|
|
31
|
+
*
|
|
32
|
+
* @param rules the merged rules (`mergePrivacy`).
|
|
33
|
+
*/
|
|
22
34
|
export function privacyFindings(text, where, rules) {
|
|
23
35
|
const findings = [];
|
|
24
36
|
for (const email of text.match(EMAIL) ?? []) {
|
|
@@ -32,7 +44,12 @@ export function privacyFindings(text, where, rules) {
|
|
|
32
44
|
findings.push(`${where}: something shaped like ${name}`);
|
|
33
45
|
return findings;
|
|
34
46
|
}
|
|
35
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Throws unless every URL's hostname is in `rules.allowHosts` (loopback by default), so a run
|
|
49
|
+
* cannot touch a real environment by accident.
|
|
50
|
+
*
|
|
51
|
+
* @throws Error naming the first refused host, or a `TypeError` for a URL that does not parse.
|
|
52
|
+
*/
|
|
36
53
|
export function assertAllowedTarget(urls, rules) {
|
|
37
54
|
for (const url of urls) {
|
|
38
55
|
const host = new URL(url).hostname;
|