From 22b3a8dec4056fc65ad631b7b1b3c6dc4e15470c Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Sun, 11 Oct 2026 15:24:47 +0100 Subject: [PATCH 1/2] docs: establish the Weave platform knowledge base --- .azuredevops/PULL_REQUEST_TEMPLATE.md | 22 - .../pull_request_template/branches/dev.md | 20 - .../branches/terraform.md | 21 - .github/CODEOWNERS | 12 - .github/PULL_REQUEST_TEMPLATE.md | 55 +- .github/dependabot.yml | 34 +- .github/workflows/auto-merge-dependabot.yml | 38 - .github/workflows/dependancy-review.yml | 24 - .github/workflows/deploy.yml | 79 + .github/workflows/docs-kit-release.yml | 24 + .github/workflows/go-lint.yml | 85 - .github/workflows/linter.yml | 47 - .github/workflows/pr-title-validation.yml | 54 - .github/workflows/quality-gate.yml | 70 + .github/workflows/release-please.yml | 55 - .github/workflows/source-refresh.yml | 31 + .gitignore | 59 +- .golangci.yml | 182 - .markdownlint-cli2.yaml | 11 + .node-version | 1 + .pipelines/linter.yml | 17 - .pipelines/pipeline.yml | 31 - CODE_OF_CONDUCT.md | 4 +- CONTRIBUTING.md | 22 +- README.md | 154 +- SECURITY.md | 6 +- deployment/README.md | 62 + diagrams/manifest.yaml | 76 + diagrams/sources/agent-core.json | 124 + .../sources/documentation-architecture.json | 145 + diagrams/sources/guestweave-macos.json | 124 + diagrams/sources/guestweave-windows.json | 124 + diagrams/sources/hostweave.json | 124 + diagrams/sources/imageweave-construction.json | 110 + diagrams/sources/module-startup.json | 102 + diagrams/sources/platform-context.json | 26 + diagrams/sources/publishing-workflow.json | 126 + docs/README.md | 132 - docs/development/architecture.mdx | 62 + docs/development/documentation.md | 59 + docs/development/roadmap.md | 41 + docs/markdown-syntax-guide.md | 103 - docs/media/MicrosoftAzure-32px.png | Bin 1020 -> 0 bytes docs/media/MicrosoftPowerShellCore-32px.png | Bin 1596 -> 0 bytes docs/operations/publishing.mdx | 50 + docs/platform/architecture.mdx | 47 + docs/platform/compatibility.md | 45 + docs/platform/glossary.md | 32 + docs/platform/repositories.md | 40 + docs/products/agent-core/hld.mdx | 46 + docs/products/agent-modules/hld.mdx | 44 + docs/products/guestweave/index.md | 34 + docs/products/guestweave/macos.mdx | 48 + docs/products/guestweave/windows.mdx | 44 + docs/products/hostweave/hld.mdx | 48 + docs/products/imageweave/hld.mdx | 44 + docs/reference/contracts.md | 31 + docs/reference/index.md | 42 + docs/start-here/index.md | 39 + package-lock.json | 19953 ++++++++++++++++ package.json | 32 + packages/docs-kit/LICENSE | 21 + packages/docs-kit/package.json | 10 + packages/docs-kit/schemas/page.schema.json | 18 + packages/docs-kit/src/Diagram.tsx | 11 + packages/docs-kit/src/Search.tsx | 23 + packages/docs-kit/src/styles.css | 81 + scripts/check-browser.mjs | 71 + scripts/check-links.mjs | 18 + scripts/contracts.mjs | 19 + scripts/deployment-manifest.mjs | 13 + scripts/diagrams.mjs | 39 + scripts/lib.mjs | 29 + scripts/prepare-companion.mjs | 99 + scripts/select-build.mjs | 31 + scripts/smoke-deployment.mjs | 45 + scripts/source-refresh.mjs | 30 + scripts/tests/evidence.test.mjs | 15 + scripts/tests/select-build.test.mjs | 15 + scripts/validate.mjs | 69 + site/docusaurus.config.ts | 48 + site/package.json | 21 + site/sidebars.ts | 13 + site/src/pages/index.tsx | 17 + site/src/pages/search.tsx | 4 + site/src/theme/DocItem/Content/index.tsx | 15 + site/src/theme/MDXComponents/index.tsx | 3 + site/tsconfig.json | 1 + .../proto/weave/agent/v1/handshake.proto | 58 + .../proto/weave/agent/v1/module.proto | 70 + .../proto/weave/agent/v1/registry.proto | 59 + .../schema/channel-manifest.schema.json | 151 + .../schema/module-manifest.schema.json | 96 + .../pkg/spec/schema/vm-config-v1.schema.json | 144 + sources/repositories.yaml | 46 + sources/sources.lock.yaml | 144 + .../branding}/weave_mesh_platform.png | Bin .../branding}/weave_modular_systems.png | Bin .../branding}/weave_periodic_table_tile.png | Bin .../branding}/weave_woven_monogram.png | Bin static/img/weave.svg | 1 + tools/archify/LICENSE | 22 + tools/archify/PROVENANCE.json | 87 + tools/archify/SKILL.md | 137 + tools/archify/THIRD_PARTY_NOTICES.md | 56 + tools/archify/assets/template.html | 14787 ++++++++++++ tools/archify/bin/archify.mjs | 1990 ++ tools/archify/bin/open-artifact.mjs | 86 + tools/archify/bin/preview.mjs | 648 + tools/archify/bin/visual-check.mjs | 829 + tools/archify/brand-marks/README.md | 31 + tools/archify/brand-marks/catalog.json | 131 + tools/archify/delta/architecture-delta.mjs | 1214 + .../archify/examples/agent-run.lifecycle.json | 71 + .../examples/agent-tool-call.workflow.json | 94 + .../async-job-roundtrip.sequence.json | 61 + .../brand-aware-delivery.architecture.json | 47 + .../examples/cache-miss-request.sequence.json | 81 + .../checkout-platform.base.architecture.json | 31 + .../checkout-platform.head.architecture.json | 31 + .../examples/dataflow-product-analytics.html | 14898 ++++++++++++ .../deployment-release.lifecycle.json | 49 + .../examples/event-stream.dataflow.json | 57 + .../examples/incident-response.workflow.json | 64 + .../archify/examples/lifecycle-agent-run.html | 14847 ++++++++++++ .../examples/product-analytics.dataflow.json | 76 + .../production-deployment.architecture.json | 71 + .../examples/release-delivery.workflow.json | 62 + .../examples/sequence-cache-miss-request.html | 14913 ++++++++++++ tools/archify/examples/web-app-rendered.html | 14862 ++++++++++++ .../examples/web-app.architecture.json | 46 + .../workflow-agent-tool-call-rendered.html | 14904 ++++++++++++ tools/archify/migrations/workflow-v2.mjs | 279 + tools/archify/package.json | 37 + tools/archify/recipes/scenarios.mjs | 391 + .../archify/references/authoring-contract.md | 191 + tools/archify/references/brand-marks.md | 65 + tools/archify/references/delivery-contract.md | 112 + tools/archify/references/viewer-runtime.md | 45 + tools/archify/renderers/architecture/grid.mjs | 62 + .../architecture/render-architecture.mjs | 1078 + tools/archify/renderers/dataflow/README.md | 104 + .../renderers/dataflow/render-dataflow.mjs | 483 + tools/archify/renderers/lifecycle/README.md | 115 + .../renderers/lifecycle/render-lifecycle.mjs | 561 + tools/archify/renderers/sequence/README.md | 114 + .../renderers/sequence/render-sequence.mjs | 453 + .../archify/renderers/shared/brand-marks.mjs | 563 + tools/archify/renderers/shared/cli.mjs | 218 + .../renderers/shared/desktop-readability.mjs | 26 + .../archify/renderers/shared/diagnostics.mjs | 127 + .../renderers/shared/engineering-profiles.mjs | 157 + .../shared/generated-brand-marks.mjs | 2003 ++ .../renderers/shared/generated-validators.mjs | 13 + tools/archify/renderers/shared/geometry.mjs | 1423 ++ tools/archify/renderers/shared/i18n.mjs | 594 + .../renderers/shared/layout-report.mjs | 40 + tools/archify/renderers/shared/legend.mjs | 217 + .../archify/renderers/shared/output-path.mjs | 321 + .../renderers/shared/repository-evidence.mjs | 235 + tools/archify/renderers/shared/text-fit.mjs | 49 + tools/archify/renderers/shared/utils.mjs | 232 + tools/archify/renderers/shared/validator.mjs | 86 + tools/archify/renderers/workflow/README.md | 223 + .../renderers/workflow/render-workflow.mjs | 35 + .../renderers/workflow/workflow-compiler.mjs | 4400 ++++ .../workflow/workflow-migration-geometry.mjs | 144 + tools/archify/schemas/README.md | 211 + .../archify/schemas/architecture.schema.json | 176 + tools/archify/schemas/common.schema.json | 115 + tools/archify/schemas/dataflow.schema.json | 243 + tools/archify/schemas/lifecycle.schema.json | 266 + tools/archify/schemas/sequence.schema.json | 223 + tools/archify/schemas/workflow.schema.json | 428 + tools/archify/scripts/check-render-output.mjs | 835 + tools/archify/scripts/check-update.mjs | 1667 ++ .../archify/scripts/generate-brand-marks.mjs | 141 + tools/archify/scripts/generate-validators.mjs | 66 + tools/archify/scripts/render-examples.mjs | 26 + tools/archify/scripts/update-contract.mjs | 182 + tools/archify/skill-release.json | 10 + workload/README.md | 1 - 182 files changed, 138859 insertions(+), 1075 deletions(-) delete mode 100644 .azuredevops/PULL_REQUEST_TEMPLATE.md delete mode 100644 .azuredevops/pull_request_template/branches/dev.md delete mode 100644 .azuredevops/pull_request_template/branches/terraform.md delete mode 100644 .github/workflows/auto-merge-dependabot.yml delete mode 100644 .github/workflows/dependancy-review.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/docs-kit-release.yml delete mode 100644 .github/workflows/go-lint.yml delete mode 100644 .github/workflows/linter.yml delete mode 100644 .github/workflows/pr-title-validation.yml create mode 100644 .github/workflows/quality-gate.yml delete mode 100644 .github/workflows/release-please.yml create mode 100644 .github/workflows/source-refresh.yml delete mode 100644 .golangci.yml create mode 100644 .markdownlint-cli2.yaml create mode 100644 .node-version delete mode 100644 .pipelines/linter.yml delete mode 100644 .pipelines/pipeline.yml create mode 100644 deployment/README.md create mode 100644 diagrams/manifest.yaml create mode 100644 diagrams/sources/agent-core.json create mode 100644 diagrams/sources/documentation-architecture.json create mode 100644 diagrams/sources/guestweave-macos.json create mode 100644 diagrams/sources/guestweave-windows.json create mode 100644 diagrams/sources/hostweave.json create mode 100644 diagrams/sources/imageweave-construction.json create mode 100644 diagrams/sources/module-startup.json create mode 100644 diagrams/sources/platform-context.json create mode 100644 diagrams/sources/publishing-workflow.json delete mode 100644 docs/README.md create mode 100644 docs/development/architecture.mdx create mode 100644 docs/development/documentation.md create mode 100644 docs/development/roadmap.md delete mode 100644 docs/markdown-syntax-guide.md delete mode 100644 docs/media/MicrosoftAzure-32px.png delete mode 100644 docs/media/MicrosoftPowerShellCore-32px.png create mode 100644 docs/operations/publishing.mdx create mode 100644 docs/platform/architecture.mdx create mode 100644 docs/platform/compatibility.md create mode 100644 docs/platform/glossary.md create mode 100644 docs/platform/repositories.md create mode 100644 docs/products/agent-core/hld.mdx create mode 100644 docs/products/agent-modules/hld.mdx create mode 100644 docs/products/guestweave/index.md create mode 100644 docs/products/guestweave/macos.mdx create mode 100644 docs/products/guestweave/windows.mdx create mode 100644 docs/products/hostweave/hld.mdx create mode 100644 docs/products/imageweave/hld.mdx create mode 100644 docs/reference/contracts.md create mode 100644 docs/reference/index.md create mode 100644 docs/start-here/index.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 packages/docs-kit/LICENSE create mode 100644 packages/docs-kit/package.json create mode 100644 packages/docs-kit/schemas/page.schema.json create mode 100644 packages/docs-kit/src/Diagram.tsx create mode 100644 packages/docs-kit/src/Search.tsx create mode 100644 packages/docs-kit/src/styles.css create mode 100644 scripts/check-browser.mjs create mode 100644 scripts/check-links.mjs create mode 100644 scripts/contracts.mjs create mode 100644 scripts/deployment-manifest.mjs create mode 100644 scripts/diagrams.mjs create mode 100644 scripts/lib.mjs create mode 100644 scripts/prepare-companion.mjs create mode 100644 scripts/select-build.mjs create mode 100644 scripts/smoke-deployment.mjs create mode 100644 scripts/source-refresh.mjs create mode 100644 scripts/tests/evidence.test.mjs create mode 100644 scripts/tests/select-build.test.mjs create mode 100644 scripts/validate.mjs create mode 100644 site/docusaurus.config.ts create mode 100644 site/package.json create mode 100644 site/sidebars.ts create mode 100644 site/src/pages/index.tsx create mode 100644 site/src/pages/search.tsx create mode 100644 site/src/theme/DocItem/Content/index.tsx create mode 100644 site/src/theme/MDXComponents/index.tsx create mode 100644 site/tsconfig.json create mode 100644 sources/contracts/agent-core/proto/weave/agent/v1/handshake.proto create mode 100644 sources/contracts/agent-core/proto/weave/agent/v1/module.proto create mode 100644 sources/contracts/agent-core/proto/weave/agent/v1/registry.proto create mode 100644 sources/contracts/agent-core/schema/channel-manifest.schema.json create mode 100644 sources/contracts/agent-core/schema/module-manifest.schema.json create mode 100644 sources/contracts/oci/pkg/spec/schema/vm-config-v1.schema.json create mode 100644 sources/repositories.yaml create mode 100644 sources/sources.lock.yaml rename {docs/media => static/branding}/weave_mesh_platform.png (100%) rename {docs/media => static/branding}/weave_modular_systems.png (100%) rename {docs/media => static/branding}/weave_periodic_table_tile.png (100%) rename {docs/media => static/branding}/weave_woven_monogram.png (100%) create mode 100644 static/img/weave.svg create mode 100644 tools/archify/LICENSE create mode 100644 tools/archify/PROVENANCE.json create mode 100644 tools/archify/SKILL.md create mode 100644 tools/archify/THIRD_PARTY_NOTICES.md create mode 100644 tools/archify/assets/template.html create mode 100755 tools/archify/bin/archify.mjs create mode 100644 tools/archify/bin/open-artifact.mjs create mode 100644 tools/archify/bin/preview.mjs create mode 100644 tools/archify/bin/visual-check.mjs create mode 100644 tools/archify/brand-marks/README.md create mode 100644 tools/archify/brand-marks/catalog.json create mode 100644 tools/archify/delta/architecture-delta.mjs create mode 100644 tools/archify/examples/agent-run.lifecycle.json create mode 100644 tools/archify/examples/agent-tool-call.workflow.json create mode 100644 tools/archify/examples/async-job-roundtrip.sequence.json create mode 100644 tools/archify/examples/brand-aware-delivery.architecture.json create mode 100644 tools/archify/examples/cache-miss-request.sequence.json create mode 100644 tools/archify/examples/checkout-platform.base.architecture.json create mode 100644 tools/archify/examples/checkout-platform.head.architecture.json create mode 100644 tools/archify/examples/dataflow-product-analytics.html create mode 100644 tools/archify/examples/deployment-release.lifecycle.json create mode 100644 tools/archify/examples/event-stream.dataflow.json create mode 100644 tools/archify/examples/incident-response.workflow.json create mode 100644 tools/archify/examples/lifecycle-agent-run.html create mode 100644 tools/archify/examples/product-analytics.dataflow.json create mode 100644 tools/archify/examples/production-deployment.architecture.json create mode 100644 tools/archify/examples/release-delivery.workflow.json create mode 100644 tools/archify/examples/sequence-cache-miss-request.html create mode 100644 tools/archify/examples/web-app-rendered.html create mode 100644 tools/archify/examples/web-app.architecture.json create mode 100644 tools/archify/examples/workflow-agent-tool-call-rendered.html create mode 100644 tools/archify/migrations/workflow-v2.mjs create mode 100644 tools/archify/package.json create mode 100644 tools/archify/recipes/scenarios.mjs create mode 100644 tools/archify/references/authoring-contract.md create mode 100644 tools/archify/references/brand-marks.md create mode 100644 tools/archify/references/delivery-contract.md create mode 100644 tools/archify/references/viewer-runtime.md create mode 100644 tools/archify/renderers/architecture/grid.mjs create mode 100644 tools/archify/renderers/architecture/render-architecture.mjs create mode 100644 tools/archify/renderers/dataflow/README.md create mode 100644 tools/archify/renderers/dataflow/render-dataflow.mjs create mode 100644 tools/archify/renderers/lifecycle/README.md create mode 100644 tools/archify/renderers/lifecycle/render-lifecycle.mjs create mode 100644 tools/archify/renderers/sequence/README.md create mode 100644 tools/archify/renderers/sequence/render-sequence.mjs create mode 100644 tools/archify/renderers/shared/brand-marks.mjs create mode 100644 tools/archify/renderers/shared/cli.mjs create mode 100644 tools/archify/renderers/shared/desktop-readability.mjs create mode 100644 tools/archify/renderers/shared/diagnostics.mjs create mode 100644 tools/archify/renderers/shared/engineering-profiles.mjs create mode 100644 tools/archify/renderers/shared/generated-brand-marks.mjs create mode 100644 tools/archify/renderers/shared/generated-validators.mjs create mode 100644 tools/archify/renderers/shared/geometry.mjs create mode 100644 tools/archify/renderers/shared/i18n.mjs create mode 100644 tools/archify/renderers/shared/layout-report.mjs create mode 100644 tools/archify/renderers/shared/legend.mjs create mode 100644 tools/archify/renderers/shared/output-path.mjs create mode 100644 tools/archify/renderers/shared/repository-evidence.mjs create mode 100644 tools/archify/renderers/shared/text-fit.mjs create mode 100644 tools/archify/renderers/shared/utils.mjs create mode 100644 tools/archify/renderers/shared/validator.mjs create mode 100644 tools/archify/renderers/workflow/README.md create mode 100644 tools/archify/renderers/workflow/render-workflow.mjs create mode 100644 tools/archify/renderers/workflow/workflow-compiler.mjs create mode 100644 tools/archify/renderers/workflow/workflow-migration-geometry.mjs create mode 100644 tools/archify/schemas/README.md create mode 100644 tools/archify/schemas/architecture.schema.json create mode 100644 tools/archify/schemas/common.schema.json create mode 100644 tools/archify/schemas/dataflow.schema.json create mode 100644 tools/archify/schemas/lifecycle.schema.json create mode 100644 tools/archify/schemas/sequence.schema.json create mode 100644 tools/archify/schemas/workflow.schema.json create mode 100644 tools/archify/scripts/check-render-output.mjs create mode 100644 tools/archify/scripts/check-update.mjs create mode 100644 tools/archify/scripts/generate-brand-marks.mjs create mode 100644 tools/archify/scripts/generate-validators.mjs create mode 100644 tools/archify/scripts/render-examples.mjs create mode 100644 tools/archify/scripts/update-contract.mjs create mode 100644 tools/archify/skill-release.json delete mode 100644 workload/README.md diff --git a/.azuredevops/PULL_REQUEST_TEMPLATE.md b/.azuredevops/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index f6bc4ef..0000000 --- a/.azuredevops/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,22 +0,0 @@ -# Change - -***Feel free to remove this sample text*** ->Thank you for your contribution! Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. - -## Type of Change - -Please delete options that are not relevant. - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] This change requires a documentation update (Wiki) - -## Checklist - -- [ ] I'm sure there are no other open Pull Requests for the same update/change. -- [ ] My corresponding Pipelines / Checks run clean and green without any errors or warnings. -- [ ] My code follows the style guidelines of this project. -- [ ] I have commented my code, particularly in hard-to-understand areas. -- [ ] I have made corresponding changes to the documentation (readme). -- [ ] I did format my code. diff --git a/.azuredevops/pull_request_template/branches/dev.md b/.azuredevops/pull_request_template/branches/dev.md deleted file mode 100644 index 8e9c3a3..0000000 --- a/.azuredevops/pull_request_template/branches/dev.md +++ /dev/null @@ -1,20 +0,0 @@ -# Change for dev branch - ->Please describe your change ***here***. - -## Type of Change - -Please delete options that are not relevant. - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] This change requires a documentation update (Wiki) - -## Checklist - -- [ ] I'm sure there are no other open Pull Requests for the same update/change. -- [ ] My code follows the style guidelines of this project. -- [ ] I have commented my code, particularly in hard-to-understand areas (if necessary). -- [ ] I have made corresponding changes to the documentation (readme). -- [ ] I did format my code. \ No newline at end of file diff --git a/.azuredevops/pull_request_template/branches/terraform.md b/.azuredevops/pull_request_template/branches/terraform.md deleted file mode 100644 index 0decb03..0000000 --- a/.azuredevops/pull_request_template/branches/terraform.md +++ /dev/null @@ -1,21 +0,0 @@ -# Change - ->Please describe your change ***here***. - -## Type of Change - -Please delete options that are not relevant. - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] This change requires a documentation update (Wiki) - -## Checklist - -- [ ] I agree with the `Terraform Speculative Plan` and its infrastructure changes (`add`, `remove` or `destroy`). -- [ ] I'm sure there are no other open Pull Requests for the same update/change. -- [ ] My code follows the style guidelines of this project. -- [ ] I have commented my code, particularly in hard-to-understand areas (if necessary). -- [ ] I have made corresponding changes to the documentation (readme). -- [ ] I did format my code (`terraform fmt`). \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2d1d564..bcdecaf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,13 +1 @@ -# See for instructions on this file - -# These owners will be the default owners for everything in -# the repo. Unless a later match takes precedence, -# @global-owner1 and @global-owner2 will be requested for -# review when someone opens a pull request. - * @ShocOne - -.azuredevops/** @ShocOne -.github/** @ShocOne -.pipelines/** @ShocOne -workload/** @ShocOne diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 23ed5dc..73e1fef 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,49 +1,14 @@ -# Pull Request Description +Describe the knowledge change and who it helps. -## Summary -[Provide a brief description of the changes in this PR] +## Evidence -### Issue Reference -Fixes #[Issue Number] +- Source commits and inspected implementation paths: +- Contract or runtime checks performed: +- Compatibility limits or unresolved claims: -### Motivation and Context -- Why is this change needed? -- What problem does it solve? -- If it fixes an open issue, please link to the issue here +## Validation -### Dependencies -- List any dependencies that are required for this change -- Include any configuration changes needed -- Note any version updates required - -## Type of Change -Please mark the relevant option with an `x`: -- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) -- [ ] ✨ New feature (non-breaking change which adds functionality) -- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] 📝 Documentation update (Wiki/README/Code comments) -- [ ] ♻️ Refactor (code improvement without functional changes) -- [ ] 🎨 Style update (formatting, renaming) -- [ ] 🔧 Configuration change -- [ ] 📦 Dependency update - -## Testing -- [ ] I have added unit tests that prove my fix is effective or that my feature works -- [ ] New and existing unit tests pass locally with my changes -- [ ] I have tested this code in the following browsers/environments: [list environments] - -## Quality Checklist -- [ ] I have reviewed my own code before requesting review -- [ ] I have verified there are no other open Pull Requests for the same update/change -- [ ] All CI/CD pipelines pass without errors or warnings -- [ ] My code follows the established style guidelines of this project -- [ ] My comments are used only when necessary, ideally where the codes purpose is not self explanatory (eg: necessary magic numbers) -- [ ] I have added necessary documentation (if appropriate) -- [ ] I have made corresponding changes to the README and other relevant documentation -- [ ] My changes generate no new warnings - -## Screenshots/Recordings (if appropriate) -[Add screenshots or recordings that demonstrate the changes] - -## Additional Notes -[Add any additional information that might be helpful for reviewers] +- [ ] Content, evidence, build and link checks pass. +- [ ] Diagrams pass showcase validation and browser evidence. +- [ ] An image-capable reviewer inspected both diagram themes. +- [ ] Any export from a private source has publication review. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 44b0540..1208611 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,29 +1,13 @@ -# See GitHub's documentation for more information on this file: -# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates version: 2 updates: - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: npm + directory: / schedule: - # Check for updates to GitHub Actions every week - interval: "weekly" - commit-message: - # Prefix all commit messages with "chore(deps): " - # This follows conventional commits and works with release-please - prefix: "chore(deps)" - prefix-development: "chore(deps-dev)" - include: "scope" - - # Maintain dependencies for Go modules - - package-ecosystem: "gomod" - directory: "/" + interval: weekly + groups: + docusaurus: + patterns: ['@docusaurus/*'] + - package-ecosystem: github-actions + directory: / schedule: - # Check for updates to Go modules every week - interval: "weekly" - commit-message: - # Prefix all commit messages with "chore(deps): " - # This follows conventional commits and works with release-please - prefix: "chore(deps)" - prefix-development: "chore(deps-dev)" - include: "scope" \ No newline at end of file + interval: weekly diff --git a/.github/workflows/auto-merge-dependabot.yml b/.github/workflows/auto-merge-dependabot.yml deleted file mode 100644 index 5caaf02..0000000 --- a/.github/workflows/auto-merge-dependabot.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Auto-Merge Dependabot - - -on: - pull_request: - branches: [main] - -permissions: - pull-requests: write - contents: write - -jobs: - dependabot-merge: - name: '🤖 Auto-Merge Dependabot' - runs-on: ubuntu-latest - - if: ${{ github.actor == 'dependabot[bot]' }} - - steps: - - - name: Harden Runner - uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df # v2.18.0 - with: - egress-policy: audit - - - name: Dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - - name: Enable auto-merge for Dependabot PRs - # Only if version bump is not a major version change - if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}} - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/dependancy-review.yml b/.github/workflows/dependancy-review.yml deleted file mode 100644 index a798739..0000000 --- a/.github/workflows/dependancy-review.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: 'Dependency Review' -on: [pull_request] - -permissions: - contents: read - -jobs: - dependency-review: - name: '🔎 Dependency Review' - runs-on: ubuntu-latest - steps: - - - name: Harden Runner - uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df # v2.18.0 - with: - egress-policy: audit - - - name: 'Checkout Repository' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - - name: 'Dependency Review' - uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..069bcc0 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,79 @@ +name: Publish documentation +on: + workflow_dispatch: + inputs: + build_run: + description: 'Successful main Documentation quality gate run ID (internal owner release)' + required: true + type: string + workflow_run: + workflows: [Documentation quality gate] + branches: [main] + types: [completed] +permissions: + contents: read + actions: read +concurrency: + group: docs-production + cancel-in-progress: false +jobs: + select: + if: vars.DOCS_DEPLOY_ENABLED == 'true' && ((github.event_name == 'workflow_run' && vars.DOCS_AUDIENCE != 'internal' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_repository.full_name == github.repository) || (github.event_name == 'workflow_dispatch' && vars.DOCS_AUDIENCE == 'internal' && github.ref == 'refs/heads/main' && github.actor == vars.DOCS_RELEASE_OWNER)) + runs-on: ubuntu-latest + outputs: + commit: ${{ steps.build.outputs.commit }} + run: ${{ steps.build.outputs.run }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: main + persist-credentials: false + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .node-version + - id: build + env: + GH_TOKEN: ${{ github.token }} + DOCS_BUILD_RUN: ${{ github.event.workflow_run.id || inputs.build_run }} + run: node scripts/select-build.mjs + publish: + needs: select + runs-on: ubuntu-latest + environment: production + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: ${{ needs.select.outputs.commit }} + persist-credentials: false + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .node-version + cache: npm + - run: npm ci --ignore-scripts + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: documentation-site + path: site/build + run-id: ${{ needs.select.outputs.run }} + github-token: ${{ github.token }} + - name: Bind deployment to verified build + env: + DOCS_COMMIT: ${{ needs.select.outputs.commit }} + DOCS_RUN: ${{ needs.select.outputs.run }} + run: node scripts/deployment-manifest.mjs + - name: Deploy production + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + DOCS_PAGES_PROJECT: ${{ vars.DOCS_PAGES_PROJECT }} + DOCS_COMMIT: ${{ needs.select.outputs.commit }} + run: npx wrangler pages deploy site/build --project-name "$DOCS_PAGES_PROJECT" --branch main --commit-hash "$DOCS_COMMIT" + - name: Verify public deployment or internal Access boundary + env: + DOCS_CANONICAL_URL: ${{ vars.DOCS_CANONICAL_URL }} + DOCS_AUDIENCE: ${{ vars.DOCS_AUDIENCE || 'public' }} + DOCS_PAGES_PROJECT: ${{ vars.DOCS_PAGES_PROJECT }} + DOCS_COMMIT: ${{ needs.select.outputs.commit }} + CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }} + CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }} + run: node scripts/smoke-deployment.mjs diff --git a/.github/workflows/docs-kit-release.yml b/.github/workflows/docs-kit-release.yml new file mode 100644 index 0000000..ccd182d --- /dev/null +++ b/.github/workflows/docs-kit-release.yml @@ -0,0 +1,24 @@ +name: Release shared documentation kit +on: + push: + tags: ['docs-kit-v*'] +permissions: + contents: write +jobs: + release: + if: github.repository == 'weaveplatform/weaveplatform.run' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .node-version + - name: Check tag and package version match + run: node -e 'const p = require("./packages/docs-kit/package.json"); if (process.env.GITHUB_REF_NAME !== "docs-kit-v" + p.version) process.exit(1)' + - run: npm pack --workspace packages/docs-kit + - name: Publish immutable kit archive and checksum + env: + GH_TOKEN: ${{ github.token }} + run: | + sha256sum weaveplatform-docs-kit-*.tgz > docs-kit.sha256 + gh release create "$GITHUB_REF_NAME" weaveplatform-docs-kit-*.tgz docs-kit.sha256 --title "$GITHUB_REF_NAME" --notes 'Shared documentation theme, components and page schema. Pin the archive checksum before upgrading the private site.' diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml deleted file mode 100644 index f582e2d..0000000 --- a/.github/workflows/go-lint.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: go | Linter - -on: - workflow_dispatch: - pull_request: - types: [opened, synchronize] - paths: - - '.github/workflows/go-lint.yml' - - '.golangci.yml' - - '**/*.go' - -permissions: - contents: read - pull-requests: write # Needed for "only-new-issues" and commenting on PR - issues: write # Needed for commenting on PR - -jobs: - golint: - name: '✨ Run golangci-lint' - runs-on: ubuntu-24.04-arm - - steps: - - name: Harden Runner - uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df # v2.18.0 - with: - egress-policy: audit - - - name: Check Out - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 # Get full history for full merge-base detection - - - name: Set up Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 - with: - go-version-file: 'go.mod' - cache-dependency-path: 'go.sum' - cache: true - go-version: stable - - - name: Run golangci-lint - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 - with: - version: latest - args: --timeout=30m --verbose --config=./.golangci.yml --issues-exit-code=0 - only-new-issues: true - github-token: ${{ secrets.GITHUB_TOKEN }} - skip-cache: false # restore and save cache - skip-save-cache: false # allow saving any new cache - cache-invalidation-interval: 7 # auto-invalidate (refresh) once per week - - # Save artifacts on failure - - name: Save artifacts - if: failure() - run: | - mkdir -p wr_actions - echo ${{ github.repository_owner }} > wr_actions/ghowner.txt - echo ${{ github.event.repository.name }} > wr_actions/ghrepo.txt - echo ${{ github.event.pull_request.number }} > wr_actions/prnumber.txt - - - name: Upload artifacts - if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: artifact - path: wr_actions - - # Comment on failure - - name: Get run url - if: failure() - run: | - echo "gha_url=https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}" >> $GITHUB_ENV - - - name: Send build failure comment - if: failure() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - result-encoding: string - script: | - github.rest.issues.createComment({ - issue_number: ${{ github.event.pull_request.number }}, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'Build failure \n\n This pull request contains a build failure which needs addressed [here](${{ env.gha_url}}) .' - }) \ No newline at end of file diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml deleted file mode 100644 index f60be9b..0000000 --- a/.github/workflows/linter.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Linter - -on: - pull_request: - paths-ignore: - - '.github/**' - -permissions: - contents: read - packages: read - statuses: write # To report GitHub Actions status checks - -jobs: - build: - name: '✨ Linter' - runs-on: ubuntu-latest - timeout-minutes: 25 - steps: - - - name: Harden Runner - uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df # v2.18.0 - with: - egress-policy: audit - - - name: Checkout Code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - - name: Lint Code Base with Super-Linter - uses: super-linter/super-linter@61abc07d755095a68f4987d1c2c3d1d64408f1f9 # v8.5.0 - env: - DEFAULT_BRANCH: origin/main - VALIDATE_ALL_CODEBASE: false # tells the linter to only check files that have been modified in the current pull request - VALIDATE_GO: true - VALIDATE_GOFMT: true - VALIDATE_GOCYCLO: true - VALIDATE_GOVET: true - VALIDATE_GOLANGCI_LINT: true - VALIDATE_CPD: false # Disable CPD (Copy Paste Detection) for all languages - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # To report GitHub Actions status checks - MARKDOWN_CONFIG_FILE: .markdown-lint.yml - FILTER_REGEX_EXCLUDE: | - .*\.md$| # Exclude all markdown files - .*test/.*| # Exclude all files in any test directory - .*_test\.go$| # Exclude Go test files (e.g., *_test.go) - .*\.json$ # Exclude all JSON files diff --git a/.github/workflows/pr-title-validation.yml b/.github/workflows/pr-title-validation.yml deleted file mode 100644 index f882ea8..0000000 --- a/.github/workflows/pr-title-validation.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: PR Title Validation - -on: - pull_request: - types: [opened, edited, synchronize, reopened] - -jobs: - validate-pr-title: - name: '✅ Validate PR Title' - runs-on: ubuntu-latest - steps: - - - name: Harden Runner - uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df # v2.18.0 - with: - egress-policy: audit - - - name: Check PR Title - run: | - PR_TITLE="${{ github.event.pull_request.title }}" - PATTERN="^(([Ff]eat|[Ff]ix|[Dd]ocs|[Ss]tyle|[Rr]efactor|[Tt]est|[Cc]hore|[Bb]uild|[Cc]i|[Pp]erf)(\(.+\))?: .+|dependabot.*)$" - - if ! echo "$PR_TITLE" | grep -qE "$PATTERN"; then - echo "❌ ERROR: Invalid PR title format" - echo "" - echo "Got PR title: $PR_TITLE" - echo "" - echo "PR titles must:" - echo "1. Start with one of these prefixes (case-insensitive first letter):" - echo " - feat / Feat (for new features)" - echo " - fix / Fix (for bug fixes)" - echo " - docs / Docs (for documentation)" - echo " - style / Style (for formatting)" - echo " - refactor / Refactor (for code restructuring)" - echo " - test / Test (for adding tests)" - echo " - chore / Chore (for maintenance)" - echo " - build / Build (for build system)" - echo " - ci / CI (for CI/CD)" - echo " - perf / Perf (for performance)" - echo "" - echo "2. Optionally include a scope in parentheses after the type" - echo "" - echo "3. Include a description after a colon and space" - echo "" - echo "Examples:" - echo "✅ feat: add new resource" - echo "✅ feat(api): add new endpoint" - echo "✅ fix: resolve connection timeout" - echo "✅ fix(database): fix query performance" - echo "✅ dependabot: bump lodash from 4.17.20 to 4.17.21" - exit 1 - fi - - echo "✅ PR title '$PR_TITLE' follows the conventional commit format and is compatible with the release-please" diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml new file mode 100644 index 0000000..88af1e0 --- /dev/null +++ b/.github/workflows/quality-gate.yml @@ -0,0 +1,70 @@ +name: Documentation quality gate +on: + push: + branches: [main] + pull_request: + workflow_dispatch: +permissions: + contents: read +concurrency: + group: docs-quality-${{ github.ref }} + cancel-in-progress: true +env: + DOCS_AUDIENCE: ${{ vars.DOCS_AUDIENCE || 'public' }} +jobs: + validate: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .node-version + cache: npm + - run: npm ci + - run: npm run validate && npm test + - run: npx markdownlint-cli2 + - run: npm run build && npm run check:links + - run: npm run typecheck + - name: Browser evidence for diagrams + run: npm run diagrams:visual + - name: Site browser checks + env: + CHROME_BIN: /usr/bin/google-chrome + run: npm run check:browser + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: always() + with: + name: documentation-evidence + path: .artifacts/ + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: documentation-site + path: site/build/ + preview: + needs: validate + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && vars.DOCS_DEPLOY_ENABLED == 'true' + runs-on: ubuntu-latest + environment: preview + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .node-version + cache: npm + - run: npm ci --ignore-scripts + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: documentation-site + path: site/build + - name: Deploy trusted preview artifact + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + DOCS_PAGES_PROJECT: ${{ vars.DOCS_PAGES_PROJECT }} + PREVIEW_BRANCH: pr-${{ github.event.pull_request.number }} + run: npx wrangler pages deploy site/build --project-name "$DOCS_PAGES_PROJECT" --branch "$PREVIEW_BRANCH" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml deleted file mode 100644 index a02cd74..0000000 --- a/.github/workflows/release-please.yml +++ /dev/null @@ -1,55 +0,0 @@ -on: - push: - branches: - - main - -permissions: - contents: write - pull-requests: write - -name: release-please - -jobs: - release-please: - name: '🔖 Release Please' - runs-on: ubuntu-latest - steps: - - - name: Harden Runner - uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df # v2.18.0 - with: - egress-policy: audit - - # The org's release-please GitHub App rather than a personal token: the - # token is minted per run and expires in an hour, and it belongs to the - # org rather than to whoever created the PAT — a PAT dies with its - # owner's access and takes every repo's releases with it. - # - # actions:write is the load-bearing permission. Tags pushed with the - # default GITHUB_TOKEN deliberately do not trigger workflows, which - # would leave every release tagged and unbuilt; the App can. - # - # continue-on-error keeps releases working where the App is not yet - # installed or its key is not provisioned: the next step falls back to - # the PAT and says so, because a silent fallback is how a repo ends up - # believing it uses the App when it never has. - - uses: actions/create-github-app-token@v2 - id: app-token - if: ${{ vars.RP_APP_ID != '' }} - continue-on-error: true - with: - app-id: ${{ vars.RP_APP_ID }} - private-key: ${{ secrets.RP_APP_PRIVATE_KEY }} - - - name: Which credential - run: | - if [ -n "${{ steps.app-token.outputs.token }}" ]; then - echo "release-please is using the org's release-please App" - else - echo "::warning::App token unavailable (RP_APP_ID/RP_APP_PRIVATE_KEY unset, or the App is not installed here) — falling back to RELEASE_PLEASE_PAT" - fi - - - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 - with: - release-type: terraform-module - token: ${{ steps.app-token.outputs.token || secrets.RELEASE_PLEASE_PAT }} \ No newline at end of file diff --git a/.github/workflows/source-refresh.yml b/.github/workflows/source-refresh.yml new file mode 100644 index 0000000..79d0b46 --- /dev/null +++ b/.github/workflows/source-refresh.yml @@ -0,0 +1,31 @@ +name: Review upstream evidence changes +on: + workflow_dispatch: + schedule: + - cron: '17 7 * * 1' +permissions: + contents: write + pull-requests: write +jobs: + refresh: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .node-version + cache: npm + - run: npm ci --ignore-scripts + - name: Compare pinned implementation evidence + env: + GH_TOKEN: ${{ secrets.DOCS_SOURCE_TOKEN || github.token }} + DOCS_AUDIENCE: ${{ vars.DOCS_AUDIENCE || 'public' }} + run: node scripts/source-refresh.mjs --write + - uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7 + with: + branch: docs/upstream-evidence-review + add-paths: sources/drift.json + commit-message: 'docs: report changed upstream implementation evidence' + title: 'Review changed implementation evidence' + body: 'Review the changed files against affected pages and diagrams. This proposal does not change source pins, prose or verification status.' + draft: true diff --git a/.gitignore b/.gitignore index 8ee7cb1..27cfbdf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,13 @@ -# macOS +node_modules/ +site/.docusaurus/ +site/build/ +static/diagrams/ +static/contracts/ +.artifacts/ +.companion/ +.env +.env.* +!.env.example .DS_Store - -# VSCode -.vs -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Build and temp files -/build/ -/tmp/ *.log -*.bak - -# Windows-specific -*.winfile eol=crlf - -# Go development -*.test -/vendor/ -/bin/ -/dist/ -/pkg/ -coverage.out -coverage.html - -# Python virtual environments -scripts/python/venv/ - -# Python cache and compiled files -__pycache__/ -*.py[cod] -*$py.class -*.pyo -.pytest_cache/ -*.egg-info/ - -# AI instructions -.claude -.cursor - -# Binaries and executables -*.dll -*.exe -*.dylib -*.so \ No newline at end of file +*.tgz diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 8d5c44b..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,182 +0,0 @@ -version: "2" - -run: - timeout: 30m - # CI mode: Fails if go.mod needs updates, ensuring dependencies are properly declared - modules-download-mode: readonly - allow-parallel-runners: true - allow-serial-runners: false - concurrency: 2 # Set to 2 to avoid overwhelming the CI server - -issues: - # Don’t cap lint errors per linter—report everything in new code. - max-issues-per-linter: 0 - - # Don’t limit duplicates by text—if the same error crops up on multiple lines, show them all. - max-same-issues: 0 - - # Don’t dedupe by line; every instance matters. - uniq-by-line: false - - # Only fail on new issues introduced by new PR's. - new: true - - # Define the “base” for “new” issues as the merge-base against main. - new-from-merge-base: main - - # Also catch anything in new or modified files, even outside the diff hunk. - whole-files: true - - # Automatically apply any fixable issues (like gofmt/goimports). - fix: true - - -linters: - default: none - enable: - # Core correctness & compile checks - - govet # catches suspicious Go constructs - - staticcheck # static analyses (nil‐checks, API misuse, etc.) - - errcheck # ensures you never ignore an error return - - unused # finds dead code we no longer need - - ineffassign # flags assignments that have no effect - - # error-handling best practices - - errorlint # flag missing %w wraps - - err113 # enforce correct error-handling patterns - - # Terraform‐specific safety & resource handling - - bodyclose # makes sure every HTTP response body is closed - - gosec # scans for security issues (e.g. hardcoded credentials) - - # Specifically important for API clients (Microsoft 365 API) - - contextcheck # check whether function uses a non-inherited context - - noctx # finds sending http request without context.Context - - nilnil # checks that there is no simultaneous return of nil error and invalid value - - # Quality & style - - gocritic # catches a variety of performance/style bugs - - prealloc # recommends when you can preallocate slices/maps - - misspell # finds typos in comments, docs, & variable names - - gocyclo # warn on high cyclomatic complexity - - wrapcheck # checks that errors from external packages are wrapped - - # API/Terraform specific - - musttag # enforce field tags in (un)marshaled structs - - tagliatelle # checks struct tags (important for JSON/XML APIs) - - # 3) Per-linter configuration - settings: - nakedret: - max-func-lines: 40 - - # 4) Issue-filtering (not disabling analysis, just silencing reports) - exclusions: - # skip generated files strictly matching Go’s “Code generated … DO NOT EDIT.” - generated: strict - - # warn if any of these exclusions never match - warn-unused: true - - # apply standard “false positive” presets - presets: - - comments - - std-error-handling - - common-false-positives - - legacy - - # fine-grained rules to drop issues by path, linter, text, or source - rules: - # in test files, silence these noisy checks - - path: _test\.go - linters: - - gocyclo - - errcheck - - dupl - - gosec - - # only run “forbidigo” on tests (silence everywhere else) - - path-except: _test\.go - linters: - - forbidigo - - # silence known gosec warning in this vendored code - - path: internal/hmac/ - text: "weak cryptographic primitive" - linters: - - gosec - - # drop staticcheck’s SA9003 messages - - linters: - - staticcheck - text: "SA9003:" - - # exclude “lll” warnings on go:generate lines - - linters: - - lll - source: "^//go:generate " - - # exclude entire files from reporting (still analyzed) - paths: - - ".*\\.gen\\.go$" # Generated Go files - - ".*_test\\.go$" # Test files (already handled with more specific rules) - - "examples/.*" # Example code for documentation - - "tools/.*" # Build/helper tools - - "scripts/.*" # Scripts for development - - "docs/.*\\.go$" # Documentation generators - - # Never exclude these critical paths, even if they match patterns above - paths-except: - - "internal/.*\\.go$" # Core provider logic - -formatters: - # Only run the formatters you actually want to auto‐fix in CI - enable: - - gofumpt # strict Go formatting (superset of gofmt) - - goimports # fixes imports (add/remove/sort) - - gci # groups & sorts import blocks - - golines # wraps long lines to a max length (e.g. 120 chars) - - # Per‐formatter settings (optional—only if you need to tweak defaults) - settings: - gci: - # customize your import sections order: standard, external, project - sections: - - Standard - - Default - - Prefix(github.com/weaveplatform) - - # Exclude generated or special‐case files from formatting checks - exclusions: - warn-unused: true # error if any of the below rules never match - generated: strict # only skip files with exact “// Code generated … DO NOT EDIT.” - paths: - - ".*\\.gen\\.go$" # ignore any *.gen.go files - - "internal/tools/.*" # ignore codegen tools dir - -output: - formats: - # 1) Text format for the Action log - text: - # Send lint results to stderr so the Action log shows them even when piped - path: stderr - # Include the linter name after each issue - print-linter-name: true - # Show the source lines for each issue - print-issued-lines: true - # Keep ANSI colors enabled for readability in the Actions console - colors: true - - # 2) SARIF format for GitHub Code Scanning integration - sarif: - path: golangci-lint-report.sarif - - # Use relative paths (from the module root) in output - path-mode: "" - - sort-order: - - severity - - file - - # Show summary statistics at the end of the report - show-stats: true diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml new file mode 100644 index 0000000..629ea50 --- /dev/null +++ b/.markdownlint-cli2.yaml @@ -0,0 +1,11 @@ +config: + MD013: false + MD033: false + MD024: false + MD025: false +globs: + - docs/**/*.md + - docs/**/*.mdx + - README.md + - CONTRIBUTING.md + - SECURITY.md diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..de889f8 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.23.3 diff --git a/.pipelines/linter.yml b/.pipelines/linter.yml deleted file mode 100644 index 968b5d1..0000000 --- a/.pipelines/linter.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: SuperLinter - -trigger: -- main - -pool: - vmImage: ubuntu-latest - -jobs: - - - job: lint - displayName: Linter - steps: - - script: | - docker pull github/super-linter:latest - docker run -e RUN_LOCAL=true -v $(System.DefaultWorkingDirectory):/tmp/lint github/super-linter - displayName: 'Lint Code Base' \ No newline at end of file diff --git a/.pipelines/pipeline.yml b/.pipelines/pipeline.yml deleted file mode 100644 index bf35cc0..0000000 --- a/.pipelines/pipeline.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: "Sample Pipeline" - -trigger: -- main - -pool: - vmImage: ubuntu-latest - -jobs: - - - job: Job - displayName: "Job" - steps: - - - checkout: self - fetchDepth: 0 - persistCredentials: true - - - task: Bash@3 - displayName: "Bash" - inputs: - targetType: "inline" - script: | - echo Bash - - - task: PowerShell@2 - displayName: "PowerShell" - inputs: - targetType: "inline" - script: | - echo PowerShell diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index bbb2e31..f1a5f24 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,9 @@ a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +reported privately to the [repository maintainers](https://github.com/weaveplatform/weaveplatform.run/blob/main/.github/CODEOWNERS). +If you do not have a private contact route, open a minimal issue requesting one +without including incident details. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bad7309..9d897e9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,17 +1,13 @@ -# Contribution +# Contributing -Thanks for considering contributing to this project! We are really glad you are reading this, because we need volunteer developers to help this project come to fruition. +Start with the [documentation workflow](docs/development/documentation.md). Explain the knowledge change, its audience and the implementation evidence used to verify it. -Please note we have a code of conduct, please follow it in all your interactions with the project. +- Inspect entrypoints, handlers, interfaces and deployment files before relying on an existing README. +- Record exact source commits and paths. Separate static inspection from runtime acceptance. +- Update prose and diagram sources together when a component or relationship changes. +- Add private implementation evidence and runbooks to `weaveplatform.dev`; publicly exported contracts require publication review. +- Keep generated contract definitions authoritative upstream. -## Issues +Run the validation, build, link and browser checks listed in the README. Inspect Archify screenshots in both themes before approval. CI measurements are evidence for containment, not an automatic judgement of visual polish. -If you find any bugs, please file an issue in the [GitHub issues][GitHubIssues] page. Please fill out the provided template with the appropriate information. - -If you are taking the time to mention a problem, even a seemingly minor one, it is greatly appreciated, and a totally valid contribution to this project. Thank you! - - - - -[GitHubIssues]: -[Contributing]: CONTRIBUTING.md +Submit documentation defects through [this repository's issues](https://github.com/weaveplatform/weaveplatform.run/issues). For confidential findings use the private companion or GitHub's private security-reporting feature. diff --git a/README.md b/README.md index 3b881e6..98a1f21 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,58 @@ -## Platform tier -weaveplatform-api -├── proto/ # gRPC IDL -│ ├── agent/v1/ # core ↔ module: handshake, host services, lifecycle -│ ├── guestweave/v1/ # per-service RPC surfaces -│ ├── hostweave/v1/ -│ └── gateweave/v1/ -├── openapi/ # REST surfaces (generated from proto where they overlap) -│ ├── guestweave.yaml -│ └── hostweave.yaml -├── events/ # event envelopes, topic definitions -├── schemas/ # shared domain types: Device, Session, Identity, Error, AuthClaims -├── gen/go/ # generated Go — the only thing consumers import -└── buf.yaml # breaking-change detection in CI - -weaveplatform-sdk -├── errors/ # structured error taxonomy -├── log/ # slog conventions, redaction -├── config/ # schema, precedence, validation -├── retry/ # backoff, circuit breaker -├── platform/ # seam over go-bindings-*: capability-shaped, not API-shaped -│ ├── platform_darwin.go -│ ├── platform_windows.go -│ └── platform_linux.go -└── module/ # module scaffolding: Module iface, Host client, serve loop - -weaveplatform-agent -├── cmd/ -│ ├── weaveboot/ # supervises core; only thing launchd/SCM starts -│ ├── weaveagentd/ # core daemon (root/SYSTEM) — Zone A, CGO_ENABLED=0 -│ ├── weaveagent/ # CLI, thin client over local socket -│ └── weaveportal/ # user-session UI — Zone B, AppKit/WinUI3 -├── internal/ -│ ├── identity/ # device identity, attestation, TPM/Secure Enclave, enrolment -│ ├── transport/ # GateWeave channel; vsock/HvSocket when guest -│ ├── policy/ # fetch, verify, evaluate, notify -│ ├── store/ # namespaced, encrypted at rest -│ ├── supervisor/ # spawn, verify signature, privilege drop, health, swap, drain -│ ├── capability/ # host probe; gates module launch -│ ├── lifecycle/ # fetch, stage, promote, N-1 rollback -│ ├── ui/ # surface brokerage — core never draws -│ └── bus/ # module event fan-out -├── template/ # scaffolding for a new product module -└── packaging/ # pkg, MSI, deb — the immutable install footprint - -weaveplatform-manifest -├── channels/ # signed: which module versions constitute a known-good set -│ ├── stable.json -│ ├── beta.json -│ └── pinned/ # self-hosted, mapped to service versions -└── keys/ # signing chain, rotation policy - -## Product tier -weave -├── cmd/weave/# the service -├── internal/ # the internal components of the product -├── agent/ # module binary — ships and versions with the product -│ └── cmd/weave-agent/ -├── sdk/ # client library for this service -└── api/ # surfaces used by nobody else (shared ones → weaveplatform-api) - -appweave deviceweave labweave remoteweave sightweave - ├─ agent/ ├─ agent/ ├─ agent/ ├─ agent/ ├─ agent/ - -gateweave hostweave # server-side only, no module - -guestweave ├─ agent/ -guestweave-macos # Virtualization / Hypervisor.framework -guestweave-windows # HCS -guestweave-linux # libvirt - -## guestweave -### product description: -a cli tool for guest vm management. Not bare metal, but a vm manager for guest vm management. -3 seperate vm mangers exist covering linux, windows and macos hosts. -linux can run linux on linux and windows on linux. -macos can run mac on mac, linux on mac and windows on mac. -windows can run windows on windows and linux on windows. - -Windows engine is built upon hcs. macos engine is built on virtualization.framework for macos and linux -and Hypervisor.framework for windows. linux engine is built upon libvirt. -vm's can run headless and headed for all guest/vm combinations. All cli operations are exposed via a restful api. - -supports commands like create, start, stop, restart, migrate, clone, snapshot, suspend, migrate, prune and delete -guest vms. Additionally it supports the ability to run multiple guest vms on the same host. -macOS images are provisioned from a apple CDW supplied IPSW. Windows images are provisioned from a microsoft CDN. -Linux images are provisioned from a linux distribution CDN. Remote commands are supported for all guest vms, such -as resolve ip, ssh, login, logout and restart. All OS install options can be driven either as attended (via the ui) -or unattended using OS specific kickstart tools or keypress scripts. - -A basic ui is provided to allow for the management of guest vms as well. But tool is primarily expected to be used via the cli. - -## hostweave -### product description: -a host orchestrator for host management. can leverage guestweave to call on the features of the guest vm manager orchestrator -deploys into cloud. +# Weave platform knowledge base + +This repository is the canonical source for public Weave documentation and its website, **weaveplatform.run**. It serves consumers, developers and operators. Confidential implementation evidence and operational knowledge live in the private **weaveplatform.dev** companion. + +The first release covers the platform HLD and GuestWeave, HostWeave, Imageweave, agent-core and agent-modules. Diagrams use **Archify**. Claims follow inspected code at exact commits; the compatibility register makes incomplete integrations explicit. + +## Local use + +Use Node **22.23.3** and npm: + +```bash +npm ci +npm run start +``` + +For the complete static website, including search: + +```bash +npm run build +npm run serve +``` + +## Verification + +```bash +npm run validate +npm test +npm run build +npm run check:links +npm run typecheck +npm run diagrams:visual +npm run check:browser +``` + +Browser checks use installed Chrome on macOS and the runner browser in CI. Override the site browser with `CHROME_BIN` if necessary. Archify uses its packaged browser discovery. Its delivery receipt, automated browser evidence and perceptual review are separate claims. + +## Layout + +| Location | Owns | +| --- | --- | +| `docs/` | Authored product and platform knowledge | +| `diagrams/sources/` | Canonical typed diagram specifications | +| `sources/` | Repository inventory, exact source pins and reviewed contract snapshots | +| `site/` | Docusaurus navigation, pages and static build | +| `packages/docs-kit/` | Shared theme, components and content schema | +| `tools/archify/` | Checksummed renderer distribution and third-party notices | +| `scripts/` | Content, evidence, diagram, browser and publication checks | +| `deployment/` | Hosting prerequisites and release procedures | + +See [contribution guidance](CONTRIBUTING.md), [publishing setup](deployment/README.md) and the [content roadmap](docs/development/roadmap.md). + +## Companion bootstrap + +`npm run companion:prepare -- --out /absolute/empty/path` prepares an independent private repository with the same checks and a checksummed bootstrap of the shared kit. Add `--evidence-dir /absolute/evidence/cache` to include the selected private contract snapshots. It does not copy credentials, Git metadata, node_modules or public product prose. + +Until the first public docs-kit release exists, the private site uses that recorded bootstrap. Later releases are immutable archives from this repository; update the private pin through a reviewed change. + +No live publication is implied by a successful local build. Cloudflare deployment remains disabled until the account and access setup pass the documented checks. diff --git a/SECURITY.md b/SECURITY.md index ab8a342..0a35ba1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,5 @@ -# Security Policy +# Security reporting -If you discover a security issue in this repo, please submit it through the [GitHub Security Bug Bounty]() +Report confidential security issues through GitHub's private vulnerability reporting for this repository. Public issues are appropriate for ordinary documentation defects. -Thanks for helping make GitHub safe for everyone. \ No newline at end of file +The public build must not contain private implementation snapshots, internal runbooks, credentials or internal search indexes. Access protection must cover every hostname of the private site before internal artifacts are uploaded. See [hosting setup](deployment/README.md). diff --git a/deployment/README.md b/deployment/README.md new file mode 100644 index 0000000..f8481d1 --- /dev/null +++ b/deployment/README.md @@ -0,0 +1,62 @@ +# Hosting setup and release verification + +The site builds locally without Cloudflare credentials. Production publication is deliberately gated by `DOCS_DEPLOY_ENABLED` until the account, domains, Access policies and environment rules are configured. + +## Projects and domains + +Create Direct Upload Pages projects named `weaveplatform-public` and `weaveplatform-internal`, each with production branch `main`. Use `weaveplatform.run` and `weaveplatform.dev` as their canonical custom domains. Attach and validate domains before adding Access to the custom domain; initially deploy only a placeholder page to the internal project. + +See Cloudflare's [Direct Upload guide](https://developers.cloudflare.com/pages/how-to/use-direct-upload-with-continuous-integration/) and [Access configuration](https://developers.cloudflare.com/pages/platform/known-issues/). + +## GitHub configuration + +Create `preview` and `production` GitHub environments. Restrict production to `main` and require repository-owner approval. Set these repository variables: + +| Variable | Public value | Internal value | +| --- | --- | --- | +| `DOCS_AUDIENCE` | `public` | `internal` | +| `DOCS_PAGES_PROJECT` | `weaveplatform-public` | `weaveplatform-internal` | +| `DOCS_CANONICAL_URL` | `https://weaveplatform.run` | `https://weaveplatform.dev` | +| `DOCS_DEPLOY_ENABLED` | `false` until setup passes | `false` until Access verification passes | +| `DOCS_RELEASE_OWNER` | Unused; environment review applies | `ShocOne`; only this owner can dispatch an internal release | + +The public production reviewer rule is configured. GitHub rejected required reviewers for the private repository's current billing plan. Internal publication therefore requires the release owner to dispatch the workflow on main with an exact passing build run ID; the workflow independently validates that run's repository, main branch, push event, quality-workflow identity, success and commit before downloading its artifact. + +Add `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN` as environment secrets in each environment. The deployment token requires Pages edit permission for the selected account. Use `gh secret set NAME --repo OWNER/REPO --env ENVIRONMENT` and supply its value through stdin or the interactive prompt. + +The private repository's source-refresh workflow also needs a read-only `DOCS_SOURCE_TOKEN` covering the selected private product repositories. Public builds never need that credential. + +Allow GitHub Actions to propose pull requests if source-refresh proposals are desired. Set branch protection to require the documentation validation job and code-owner review. + +## Internal authentication + +Create a Cloudflare GitHub identity provider using its [documented OAuth setup](https://developers.cloudflare.com/cloudflare-one/integrations/identity-providers/github/). Restrict access to the `weaveplatform/docs-internal` team, which also grants read access to the private repository. + +Configure Access applications for all three hostname patterns: + +- `weaveplatform.dev` (plus `www.weaveplatform.dev` only if configured). +- `weaveplatform-internal.pages.dev`. +- `*.weaveplatform-internal.pages.dev` for preview aliases and deployment hashes. + +The Pages [preview Access setting](https://developers.cloudflare.com/pages/configuration/preview-deployments/#customize-preview-deployments-access) protects previews only. Configure production and custom-domain applications separately. + +Use placeholder content to verify an anonymous visitor and an authenticated non-team user cannot retrieve HTML, scripts, search bundles or diagrams at each hostname. Verify an authorised team member can retrieve them. The CI smoke job needs a separate narrowly scoped Access service-token policy and `CF_ACCESS_CLIENT_ID` / `CF_ACCESS_CLIENT_SECRET` secrets in production. + +Record the checks and policy identifiers in the private environment register before enabling deployment. Robots directives are supplementary; Access enforces confidentiality. + +The smoke script checks the canonical host and internal production Pages alias, their scripts and styles, search bundle, registered diagrams and deployment manifest. Set `DOCS_PREVIEW_URLS` to a JSON array of actual preview/hash URLs to check wildcard protection. Human non-team and authorised-team checks remain required for the GitHub identity policy. + +## Publishing + +Merge a reviewed change into `main`. The quality workflow builds the site and retains the site and evidence artifacts. Public production starts from that successful main-push run and waits for environment approval. For an internal release, the configured owner dispatches **Publish documentation** on main with the successful quality run ID. Both consume that exact artifact and write `deployment.json` with the documentation commit, build run and source-lock hash. + +Trusted same-repository pull requests receive a `pr-N` Pages preview when deployment is enabled. Forks get validation and downloadable artifacts. + +## Rollback + +1. Record the failing and previous successful Pages deployment IDs and documentation commits. +2. In Cloudflare Pages, select the previous production deployment and choose **Rollback to this deployment**. +3. Run `scripts/smoke-deployment.mjs` with the canonical URL and audience. Internal checks additionally require the Access service token. +4. Confirm `deployment.json` identifies the restored commit and record the outcome in the private release log. + +Initial deployment and rollback are not marked verified until these checks have actually run against the configured account. diff --git a/diagrams/manifest.yaml b/diagrams/manifest.yaml new file mode 100644 index 0000000..3c139cb --- /dev/null +++ b/diagrams/manifest.yaml @@ -0,0 +1,76 @@ +schema_version: 1 +diagrams: +- id: documentation-architecture + type: architecture + title: Documentation ownership and publication + source: diagrams/sources/documentation-architecture.json + audience: public + evidence: + - documentation:scripts/prepare-companion.mjs + - documentation:.github/workflows/quality-gate.yml + - documentation:.github/workflows/deploy.yml +- id: publishing-workflow + type: workflow + title: Documentation review and production release + source: diagrams/sources/publishing-workflow.json + audience: public + evidence: + - documentation:.github/workflows/quality-gate.yml + - documentation:.github/workflows/deploy.yml +- id: agent-core + type: architecture + title: 'Weave agent: supervision and host services' + source: diagrams/sources/agent-core.json + audience: public + evidence: + - agent-core:internal/core/core.go + - agent-core:cmd/weaveboot/main.go +- id: guestweave-macos + type: architecture + title: GuestWeave on a macOS host + source: diagrams/sources/guestweave-macos.json + audience: public + evidence: + - guestweave-macos:internal/httpapi/server.go + - guestweave-macos:go.mod +- id: guestweave-windows + type: architecture + title: GuestWeave on a Windows host + source: diagrams/sources/guestweave-windows.json + audience: public + evidence: + - guestweave-windows:internal/serve/http.go + - guestweave-windows:internal/oci/oci.go +- id: hostweave + type: architecture + title: 'HostWeave: placement to VM execution' + source: diagrams/sources/hostweave.json + audience: public + evidence: + - hostweave:internal/server/server.go + - hostweave:agent/runtime/guestweave/guestweave.go +- id: imageweave-construction + type: workflow + title: 'Imageweave: construction is not runtime qualification' + source: diagrams/sources/imageweave-construction.json + audience: public + evidence: + - imageweave:pkg/delivery/native.go + - imageweave:pkg/plan/plan.go +- id: module-startup + type: sequence + title: Capability module startup + source: diagrams/sources/module-startup.json + audience: public + evidence: + - agent-modules:sdk/modulesdk/serve.go + - agent-modules:sdk/modulesdk/module.go +- id: platform-context + type: architecture + title: 'Weave platform: execution and image construction' + source: diagrams/sources/platform-context.json + audience: public + evidence: + - hostweave:agent/runtime/guestweave/guestweave.go + - imageweave:pkg/delivery/native.go + - agent-core:internal/core/core.go diff --git a/diagrams/sources/agent-core.json b/diagrams/sources/agent-core.json new file mode 100644 index 0000000..2905dc2 --- /dev/null +++ b/diagrams/sources/agent-core.json @@ -0,0 +1,124 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Weave agent: supervision and host services", + "locale": "en", + "quality_profile": "showcase", + "viewBox": [ + 1240, + 720 + ] + }, + "components": [ + { + "id": "manager", + "type": "external", + "label": "Service manager", + "sublabel": "launchd, systemd or SCM", + "pos": [ + 40, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "boot", + "type": "backend", + "label": "weaveboot", + "sublabel": "Stage, supervise and roll back", + "pos": [ + 350, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "core", + "type": "backend", + "label": "Agent core", + "sublabel": "Verify and supervise modules", + "pos": [ + 660, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "module", + "type": "backend", + "label": "Capability module", + "sublabel": "Separate module process", + "pos": [ + 970, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "store", + "type": "security", + "label": "Host services", + "sublabel": "Identity, policy, store and bus", + "pos": [ + 660, + 410 + ], + "size": [ + 220, + 84 + ] + } + ], + "connections": [ + { + "to": "boot", + "label": "Start service", + "from": "manager" + }, + { + "to": "core", + "label": "Supervise", + "from": "boot" + }, + { + "to": "module", + "label": "Verify and launch", + "from": "core" + }, + { + "to": "store", + "label": "Serve RPC", + "from": "core", + "labelDy": 24 + } + ], + "cards": [ + { + "dot": "rose", + "title": "Verification", + "items": [ + "Core refuses module execution when verification is unavailable." + ] + }, + { + "dot": "violet", + "title": "Ownership", + "items": [ + "Service manager owns weaveboot; core owns module supervision." + ] + } + ] +} diff --git a/diagrams/sources/documentation-architecture.json b/diagrams/sources/documentation-architecture.json new file mode 100644 index 0000000..833e9af --- /dev/null +++ b/diagrams/sources/documentation-architecture.json @@ -0,0 +1,145 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Documentation ownership and publication", + "locale": "en", + "quality_profile": "showcase", + "viewBox": [ + 1240, + 720 + ] + }, + "components": [ + { + "id": "public-source", + "type": "database", + "label": "weaveplatform.run", + "sublabel": "Public knowledge source", + "pos": [ + 40, + 150 + ], + "size": [ + 230, + 84 + ] + }, + { + "id": "public-build", + "type": "backend", + "label": "Public build", + "sublabel": "Pinned docs-kit and evidence", + "pos": [ + 480, + 150 + ], + "size": [ + 230, + 84 + ] + }, + { + "id": "public-pages", + "type": "cloud", + "label": "Public Pages", + "sublabel": "weaveplatform.run", + "pos": [ + 940, + 150 + ], + "size": [ + 250, + 84 + ] + }, + { + "id": "private-source", + "type": "database", + "label": "weaveplatform.dev", + "sublabel": "Private evidence and operations", + "pos": [ + 40, + 420 + ], + "size": [ + 230, + 84 + ] + }, + { + "id": "private-build", + "type": "backend", + "label": "Internal build", + "sublabel": "Same pinned docs-kit", + "pos": [ + 480, + 420 + ], + "size": [ + 230, + 84 + ] + }, + { + "id": "private-pages", + "type": "security", + "label": "Pages with Access", + "sublabel": "weaveplatform.dev", + "pos": [ + 940, + 420 + ], + "size": [ + 250, + 84 + ] + } + ], + "connections": [ + { + "from": "public-source", + "to": "public-build", + "label": "Review and build" + }, + { + "from": "public-build", + "to": "public-pages", + "label": "Direct Upload" + }, + { + "from": "private-source", + "to": "private-build", + "label": "Review and build" + }, + { + "from": "private-build", + "to": "private-pages", + "label": "Protected upload", + "variant": "security" + } + ], + "cards": [ + { + "dot": "violet", + "title": "Shared tooling", + "items": [ + "Theme and components are released upstream in weaveplatform.run." + ] + }, + { + "dot": "rose", + "title": "Publication boundary", + "items": [ + "Internal hosts require Access before confidential content is uploaded." + ] + }, + { + "dot": "amber", + "title": "Hosting status", + "items": [ + "Local builds are verified; Cloudflare setup is still pending." + ] + } + ] +} diff --git a/diagrams/sources/guestweave-macos.json b/diagrams/sources/guestweave-macos.json new file mode 100644 index 0000000..c54370b --- /dev/null +++ b/diagrams/sources/guestweave-macos.json @@ -0,0 +1,124 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "GuestWeave on a macOS host", + "locale": "en", + "quality_profile": "showcase", + "viewBox": [ + 1240, + 720 + ] + }, + "components": [ + { + "id": "client", + "type": "external", + "label": "User or integration", + "sublabel": "CLI, HTTP or MCP", + "pos": [ + 40, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "commands", + "type": "backend", + "label": "Command layer", + "sublabel": "Shared lifecycle operations", + "pos": [ + 350, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "vmservice", + "type": "backend", + "label": "VM service", + "sublabel": "Configuration and state", + "pos": [ + 660, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "backend", + "type": "backend", + "label": "Host backend", + "sublabel": "Apple virtualization APIs", + "pos": [ + 970, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "storage", + "type": "database", + "label": "VM files", + "sublabel": "Disks and identity state", + "pos": [ + 660, + 410 + ], + "size": [ + 220, + 84 + ] + } + ], + "connections": [ + { + "to": "commands", + "label": "Invoke", + "from": "client" + }, + { + "to": "vmservice", + "label": "Lifecycle", + "from": "commands" + }, + { + "to": "backend", + "label": "Native VM calls", + "from": "vmservice" + }, + { + "to": "storage", + "label": "Persist", + "from": "vmservice", + "labelDy": 24 + } + ], + "cards": [ + { + "dot": "cyan", + "title": "Host responsibility", + "items": [ + "GuestWeave owns local VM state and lifecycle." + ] + }, + { + "dot": "amber", + "title": "Guest matrix", + "items": [ + "Host architecture and guest OS constrain backend selection." + ] + } + ] +} diff --git a/diagrams/sources/guestweave-windows.json b/diagrams/sources/guestweave-windows.json new file mode 100644 index 0000000..eef8cde --- /dev/null +++ b/diagrams/sources/guestweave-windows.json @@ -0,0 +1,124 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "GuestWeave on a Windows host", + "locale": "en", + "quality_profile": "showcase", + "viewBox": [ + 1240, + 720 + ] + }, + "components": [ + { + "id": "client", + "type": "external", + "label": "User or integration", + "sublabel": "CLI or HTTP", + "pos": [ + 40, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "commands", + "type": "backend", + "label": "Command layer", + "sublabel": "VM lifecycle operations", + "pos": [ + 350, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "hcs", + "type": "backend", + "label": "HCS backend", + "sublabel": "Compute-system management", + "pos": [ + 660, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "guest", + "type": "backend", + "label": "Guest VM", + "sublabel": "Windows or Linux", + "pos": [ + 970, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "network", + "type": "cloud", + "label": "HCN networking", + "sublabel": "Host compute networks", + "pos": [ + 660, + 410 + ], + "size": [ + 220, + 84 + ] + } + ], + "connections": [ + { + "to": "commands", + "label": "Invoke", + "from": "client" + }, + { + "to": "hcs", + "label": "Host calls", + "from": "commands" + }, + { + "to": "guest", + "label": "Create and start", + "from": "hcs" + }, + { + "to": "network", + "label": "Network setup", + "from": "hcs", + "labelDy": 24 + } + ], + "cards": [ + { + "dot": "cyan", + "title": "Implementation", + "items": [ + "Windows host execution uses HCS and HCN." + ] + }, + { + "dot": "amber", + "title": "Image contract", + "items": [ + "The current Windows OCI path uses GuestWeave VHDX v2." + ] + } + ] +} diff --git a/diagrams/sources/hostweave.json b/diagrams/sources/hostweave.json new file mode 100644 index 0000000..71a853f --- /dev/null +++ b/diagrams/sources/hostweave.json @@ -0,0 +1,124 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "HostWeave: placement to VM execution", + "locale": "en", + "quality_profile": "showcase", + "viewBox": [ + 1240, + 720 + ] + }, + "components": [ + { + "id": "caller", + "type": "external", + "label": "Operator", + "sublabel": "hwctl, web UI or API", + "pos": [ + 40, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "server", + "type": "backend", + "label": "Control plane", + "sublabel": "API, placement and leases", + "pos": [ + 350, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "agent", + "type": "backend", + "label": "Host agent", + "sublabel": "Work execution and health", + "pos": [ + 660, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "cli", + "type": "backend", + "label": "GuestWeave CLI", + "sublabel": "Pinned runtime binary", + "pos": [ + 970, + 150 + ], + "size": [ + 220, + 84 + ] + }, + { + "id": "state", + "type": "database", + "label": "Control-plane state", + "sublabel": "Configured store and blobs", + "pos": [ + 350, + 410 + ], + "size": [ + 220, + 84 + ] + } + ], + "connections": [ + { + "to": "server", + "label": "HTTPS", + "from": "caller" + }, + { + "to": "agent", + "label": "Work leases", + "from": "server" + }, + { + "to": "cli", + "label": "Subprocess calls", + "from": "agent" + }, + { + "to": "state", + "label": "Read and write", + "from": "server", + "labelDy": 24 + } + ], + "cards": [ + { + "dot": "cyan", + "title": "Outbound connection", + "items": [ + "The host agent initiates its connection to the control plane." + ] + }, + { + "dot": "amber", + "title": "Boundary", + "items": [ + "The guest runtime adapter invokes the CLI; it does not import it." + ] + } + ] +} diff --git a/diagrams/sources/imageweave-construction.json b/diagrams/sources/imageweave-construction.json new file mode 100644 index 0000000..b5b7eb6 --- /dev/null +++ b/diagrams/sources/imageweave-construction.json @@ -0,0 +1,110 @@ +{ + "schema_version": 2, + "diagram_type": "workflow", + "meta": { + "title": "Imageweave: construction is not runtime qualification", + "locale": "en", + "quality_profile": "showcase" + }, + "lanes": [ + { + "id": "construction", + "label": "Construction" + }, + { + "id": "verification", + "label": "Artifact verification" + } + ], + "mainPath": [ + "inputs", + "plan", + "packer", + "import", + "inspect" + ], + "nodes": [ + { + "id": "inputs", + "lane": "construction", + "col": 0, + "type": "external", + "label": "Pinned inputs", + "sublabel": "Media + recipe" + }, + { + "id": "plan", + "lane": "construction", + "col": 1, + "type": "backend", + "label": "Resolve plan", + "sublabel": "Scenario and template" + }, + { + "id": "packer", + "lane": "construction", + "col": 2, + "type": "backend", + "label": "Packer build", + "sublabel": "Install or restore" + }, + { + "id": "import", + "lane": "verification", + "col": 3, + "type": "backend", + "label": "OCI handoff", + "sublabel": "Manifest-listed outputs" + }, + { + "id": "inspect", + "lane": "verification", + "col": 4, + "type": "security", + "label": "Inspect", + "sublabel": "Deep + strict" + } + ], + "edges": [ + { + "from": "inputs", + "to": "plan", + "label": "Resolve", + "role": "main" + }, + { + "from": "plan", + "to": "packer", + "label": "Build", + "role": "main" + }, + { + "from": "packer", + "to": "import", + "label": "Manifest", + "role": "main" + }, + { + "from": "import", + "to": "inspect", + "label": "Inspect", + "role": "main" + } + ], + "cards": [ + { + "dot": "amber", + "title": "Qualification boundary", + "items": [ + "A constructed and inspected artifact still needs runtime evidence." + ] + }, + { + "dot": "cyan", + "title": "Publication boundary", + "items": [ + "Native construction does not publish or promote an image." + ] + } + ] +} diff --git a/diagrams/sources/module-startup.json b/diagrams/sources/module-startup.json new file mode 100644 index 0000000..c70855a --- /dev/null +++ b/diagrams/sources/module-startup.json @@ -0,0 +1,102 @@ +{ + "schema_version": 1, + "diagram_type": "sequence", + "meta": { + "title": "Capability module startup", + "locale": "en", + "quality_profile": "showcase", + "column_fit": "spread", + "viewBox": [ + 1240, + 620 + ] + }, + "participants": [ + { + "id": "core", + "type": "backend", + "label": "Agent core" + }, + { + "id": "module", + "type": "backend", + "label": "Module SDK" + }, + { + "id": "services", + "type": "security", + "label": "Host services" + } + ], + "messages": [ + { + "from": "core", + "to": "module", + "y": 180, + "label": "Launch verified binary", + "variant": "security" + }, + { + "from": "module", + "to": "core", + "y": 230, + "label": "Handshake: protocol and address", + "variant": "return" + }, + { + "from": "core", + "to": "module", + "y": 280, + "label": "Init with host services and config" + }, + { + "from": "module", + "to": "services", + "y": 330, + "label": "Authenticated host RPC", + "variant": "security" + }, + { + "from": "module", + "to": "core", + "y": 380, + "label": "Init completes", + "variant": "return" + }, + { + "from": "core", + "to": "module", + "y": 430, + "label": "Start" + }, + { + "from": "core", + "to": "module", + "y": 480, + "label": "Health" + }, + { + "from": "module", + "to": "core", + "y": 530, + "label": "Module health", + "variant": "return" + } + ], + "cards": [ + { + "dot": "rose", + "title": "Before lifecycle", + "items": [ + "Unsupported protocol windows are rejected before module Init." + ] + }, + { + "dot": "violet", + "title": "SDK ownership", + "items": [ + "The SDK serves lifecycle RPCs and brokers the Host interface." + ] + } + ] +} diff --git a/diagrams/sources/platform-context.json b/diagrams/sources/platform-context.json new file mode 100644 index 0000000..b5768be --- /dev/null +++ b/diagrams/sources/platform-context.json @@ -0,0 +1,26 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": {"title": "Weave platform: execution and image construction", "locale": "en", "quality_profile": "showcase", "viewBox": [1240, 720]}, + "components": [ + {"id": "operator", "type": "external", "label": "Operator", "sublabel": "API, UI or hwctl", "pos": [40, 140], "size": [190, 84]}, + {"id": "orchestrator", "type": "backend", "label": "HostWeave", "sublabel": "Placement and work leases", "pos": [360, 140], "size": [230, 84]}, + {"id": "hostagent", "type": "backend", "label": "Host agent", "sublabel": "Runtime adapter", "pos": [690, 140], "size": [220, 84]}, + {"id": "vmmanager", "type": "backend", "label": "GuestWeave", "sublabel": "Host VM lifecycle", "pos": [1020, 140], "size": [190, 84]}, + {"id": "guestagent", "type": "backend", "label": "Guest agent core", "sublabel": "Supervised capabilities", "pos": [1020, 400], "size": [190, 84]}, + {"id": "imagebuilder", "type": "backend", "label": "Imageweave", "sublabel": "Packer construction", "pos": [40, 400], "size": [190, 84]}, + {"id": "artifacttools", "type": "backend", "label": "Weave OCI", "sublabel": "Artifact handoff and checks", "pos": [360, 400], "size": [230, 84]} + ], + "connections": [ + {"from": "operator", "to": "orchestrator", "label": "HTTPS API", "variant": "emphasis"}, + {"from": "orchestrator", "to": "hostagent", "label": "Work leases", "variant": "emphasis"}, + {"from": "hostagent", "to": "vmmanager", "label": "CLI adapter", "variant": "emphasis"}, + {"from": "vmmanager", "to": "guestagent", "label": "Guest channel", "labelDy": 24}, + {"from": "imagebuilder", "to": "artifacttools", "label": "Build manifest"} + ], + "cards": [ + {"dot": "cyan", "title": "Execution", "items": ["HostWeave places work; GuestWeave owns VM lifecycle."]}, + {"dot": "amber", "title": "Compatibility", "items": ["Image construction does not prove runtime compatibility."]}, + {"dot": "violet", "title": "Agent roles", "items": ["The host agent and guest agent core are separate processes."]} + ] +} diff --git a/diagrams/sources/publishing-workflow.json b/diagrams/sources/publishing-workflow.json new file mode 100644 index 0000000..4b6145b --- /dev/null +++ b/diagrams/sources/publishing-workflow.json @@ -0,0 +1,126 @@ +{ + "schema_version": 2, + "diagram_type": "workflow", + "meta": { + "title": "Documentation review and production release", + "locale": "en", + "quality_profile": "showcase" + }, + "lanes": [ + { + "id": "review", + "label": "Review" + }, + { + "id": "production", + "label": "Production" + } + ], + "nodes": [ + { + "id": "proposal", + "lane": "review", + "col": 0, + "type": "external", + "label": "Pull request", + "sublabel": "Evidence and prose" + }, + { + "id": "pr-checks", + "lane": "review", + "col": 1, + "type": "backend", + "label": "PR checks", + "sublabel": "Build and browsers" + }, + { + "id": "main-push", + "lane": "review", + "col": 2, + "type": "backend", + "label": "Main push", + "sublabel": "Reviewed merge" + }, + { + "id": "verified-build", + "lane": "production", + "col": 2, + "type": "database", + "label": "Build artifact", + "sublabel": "Passing main CI" + }, + { + "id": "release-gate", + "lane": "production", + "col": 3, + "type": "security", + "label": "Release gate", + "sublabel": "Production approval" + }, + { + "id": "upload", + "lane": "production", + "col": 4, + "type": "cloud", + "label": "Pages upload", + "sublabel": "Exact CI artifact" + }, + { + "id": "check-live", + "lane": "production", + "col": 5, + "type": "security", + "label": "Smoke checks", + "sublabel": "Commit and access" + } + ], + "edges": [ + { + "from": "proposal", + "to": "pr-checks", + "label": "Validate" + }, + { + "from": "pr-checks", + "to": "main-push", + "label": "Review" + }, + { + "from": "main-push", + "to": "verified-build", + "label": "Rebuild" + }, + { + "from": "verified-build", + "to": "release-gate", + "label": "Success" + }, + { + "from": "release-gate", + "to": "upload", + "label": "Approve", + "variant": "security" + }, + { + "from": "upload", + "to": "check-live", + "label": "Verify" + } + ], + "cards": [ + { + "dot": "amber", + "title": "Enablement", + "items": [ + "Deployment remains disabled until hosting and Access are verified." + ] + }, + { + "dot": "rose", + "title": "Recovery", + "items": [ + "A failed smoke check requires investigation or deployment rollback." + ] + } + ] +} diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index aa04a89..0000000 --- a/docs/README.md +++ /dev/null @@ -1,132 +0,0 @@ -# Project Title: Weave Platform - -## Platform tier -weaveplatform-api -├── proto/ # gRPC IDL -│ ├── agent/v1/ # core ↔ module: handshake, host services, lifecycle -│ ├── guestweave/v1/ # per-service RPC surfaces -│ ├── hostweave/v1/ -│ └── gateweave/v1/ -├── openapi/ # REST surfaces (generated from proto where they overlap) -│ ├── guestweave.yaml -│ └── hostweave.yaml -├── events/ # event envelopes, topic definitions -├── schemas/ # shared domain types: Device, Session, Identity, Error, AuthClaims -├── gen/go/ # generated Go — the only thing consumers import -└── buf.yaml # breaking-change detection in CI - -weaveplatform-sdk -├── errors/ # structured error taxonomy -├── log/ # slog conventions, redaction -├── config/ # schema, precedence, validation -├── retry/ # backoff, circuit breaker -├── platform/ # seam over go-bindings-*: capability-shaped, not API-shaped -│ ├── platform_darwin.go -│ ├── platform_windows.go -│ └── platform_linux.go -└── module/ # module scaffolding: Module iface, Host client, serve loop - -weaveplatform-agent -├── cmd/ -│ ├── weaveboot/ # supervises core; only thing launchd/SCM starts -│ ├── weaveagentd/ # core daemon (root/SYSTEM) — Zone A, CGO_ENABLED=0 -│ ├── weaveagent/ # CLI, thin client over local socket -│ └── weaveportal/ # user-session UI — Zone B, AppKit/WinUI3 -├── internal/ -│ ├── identity/ # device identity, attestation, TPM/Secure Enclave, enrolment -│ ├── transport/ # GateWeave channel; vsock/HvSocket when guest -│ ├── policy/ # fetch, verify, evaluate, notify -│ ├── store/ # namespaced, encrypted at rest -│ ├── supervisor/ # spawn, verify signature, privilege drop, health, swap, drain -│ ├── capability/ # host probe; gates module launch -│ ├── lifecycle/ # fetch, stage, promote, N-1 rollback -│ ├── ui/ # surface brokerage — core never draws -│ └── bus/ # module event fan-out -├── template/ # scaffolding for a new product module -└── packaging/ # pkg, MSI, deb — the immutable install footprint - -weaveplatform-manifest -├── channels/ # signed: which module versions constitute a known-good set -│ ├── stable.json -│ ├── beta.json -│ └── pinned/ # self-hosted, mapped to service versions -└── keys/ # signing chain, rotation policy - -## Product tier -weave -├── cmd/weave/# the service -├── internal/ # the internal components of the product -├── agent/ # module binary — ships and versions with the product -│ └── cmd/weave-agent/ -├── sdk/ # client library for this service -└── api/ # surfaces used by nobody else (shared ones → weaveplatform-api) - -appweave deviceweave labweave remoteweave sightweave - ├─ agent/ ├─ agent/ ├─ agent/ ├─ agent/ ├─ agent/ - -gateweave hostweave # server-side only, no module - -guestweave ├─ agent/ -guestweave-macos # Virtualization / Hypervisor.framework -guestweave-windows # HCS -guestweave-linux # libvirt - -## guestweave -### product description: -a cli tool for guest vm management. Not bare metal, but a vm manager for guest vm management. -3 seperate vm mangers exist covering linux, windows and macos hosts. -linux can run linux on linux and windows on linux. -macos can run mac on mac, linux on mac and windows on mac. -windows can run windows on windows and linux on windows. - -Windows engine is built upon hcs. macos engine is built on virtualization.framework for macos and linux -and Hypervisor.framework for windows. linux engine is built upon libvirt. -vm's can run headless and headed for all guest/vm combinations. All cli operations are exposed via a restful api. - -supports commands like create, start, stop, restart, migrate, clone, snapshot, suspend, migrate, prune and delete -guest vms. Additionally it supports the ability to run multiple guest vms on the same host. -macOS images are provisioned from a apple CDW supplied IPSW. Windows images are provisioned from a microsoft CDN. -Linux images are provisioned from a linux distribution CDN. Remote commands are supported for all guest vms, such -as resolve ip, ssh, login, logout and restart. All OS install options can be driven either as attended (via the ui) -or unattended using OS specific kickstart tools or keypress scripts. - -A basic ui is provided to allow for the management of guest vms as well. But tool is primarily expected to be used via the cli. - -## hostweave -### product description: -a fleet scheduler for guest vms. not a hypervisor and not a vm manager, but the layer above -guestweave that decides where work runs. hosts run a guestweave agent module and poll for work, so -outbound connections only and cloud hosts behind nat, racked machines and roaming laptops all -participate the same way. all operations are exposed via a restful api and a cli. - -work is submitted declaratively as a spec and reconciled, not driven as imperative commands. -three workload kinds exist. jobs run to completion and return an exit code and artefacts. sessions -are held by a person, a cua or a service until released or expired. services stay up and are -restarted on failure. a spec declares image, resources, lifetime, disposal, network isolation and -what to expose. disposal defaults to destroy and cannot be set to reuse after agentic execution. - -placement is a constraint solver rather than bin packing because the guest matrix is asymmetric. -macos guests run only on apple hardware, apple silicon runs arm64 windows only, and no more than -two macos guests may run on one host. hosts advertise what they can run rather than being configured -for it. affinity, anti-affinity, spread and pack, reservations, priority classes and preemption are -all supported. a queue with per-tenant fairness, ageing and visible position handles contention. - -images are content addressed and built once centrally using the unattended install paths guestweave -already supports, then cached per host and fast cloned on instantiation using apfs clonefile, -differencing disks or qcow2 backing files. warm pools are kept per image spec and autoscaled on -queue depth. time to ready is the metric the product is designed around, measured in seconds rather -than the tens of minutes a cdn provisioned install costs. - -guardrails are enforced outside the guest where the workload cannot reach them. network allowlists -are applied at the virtual switch, the kill switch is vm destruction, session recording is captured -host side, the audit stream leaves over the hypervisor channel before the guest sees it, and -snapshot rollback is the final containment rung. run context is attested by the host and delivered -inward rather than read locally. - -leases, quotas and rbac scope everything to teams and projects, with macos slots counted separately -because they are the scarcest resource. cloud burst extends local capacity when it is exhausted, -with per archetype cost attribution. reconciliation is continuous, so draining a host evacuates it, -a failed host reschedules its work and migration uses the verb guestweave already has. - -hostweave knows nothing about hcs, virtualization.framework or libvirt. it speaks specs and leases, -and translating those into guestweave calls is the host agent's job. diff --git a/docs/development/architecture.mdx b/docs/development/architecture.mdx new file mode 100644 index 0000000..949a517 --- /dev/null +++ b/docs/development/architecture.mdx @@ -0,0 +1,62 @@ +--- +title: Knowledge base architecture +description: Repository layout, knowledge ownership, shared tooling and publication boundaries. +product: documentation +audience: [contributor, developer, operator] +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: + - documentation:scripts/prepare-companion.mjs + - documentation:scripts/validate.mjs + - documentation:site/docusaurus.config.ts + - documentation:.github/workflows/deploy.yml +--- + +The knowledge base has two authoritative homes. `weaveplatform.run` owns public product explanations and the public website. The private `weaveplatform.dev` repository owns confidential evidence, environment records and operational knowledge. Both produce static Docusaurus websites using the same documentation kit. + + +Each repository reviews and builds its own content. Public Pages serves the consumer and developer site; the internal site requires Cloudflare Access. Hosting setup is pending. + + +## Repository layout + +| Path | Responsibility | +| --- | --- | +| `docs/` | Authored platform, product, reference, development and operations pages | +| `diagrams/sources/` | Canonical Archify JSON, registered with evidence in `diagrams/manifest.yaml` | +| `sources/repositories.yaml` | Platform repository inventory and documentation scope | +| `sources/sources.lock.yaml` | Exact implementation commits, inspected paths and checksums | +| `sources/contracts/` | Reviewed contract snapshots from authoritative upstream code | +| `site/` | Docusaurus configuration, navigation and pages | +| `packages/docs-kit/` | Shared typography, components and page metadata schema | +| `tools/archify/` | Checksummed renderer distribution and third-party notices | +| `scripts/` | Evidence, content, diagram, build and browser verification | +| `.github/workflows/` | Validation, preview, production and upstream-review workflows | +| `deployment/` | Hosting configuration, access verification and rollback procedures | + +Generated viewers, contract downloads, search indexes and site output are build products. Keep canonical specifications and snapshots under version control; retain receipts and screenshots as CI artifacts. + +## Authority and audience + +Maintain each fact in its authoritative home. Internal pages link to public HLDs instead of copying their prose. A private investigation can supply a reviewed public explanation, but private contracts and environment details stay in the internal repository. + +Organise knowledge around reader tasks: understand the platform, develop integrations, look up interfaces and operate services. Product guides follow those same categories as content grows. The initial content is the selected product HLDs; API task guides and tested infrastructure implementation guides follow the [roadmap](roadmap.md). + +## Evidence and maintenance + +Page metadata declares audience, owner, verification date, status and source references. Source locks bind inspected code to exact commits and checksums. Diagram manifests bind relationships to that same evidence. Validation rejects unresolved references, unregistered contract files, private snapshots in a public build and draft pages in the published tree. + +Static inspection and runtime acceptance are distinct statuses. Upstream refresh reports changed evidence for review; it does not automatically update claims or their verification dates. + +## Shared tooling + +The public repository owns `@weaveplatform/docs-kit`. The private bootstrap records its file checksums in `UPSTREAM.json`, which validation enforces. Subsequent upgrades should consume a reviewed immutable release archive and its checksum. + +The Archify distribution has a separate provenance file. Renderer changes require an intentional upstream upgrade and a new diagram validation and visual review cycle. + +## Publication + +The [publishing workflow](../operations/publishing.mdx) deploys the artifact produced by successful CI at an exact documentation commit. Source revisions remain in the deployment manifest. Public and internal projects have separate domains and access boundaries. + +Cloudflare publication remains disabled until account setup and access verification are complete. The existing local build proves the repository can produce the website; live deployment and rollback require their own observed results. diff --git a/docs/development/documentation.md b/docs/development/documentation.md new file mode 100644 index 0000000..3b0ddbf --- /dev/null +++ b/docs/development/documentation.md @@ -0,0 +1,59 @@ +--- +title: Contribute documentation +description: Author and verify Weave knowledge using implementation evidence and Archify + diagrams. +product: documentation +audience: +- contributor +- developer +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- documentation:scripts/validate.mjs +- documentation:scripts/diagrams.mjs +- documentation:sources/sources.lock.yaml +--- + +Documentation changes follow the same reviewed workflow as code. Every material implementation claim needs evidence at an exact source baseline. + +## Local authoring + +```bash +npm ci +npm run start +``` + +Use Node 22 at the version recorded in `.node-version`. Markdown lives in `docs/`; navigation lives in the site sidebar. Diagram JSON lives in `diagrams/sources/` and is registered in its manifest. + +```bash +npm run validate +npm test +npm run build +npm run check:links +npm run typecheck +npm run diagrams:visual +npm run check:browser +``` + +Search is indexed from the production HTML. Use `npm run serve` after a build to inspect it locally. Browser checks serve and stop their own preview. + +## Evidence and ownership + +Pages declare title, description, product, audience, owner, status, verification date and evidence references in frontmatter. `sources/sources.lock.yaml` records exact source revisions, inspected paths and checksums. Public builds accept snapshots only from public sources; private implementation evidence stays in the companion. + +Use `code-inspected` for static implementation review. Use `runtime-verified` only when a reproducible acceptance record exists. A changed upstream file does not automatically reverify an existing page. + +## Diagrams + +Use the pinned Archify bundle. Author the appropriate architecture, workflow or sequence JSON with stable IDs, a clear main path and `meta.quality_profile: showcase`. Diagram evidence references identify the actual code behind the relationships. + +The build requires nine showcase checks, zero composition errors and zero warnings. Visual checks bind browser screenshots and measurements to the delivered HTML. Inspect the screenshots in both themes before approval; machine containment checks do not establish visual polish. + +Generation receipts and browser screenshots remain build artifacts. The website includes only delivered viewers and approved content. + +## Pull request review + +Explain the user-visible knowledge change, source baseline, cross-checks and limitations. Reviewers confirm the evidence, inspect diagrams, and approve any public export from a private implementation. Keep proposed behaviour separate from implemented behaviour. + +Upstream refresh produces a review report. It does not rewrite prose, change verification dates or silently move the baseline. diff --git a/docs/development/roadmap.md b/docs/development/roadmap.md new file mode 100644 index 0000000..d17f260 --- /dev/null +++ b/docs/development/roadmap.md @@ -0,0 +1,41 @@ +--- +title: Documentation roadmap +description: Prioritised content milestones following the first architecture release. +product: documentation +audience: +- contributor +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- documentation:diagrams/manifest.yaml +- hostweave:pkg/images/vm.go +- imageweave:pkg/delivery/native.go +--- + +## Milestone 1: architecture + +The first release includes the platform map, product HLDs, interface catalogue, compatibility register and interactive diagrams. Each claim follows a recorded source baseline. Public and internal sources have separate publication boundaries. + +## Milestone 2: develop and integrate + +| Priority | Deliverable | Acceptance | +| --- | --- | --- | +| 1 | Write and test a capability module | SDK/core versions pinned; protocol rejection and lifecycle observed | +| 2 | GuestWeave HTTP and CLI reference by host | Router, request/response and help-output checks | +| 3 | HostWeave HTTP and host-agent reference | Generated contract and streaming/auth boundaries verified | +| 4 | Configuration and compatibility tables | Actual precedence, defaults and supported combinations checked | + +## Milestone 3: operate + +Add product installation guides, then infrastructure implementation guides covering prerequisites, topology, TLS/authentication, persistent data, startup checks, upgrades, backup, restore and rollback. + +Use the actual Docker, packaging and platform-service assets as starting evidence. Test each guide in its intended environment. Keep environment-specific infrastructure and confidential recovery procedures in the private companion. + +## Milestone 4: one proven image-to-execution journey + +Choose compatible producer and consumer versions, record the artifact digest, and exercise construction through guest readiness and workload completion. Resolve or explicitly constrain the image-format gap before publishing the journey. + +AppWeave, SightWeave and the Terraform-provider scaffold remain outside this phase. Expand the knowledge base when their implementation and required workflows are ready for verification. diff --git a/docs/markdown-syntax-guide.md b/docs/markdown-syntax-guide.md deleted file mode 100644 index 1c1dbed..0000000 --- a/docs/markdown-syntax-guide.md +++ /dev/null @@ -1,103 +0,0 @@ -# Markdown Syntax Guide - -This article offers a sample of basic Markdown syntax that can be used in content files, also it shows whether basic HTML elements are decorated with CSS. - - -## Headings - -The following HTML `

`—`

` elements represent six levels of section headings. `

` is the highest section level while `

` is the lowest. - -## H2 -### H3 -#### H4 -##### H5 -###### H6 - -## Paragraph - -Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. - -Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat. - -Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat. - -## Blockquotes - -The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations. - -### Blockquote without attribution - -> Tiam, ad mint andaepu dandae nostion secatur sequo quae. -> **Note** that you can use *Markdown syntax* within a blockquote. - -#### Blockquote with attribution - -> Simplicity is the ultimate sophistication. -> Leonardo da Vinci[^1] - -[^1]: The above quote is often attributed to Leonardo da Vinci but there is no concrete evidence to support this. - - -## Tables - - Name | Age ---------|------ - Bob | 27 - Alice | 23 - -### Inline Markdown within tables - -
- -| Inline    | Markdown    | In    | Table | -| ---------- | --------- | ----------------- | ---------- | -| *italics* | **bold** | ~~strikethrough~~    | `code` | - -
- -## Code Blocks - -### Code block indented with four spaces - - - - - - Example HTML5 Document - - -

Test

- - - -## List Types - -### Ordered List - -1. First item -2. Second item -3. Third item - -### Unordered List - -* List item -* Another item -* And another item - -### Nested list - -* Item -1. First Sub-item -2. Second Sub-item - -## Other Elements — abbr, sub, sup, kbd, mark - -GIF is a bitmap image format. - -H2O - -Xn + Yn = Zn - -Press CTRL+ALT+Delete to end the session. - -Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures. \ No newline at end of file diff --git a/docs/media/MicrosoftAzure-32px.png b/docs/media/MicrosoftAzure-32px.png deleted file mode 100644 index 20479a035301c7d0e042e08bf90d6b5f424f3911..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1020 zcmV003M>0c&c2$N&HU7<5HgbW?9;ba!ELWdLwtX>N2bZe?^J zG%hhNHDpIvQUCx0dPzhMI2znBeCV5$F+Pc(MMHJMYG}?;?w@al|!R4apo0*ryh+3qsxFIh1{N_24m`+lI z6%_h||KWY_o9{pKW_zj;M))tNIrB(U+KiVN1+ry~53iqpBs_(^`nznuAmtQzu_xOm zzESNK<(M)+dV%znw}t1h)Zu4Y*V8>+n?qG*530*|;*)Bt_(g3+ zpNf6Bnl%|-Om5Oe&CMQiz}X|KS@J(+gCAZpP=wOY6XQr?y7svGnB75yEFaqcpPPTP7FU1|z7R z3JOBUSlTa4-%l2)ZQ|QR(?r2K`8Z{7col2Lr^H7#(qyxjoTgubg_E-6SY&LL4Gkok zB@=wrH?uk)Ok~XmqjTb`Q1E@#iKvlR00TLwbCJUno1>Nz zzqB>ckSC*CYE_I1eNqPvz}9-6Cy+y9eMCq{8VAO)qFM(E_rS@ivEyhgmP~YUDjlF_ zL7|5vFPN&=N(W-l0S&Y`^eApYKE`hmygCy*puVm-s^Ki~MBECb&gSaDnj*6P544mzf_3`dS%0Qy~fMo$r1={W>$R0c<~Ni!{Jb#6ZH;xW77Ws*j`t71^z_v zgL7wQyHvnzQqyq0Uu$xaPFS&Dc*W8YZ1WFr+wQ9k4&<})zfWanv}m*&)u^oZ>eChM qi2715Ub*%-(y?+QjPS3ZQhf(^2-AtP^Crds0000Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BK;VQFr3E^cLXAT%y9E;VFFSW*B01+7U$K~z{r?bhi}R96@VaCT&yfnk+(W?Hu zgQliJQN;W7oI7WRxid`k58z4O4-?4z&b@Qq_Z*A4%*9;#?{K1XLtK3SFcNgtOY5ev z&e9qb)+t&8!a7OocWoX2jf+2h9%4qBrgsQ|D_i$pSLHWy%Ejvto!5Y9^%_Lw)**6X z4I*-?5uR0vu#5^Q7L*|*wG_e0C6FZ+LK>fspjb5mlndb(l?mVQH25fz;1wJP52+IF z0g;gShhtUqwm!z0W=H3@Z4}nxYiIC9PlM+nKxKf3CKEg!o>CRu10&()7YebD90gSy zt}xCt&u;su#~5$kybt}q{DH={T?ospKxjI_)0Po@DddSo1Yg4NMevWxf^Wov8C<5r z!w4P>kyj9ETlaM^PMGe?uv^u-e?%#G@>*QEb^|mI9zH_N`Zu+B3c-^Ioq9MG&*KpbS7Xbr55VzD0LK5D zMA^EH2KZtG#!x();OUqjmLk9<{uB=(xD?I=mqsK__FUjpj1;E*)TOo2>IOX?N$`m5 zD)QP_V&^-buq6QZ{vAhQRSUy&dE7735RZjNP&B{cc-&jcKe>~e4>AjDht8jeEhB|# z-P84<0M8+K)+&T%tbihIISwBFj4c5eyL%5gng;kQDV{;_RDvhMQ*MC!hCu8sBX|J8 z=fgqd0ZrY;&lzVFz_NDZfvZtGuG{epU$C6)cn#Tf#W>t`k}UzaH8O@|^$IPnNQ4)U zDAwmUX;pKBMvFTOa9T@iUNL_Fl|Q$Am<}2_88ouQ0@7yr(A2cx z?^`2`1t!TswDs+y@Cnc4dp#i|4@Ju=QK+fFl9Dpy7nLHfpae;&8L+XN2PmKL^4Q;pLs%A`0Jz_L)?dBNrS8w8HJjD|;7h`&Qn*G%@D=O;< zYN^L1p1u#de&&=sXq=9(1{#b9O=aUt{1=gZhR);tWEPZ?-<17{ z)7JJrX*_8+itBGQ!b^qWjmF%dv9z=@focDf*FX5_Ph0@`JbO-GhuHWUZG4s1)iGKZ ugw;)}fA%_Zj*I`epA#`^E^{%8g~k6vUCGv)RWSts0000 +A reviewed merge runs CI again on main. Production consumes that successful build artifact through its environment gate, uploads it and checks the deployed commit and reader access. + + +## Deployment prerequisites + +Configure the Cloudflare account and API token through protected repository secrets. Set the Pages project name and canonical domain in repository variables. The token needs Pages edit permission for the chosen account; account/Access provisioning uses a separate operator credential. + +Publishing remains disabled until `DOCS_DEPLOY_ENABLED` is `true`. This allows the repository and checks to exist before the hosting account and custom domains are ready. + +## Review and deployment + +Pull requests run content validation, tests, diagram delivery, browser evidence, the site build and local-link checks. Trusted same-repository branches can receive preview deployments. Forks receive build artifacts without deployment credentials. + +Public production publishes a successful `main` build through the `production` environment, configured with owner approval and branch restrictions. Internal production uses an owner-triggered workflow because the private repository's current GitHub plan does not support required environment reviewers. The selected run must be a successful main-push documentation build from that same repository. The workflow records the site commit and build run; the source-lock file captures the implementation baseline. + +## Internal access + +The private [weaveplatform.dev repository](https://github.com/weaveplatform/weaveplatform.dev) grants its `docs-internal` GitHub team read access. Configure Cloudflare Access to admit that same team. + +Protect the canonical domain, default `pages.dev` domain and preview wildcard. A preview Access policy alone does not secure production. Apply policies using placeholder content, then confirm anonymous and non-team users cannot fetch pages, scripts, search bundles or diagrams before uploading internal knowledge. + +## Rollback + +Retain a previous successful deployment and its source commit. Use the documented deployment rollback procedure, then repeat production smoke checks. A failed smoke check marks the run failed; it must not be described as successful publication. + +## Upstream maintenance + +Weekly and manual refresh jobs compare relevant implementation files with pinned evidence and open a review proposal when changes occur. They do not update facts or verification status automatically. + +Detailed setup, Access verification and bootstrap commands are in the repository’s deployment README. Hosting credentials and actual deployment checks are environment prerequisites; this page does not claim a live site has already passed them. diff --git a/docs/platform/architecture.mdx b/docs/platform/architecture.mdx new file mode 100644 index 0000000..7d1befb --- /dev/null +++ b/docs/platform/architecture.mdx @@ -0,0 +1,47 @@ +--- +title: Platform architecture +description: The implemented responsibilities and connections between Weave products. +product: platform +audience: +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- hostweave:agent/runtime/guestweave/guestweave.go +- imageweave:pkg/delivery/native.go +- agent-core:internal/core/core.go +--- + +The platform separates **placement**, **VM lifecycle**, **image construction** and **guest capabilities**. Those responsibilities connect through explicit interfaces; a common product family does not make every artifact or protocol interchangeable. + + +HostWeave assigns work to a host agent. Its GuestWeave adapter invokes a local CLI. Imageweave hands construction output to OCI tooling through a build manifest. Guest capabilities live behind agent core and its modules. + + +## Responsibilities + +| Area | Owns | Important boundary | +| --- | --- | --- | +| GuestWeave | Local VM lifecycle and host-specific execution | Separate macOS and Windows implementations | +| HostWeave | Control-plane APIs, placement and work execution coordination | Host agents execute through runtime adapters | +| Imageweave | Reviewed image scenarios, construction and qualification paths | Construction alone does not grant runtime acceptance | +| Agent core | Authenticated host services, module verification and supervision | Capability modules execute as separate processes | +| Agent modules | The module SDK and OS-specific capabilities | SDK and module releases have their own versions | + +## Execution and agent roles + +The HostWeave host agent is the process responsible for executing work assigned by the orchestration control plane. Its GuestWeave VM adapter invokes a `weave` binary and interprets the result. The agent core running in a guest is a different process: it supervises capability modules and brokers host/guest communication. + +Document these roles separately. A box labelled only “agent” hides two different lifecycles and trust boundaries. + +## Image and release dependencies + +The shared OCI layer owns the Weave artifact format and artifact tooling. Release-channel data identifies promoted module and image versions. They appear in this first release as dependencies of the selected products rather than separate product documentation areas. + +A build manifest, registry artifact, qualification result and promoted release are distinct objects. There is no verified universal build → publish → promote → run path in this baseline. The [compatibility register](compatibility.md) explains the current image-format gap. + +## Design baseline + +This HLD follows the pinned implementation baseline dated 11 October 2026. [Imageweave’s native construction path](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/pkg/delivery/native.go) and [agent-core assembly](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/core/core.go) provide public evidence. The HostWeave adapter evidence is retained in the internal knowledge base. diff --git a/docs/platform/compatibility.md b/docs/platform/compatibility.md new file mode 100644 index 0000000..23a3487 --- /dev/null +++ b/docs/platform/compatibility.md @@ -0,0 +1,45 @@ +--- +title: Compatibility and known gaps +description: Track verified integration boundaries without treating intended architecture + as runtime support. +product: platform +audience: +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- oci:pkg/spec/mediatypes.go +- hostweave:pkg/images/vm.go +- guestweave-windows:internal/oci/oci.go +- imageweave:pkg/delivery/native.go +- agent-modules:sdk/buf.gen.yaml +--- + +Compatibility claims apply to an exact source or release baseline, host platform and artifact contract. The table below records inspected code boundaries; its status does not imply a successful real-machine acceptance run. + +| Integration | Inspected behaviour | Status | +| --- | --- | --- | +| HostWeave → GuestWeave | VM adapter shells out to the GuestWeave CLI on macOS or Windows | Implemented adapter; runtime combinations require acceptance | +| Imageweave → Weave OCI | Native construction invokes manifest import, packing and deep inspection | Implemented construction handoff | +| Agent core → modules | Version-window negotiation and lifecycle RPC | Implemented protocol boundary | +| Shared OCI images → HostWeave | HostWeave’s parser lists different formats from the shared Weave contract | Compatibility gap; do not claim universal admission | +| Shared OCI images → Windows GuestWeave | Windows OCI code implements GuestWeave VHDX v2 media types | Compatibility gap with shared raw-chunk contract | +| Linux GuestWeave host implementation | No corresponding current repository in the inspected organisation inventory | Outside this documentation baseline | + +## Image formats + +The shared OCI contract defines `application/vnd.weave.guest.config.v1+json` and raw disk chunks. HostWeave’s inspected VM image parser recognises GuestWeave VHDX v2, Tart and Lume media types. Windows GuestWeave’s OCI implementation likewise names the GuestWeave VHDX v2 contract. + +These are different formats. Successful registry transport does not prove that a consumer recognises the artifact, can construct a VM from it or can boot it. Before adding an end-to-end guide, exercise the selected producer and consumer versions together and record the artifact digest and runtime evidence. + +## Construction and qualification + +Imageweave’s native `Construction` result deliberately carries no runtime acceptance field. Its plan can be unverified. Inspection establishes artifact structure; qualification must independently establish the required runtime behaviour. + +## Protocol versions + +The module SDK pins a core release for protocol generation. The release-channel verifier has its own core pin. Do not replace these independent pins with a platform-wide “latest” version. + +Evidence: [shared OCI media types](https://github.com/weaveplatform/weaveplatform-oci/blob/2f088490822cf14a5e9b6824bffe07e932753715/pkg/spec/mediatypes.go), [native construction boundary](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/pkg/delivery/native.go), [SDK protocol generation](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/buf.gen.yaml). Private parser and adapter evidence is retained internally. diff --git a/docs/platform/glossary.md b/docs/platform/glossary.md new file mode 100644 index 0000000..f7e2c80 --- /dev/null +++ b/docs/platform/glossary.md @@ -0,0 +1,32 @@ +--- +title: Platform glossary +description: Shared terminology for hosts, guests, images, modules and qualification. +product: platform +audience: +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- agent-modules:sdk/modulesdk/module.go +- imageweave:pkg/delivery/native.go +--- + +| Term | Meaning | +| --- | --- | +| Host | A machine that supplies a runtime and capacity for work | +| Guest | A VM running on a host | +| GuestWeave | The host-specific VM manager | +| Host agent | HostWeave’s work-execution process; separate from guest agent core | +| Agent core | The runtime that verifies and supervises capability modules | +| Module | A separately launched capability process using the module protocol | +| Host services | The identity, policy, storage, event and other services core offers to a module | +| Image construction | Producing VM image artifacts from reviewed inputs | +| Qualification | Evidence that a candidate meets the required runtime conditions | +| OCI artifact | Registry-transported content described by media types and digests | +| Release channel | Signed data declaring promoted versions and digests | +| Source baseline | An exact implementation commit used to verify documentation | +| HLD | High-level design: responsibilities, components, interfaces and boundaries | + +Use the complete agent role in a diagram or operational instruction. Use qualification for observed runtime evidence, rather than as a synonym for a successful image build. diff --git a/docs/platform/repositories.md b/docs/platform/repositories.md new file mode 100644 index 0000000..24f2f3c --- /dev/null +++ b/docs/platform/repositories.md @@ -0,0 +1,40 @@ +--- +title: Repository catalogue +description: The verified Weave organisation inventory and documentation boundaries. +product: platform +audience: +- contributor +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- documentation:sources/repositories.yaml +- agent-modules:sdk/buf.gen.yaml +--- + +The organisation inventory was checked through GitHub on 11 October 2026. It contained 14 repositories. Private visibility is separate from whether a product has consumer-facing documentation. + +| Repository | Visibility | Documentation role | +| --- | --- | --- | +| [weaveplatform-agent-core](https://github.com/weaveplatform/weaveplatform-agent-core) | public | product | +| [weaveplatform-agent-modules](https://github.com/weaveplatform/weaveplatform-agent-modules) | public | product | +| [hostweave](https://github.com/weaveplatform/hostweave) | private | product | +| [guestweave-cli-macos](https://github.com/weaveplatform/guestweave-cli-macos) | private | product | +| [guestweave-cli-windows](https://github.com/weaveplatform/guestweave-cli-windows) | private | product | +| [imageweave](https://github.com/weaveplatform/imageweave) | public | product | +| [weaveplatform-oci](https://github.com/weaveplatform/weaveplatform-oci) | public | dependency | +| [weaveplatform-release-channels](https://github.com/weaveplatform/weaveplatform-release-channels) | public | dependency | +| [weaveplatform.run](https://github.com/weaveplatform/weaveplatform.run) | public | documentation | +| [weaveplatform-docs](https://github.com/weaveplatform/weaveplatform-docs) | public | deprecated-scaffold | +| [weaveplatform-template](https://github.com/weaveplatform/weaveplatform-template) | private | tooling | +| [appweave](https://github.com/weaveplatform/appweave) | private | deferred | +| [sightweave](https://github.com/weaveplatform/sightweave) | private | deferred | +| [terraform-provider-hostweave](https://github.com/weaveplatform/terraform-provider-hostweave) | private | scaffold | + +Initial product coverage is GuestWeave, HostWeave, Imageweave, agent-core and agent-modules. OCI and release channels appear as dependencies. The private companion repository is provisioned as part of this documentation implementation; it is an addition to this discovery baseline. + +Older local API/SDK checkouts use previous repository identities. They are not the current protocol or SDK authorities. The old `weaveplatform-channels` GitHub identity redirects to `weaveplatform-release-channels`. + +The original documentation scaffold also named products and a Linux GuestWeave implementation absent from this organisation inventory. Those names do not establish a current implementation or supported workflow. diff --git a/docs/products/agent-core/hld.mdx b/docs/products/agent-core/hld.mdx new file mode 100644 index 0000000..5205ffe --- /dev/null +++ b/docs/products/agent-core/hld.mdx @@ -0,0 +1,46 @@ +--- +title: 'Agent core: HLD' +description: Verification, supervision, local host services and the guest channel. +product: agent-core +audience: +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- agent-core:cmd/weaveboot/main.go +- agent-core:cmd/weave-agent/main.go +- agent-core:internal/core/core.go +- agent-core:proto/weave/agent/v1/module.proto +--- + +Agent core is the supervised capability runtime on a device or guest. It verifies and launches modules, offers host services and assembles the communication and policy boundaries those modules use. + + +The OS service manager runs weaveboot. weaveboot supervises core, and core verifies and supervises capability modules. Core also provides the host-service RPC surface. + + +## Process ownership + +The installed service entrypoint is `weaveboot`. It stages and supervises core and provides rollback behaviour. The core binary is `weave-agent`; local control is exposed through `weavectl`. These names replace the speculative binary names in the original repository scaffold. + +Core owns the module registry and supervisor. A module is a separate process, with its own negotiated protocol and lifecycle. It is not an in-process product plugin. + +## Runtime assembly + +Core assembles identity, policy management, namespaced storage, host services, the event bus, local control and guest transport. The host-service surface lets modules obtain capabilities without owning core’s private keys or another module’s namespace. + +Transport selection depends on host capabilities and configuration. Unsupported selection disables that channel rather than inventing a transport. The guest channel’s host-authentication key is a separate trust input from the keys used to verify module/channel data. + +## Verification and failure handling + +When no module verifier is configured, the fallback refuses execution. Storage failure can affect identity and queue services. The inspected core distinguishes a locked store that stops startup from other failures that expose degraded/unavailable services; this is a concrete condition to preserve in future recovery guides. + +weaveboot’s readiness and promotion behaviour is separate from a module’s health. A core process being ready does not mean every capability module is healthy. + +## Protocol authority + +Core’s `proto/` owns the agent wire contract. Core generates a private implementation copy; the module SDK generates its own copy against a pinned release. Channel and module JSON schemas are also published from core. + +Evidence: [weaveboot entrypoint](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/cmd/weaveboot/main.go), [core assembly and failure handling](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/core/core.go), [module lifecycle protocol](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/proto/weave/agent/v1/module.proto). diff --git a/docs/products/agent-modules/hld.mdx b/docs/products/agent-modules/hld.mdx new file mode 100644 index 0000000..5bfa43c --- /dev/null +++ b/docs/products/agent-modules/hld.mdx @@ -0,0 +1,44 @@ +--- +title: 'Agent modules and SDK: HLD' +description: The module runtime, Host interface and independently released OS capabilities. +product: agent-modules +audience: +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- agent-modules:sdk/modulesdk/module.go +- agent-modules:sdk/modulesdk/serve.go +- agent-modules:sdk/buf.gen.yaml +- agent-modules:go.work +--- + +The agent-modules repository owns the current module SDK and OS-specific capability modules. Modules are independent Go modules rather than a single application with one release number. + + +Core launches a verified module. The SDK negotiates the protocol, serves lifecycle RPCs and connects to authenticated host services during Init. Unsupported protocol windows are rejected before the module lifecycle begins. + + +## Module and Host interfaces + +A module implements `ID`, `Requires`, `Init`, `Start`, `Stop` and `Health`. `modulesdk.Serve` handles its negotiated startup and lifecycle serving. + +The `Host` interface exposes identity, transport, policy, namespaced storage, events, UI brokerage, logging and the module registry. It is deliberately bounded. Configuration and recurring work are optional module-side interfaces, rather than reasons to widen the wire contract. + +## Startup and lifecycle + +The SDK reads the core-advertised protocol window and refuses an unsupported protocol before initialisation. It opens its lifecycle server, writes one handshake line containing protocol and endpoint information, and then stops using stdout for ordinary output. + +Host-service connection is deferred until Init, when core’s host listener is available. The SDK exits on shutdown, loss of core connection, signal or server failure, avoiding a detached module continuing without its supervisor. + +Health has a rich module-level status alongside the standard gRPC health service. Operational guidance must identify which surface it is checking. + +## Versioning and capability scope + +The repository’s SDK, packaging tools and OS capability modules have independent module paths and releases. The SDK’s protocol-generation core pin is separate from whichever core release a device installs. Compatibility follows the negotiated window and tested release combination. + +OS-specific capabilities include execution, display, clipboard, power, presence, session, metrics and time. A module’s presence is different from its health and the capabilities it can actually serve in the current session. + +Evidence: [Module and Host interfaces](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/modulesdk/module.go), [SDK serve loop](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/modulesdk/serve.go), [protocol generation](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/buf.gen.yaml). diff --git a/docs/products/guestweave/index.md b/docs/products/guestweave/index.md new file mode 100644 index 0000000..dcf0f42 --- /dev/null +++ b/docs/products/guestweave/index.md @@ -0,0 +1,34 @@ +--- +title: GuestWeave +description: Understand the separate macOS and Windows host VM managers. +product: guestweave +audience: +- consumer +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- guestweave-macos:internal/httpapi/server.go +- guestweave-windows:internal/serve/http.go +- hostweave:agent/runtime/guestweave/guestweave.go +slug: /products/guestweave +--- + +GuestWeave manages guest virtual machines on a local host. The current documentation covers two implementations: macOS and Windows. Their command surfaces, HTTP routes, backend APIs and image formats must be documented separately. + +| Host implementation | Design | Key interface boundary | +| --- | --- | --- | +| macOS | [macOS HLD](macos.mdx) | CLI, HTTP adapters and a separate MCP server | +| Windows | [Windows HLD](windows.mdx) | CLI and guarded HTTP server over Windows host services | + +HostWeave can drive GuestWeave through a runtime adapter that invokes the local CLI. GuestWeave remains responsible for VM lifecycle; HostWeave remains responsible for placement and orchestration. + +## Consumer contract + +The implementations expose VM creation and lifecycle operations, but similar operation names do not guarantee identical requests. For example, the inspected macOS router uses a VM `run` route, while Windows uses `start`. The [reference index](../../reference/index.md) points to the two contracts independently. + +## Documentation baseline + +Both implementations have private source repositories. This public knowledge base describes their approved product responsibilities and interfaces; detailed evidence references live in the private companion. Host/guest combinations need runtime acceptance before receiving a support claim. diff --git a/docs/products/guestweave/macos.mdx b/docs/products/guestweave/macos.mdx new file mode 100644 index 0000000..a3a38c9 --- /dev/null +++ b/docs/products/guestweave/macos.mdx @@ -0,0 +1,48 @@ +--- +title: 'GuestWeave on macOS: HLD' +description: VM lifecycle, host backend and HTTP boundaries on macOS. +product: guestweave +audience: +- consumer +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- guestweave-macos:internal/httpapi/server.go +- guestweave-macos:internal/httpapi/router_schema_test.go +- guestweave-macos:go.mod +--- + +GuestWeave on macOS brings CLI and HTTP operations into a shared command and VM service layer. The HTTP API is an adapter over those operations. MCP is a separate server surface, so it must receive its own reference and authentication review. + + +Requests enter through a product interface, reach shared lifecycle operations and invoke the host backend. VM files retain the local state needed by later operations. + + +## Components and boundaries + +| Component | Responsibility | +| --- | --- | +| CLI and HTTP adapters | Parse product requests and return operation results | +| Command layer | Share lifecycle behaviour between entrypoints | +| VM service | Coordinate VM configuration and local state | +| Host backend | Invoke macOS virtualization facilities | +| VM files | Persist disks, configuration and VM state | + +Backend selection is host-architecture and guest-OS dependent. The presence of a code path does not certify every possible guest combination. + +## HTTP interface + +The inspected server binds to `127.0.0.1` and mounts product routes under `/weave`. VM operations include creation, `run`, stop, shutdown, restart, snapshots and guest execution. Its specification is served at `/weave/openapi.yaml`. + +This loopback boundary is material to deployment guidance. Any future remote exposure guide must establish authentication, transport protection and origin handling for that specific setup. + +## Orchestration integration + +HostWeave’s GuestWeave runtime adapter calls the local CLI. Keep binary version, arguments, output parsing and readiness behaviour in the integration contract. The local HTTP API is a separate integration surface. + +## Verification and limitations + +The repository includes a router/spec test that compares served HTTP methods and paths with OpenAPI operations. This HLD is code-inspected at the recorded `main` baseline; macOS VM acceptance was not exercised for this documentation release. API reference must also verify request and response semantics, beyond route parity. diff --git a/docs/products/guestweave/windows.mdx b/docs/products/guestweave/windows.mdx new file mode 100644 index 0000000..6559510 --- /dev/null +++ b/docs/products/guestweave/windows.mdx @@ -0,0 +1,44 @@ +--- +title: 'GuestWeave on Windows: HLD' +description: Windows VM lifecycle, HTTP protection and the current image contract. +product: guestweave +audience: +- consumer +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- guestweave-windows:internal/serve/http.go +- guestweave-windows:internal/serve/openapi_test.go +- guestweave-windows:internal/oci/oci.go +--- + +GuestWeave on Windows manages local Windows and Linux guests through Windows host-compute facilities. The HTTP handlers adapt the command layer; the runtime implementation and local networking remain host responsibilities. + + +The command layer invokes HCS-backed VM lifecycle operations. HCN provides host networking. Guest execution is a separate boundary from the HTTP entrypoint. + + +## HTTP interface and trust boundary + +VM routes are mounted under `/weave/vms`. The inspected server includes start, stop, suspend, guest execution, disk and snapshot operations. Liveness is at `/healthz`; the contract is available at `/openapi.json`. + +`ListenAndServe` loads or creates an API token and wraps the handler with bearer-token and host checks. The bare handler constructor does not apply that protection itself. Integrations mounting the handler must preserve the wrappers. + +This distinction belongs in infrastructure and embedding guides because inspecting only the router would miss the server’s authentication boundary. + +## Images and state + +The current Windows OCI implementation names GuestWeave VHDX v2 configuration and compressed disk-layer media types. VM identities are handled as part of the implementation’s image/lifecycle behaviour; do not infer interchangeability with the newer shared Weave raw-chunk contract. + +Disk and snapshot operations change persistent host state. Future task guides must identify the VM, expected state, recovery behaviour and cleanup procedure for the selected release. + +## Orchestration integration + +HostWeave invokes the Windows GuestWeave CLI through its VM runtime adapter. CLI version and output compatibility are separate from the HTTP schema. + +## Verification and limitations + +The repository includes a router/spec parity test. This HLD follows the inspected code baseline; it does not claim a Windows-host acceptance run. The [compatibility register](../../platform/compatibility.md) records the shared-image-format gap. diff --git a/docs/products/hostweave/hld.mdx b/docs/products/hostweave/hld.mdx new file mode 100644 index 0000000..e7db49f --- /dev/null +++ b/docs/products/hostweave/hld.mdx @@ -0,0 +1,48 @@ +--- +title: 'HostWeave: HLD' +description: Control-plane interfaces, host agents and the GuestWeave execution boundary. +product: hostweave +audience: +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- hostweave:internal/server/server.go +- hostweave:agent/runtime/guestweave/guestweave.go +- hostweave:pkg/images/vm.go +- hostweave:Dockerfile +--- + +HostWeave coordinates work across hosts. Its control plane handles product requests and placement; a host agent executes the assigned work through a runtime adapter. GuestWeave owns VM lifecycle on the local machine. + + +An operator calls the control plane. The host agent initiates its connection and receives work leases; the GuestWeave adapter executes subprocess commands against the local VM manager. + + +## Control-plane interface + +The server mounts generated OpenAPI handlers on a Chi router. API requests pass through authentication, request limits, observation and operation middleware; authorisation is enforced through the server’s policy layer. Streaming job events, logs and platform changes are registered alongside the generated routes. + +Signed-URL blob transfers use a distinct authorised transfer boundary. They bypass JSON request limits and API middleware by design. Deployment and integration guides must distinguish API credentials from signed artifact URLs. + +## Host execution + +The GuestWeave adapter supports macOS and Windows hosts. It invokes `weave` as a subprocess and interprets VM information and execution results. It supports an optional configured binary checksum; checksum verification is not unconditional in the constructor. + +Readiness and guest execution capabilities are checked independently of successful process creation. A working CLI invocation alone does not prove that an assigned guest workload is ready. + +## Storage and deployment + +The control plane receives configured store, blob and secret implementations. The repository’s Docker build packages the service, CLI and web UI into a non-root runtime image. Its non-loopback API defaults refer to mounted TLS certificate and key files. + +Those files are evidence for a deployment guide, not a complete production installation procedure. Certificate lifecycle, durable storage, migrations, backup and recovery require a tested environment-specific guide. + +## Image compatibility + +The inspected parser recognises GuestWeave VHDX v2, Tart and Lume artifact formats. Do not describe all shared Weave OCI images as admitted by this version. See the [compatibility register](../../platform/compatibility.md). + +## Evidence baseline + +This HLD is code-inspected at the recorded `main` commit. Private server, runtime adapter, image-parser and Docker evidence is retained in the internal knowledge base. A cross-product acceptance run remains part of the next content milestone. diff --git a/docs/products/imageweave/hld.mdx b/docs/products/imageweave/hld.mdx new file mode 100644 index 0000000..46119d5 --- /dev/null +++ b/docs/products/imageweave/hld.mdx @@ -0,0 +1,44 @@ +--- +title: 'Imageweave: HLD' +description: Reviewed image inputs, Packer construction and qualification boundaries. +product: imageweave +audience: +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- imageweave:pkg/plan/plan.go +- imageweave:internal/packerplugin/builder.go +- imageweave:pkg/delivery/native.go +- imageweave:pkg/delivery/build.go +--- + +Imageweave resolves reviewed image scenarios and constructs VM artifacts through Packer. It owns construction and its qualification paths; OCI tooling owns artifact packaging and checks at the handoff. + + +Pinned inputs resolve to a plan and Packer template. The native path imports manifest-listed outputs, packs the artifact and performs deep inspection. Runtime qualification is a separate requirement. + + +## Inputs and planning + +The request names an image family, release, architecture and purpose. Planning validates the relevant input shape, selects the scenario and freezes its template variables. Input hashes and reviewed recipe identity matter independently of filenames. + +The resolver branches for native macOS/Windows construction and other scenarios. A resolved plan is a candidate, with qualification explicitly unverified where applicable. Source authentication remains the caller’s responsibility; checking a byte hash alone does not establish who supplied those bytes. + +## Native construction + +The native Packer plugin implements the builder boundary. Its output manifest identifies the artifacts eligible for import. The construction path invokes Packer initialisation, validation and build, then calls `weaveoci bundle import-imageweave`, packing and strict deep inspection. + +The native `Construction` result contains layout, bundle, manifest and recipe information. It deliberately has no acceptance field. OS install or restore completion is different from first-boot, identity and reboot evidence. + +## Qualification and publication + +Construction, runtime qualification, authenticated admission and channel promotion are distinct stages. The native construction operation does not publish an image or grant promotion permission. Other delivery paths carry their own acceptance outputs; document each path independently. + +## Operational constraints + +Build hosts need the chosen Packer plugin, media and runtime prerequisites. Temporary workspace, disk space, firmware inputs and signing/publishing credentials belong in scenario-specific guides. A guide must record the exact build recipe and producer/consumer compatibility. + +Evidence: [plan resolver](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/pkg/plan/plan.go), [native Packer builder](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/internal/packerplugin/builder.go), [native construction](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/pkg/delivery/native.go). This source baseline does not imply an available tagged Imageweave binary release. diff --git a/docs/reference/contracts.md b/docs/reference/contracts.md new file mode 100644 index 0000000..7057647 --- /dev/null +++ b/docs/reference/contracts.md @@ -0,0 +1,31 @@ +--- +title: Public contract catalogue +description: Download checksummed contracts from the pinned public-source baseline. +product: platform +audience: +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- agent-core:schema/module-manifest.schema.json +- agent-core:schema/channel-manifest.schema.json +- oci:pkg/spec/schema/vm-config-v1.schema.json +- agent-core:proto/weave/agent/v1/module.proto +--- + +These snapshots originate from public repositories and are checked against the source-lock checksums on every build. Changes belong upstream; update the pin, snapshot and documentation together after reviewing the implementation. + +| Contract | Snapshot | Owning source | +| --- | --- | --- | +| Module manifest | [JSON schema](/contracts/module-manifest.schema.json) | Agent core | +| Release channel | [JSON schema](/contracts/channel-manifest.schema.json) | Agent core | +| VM artifact configuration | [JSON schema](/contracts/vm-config-v1.schema.json) | Weave OCI | +| Module lifecycle | [Protobuf](/contracts/module.proto) | Agent core | +| Module handshake | [Protobuf](/contracts/handshake.proto) | Agent core | +| Module registry | [Protobuf](/contracts/registry.proto) | Agent core | + +The protobuf snapshots are reference downloads. They may import other protocol files; they are not distributed here as a standalone compilation bundle. Use the pinned owning repository for code generation. + +Schema validation alone does not prove parser, verifier or runtime acceptance. Treat the [compatibility register](../platform/compatibility.md) as part of any integration decision. diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 0000000..8b0726b --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,42 @@ +--- +title: Reference index +description: Find the authoritative product APIs, agent protocols and configuration + contracts. +product: platform +audience: +- consumer +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- guestweave-macos:internal/httpapi/server.go +- guestweave-windows:internal/serve/http.go +- hostweave:api/openapi.yaml +- agent-core:proto/weave/agent/v1/module.proto +- agent-modules:sdk/modulesdk/module.go +slug: /reference +--- + +The platform has several distinct interfaces. Generate reference from the owning repository’s contract and verify that contract against implementation. A central reference index does not make the interfaces one API. + +| Interface | Authority | Validation boundary | +| --- | --- | --- | +| GuestWeave macOS HTTP | Product OpenAPI document served at `/weave/openapi.yaml` | Router/spec parity plus request/response conformance | +| GuestWeave Windows HTTP | Product OpenAPI document served at `/openapi.json` | Router/spec parity and bearer-token wrappers | +| HostWeave HTTP | Product `api/openapi.yaml` and generated server | Generated handlers, middleware and semantic tests | +| HostWeave host-agent protocol | Product agent protobuf | Gateway/runtime protocol tests | +| Agent lifecycle and host services | Agent-core protobuf | Buf lint, breaking-change and generation checks | +| Module implementation API | Agent-modules SDK | SDK interfaces and lifecycle/testkit behaviour | +| Module/channel manifests | Agent-core JSON schemas and parsers | Schema validity, parser rules and verifier checks | + +## Available contract snapshots + +The [contract catalogue](contracts.md) exposes public-source schemas and protobuf snapshots at recorded commits. These snapshots are inputs for reference generation; they are not a second editable contract. + +Private-product OpenAPI documents remain in the private companion until a publication review approves an export. Their public interface entrypoints are documented above without uploading raw private source files. + +## Next reference work + +The next milestone adds generated operation pages, examples and configuration tables after semantic cross-checking. CLI reference will use actual command definitions or help output. Request bodies, authentication, error responses, streaming and operation completion must be verified in addition to method/path parity. diff --git a/docs/start-here/index.md b/docs/start-here/index.md new file mode 100644 index 0000000..e187f06 --- /dev/null +++ b/docs/start-here/index.md @@ -0,0 +1,39 @@ +--- +title: Start here +description: Choose a path through the Weave platform knowledge base. +product: platform +audience: +- consumer +- developer +- operator +- contributor +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- hostweave:agent/runtime/guestweave/guestweave.go +- agent-core:internal/core/core.go +slug: /start-here +--- + +Weave brings together virtual machine management, workload orchestration, image construction and a capability-based agent runtime. This knowledge base explains the responsibilities of those parts and the interfaces between them. + +## Choose your starting point + +| Your task | Start with | +| --- | --- | +| Understand the whole platform | [Platform architecture](../platform/architecture.mdx) | +| Run or integrate with local VMs | [GuestWeave](../products/guestweave/index.md) | +| Place work across hosts | [HostWeave HLD](../products/hostweave/hld.mdx) | +| Build VM images | [Imageweave HLD](../products/imageweave/hld.mdx) | +| Understand device and guest capabilities | [Agent core](../products/agent-core/hld.mdx) and [agent modules](../products/agent-modules/hld.mdx) | +| Find the contract behind an interface | [Reference index](../reference/index.md) | +| Contribute to this knowledge base | [Documentation workflow](../development/documentation.md) | + +## Read the evidence correctly + +The initial release focuses on high-level designs. These pages describe inspected implementation at recorded commits on `main`; they do not certify a deployed environment or every host/guest combination. A release tag and an implementation baseline are separate facts. + +The [compatibility register](../platform/compatibility.md) names integration limits. Runnable tutorials and infrastructure guides are added when their exact versions and procedures have been exercised. [The roadmap](../development/roadmap.md) records that work. + +Public explanations live here. Confidential implementation evidence, environments and operational knowledge belong in the private `weaveplatform.dev` companion repository. A public product page remains public even when its source repository is private. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c8e81c7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,19953 @@ +{ + "name": "weaveplatform-docs-workspace", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "weaveplatform-docs-workspace", + "workspaces": [ + "site", + "packages/docs-kit" + ], + "devDependencies": { + "@playwright/test": "1.58.2", + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "gray-matter": "4.0.3", + "markdownlint-cli2": "0.20.0", + "pagefind": "1.5.2", + "typescript": "5.9.3", + "wrangler": "4.149.0", + "yaml": "2.8.2" + }, + "engines": { + "node": ">=22 <23" + } + }, + "node_modules/@11ty/gray-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@11ty/gray-matter/-/gray-matter-1.0.0.tgz", + "integrity": "sha512-7mJJl+wf1AByoT0PknQiQfOPnVNT4fevGrUBVWO4HXsnYn1aQPyRyrELYrNUFleUBM++KzMKN6QaxHPk0t/6/g==", + "license": "MIT", + "dependencies": { + "js-yaml": "^4.1.0", + "kind-of": "^6.0.3", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=11" + } + }, + "node_modules/@11ty/gray-matter/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@11ty/gray-matter/node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.25.0.tgz", + "integrity": "sha512-rSTin9Uta23uaewYVQEp8XI9T3iA/zrg0/1G2vhf8oFFDxFL5vybnZ5IQwsVAg4JpKxPX4/WYNKdcfWrZymk7w==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.19.12", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.12.tgz", + "integrity": "sha512-nJU03L3Q0LlfnFsQTE1YppSrNcbP0lBg29mCaGCeYPog841HP9WWk5KtrTvNH2LPX6p5eELPFetSeSO8I1MFPg==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.12", + "@algolia/autocomplete-shared": "1.19.12" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.12", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.12.tgz", + "integrity": "sha512-L3Fvu4Ajb7c9X1DMTHFFD8d1uK898b9bg9ENrjLpkSP8BAzO4vS5/MIsU0ks0FqpOihZ4VDAVYRaU6bhm/CljQ==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.12" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.19.12", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.12.tgz", + "integrity": "sha512-goe55oftoEduuOHhTg4viHr58/ZsZvYEtfxkKFy6LdJxZ7+jOdVwp2bZz7qjip9CdFqkIB+34d4esftcaErCzA==", + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.59.0.tgz", + "integrity": "sha512-bm2XN0hCSMYwStSsCBT0/PUB2BDxoyR1Lnub3c392HMEy9bi8PUSW8vR6zltVEKWG2t4PQFWMD5C07bmJxGgPg==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.59.0.tgz", + "integrity": "sha512-XOFPOTa69WuqHR6c5tMgnUUwwqQgNSzMpxmhrgA9KmxRf8WIqEa0cokHJvohk5CYb7CZx0xeSL6Bk2IUJ7Lv7Q==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.59.0.tgz", + "integrity": "sha512-PC8ipLOYFKRTfIUY1J3FJxS6ryzWziaXmIX9/sNMoUR8L+XhF7hX2QAeUI87YVl5zujvlYTSOb+HAIqKXDjyHQ==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.59.0.tgz", + "integrity": "sha512-yFNcCMM5fHiyoR0HuxMrzy+VjDcmhFUZTm2IJ2DHwGsVH3B5SEob4zTmeEZ3j/AZqNnmrJOCKl/tJnBg7+84bA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.59.0.tgz", + "integrity": "sha512-GYja6HkDt2VrQhWmB2cLx3Z5fDwI9no7q+xwCWcFrTPm5CLH9QZvK0XLO9co1FcNIDxxkMaP3AXM39/XcecEOg==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.59.0.tgz", + "integrity": "sha512-Wofg7bMpWh8N5qDDZs0wy6whc+KMmdNsxgrIGp9Ug2s1Bka0uq7AjyckdxReKPHLA0Q1qH8X/SNh0wn2t2X2Lw==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.59.0.tgz", + "integrity": "sha512-fHnALZfbEnODczGk14Y/1YBRApp6UEpZUTexGcMUPzY7RDc7q4HN2Y6jh0KUG8Jo9B+q+wOoQEc3ziR0ErbuFg==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", + "license": "MIT" + }, + "node_modules/@algolia/ingestion": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.59.0.tgz", + "integrity": "sha512-Fa38s1mHgoaLCT117sfJ6P78rtxUt93CYxBYpq1VOIcslaF+cH+1h5uikAPsKfxLnsuEDZHiBiaI6eNPTsQMRA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.59.0.tgz", + "integrity": "sha512-NyNsRSqM2tF1MX7ZGw/j4rduoEJiQ5wfvbSo/CFVdEzYCjyxbGFRPOZyS/GETJG9rk1TdHOq8NZqKqk/u1fm4g==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.59.0.tgz", + "integrity": "sha512-nXBK2uygWtvbCOffMWqqfjjcEtp9enDKY5/2Pw/Hhw8VVaOyK2ThbGK04bNX/Le+qoK1VHYkodWGVTKfw0Otkw==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.59.0.tgz", + "integrity": "sha512-yb+4afX/zja8QwX0KmV4/ae2kyYkRknsE79CRusYS2U5D8qwn2wq0cn1x12f3tm3F3+5FgmDdbuGdQTuCLSKMQ==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.59.0.tgz", + "integrity": "sha512-Lp52TmpA1QtNmdHzs505Xwf4tgII7RAapkDSF9AAtWIBETcGaTIaXci4Rd9nS6SdaWK1BWTSqAPp5wzh6UN3Ag==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.59.0.tgz", + "integrity": "sha512-YYHLEs5rC6oRTFwT7bJaKBR9NSFzikeVHGAT1ffATmUQzR8XqyhHXjoieAUoR7fBU6I5cfy1FVeh5GoRgriuzA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.10.tgz", + "integrity": "sha512-/YY9soQuWa8TURtHXHT+GbklpbAA+dZErxFGHIr6qgDg96xuGWrTAs1+MfjgVJPF0YLQsOVsOYs8lhgsPbOinQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.5.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz", + "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.29.7.tgz", + "integrity": "sha512-J0wGhKan+rIiE2OhfhRptySLrJ6SjQYM6b6N1FMlhyhCcw1Mig8vQjWchyB+bgHGDvaWo6Diu6CLRMra2uMtmg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", + "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", + "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", + "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", + "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", + "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.8.tgz", + "integrity": "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.9.tgz", + "integrity": "sha512-FFwIwzU+7SCOuxxV4YtJql6T9981ZVTm+FHO5GhVsRqCTdy0WwrEZh3l42ARpXruJUDGOptdduHW6Zr7pNPLNg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz", + "integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-transform-react-display-name": "^7.29.7", + "@babel/plugin-transform-react-jsx": "^7.29.7", + "@babel/plugin-transform-react-jsx-development": "^7.29.7", + "@babel/plugin-transform-react-pure-annotations": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.10.tgz", + "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", + "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.2.tgz", + "integrity": "sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": ">1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20261006.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20261006.1.tgz", + "integrity": "sha512-lCX6avJO2It9AyDEYitjmOtJRVp/xmaW0TziyoLAnfxlzozyyRbSFfIIMMCiDx/sTP1++E4VLbV+7BHSD33f0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20261006.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20261006.1.tgz", + "integrity": "sha512-PyFGjOaldtppnLuKsdkoYWXfLVpDjuf8u+uoykX+1Xvxo2EPmCVViq0iDKm+U7c7PRno85ehS3/MPVXyk2HwHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20261006.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20261006.1.tgz", + "integrity": "sha512-Ntf9S8eesmVufdjaT2kzWlz6Ng9XGYTc6cHQVRKAg59lFufwsIabGeMT6U4KkrpEeSIxgHq8aPtuLbZXE19tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20261006.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20261006.1.tgz", + "integrity": "sha512-JyeDBymVWw5tVRebSsVE8ojDYxd6UlZrAseHU8G+NpIyd5wF61tXlF/axLeCUqu0pOhBFHb8I5QKi0jwrCK41Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20261006.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20261006.1.tgz", + "integrity": "sha512-nWP0URNqOBN9fa/gZ7eh/w051DwjOXN8j4lDZDYVXyQPsux6sV3DfyYZT+CjR/rt9nKBSmJT9QE9/bWT18+84w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", + "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/postcss-alpha-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", + "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", + "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", + "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-function-display-p3-linear": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", + "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", + "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", + "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", + "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-contrast-color-function": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", + "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", + "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", + "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", + "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", + "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", + "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", + "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", + "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", + "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", + "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", + "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", + "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.1.tgz", + "integrity": "sha512-TQUGBuRvxdc7TgNSTevYqrL8oItxiwPDixk20qCB5me/W8uF7BPbhRrAvFuhEoywQp/woRsUZ6SJ+sU5idZAIA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", + "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-position-area-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-position-area-property/-/postcss-position-area-property-1.0.0.tgz", + "integrity": "sha512-fUP6KR8qV2NuUZV3Cw8itx0Ep90aRjAZxAEzC3vrl6yjFv+pFsQbR18UuQctEKmA72K9O27CoYiKEgXxkqjg8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", + "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-property-rule-prelude-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-property-rule-prelude-list/-/postcss-property-rule-prelude-list-1.0.0.tgz", + "integrity": "sha512-IxuQjUXq19fobgmSSvUDO7fVwijDJaZMvWQugxfEUxmjBeDCVaDuMpsZ31MsTm5xbnhA+ElDi0+rQ7sQQGisFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", + "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", + "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", + "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", + "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-syntax-descriptor-syntax-production": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-syntax-descriptor-syntax-production/-/postcss-syntax-descriptor-syntax-production-1.0.1.tgz", + "integrity": "sha512-GneqQWefjM//f4hJ/Kbox0C6f2T7+pi4/fqTqOFGTL3EjnvOReTqO1qUQ30CaUjkwjYq9qZ41hzarrAxCc4gow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-system-ui-font-family": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-system-ui-font-family/-/postcss-system-ui-font-family-1.0.0.tgz", + "integrity": "sha512-s3xdBvfWYfoPSBsikDXbuorcMG1nN1M6GdU0qBsGfcmNR0A/qhloQZpTxjA3Xsyrk1VJvwb2pOfiOT3at/DuIQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", + "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", + "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.7.1.tgz", + "integrity": "sha512-uzIFuu2Wsf3+/ZdLGDl6MhpjCZQ5DTMBGe1u/2DdQV+GlHEerOcthWZAoptCsNtjk+DX4Tbx3HmEgJE7RuMQhw==", + "license": "MIT", + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@docsearch/css": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.7.1.tgz", + "integrity": "sha512-AsMPnbNmVLDNjhtgfq4ZRskNvWR8vuK6riEegHp3kjBFtxbmTWPzZ5s1FJ04SGr2D0ZXglueUNISsV2sDgV6Jw==", + "license": "MIT" + }, + "node_modules/@docsearch/react": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.7.1.tgz", + "integrity": "sha512-MhRNQp+Ew+i/xc/+ydDgSQmu6/lEH+Og8F0d1qpXWV/kKNw8OhFfDnLNJlNLiqZQhU1GaEv37G4wxipKw+HdNA==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.19.2", + "@docsearch/core": "4.7.1", + "@docsearch/css": "4.7.1" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-core": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", + "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", + "@algolia/autocomplete-shared": "1.19.2" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", + "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.2" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-shared": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", + "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@docusaurus/babel": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.10.2.tgz", + "integrity": "sha512-aJ1hpGyvfkte3dDAfNbWM4biW4yWZBVz7TIGLZP+v+tWOBgxX3e0N5ZIXHIvmfNNXTI77pcHUx3KmtOk05Ze3Q==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/bundler": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.10.2.tgz", + "integrity": "sha512-i0ZNcy0f0WhaOlYVgzLsWhIoEXO9kS3HRoKPtgE6vQtZUq7arKZaYdNBudr3mqCmd+TyOkwtwfHgs1ENj07r5g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.10.2", + "@docusaurus/cssnano-preset": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.11.0", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.2", + "null-loader": "^4.0.1", + "postcss": "^8.5.4", + "postcss-loader": "^7.3.4", + "postcss-preset-env": "^10.2.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^7.0.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.10.2.tgz", + "integrity": "sha512-EYByj6nk+aD9KeVxV6Hmo2/nAAT79P21Y82ycTBOBtrmqilloIbIEhgL2/8Xpt2Jz/pgNqHAwyusOGwmbKeJmA==", + "license": "MIT", + "dependencies": { + "@docusaurus/babel": "3.10.2", + "@docusaurus/bundler": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "core-js": "^3.31.1", + "detect-port": "^2.1.0", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "execa": "^5.1.1", + "fs-extra": "^11.1.1", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.6.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "open": "^8.4.0", + "p-map": "^4.0.0", + "prompts": "^2.4.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.3", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.7", + "tinypool": "^1.0.2", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^5.2.2", + "webpack-merge": "^6.0.1" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*", + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@docusaurus/cssnano-preset": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.2.tgz", + "integrity": "sha512-4gCnHRbJLTloiwfvFAa92tgb2gI4KYhvjfQVYnEaiMO/EgvWfCo1LwytHXen+1oZAN0VAlS0JAPxp3MsvKDa3A==", + "license": "MIT", + "dependencies": { + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.5.4", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/logger": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.10.2.tgz", + "integrity": "sha512-gSEwqtPfCAnC3ZSJY6xL7tcIfgg0vFD39jbv93eakuweyvO2864xR0K+kmKwBhkTCtWRNjuGGnb5rdmkD/ndqw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/mdx-loader": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.10.2.tgz", + "integrity": "sha512-9Fd4V/SFjfrVQ0JH5EN0+iPWyFunvTeQE3gfyFeetqPaXMP0OylIjOw16dCuXG4NZJrYdBqwzjh18/h3gRi47w==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "image-size": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/module-type-aliases": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.2.tgz", + "integrity": "sha512-h/I5e4jaAhDHW4vaLENi1i2hnOEnXY1t9R+nnRTbgUl7ymVRzN/HF7dDfj8rKYGj8gfIge+Ef+iYRAMtbGvsrQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.10.2", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@docusaurus/plugin-content-blog": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.2.tgz", + "integrity": "sha512-0cbEnNKf0InmLkhj/+nVRmqEnWEoOE8Mh+2x1qOXI0qYpCnphq4RXknVJ8BvybKRXqYVvbmdMfiJSup+k4tm5w==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "cheerio": "1.0.0-rc.12", + "combine-promises": "^1.1.0", + "feed": "^4.2.2", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "srcset": "^4.0.0", + "tslib": "^2.6.0", + "unist-util-visit": "^5.0.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-docs": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.2.tgz", + "integrity": "sha512-Sqwl4FPoZBDrlY8I2VU2H8O0M91CHp9T8ToMSkTZmjvHCif+1laqfXi6sTk8IfyVS/trN5yNjcWd1bFsGB6W5Q==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@types/react-router-config": "^5.0.7", + "combine-promises": "^1.1.0", + "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "tslib": "^2.6.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-docs/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@docusaurus/plugin-content-docs/node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@docusaurus/plugin-content-pages": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.2.tgz", + "integrity": "sha512-h5R12sZ/vV9EPiVjvIl9YFCOwkpwXes7dQMYt3EvP6Pphu4amHxxTqWxf08Fl5DR8h+oZMbWpFTNw5vKEYfvzQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-css-cascade-layers": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.2.tgz", + "integrity": "sha512-UkdvQby5OQUKWrw3lLnSTJXQ6VETaUVTuPQX9AABtmFm5h+ifEBx1OQ+LN726Q4byuwBf2ElHkf4qU4hTxdvRg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/plugin-debug": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.10.2.tgz", + "integrity": "sha512-8vbZNOSCpnsT57EY6CgN7sgRVmx3KTYwO8Uvo2pbxOyb8tbqAwtT9SslqaQ41HbA1v1hpn5RP7u5s2KvRwAFpQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "fs-extra": "^11.1.1", + "react-json-view-lite": "^2.3.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-analytics": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.2.tgz", + "integrity": "sha512-kMHMBK9j4VAtgd5owwrRLRIi0EjkrpXlX7ePj1+y68XfVZV9I1T4S+koPDm+Hfw2TtnyHvh0uNrDvjz+DjQGVA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-gtag": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.2.tgz", + "integrity": "sha512-Vt90nNFhtAChRe9+it1hcHFgFvETdSnOkL5Bma+p6E/yU2tAYrvvyk+gv+LJGM2ZUkyKuKXLRsZ2Lb0bO7+Vog==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-tag-manager": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.2.tgz", + "integrity": "sha512-MLCffCldysi/R0nzJQP7ZWd0xAoGNnSTiVOo6TTR6mKVGFhE+/XArGe67ZcaZv1uytgQXoXs92VJrgVDrz80rQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-sitemap": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.2.tgz", + "integrity": "sha512-PODkwg5XetLML3hU/3xpCKJUZ9cqExLaBnD/Fzzwj2VHogLeqnDisLIujae87zuze7T4mCm2A6KEqZkyiz07EQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "fs-extra": "^11.1.1", + "sitemap": "^7.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-svgr": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.2.tgz", + "integrity": "sha512-JgfT3jWM0TJ8Uw0cEcqxHpybngQY1vlBYpuuNO+gEh5iPh5Ar+vxq/u9CFrYsWeXy48BN7Db76Pzp2edNXUQ8A==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@svgr/core": "8.1.0", + "@svgr/webpack": "^8.1.0", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/preset-classic": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.10.2.tgz", + "integrity": "sha512-a4B3VczmDl99zK0EufDQYomdJ186WDingjmDXxhN2PNPS9Ty/Y2M5CLFX1KQMRKqRTLiRDKfutzG5IY1FC/ceg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/plugin-content-blog": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/plugin-content-pages": "3.10.2", + "@docusaurus/plugin-css-cascade-layers": "3.10.2", + "@docusaurus/plugin-debug": "3.10.2", + "@docusaurus/plugin-google-analytics": "3.10.2", + "@docusaurus/plugin-google-gtag": "3.10.2", + "@docusaurus/plugin-google-tag-manager": "3.10.2", + "@docusaurus/plugin-sitemap": "3.10.2", + "@docusaurus/plugin-svgr": "3.10.2", + "@docusaurus/theme-classic": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/theme-search-algolia": "3.10.2", + "@docusaurus/types": "3.10.2" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-classic": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.10.2.tgz", + "integrity": "sha512-JqTSLQmqmA9uKWZsD5iwBGJ4JyKB4/yTw6PsSXVPRJG/6GAm/u+add9Iip+hvwP12/AnPNztrdxsI14NJW4KeA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/plugin-content-blog": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/plugin-content-pages": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/theme-translations": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "copy-text-to-clipboard": "^3.2.0", + "infima": "0.2.0-alpha.45", + "lodash": "^4.17.21", + "nprogress": "^0.2.0", + "postcss": "^8.5.4", + "prism-react-renderer": "^2.3.0", + "prismjs": "^1.29.0", + "react-router-dom": "^5.3.4", + "rtlcss": "^4.1.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-common": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.10.2.tgz", + "integrity": "sha512-R9b/vMpK1yye6hNZTA6x/ivRv+at6GhxnXcxkpzCGzO1R1RwiquqiFg2wMFh6aqlJTpWRFKpFD2TzCDQcyOU0A==", + "license": "MIT", + "dependencies": { + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "clsx": "^2.0.0", + "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^2.3.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-search-algolia": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.2.tgz", + "integrity": "sha512-1msxllyhi/5m77JukXtp5UFnUAriwZIC1oJ7MTnpQpCwLTbclJi5BK5n28CTZuSXpQN2ewbbnqRgAhMM6c6ihg==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "^1.19.2", + "@docsearch/react": "^3.9.0 || ^4.3.2", + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/theme-translations": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "algoliasearch": "^5.37.0", + "algoliasearch-helper": "^3.26.0", + "clsx": "^2.0.0", + "eta": "^2.2.0", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-translations": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.10.2.tgz", + "integrity": "sha512-iv20wrxnyXkY89LM3TzRlzGlt5fIGO5UnaR6UL1ZVfB9RRFjxQFQ6awDrwAc6Km8Y5gD8pInuwYPF+6/TiCxXA==", + "license": "MIT", + "dependencies": { + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/tsconfig": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.10.2.tgz", + "integrity": "sha512-5GiB7h/nFsMFPO9mCqcRNE1yA5TSXXNCshNIgHPL6fCPOjcTDixs6qjQBu8ddkgPcicwCvOA7n3jeK2rGdJk6g==", + "license": "MIT" + }, + "node_modules/@docusaurus/types": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.10.2.tgz", + "integrity": "sha512-B6rvfwIFSapUqUJjMriZswX13K8l5Z7AcmVE6uTEJpYddQieSTR12DsGaFtcZAIDsQd4p+0WTl0Vc6jmZK0Trw==", + "license": "MIT", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/mdast": "^4.0.2", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.95.0", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/types/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@docusaurus/types/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docusaurus/utils": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.10.2.tgz", + "integrity": "sha512-xx0W3eav2uW1NRIpuHJWNwLTC15xPNjU4Uxi9NSnd3swYC96BE3vFiT93SD8s24kmAAWNwgZwfZ2fghGZ01Lcw==", + "license": "MIT", + "dependencies": { + "@11ty/gray-matter": "^1.0.0", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "escape-string-regexp": "^4.0.0", + "execa": "^5.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "github-slugger": "^1.5.0", + "globby": "^11.1.0", + "jiti": "^1.20.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.5", + "p-queue": "^6.6.2", + "prompts": "^2.4.2", + "resolve-pathname": "^3.0.0", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/utils-common": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.10.2.tgz", + "integrity": "sha512-x3Dz6jv6iQKBNjBmVTu8p57abMp/VNTUgKBMgRVXJc5444orBTsArv0+cdfrXTiz/VMmHfDRVkPbL7GH2B7T7w==", + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.10.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/utils-validation": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.10.2.tgz", + "integrity": "sha512-sn8unbDfUL585NtR3cwHefPicOyaHvPaX7VD0aOg/siIxUBoKyKKaGEqzJZDS64mM43TnxurkYDtmB1wsJlZsw==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "fs-extra": "^11.2.0", + "joi": "^17.9.2", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/utils-validation/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@docusaurus/utils-validation/node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@docusaurus/utils/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@docusaurus/utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@docusaurus/utils/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@docusaurus/utils/node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@docusaurus/utils/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fontsource/dm-sans": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/dm-sans/-/dm-sans-5.3.0.tgz", + "integrity": "sha512-lYJtMXXO28q1z+yz+z8XKd0s4hXaa9QdkETzkyD760sidCv5heI86weYA0sx0Nc4pAMAQTUuyf4gO44cYKKS9g==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/space-grotesk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/space-grotesk/-/space-grotesk-5.3.0.tgz", + "integrity": "sha512-ksnGizDPXIDuvqcTYTSrmZ+evx9sDlS8rp7+42BQ7wU+spt3twEoXfbJz672C+5CLg6VeUQwRy5RXshWb67LcQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.80.0.tgz", + "integrity": "sha512-qMKWshnyjbyhm+NbzqGE3w5y1mckFFEMj0aDPU2/JBNh1BzIjVy9esq9lBmRnNVmhMEkxwDzLZ8Gqh5G1P5Dag==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.80.0.tgz", + "integrity": "sha512-ZBEKl7J6dbkRCfmrFV48Re211A/FHqniTUhsWCsaRteNSOqMgkX3qF1UreVgwB9oSPm81JLAwhIi6Lb/NH/PoA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.80.0.tgz", + "integrity": "sha512-yiBlUfkMMRFFGy8CD/h2zd5rPnrM6bjlF+nx2LRAxNV6hOSiuObQIUEE7m7y2VTMisjxCRtzINH94bFGySWL7g==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "@jsonjoy.com/fs-print": "4.80.0", + "@jsonjoy.com/fs-snapshot": "4.80.0", + "glob-to-regex.js": "^1.3.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.80.0.tgz", + "integrity": "sha512-OIOIhqaWiwUySFMny4epIAoviXsyMaQQ63PTc3zH+5HEOvarl5hpUmZIhp2m5+OfyKAh5ylxXnQIgew1OgJDVw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.80.0.tgz", + "integrity": "sha512-ljeaR5xwtX1EyRmB0wvXANUMQZ3mjKcm7z8s2N8D8SLQ04CcAGtUhHFRZQXFb9mhgFyEVMydQGQtSOFS7pcrFQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.80.0.tgz", + "integrity": "sha512-xwHeVVi3f+Khg/Fmb8i5qoYVmQ08QznE5TWhQokUmIZjz4KBPzU+zt/+bahBI6G1ghtqajt27lWuAciIxApuzA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "glob-to-regex.js": "^1.3.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.80.0.tgz", + "integrity": "sha512-1ocZLJw/m/xlq0moaIHO7HV9nHRVBUvdc2zMzhsJGkFOrqfwWvexfCv5cYCPPLRhP+9p3yunoi4Ep5YhRORC9Q==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.80.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.80.0.tgz", + "integrity": "sha512-3AV23Q1H228KTtJKTUE0nNq8FCtxSvtENOLrEat80wkAJCKHuqQEv8yjhpS8Bei2OEFJAnluGXp9Ri2SWBlROw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "license": "MIT", + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz", + "integrity": "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.2.tgz", + "integrity": "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.2.tgz", + "integrity": "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.2.tgz", + "integrity": "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.2.tgz", + "integrity": "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.2.tgz", + "integrity": "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.2.tgz", + "integrity": "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.10.0.tgz", + "integrity": "sha512-CkX0H4NCIOMHOU3rh2xXZywinYZ/EnJlaOlJiGI0e5L4XjFqHf4iH30LMbXWChVppdA9ljbanjtzQFOhDNfTWg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.10.0", + "@peculiar/asn1-x509": "^2.10.0", + "@peculiar/asn1-x509-attr": "^2.10.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.10.0.tgz", + "integrity": "sha512-jTPTr/9rxKM+niQLMF3jiAMb0lWHUhUHIuSOhdGCIfpB4FAQYf9SoiXOgxP8bS/68IB+bj+1OHvVnDVCQfVUZw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.10.0", + "@peculiar/asn1-x509": "^2.10.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.10.0.tgz", + "integrity": "sha512-GFd3iOjFrWX+QWH2R2dO5QSJyyRGv9CIBKtRlGlPNCvb2RvmCbBDexe91MJgV76c69d998Xsa+CvuQ98seoiPg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.10.0", + "@peculiar/asn1-x509": "^2.10.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.10.0.tgz", + "integrity": "sha512-1y3QK9ZH1IPleAMmRoJlPS10eGkAWnULETW8zDFNUK1ffqpG7zmDY+kYBMYQgwlcQZ2iitLm2z2EBP0xzRXpaA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.10.0", + "@peculiar/asn1-pkcs8": "^2.10.0", + "@peculiar/asn1-rsa": "^2.10.0", + "@peculiar/asn1-schema": "^2.10.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.10.0.tgz", + "integrity": "sha512-Ri+BZT9bnwqlHWmhs7lvWjvJRxKev2hA2+4EbZajgeYWCbR8hyv0znF4DhsFouOhMj2nSDV/iGJ6DMQtwITnCw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.10.0", + "@peculiar/asn1-x509": "^2.10.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.10.0.tgz", + "integrity": "sha512-XIXsbDQFezYk6fudczkuvawkRD4GNpISGCqfYhdkJlvcGF/RFknU+0DDJagOaJqBf+PdPzk4J9oMqDGcvfR9HQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.10.0", + "@peculiar/asn1-pfx": "^2.10.0", + "@peculiar/asn1-pkcs8": "^2.10.0", + "@peculiar/asn1-schema": "^2.10.0", + "@peculiar/asn1-x509": "^2.10.0", + "@peculiar/asn1-x509-attr": "^2.10.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.10.0.tgz", + "integrity": "sha512-4Jvmwlh3gZAhNZ4/u7JSyLuce9hofOFZ4o3PYKAccCImGnyaT5CMym/ATgvAvqpOYFNW531oPZvJlEBhJy9VfA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.10.0", + "@peculiar/asn1-x509": "^2.10.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.10.0.tgz", + "integrity": "sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw==", + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.10.0.tgz", + "integrity": "sha512-ucNVg8+ANveTpMN3fy9lA2alryONdXc2A4cEG2hMniWbvQt+YOZoe8BI80YYNO8FBcuDY1qbDQ4uQGQVraHxGA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.10.0", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.10.0.tgz", + "integrity": "sha512-/85GtKOKmgvuSJNlaFfwGWNdRSZZ+hpF02NyM01XiCdzpaZONiBltDyfluFPvFx966CR+ZHNSG1jniwpy07oGg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.10.0", + "@peculiar/asn1-x509": "^2.10.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.3.tgz", + "integrity": "sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==", + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT" + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@slorber/remark-comment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", + "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.1.0", + "micromark-util-symbol": "^1.0.1" + } + }, + "node_modules/@slorber/remark-comment/node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@slorber/remark-comment/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/@slorber/remark-comment/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/@slorber/remark-comment/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz", + "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz", + "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "license": "MIT" + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", + "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-config": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", + "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "^5.1.0" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "license": "MIT" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "license": "ISC" + }, + "node_modules/@weaveplatform/docs-kit": { + "resolved": "packages/docs-kit", + "link": true + }, + "node_modules/@weaveplatform/documentation-site": { + "resolved": "site", + "link": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/address/-/address-2.0.3.tgz", + "integrity": "sha512-XNAb/a6TCqou+TufU8/u11HCu9x1gYvOoxLwtlXgIqmkrYQADVv6ljyW2zwiPhHz9R1gItAWpuDrdJMmrOBFEA==", + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/algoliasearch": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.59.0.tgz", + "integrity": "sha512-wUXzaeI7B526W4y1gFg3lcxgDZ67XSgRJIiellYWOas/pLpO7rOtxm9Gr2E/C8aiSDXIgx1q5TdSsvK67Uakqw==", + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.25.0", + "@algolia/client-abtesting": "5.59.0", + "@algolia/client-analytics": "5.59.0", + "@algolia/client-common": "5.59.0", + "@algolia/client-insights": "5.59.0", + "@algolia/client-personalization": "5.59.0", + "@algolia/client-query-suggestions": "5.59.0", + "@algolia/client-search": "5.59.0", + "@algolia/ingestion": "1.59.0", + "@algolia/monitoring": "1.59.0", + "@algolia/recommend": "5.59.0", + "@algolia/requester-browser-xhr": "5.59.0", + "@algolia/requester-fetch": "5.59.0", + "@algolia/requester-node-http": "5.59.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/algoliasearch-helper": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.30.0.tgz", + "integrity": "sha512-leiyAC/Giqxk9OW2YCUZDtqKZInSwMgdhd4PNqeUC8eNBZgtIB9BlUR0RkAbHeS9r5GxI1GiJUu31SO8RjWfaA==", + "license": "MIT", + "dependencies": { + "@algolia/events": "^4.0.1" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 6" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/autoprefixer": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.1.tgz", + "integrity": "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.9", + "caniuse-lite": "^1.0.30001810", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "license": "MIT", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "license": "MIT", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz", + "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.8", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", + "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.16.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.4.tgz", + "integrity": "sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001815", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz", + "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.10.0.tgz", + "integrity": "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combine-promises": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", + "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "license": "ISC" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compressible/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz", + "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "destroy": "1.2.0", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "license": "BSD-2-Clause", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/copy-text-to-clipboard": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz", + "integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.50.0.tgz", + "integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==", + "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/cosmiconfig/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", + "integrity": "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==", + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", + "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.12.0.tgz", + "integrity": "sha512-A8/XPAtGymaiKrVU++Xxmu+277gNdjKH0876QFiEFufAyKPX7V/+jVynbfiN9b45Hz07iKO4b7oJuECFTl6YQQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-advanced": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "license": "MIT", + "dependencies": { + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.0", + "cssnano-preset-default": "^6.1.2", + "postcss-discard-unused": "^6.0.5", + "postcss-merge-idents": "^6.0.3", + "postcss-reduce-idents": "^6.0.3", + "postcss-zindex": "^6.0.2" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, + "node_modules/detect-port": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-2.1.0.tgz", + "integrity": "sha512-epZuWb/6Q62L+nDHJc/hQAqf8pylsqgk3BpZXVBx1CDnr3nkrVNn73Uu1rXcFzkNcc+hkP3whuOg7JZYaQB65Q==", + "license": "MIT", + "dependencies": { + "address": "^2.0.1" + }, + "bin": { + "detect": "dist/commonjs/bin/detect-port.js", + "detect-port": "dist/commonjs/bin/detect-port.js" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.452", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.452.tgz", + "integrity": "sha512-cVvk1YBSj6571VmV6hdJTqj94Y79xSu8cj6Y9t6ae4Y4431z8/7EJFvu3rVp90xhfuv9BpPKUmaLfTo4+FOGBg==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/emoticon": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.1.tgz", + "integrity": "sha512-B0np3dcdxqILX5e9nEi5/Fr4K7gL4oYFVPV1zRa2e9wRCbQoZZNWOZFYyoInvXUPJXBXjss+QXlWLJChDEHDkA==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-value-to-estree": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", + "integrity": "sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", + "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eval": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", + "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", + "dependencies": { + "@types/node": "*", + "require-like": ">= 0.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz", + "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.13", + "proxy-addr": "~2.0.7", + "qs": "~6.16.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/express/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/feed": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", + "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "license": "MIT", + "dependencies": { + "xml-js": "^1.6.11" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/file-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/file-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "license": "ISC" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.3.1.tgz", + "integrity": "sha512-zhWhMRsgnOym+1txhUmYJGD6eUsbUdrZv55gCYKuJX8t2p3+M8My23PD09bEIsreGUs5pnSa/idmcfp2/IlqGg==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-15.0.0.tgz", + "integrity": "sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.8", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.8.tgz", + "integrity": "sha512-MZmKQcTnhEh1SPSyMiEytIeDZDUoBZVorNHivQGXMASHf/BSGGOrKa2xQ5bGx3TCe1n109ecCt+cpww7wwWhKA==", + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x || 2.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "license": "BSD-2-Clause" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", + "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.12.tgz", + "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.4.tgz", + "integrity": "sha512-QRUkFFsRV/6fuESxb9Vkq+a0LkSrgKXuc2NEqfikiXxxN/G3tjWt5EVUlMaImRBZRZK/jRBEbYvpPYZL8t08Zw==", + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infima": { + "version": "0.2.0-alpha.45", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", + "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "license": "MIT", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", + "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-yarn-global": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/joi": { + "version": "17.13.8", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.8.tgz", + "integrity": "sha512-iPKOGmiRw1jxf/JOPwxmCcUQAOdF359mdzYiP2DJ+TMX0YK2zjK3D+zYOaGjpumWxOFF/l2xVWjRVK5bGSLdEw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "license": "MIT", + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/launch-editor": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.2.tgz", + "integrity": "sha512-NHJEb30MGBCkmZXLZwz25t37QR4aY03ZzPYUJXOJaOa92uWwExI9SBQUuIznBYc1xkD7cJNGX7oc3Nl3sq2kQQ==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/markdownlint": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.40.0.tgz", + "integrity": "sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.2", + "string-width": "8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.20.0.tgz", + "integrity": "sha512-esPk+8Qvx/f0bzI7YelUeZp+jCtFOk3KjZ7s9iBQZ6HlymSXoTtWGiIRZP05/9Oy2ehIoIjenVwndxGtxOIJYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "globby": "15.0.0", + "js-yaml": "4.1.1", + "jsonc-parser": "3.3.1", + "markdown-it": "14.1.0", + "markdownlint": "0.40.0", + "markdownlint-cli2-formatter-default": "0.0.6", + "micromatch": "4.0.8" + }, + "bin": { + "markdownlint-cli2": "markdownlint-cli2-bin.mjs" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2-formatter-default": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.6.tgz", + "integrity": "sha512-VVDGKsq9sgzu378swJ0fcHfSicUnMxnL8gnLm/Q4J/xsNJ4e5bA6lvAz7PCzIl0/No0lHyaWdqVD2jotxOSFMQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + }, + "peerDependencies": { + "markdownlint-cli2": ">=0.0.4" + } + }, + "node_modules/markdownlint-cli2/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/markdownlint-cli2/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.1.tgz", + "integrity": "sha512-Gqpa3MHorXYEZ0VfbroyvBlVKUfz371V6mIERqOYMcb0W1D3LwmFq1ZW4O2TDrbn+4ZmGLwMWeFRJgYZ+bc5fQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-character": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.3.tgz", + "integrity": "sha512-xPgpDNl0/OXHsI7TlaIs22lWnH3KlpvZdXG1ET/m/YT2Hhdkx8lJV4kLphE6l9simyM4KPDFBwas08BgEob6Jw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.1.0.tgz", + "integrity": "sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.1.tgz", + "integrity": "sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.0.tgz", + "integrity": "sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "license": "CC0-1.0" + }, + "node_modules/mdurl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", + "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.80.0.tgz", + "integrity": "sha512-N70W2isi44XdGn7uPo9NJ4zRukcdkwSFHlnYx42SWOH/+hzqNc8P7+tVoA2up4z7faHvNi1RSNCFjxoYQ71VPQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.80.0", + "@jsonjoy.com/fs-fsa": "4.80.0", + "@jsonjoy.com/fs-node": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-to-fsa": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "@jsonjoy.com/fs-print": "4.80.0", + "@jsonjoy.com/fs-snapshot": "4.80.0", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.3.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz", + "integrity": "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-events-to-acorn/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", + "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/miniflare": { + "version": "5.20261006.1-alpha", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20261006.1-alpha.tgz", + "integrity": "sha512-6ylv8tamkrspDI2R/cAYfv+YWmfGNdXe76Pi9TauEUWsFVXn3LNF9Yk8oduzf1a+TYsSRYTqlOE7+nGGRsMHdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "0.35.5", + "undici": "7.29.1", + "workerd": "1.20261006.1", + "ws": "8.21.0", + "youch": "4.1.0-beta.10" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.13.0.tgz", + "integrity": "sha512-OO1NF2zHj6sf4CNv2AsUWYcS8WEwkVKQDXxxKzlOxx1Xl+WlqYtVC+oNlGNwGkLAOnpj277BSHypWwRtd9w4JQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.3", + "terser": "^5.51.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@napi-rs/image": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "imagemin": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "sharp": { + "optional": true + }, + "svgo": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-emoji/node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/node-releases": { + "version": "2.0.58", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz", + "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/null-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", + "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/null-loader/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/null-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/null-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/null-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "license": "MIT", + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pagefind": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", + "dev": true, + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.2", + "@pagefind/darwin-x64": "1.5.2", + "@pagefind/freebsd-x64": "1.5.2", + "@pagefind/linux-arm64": "1.5.2", + "@pagefind/linux-x64": "1.5.2", + "@pagefind/windows-arm64": "1.5.2", + "@pagefind/windows-x64": "1.5.2" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", + "license": "ISC" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkijs": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz", + "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==", + "license": "BSD-3-Clause", + "workspaces": [ + "website" + ], + "dependencies": { + "@noble/hashes": "1.8.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", + "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-custom-media": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", + "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-properties": { + "version": "14.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", + "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", + "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-unused": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", + "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-lab-function": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", + "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-loader": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", + "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-merge-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", + "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nesting": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-resolve-nested": "^3.1.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", + "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-preset-env": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.6.1.tgz", + "integrity": "sha512-yrk74d9EvY+W7+lO9Aj1QmjWY9q5NsKjK2V9drkOPZB/X6KZ0B3igKsHUYakb7oYVhnioWypQX3xGuePf89f3g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-alpha-function": "^1.0.1", + "@csstools/postcss-cascade-layers": "^5.0.2", + "@csstools/postcss-color-function": "^4.0.12", + "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", + "@csstools/postcss-color-mix-function": "^3.0.12", + "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", + "@csstools/postcss-content-alt-text": "^2.0.8", + "@csstools/postcss-contrast-color-function": "^2.0.12", + "@csstools/postcss-exponential-functions": "^2.0.9", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.11", + "@csstools/postcss-gradients-interpolation-method": "^5.0.12", + "@csstools/postcss-hwb-function": "^4.0.12", + "@csstools/postcss-ic-unit": "^4.0.4", + "@csstools/postcss-initial": "^2.0.1", + "@csstools/postcss-is-pseudo-class": "^5.0.3", + "@csstools/postcss-light-dark-function": "^2.0.11", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.4", + "@csstools/postcss-media-minmax": "^2.0.9", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.1", + "@csstools/postcss-oklab-function": "^4.0.12", + "@csstools/postcss-position-area-property": "^1.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/postcss-property-rule-prelude-list": "^1.0.0", + "@csstools/postcss-random-function": "^2.0.1", + "@csstools/postcss-relative-color-syntax": "^3.0.12", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.4", + "@csstools/postcss-stepped-value-functions": "^4.0.9", + "@csstools/postcss-syntax-descriptor-syntax-production": "^1.0.1", + "@csstools/postcss-system-ui-font-family": "^1.0.0", + "@csstools/postcss-text-decoration-shorthand": "^4.0.3", + "@csstools/postcss-trigonometric-functions": "^4.0.9", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.23", + "browserslist": "^4.28.1", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.3", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.6.0", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.12", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.6", + "postcss-custom-properties": "^14.0.6", + "postcss-custom-selectors": "^8.0.5", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.4", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.12", + "postcss-logical": "^8.1.0", + "postcss-nesting": "^13.0.2", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-reduce-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", + "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sort-media-queries": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", + "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", + "license": "MIT", + "dependencies": { + "sort-css-media-queries": "2.2.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.23" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/postcss-zindex": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", + "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", + "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", + "license": "MIT", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "name": "@slorber/react-helmet-async", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz", + "integrity": "sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.2.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-json-view-lite": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", + "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-loadable": { + "name": "@docusaurus/react-loadable", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", + "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-loadable-ssr-addon-v5-slorber": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.3.tgz", + "integrity": "sha512-GXfh9VLwB5ERaCsU6RULh7tkemeX15aNh6wuMEBtfdyMa7fFG8TXrhXlx1SoEK2Ty/l6XIkzzYIQmyaWW3JgdQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.3" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "react-loadable": "*", + "webpack": ">=4.41.1 || 5.x" + } + }, + "node_modules/react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-config": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", + "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2" + }, + "peerDependencies": { + "react": ">=15", + "react-router": ">=5" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/react-router/node_modules/path-to-regexp": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "license": "MIT", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/regenerate": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.5.0.tgz", + "integrity": "sha512-1s+CxXjJ71i7LNVy8nYwg47XV4y6lAnOEhsMtSKq3WpmPfhl9LuKlTqKIn/3yUpE8lBz+W+TT88Bxyhnr0U4yw==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.3.0.tgz", + "integrity": "sha512-9ns8odR8e9q3otKeHj3DE80jZYKg6zVeZKE+zQmRKXFoVUcuiTGeO6iusDhPRoIQQPVPRvH+SfBhrTud4eczuw==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexpu-core": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.5.3.tgz", + "integrity": "sha512-E8VgUoSK+LMQrLboDxavm76MTBGrQhx9RiNuomRmYnZ10Uw12HrI8fkxOOncktb2hBpzIZ9SV/347euQHG5z+g==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.5.0", + "regenerate-unicode-properties": "^10.3.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^3.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.4.tgz", + "integrity": "sha512-EWV80YgFaVgFrjGCAsV7oybp4GOxbls5RzBg8OB0nKEVXsBJR1JlV8Di1JhCROg3MzTfe1RI8yitIm1eOs68iA==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remark-directive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive/node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-emoji": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", + "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.2", + "emoticon": "^4.0.1", + "mdast-util-find-and-replace": "^3.0.1", + "node-emoji": "^2.1.0", + "unified": "^11.0.4" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/remark-frontmatter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-frontmatter": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/renderkid/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/renderkid/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/renderkid/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", + "engines": { + "node": "*" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.13", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.13.tgz", + "integrity": "sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.17.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", + "license": "MIT" + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rtlcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT", + "peer": true + }, + "node_modules/schema-dts": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", + "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", + "license": "Apache-2.0" + }, + "node_modules/schema-utils": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.5.0.tgz", + "integrity": "sha512-zJlMCZ0cAR5p/Y4oVpRoqioDMJcGxaXRrQ/4rP4WyR84vc5z/DolXdbvXeDpTwbtocDFr2rhPqHPErDCUtz2kA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "license": "MIT", + "peer": true + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", + "license": "MIT", + "dependencies": { + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-handler": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz", + "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==", + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.5", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", + "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.3.tgz", + "integrity": "sha512-tAjEd+wt/YwnEbfNB2ht51ybBJxbEWwe5ki/Z//Wh0rpBFTCUSj46GnxUKEWzhfuJTsee8x3lybHxFgUMig2hw==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=5.6.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sort-css-media-queries": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", + "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", + "license": "MIT", + "engines": { + "node": ">= 6.3.0" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/srcset": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", + "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.1.0.tgz", + "integrity": "sha512-bwLf38YmY+TDYHJw1Ex0Co8c4yeXuJAo8YnXGZrscxrvYoVVIvLeniEkV1Ks/54VteMnL4FtyN1+P+TZJdUPmQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/svgo": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.5.tgz", + "integrity": "sha512-8SQMzdrvWaD8deUmrnYB+ASyxBVgWUOilg+A75nE/76WdLpj6LopCwiAVvkzkcqy/9b7t2Mg7faFLjg0ZRcZ3w==", + "license": "MIT", + "dependencies": { + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/thingies": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.1.tgz", + "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==", + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", + "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "license": "MIT", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-position/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-visit/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/boxen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", + "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/update-notifier/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/url-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/url-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/url-loader/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/vfile/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpack": { + "version": "5.111.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.111.1.tgz", + "integrity": "sha512-cNypaz0RP+S4cvQVi1M/3bRUkvNP0xZpL0TjFx+c6jg64VbxD+2weWDgY9UnjRI6dhZgtaj8DU76GGFcJ79xPQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.25.0", + "es-module-lexer": "^2.1.0", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.7.0", + "schema-utils": "^4.5.0", + "tapable": "^2.3.0", + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.1" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.6.tgz", + "integrity": "sha512-yBWCMvIfUmuhAE8vdqUKzH0vg9kuWN0KeG4vBnqRplUFHRU7lMQjkiJWxVQzvo2BTewqhPhDlMB41rAt2jVA9A==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-middleware/node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz", + "integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==", + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.22.1", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.14.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^5.5.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.6.0.tgz", + "integrity": "sha512-EIMmPVvNI0CYXyRPp34F2Qk7W37/BZVZIofS6LAUnrNjCVahS+hO4mxwFJDjQjODTGZDtVzLYoN2+1dYkhk9qA==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpackbar": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-7.0.0.tgz", + "integrity": "sha512-aS9soqSO2iCHgqHoCrj4LbfGQUboDCYJPSFOAchEK+9psIjNrfSWW4Y0YEz67MKURNvMmfo0ycOg9d/+OOf9/Q==", + "license": "MIT", + "dependencies": { + "ansis": "^3.2.0", + "consola": "^3.2.3", + "pretty-time": "^1.1.0", + "std-env": "^3.7.0" + }, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@rspack/core": "*", + "webpack": "3 || 4 || 5" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/websocket-driver": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" + }, + "node_modules/workerd": { + "version": "1.20261006.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20261006.1.tgz", + "integrity": "sha512-8MadBuffZ5NdpLwZjUmsqT5QlaKXq4gjgT6wzcKeynZTICNXyePhc+5QVoKiK7+MMhFTLNrRO2KXLlpk8Q6BkA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20261006.1", + "@cloudflare/workerd-darwin-arm64": "1.20261006.1", + "@cloudflare/workerd-linux-64": "1.20261006.1", + "@cloudflare/workerd-linux-arm64": "1.20261006.1", + "@cloudflare/workerd-windows-64": "1.20261006.1" + } + }, + "node_modules/wrangler": { + "version": "4.149.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.149.0.tgz", + "integrity": "sha512-OzK7xmB5r5iLKb3cIT3783g13fe6T7xyeKu9LEdUyl+DKAM2KP/jkjnE0OUhyw+mB/XJM9ycjgF3MWm8PR9amg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.5.0", + "@cloudflare/unenv-preset": "2.16.2", + "blake3-wasm": "2.1.5", + "esbuild": "0.28.2", + "miniflare": "5.20261006.1-alpha", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20261006.1" + }, + "bin": { + "cf-wrangler": "bin/cf-wrangler.js", + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^5.20261006.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/wrangler/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/docs-kit": { + "name": "@weaveplatform/docs-kit", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@fontsource/dm-sans": "5.3.0", + "@fontsource/space-grotesk": "5.3.0" + }, + "peerDependencies": { + "@docusaurus/core": "3.10.2", + "react": "^19.0.0" + } + }, + "site": { + "name": "@weaveplatform/documentation-site", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/preset-classic": "3.10.2", + "@docusaurus/tsconfig": "3.10.2", + "@fontsource/dm-sans": "5.3.0", + "@fontsource/space-grotesk": "5.3.0", + "@mdx-js/react": "3.1.1", + "@weaveplatform/docs-kit": "0.1.0", + "clsx": "2.1.1", + "react": "19.2.4", + "react-dom": "19.2.4" + } + }, + "site/node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "site/node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "site/node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..16eb575 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "weaveplatform-docs-workspace", + "private": true, + "workspaces": ["site", "packages/docs-kit"], + "engines": {"node": ">=22 <23"}, + "scripts": { + "start": "npm run diagrams && npm run start --workspace site -- --no-open", + "validate": "node scripts/validate.mjs", + "diagrams": "node scripts/diagrams.mjs", + "diagrams:visual": "node scripts/diagrams.mjs --visual", + "build": "npm run validate && npm run contracts && npm run diagrams && npm run build --workspace site && pagefind --site site/build --glob 'docs/**/*.html'", + "contracts": "node scripts/contracts.mjs", + "serve": "npm run serve --workspace site -- --no-open", + "check:browser": "node scripts/check-browser.mjs", + "typecheck": "tsc --noEmit --project site/tsconfig.json", + "check:links": "node scripts/check-links.mjs", + "sources:check": "node scripts/source-refresh.mjs", + "companion:prepare": "node scripts/prepare-companion.mjs", + "test": "node --test scripts/tests/*.test.mjs" + }, + "devDependencies": { + "@playwright/test": "1.58.2", + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "gray-matter": "4.0.3", + "markdownlint-cli2": "0.20.0", + "pagefind": "1.5.2", + "typescript": "5.9.3", + "wrangler": "4.149.0", + "yaml": "2.8.2" + } +} diff --git a/packages/docs-kit/LICENSE b/packages/docs-kit/LICENSE new file mode 100644 index 0000000..4deabbb --- /dev/null +++ b/packages/docs-kit/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Deployment Theory + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/docs-kit/package.json b/packages/docs-kit/package.json new file mode 100644 index 0000000..3c96152 --- /dev/null +++ b/packages/docs-kit/package.json @@ -0,0 +1,10 @@ +{ + "name": "@weaveplatform/docs-kit", + "version": "0.1.0", + "type": "module", + "license": "MIT", + "files": ["src", "schemas"], + "exports": {"./components/Diagram": "./src/Diagram.tsx", "./components/Search": "./src/Search.tsx", "./styles.css": "./src/styles.css", "./schema/page": "./schemas/page.schema.json"}, + "dependencies": {"@fontsource/dm-sans": "5.3.0", "@fontsource/space-grotesk": "5.3.0"}, + "peerDependencies": {"@docusaurus/core": "3.10.2", "react": "^19.0.0"} +} diff --git a/packages/docs-kit/schemas/page.schema.json b/packages/docs-kit/schemas/page.schema.json new file mode 100644 index 0000000..9fd4a95 --- /dev/null +++ b/packages/docs-kit/schemas/page.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["title", "description", "product", "audience", "owner", "status", "verified_at", "evidence"], + "properties": { + "title": {"type": "string", "minLength": 1}, + "description": {"type": "string", "minLength": 10}, + "product": {"enum": ["platform", "guestweave", "hostweave", "imageweave", "agent-core", "agent-modules", "documentation"]}, + "audience": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"enum": ["consumer", "developer", "operator", "contributor"]}}, + "owner": {"type": "string", "minLength": 1}, + "status": {"enum": ["code-inspected", "runtime-verified", "draft"]}, + "verified_at": {"type": "string", "format": "date"}, + "evidence": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "pattern": "^[a-z][a-z0-9-]*:.+"}}, + "slug": {"type": "string"}, + "sidebar_position": {"type": "integer"} + }, + "additionalProperties": false +} diff --git a/packages/docs-kit/src/Diagram.tsx b/packages/docs-kit/src/Diagram.tsx new file mode 100644 index 0000000..59c51bf --- /dev/null +++ b/packages/docs-kit/src/Diagram.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +export default function Diagram({id, title, children}: {id: string; title: string; children?: React.ReactNode}) { + const href = useBaseUrl(`/diagrams/${id}.html`); + return
+ + + + + +`; +} + +function compactMessage(value) { + let text = String(value || 'Preview build failed without a diagnostic.').trim(); + const lines = text.split(/\r?\n/); + const errorLine = lines.findIndex((line) => /^Error:\s/.test(line)); + if (errorLine > 0) text = lines.slice(errorLine).join('\n'); + const relevant = text.split(/\r?\n/); + const stackLine = relevant.findIndex((line, index) => index > 0 && /^\s*at\s/.test(line)); + if (stackLine > 0) text = relevant.slice(0, stackLine).join('\n'); + return text.length > 6000 ? `${text.slice(0, 6000)}\n… diagnostic truncated` : text; +} + +function redactDiagnostic(value, paths) { + let text = compactMessage(value); + for (const [absolutePath, replacement] of paths) { + if (!absolutePath) continue; + text = text.split(absolutePath).join(replacement); + } + return text; +} + +function safeJson(value) { + return JSON.stringify(value).replace(/ { resolveClosed = resolve; }); + + function publicState() { + return JSON.parse(JSON.stringify(state)); + } + + function sendState(res) { + res.write(`event: state\ndata: ${safeJson(publicState())}\n\n`); + } + + function broadcast() { + for (const res of clients) sendState(res); + } + + const page = Buffer.from(previewPage()); + const server = http.createServer((req, res) => { + const expectedHost = `${loopbackHost}:${port}`; + if (req.headers.host !== expectedHost) { + res.writeHead(403, responseHeaders('text/plain; charset=utf-8')); + res.end('Forbidden host'); + return; + } + if (req.method !== 'GET' && req.method !== 'HEAD') { + res.writeHead(405, { ...responseHeaders('text/plain; charset=utf-8'), Allow: 'GET, HEAD' }); + res.end('Method not allowed'); + return; + } + + let url; + try { + url = new URL(req.url, `http://${expectedHost}`); + } catch { + res.writeHead(400, responseHeaders('text/plain; charset=utf-8')); + res.end('Bad request'); + return; + } + + if (url.pathname === '/') { + res.writeHead(200, { + ...responseHeaders('text/html; charset=utf-8'), + 'Content-Security-Policy': "default-src 'none'; frame-src 'self'; connect-src 'self'; script-src 'unsafe-inline'; style-src 'unsafe-inline'", + 'Content-Length': page.byteLength, + }); + if (req.method === 'HEAD') res.end(); + else res.end(page); + return; + } + if (url.pathname === '/state') { + const body = Buffer.from(`${safeJson(publicState())}\n`); + res.writeHead(200, { ...responseHeaders('application/json; charset=utf-8'), 'Content-Length': body.byteLength }); + if (req.method === 'HEAD') res.end(); + else res.end(body); + return; + } + if (url.pathname === '/artifact.html') { + if (!artifactBuffer) { + res.writeHead(404, responseHeaders('text/plain; charset=utf-8')); + res.end('No verified artifact yet'); + return; + } + res.writeHead(200, { ...responseHeaders('text/html; charset=utf-8'), 'Content-Length': artifactBuffer.byteLength }); + if (req.method === 'HEAD') res.end(); + else res.end(artifactBuffer); + return; + } + if (url.pathname === '/events' && req.method === 'GET') { + res.writeHead(200, { + ...responseHeaders('text/event-stream; charset=utf-8'), + Connection: 'keep-alive', + }); + res.write('retry: 1000\n\n'); + clients.add(res); + sendState(res); + req.on('close', () => clients.delete(res)); + return; + } + + res.writeHead(404, responseHeaders('text/plain; charset=utf-8')); + res.end('Not found'); + }); + + try { + await new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(0, loopbackHost, () => { + server.off('error', reject); + port = server.address().port; + resolve(); + }); + }); + } catch (error) { + try { server.close(); } catch {} + fs.rmSync(stagingDirectory, { recursive: true, force: true }); + throw error; + } + + const url = `http://${loopbackHost}:${port}/`; + + function finishStop() { + if (stopped || child || !serverClosed) return; + stopped = true; + clearTimeout(debounceTimer); + clearInterval(pollTimer); + clearTimeout(stopGraceTimer); + clearTimeout(stopKillTimer); + try { + fs.rmSync(stagingDirectory, { recursive: true, force: true }); + } finally { + resolveClosed(); + } + } + + function signalActiveChild(signal) { + if (!child || child.exitCode !== null || child.signalCode !== null) return; + try { + if (process.platform !== 'win32' && child.pid) process.kill(-child.pid, signal); + else child.kill(signal); + } catch (error) { + if (error.code === 'ESRCH') return; + try { child.kill(signal); } catch {} + } + } + + function closeServer() { + if (serverClosing) return; + serverClosing = true; + for (const res of clients) res.end(); + clients.clear(); + server.close(() => { + serverClosed = true; + finishStop(); + }); + server.closeIdleConnections?.(); + } + + function startBoundedChildDrain() { + if (!child || stopGraceTimer || stopKillTimer) return; + stopGraceTimer = setTimeout(() => { + stopGraceTimer = undefined; + if (!child) return finishStop(); + signalActiveChild('SIGTERM'); + stopKillTimer = setTimeout(() => { + stopKillTimer = undefined; + signalActiveChild('SIGKILL'); + }, stopKillMs); + }, stopGraceMs); + } + + async function stop({ force = false } = {}) { + if (!stopping) { + stopping = true; + clearTimeout(debounceTimer); + clearInterval(pollTimer); + watcher?.close(); + closeServer(); + } + if (child && force) { + clearTimeout(stopGraceTimer); + clearTimeout(stopKillTimer); + stopGraceTimer = undefined; + stopKillTimer = undefined; + signalActiveChild('SIGKILL'); + } else if (child) { + startBoundedChildDrain(); + } else { + finishStop(); + } + return closed; + } + + function publishFailure(receipt, stdout, stderr, candidatePath, snapshotPath) { + const repairDetails = receipt?.diagnostics + ?.slice(0, 12) + .map((entry) => { + const fix = entry.supportedFixes?.length ? `\nFix: ${entry.supportedFixes.join('; ')}` : ''; + return `[${entry.code}] ${entry.message}${fix}`; + }) || []; + const checkerDetails = receipt?.checker?.checks + ?.filter((check) => !check.ok) + .flatMap((check) => check.details || []) + .filter(Boolean) + .slice(0, 12) || []; + const diagnostic = [ + receipt?.error, + ...(repairDetails.length ? repairDetails : checkerDetails), + ].filter(Boolean).join('\n') || stderr || stdout; + state.status = 'needs-fix'; + state.failure = { + stage: receipt?.stage || 'render', + message: redactDiagnostic( + diagnostic, + [ + [inputPath, ''], + [outputPath, ''], + [snapshotPath, ''], + [candidatePath, ''], + [stagingDirectory, ''], + [path.resolve(here, '..'), ''], + [path.resolve(options.cwd || process.cwd()), ''], + ...(options.repoRoot ? [[path.resolve(options.repoRoot), '']] : []), + ], + ), + }; + broadcast(); + } + + function commitCandidate(candidatePath, receipt, generationHash) { + let candidate; + try { + candidate = fs.readFileSync(candidatePath); + const digest = sha256(candidate); + if (digest !== receipt?.artifact?.sha256) { + throw new Error('Verified candidate bytes do not match the delivery receipt.'); + } + resolveOutputPath(outputRequest); + const sameArtifact = state.lastVerified?.sha256 === digest; + let outputMatches = false; + if (sameArtifact) { + try { outputMatches = sha256(fs.readFileSync(outputPath)) === digest; } catch {} + } + const currentSource = sourceDigest(inputPath); + if (currentSource.hash !== generationHash) { + return { committed: false, supersededBy: currentSource }; + } + if (!sameArtifact || !outputMatches) fs.renameSync(candidatePath, outputPath); + artifactBuffer = candidate; + lastGoodSourceHash = generationHash; + state.status = 'verified'; + if (!sameArtifact) { + state.revision += 1; + state.lastVerified = { + sha256: digest, + bytes: candidate.byteLength, + checksPassed: receipt.validation.checksPassed, + checkCount: receipt.validation.checkCount, + compositionProfile: receipt.validation.compositionProfile, + compositionStatus: receipt.validation.compositionStatus, + }; + } + state.failure = null; + broadcast(); + return { committed: true, supersededBy: null }; + } catch (error) { + publishFailure({ stage: 'commit', error: `Could not publish the verified preview: ${error.message}` }, '', '', candidatePath); + return { committed: false, supersededBy: null }; + } + } + + function beginBuild(digest, epoch) { + if (stopping || child) return; + activeHash = digest.hash; + activeEpoch = epoch; + state.generation += 1; + state.status = 'checking'; + state.failure = null; + broadcast(); + + const candidatePath = path.join(stagingDirectory, `generation-${state.generation}.html`); + const snapshotPath = path.join(stagingDirectory, `generation-${state.generation}.json`); + if (digest.bytes !== null) { + try { + fs.writeFileSync(snapshotPath, digest.bytes, { flag: 'wx', mode: 0o600 }); + } catch (error) { + publishFailure( + { stage: 'prepare', error: `Could not snapshot the observed input: ${error.message}` }, + '', + '', + candidatePath, + snapshotPath, + ); + return; + } + } + const args = [options.deliveryCli || cliPath, 'deliver', type, snapshotPath, candidatePath, '--json']; + if (options.quality) args.push('--quality', options.quality); + if (options.repoRoot) args.push('--repo-root', path.resolve(options.repoRoot)); + let stdout = ''; + let stderr = ''; + child = spawn(process.execPath, args, { + cwd: options.cwd || process.cwd(), + env: process.env, + stdio: ['ignore', 'pipe', 'pipe'], + detached: process.platform !== 'win32', + }); + child.stdout.setEncoding('utf8'); + child.stderr.setEncoding('utf8'); + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + child.on('error', (error) => { stderr += error.message; }); + child.on('close', (code) => { + const receipt = parseReceipt(stdout); + const generationEpoch = activeEpoch; + const generationHash = activeHash; + const stale = generationEpoch !== sourceEpoch; + let supersededBy = null; + child = null; + clearTimeout(stopGraceTimer); + clearTimeout(stopKillTimer); + stopGraceTimer = undefined; + stopKillTimer = undefined; + if (!stopping && !stale && code === 0 && receipt?.ok) { + ({ supersededBy } = commitCandidate(candidatePath, receipt, generationHash)); + } else if (!stopping && !stale) { + publishFailure(receipt, stdout, stderr, candidatePath, snapshotPath); + } + try { fs.rmSync(candidatePath, { force: true }); } catch {} + try { fs.rmSync(snapshotPath, { force: true }); } catch {} + + if (stopping) { + finishStop(); + } else if (pendingBuild || stale || supersededBy) { + pendingBuild = false; + if (supersededBy && sourceEpoch === generationEpoch) sourceEpoch += 1; + const digest = supersededBy || sourceDigest(inputPath); + queueStableBuild(digest.hash, true); + } + }); + } + + function queueStableBuild(hash, immediate = false) { + queuedHash = hash; + clearTimeout(debounceTimer); + const launch = () => { + if (stopping) return; + const digest = sourceDigest(inputPath); + if (digest.hash !== queuedHash) { + queueStableBuild(digest.hash); + return; + } + if (digest.hash === lastGoodSourceHash) { + if (state.status !== 'verified' && state.lastVerified) { + state.status = 'verified'; + state.failure = null; + broadcast(); + } + return; + } + if (child) { + pendingBuild = true; + return; + } + beginBuild(digest, sourceEpoch); + }; + debounceTimer = setTimeout(launch, immediate ? 0 : debounceMs); + } + + function observeSource({ immediate = false } = {}) { + const digest = sourceDigest(inputPath); + if (!immediate && digest.hash === queuedHash) return; + sourceEpoch += 1; + queueStableBuild(digest.hash, immediate); + } + + if (options.watch !== false) { + try { + watcher = fs.watch(path.dirname(inputPath), (event, filename) => { + if (!filename || filename.toString() === path.basename(inputPath)) observeSource(); + }); + } catch (error) { + await stop(); + throw new Error(`Could not watch the input directory: ${error.message}`); + } + } + pollTimer = setInterval(() => observeSource(), pollMs); + + let opener = null; + if (shouldOpen) { + try { + opener = openLoopbackUrl(url); + } catch { + opener = { requested: true, status: 'unsupported', target: url, method: null }; + } + } + + observeSource({ immediate: true }); + + return { + url, + input: inputPath, + output: outputPath, + opener, + state: publicState, + stop, + closed, + }; +} + +export async function runPreview(options) { + const preview = await startPreview(options); + console.log(`preview ${preview.url}`); + console.log(`watching ${preview.input}`); + console.log(`output ${preview.output}`); + if (preview.opener && preview.opener.status !== 'opened') { + console.error(`Could not open the preview (${preview.opener.status}). Open it manually: ${preview.url}`); + } + + let signalCount = 0; + const stop = () => { + signalCount += 1; + if (signalCount === 1) { + console.log('\nstopping preview…'); + preview.stop(); + } else { + console.log('\nforcing preview shutdown…'); + preview.stop({ force: true }); + } + }; + process.on('SIGINT', stop); + process.on('SIGTERM', stop); + await preview.closed; + process.off('SIGINT', stop); + process.off('SIGTERM', stop); +} diff --git a/tools/archify/bin/visual-check.mjs b/tools/archify/bin/visual-check.mjs new file mode 100644 index 0000000..8adb154 --- /dev/null +++ b/tools/archify/bin/visual-check.mjs @@ -0,0 +1,829 @@ +import { spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { + DESKTOP_READABILITY_VIEWPORT, + MIN_PROJECTED_NODE_TEXT_PX, +} from '../renderers/shared/desktop-readability.mjs'; + +export const VISUAL_CHECK_VIEWPORTS = Object.freeze([ + DESKTOP_READABILITY_VIEWPORT, + Object.freeze({ width: 1600, height: 1000 }), + Object.freeze({ width: 1920, height: 1080 }), + Object.freeze({ width: 2048, height: 1320 }), +]); + +const CAPTURE_VIEWPORTS = Object.freeze([ + VISUAL_CHECK_VIEWPORTS[0], + VISUAL_CHECK_VIEWPORTS[VISUAL_CHECK_VIEWPORTS.length - 1], +]); +const THEMES = Object.freeze(['light', 'dark']); +const EXIT = Object.freeze({ pass: 0, fail: 1, skipped: 2 }); +export const CHROME_NO_SANDBOX_ENV = 'ARCHIFY_CHROME_NO_SANDBOX'; + +function sha256(buffer) { + return createHash('sha256').update(buffer).digest('hex'); +} + +function htmlEscape(value) { + return String(value).replace(/[&<>"']/g, (char) => ({ + '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', + })[char]); +} + +function safeUnlink(file) { + try { + fs.rmSync(file, { force: true }); + } catch { + // A stale optional sidecar must never make the delivered HTML mutable. + } +} + +function writeAtomic(file, contents) { + const temporary = `${file}.tmp-${process.pid}`; + try { + fs.writeFileSync(temporary, contents, { flag: 'w' }); + fs.renameSync(temporary, file); + } finally { + safeUnlink(temporary); + } +} + +function screenshotKey(width, height, theme) { + return `${width}x${height}:${theme}`; +} + +export function sidecarPaths(artifactPath) { + const artifact = path.resolve(artifactPath); + const stem = artifact.replace(/\.html?$/i, ''); + const base = `${stem}.visual-check`; + const screenshots = CAPTURE_VIEWPORTS.flatMap(({ width, height }) => THEMES.map((theme) => ({ + width, + height, + theme, + path: `${base}.${width}x${height}.${theme}.png`, + }))); + return { + base, + receipt: `${base}.json`, + contactSheet: `${base}.html`, + screenshots, + }; +} + +function cleanupCaptureSidecars(paths) { + safeUnlink(paths.contactSheet); + for (const screenshot of paths.screenshots) safeUnlink(screenshot.path); +} + +function executable(file, platform = process.platform) { + if (!file) return null; + try { + fs.accessSync(file, platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK); + return path.resolve(file); + } catch { + return null; + } +} + +function findOnPath(command, env, platform) { + const directories = String(env.PATH || '').split(path.delimiter).filter(Boolean); + const extensions = platform === 'win32' + ? String(env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean) + : ['']; + for (const directory of directories) { + for (const extension of extensions) { + const candidate = path.join(directory, `${command}${extension}`); + const resolved = executable(candidate, platform); + if (resolved) return resolved; + } + } + return null; +} + +export function findChrome({ env = process.env, platform = process.platform } = {}) { + if (Object.prototype.hasOwnProperty.call(env, 'ARCHIFY_CHROME')) { + return executable(env.ARCHIFY_CHROME, platform); + } + + const fixed = []; + const commands = []; + if (platform === 'darwin') { + fixed.push( + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + '/Applications/Chromium.app/Contents/MacOS/Chromium', + ); + } else if (platform === 'win32') { + for (const root of [env.PROGRAMFILES, env['PROGRAMFILES(X86)'], env.LOCALAPPDATA].filter(Boolean)) { + fixed.push( + path.join(root, 'Google', 'Chrome', 'Application', 'chrome.exe'), + path.join(root, 'Chromium', 'Application', 'chrome.exe'), + ); + } + } else { + commands.push('google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser'); + } + + for (const candidate of fixed) { + const resolved = executable(candidate, platform); + if (resolved) return resolved; + } + for (const command of commands) { + const resolved = findOnPath(command, env, platform); + if (resolved) return resolved; + } + return null; +} + +class PipeCdp { + constructor(child, { failureDetails = () => '' } = {}) { + this.child = child; + this.failureDetails = failureDetails; + this.nextId = 1; + this.buffer = ''; + this.pending = new Map(); + this.waiters = []; + this.writePipe = child.stdio[3]; + this.readPipe = child.stdio[4]; + this.readPipe.setEncoding('utf8'); + this.readPipe.on('data', (chunk) => this.consume(chunk)); + this.writePipe.on('error', (error) => this.failAll(this.failure('write pipe', error))); + this.readPipe.on('error', (error) => this.failAll(this.failure('read pipe', error))); + child.once('error', (error) => this.failAll(this.failure('process launch', error))); + child.once('close', (code, signal) => { + const ending = signal ? `signal ${signal}` : `exit code ${code}`; + this.failAll(this.failure('process exit', new Error(`Chrome closed with ${ending}`))); + }); + } + + failure(stage, error) { + const code = error?.code ? ` [${error.code}]` : ''; + const details = this.failureDetails(); + return new Error([ + `Chrome DevTools ${stage} failed: ${error?.message || String(error)}${code}`, + details, + ].filter(Boolean).join('\n')); + } + + consume(chunk) { + this.buffer += chunk; + let boundary; + while ((boundary = this.buffer.indexOf('\0')) >= 0) { + const raw = this.buffer.slice(0, boundary); + this.buffer = this.buffer.slice(boundary + 1); + if (!raw) continue; + let message; + try { + message = JSON.parse(raw); + } catch (error) { + this.failAll(new Error(`Chrome DevTools returned invalid JSON: ${error.message}`)); + continue; + } + if (message.id) { + const pending = this.pending.get(message.id); + if (!pending) continue; + clearTimeout(pending.timer); + this.pending.delete(message.id); + if (message.error) pending.reject(new Error(`${pending.method}: ${message.error.message}`)); + else pending.resolve(message.result || {}); + continue; + } + for (const waiter of [...this.waiters]) { + if (waiter.method !== message.method) continue; + if (waiter.sessionId && waiter.sessionId !== message.sessionId) continue; + clearTimeout(waiter.timer); + this.waiters.splice(this.waiters.indexOf(waiter), 1); + waiter.resolve(message.params || {}); + } + } + } + + send(method, params = {}, sessionId = undefined, timeoutMs = 15000) { + const id = this.nextId++; + const message = { id, method, params }; + if (sessionId) message.sessionId = sessionId; + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + this.pending.delete(id); + reject(new Error(`${method}: timed out after ${timeoutMs}ms`)); + }, timeoutMs); + this.pending.set(id, { method, resolve, reject, timer }); + try { + this.writePipe.write(`${JSON.stringify(message)}\0`, (error) => { + if (error) this.failAll(this.failure('write pipe', error)); + }); + } catch (error) { + this.failAll(this.failure('write pipe', error)); + } + }); + } + + waitFor(method, sessionId, timeoutMs = 15000) { + return new Promise((resolve, reject) => { + const waiter = { method, sessionId, resolve, reject, timer: null }; + waiter.timer = setTimeout(() => { + this.waiters.splice(this.waiters.indexOf(waiter), 1); + reject(new Error(`${method}: event timed out after ${timeoutMs}ms`)); + }, timeoutMs); + this.waiters.push(waiter); + }); + } + + failAll(error) { + for (const pending of this.pending.values()) { + clearTimeout(pending.timer); + pending.reject(error); + } + for (const waiter of this.waiters) { + clearTimeout(waiter.timer); + waiter.reject(error); + } + this.pending.clear(); + this.waiters = []; + } +} + +export function chromeVisualBrowserArgs(profileRoot, { + env = process.env, + getuid = typeof process.getuid === 'function' ? () => process.getuid() : null, +} = {}) { + const args = [ + '--headless=new', + '--remote-debugging-pipe', + '--disable-gpu', + '--hide-scrollbars', + '--disable-background-networking', + '--disable-component-update', + '--disable-default-apps', + '--disable-sync', + '--metrics-recording-only', + '--no-first-run', + '--no-default-browser-check', + '--disable-background-timer-throttling', + '--disable-backgrounding-occluded-windows', + '--disable-renderer-backgrounding', + '--force-device-scale-factor=1', + `--user-data-dir=${profileRoot}`, + 'about:blank', + ]; + const rootUser = typeof getuid === 'function' && getuid() === 0; + const sandboxOptOut = env?.[CHROME_NO_SANDBOX_ENV] === '1'; + if (rootUser || sandboxOptOut) args.unshift('--no-sandbox'); + return args; +} + +async function evaluate(cdp, sessionId, expression, awaitPromise = false) { + const response = await cdp.send('Runtime.evaluate', { + expression, + awaitPromise, + returnByValue: true, + }, sessionId); + if (response.exceptionDetails) { + throw new Error(response.exceptionDetails.exception?.description + || response.exceptionDetails.text + || 'Runtime.evaluate failed'); + } + return response.result?.value; +} + +export class ChromeVisualBrowser { + constructor(chromePath, { + env = process.env, + getuid = typeof process.getuid === 'function' ? () => process.getuid() : null, + spawnImpl = spawn, + } = {}) { + this.profileRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-visual-check-profile-')); + this.stderr = ''; + const args = chromeVisualBrowserArgs(this.profileRoot, { env, getuid }); + this.child = spawnImpl(chromePath, args, { stdio: ['ignore', 'ignore', 'pipe', 'pipe', 'pipe'] }); + this.child.stderr.setEncoding('utf8'); + this.child.stderr.on('data', (chunk) => { + this.stderr = `${this.stderr}${chunk}`.slice(-8000); + }); + this.child.stderr.on('error', (error) => { + this.stderr = `${this.stderr}\nChrome stderr stream failed: ${error.message}`.trim().slice(-8000); + }); + this.cdp = new PipeCdp(this.child, { + failureDetails: () => { + const exit = this.child.signalCode + ? `signal ${this.child.signalCode}` + : this.child.exitCode == null ? 'still running' : `exit code ${this.child.exitCode}`; + const stderr = this.stderr.trim(); + return [ + `Chrome process: ${exit}.`, + stderr ? `Chrome stderr:\n${stderr}` : '', + ].filter(Boolean).join('\n'); + }, + }); + this.sessionPromise = this.attach(); + } + + async attach() { + const targets = await this.cdp.send('Target.getTargets'); + let target = targets.targetInfos?.find((item) => item.type === 'page'); + if (!target) { + const created = await this.cdp.send('Target.createTarget', { url: 'about:blank' }); + target = { targetId: created.targetId }; + } + const attached = await this.cdp.send('Target.attachToTarget', { + targetId: target.targetId, + flatten: true, + }); + await this.cdp.send('Page.enable', {}, attached.sessionId); + await this.cdp.send('Runtime.enable', {}, attached.sessionId); + return attached.sessionId; + } + + async inspect({ artifactPath, width, height, theme, screenshotPath }) { + const sessionId = await this.sessionPromise; + await this.cdp.send('Emulation.setDeviceMetricsOverride', { + width, + height, + deviceScaleFactor: 1, + mobile: false, + }, sessionId); + + const url = new URL(pathToFileURL(artifactPath).href); + url.searchParams.set('theme', theme); + const loaded = this.cdp.waitFor('Page.loadEventFired', sessionId); + const navigation = await this.cdp.send('Page.navigate', { url: url.href }, sessionId); + if (navigation.errorText) throw new Error(`Chrome navigation failed: ${navigation.errorText}`); + await loaded; + await evaluate(this.cdp, sessionId, `(function () { + document.documentElement.setAttribute('data-motion', 'still'); + var panel = document.querySelector('.diagram-container'); + if (panel) panel.setAttribute('data-detail-level', 'read'); + var fontsReady = document.fonts && document.fonts.ready + ? document.fonts.ready.catch(function () {}) + : Promise.resolve(); + return fontsReady.then(function () { + if (window.Archify && Archify.readerLayout && typeof Archify.readerLayout.whenStable === 'function') { + return Archify.readerLayout.whenStable(); + } + }).then(function () { + if (window.Archify && Archify.viewerChromeLayout && typeof Archify.viewerChromeLayout.whenStable === 'function') { + return Archify.viewerChromeLayout.whenStable(); + } + }).then(function () { + if (window.Archify && Archify.readerLayout && typeof Archify.readerLayout.whenStable === 'function') { + return Archify.readerLayout.whenStable(); + } + }).then(function () { + if (window.Archify && Archify.viewerChromeLayout && typeof Archify.viewerChromeLayout.whenStable === 'function') { + return Archify.viewerChromeLayout.whenStable(); + } + return new Promise(function (resolve) { + requestAnimationFrame(function () { requestAnimationFrame(resolve); }); + }); + }); + })()`, true); + + const metrics = await evaluate(this.cdp, sessionId, `(function () { + var reader = document.querySelector('.container'); + var diagram = document.querySelector('.diagram-container'); + var svg = diagram && ( + diagram.querySelector(':scope > svg') || + diagram.querySelector(':scope > .diagram-stage > svg') + ); + var stage = diagram && (diagram.querySelector(':scope > .diagram-stage') || svg); + var legend = svg && svg.querySelector('[data-legend]'); + var navigationDock = diagram && diagram.querySelector('.diagram-nav'); + var viewBox = svg && svg.viewBox && svg.viewBox.baseVal; + var diagramWidth = svg ? svg.getBoundingClientRect().width : 0; + var viewBoxWidth = viewBox ? viewBox.width : 0; + var scale = viewBoxWidth > 0 ? Math.min(1, diagramWidth / viewBoxWidth) : 0; + var minimum = null; + if (svg && scale > 0) { + Array.from(svg.querySelectorAll('text[data-node-label], text[data-boundary-label], text[data-detail="context"]')).forEach(function (text) { + var detail = text.hasAttribute('data-node-label') + ? 'primary' + : text.hasAttribute('data-boundary-label') ? 'boundary' : 'context'; + if (detail === 'context' && !text.closest('[data-node-id]')) return; + var sourceFontPx = parseFloat(text.getAttribute('font-size') || ''); + if (!Number.isFinite(sourceFontPx)) return; + var projectedFontPx = sourceFontPx * scale; + if (!minimum || projectedFontPx < minimum.projectedFontPx) { + minimum = { + text: (text.textContent || '').trim(), + detail: detail, + sourceFontPx: sourceFontPx, + projectedFontPx: projectedFontPx + }; + } + }); + } + function intersectionArea(a, b) { + if (!a || !b || !a.width || !a.height || !b.width || !b.height) return 0; + var width = Math.max(0, Math.min(a.right, b.right) - Math.max(a.left, b.left)); + var height = Math.max(0, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top)); + return width * height; + } + var legendRect = legend ? legend.getBoundingClientRect() : null; + var stageRect = window.Archify && Archify.viewerChromeLayout + && typeof Archify.viewerChromeLayout.stageRect === 'function' + ? Archify.viewerChromeLayout.stageRect() + : (stage ? stage.getBoundingClientRect() : null); + var navigationDockRect = navigationDock ? navigationDock.getBoundingClientRect() : null; + var stageDockIntersectionArea = intersectionArea(stageRect, navigationDockRect); + var viewerChromeReceipt = window.Archify && Archify.viewerChromeLayout + && typeof Archify.viewerChromeLayout.receipt === 'function' + ? Archify.viewerChromeLayout.receipt() + : null; + return { + innerWidth: window.innerWidth, + innerHeight: window.innerHeight, + scrollWidth: Math.ceil(document.documentElement.scrollWidth), + scrollHeight: Math.ceil(document.documentElement.scrollHeight), + resolvedTheme: document.documentElement.getAttribute('data-theme') || '', + readerWidth: reader ? reader.getBoundingClientRect().width : 0, + diagramWidth: diagramWidth, + viewBoxWidth: viewBoxWidth, + minimumProjectedNodeTextPx: minimum ? minimum.projectedFontPx : null, + minimumProjectedNodeText: minimum ? minimum.text : null, + minimumProjectedNodeTextDetail: minimum ? minimum.detail : null, + hasLegend: Boolean(legendRect && legendRect.width && legendRect.height), + hasNavigationDock: Boolean(navigationDockRect && navigationDockRect.width && navigationDockRect.height), + legendDockIntersectionArea: stageDockIntersectionArea > 0 + ? intersectionArea(legendRect, navigationDockRect) + : 0, + dockStageIntersectionArea: stageDockIntersectionArea, + dockStageGap: stageRect && navigationDockRect ? navigationDockRect.top - stageRect.bottom : null, + viewerChromeRequiredGap: viewerChromeReceipt ? viewerChromeReceipt.gap : null, + viewerChromeReserve: viewerChromeReceipt ? viewerChromeReceipt.reserve : 0, + viewerChromeActive: viewerChromeReceipt ? viewerChromeReceipt.active : false + }; + })()`); + if (!metrics || !Number.isFinite(metrics.scrollWidth) || !Number.isFinite(metrics.scrollHeight)) { + throw new Error('Chrome returned incomplete containment metrics.'); + } + + if (screenshotPath) { + const capture = await this.cdp.send('Page.captureScreenshot', { + format: 'png', + fromSurface: true, + captureBeyondViewport: false, + }, sessionId, 20000); + if (!capture.data) throw new Error('Chrome returned an empty screenshot.'); + fs.writeFileSync(screenshotPath, Buffer.from(capture.data, 'base64')); + } + return metrics; + } + + async close() { + this.cdp.failAll(new Error('visual-check finished')); + if (this.child.exitCode === null && this.child.signalCode === null) { + this.child.kill('SIGTERM'); + await new Promise((resolve) => { + const timer = setTimeout(() => { + if (this.child.exitCode === null && this.child.signalCode === null) this.child.kill('SIGKILL'); + resolve(); + }, 1500); + this.child.once('exit', () => { + clearTimeout(timer); + resolve(); + }); + }); + } + try { + fs.rmSync(this.profileRoot, { recursive: true, force: true }); + } catch { + // Chrome may briefly retain profile files on Windows; evidence is done. + } + } +} + +function observation({ width, height, theme, metrics }) { + const innerWidth = Number(metrics.innerWidth); + const innerHeight = Number(metrics.innerHeight); + const scrollWidth = Number(metrics.scrollWidth); + const scrollHeight = Number(metrics.scrollHeight); + const overflowX = scrollWidth > innerWidth; + const overflowY = scrollHeight > innerHeight; + const minimumProjectedNodeTextPx = metrics.minimumProjectedNodeTextPx == null + ? null + : Number(metrics.minimumProjectedNodeTextPx); + const readabilityOk = minimumProjectedNodeTextPx == null + || minimumProjectedNodeTextPx >= MIN_PROJECTED_NODE_TEXT_PX; + const legendDockIntersectionArea = Number(metrics.legendDockIntersectionArea) || 0; + const dockStageIntersectionArea = Number(metrics.dockStageIntersectionArea) || 0; + const dockStageGap = metrics.dockStageGap == null ? null : Number(metrics.dockStageGap); + const receiptDockStageGap = metrics.viewerChromeRequiredGap == null + ? null + : Number(metrics.viewerChromeRequiredGap); + const requiredDockStageGap = Number.isFinite(receiptDockStageGap) ? receiptDockStageGap : 0; + const viewerChromeStageOk = !metrics.hasNavigationDock || ( + Number.isFinite(dockStageGap) + && dockStageIntersectionArea <= 0.5 + && dockStageGap >= requiredDockStageGap - 1 + ); + const viewerChromeOk = legendDockIntersectionArea <= 0.5 && viewerChromeStageOk; + return { + width, + height, + theme, + innerWidth, + innerHeight, + scrollWidth, + scrollHeight, + overflowX, + overflowY, + ok: !overflowX && !overflowY, + readerWidth: Number(metrics.readerWidth) || null, + diagramWidth: Number(metrics.diagramWidth) || null, + viewBoxWidth: Number(metrics.viewBoxWidth) || null, + minimumProjectedNodeTextPx, + minimumProjectedNodeText: metrics.minimumProjectedNodeText || null, + minimumProjectedNodeTextDetail: metrics.minimumProjectedNodeTextDetail || null, + minimumRequiredNodeTextPx: MIN_PROJECTED_NODE_TEXT_PX, + readabilityOk, + hasLegend: Boolean(metrics.hasLegend), + hasNavigationDock: Boolean(metrics.hasNavigationDock), + legendDockIntersectionArea, + dockStageIntersectionArea, + dockStageGap, + requiredDockStageGap, + viewerChromeStageOk, + viewerChromeReserve: Number(metrics.viewerChromeReserve) || 0, + viewerChromeActive: Boolean(metrics.viewerChromeActive), + viewerChromeOk, + resolvedTheme: metrics.resolvedTheme || theme, + }; +} + +function contactSheetHtml({ artifactPath, receipt, screenshots }) { + const cards = screenshots.map((entry) => ` +
+ ${htmlEscape(`${entry.theme} ${entry.width} by ${entry.height}`)} +
${htmlEscape(entry.theme.toUpperCase())} · ${entry.width}×${entry.height} · containment ${entry.ok ? 'pass' : 'fail'}
+
`).join(''); + return ` + + + + +Archify automated browser evidence · ${htmlEscape(path.basename(artifactPath))} + + + +

Automated browser evidence

${htmlEscape(path.basename(artifactPath))} · visual-check containment ${htmlEscape(receipt.containment.status)} · perceptual visual review pending

+
${cards} +
+ + +`; +} + +function viewportSubject(artifact, entry) { + return { + artifact, + viewport: { width: entry.width, height: entry.height, theme: entry.theme }, + }; +} + +function failureDiagnostic({ code, message, subject, evidence, supportedFixes, severity = 'error' }) { + return { code, severity, message, subject, evidence, supportedFixes }; +} + +function observationDiagnostics({ artifact, allObservations, readabilityObservations }) { + const diagnostics = []; + for (const entry of allObservations) { + if (!entry.ok) { + diagnostics.push(failureDiagnostic({ + code: 'viewer/viewport-overflow', + message: `The rendered artifact overflows the ${entry.width}x${entry.height} ${entry.theme} viewport.`, + subject: viewportSubject(artifact, entry), + evidence: { + innerWidth: entry.innerWidth, + innerHeight: entry.innerHeight, + scrollWidth: entry.scrollWidth, + scrollHeight: entry.scrollHeight, + overflowX: entry.overflowX, + overflowY: entry.overflowY, + }, + supportedFixes: [ + `contain the rendered layout within ${entry.width}x${entry.height}, then rerun visual-check`, + ], + })); + } + if (entry.legendDockIntersectionArea > 0.5) { + diagnostics.push(failureDiagnostic({ + code: 'viewer/chrome-legend-clearance', + message: `The navigation Dock obscures the SVG Legend at ${entry.width}x${entry.height} (${entry.theme}).`, + subject: viewportSubject(artifact, entry), + evidence: { legendDockIntersectionArea: entry.legendDockIntersectionArea }, + supportedFixes: [ + 'move the SVG Legend or Viewer Dock until legendDockIntersectionArea is 0, then rerun visual-check', + ], + })); + } + if (!entry.viewerChromeStageOk) { + const stageOverlapsDock = entry.dockStageIntersectionArea > 0.5; + diagnostics.push(failureDiagnostic({ + code: 'viewer/chrome-stage-clearance', + message: stageOverlapsDock + ? `Navigation Dock enters the protected SVG stage at ${entry.width}x${entry.height} (${entry.theme}).` + : `Navigation Dock clearance from the protected SVG stage is below the required gap at ${entry.width}x${entry.height} (${entry.theme}).`, + subject: viewportSubject(artifact, entry), + evidence: { + dockStageIntersectionArea: entry.dockStageIntersectionArea, + dockStageGap: entry.dockStageGap, + requiredDockStageGap: entry.requiredDockStageGap, + }, + supportedFixes: [ + `adjust Viewer stage reservation or clipping until dockStageGap is at least ${entry.requiredDockStageGap} and dockStageIntersectionArea is 0, then rerun visual-check`, + ], + })); + } + } + for (const entry of readabilityObservations) { + if (entry.readabilityOk) continue; + diagnostics.push(failureDiagnostic({ + code: 'viewer/projected-text-readability', + message: `Projected ${entry.minimumProjectedNodeTextDetail || 'node'} text is below the readability floor at ${entry.width}x${entry.height}.`, + subject: viewportSubject(artifact, entry), + evidence: { + text: entry.minimumProjectedNodeText, + detail: entry.minimumProjectedNodeTextDetail, + minimumProjectedNodeTextPx: entry.minimumProjectedNodeTextPx, + minimumRequiredNodeTextPx: entry.minimumRequiredNodeTextPx, + }, + supportedFixes: [ + `increase projected node text to at least ${entry.minimumRequiredNodeTextPx}px at ${entry.width}x${entry.height}, then rerun visual-check`, + ], + })); + } + return diagnostics; +} + +function baseReceipt({ artifactPath, artifact, outputs, chrome }) { + return { + schemaVersion: 1, + ok: false, + command: 'visual-check', + evidenceKind: 'automated-browser', + status: 'fail', + visualReview: 'pending', + artifact: { + path: artifactPath, + sha256: sha256(artifact), + bytes: artifact.byteLength, + }, + state: { detail: 'read', motion: 'still' }, + chrome, + diagnostics: [], + containment: { status: 'fail', viewports: [] }, + readability: { status: 'fail', minimumProjectedNodeTextPx: MIN_PROJECTED_NODE_TEXT_PX, viewports: [] }, + viewerChrome: { status: 'fail', viewports: [] }, + captures: { status: 'fail', screenshots: [], contactSheet: null }, + sidecars: { + receipt: path.basename(outputs.receipt), + contactSheet: path.basename(outputs.contactSheet), + }, + }; +} + +function persistReceipt(outputs, receipt) { + writeAtomic(outputs.receipt, `${JSON.stringify(receipt, null, 2)}\n`); +} + +export async function runVisualCheck({ + artifactPath, + chromePath, + resolveChrome = findChrome, + browserFactory = async (resolvedChrome) => new ChromeVisualBrowser(resolvedChrome), +} = {}) { + if (!artifactPath) throw new Error('visual-check requires one delivered HTML artifact.'); + const artifact = path.resolve(artifactPath); + if (!/\.html?$/i.test(artifact)) throw new Error('visual-check requires an .html artifact.'); + const artifactBytes = fs.readFileSync(artifact); + const outputs = sidecarPaths(artifact); + cleanupCaptureSidecars(outputs); + safeUnlink(outputs.receipt); + + const resolvedChrome = chromePath || resolveChrome(); + const receipt = baseReceipt({ + artifactPath: artifact, + artifact: artifactBytes, + outputs, + chrome: resolvedChrome + ? { status: 'available', executable: resolvedChrome } + : { status: 'unavailable', executable: null }, + }); + + if (!resolvedChrome) { + receipt.status = 'skipped'; + receipt.containment.status = 'skipped'; + receipt.readability.status = 'skipped'; + receipt.viewerChrome.status = 'skipped'; + receipt.captures.status = 'skipped'; + receipt.error = 'Chrome or Chromium is unavailable. Set ARCHIFY_CHROME to its executable path.'; + receipt.diagnostics = [failureDiagnostic({ + code: 'viewer/chrome-unavailable', + severity: 'warning', + message: receipt.error, + subject: { artifact }, + evidence: { executable: null }, + supportedFixes: ['set ARCHIFY_CHROME to a Chrome or Chromium executable and rerun visual-check'], + })]; + persistReceipt(outputs, receipt); + return { exitCode: EXIT.skipped, receipt }; + } + + let browser; + try { + browser = await browserFactory(resolvedChrome); + const observations = new Map(); + const screenshotsByKey = new Map(outputs.screenshots.map((entry) => [ + screenshotKey(entry.width, entry.height, entry.theme), + entry, + ])); + + for (const viewport of VISUAL_CHECK_VIEWPORTS) { + const key = screenshotKey(viewport.width, viewport.height, 'light'); + const screenshot = screenshotsByKey.get(key); + const metrics = await browser.inspect({ + artifactPath: artifact, + ...viewport, + theme: 'light', + ...(screenshot ? { screenshotPath: screenshot.path } : {}), + }); + observations.set(key, observation({ ...viewport, theme: 'light', metrics })); + } + for (const viewport of CAPTURE_VIEWPORTS) { + const key = screenshotKey(viewport.width, viewport.height, 'dark'); + const screenshot = screenshotsByKey.get(key); + const metrics = await browser.inspect({ + artifactPath: artifact, + ...viewport, + theme: 'dark', + screenshotPath: screenshot.path, + }); + observations.set(key, observation({ ...viewport, theme: 'dark', metrics })); + } + + const afterBytes = fs.readFileSync(artifact); + if (sha256(afterBytes) !== receipt.artifact.sha256 || afterBytes.byteLength !== receipt.artifact.bytes) { + throw new Error('The delivered artifact changed while visual-check was running.'); + } + + receipt.containment.viewports = VISUAL_CHECK_VIEWPORTS.map(({ width, height }) => ( + observations.get(screenshotKey(width, height, 'light')) + )); + receipt.readability.viewports = receipt.containment.viewports.map((entry) => ({ ...entry })); + receipt.viewerChrome.viewports = receipt.containment.viewports.map((entry) => ({ ...entry })); + receipt.captures.screenshots = outputs.screenshots.map((entry) => ({ + ...observations.get(screenshotKey(entry.width, entry.height, entry.theme)), + file: path.basename(entry.path), + })); + const allObservations = [...observations.values()]; + const containmentPass = allObservations.every((entry) => entry.ok); + const readabilityPass = receipt.readability.viewports.every((entry) => entry.readabilityOk); + const viewerChromePass = allObservations.every((entry) => entry.viewerChromeOk); + receipt.diagnostics = observationDiagnostics({ + artifact, + allObservations, + readabilityObservations: receipt.readability.viewports, + }); + receipt.containment.status = containmentPass ? 'pass' : 'fail'; + receipt.readability.status = readabilityPass ? 'pass' : 'fail'; + receipt.viewerChrome.status = viewerChromePass ? 'pass' : 'fail'; + receipt.captures.status = 'pass'; + receipt.captures.contactSheet = path.basename(outputs.contactSheet); + receipt.status = containmentPass && readabilityPass && viewerChromePass ? 'pass' : 'fail'; + receipt.ok = containmentPass && readabilityPass && viewerChromePass; + writeAtomic(outputs.contactSheet, contactSheetHtml({ + artifactPath: artifact, + receipt, + screenshots: receipt.captures.screenshots, + })); + persistReceipt(outputs, receipt); + return { exitCode: receipt.ok ? EXIT.pass : EXIT.fail, receipt }; + } catch (error) { + cleanupCaptureSidecars(outputs); + receipt.status = 'fail'; + receipt.ok = false; + receipt.error = error.message; + receipt.containment.status = 'fail'; + receipt.readability.status = 'fail'; + receipt.viewerChrome.status = 'fail'; + receipt.captures.status = 'fail'; + receipt.captures.screenshots = []; + receipt.captures.contactSheet = null; + receipt.diagnostics = [failureDiagnostic({ + code: 'viewer/visual-check-runtime', + message: 'visual-check could not complete its Chrome inspection.', + subject: { artifact }, + evidence: { reason: error.message }, + supportedFixes: ['resolve the reported Chrome inspection error, then rerun visual-check'], + })]; + persistReceipt(outputs, receipt); + return { exitCode: EXIT.fail, receipt }; + } finally { + if (browser?.close) await browser.close(); + } +} diff --git a/tools/archify/brand-marks/README.md b/tools/archify/brand-marks/README.md new file mode 100644 index 0000000..83578f6 --- /dev/null +++ b/tools/archify/brand-marks/README.md @@ -0,0 +1,31 @@ +# Built-in brand marks + +Archify ships a bounded catalogue of 107 commonly used brands for architecture, +workflow, sequence, data-flow, and lifecycle nodes. The mark is optional authored +identity: it never replaces the node's semantic `type`, color, label, or +relationships. + +Unknown sites are handled by an explicit two-stage workflow. Run +`node bin/archify.mjs brands capture --json`, then author the returned +digest-pinned `brand` value. Normal render and validate commands do not perform +an unpinned capture, and changed or unavailable content fails closed. + +Most vector paths and brand metadata are generated from Simple Icons 16.28.0. +The OpenAI mark is traced to OpenAI's official brand guidelines. Every generated +entry records its source and, when available upstream, its guidelines and license +metadata in `renderers/shared/generated-brand-marks.mjs`. + +Brand names and logos may be trademarks of their respective owners. Simple +Icons' CC0 license covers its collection work, not every underlying trademark or +artwork. Contributors must review the recorded source, current brand guidelines, +and intended referential use before adding or updating a mark. Archify does not +imply sponsorship, endorsement, or partnership. + +Edit `catalog.json`, then regenerate the committed zero-runtime-dependency bundle: + +```bash +npm run generate:brand-marks +npm run check:brand-marks +``` + +Do not hand-edit `renderers/shared/generated-brand-marks.mjs`. diff --git a/tools/archify/brand-marks/catalog.json b/tools/archify/brand-marks/catalog.json new file mode 100644 index 0000000..ac56989 --- /dev/null +++ b/tools/archify/brand-marks/catalog.json @@ -0,0 +1,131 @@ +{ + "schemaVersion": 1, + "marks": [ + { + "id": "openai", + "title": "OpenAI", + "category": "ai", + "aliases": ["chatgpt", "gpt", "codex"], + "domains": ["openai.com", "chatgpt.com"], + "custom": { + "viewBox": 20, + "hex": "000000", + "path": "M11.248 18.25q-.825 0-1.568-.314a4.3 4.3 0 0 1-1.32-.874 4 4 0 0 1-1.304.214 4 4 0 0 1-2.046-.544 4.27 4.27 0 0 1-1.518-1.485 4 4 0 0 1-.56-2.095q0-.48.131-1.04A4.4 4.4 0 0 1 2.04 10.71a4.07 4.07 0 0 1 .017-3.4 4.2 4.2 0 0 1 1.056-1.418 3.8 3.8 0 0 1 1.6-.842 3.9 3.9 0 0 1 .76-1.683q.593-.759 1.451-1.188a4.04 4.04 0 0 1 1.832-.429q.825 0 1.567.313.742.314 1.32.875a4 4 0 0 1 1.304-.215q1.106 0 2.046.545a4.14 4.14 0 0 1 1.501 1.485q.578.941.578 2.095 0 .48-.132 1.04.66.61 1.023 1.419.363.792.363 1.666 0 .892-.38 1.717a4.3 4.3 0 0 1-1.072 1.435 3.8 3.8 0 0 1-1.584.825 3.8 3.8 0 0 1-.775 1.683 4.06 4.06 0 0 1-1.436 1.188 4.04 4.04 0 0 1-1.832.429m-4.076-2.062q.825 0 1.435-.347l3.103-1.782a.36.36 0 0 0 .164-.313v-1.42L7.881 14.62a.67.67 0 0 1-.726 0l-3.118-1.798a.5.5 0 0 1-.017.115v.198q0 .841.396 1.551.413.693 1.139 1.089a3.2 3.2 0 0 0 1.617.412m.165-2.69a.4.4 0 0 0 .181.05q.083 0 .165-.05l1.238-.71-3.977-2.31a.7.7 0 0 1-.363-.643v-3.58q-.825.362-1.32 1.122a2.9 2.9 0 0 0-.495 1.65q0 .809.413 1.55.412.743 1.072 1.123zm3.91 3.663q.875 0 1.585-.396a2.96 2.96 0 0 0 1.534-2.64v-3.564a.32.32 0 0 0-.165-.297l-1.254-.726v4.604a.7.7 0 0 1-.363.643l-3.119 1.799a3 3 0 0 0 1.783.577m.627-6.039V8.878L10.01 7.822 8.129 8.878v2.244l1.881 1.056zM7.057 5.859a.7.7 0 0 1 .363-.644l3.119-1.798a3 3 0 0 0-1.782-.578q-.874 0-1.584.396A2.96 2.96 0 0 0 6.05 4.324a3.07 3.07 0 0 0-.396 1.551v3.547q0 .199.165.314l1.237.726zm8.383 7.887q.825-.364 1.303-1.123.495-.758.495-1.65a3.15 3.15 0 0 0-.412-1.55q-.413-.743-1.073-1.123l-3.086-1.782q-.099-.065-.181-.049a.3.3 0 0 0-.165.05l-1.238.692 3.993 2.327a.6.6 0 0 1 .264.264.64.64 0 0 1 .1.363zm-3.317-8.382a.63.63 0 0 1 .726 0l3.135 1.831v-.297q0-.792-.396-1.501a2.86 2.86 0 0 0-1.105-1.155q-.71-.43-1.65-.43-.825 0-1.436.347L8.294 5.941a.36.36 0 0 0-.165.314v1.418z", + "source": "https://openai.com/brand/", + "guidelines": "https://openai.com/brand/" + } + }, + { "id": "claude", "category": "ai", "simpleIcon": "claude", "aliases": ["claude-ai"], "domains": ["claude.ai"] }, + { "id": "anthropic", "category": "ai", "simpleIcon": "anthropic", "domains": ["anthropic.com"] }, + { "id": "google-gemini", "category": "ai", "simpleIcon": "googlegemini", "aliases": ["gemini"], "domains": ["gemini.google.com"] }, + { "id": "deepseek", "category": "ai", "simpleIcon": "deepseek", "domains": ["deepseek.com"] }, + { "id": "qwen", "category": "ai", "simpleIcon": "qwen", "domains": ["qwen.ai"] }, + { "id": "meta", "category": "ai", "simpleIcon": "meta", "aliases": ["llama"], "domains": ["meta.com"] }, + { "id": "mistral-ai", "category": "ai", "simpleIcon": "mistralai", "aliases": ["mistral"], "domains": ["mistral.ai"] }, + { "id": "hugging-face", "category": "ai", "simpleIcon": "huggingface", "aliases": ["huggingface"], "domains": ["huggingface.co"] }, + { "id": "ollama", "category": "ai", "simpleIcon": "ollama", "domains": ["ollama.com"] }, + { "id": "openrouter", "category": "ai", "simpleIcon": "openrouter", "aliases": ["open-router"], "domains": ["openrouter.ai"] }, + { "id": "perplexity", "category": "ai", "simpleIcon": "perplexity", "domains": ["perplexity.ai"] }, + { "id": "replicate", "category": "ai", "simpleIcon": "replicate", "domains": ["replicate.com"] }, + + { "id": "google-cloud", "category": "cloud", "simpleIcon": "googlecloud", "aliases": ["gcp", "googlecloud"], "domains": ["cloud.google.com"] }, + { "id": "cloudflare", "category": "cloud", "simpleIcon": "cloudflare", "domains": ["cloudflare.com"] }, + { "id": "vercel", "category": "cloud", "simpleIcon": "vercel", "domains": ["vercel.com"] }, + { "id": "netlify", "category": "cloud", "simpleIcon": "netlify", "domains": ["netlify.com"] }, + { "id": "digitalocean", "category": "cloud", "simpleIcon": "digitalocean", "aliases": ["digital-ocean"], "domains": ["digitalocean.com"] }, + { "id": "render", "category": "cloud", "simpleIcon": "render", "domains": ["render.com"] }, + { "id": "railway", "category": "cloud", "simpleIcon": "railway", "domains": ["railway.com", "railway.app"] }, + { "id": "fly-io", "category": "cloud", "simpleIcon": "flydotio", "aliases": ["fly.io"], "domains": ["fly.io"] }, + { "id": "cloudinary", "category": "cloud", "simpleIcon": "cloudinary", "domains": ["cloudinary.com"] }, + { "id": "alibaba-cloud", "category": "cloud", "simpleIcon": "alibabacloud", "aliases": ["aliyun"], "domains": ["alibabacloud.com", "aliyun.com"] }, + { "id": "firebase", "category": "cloud", "simpleIcon": "firebase", "domains": ["firebase.google.com"] }, + { "id": "supabase", "category": "cloud", "simpleIcon": "supabase", "domains": ["supabase.com"] }, + { "id": "neon", "category": "cloud", "simpleIcon": "neon", "domains": ["neon.tech"] }, + + { "id": "github", "category": "engineering", "simpleIcon": "github", "domains": ["github.com"] }, + { "id": "gitlab", "category": "engineering", "simpleIcon": "gitlab", "domains": ["gitlab.com"] }, + { "id": "bitbucket", "category": "engineering", "simpleIcon": "bitbucket", "domains": ["bitbucket.org"] }, + { "id": "docker", "category": "engineering", "simpleIcon": "docker", "domains": ["docker.com"] }, + { "id": "kubernetes", "category": "engineering", "simpleIcon": "kubernetes", "aliases": ["k8s"], "domains": ["kubernetes.io"] }, + { "id": "terraform", "category": "engineering", "simpleIcon": "terraform", "domains": ["terraform.io"] }, + { "id": "pulumi", "category": "engineering", "simpleIcon": "pulumi", "domains": ["pulumi.com"] }, + { "id": "ansible", "category": "engineering", "simpleIcon": "ansible", "domains": ["ansible.com"] }, + { "id": "jenkins", "category": "engineering", "simpleIcon": "jenkins", "domains": ["jenkins.io"] }, + { "id": "circleci", "category": "engineering", "simpleIcon": "circleci", "aliases": ["circle-ci"], "domains": ["circleci.com"] }, + { "id": "github-actions", "category": "engineering", "simpleIcon": "githubactions" }, + { "id": "argo", "category": "engineering", "simpleIcon": "argo", "aliases": ["argocd", "argo-cd"], "domains": ["argoproj.github.io"] }, + { "id": "helm", "category": "engineering", "simpleIcon": "helm", "domains": ["helm.sh"] }, + { "id": "grafana", "category": "engineering", "simpleIcon": "grafana", "domains": ["grafana.com"] }, + { "id": "prometheus", "category": "engineering", "simpleIcon": "prometheus", "domains": ["prometheus.io"] }, + { "id": "sentry", "category": "engineering", "simpleIcon": "sentry", "domains": ["sentry.io"] }, + { "id": "datadog", "category": "engineering", "simpleIcon": "datadog", "domains": ["datadoghq.com"] }, + { "id": "pagerduty", "category": "engineering", "simpleIcon": "pagerduty", "aliases": ["pager-duty"], "domains": ["pagerduty.com"] }, + + { "id": "postgresql", "category": "data", "simpleIcon": "postgresql", "aliases": ["postgres"], "domains": ["postgresql.org"] }, + { "id": "mysql", "category": "data", "simpleIcon": "mysql", "domains": ["mysql.com"] }, + { "id": "mongodb", "category": "data", "simpleIcon": "mongodb", "aliases": ["mongo"], "domains": ["mongodb.com"] }, + { "id": "redis", "category": "data", "simpleIcon": "redis", "domains": ["redis.io"] }, + { "id": "apache-kafka", "category": "data", "simpleIcon": "apachekafka", "aliases": ["kafka"], "domains": ["kafka.apache.org"] }, + { "id": "rabbitmq", "category": "data", "simpleIcon": "rabbitmq", "aliases": ["rabbit-mq"], "domains": ["rabbitmq.com"] }, + { "id": "clickhouse", "category": "data", "simpleIcon": "clickhouse", "domains": ["clickhouse.com"] }, + { "id": "elasticsearch", "category": "data", "simpleIcon": "elasticsearch", "aliases": ["elastic"], "domains": ["elastic.co"] }, + { "id": "opensearch", "category": "data", "simpleIcon": "opensearch", "aliases": ["open-search"], "domains": ["opensearch.org"] }, + { "id": "snowflake", "category": "data", "simpleIcon": "snowflake", "domains": ["snowflake.com"] }, + { "id": "databricks", "category": "data", "simpleIcon": "databricks", "domains": ["databricks.com"] }, + { "id": "planetscale", "category": "data", "simpleIcon": "planetscale", "aliases": ["planet-scale"], "domains": ["planetscale.com"] }, + { "id": "prisma", "category": "data", "simpleIcon": "prisma", "domains": ["prisma.io"] }, + { "id": "sqlite", "category": "data", "simpleIcon": "sqlite", "domains": ["sqlite.org"] }, + { "id": "mariadb", "category": "data", "simpleIcon": "mariadb", "aliases": ["maria-db"], "domains": ["mariadb.org"] }, + { "id": "influxdb", "category": "data", "simpleIcon": "influxdb", "aliases": ["influx-db"], "domains": ["influxdata.com"] }, + { "id": "apache-airflow", "category": "data", "simpleIcon": "apacheairflow", "aliases": ["airflow"], "domains": ["airflow.apache.org"] }, + + { "id": "notion", "category": "collaboration", "simpleIcon": "notion", "domains": ["notion.so"] }, + { "id": "figma", "category": "collaboration", "simpleIcon": "figma", "domains": ["figma.com"] }, + { "id": "jira", "category": "collaboration", "simpleIcon": "jira", "domains": ["atlassian.com"] }, + { "id": "linear", "category": "collaboration", "simpleIcon": "linear", "domains": ["linear.app"] }, + { "id": "discord", "category": "collaboration", "simpleIcon": "discord", "domains": ["discord.com"] }, + { "id": "zoom", "category": "collaboration", "simpleIcon": "zoom", "domains": ["zoom.us"] }, + { "id": "trello", "category": "collaboration", "simpleIcon": "trello", "domains": ["trello.com"] }, + { "id": "asana", "category": "collaboration", "simpleIcon": "asana", "domains": ["asana.com"] }, + { "id": "airtable", "category": "collaboration", "simpleIcon": "airtable", "domains": ["airtable.com"] }, + { "id": "miro", "category": "collaboration", "simpleIcon": "miro", "domains": ["miro.com"] }, + { "id": "stripe", "category": "business", "simpleIcon": "stripe", "domains": ["stripe.com"] }, + { "id": "shopify", "category": "business", "simpleIcon": "shopify", "domains": ["shopify.com"] }, + { "id": "hubspot", "category": "business", "simpleIcon": "hubspot", "domains": ["hubspot.com"] }, + { "id": "paypal", "category": "business", "simpleIcon": "paypal", "domains": ["paypal.com"] }, + { "id": "intercom", "category": "business", "simpleIcon": "intercom", "domains": ["intercom.com"] }, + { "id": "zendesk", "category": "business", "simpleIcon": "zendesk", "domains": ["zendesk.com"] }, + { "id": "wordpress", "category": "business", "simpleIcon": "wordpress", "domains": ["wordpress.org", "wordpress.com"] }, + { "id": "woocommerce", "category": "business", "simpleIcon": "woocommerce", "aliases": ["woo-commerce"], "domains": ["woocommerce.com"] }, + + { "id": "wechat", "category": "channel", "simpleIcon": "wechat", "aliases": ["weixin", "微信"], "domains": ["weixin.qq.com"] }, + { "id": "youtube", "category": "channel", "simpleIcon": "youtube", "domains": ["youtube.com", "youtu.be"] }, + { "id": "tiktok", "category": "channel", "simpleIcon": "tiktok", "aliases": ["douyin", "抖音"], "domains": ["tiktok.com", "douyin.com"] }, + { "id": "x", "category": "channel", "simpleIcon": "x", "aliases": ["twitter"], "domains": ["x.com", "twitter.com"] }, + { "id": "instagram", "category": "channel", "simpleIcon": "instagram", "domains": ["instagram.com"] }, + { "id": "facebook", "category": "channel", "simpleIcon": "facebook", "domains": ["facebook.com"] }, + { "id": "reddit", "category": "channel", "simpleIcon": "reddit", "domains": ["reddit.com"] }, + { "id": "telegram", "category": "channel", "simpleIcon": "telegram", "domains": ["telegram.org", "t.me"] }, + { "id": "whatsapp", "category": "channel", "simpleIcon": "whatsapp", "domains": ["whatsapp.com"] }, + { "id": "pinterest", "category": "channel", "simpleIcon": "pinterest", "domains": ["pinterest.com"] }, + + { "id": "python", "category": "language", "simpleIcon": "python", "domains": ["python.org"] }, + { "id": "typescript", "category": "language", "simpleIcon": "typescript", "aliases": ["ts"], "domains": ["typescriptlang.org"] }, + { "id": "javascript", "category": "language", "simpleIcon": "javascript", "aliases": ["js"] }, + { "id": "go", "category": "language", "simpleIcon": "go", "aliases": ["golang"], "domains": ["go.dev"] }, + { "id": "rust", "category": "language", "simpleIcon": "rust", "domains": ["rust-lang.org"] }, + { "id": "node-js", "category": "framework", "simpleIcon": "nodedotjs", "aliases": ["node", "nodejs"], "domains": ["nodejs.org"] }, + { "id": "react", "category": "framework", "simpleIcon": "react", "aliases": ["reactjs"], "domains": ["react.dev"] }, + { "id": "vue", "category": "framework", "simpleIcon": "vuedotjs", "aliases": ["vuejs", "vue.js"], "domains": ["vuejs.org"] }, + { "id": "next-js", "category": "framework", "simpleIcon": "nextdotjs", "aliases": ["nextjs", "next.js"], "domains": ["nextjs.org"] }, + { "id": "pytorch", "category": "framework", "simpleIcon": "pytorch", "domains": ["pytorch.org"] }, + { "id": "tensorflow", "category": "framework", "simpleIcon": "tensorflow", "domains": ["tensorflow.org"] }, + { "id": "angular", "category": "framework", "simpleIcon": "angular", "domains": ["angular.dev"] }, + { "id": "svelte", "category": "framework", "simpleIcon": "svelte", "domains": ["svelte.dev"] }, + { "id": "django", "category": "framework", "simpleIcon": "django", "domains": ["djangoproject.com"] }, + { "id": "flask", "category": "framework", "simpleIcon": "flask", "domains": ["palletsprojects.com"] }, + { "id": "fastapi", "category": "framework", "simpleIcon": "fastapi", "domains": ["fastapi.tiangolo.com"] }, + { "id": "spring", "category": "framework", "simpleIcon": "spring", "aliases": ["spring-boot"], "domains": ["spring.io"] }, + { "id": "dotnet", "category": "framework", "simpleIcon": "dotnet", "aliases": [".net"], "domains": ["dotnet.microsoft.com"] } + ] +} diff --git a/tools/archify/delta/architecture-delta.mjs b/tools/archify/delta/architecture-delta.mjs new file mode 100644 index 0000000..0c49ae7 --- /dev/null +++ b/tools/archify/delta/architecture-delta.mjs @@ -0,0 +1,1214 @@ +const COMPARATOR_VERSION = 1; +const CANONICAL_VERSION = 1; + +export class ArchitectureDeltaError extends Error { + constructor(code, message, details = {}) { + super(message); + this.name = 'ArchitectureDeltaError'; + this.code = code; + this.details = details; + } +} + +const codepointOrder = (left, right) => (left < right ? -1 : left > right ? 1 : 0); +const sorted = (values) => [...values].sort((left, right) => codepointOrder(String(left), String(right))); + +function canonical(value) { + if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`; + if (value && typeof value === 'object') { + return `{${Object.keys(value).sort(codepointOrder).map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`; + } + return JSON.stringify(value); +} + +const equal = (left, right) => canonical(left) === canonical(right); + +function sortedObjects(values) { + return [...values].sort((left, right) => codepointOrder(canonical(left), canonical(right))); +} + +function sortedBy(values, keyFor) { + return [...values].sort((left, right) => codepointOrder(String(keyFor(left)), String(keyFor(right)))); +} + +function normalizeRepository(repository) { + if (!repository) return undefined; + return { + url: String(repository.url || '').trim().replace(/\.git\/?$/i, '').replace(/\/$/, '').toLowerCase(), + revision: String(repository.revision || '').toLowerCase(), + }; +} + +function normalizeComponent(component) { + return { + ...component, + ...(Array.isArray(component.sources) ? { sources: sortedObjects(component.sources) } : {}), + }; +} + +function normalizeBoundary(boundary) { + return { ...boundary, wraps: sorted(boundary.wraps || []) }; +} + +export function canonicalArchitecture(diagram) { + const meta = { ...(diagram.meta || {}) }; + delete meta.output; + if (meta.repository) meta.repository = normalizeRepository(meta.repository); + return { + schema_version: diagram.schema_version, + diagram_type: diagram.diagram_type, + meta, + ...(diagram.layout ? { layout: diagram.layout } : {}), + components: sortedBy((diagram.components || []).map(normalizeComponent), (component) => component.id), + boundaries: sortedBy((diagram.boundaries || []).map(normalizeBoundary), boundaryKey), + connections: sortedBy(diagram.connections || [], (connection) => connection.id || ''), + ...(diagram.cards ? { cards: diagram.cards } : {}), + }; +} + +export function canonicalArchitectureJson(diagram) { + return canonical(canonicalArchitecture(diagram)); +} + +function fail(code, message, details) { + throw new ArchitectureDeltaError(code, message, details); +} + +function requireComparableShape(diagram, side) { + if (diagram?.schema_version !== 1) { + fail('delta/schema-version-mismatch', `${side} must use schema_version 1.`, { side, path: '/schema_version', actual: diagram?.schema_version }); + } + if (diagram?.diagram_type !== 'architecture') { + fail('delta/type-mismatch', `${side} must use diagram_type architecture.`, { side, path: '/diagram_type', actual: diagram?.diagram_type }); + } +} + +function stableIndex(items, collection, side, missingCode = 'delta/stable-id-required') { + const index = new Map(); + const missing = []; + const duplicates = []; + (items || []).forEach((item, itemIndex) => { + if (!item?.id) missing.push(`/${collection}/${itemIndex}/id`); + else if (index.has(item.id)) duplicates.push(item.id); + else index.set(item.id, item); + }); + if (missing.length) { + fail(missingCode, `${side} ${collection} require authored stable ids for comparison.`, { + side, + paths: sorted(missing), + supportedFixes: [`add a unique id to every ${collection} item`], + }); + } + if (duplicates.length) { + fail('delta/duplicate-stable-id', `${side} ${collection} contain duplicate ids.`, { + side, + collection, + ids: sorted(new Set(duplicates)), + supportedFixes: [`make every ${collection} id unique`], + }); + } + return index; +} + +const boundaryKey = (boundary) => `${boundary.kind}\u001f${boundary.label}`; + +function boundaryIndex(boundaries, side) { + const index = new Map(); + const ambiguous = []; + for (const boundary of boundaries || []) { + const key = boundaryKey(boundary); + if (index.has(key)) ambiguous.push(`${boundary.kind}:${boundary.label}`); + else index.set(key, boundary); + } + if (ambiguous.length) { + fail('delta/boundary-key-ambiguous', `${side} boundary kind + label keys must be unique.`, { + side, + boundaries: sorted(new Set(ambiguous)), + supportedFixes: ['rename one duplicate boundary or add stable boundary ids in a future schema version'], + }); + } + return index; +} + +function normalizedField(item, field) { + const value = item?.[field]; + if (field === 'sources' && Array.isArray(value)) return sortedObjects(value); + if (field === 'wraps' && Array.isArray(value)) return sorted(value); + return value; +} + +function fieldChanges(before, after, groups) { + const classifications = []; + const changedFields = []; + for (const [classification, fields] of Object.entries(groups)) { + const changed = fields.filter((field) => !equal(normalizedField(before, field), normalizedField(after, field))); + if (changed.length) classifications.push(classification); + changedFields.push(...changed.map((field) => `/${field}`)); + } + return { classifications: sorted(classifications), changedFields: sorted(changedFields) }; +} + +const COMPONENT_FIELDS = { + semantic: ['type', 'label', 'sublabel', 'tag'], + evidence: ['sources'], + geometry: ['row', 'col', 'pos', 'size'], +}; +const CONNECTION_FIELDS = { + topology: ['from', 'to'], + semantic: ['label', 'variant'], + geometry: ['fromSide', 'toSide', 'route', 'via', 'labelAt', 'labelDx', 'labelDy', 'labelSegment', 'width'], +}; +const BOUNDARY_FIELDS = { scope: ['wraps'], geometry: ['pad'] }; + +function statusFor(classifications, kind) { + if (classifications.some((value) => ['topology', 'semantic', 'scope'].includes(value))) return 'changed'; + if (classifications.includes('evidence')) return 'evidence-changed'; + if (classifications.includes('geometry')) return kind === 'connection' ? 'rerouted' : kind === 'component' ? 'moved' : 'geometry-changed'; + return 'same'; +} + +function compareEntities(baseIndex, headIndex, kind, groups, describe) { + const changes = []; + const identityClassification = kind === 'connection' ? 'topology' : kind === 'boundary' ? 'scope' : 'semantic'; + for (const id of sorted(new Set([...baseIndex.keys(), ...headIndex.keys()]))) { + const base = baseIndex.get(id); + const head = headIndex.get(id); + if (!base) changes.push({ ...describe(id, undefined, head), status: 'added', classifications: [identityClassification], changedFields: [] }); + else if (!head) changes.push({ ...describe(id, base, undefined), status: 'removed', classifications: [identityClassification], changedFields: [] }); + else { + const fields = fieldChanges(base, head, groups); + const status = statusFor(fields.classifications, kind); + if (status !== 'same') changes.push({ ...describe(id, base, head), status, ...fields }); + } + } + return changes; +} + +function summaryFor(changes, shape) { + const summary = Object.fromEntries(shape.map((key) => [key, 0])); + for (const change of changes) { + const key = change.status.replace(/-([a-z])/g, (_all, letter) => letter.toUpperCase()); + if (Object.hasOwn(summary, key)) summary[key] += 1; + } + return summary; +} + +function presentationChanged(base, head) { + const basePresentation = { + title: base.meta?.title, + subtitle: base.meta?.subtitle, + animation: base.meta?.animation, + visual_preset: base.meta?.visual_preset, + quality_profile: base.meta?.quality_profile, + engineering_profile: base.meta?.engineering_profile, + legend: base.meta?.legend, + views: base.meta?.views, + viewBox: base.meta?.viewBox, + layout: base.layout, + cards: base.cards, + }; + const headPresentation = { + title: head.meta?.title, + subtitle: head.meta?.subtitle, + animation: head.meta?.animation, + visual_preset: head.meta?.visual_preset, + quality_profile: head.meta?.quality_profile, + engineering_profile: head.meta?.engineering_profile, + legend: head.meta?.legend, + views: head.meta?.views, + viewBox: head.meta?.viewBox, + layout: head.layout, + cards: head.cards, + }; + return !equal(basePresentation, headPresentation); +} + +export function compareArchitecture(base, head, evidence = {}) { + requireComparableShape(base, 'base'); + requireComparableShape(head, 'head'); + const baseComponents = stableIndex(base.components, 'components', 'base'); + const headComponents = stableIndex(head.components, 'components', 'head'); + const shared = sorted([...baseComponents.keys()].filter((id) => headComponents.has(id))); + if (!shared.length) { + fail('delta/no-shared-component-id', 'The snapshots share no component id, so Archify cannot prove that they describe the same system.', { + supportedFixes: ['preserve at least one authored component id across snapshots'], + }); + } + + const baseConnections = stableIndex(base.connections, 'connections', 'base', 'delta/relationship-id-required'); + const headConnections = stableIndex(head.connections, 'connections', 'head', 'delta/relationship-id-required'); + const baseBoundaries = boundaryIndex(base.boundaries, 'base'); + const headBoundaries = boundaryIndex(head.boundaries, 'head'); + + const baseRepository = normalizeRepository(base.meta?.repository); + const headRepository = normalizeRepository(head.meta?.repository); + if (baseRepository && headRepository && baseRepository.url !== headRepository.url) { + fail('delta/repository-mismatch', 'The snapshots name different repositories.', { + baseRepository: baseRepository.url, + headRepository: headRepository.url, + supportedFixes: ['compare snapshots from the same repository or remove repository evidence from both inputs'], + }); + } + const proofLevel = baseRepository && headRepository + && evidence.baseVerified && evidence.headVerified + && /^[a-f0-9]{40}$/.test(baseRepository.revision) + && /^[a-f0-9]{40}$/.test(headRepository.revision) + ? 'revision-pinned' + : 'authored'; + + const components = compareEntities(baseComponents, headComponents, 'component', COMPONENT_FIELDS, (id, before, after) => ({ + id, + baseLabel: before?.label, + headLabel: after?.label, + })); + const connections = compareEntities(baseConnections, headConnections, 'connection', CONNECTION_FIELDS, (id, before, after) => ({ + id, + ...(before ? { base: { from: before.from, to: before.to, label: before.label || '' } } : {}), + ...(after ? { head: { from: after.from, to: after.to, label: after.label || '' } } : {}), + })); + const boundaries = compareEntities(baseBoundaries, headBoundaries, 'boundary', BOUNDARY_FIELDS, (_key, before, after) => ({ + key: `${(after || before).kind}:${(after || before).label}`, + kind: (after || before).kind, + label: (after || before).label, + })); + const provenanceChanged = !equal(baseRepository, headRepository); + + return { + schemaVersion: 1, + ok: true, + command: 'compare', + type: 'architecture', + comparatorVersion: COMPARATOR_VERSION, + canonicalVersion: CANONICAL_VERSION, + completeness: 'complete', + proofLevel, + base: { + title: base.meta?.title || '', + ...(evidence.baseRawSha256 ? { rawSha256: evidence.baseRawSha256 } : {}), + ...(evidence.baseSemanticSha256 ? { semanticSha256: evidence.baseSemanticSha256 } : {}), + ...(Number.isInteger(evidence.baseBytes) ? { bytes: evidence.baseBytes } : {}), + ...(baseRepository?.revision ? { revision: baseRepository.revision } : {}), + }, + head: { + title: head.meta?.title || '', + ...(evidence.headRawSha256 ? { rawSha256: evidence.headRawSha256 } : {}), + ...(evidence.headSemanticSha256 ? { semanticSha256: evidence.headSemanticSha256 } : {}), + ...(Number.isInteger(evidence.headBytes) ? { bytes: evidence.headBytes } : {}), + ...(headRepository?.revision ? { revision: headRepository.revision } : {}), + }, + summary: { + components: summaryFor(components, ['added', 'changed', 'evidenceChanged', 'removed', 'moved']), + connections: summaryFor(connections, ['added', 'changed', 'removed', 'rerouted']), + boundaries: summaryFor(boundaries, ['added', 'changed', 'removed', 'geometryChanged']), + presentationChanged: presentationChanged(base, head), + provenanceChanged, + }, + changes: { components, connections, boundaries }, + identity: { + components: 'components[].id', + connections: 'connections[].id (required)', + boundaries: 'boundaries[].kind + boundaries[].label (derived)', + }, + view: { visualPreset: head.meta?.visual_preset || 'classic' }, + limitations: [ + 'Authored Architecture IR only; no runtime impact, causality, risk, or mergeability is inferred.', + 'Boundary identity is conservatively derived from kind + label.', + ], + }; +} + +function esc(value) { + return String(value ?? '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); +} + +const safeJson = (value) => JSON.stringify(value, null, 2).replaceAll('<', '\\u003c').replaceAll('>', '\\u003e').replaceAll('&', '\\u0026'); + +export function extractArchitectureSvg(html) { + const match = html.match(//); + if (!match) fail('delta/svg-missing', 'A validated Architecture artifact did not contain its primary SVG.'); + return match[0]; +} + +export function extractArtifactCss(html) { + const match = html.match(/
  • `; + }).join('\n') : '
  • No authored architecture changes.
  • '; + const baseView = baseHtml + ? `` + : baseSvg; + const headView = headHtml + ? `` + : headSvg; + const html = ` + +${esc(receipt.head.title)} Architecture Delta + +

    ARCHITECTURE DELTA · ${proof}

    See what changed
    before you merge.

    ${esc(receipt.base.title)} → ${esc(receipt.head.title)}

    ${total(receipt.summary, 'added')}ADDED
    ${total(receipt.summary, 'removed')}REMOVED
    ${changed}CHANGED
    +
    + ADD− DEL~ MOD↔ MOVE
    + +
    ${deltaSvg}
    +Exact authored changes · ${rows.length}
      ${rowHtml}
    +
    Stable IDs only · completeness: complete · ${proof}Authored IR only · no risk or mergeability inference
    + +`; + return html.replace(/[ \t]+$/gm, ''); +} + +export function validateArchitectureDeltaHtml(html, receipt) { + const failures = []; + const rows = architectureDeltaChangeRows(receipt); + const deltaMarkup = html.match(/
    ([\s\S]*?)<\/section>/)?.[1] || ''; + const svgTags = [...deltaMarkup.matchAll(/<\/?svg\b[^>]*>/g)]; + let svgDepth = 0; + let svgRoots = 0; + let rootStart = -1; + let rootEnd = -1; + let svgBalanced = true; + for (const match of svgTags) { + if (match[0].startsWith(' (html.match(new RegExp(`
    ]*data-change-key="${safeKey}"[^>]*>`, 'g'))].map((match) => match[0]); + if (rowMatches.length !== 1) failures.push(`expected exactly one change row ${row.key}`); + const targets = reviewTargetTags(deltaMarkup, row); + if (!targets.length) failures.push(`missing Delta identity ${row.key}`); + if (targets.some(({ tag }) => !/\bdata-delta-state="[^"]+"/.test(tag))) failures.push(`missing Delta target state ${row.key}`); + const signature = reviewTargetSignature(targets); + const expectedSignature = expectedReviewTargetSignature(row); + const storedSignature = rowMatches[0]?.match(/\bdata-change-target-signature="([^"]*)"/)?.[1]; + if (!signature || signature !== expectedSignature || storedSignature !== expectedSignature) failures.push(`ambiguous Delta target signature ${row.key}`); + if (rowMatches[0]?.match(/\bdata-change-index="([^"]+)"/)?.[1] !== String(index)) failures.push(`incorrect change row order ${row.key}`); + const primary = primaryReviewTags(deltaMarkup, row); + const states = primary.map((tag) => tag.match(/\bdata-delta-state="([^"]+)"/)?.[1]).filter(Boolean).sort(); + if (JSON.stringify(states) !== JSON.stringify(reviewPrimaryStates(row).sort())) failures.push(`ambiguous Delta identity ${row.key}`); + const classifications = row.classifications.join(','); + if (primary.some((tag) => tag.match(/\bdata-delta-classifications="([^"]*)"/)?.[1] !== classifications)) failures.push(`conflicting Delta classification ${row.key}`); + } + // Before/After embed the complete existing explorer runtime. Validate claims + // made by the Delta shell itself, not implementation vocabulary inside an + // escaped srcdoc script (for example, "safe scale" in image export code). + const deltaShell = html.replace(/]*><\/iframe>/g, ''); + if (/\b(?:SAFE|LOW RISK|MERGEABLE|NO IMPACT|VERIFIED PR)\b/i.test(deltaShell)) failures.push('contains a forbidden risk or mergeability claim'); + if (/\b(?:NaN|Infinity)\b/.test(html)) failures.push('contains non-finite output'); + if (receipt.completeness !== 'complete') failures.push('receipt is not complete'); + if (failures.length) fail('delta/artifact-invalid', `Architecture Delta artifact failed validation: ${failures.join('; ')}.`, { failures }); + return { ok: true, checksPassed: 10, checkCount: 10 }; +} diff --git a/tools/archify/examples/agent-run.lifecycle.json b/tools/archify/examples/agent-run.lifecycle.json new file mode 100644 index 0000000..329208b --- /dev/null +++ b/tools/archify/examples/agent-run.lifecycle.json @@ -0,0 +1,71 @@ +{ + "schema_version": 1, + "diagram_type": "lifecycle", + "meta": { + "title": "Agent Run Lifecycle", + "output": "examples/lifecycle-agent-run.html", + "viewBox": [980, 660], + "animation": "trace", + "quality_profile": "showcase", + "views": [ + { "id": "main-lifecycle", "label": "Main lifecycle", "focus": ["queued", "planning", "executing", "reviewing", "completed"], "note": "Follow the ordered phases from accepted request to completed response." }, + { "id": "human-waits", "label": "Human and input waits", "focus": ["executing", "approval", "reviewing", "blocked"], "note": "See where the run pauses without becoming terminal." }, + { "id": "recovery-and-exits", "label": "Recovery and terminal exits", "focus": ["executing", "failed", "blocked", "cancelled", "expired"], "note": "Separate retryable failure from cancellation and expiry." } + ] + }, + "lanes": [ + { "id": "main", "label": "Lifecycle phases" }, + { "id": "waiting", "label": "Interruptions" }, + { "id": "exceptions", "label": "Recovery loop" }, + { "id": "terminal", "label": "Terminal exits" } + ], + "states": [ + { "id": "queued", "type": "start", "label": "Queued", "sublabel": "request accepted", "lane": "main", "col": 0, "step": "01", "tag": "entry" }, + { "id": "planning", "type": "active", "label": "Planning", "sublabel": "build task graph", "lane": "main", "col": 1, "step": "02", "tag": "model" }, + { "id": "executing", "type": "active", "label": "Executing", "sublabel": "tool calls", "lane": "main", "col": 2, "step": "03", "tag": "work" }, + { "id": "reviewing", "type": "decision", "label": "Reviewing", "sublabel": "quality gate", "lane": "main", "col": 3, "step": "04", "tag": "check" }, + { "id": "completed", "type": "success", "label": "Completed", "sublabel": "final response", "lane": "main", "col": 4, "step": "05", "tag": "done" }, + { "id": "approval", "type": "waiting", "label": "Needs Approval", "sublabel": "human gate", "lane": "waiting", "col": 0, "tag": "pause" }, + { "id": "blocked", "type": "waiting", "label": "Blocked", "sublabel": "missing input", "lane": "waiting", "col": 1, "tag": "wait" }, + { "id": "failed", "type": "failure", "label": "Failed", "sublabel": "recoverable error", "lane": "exceptions", "col": 0, "yOffset": 78, "tag": "retryable" }, + { "id": "cancelled", "type": "failure", "label": "Cancelled", "sublabel": "user stopped", "lane": "terminal", "col": 0, "tag": "terminal" }, + { "id": "expired", "type": "failure", "label": "Expired", "sublabel": "timeout", "lane": "terminal", "col": 1, "tag": "terminal" } + ], + "transitions": [ + { "id": "approval-needed", "from": "executing", "to": "approval", "variant": "security", "fromSide": "bottom", "toSide": "top", "route": "straight" }, + { "id": "review-blocked", "from": "reviewing", "to": "blocked", "variant": "default", "route": "drop" }, + { "id": "execution-failed", "from": "executing", "to": "failed", "variant": "security", "fromSide": "left", "toSide": "left", "via": [[320, 157], [320, 385]] }, + { "id": "failed-retry", "from": "failed", "to": "executing", "variant": "emphasis", "fromSide": "left", "toSide": "top", "via": [[20, 385], [20, 80], [402, 80]] }, + { "id": "block-expired", "from": "blocked", "to": "expired", "variant": "security", "fromSide": "bottom", "toSide": "top", "route": "straight" }, + { "id": "approval-cancelled", "from": "approval", "to": "cancelled", "variant": "security", "fromSide": "bottom", "toSide": "top", "via": [[480, 336], [480, 432], [402, 432]] } + ], + "cards": [ + { + "dot": "emerald", + "title": "Main Path", + "items": [ + "The run has five ordered phases from queue to completion", + "The primary lifecycle is carried by one horizontal rail", + "Completion is a phase, not a detached side box" + ] + }, + { + "dot": "amber", + "title": "Human + Input Gates", + "items": [ + "Approval pauses execution without ending the run", + "Blocked waits for missing user input", + "Wait states remain non-terminal until cancellation or expiry" + ] + }, + { + "dot": "rose", + "title": "Terminal + Recovery", + "items": [ + "Failed loops back while retry budget remains", + "Cancelled and Expired are exits from the lifecycle", + "Terminal exits do not point back into active execution" + ] + } + ] +} diff --git a/tools/archify/examples/agent-tool-call.workflow.json b/tools/archify/examples/agent-tool-call.workflow.json new file mode 100644 index 0000000..c8c3199 --- /dev/null +++ b/tools/archify/examples/agent-tool-call.workflow.json @@ -0,0 +1,94 @@ +{ + "schema_version": 2, + "diagram_type": "workflow", + "meta": { + "title": "Agent Tool Call Workflow", + "animation": "trace", + "visual_preset": "signal-flow", + "quality_profile": "showcase", + "views": [ + { + "id": "happy-path", + "label": "Request to result", + "focus": ["user", "chat", "planner", "router", "approval", "tool", "external", "final"], + "note": "Follow the successful request from user intent to the final reply." + }, + { + "id": "safety-gate", + "label": "Policy and recovery", + "focus": ["router", "approval", "blocked", "retry"], + "note": "See where risky work stops, waits for consent, or returns for revision." + }, + { + "id": "evidence-loop", + "label": "Evidence and memory", + "focus": ["external", "store", "trace"], + "note": "Isolate the durable trace and context path behind the visible answer." + } + ], + "output": "examples/workflow-agent-tool-call-rendered.html" + }, + "lanes": [ + { "id": "ui", "label": "User Interface" }, + { "id": "agent", "label": "Agent Runtime" }, + { "id": "policy", "label": "Policy & Recovery", "variant": "exception" }, + { "id": "tools", "label": "Tool Execution & Evidence" } + ], + "phases": [ + { "id": "intake", "label": "Intake", "fromCol": 0, "toCol": 1 }, + { "id": "reasoning", "label": "Plan + route", "fromCol": 2, "toCol": 3, "variant": "emphasis" }, + { "id": "execution", "label": "Execute + report", "fromCol": 4, "toCol": 5, "variant": "dashed" } + ], + "groups": [ + { "id": "agent_loop", "label": "Planning loop", "lane": "agent", "fromCol": 2, "toCol": 3, "variant": "emphasis" }, + { "id": "exception_path", "label": "Human or policy stop", "lane": "policy", "fromCol": 3, "toCol": 5, "variant": "security" }, + { "id": "evidence_path", "label": "Evidence path", "lane": "tools", "fromCol": 1, "toCol": 2, "variant": "dashed" }, + { "id": "tool_work", "label": "Tool work", "lane": "tools", "fromCol": 4, "toCol": 5, "variant": "dashed" } + ], + "mainPath": ["user", "chat", "planner", "router", "approval", "tool", "external", "final"], + "nodes": [ + { "id": "user", "lane": "ui", "col": 0, "type": "external", "label": "User", "sublabel": "asks for work", "width": 132 }, + { "id": "chat", "lane": "ui", "col": 1, "type": "frontend", "label": "Chat Surface", "sublabel": "thread + files", "width": 132 }, + { "id": "final", "lane": "ui", "col": 5, "type": "backend", "label": "Final Reply", "sublabel": "answer + changes", "width": 132 }, + { "id": "planner", "lane": "agent", "col": 2, "type": "backend", "label": "Agent Planner", "sublabel": "plan next step", "tag": "context aware", "width": 132 }, + { "id": "router", "lane": "agent", "col": 3, "type": "backend", "label": "Tool Router", "sublabel": "choose capability", "width": 132 }, + { "id": "approval", "lane": "policy", "col": 3, "type": "security", "label": "Approval Gate", "sublabel": "scope + consent", "tag": "block risky ops", "width": 132 }, + { "id": "blocked", "lane": "policy", "col": 4, "type": "security", "label": "Blocked", "sublabel": "wait or reject", "width": 132 }, + { "id": "retry", "lane": "policy", "col": 5, "type": "messagebus", "label": "Retry Path", "sublabel": "revise request", "width": 132 }, + { "id": "tool", "lane": "tools", "col": 4, "type": "messagebus", "label": "Tool Call", "sublabel": "shell / browser / MCP", "tag": "structured result", "width": 132 }, + { "id": "external", "lane": "tools", "col": 5, "type": "cloud", "label": "External API", "sublabel": "network service", "width": 132 }, + { "id": "store", "lane": "tools", "col": 1, "type": "database", "label": "Context Store", "sublabel": "repo + memory", "width": 132 }, + { "id": "trace", "lane": "tools", "col": 2, "type": "database", "label": "Trace Log", "sublabel": "events + output", "width": 132 } + ], + "edges": [ + { "id": "request-chat", "from": "user", "to": "chat", "variant": "default" }, + { "id": "plan-request", "from": "chat", "to": "planner", "label": "plan", "variant": "emphasis" }, + { "id": "planner-route", "from": "planner", "to": "router", "variant": "default" }, + { "id": "approval-check", "from": "router", "to": "approval", "label": "needs approval?", "variant": "security" }, + { "id": "approved-tool", "from": "approval", "to": "tool", "variant": "emphasis" }, + { "id": "approval-denied", "from": "approval", "to": "blocked", "label": "denied", "variant": "security", "role": "error" }, + { "id": "retry-request", "from": "blocked", "to": "retry", "variant": "dashed", "role": "branch" }, + { "id": "tool-external-call", "from": "tool", "to": "external", "variant": "default" }, + { "id": "external-reply", "from": "external", "to": "final", "variant": "emphasis", "role": "return", "fromSide": "right", "toSide": "right", "route": "outside-right", "width": 1.2 }, + { "id": "record-result", "from": "external", "to": "trace", "label": "record result", "variant": "dashed", "fromSide": "bottom", "toSide": "bottom", "route": "bottom-channel", "labelSegment": 1 }, + { "id": "write-trace-memory", "from": "store", "to": "trace", "label": "trace + memory", "variant": "dashed" } + ], + "cards": [ + { + "dot": "cyan", + "title": "Compiler Contract", + "items": [ + "Lanes and columns determine node placement", + "Labels reserve clearance; routes stay orthogonal" + ] + }, + { + "dot": "rose", + "title": "Runtime Semantics", + "items": [ + "Approval gates risky work before tool execution", + "Evidence returns through isolated trace and memory" + ] + } + ] +} diff --git a/tools/archify/examples/async-job-roundtrip.sequence.json b/tools/archify/examples/async-job-roundtrip.sequence.json new file mode 100644 index 0000000..3511049 --- /dev/null +++ b/tools/archify/examples/async-job-roundtrip.sequence.json @@ -0,0 +1,61 @@ +{ + "schema_version": 1, + "diagram_type": "sequence", + "meta": { + "title": "Async Job Roundtrip", + "output": "examples/async-job-roundtrip.html", + "viewBox": [820, 920], + "animation": "trace", + "visual_preset": "signal-flow", + "quality_profile": "showcase", + "views": [ + { "id": "accept-and-enqueue", "label": "Accept without blocking", "focus": ["client", "api", "queue"], "note": "The API acknowledges quickly after durable enqueue." }, + { "id": "work-and-retry", "label": "Background work and retry", "focus": ["queue", "worker", "provider"], "note": "Timeouts re-enter the queue instead of holding the original request open." }, + { "id": "observe-final-state", "label": "Observe final consistency", "focus": ["worker", "store", "notify", "client", "api"], "note": "Webhook delivery is primary; polling remains a bounded fallback." } + ] + }, + "participants": [ + { "id": "client", "type": "external", "label": "Client", "sublabel": "mobile app" }, + { "id": "api", "type": "backend", "label": "Jobs API", "sublabel": "request edge" }, + { "id": "queue", "type": "messagebus", "label": "Queue", "sublabel": "durable work" }, + { "id": "worker", "type": "backend", "label": "Worker", "sublabel": "background" }, + { "id": "provider", "type": "cloud", "label": "Provider", "sublabel": "external API" }, + { "id": "store", "type": "database", "label": "Job Store", "sublabel": "source of truth" }, + { "id": "notify", "type": "messagebus", "label": "Notifier", "sublabel": "webhook" } + ], + "segments": [ + { "from": 150, "to": 288, "label": "Accept" }, + { "from": 306, "to": 538, "label": "Background work" }, + { "from": 554, "to": 800, "label": "Notify + reconcile" } + ], + "messages": [ + { "from": "client", "to": "api", "y": 180, "label": "POST /jobs", "variant": "emphasis" }, + { "from": "api", "to": "queue", "y": 222, "label": "enqueue job", "variant": "emphasis" }, + { "from": "api", "to": "client", "y": 264, "label": "202 + job id", "variant": "return" }, + { "from": "queue", "to": "worker", "y": 326, "label": "deliver", "variant": "emphasis" }, + { "from": "worker", "to": "provider", "y": 368, "label": "perform work", "variant": "default" }, + { "from": "provider", "to": "worker", "y": 410, "label": "result / timeout", "variant": "return" }, + { "from": "worker", "to": "queue", "y": 452, "label": "retry if timeout", "variant": "dashed" }, + { "from": "worker", "to": "store", "y": 494, "label": "persist final state", "variant": "emphasis" }, + { "from": "worker", "to": "notify", "y": 566, "label": "job.completed", "variant": "dashed" }, + { "from": "notify", "to": "client", "y": 608, "label": "signed webhook", "variant": "dashed" }, + { "from": "client", "to": "api", "y": 650, "label": "GET /jobs/:id", "variant": "default" }, + { "from": "api", "to": "store", "y": 692, "label": "read status", "variant": "default" }, + { "from": "store", "to": "api", "y": 734, "label": "completed", "variant": "return" }, + { "from": "api", "to": "client", "y": 776, "label": "200 final result", "variant": "return" } + ], + "activations": [ + { "participant": "api", "from": 174, "to": 272, "type": "backend" }, + { "participant": "queue", "from": 216, "to": 334, "type": "messagebus" }, + { "participant": "worker", "from": 320, "to": 574, "type": "backend" }, + { "participant": "provider", "from": 362, "to": 416, "type": "cloud" }, + { "participant": "store", "from": 488, "to": 742, "type": "database" }, + { "participant": "notify", "from": 560, "to": 616, "type": "messagebus" }, + { "participant": "api", "from": 644, "to": 784, "type": "backend" } + ], + "cards": [ + { "dot": "cyan", "title": "Fast Acknowledgement", "items": ["The caller receives a durable job id before work begins", "Queue ownership is visible in the acceptance contract", "The original connection does not wait for provider latency"] }, + { "dot": "orange", "title": "Bounded Recovery", "items": ["Timeouts re-enter the queue with a retry policy", "Final state is persisted before notification", "The job store remains the source of truth"] }, + { "dot": "emerald", "title": "Two Observation Paths", "items": ["A signed webhook announces completion", "Status polling is a fallback, not a second workflow", "Both paths converge on the same final state"] } + ] +} diff --git a/tools/archify/examples/brand-aware-delivery.architecture.json b/tools/archify/examples/brand-aware-delivery.architecture.json new file mode 100644 index 0000000..1ccc9c0 --- /dev/null +++ b/tools/archify/examples/brand-aware-delivery.architecture.json @@ -0,0 +1,47 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Brand-aware AI delivery", + "quality_profile": "showcase", + "viewBox": [1120, 640], + "views": [ + { + "id": "delivery-path", + "label": "Delivery path", + "focus": ["request", "claude", "github", "container", "edge", "customer"], + "note": "Follow one delivery from the request through the model, repository, container, and edge." + }, + { + "id": "business-data", + "label": "Business and data", + "focus": ["container", "database", "billing"], + "note": "Inspect durable state and billing without losing the main delivery path." + } + ] + }, + "components": [ + { "id": "request", "type": "external", "label": "Product request", "sublabel": "Owner brief", "pos": [38, 260], "size": [138, 68] }, + { "id": "claude", "type": "frontend", "label": "Claude", "sublabel": "Plan and author", "brand": "claude", "pos": [220, 260], "size": [138, 68] }, + { "id": "github", "type": "messagebus", "label": "GitHub", "sublabel": "Review and merge", "brand": "github", "pos": [402, 260], "size": [138, 68] }, + { "id": "container", "type": "backend", "label": "Docker service", "sublabel": "Build and run", "brand": "docker", "pos": [584, 260], "size": [138, 68] }, + { "id": "edge", "type": "cloud", "label": "Cloudflare", "sublabel": "Global delivery", "brand": "cloudflare", "pos": [766, 260], "size": [138, 68] }, + { "id": "customer", "type": "external", "label": "Customers", "sublabel": "Web and mobile", "pos": [948, 260], "size": [138, 68] }, + { "id": "database", "type": "database", "label": "PostgreSQL", "sublabel": "Durable state", "brand": "postgresql", "pos": [584, 420], "size": [138, 68] }, + { "id": "billing", "type": "external", "label": "Stripe", "sublabel": "Billing events", "brand": "stripe", "pos": [766, 420], "size": [138, 68] } + ], + "connections": [ + { "id": "brief-to-claude", "from": "request", "to": "claude", "label": "brief", "variant": "emphasis" }, + { "id": "claude-to-github", "from": "claude", "to": "github", "label": "change set", "labelDy": -28 }, + { "id": "github-to-container", "from": "github", "to": "container", "label": "approved build", "labelDy": -28 }, + { "id": "container-to-edge", "from": "container", "to": "edge", "label": "deploy" }, + { "id": "edge-to-customer", "from": "edge", "to": "customer", "label": "HTTPS", "variant": "emphasis" }, + { "id": "container-to-database", "from": "container", "to": "database", "label": "SQL", "fromSide": "bottom", "toSide": "top", "labelAt": [625, 370] }, + { "id": "container-to-billing", "from": "container", "to": "billing", "label": "create charge", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "via": [[653, 374], [835, 374]] }, + { "id": "billing-to-database", "from": "billing", "to": "database", "label": "webhook", "variant": "dashed" } + ], + "cards": [ + { "dot": "cyan", "title": "Identity at a glance", "items": ["Semantic color still explains technical role", "Brand badges identify the actual products"] }, + { "dot": "amber", "title": "Portable by default", "items": ["Preset marks ship inside Archify", "Every visual export keeps the same badge"] } + ] +} diff --git a/tools/archify/examples/cache-miss-request.sequence.json b/tools/archify/examples/cache-miss-request.sequence.json new file mode 100644 index 0000000..3a5d502 --- /dev/null +++ b/tools/archify/examples/cache-miss-request.sequence.json @@ -0,0 +1,81 @@ +{ + "schema_version": 1, + "diagram_type": "sequence", + "meta": { + "title": "Cache Miss Request Sequence", + "output": "examples/sequence-cache-miss-request.html", + "viewBox": [820, 760], + "animation": "trace", + "quality_profile": "showcase", + "views": [ + { "id": "request-and-auth", "label": "Request and identity", "focus": ["user", "web", "api", "auth"], "note": "Follow the user request through the authentication check." }, + { "id": "cache-fallback", "label": "Cache fallback", "focus": ["api", "redis", "db"], "note": "See the cache miss and the source-of-truth query it triggers." }, + { "id": "return-and-trace", "label": "Return and trace", "focus": ["db", "api", "redis", "trace", "web", "user"], "note": "Separate response latency from the non-blocking observability write." } + ] + }, + "participants": [ + { "id": "user", "type": "external", "label": "User", "sublabel": "browser session" }, + { "id": "web", "type": "frontend", "label": "Web App", "sublabel": "React UI" }, + { "id": "api", "type": "backend", "label": "API", "sublabel": "request handler" }, + { "id": "auth", "type": "security", "label": "Auth", "sublabel": "JWT verify" }, + { "id": "redis", "type": "database", "label": "Redis", "sublabel": "cache" }, + { "id": "db", "type": "database", "label": "Postgres", "sublabel": "source of truth" }, + { "id": "trace", "type": "messagebus", "label": "Trace", "sublabel": "async event" } + ], + "segments": [ + { "from": 150, "to": 295, "label": "Request" }, + { "from": 315, "to": 505, "label": "Fallback" }, + { "from": 525, "to": 665, "label": "Response + trace" } + ], + "messages": [ + { "id": "open-page", "from": "user", "to": "web", "y": 185, "label": "open page", "variant": "default" }, + { "id": "dashboard-request", "from": "web", "to": "api", "y": 228, "label": "GET /dashboard", "variant": "emphasis" }, + { "id": "verify-jwt", "from": "api", "to": "auth", "y": 270, "label": "verify JWT", "variant": "security" }, + { "id": "auth-claims", "from": "auth", "to": "api", "y": 305, "label": "claims ok", "variant": "return" }, + { "id": "cache-read", "from": "api", "to": "redis", "y": 354, "label": "read cache", "variant": "default" }, + { "id": "cache-miss", "from": "redis", "to": "api", "y": 391, "label": "miss", "variant": "return" }, + { "id": "profile-query", "from": "api", "to": "db", "y": 443, "label": "query profile + metrics", "variant": "emphasis" }, + { "id": "profile-rows", "from": "db", "to": "api", "y": 489, "label": "rows", "variant": "return" }, + { "id": "cache-write", "from": "api", "to": "redis", "y": 536, "label": "set cache", "variant": "dashed" }, + { "id": "trace-emit", "from": "api", "to": "trace", "y": 580, "label": "emit trace", "variant": "dashed" }, + { "id": "dashboard-response", "from": "api", "to": "web", "y": 625, "label": "200 JSON", "variant": "return" }, + { "id": "page-render", "from": "web", "to": "user", "y": 662, "label": "render", "variant": "return" } + ], + "activations": [ + { "participant": "web", "from": 220, "to": 668, "type": "frontend" }, + { "participant": "api", "from": 228, "to": 632, "type": "backend" }, + { "participant": "auth", "from": 265, "to": 310, "type": "security" }, + { "participant": "redis", "from": 349, "to": 398, "type": "database" }, + { "participant": "db", "from": 438, "to": 496, "type": "database" }, + { "participant": "trace", "from": 575, "to": 630, "type": "messagebus" } + ], + "cards": [ + { + "dot": "emerald", + "title": "Happy Path", + "items": [ + "The main request is Web App -> API -> data source -> response", + "Return messages are quieter than forward calls", + "Activation bars make ownership duration visible" + ] + }, + { + "dot": "rose", + "title": "Policy + Fallback", + "items": [ + "JWT verification is colored as a security interaction", + "Cache miss is visible without overpowering the main path", + "Database access only appears after cache fallback" + ] + }, + { + "dot": "orange", + "title": "Async Trace", + "items": [ + "Trace emission is dashed and secondary", + "It does not block the response path", + "The diagram separates user-facing latency from observability" + ] + } + ] +} diff --git a/tools/archify/examples/checkout-platform.base.architecture.json b/tools/archify/examples/checkout-platform.base.architecture.json new file mode 100644 index 0000000..6a0697c --- /dev/null +++ b/tools/archify/examples/checkout-platform.base.architecture.json @@ -0,0 +1,31 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Checkout Platform — Baseline", + "visual_preset": "signal-flow" + }, + "components": [ + { "id": "buyers", "type": "external", "label": "Buyers", "sublabel": "Web + mobile", "pos": [40, 250], "size": [120, 60] }, + { "id": "edge", "type": "cloud", "label": "Edge Gateway", "sublabel": "TLS + routing", "pos": [220, 250], "size": [130, 60] }, + { "id": "checkout", "type": "backend", "label": "Checkout API", "sublabel": "v1 service", "pos": [430, 250], "size": [130, 60] }, + { "id": "cache", "type": "database", "label": "Session Cache", "sublabel": "Redis", "pos": [430, 100], "size": [130, 60] }, + { "id": "orders", "type": "database", "label": "Orders", "sublabel": "PostgreSQL", "pos": [640, 250], "size": [130, 60] }, + { "id": "queue", "type": "messagebus", "label": "Order Events", "sublabel": "durable queue", "pos": [430, 400], "size": [130, 60] }, + { "id": "worker", "type": "backend", "label": "Fulfilment", "sublabel": "async worker", "pos": [640, 400], "size": [130, 60] }, + { "id": "payments", "type": "external", "label": "Payment Rail", "sublabel": "external", "pos": [850, 250], "size": [130, 60] } + ], + "boundaries": [ + { "kind": "region", "label": "Production region", "wraps": ["edge", "checkout", "cache", "orders", "queue", "worker"] }, + { "kind": "security-group", "label": "Checkout trust zone", "wraps": ["checkout", "orders"] } + ], + "connections": [ + { "id": "buyer-request", "from": "buyers", "to": "edge", "label": "HTTPS", "variant": "emphasis" }, + { "id": "edge-checkout", "from": "edge", "to": "checkout" }, + { "id": "session-read", "from": "checkout", "to": "cache", "label": "session", "fromSide": "top", "toSide": "bottom", "labelDy": -66 }, + { "id": "persist-order", "from": "checkout", "to": "orders", "label": "SQL" }, + { "id": "publish-order", "from": "checkout", "to": "queue", "label": "accepted", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 66 }, + { "id": "consume-order", "from": "queue", "to": "worker" }, + { "id": "authorize-payment", "from": "orders", "to": "payments", "label": "authorize", "variant": "security" } + ] +} diff --git a/tools/archify/examples/checkout-platform.head.architecture.json b/tools/archify/examples/checkout-platform.head.architecture.json new file mode 100644 index 0000000..224d62b --- /dev/null +++ b/tools/archify/examples/checkout-platform.head.architecture.json @@ -0,0 +1,31 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Checkout Platform — Fraud Gate", + "visual_preset": "signal-flow" + }, + "components": [ + { "id": "buyers", "type": "external", "label": "Buyers", "sublabel": "Web + mobile", "pos": [40, 250], "size": [120, 60] }, + { "id": "edge", "type": "cloud", "label": "Edge Gateway", "sublabel": "TLS + routing", "pos": [220, 250], "size": [130, 60] }, + { "id": "checkout", "type": "backend", "label": "Checkout API", "sublabel": "v2 idempotent", "pos": [430, 250], "size": [130, 60] }, + { "id": "fraud", "type": "security", "label": "Fraud Gate", "sublabel": "policy scoring", "pos": [640, 100], "size": [130, 60], "tag": "new owner" }, + { "id": "orders", "type": "database", "label": "Orders", "sublabel": "PostgreSQL", "pos": [640, 250], "size": [130, 60] }, + { "id": "queue", "type": "messagebus", "label": "Order Events", "sublabel": "durable queue", "pos": [430, 420], "size": [130, 60] }, + { "id": "worker", "type": "backend", "label": "Fulfilment", "sublabel": "async worker", "pos": [640, 400], "size": [130, 60] }, + { "id": "payments", "type": "external", "label": "Payment Rail", "sublabel": "external", "pos": [850, 250], "size": [130, 60] } + ], + "boundaries": [ + { "kind": "region", "label": "Production region", "wraps": ["edge", "checkout", "fraud", "orders", "queue", "worker"] }, + { "kind": "security-group", "label": "Checkout trust zone", "wraps": ["checkout", "fraud", "orders"] } + ], + "connections": [ + { "id": "buyer-request", "from": "buyers", "to": "edge", "label": "HTTPS", "variant": "emphasis" }, + { "id": "edge-checkout", "from": "edge", "to": "checkout" }, + { "id": "fraud-check", "from": "checkout", "to": "fraud", "label": "screen", "variant": "security", "fromSide": "top", "toSide": "bottom", "labelDy": -66 }, + { "id": "persist-order", "from": "checkout", "to": "orders", "label": "SQL tx" }, + { "id": "publish-order", "from": "checkout", "to": "queue", "label": "accepted", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 76 }, + { "id": "consume-order", "from": "queue", "to": "worker" }, + { "id": "authorize-payment", "from": "fraud", "to": "payments", "label": "authorize", "variant": "security", "fromSide": "right", "toSide": "top", "via": [[820, 130], [915, 130]] } + ] +} diff --git a/tools/archify/examples/dataflow-product-analytics.html b/tools/archify/examples/dataflow-product-analytics.html new file mode 100644 index 0000000..e56dd1b --- /dev/null +++ b/tools/archify/examples/dataflow-product-analytics.html @@ -0,0 +1,14898 @@ + + + + + + + Product Analytics Data Flow Diagram + + + + + + + + + + + +
    + +
    +
    +
    +

    Product Analytics Data Flow

    +
    +
    + + + + + + + +
    + + Product Analytics Data Flow + A data-flow diagram generated by Archify. + + + + + + + + + + + + + + + + + + + + + + + + + 01 / Sources + + + 02 / Ingest + + + 03 / Process + + + 04 / Store + + + 05 / Consume + + + + + + + + + + + + + + + + Web App · browser SDK · 01 / Sources · events + + + + Web App + browser SDK + events + + + + Mobile · iOS / Android · 01 / Sources · events + + + + Mobile + iOS / Android + events + + + + Edge API · collector · 02 / Ingest · TLS + + + + Edge API + collector + TLS + + + + Consent Gate · policy filter · 03 / Process · PII guard + + + + Consent Gate + policy filter + PII guard + + + + Event Stream · Kafka topic · 03 / Process · ordered + + + + Event Stream + Kafka topic + ordered + + + + PII Vault · encrypted · 04 / Store · restricted + + + + PII Vault + encrypted + restricted + + + + Warehouse · analytics tables · 04 / Store · curated + + + + Warehouse + analytics tables + curated + + + + Feature Store · daily batch · 04 / Store · derived + + + + Feature Store + daily batch + derived + + + + Dashboards · product metrics · 05 / Consume · SQL + + + + Dashboards + product metrics + SQL + + + + ML Model · ranking job · 05 / Consume · features + + + + ML Model + ranking job + features + + + + + + clickstream + user events + + + + app events + device events + + + + identity + consent + PII touch + + + + accepted events + append-only + + + + identity map + encrypted PII + + + + normalized facts + non-PII + + + + daily aggregates + batch + + + + metrics SQL + read-only + + + + feature vectors + derived + + + + restricted join + approved only + + + + + Legend + + + primary data + + + + policy / PII + + + + async batch + + + + data store + + + + data flow + + + +

    + + + + + + + + + +
    + + +
    +
    +
    +
    +

    Primary Data Path

    +
    +
      +
    • • Events move left to right through source, ingest, process, store, and consume stages
    • +
    • • The hot path stays visually clear even with secondary batch flows
    • +
    • • Labels name data assets instead of generic API verbs
    • +
    +
    + +
    +
    +
    +

    Sensitive Boundary

    +
    +
      +
    • • Consent and PII paths are styled as security flows
    • +
    • • PII lands in a restricted vault, separate from the analytics warehouse
    • +
    • • Restricted joins are visible without implying default access
    • +
    +
    + +
    +
    +
    +

    Derived Consumers

    +
    +
      +
    • • Dashboards read curated facts from the warehouse
    • +
    • • Feature vectors are derived by batch from analytics tables
    • +
    • • Consumption paths stay distinct from collection and consent handling
    • +
    +
    +
    + +
    + + + + diff --git a/tools/archify/examples/deployment-release.lifecycle.json b/tools/archify/examples/deployment-release.lifecycle.json new file mode 100644 index 0000000..ba87dc2 --- /dev/null +++ b/tools/archify/examples/deployment-release.lifecycle.json @@ -0,0 +1,49 @@ +{ + "schema_version": 1, + "diagram_type": "lifecycle", + "meta": { + "title": "Deployment Release Lifecycle", + "output": "examples/deployment-release.html", + "viewBox": [980, 680], + "animation": "trace", + "visual_preset": "signal-flow", + "quality_profile": "showcase", + "views": [ + { "id": "promotion-rail", "label": "Promotion rail", "focus": ["queued", "building", "verifying", "ready", "live"], "note": "Follow the deployment object from accepted change to healthy production." }, + { "id": "approval-gate", "label": "Approval gate", "focus": ["verifying", "approval", "cancelled", "ready"], "note": "Approval pauses promotion and can terminate the release cleanly." }, + { "id": "rollback-outcomes", "label": "Rollback outcomes", "focus": ["ready", "rollback", "failed", "live", "paused", "rolled_back"], "note": "Separate pre-promotion failure from post-promotion health regression." } + ] + }, + "lanes": [ + { "id": "main", "label": "Release phases" }, + { "id": "waiting", "label": "Approval + health wait" }, + { "id": "recovery", "label": "Rollback controller" }, + { "id": "terminal", "label": "Terminal exits" } + ], + "states": [ + { "id": "queued", "type": "start", "label": "Queued", "sublabel": "change accepted", "lane": "main", "col": 0, "step": "01", "tag": "pending" }, + { "id": "building", "type": "active", "label": "Building", "sublabel": "immutable image", "lane": "main", "col": 1, "step": "02", "tag": "running" }, + { "id": "verifying", "type": "decision", "label": "Verifying", "sublabel": "tests + policy", "lane": "main", "col": 2, "step": "03", "tag": "gate" }, + { "id": "ready", "type": "waiting", "label": "Ready", "sublabel": "promotion pending", "lane": "main", "col": 3, "step": "04", "tag": "approved" }, + { "id": "live", "type": "success", "label": "Live", "sublabel": "production healthy", "lane": "main", "col": 4, "step": "05", "tag": "success" }, + { "id": "approval", "type": "waiting", "label": "Needs Approval", "sublabel": "release owner", "lane": "waiting", "col": 0, "tag": "pause" }, + { "id": "rollback", "type": "active", "label": "Rolling Back", "sublabel": "last good image", "lane": "recovery", "col": 1, "tag": "automatic" }, + { "id": "paused", "type": "waiting", "label": "Health Paused", "sublabel": "SLO regression", "lane": "waiting", "col": 2, "tag": "observe" }, + { "id": "cancelled", "type": "failure", "label": "Cancelled", "sublabel": "approval denied", "lane": "terminal", "col": 0, "tag": "terminal" }, + { "id": "failed", "type": "failure", "label": "Failed", "sublabel": "rollback failed", "lane": "terminal", "col": 1, "tag": "terminal" }, + { "id": "rolled_back", "type": "success", "label": "Rolled Back", "sublabel": "service restored", "lane": "terminal", "col": 2, "tag": "terminal" } + ], + "transitions": [ + { "from": "verifying", "to": "approval", "variant": "security", "route": "straight", "fromSide": "bottom", "toSide": "top" }, + { "from": "approval", "to": "cancelled", "variant": "security", "route": "straight", "fromSide": "bottom", "toSide": "top" }, + { "from": "ready", "to": "rollback", "variant": "security", "route": "straight", "fromSide": "bottom", "toSide": "top" }, + { "from": "rollback", "to": "failed", "variant": "security", "route": "straight", "fromSide": "bottom", "toSide": "top" }, + { "from": "live", "to": "paused", "variant": "dashed", "route": "straight", "fromSide": "bottom", "toSide": "top" }, + { "from": "paused", "to": "rolled_back", "variant": "emphasis", "route": "straight", "fromSide": "bottom", "toSide": "top" } + ], + "cards": [ + { "dot": "cyan", "title": "Promotion Rail", "items": ["The release object moves through five ordered phases", "Verification and approval remain distinct states", "Live means production health is currently proven"] }, + { "dot": "amber", "title": "Wait States", "items": ["Human approval can pause without consuming a worker", "A health regression pauses further rollout", "Every wait exposes the event required to continue"] }, + { "dot": "rose", "title": "Explicit Endings", "items": ["Denied approval ends as Cancelled", "Rollback controller failure ends as Failed", "Successful rollback is a terminal restored outcome"] } + ] +} diff --git a/tools/archify/examples/event-stream.dataflow.json b/tools/archify/examples/event-stream.dataflow.json new file mode 100644 index 0000000..e7cc500 --- /dev/null +++ b/tools/archify/examples/event-stream.dataflow.json @@ -0,0 +1,57 @@ +{ + "schema_version": 1, + "diagram_type": "dataflow", + "meta": { + "title": "Order Event-stream Topology", + "output": "examples/event-stream.html", + "viewBox": [1080, 780], + "animation": "trace", + "visual_preset": "signal-flow", + "quality_profile": "showcase", + "views": [ + { "id": "order-transit", "label": "Order event transit", "focus": ["checkout", "orders", "validate", "state", "fulfillment"], "note": "Follow an order from producer through ordered processing to fulfillment." }, + { "id": "payment-transit", "label": "Payment event transit", "focus": ["billing", "payments", "enrich", "state", "analytics"], "note": "Track payment facts into the shared materialized state and analytics." }, + { "id": "failure-and-replay", "label": "Failure and replay", "focus": ["validate", "enrich", "dlq", "replay", "ops"], "note": "Isolate dead letters, operator review, and controlled replay ownership." } + ] + }, + "stages": [ + { "label": "Producers" }, + { "label": "Transit" }, + { "label": "Processors" }, + { "label": "State + recovery" }, + { "label": "Consumers" } + ], + "nodes": [ + { "id": "checkout", "type": "frontend", "label": "Checkout API", "sublabel": "order producer", "stage": 0, "row": 0, "tag": "team commerce" }, + { "id": "billing", "type": "backend", "label": "Billing API", "sublabel": "payment producer", "stage": 0, "row": 2, "tag": "team money" }, + { "id": "orders", "type": "messagebus", "label": "orders.v1", "sublabel": "12 partitions", "stage": 1, "row": 0, "tag": "key: order_id" }, + { "id": "payments", "type": "messagebus", "label": "payments.v2", "sublabel": "8 partitions", "stage": 1, "row": 2, "tag": "key: order_id" }, + { "id": "validate", "type": "backend", "label": "Order Validate", "sublabel": "group fulfillment", "stage": 2, "row": 0, "tag": "ordered" }, + { "id": "enrich", "type": "backend", "label": "Payment Enrich", "sublabel": "group analytics", "stage": 2, "row": 2, "tag": "at-least-once" }, + { "id": "state", "type": "database", "label": "Order State", "sublabel": "materialized view", "stage": 3, "row": 1, "tag": "idempotent" }, + { "id": "dlq", "type": "messagebus", "label": "events.dlq", "sublabel": "poison events", "stage": 3, "row": 4, "tag": "7-day retention" }, + { "id": "fulfillment", "type": "backend", "label": "Fulfillment", "sublabel": "shipping workflow", "stage": 4, "row": 0, "tag": "consumer" }, + { "id": "analytics", "type": "database", "label": "Analytics", "sublabel": "streaming facts", "stage": 4, "row": 2, "tag": "consumer" }, + { "id": "replay", "type": "security", "label": "Replay Tool", "sublabel": "approved batch", "stage": 4, "row": 4, "tag": "operator gate" }, + { "id": "ops", "type": "external", "label": "On-call", "sublabel": "DLQ owner", "stage": 4, "row": 3, "yOffset": -18, "tag": "SRE" } + ], + "flows": [ + { "from": "checkout", "to": "orders", "label": "OrderPlaced", "classification": "schema v1", "variant": "emphasis", "route": "straight" }, + { "from": "billing", "to": "payments", "label": "PaymentCaptured", "classification": "schema v2", "variant": "emphasis", "route": "straight" }, + { "from": "orders", "to": "validate", "label": "ordered orders", "classification": "consumer group", "variant": "emphasis", "route": "straight" }, + { "from": "payments", "to": "enrich", "label": "payment facts", "classification": "at-least-once", "variant": "emphasis", "route": "straight" }, + { "from": "validate", "to": "state", "label": "valid order", "classification": "idempotent", "variant": "emphasis", "route": "vertical-channel" }, + { "from": "enrich", "to": "state", "label": "enriched payment", "classification": "idempotent", "variant": "default", "route": "vertical-channel" }, + { "from": "state", "to": "fulfillment", "label": "ready orders", "classification": "read model", "variant": "emphasis", "route": "vertical-channel" }, + { "from": "state", "to": "analytics", "label": "order facts", "classification": "non-PII", "variant": "default", "route": "vertical-channel" }, + { "from": "validate", "to": "dlq", "label": "invalid event", "classification": "dead letter", "variant": "security", "fromSide": "top", "toSide": "top", "via": [[530, 80], [20, 80], [20, 550], [745, 550]], "labelAt": [300, 550] }, + { "from": "enrich", "to": "dlq", "label": "poison event", "classification": "dead letter", "variant": "security", "route": "bottom-channel", "labelDy": 30 }, + { "from": "dlq", "to": "ops", "label": "failure sample", "classification": "restricted", "variant": "security", "route": "vertical-channel" }, + { "from": "dlq", "to": "replay", "label": "approved replay", "classification": "audited batch", "variant": "dashed", "route": "straight", "labelDy": 30 } + ], + "cards": [ + { "dot": "amber", "title": "Transit Contract", "items": ["Every event and topic is named", "Partition keys preserve per-order ordering", "Consumer groups expose processing ownership"] }, + { "dot": "emerald", "title": "State + Delivery", "items": ["Processors write an idempotent materialized view", "Fulfillment and analytics consume distinct assets", "At-least-once delivery never implies duplicate business effects"] }, + { "dot": "rose", "title": "Failure Ownership", "items": ["Poison events land in a retained dead-letter topic", "On-call inspects samples before replay", "Replay is gated, batched, and auditable"] } + ] +} diff --git a/tools/archify/examples/incident-response.workflow.json b/tools/archify/examples/incident-response.workflow.json new file mode 100644 index 0000000..7446e55 --- /dev/null +++ b/tools/archify/examples/incident-response.workflow.json @@ -0,0 +1,64 @@ +{ + "schema_version": 1, + "diagram_type": "workflow", + "meta": { + "title": "Incident Response Runbook", + "output": "examples/incident-response.html", + "animation": "trace", + "visual_preset": "signal-flow", + "quality_profile": "showcase", + "views": [ + { "id": "detect-and-triage", "label": "Detect and establish command", "focus": ["alert", "page", "triage", "declare"], "note": "Follow the first minutes from signal to an owned incident." }, + { "id": "mitigate-and-verify", "label": "Mitigate and prove recovery", "focus": ["triage", "contain", "recover", "verify", "close"], "note": "Keep mitigation separate from the evidence required to close." }, + { "id": "escalate-and-communicate", "label": "Escalation and communication", "focus": ["declare", "escalate", "update", "rollback"], "note": "See who is paged, what stakeholders hear, and when rollback begins." } + ] + }, + "lanes": [ + { "id": "signals", "label": "Signals" }, + { "id": "responders", "label": "Incident Command" }, + { "id": "mitigation", "label": "Service Mitigation" }, + { "id": "recovery", "label": "Recovery Evidence" }, + { "id": "communication", "label": "Stakeholder Communication" }, + { "id": "exceptions", "label": "Escalation + Rollback", "variant": "exception" } + ], + "phases": [ + { "id": "detect", "label": "Detect", "fromCol": 0, "toCol": 1 }, + { "id": "respond", "label": "Triage + mitigate", "fromCol": 2, "toCol": 3, "variant": "emphasis" }, + { "id": "recover", "label": "Verify + close", "fromCol": 4, "toCol": 5, "variant": "dashed" } + ], + "groups": [ + { "id": "command", "label": "Incident command", "lane": "responders", "fromCol": 1, "toCol": 3, "variant": "emphasis" }, + { "id": "exception_actions", "label": "If impact persists", "lane": "exceptions", "fromCol": 3, "toCol": 5, "variant": "security" } + ], + "mainPath": ["alert", "page", "triage", "contain", "recover", "verify", "close"], + "nodes": [ + { "id": "alert", "lane": "signals", "col": 0, "type": "messagebus", "label": "SLO Alert", "sublabel": "burn rate" }, + { "id": "page", "lane": "responders", "col": 1, "width": 76, "type": "external", "label": "Page On-call", "sublabel": "acknowledge" }, + { "id": "triage", "lane": "responders", "col": 2, "width": 64, "type": "backend", "label": "Triage", "sublabel": "scope impact" }, + { "id": "declare", "lane": "responders", "col": 3, "type": "security", "label": "Declare", "sublabel": "assign commander", "tag": "SEV-1/2" }, + { "id": "contain", "lane": "mitigation", "col": 3, "width": 72, "type": "backend", "label": "Contain", "sublabel": "stop growth" }, + { "id": "recover", "lane": "mitigation", "col": 4, "width": 52, "type": "cloud", "label": "Recover", "sublabel": "restore" }, + { "id": "verify", "lane": "recovery", "col": 5, "type": "database", "label": "Verify", "sublabel": "SLO + traces", "tag": "15 min stable" }, + { "id": "close", "lane": "communication", "col": 5, "type": "external", "label": "Resolve", "sublabel": "final update" }, + { "id": "update", "lane": "communication", "col": 3, "type": "frontend", "label": "Status Update", "sublabel": "impact + ETA" }, + { "id": "escalate", "lane": "exceptions", "col": 3, "width": 72, "type": "security", "label": "Escalate", "sublabel": "specialist" }, + { "id": "rollback", "lane": "exceptions", "col": 5, "type": "messagebus", "label": "Rollback", "sublabel": "last good" } + ], + "edges": [ + { "from": "alert", "to": "page", "label": "page", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "page", "to": "triage", "route": "bottom-channel", "fromSide": "bottom", "toSide": "bottom" }, + { "from": "triage", "to": "contain", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "contain", "to": "recover", "route": "bottom-channel", "fromSide": "bottom", "toSide": "bottom" }, + { "from": "recover", "to": "verify", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "verify", "to": "close", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "triage", "to": "declare", "variant": "security" }, + { "from": "declare", "to": "update", "variant": "dashed", "fromSide": "top", "toSide": "left", "via": [[430, 16], [20, 16], [20, 615]] }, + { "from": "update", "to": "escalate", "variant": "security", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "verify", "to": "rollback", "variant": "security", "role": "error", "route": "outside-right", "fromSide": "right", "toSide": "right" } + ], + "cards": [ + { "dot": "rose", "title": "Ownership First", "items": ["A page is not an incident until someone owns command", "Severity and scope are explicit before mitigation spreads", "Escalation names the missing expertise"] }, + { "dot": "emerald", "title": "Recovery Is Evidence", "items": ["Mitigation can reduce impact without proving recovery", "SLOs and traces must stay healthy for a fixed window", "The final update follows verification, not optimism"] }, + { "dot": "cyan", "title": "Communication Contract", "items": ["Stakeholders receive impact, action, and next update time", "Rollback remains visible as a deliberate response", "Every branch has an owner and observable exit"] } + ] +} diff --git a/tools/archify/examples/lifecycle-agent-run.html b/tools/archify/examples/lifecycle-agent-run.html new file mode 100644 index 0000000..141719b --- /dev/null +++ b/tools/archify/examples/lifecycle-agent-run.html @@ -0,0 +1,14847 @@ + + + + + + + Agent Run Lifecycle Diagram + + + + + + + + + + + +
    + +
    +
    +
    +

    Agent Run Lifecycle

    +
    +
    + + + + + + + +
    + + Agent Run Lifecycle + A lifecycle diagram generated by Archify. + + + + + + + + + + + + + + + + + + + + + + + + + 01 / Lifecycle phases + + 02 / Interruptions + Recovery loop + + 03 / Terminal exits + + + + + + + + + + + + + + + Queued · request accepted · Lifecycle phases · entry + + + + 01 + Queued + request accepted + entry + + + + Planning · build task graph · Lifecycle phases · model + + + + 02 + Planning + build task graph + model + + + + Executing · tool calls · Lifecycle phases · work + + + + 03 + Executing + tool calls + work + + + + Reviewing · quality gate · Lifecycle phases · check + + + + 04 + Reviewing + quality gate + check + + + + Completed · final response · Lifecycle phases · done + + + + 05 + Completed + final response + done + + + + Needs Approval · human gate · Interruptions · pause + + + + Needs Approval + human gate + pause + + + + Blocked · missing input · Interruptions · wait + + + + Blocked + missing input + wait + + + + Failed · recoverable error · Recovery loop · retryable + + + + Failed + recoverable error + retryable + + + + Cancelled · user stopped · Terminal exits · terminal + + + + Cancelled + user stopped + terminal + + + + Expired · timeout · Terminal exits · terminal + + + + Expired + timeout + terminal + + + + + + + + + + + + + Legend + + + start + + + + active state + + + + waiting + + + + decision + + + + terminal success + + + + failure / exit + + + +

    + + + + + + + + + +
    + + +
    +
    +
    +
    +

    Main Path

    +
    +
      +
    • • The run has five ordered phases from queue to completion
    • +
    • • The primary lifecycle is carried by one horizontal rail
    • +
    • • Completion is a phase, not a detached side box
    • +
    +
    + +
    +
    +
    +

    Human + Input Gates

    +
    +
      +
    • • Approval pauses execution without ending the run
    • +
    • • Blocked waits for missing user input
    • +
    • • Wait states remain non-terminal until cancellation or expiry
    • +
    +
    + +
    +
    +
    +

    Terminal + Recovery

    +
    +
      +
    • • Failed loops back while retry budget remains
    • +
    • • Cancelled and Expired are exits from the lifecycle
    • +
    • • Terminal exits do not point back into active execution
    • +
    +
    +
    + +
    + + + + diff --git a/tools/archify/examples/product-analytics.dataflow.json b/tools/archify/examples/product-analytics.dataflow.json new file mode 100644 index 0000000..57270cf --- /dev/null +++ b/tools/archify/examples/product-analytics.dataflow.json @@ -0,0 +1,76 @@ +{ + "schema_version": 1, + "diagram_type": "dataflow", + "meta": { + "title": "Product Analytics Data Flow", + "output": "examples/dataflow-product-analytics.html", + "viewBox": [1080, 760], + "animation": "trace", + "quality_profile": "showcase", + "views": [ + { "id": "collection-path", "label": "Collection path", "focus": ["web", "mobile", "edge", "stream"], "note": "Follow product events from clients into the ordered event stream." }, + { "id": "consent-boundary", "label": "Consent and PII", "focus": ["edge", "consent", "pii"], "note": "Isolate the policy gate and restricted identity store." }, + { "id": "analytics-consumers", "label": "Curated consumers", "focus": ["stream", "warehouse", "dashboard", "features", "model"], "note": "See curated facts, dashboards, and the derived feature path." } + ] + }, + "stages": [ + { "label": "Sources" }, + { "label": "Ingest" }, + { "label": "Process" }, + { "label": "Store" }, + { "label": "Consume" } + ], + "nodes": [ + { "id": "web", "type": "frontend", "label": "Web App", "sublabel": "browser SDK", "stage": 0, "row": 0, "tag": "events" }, + { "id": "mobile", "type": "frontend", "label": "Mobile", "sublabel": "iOS / Android", "stage": 0, "row": 2, "tag": "events" }, + { "id": "edge", "type": "cloud", "label": "Edge API", "sublabel": "collector", "stage": 1, "row": 1, "tag": "TLS" }, + { "id": "consent", "type": "security", "label": "Consent Gate", "sublabel": "policy filter", "stage": 2, "row": 0, "tag": "PII guard" }, + { "id": "stream", "type": "messagebus", "label": "Event Stream", "sublabel": "Kafka topic", "stage": 2, "row": 2, "tag": "ordered" }, + { "id": "pii", "type": "security", "label": "PII Vault", "sublabel": "encrypted", "stage": 3, "row": 0, "tag": "restricted" }, + { "id": "warehouse", "type": "database", "label": "Warehouse", "sublabel": "analytics tables", "stage": 3, "row": 2, "tag": "curated" }, + { "id": "features", "type": "database", "label": "Feature Store", "sublabel": "daily batch", "stage": 3, "row": 4, "tag": "derived" }, + { "id": "dashboard", "type": "backend", "label": "Dashboards", "sublabel": "product metrics", "stage": 4, "row": 1, "tag": "SQL" }, + { "id": "model", "type": "backend", "label": "ML Model", "sublabel": "ranking job", "stage": 4, "row": 4, "tag": "features" } + ], + "flows": [ + { "id": "web-clickstream", "from": "web", "to": "edge", "label": "clickstream", "classification": "user events", "variant": "emphasis", "fromSide": "right", "toSide": "left", "via": [[205, 157], [205, 271]], "labelAt": [204, 190] }, + { "id": "mobile-events", "from": "mobile", "to": "edge", "label": "app events", "classification": "device events", "variant": "default", "fromSide": "right", "toSide": "left", "via": [[222, 385], [222, 271]], "labelAt": [220, 342] }, + { "id": "consent-enrichment", "from": "edge", "to": "consent", "label": "identity + consent", "classification": "PII touch", "variant": "security", "fromSide": "top", "toSide": "left", "via": [[315, 112], [450, 112], [450, 157]], "labelAt": [382, 100] }, + { "id": "accepted-events", "from": "edge", "to": "stream", "label": "accepted events", "classification": "append-only", "variant": "emphasis", "fromSide": "right", "toSide": "left", "via": [[420, 271], [420, 385]], "labelAt": [438, 324] }, + { "id": "identity-map", "from": "consent", "to": "pii", "label": "identity map", "classification": "encrypted PII", "variant": "security", "route": "straight", "labelAt": [638, 144] }, + { "id": "normalized-facts", "from": "stream", "to": "warehouse", "label": "normalized facts", "classification": "non-PII", "variant": "emphasis", "route": "straight", "labelAt": [638, 372] }, + { "id": "daily-aggregates", "from": "warehouse", "to": "features", "label": "daily aggregates", "classification": "batch", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "route": "straight", "labelAt": [745, 496] }, + { "id": "metrics-query", "from": "warehouse", "to": "dashboard", "label": "metrics SQL", "classification": "read-only", "variant": "default", "fromSide": "right", "toSide": "left", "via": [[852, 385], [852, 271]], "labelAt": [830, 326] }, + { "id": "feature-vectors", "from": "features", "to": "model", "label": "feature vectors", "classification": "derived", "variant": "dashed", "route": "straight", "labelAt": [852, 598] }, + { "id": "restricted-join", "from": "pii", "to": "dashboard", "label": "restricted join", "classification": "approved only", "variant": "security", "fromSide": "right", "toSide": "top", "via": [[878, 157], [878, 212], [960, 212]], "labelAt": [880, 198] } + ], + "cards": [ + { + "dot": "emerald", + "title": "Primary Data Path", + "items": [ + "Events move left to right through source, ingest, process, store, and consume stages", + "The hot path stays visually clear even with secondary batch flows", + "Labels name data assets instead of generic API verbs" + ] + }, + { + "dot": "rose", + "title": "Sensitive Boundary", + "items": [ + "Consent and PII paths are styled as security flows", + "PII lands in a restricted vault, separate from the analytics warehouse", + "Restricted joins are visible without implying default access" + ] + }, + { + "dot": "orange", + "title": "Derived Consumers", + "items": [ + "Dashboards read curated facts from the warehouse", + "Feature vectors are derived by batch from analytics tables", + "Consumption paths stay distinct from collection and consent handling" + ] + } + ] +} diff --git a/tools/archify/examples/production-deployment.architecture.json b/tools/archify/examples/production-deployment.architecture.json new file mode 100644 index 0000000..472cb83 --- /dev/null +++ b/tools/archify/examples/production-deployment.architecture.json @@ -0,0 +1,71 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Production Deployment Ownership", + "output": "examples/production-deployment.html", + "visual_preset": "blueprint", + "animation": "trace", + "quality_profile": "showcase", + "engineering_profile": "deployment-ownership", + "views": [ + { + "id": "request-boundary", + "label": "Request crosses the edge", + "focus": ["clients", "edge", "gateway", "api_a", "api_b"], + "note": "Follow public traffic into the private application network." + }, + { + "id": "state-ownership", + "label": "State and ownership", + "focus": ["api_a", "api_b", "redis", "postgres", "replica"], + "note": "Separate stateless platform workloads from data-team-owned state." + }, + { + "id": "async-operations", + "label": "Async and operations", + "focus": ["api_b", "events", "worker", "audit", "observability"], + "note": "See the asynchronous work and the evidence it emits." + } + ] + }, + "components": [ + { "id": "clients", "type": "external", "label": "Customers", "sublabel": "web + mobile", "pos": [38, 300], "size": [122, 60] }, + { "id": "edge", "type": "cloud", "label": "Global Edge", "sublabel": "CDN + WAF", "pos": [230, 300], "size": [126, 60], "tag": "edge team" }, + { "id": "gateway", "type": "security", "label": "API Gateway", "sublabel": "public :443", "pos": [430, 300], "size": [128, 60], "tag": "platform" }, + { "id": "api_a", "type": "backend", "label": "API Pods / AZ-a", "sublabel": "private subnet", "pos": [630, 195], "size": [136, 62], "tag": "app team" }, + { "id": "api_b", "type": "backend", "label": "API Pods / AZ-b", "sublabel": "private subnet", "pos": [630, 405], "size": [136, 62], "tag": "app team" }, + { "id": "redis", "type": "database", "label": "Redis", "sublabel": "multi-AZ cache", "pos": [840, 195], "size": [126, 62], "tag": "platform" }, + { "id": "postgres", "type": "database", "label": "PostgreSQL", "sublabel": "primary / encrypted", "pos": [840, 405], "size": [126, 62], "tag": "data team" }, + { "id": "events", "type": "messagebus", "label": "Event Bus", "sublabel": "orders.v1", "pos": [1040, 300], "size": [126, 60], "tag": "platform" }, + { "id": "worker", "type": "backend", "label": "Workers", "sublabel": "private workload", "pos": [1190, 300], "size": [126, 60], "tag": "app team" }, + { "id": "replica", "type": "database", "label": "DR Replica", "sublabel": "eu-west-1", "pos": [1040, 578], "size": [126, 62], "tag": "data team" }, + { "id": "audit", "type": "cloud", "label": "Audit Archive", "sublabel": "immutable objects", "pos": [1190, 450], "size": [126, 62], "tag": "security" }, + { "id": "observability", "type": "external", "label": "Observability", "sublabel": "metrics + traces", "pos": [1190, 85], "size": [126, 62], "tag": "SRE" } + ], + "boundaries": [ + { "kind": "region", "label": "AWS us-east-1 / production", "wraps": ["edge", "gateway", "api_a", "api_b", "redis", "postgres", "events", "worker", "audit"], "pad": 20 }, + { "kind": "security-group", "label": "private application network", "wraps": ["api_a", "api_b", "redis", "postgres", "events", "worker"], "pad": 14 }, + { "kind": "region", "label": "AWS eu-west-1 / disaster recovery", "wraps": ["replica"] }, + { "kind": "security-group", "label": "DR private subnet", "wraps": ["replica"], "pad": 14 } + ], + "connections": [ + { "from": "clients", "to": "edge", "label": "HTTPS", "variant": "emphasis" }, + { "from": "edge", "to": "gateway", "label": "mTLS", "variant": "security" }, + { "from": "gateway", "to": "api_a", "label": "VPC route", "variant": "emphasis", "route": "orthogonal-h", "labelAt": [594, 275] }, + { "from": "gateway", "to": "api_b", "label": "VPC route", "variant": "emphasis", "route": "orthogonal-h", "labelAt": [594, 385] }, + { "from": "api_a", "to": "redis", "label": "cache", "route": "straight" }, + { "from": "api_b", "to": "postgres", "label": "SQL", "route": "straight" }, + { "from": "api_a", "to": "events", "label": "publish", "variant": "dashed", "fromSide": "top", "toSide": "top", "via": [[698, 170], [1103, 170]] }, + { "from": "api_b", "to": "events", "variant": "dashed", "fromSide": "top", "toSide": "bottom", "via": [[698, 380], [1103, 380]] }, + { "from": "events", "to": "worker", "variant": "emphasis" }, + { "from": "postgres", "to": "replica", "label": "cross-region WAL", "variant": "security", "route": "orthogonal-v", "labelAt": [1003, 529] }, + { "from": "worker", "to": "audit", "label": "evidence", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 58 }, + { "from": "worker", "to": "observability", "label": "OTLP", "variant": "dashed", "route": "orthogonal-v" } + ], + "cards": [ + { "dot": "cyan", "title": "Runtime Ownership", "items": ["Platform owns the edge, gateway, cache, and event bus", "Application teams own API pods and workers", "Data owns primary and disaster-recovery state"] }, + { "dot": "rose", "title": "Named Crossings", "items": ["Public HTTPS terminates at the managed edge", "mTLS crosses into the application network", "Cross-region WAL is explicit and encrypted"] }, + { "dot": "emerald", "title": "Operational Evidence", "items": ["Workers emit traces to SRE-owned observability", "Audit evidence lands in immutable storage", "Unknown placement should remain marked, never invented"] } + ] +} diff --git a/tools/archify/examples/release-delivery.workflow.json b/tools/archify/examples/release-delivery.workflow.json new file mode 100644 index 0000000..915e39c --- /dev/null +++ b/tools/archify/examples/release-delivery.workflow.json @@ -0,0 +1,62 @@ +{ + "schema_version": 1, + "diagram_type": "workflow", + "meta": { + "title": "Release Delivery Workflow", + "output": "examples/release-delivery.html", + "animation": "trace", + "quality_profile": "showcase", + "views": [ + { "id": "commit-to-checks", "label": "Commit to green build", "focus": ["commit", "pull_request", "build", "checks"], "note": "Follow the change through reproducible build and blocking quality gates." }, + { "id": "approval-to-production", "label": "Approve and promote", "focus": ["checks", "approval", "deploy", "verify_prod", "announce"], "note": "See who authorizes production and how success is verified." }, + { "id": "rollback-path", "label": "Failure and rollback", "focus": ["checks", "failed", "verify_prod", "rollback", "deploy"], "note": "Isolate the two places where delivery stops or reverses safely." } + ] + }, + "lanes": [ + { "id": "dev", "label": "Developer" }, + { "id": "ci", "label": "Continuous Integration" }, + { "id": "approval", "label": "Release Governance" }, + { "id": "environment", "label": "Production Environment" }, + { "id": "communication", "label": "Release Communication" }, + { "id": "exceptions", "label": "Failure + Rollback", "variant": "exception" } + ], + "phases": [ + { "id": "change", "label": "Change", "fromCol": 0, "toCol": 1 }, + { "id": "verify", "label": "Build + verify", "fromCol": 2, "toCol": 3, "variant": "emphasis" }, + { "id": "promote", "label": "Promote + observe", "fromCol": 4, "toCol": 5, "variant": "dashed" } + ], + "groups": [ + { "id": "blocking_checks", "label": "Blocking checks", "lane": "ci", "fromCol": 2, "toCol": 3, "variant": "emphasis" }, + { "id": "rollback_work", "label": "Recovery path", "lane": "exceptions", "fromCol": 3, "toCol": 5, "variant": "security" } + ], + "mainPath": ["commit", "pull_request", "build", "checks", "approval", "deploy", "verify_prod", "announce"], + "nodes": [ + { "id": "commit", "lane": "dev", "col": 0, "type": "frontend", "label": "Commit", "sublabel": "signed change" }, + { "id": "pull_request", "lane": "dev", "col": 1, "type": "frontend", "label": "Pull Request", "sublabel": "reviewed diff" }, + { "id": "build", "lane": "ci", "col": 2, "type": "backend", "label": "Build", "sublabel": "locked inputs", "tag": "reproducible" }, + { "id": "checks", "lane": "ci", "col": 3, "type": "security", "label": "Quality Gates", "sublabel": "test + scan", "tag": "blocking" }, + { "id": "approval", "lane": "approval", "col": 4, "type": "security", "label": "Approve", "sublabel": "release owner", "tag": "human gate" }, + { "id": "deploy", "lane": "environment", "col": 4, "type": "cloud", "label": "Deploy", "sublabel": "canary 10%", "tag": "production" }, + { "id": "verify_prod", "lane": "environment", "col": 5, "type": "backend", "label": "Verify", "sublabel": "smoke + SLO" }, + { "id": "announce", "lane": "communication", "col": 5, "type": "external", "label": "Announce", "sublabel": "status + notes" }, + { "id": "failed", "lane": "exceptions", "col": 2, "type": "security", "label": "Stop Release", "sublabel": "gate failed" }, + { "id": "rollback", "lane": "exceptions", "col": 4, "width": 64, "type": "messagebus", "label": "Rollback", "sublabel": "last good image", "tag": "owner: on-call" } + ], + "edges": [ + { "from": "commit", "to": "pull_request" }, + { "from": "pull_request", "to": "build", "label": "merge", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "build", "to": "checks" }, + { "from": "checks", "to": "approval", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "approval", "to": "deploy", "variant": "security", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "deploy", "to": "verify_prod" }, + { "from": "verify_prod", "to": "announce", "label": "healthy", "variant": "emphasis", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "checks", "to": "failed", "label": "red", "variant": "security", "role": "error", "route": "drop", "fromSide": "bottom", "toSide": "top" }, + { "from": "verify_prod", "to": "rollback", "variant": "security", "role": "error", "route": "outside-right", "fromSide": "right", "toSide": "right" }, + { "from": "rollback", "to": "deploy", "label": "restore", "variant": "dashed", "role": "return", "route": "return-left", "fromSide": "left", "toSide": "left" } + ], + "cards": [ + { "dot": "emerald", "title": "One Happy Path", "items": ["Every change is reviewed before a reproducible build", "Blocking checks must be green before human approval", "Production is complete only after smoke and SLO verification"] }, + { "dot": "rose", "title": "Stop Conditions", "items": ["Test or security failure stops promotion", "Production health can reverse a release", "Rollback ownership is visible before an incident"] }, + { "dot": "cyan", "title": "Release Evidence", "items": ["Approval, immutable image, and check results are retained", "The release announcement follows verification", "The main path remains readable without hiding failure"] } + ] +} diff --git a/tools/archify/examples/sequence-cache-miss-request.html b/tools/archify/examples/sequence-cache-miss-request.html new file mode 100644 index 0000000..d3e2c1e --- /dev/null +++ b/tools/archify/examples/sequence-cache-miss-request.html @@ -0,0 +1,14913 @@ + + + + + + + Cache Miss Request Sequence Diagram + + + + + + + + + + + +
    + +
    +
    +
    +

    Cache Miss Request Sequence

    +
    +
    + + + + + + + +
    + + Cache Miss Request Sequence + A sequence diagram generated by Archify. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + open page + + + + + + + + GET /dashboard + + + + + + + + verify JWT + + + + + + + + claims ok + + + + + + + + read cache + + + + + + + + miss + + + + + + + + query profile + metrics + + + + + + + + rows + + + + + + + + set cache + + + + + + + + emit trace + + + + + + + + 200 JSON + + + + + + + + render + + + + + + + Request + + + + Fallback + + + + Response + trace + + + + + User · browser session · Sequence participant + + + + User + browser session + + + + Web App · React UI · Sequence participant + + + + Web App + React UI + + + + API · request handler · Sequence participant + + + + API + request handler + + + + Auth · JWT verify · Sequence participant + + + + Auth + JWT verify + + + + Redis · cache · Sequence participant + + + + Redis + cache + + + + Postgres · source of truth · Sequence participant + + + + Postgres + source of truth + + + + Trace · async event · Sequence participant + + + + Trace + async event + + + + + Legend + + + request + + + + return + + + + security + + + + async trace + + + + default message + + + +

    + + + + + + + + + +
    + + +
    +
    +
    +
    +

    Happy Path

    +
    +
      +
    • • The main request is Web App -> API -> data source -> response
    • +
    • • Return messages are quieter than forward calls
    • +
    • • Activation bars make ownership duration visible
    • +
    +
    + +
    +
    +
    +

    Policy + Fallback

    +
    +
      +
    • • JWT verification is colored as a security interaction
    • +
    • • Cache miss is visible without overpowering the main path
    • +
    • • Database access only appears after cache fallback
    • +
    +
    + +
    +
    +
    +

    Async Trace

    +
    +
      +
    • • Trace emission is dashed and secondary
    • +
    • • It does not block the response path
    • +
    • • The diagram separates user-facing latency from observability
    • +
    +
    +
    + +
    + + + + diff --git a/tools/archify/examples/web-app-rendered.html b/tools/archify/examples/web-app-rendered.html new file mode 100644 index 0000000..d6d45a5 --- /dev/null +++ b/tools/archify/examples/web-app-rendered.html @@ -0,0 +1,14862 @@ + + + + + + + Sample Web App Diagram + + + + + + + + + + + +
    + +
    +
    +
    +

    Sample Web App

    +
    +
    + + + + + + + +
    + + Sample Web App + An architecture diagram generated by Archify. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Users · Browser / Mobile · Architecture component + + + + Users + Browser / Mobile + + + + Auth Provider · OAuth 2.0 · Architecture component · JWT + PKCE + + + + Auth Provider + OAuth 2.0 + JWT + PKCE + + + + CloudFront · CDN · AWS Region: us-west-2 + + + + CloudFront + CDN + + + + Load Balancer · HTTPS :443 · AWS Region: us-west-2 › sg-api :443/:8000 + + + + Load Balancer + HTTPS :443 + + + + API Server · FastAPI :8000 · AWS Region: us-west-2 › sg-api :443/:8000 + + + + API Server + FastAPI :8000 + + + + Redis · cache :6379 · AWS Region: us-west-2 + + + + Redis + cache :6379 + + + + PostgreSQL · primary :5432 · AWS Region: us-west-2 + + + + PostgreSQL + primary :5432 + + + + S3 · static assets · AWS Region: us-west-2 · OAI protected + + + + S3 + static assets + OAI protected + + + + SQS · job queue · AWS Region: us-west-2 + + + + SQS + job queue + + + + Worker · async jobs · AWS Region: us-west-2 + + + + Worker + async jobs + + + + + + HTTPS + + + + verify JWT + + + + + static + + + + + read-through + + + + SQL + + + + enqueue + + + + + + + AWS Region: us-west-2 + + + + + sg-api :443/:8000 + + + + + Legend + + + Backend + + + + Database + + + + Cloud + + + + Security + + + + Message bus + + + + External + + + +

    + + + + + + + + + +
    + + +
    +
    +
    +
    +

    Edge

    +
    +
      +
    • • CloudFront CDN fronts all traffic
    • +
    • • S3 serves static assets via OAI
    • +
    +
    + +
    +
    +
    +

    Application

    +
    +
      +
    • • FastAPI behind an HTTPS load balancer
    • +
    • • Redis read-through cache
    • +
    • • Async work drained from SQS by a worker
    • +
    +
    + +
    +
    +
    +

    Security

    +
    +
      +
    • • OAuth 2.0 with JWT + PKCE
    • +
    • • API + LB isolated in a security group
    • +
    +
    +
    + +
    + + + + diff --git a/tools/archify/examples/web-app.architecture.json b/tools/archify/examples/web-app.architecture.json new file mode 100644 index 0000000..9f6cb14 --- /dev/null +++ b/tools/archify/examples/web-app.architecture.json @@ -0,0 +1,46 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Sample Web App", + "output": "web-app-rendered.html", + "quality_profile": "showcase", + "views": [ + { "id": "request-path", "label": "Primary request path", "focus": ["users", "cdn", "lb", "api", "db"], "note": "Follow the primary customer request from the edge to durable state." }, + { "id": "identity-and-cache", "label": "Identity and cache", "focus": ["auth", "api", "cache"], "note": "Isolate authentication and the read-through cache beside the request path." }, + { "id": "async-work", "label": "Static and async work", "focus": ["cdn", "s3", "api", "queue", "worker"], "note": "See the two secondary paths without adding noise to the main request." } + ] + }, + "components": [ + { "id": "users", "type": "external", "label": "Users", "sublabel": "Browser / Mobile", "pos": [40, 300], "size": [120, 60] }, + { "id": "auth", "type": "security", "label": "Auth Provider", "sublabel": "OAuth 2.0", "pos": [40, 110], "size": [120, 64], "tag": "JWT + PKCE" }, + { "id": "cdn", "type": "cloud", "label": "CloudFront", "sublabel": "CDN", "pos": [250, 300], "size": [130, 60] }, + { "id": "lb", "type": "cloud", "label": "Load Balancer", "sublabel": "HTTPS :443", "pos": [460, 300], "size": [130, 60] }, + { "id": "api", "type": "backend", "label": "API Server", "sublabel": "FastAPI :8000", "pos": [670, 300], "size": [130, 60] }, + { "id": "cache", "type": "database", "label": "Redis", "sublabel": "cache :6379", "pos": [670, 150], "size": [130, 60] }, + { "id": "db", "type": "database", "label": "PostgreSQL", "sublabel": "primary :5432", "pos": [880, 300], "size": [130, 60] }, + { "id": "s3", "type": "cloud", "label": "S3", "sublabel": "static assets", "pos": [250, 440], "size": [130, 60], "tag": "OAI protected" }, + { "id": "queue", "type": "messagebus", "label": "SQS", "sublabel": "job queue", "pos": [670, 440], "size": [130, 60] }, + { "id": "worker", "type": "backend", "label": "Worker", "sublabel": "async jobs", "pos": [880, 440], "size": [130, 60] } + ], + "boundaries": [ + { "kind": "region", "label": "AWS Region: us-west-2", "wraps": ["cdn", "lb", "api", "cache", "db", "s3", "queue", "worker"] }, + { "kind": "security-group", "label": "sg-api :443/:8000", "wraps": ["lb", "api"] } + ], + "connections": [ + { "id": "users-to-cdn", "from": "users", "to": "cdn", "label": "HTTPS", "variant": "emphasis" }, + { "id": "jwt-verification", "from": "auth", "to": "api", "label": "verify JWT", "variant": "security", "fromSide": "right", "toSide": "top", "via": [[620, 142], [620, 246], [735, 246]] }, + { "id": "cdn-to-lb", "from": "cdn", "to": "lb" }, + { "id": "static-assets", "from": "cdn", "to": "s3", "label": "static", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 58 }, + { "id": "lb-to-api", "from": "lb", "to": "api" }, + { "id": "cache-read-through", "from": "api", "to": "cache", "label": "read-through", "fromSide": "top", "toSide": "bottom", "labelDy": -68 }, + { "id": "api-sql", "from": "api", "to": "db", "label": "SQL" }, + { "id": "enqueue-job", "from": "api", "to": "queue", "label": "enqueue", "variant": "dashed", "fromSide": "bottom", "toSide": "top", "labelDy": 58 }, + { "id": "queue-to-worker", "from": "queue", "to": "worker" } + ], + "cards": [ + { "dot": "cyan", "title": "Edge", "items": ["CloudFront CDN fronts all traffic", "S3 serves static assets via OAI"] }, + { "dot": "emerald", "title": "Application", "items": ["FastAPI behind an HTTPS load balancer", "Redis read-through cache", "Async work drained from SQS by a worker"] }, + { "dot": "rose", "title": "Security", "items": ["OAuth 2.0 with JWT + PKCE", "API + LB isolated in a security group"] } + ] +} diff --git a/tools/archify/examples/workflow-agent-tool-call-rendered.html b/tools/archify/examples/workflow-agent-tool-call-rendered.html new file mode 100644 index 0000000..0fb6bb9 --- /dev/null +++ b/tools/archify/examples/workflow-agent-tool-call-rendered.html @@ -0,0 +1,14904 @@ + + + + + + + Agent Tool Call Workflow Diagram + + + + + + + + + + + +
    + +
    +
    +
    +

    Agent Tool Call Workflow

    +
    +
    + + + + + + + +
    + + Agent Tool Call Workflow + A workflow diagram generated by Archify. + + + + + + + + + + + + + + + + + + + + + + + + + 01 / User Interface + + + 02 / Agent Runtime + + + + EX / Policy & Recovery + + + 04 / Tool Execution & Evidence + + + + + Intake + + + Plan + route + + + Execute + report + + + + Planning loop + + Human or policy stop + + Evidence path + + Tool work + + + + + + + + + + + + + + + + + User · asks for work · User Interface › Intake + + + + User + asks for work + + + + Chat Surface · thread + files · User Interface › Intake + + + + Chat Surface + thread + files + + + + Final Reply · answer + changes · User Interface › Execute + report + + + + Final Reply + answer + changes + + + + Agent Planner · plan next step · Agent Runtime › Planning loop › Plan + route · context aware + + + + Agent Planner + plan next step + context aware + + + + Tool Router · choose capability · Agent Runtime › Planning loop › Plan + route + + + + Tool Router + choose capability + + + + Approval Gate · scope + consent · Policy & Recovery › Human or policy stop › Plan + route · block risky ops + + + + Approval Gate + scope + consent + block risky ops + + + + Blocked · wait or reject · Policy & Recovery › Human or policy stop › Execute + report + + + + Blocked + wait or reject + + + + Retry Path · revise request · Policy & Recovery › Human or policy stop › Execute + report + + + + Retry Path + revise request + + + + Context Store · repo + memory · Tool Execution & Evidence › Evidence path › Intake + + + + Context Store + repo + memory + + + + Trace Log · events + output · Tool Execution & Evidence › Evidence path › Plan + route + + + + Trace Log + events + output + + + + Tool Call · shell / browser / MCP · Tool Execution & Evidence › Tool work › Execute + report · structured result + + + + Tool Call + shell / browser / MCP + structured result + + + + External API · network service · Tool Execution & Evidence › Tool work › Execute + report + + + + External API + network service + + + + + + needs approval? + + + + denied + + + + + + plan + + + + + record result + + + + + + + trace + memory + + + + + Legend + + + User UI + + + + Agent logic + + + + Policy + + + + Tool action + + + + Context / trace + + + + Cloud service + + + + External system + + + +

    + + + + + + + + + +
    + + +
    +
    +
    +
    +

    Compiler Contract

    +
    +
      +
    • • Lanes and columns determine node placement
    • +
    • • Labels reserve clearance; routes stay orthogonal
    • +
    +
    + +
    +
    +
    +

    Runtime Semantics

    +
    +
      +
    • • Approval gates risky work before tool execution
    • +
    • • Evidence returns through isolated trace and memory
    • +
    +
    +
    + +
    + + + + diff --git a/tools/archify/migrations/workflow-v2.mjs b/tools/archify/migrations/workflow-v2.mjs new file mode 100644 index 0000000..85ba80c --- /dev/null +++ b/tools/archify/migrations/workflow-v2.mjs @@ -0,0 +1,279 @@ +import { compileWorkflow } from '../renderers/workflow/workflow-compiler.mjs'; +import { + createMappedWorkflowCandidate, + intrinsicWorkflow, + planningWorkflow, +} from '../renderers/workflow/workflow-migration-geometry.mjs'; +import { validateSchema } from '../renderers/shared/validator.mjs'; + +export { createHorizontalRankMapper } from '../renderers/workflow/workflow-migration-geometry.mjs'; + +const TARGET_SCHEMA_VERSION = 2; + +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + +function diagnostic({ code, message, subject = {}, evidence = {}, supportedFixes = [] }) { + return { + code, + severity: 'error', + message, + subject, + evidence, + supportedFixes, + }; +} + +function schemaDiagnostics(workflow) { + try { + validateSchema('workflow', workflow); + return []; + } catch (error) { + if (Array.isArray(error?.archifyDiagnostics)) { + return error.archifyDiagnostics.map((entry) => ({ ...entry })); + } + throw error; + } +} + +function legacyLayoutProbe(workflow, qualityProfile) { + // The probe discovers fixed-v1 rank centers, not authored canvas capacity. + // Omitting viewBox lets a capacity-only legacy failure reach the v2 compiler, + // which can measure and monotonically expand the real migrated document. + const probe = { + schema_version: 1, + diagram_type: 'workflow', + meta: { + title: workflow.meta.title, + ...(workflow.meta.locale ? { locale: workflow.meta.locale } : {}), + legend: { mode: 'hidden' }, + }, + lanes: clone(workflow.lanes), + nodes: [{ + id: 'migration_probe', + lane: workflow.lanes[0].id, + col: 0, + type: 'backend', + label: 'Probe', + }], + edges: [], + }; + return compileWorkflow({ workflow: probe, qualityProfile }); +} + +function legacyRequirementProbe(workflow, qualityProfile) { + // Measure the complete fixed-v1 document without treating an authored + // viewBox as its intrinsic requirement. The authored viewBox remains a + // migration capacity and is preserved separately on the migrated document. + const probe = clone(workflow); + delete probe.meta.viewBox; + return compileWorkflow({ workflow: probe, qualityProfile }); +} + +function requiredViewBoxFrom(result) { + if (Array.isArray(result?.receipt?.requiredViewBox)) { + return [...result.receipt.requiredViewBox]; + } + const required = result?.diagnostics + ?.map((entry) => entry?.evidence?.requiredViewBox) + .find((candidate) => Array.isArray(candidate) && candidate.length === 2); + return required ? [...required] : null; +} + +function expandableViewBox(result) { + if (result.ok || !result.diagnostics?.length) return null; + if (!result.diagnostics.every((entry) => entry.code === 'workflow/viewbox-capacity')) return null; + return requiredViewBoxFrom(result); +} + +function result({ + ok, + document, + fromSchemaVersion = 1, + preExistingDiagnostics = [], + migrationDiagnostics = [], + newSchemaDiagnostics = [], + changedCoordinates = [], + oldRequiredViewBox = null, + newRequiredViewBox = null, +}) { + return { + ok, + ...(document ? { document } : {}), + fromSchemaVersion, + toSchemaVersion: TARGET_SCHEMA_VERSION, + preExistingDiagnostics, + migrationDiagnostics, + newSchemaDiagnostics, + changedCoordinates, + oldRequiredViewBox, + newRequiredViewBox, + }; +} + +export function migrateWorkflowDocument(inputWorkflow) { + if (!inputWorkflow || typeof inputWorkflow !== 'object' || Array.isArray(inputWorkflow)) { + return result({ + ok: false, + migrationDiagnostics: [diagnostic({ + code: 'migration/source-document', + message: 'Workflow migration requires one parsed JSON object.', + supportedFixes: ['provide one workflow schema v1 JSON document'], + })], + }); + } + // Migration has no quality override: the authored policy (or effective + // standard default) must validate the document after it leaves this process. + const qualityProfile = inputWorkflow.meta?.quality_profile || 'standard'; + + const workflow = clone(inputWorkflow); + const preExistingDiagnostics = schemaDiagnostics(workflow); + if (preExistingDiagnostics.length) { + return result({ + ok: false, + fromSchemaVersion: workflow.schema_version, + preExistingDiagnostics, + }); + } + if (workflow.schema_version === TARGET_SCHEMA_VERSION) { + const compiled = compileWorkflow({ workflow: clone(workflow), qualityProfile }); + const requiredViewBox = requiredViewBoxFrom(compiled); + if (!compiled.ok) { + return result({ + ok: false, + fromSchemaVersion: TARGET_SCHEMA_VERSION, + preExistingDiagnostics: compiled.diagnostics, + oldRequiredViewBox: requiredViewBox, + newRequiredViewBox: requiredViewBox, + }); + } + return result({ + ok: true, + document: workflow, + fromSchemaVersion: TARGET_SCHEMA_VERSION, + oldRequiredViewBox: requiredViewBox, + newRequiredViewBox: requiredViewBox, + }); + } + if (workflow.schema_version !== 1) { + return result({ + ok: false, + fromSchemaVersion: workflow.schema_version, + migrationDiagnostics: [diagnostic({ + code: 'migration/source-schema-version', + message: 'Workflow migration to schema v2 requires a schema v1 or v2 source.', + subject: { path: '/schema_version' }, + evidence: { actual: workflow.schema_version, expected: [1, 2] }, + supportedFixes: ['use an unchanged schema v1 workflow or an already migrated schema v2 workflow as the source'], + })], + }); + } + + const legacy = compileWorkflow({ workflow: clone(workflow), qualityProfile }); + const legacyProbe = legacyLayoutProbe(workflow, qualityProfile); + if (!legacyProbe.ok) { + return result({ + ok: false, + preExistingDiagnostics: legacy.ok ? [] : legacy.diagnostics, + migrationDiagnostics: legacyProbe.diagnostics, + }); + } + const legacyRequirement = legacyRequirementProbe(workflow, qualityProfile); + const oldRequiredViewBox = requiredViewBoxFrom(legacyRequirement) + || requiredViewBoxFrom(legacyProbe) + || requiredViewBoxFrom(legacy); + const preExistingLayoutDiagnostics = legacy.ok ? [] : legacy.diagnostics; + + let planned = compileWorkflow({ workflow: intrinsicWorkflow(workflow), qualityProfile }); + if (!planned.ok) { + // Old absolute pins can be invalid at the new rank centers before their X + // coordinates are mapped. Obtain the same rank plan from an automatic-route + // projection, then validate every authored pin again after mapping. + planned = compileWorkflow({ workflow: planningWorkflow(workflow), qualityProfile }); + } + if (!planned.ok) { + return result({ + ok: false, + preExistingDiagnostics: preExistingLayoutDiagnostics, + newSchemaDiagnostics: planned.diagnostics, + oldRequiredViewBox, + newRequiredViewBox: requiredViewBoxFrom(planned), + }); + } + + let mappedCandidate; + try { + mappedCandidate = createMappedWorkflowCandidate( + workflow, + legacyProbe.receipt.columns, + planned.receipt.columns, + ); + } catch (error) { + return result({ + ok: false, + preExistingDiagnostics: preExistingLayoutDiagnostics, + migrationDiagnostics: [diagnostic({ + code: 'migration/rank-mapping', + message: 'Could not construct a stable horizontal rank mapping.', + evidence: { reason: error.message }, + supportedFixes: ['report the workflow and compiler receipts to the Archify maintainers'], + })], + oldRequiredViewBox, + newRequiredViewBox: requiredViewBoxFrom(planned), + }); + } + + const { document: migrated, changedCoordinates } = mappedCandidate; + + let compiled = compileWorkflow({ workflow: migrated, qualityProfile }); + const requiredExpansion = migrated.meta.viewBox ? expandableViewBox(compiled) : null; + if (requiredExpansion) { + const current = migrated.meta.viewBox; + const expanded = [ + Math.max(current[0], requiredExpansion[0]), + Math.max(current[1], requiredExpansion[1]), + ]; + if (expanded[0] > current[0] || expanded[1] > current[1]) { + migrated.meta.viewBox = expanded; + compiled = compileWorkflow({ workflow: migrated, qualityProfile }); + } + } + + const newRequiredViewBox = requiredViewBoxFrom(compiled) || requiredViewBoxFrom(planned); + if (!compiled.ok) { + return result({ + ok: false, + preExistingDiagnostics: preExistingLayoutDiagnostics, + newSchemaDiagnostics: compiled.diagnostics, + changedCoordinates, + oldRequiredViewBox, + newRequiredViewBox, + }); + } + + const migratedSchemaDiagnostics = schemaDiagnostics(migrated); + if (migratedSchemaDiagnostics.length) { + return result({ + ok: false, + preExistingDiagnostics: preExistingLayoutDiagnostics, + newSchemaDiagnostics: migratedSchemaDiagnostics, + changedCoordinates, + oldRequiredViewBox, + newRequiredViewBox, + }); + } + + return result({ + ok: true, + document: migrated, + preExistingDiagnostics: preExistingLayoutDiagnostics, + changedCoordinates, + oldRequiredViewBox, + newRequiredViewBox, + }); +} + +export function serializeMigratedWorkflow(workflow) { + return `${JSON.stringify(workflow, null, 2)}\n`; +} diff --git a/tools/archify/package.json b/tools/archify/package.json new file mode 100644 index 0000000..1742931 --- /dev/null +++ b/tools/archify/package.json @@ -0,0 +1,37 @@ +{ + "name": "archify", + "version": "2.17.0-dev.1", + "private": true, + "type": "module", + "description": "JSON-IR diagram renderers (architecture / workflow / sequence / dataflow / lifecycle).", + "license": "MIT", + "bin": { + "archify": "./bin/archify.mjs" + }, + "engines": { + "node": ">=18" + }, + "scripts": { + "generate:brand-marks": "node scripts/generate-brand-marks.mjs", + "check:brand-marks": "node scripts/generate-brand-marks.mjs --check", + "generate:validators": "node scripts/generate-validators.mjs", + "check:validators": "node scripts/generate-validators.mjs --check", + "check:release-identity": "node ../scripts/check-release-identity.mjs", + "build:gallery": "node ../scripts/build-gallery.mjs ../docs", + "build:guide": "node ../scripts/build-guide.mjs ../docs/guide.html", + "build:start": "node ../scripts/build-start.mjs ../docs/start.html", + "build:readme-showcase": "node ../scripts/build-readme-showcase.mjs", + "test:webm": "node test/webm-artifact.smoke.mjs && node --test test/site-language-integration.mjs", + "test": "npm run check:brand-marks && npm run check:validators && npm run check:release-identity && node test/golden.mjs && node ../scripts/run-tests.mjs", + "render:examples": "node scripts/render-examples.mjs ../examples" + }, + "devDependencies": { + "ajv": "^8.17.1", + "parse5": "7.3.0", + "saxes": "6.0.0", + "simple-icons": "16.28.0" + }, + "overrides": { + "fast-uri": "3.1.5" + } +} diff --git a/tools/archify/recipes/scenarios.mjs b/tools/archify/recipes/scenarios.mjs new file mode 100644 index 0000000..5076134 --- /dev/null +++ b/tools/archify/recipes/scenarios.mjs @@ -0,0 +1,391 @@ +const RAW_RECIPES = [ + { + id: 'system-overview', type: 'architecture', proof: 'web-app', + presentation: { preset: 'classic', motion: 'static', views: 'optional' }, + start: { + en: { descriptionPrompt: 'Use Archify to turn this plain-language system description into a high-level architecture diagram: [describe the users, core components, primary path, external dependencies, and boundaries]. No repository is required. Ask only for missing facts that would materially change the diagram, mark any remaining unknowns instead of inventing them, and keep one obvious primary path across 8–12 core components.' }, + zh: { descriptionPrompt: '用 Archify 把下面这段自然语言系统描述画成高层架构图:[在这里描述用户、核心组件、主要路径、外部依赖和边界]。不需要代码库。只追问会实质影响图的缺失信息,其余不确定内容要标明而不是编造;保留 8–12 个核心组件和一条一眼可见的主路径。' }, + }, + signals: [['system overview', 12], ['architecture', 10], ['components', 6], ['services', 4], ['repository', 5], ['trust boundary', 8], ['架构', 10], ['系统总览', 12], ['组件', 6], ['服务', 4], ['仓库', 5], ['信任边界', 8]], + en: { + title: 'System overview', question: 'What exists, who owns it, and how is it connected?', + summary: 'A bounded map of core components, external dependencies, primary paths, and trust boundaries.', + useWhen: 'Onboarding, design reviews, repository orientation, or explaining a service landscape.', + avoidWhen: 'The audience needs exact call order, state transitions, or row-level data lineage.', + include: ['8–12 core components', 'one primary path', 'external dependencies', 'trust boundaries'], + prompt: 'Analyze this repository, then use Archify to create a high-level architecture diagram. Show 8–12 core runtime components, one primary request or data path, external dependencies, ownership or trust boundaries, and put supporting detail in cards instead of adding more edges.', + }, + zh: { + title: '系统总览', question: '系统里有什么、归谁负责、彼此如何连接?', + summary: '用一张有边界的图展示核心组件、外部依赖、主路径和信任边界。', + useWhen: '适合新人上手、方案评审、仓库梳理和服务全景说明。', + avoidWhen: '如果重点是精确调用顺序、状态流转或字段级血缘,请换其他配方。', + include: ['8–12 个核心组件', '一条主路径', '外部依赖', '归属或信任边界'], + prompt: '分析这个仓库,然后用 Archify 生成高层系统架构图。展示 8–12 个核心运行时组件、一条主要请求或数据路径、外部依赖、归属或信任边界;支持性细节放进卡片,不要继续堆连线。', + }, + }, + { + id: 'deployment-ownership', type: 'architecture', proof: 'deployment-ownership', + presentation: { preset: 'blueprint', motion: 'trace', views: 'recommended' }, + signals: [['deployment topology', 14], ['region', 7], ['vpc', 9], ['cluster', 6], ['availability zone', 8], ['ownership', 7], ['cloud deployment', 12], ['部署拓扑', 14], ['区域', 6], ['集群', 6], ['可用区', 8], ['资源归属', 9], ['跨区', 8]], + en: { + title: 'Deployment ownership', question: 'Where does each workload run, and what crosses a boundary?', + summary: 'A deployment-focused map of regions, networks, clusters, workloads, stores, and cross-boundary mechanisms.', + useWhen: 'Cloud reviews, production readiness, multi-region planning, or infrastructure ownership handoffs.', + avoidWhen: 'Deployment facts are unknown or the real question is application behavior rather than placement.', + include: ['regions and networks', 'workload ownership', 'stateful services', 'named boundary crossings'], + prompt: 'Use Archify to draw the production deployment topology. Group resources by region, network, cluster, and owner; show workloads and stateful services; label every cross-boundary mechanism. Do not invent deployment facts—mark unknown areas explicitly. If the user wants a fail-closed deployment review, ask before setting meta.engineering_profile to deployment-ownership; otherwise leave the engineering profile unset.', + }, + zh: { + title: '部署与归属', question: '每个工作负载运行在哪里,哪些连接跨越了边界?', + summary: '围绕 Region、网络、集群、工作负载、存储和跨边界机制组织部署图。', + useWhen: '适合云上评审、生产就绪、多区域规划和基础设施交接。', + avoidWhen: '部署事实不清楚,或真正问题是应用行为而不是资源位置时不要使用。', + include: ['区域与网络', '工作负载归属', '有状态服务', '明确的跨边界机制'], + prompt: '用 Archify 绘制生产部署拓扑。按区域、网络、集群和负责人分组,展示工作负载与有状态服务,并标注每一种跨边界机制。不要编造部署事实,不确定的区域要明确标出。如果用户需要失败即阻断的部署评审,先征得确认,再把 meta.engineering_profile 设为 deployment-ownership;否则不要启用工程画像。', + }, + }, + { + id: 'agent-tool-call', type: 'workflow', proof: 'agent-tool-call', + presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' }, + start: { + en: { descriptionPrompt: 'Use Archify workflow mode to turn this description into a diagram: [paste the actors, main steps, decisions, approvals, and exception paths]. Use lanes for distinct owners, keep one unmistakable happy path, and mark missing ownership or unresolved branches instead of inventing them.' }, + zh: { descriptionPrompt: '用 Archify 工作流模式把下面的描述画成图:[粘贴参与者、主要步骤、决策、审批和异常路径]。不同负责方使用独立泳道,保留一条明确的成功主路径,缺失的负责人或未定分支要标明而不是编造。' }, + }, + signals: [['agent tool call', 16], ['tool call', 12], ['approval gate', 10], ['human in the loop', 9], ['mcp', 7], ['planner', 6], ['agent loop', 10], ['智能体工具调用', 16], ['工具调用', 12], ['审批门', 10], ['人在回路', 9], ['规划器', 6], ['智能体循环', 10]], + en: { + title: 'Agent tool-call loop', question: 'How does an agent plan, get permission, act, recover, and report?', + summary: 'A lane-based agent loop with policy gates, tool execution, exception recovery, evidence, and final response.', + useWhen: 'Explaining agent runtimes, MCP/tool orchestration, approvals, retries, or observability.', + avoidWhen: 'The goal is only to show static agent components or exact API message timing.', + include: ['request and planning', 'policy or approval gate', 'tool execution', 'exception and evidence paths'], + prompt: 'Use Archify workflow mode to explain this agent tool-call loop. Separate user surface, agent runtime, policy boundary, exception handling, tool execution, and observability into lanes. Make the successful path primary and show approval, retry, blocked, and evidence paths explicitly.', + }, + zh: { + title: '智能体工具调用', question: '智能体如何规划、获批、执行、恢复并汇报?', + summary: '用泳道表达策略门、工具执行、异常恢复、证据和最终回复。', + useWhen: '适合解释 Agent Runtime、MCP/工具编排、审批、重试和可观测性。', + avoidWhen: '如果只想看静态组件,或重点是精确 API 消息时序,请换其他配方。', + include: ['请求与规划', '策略或审批门', '工具执行', '异常与证据路径'], + prompt: '用 Archify 工作流模式解释这段智能体工具调用。把用户界面、Agent Runtime、策略边界、异常处理、工具执行和可观测性分成泳道;突出成功主路径,并明确展示审批、重试、阻塞和证据路径。', + }, + }, + { + id: 'delivery-workflow', type: 'workflow', proof: 'delivery-workflow', + presentation: { preset: 'classic', motion: 'trace', views: 'optional' }, + signals: [['ci/cd', 14], ['release workflow', 14], ['deployment pipeline', 11], ['pull request', 7], ['staging', 7], ['rollback', 8], ['发布流程', 14], ['流水线', 9], ['上线', 7], ['预发', 7], ['回滚', 8], ['审批发布', 10]], + en: { + title: 'Delivery workflow', question: 'How does a change move safely from commit to production?', + summary: 'A delivery flow with build, checks, environments, approvals, smoke tests, rollback, and ownership lanes.', + useWhen: 'CI/CD design, release reviews, deployment governance, or onboarding developers to delivery.', + avoidWhen: 'The question is where infrastructure runs or what states a deployment object can occupy.', + include: ['trigger and build', 'blocking checks', 'approval and environments', 'rollback and verification'], + prompt: 'Use Archify workflow mode to draw this delivery process from commit to production. Separate developer, CI, approval, environment, and exception lanes; mark blocking checks, smoke tests, ownership, and the rollback path. Keep one unmistakable happy path.', + }, + zh: { + title: '研发交付流程', question: '一次变更如何安全地从提交走到生产?', + summary: '展示构建、检查、环境、审批、冒烟、回滚和负责人泳道。', + useWhen: '适合 CI/CD 设计、发布评审、部署治理和研发新人上手。', + avoidWhen: '如果重点是基础设施位置或部署对象的状态集合,请换架构图或生命周期图。', + include: ['触发与构建', '阻断检查', '审批与环境', '回滚与验证'], + prompt: '用 Archify 工作流模式绘制从代码提交到生产发布的流程。拆分开发者、CI、审批、环境和异常泳道;标出阻断检查、冒烟测试、负责人和回滚路径,并保留一条一眼可见的成功主路径。', + }, + }, + { + id: 'incident-runbook', type: 'workflow', proof: 'incident-runbook', + presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' }, + signals: [['incident response', 15], ['runbook', 12], ['outage', 9], ['triage', 8], ['mitigation', 8], ['escalation', 7], ['事故处置', 15], ['故障', 9], ['应急预案', 12], ['排障', 9], ['缓解', 7], ['升级响应', 8]], + en: { + title: 'Incident runbook', question: 'How do responders detect, triage, mitigate, verify, and escalate?', + summary: 'An operational workflow that separates signals, responders, mitigation, communications, and recovery proof.', + useWhen: 'Incident playbooks, on-call handoffs, reliability reviews, and tabletop exercises.', + avoidWhen: 'The audience needs live metrics or a post-incident component topology instead of response actions.', + include: ['detection signal', 'triage owner', 'mitigation and rollback', 'verification and communication'], + prompt: 'Use Archify workflow mode to turn this incident runbook into responder lanes. Show detection, triage, mitigation, escalation, communication, rollback, and recovery verification. Separate decision gates from actions and make missing ownership visible.', + }, + zh: { + title: '事故处置 Runbook', question: '响应者如何发现、分诊、缓解、验证并升级?', + summary: '把信号、响应者、缓解动作、沟通和恢复证据拆成可执行流程。', + useWhen: '适合故障预案、On-call 交接、稳定性评审和桌面演练。', + avoidWhen: '如果受众需要实时指标仪表盘或事故后的组件拓扑,而不是响应动作,请换其他视图。', + include: ['发现信号', '分诊负责人', '缓解与回滚', '恢复验证与沟通'], + prompt: '用 Archify 工作流模式把事故处置预案画成响应者泳道。展示发现、分诊、缓解、升级、沟通、回滚和恢复验证;把决策门与操作分开,并让缺失的负责人清晰可见。', + }, + }, + { + id: 'api-request', type: 'sequence', proof: 'cache-miss', + presentation: { preset: 'classic', motion: 'trace', views: 'optional' }, + start: { + en: { descriptionPrompt: 'Use Archify sequence mode to draw this interaction: [paste the participants, calls, returns, fallback, and asynchronous side effects]. Keep message order unambiguous, labels short, and unknown behavior explicit. No repository is required.' }, + zh: { descriptionPrompt: '用 Archify 时序模式绘制下面的交互:[粘贴参与者、调用、返回、回退和异步副作用]。确保消息顺序无歧义、标签简短,并明确标注未知行为。不需要代码库。' }, + }, + signals: [['api request', 14], ['request response', 12], ['call chain', 11], ['cache miss', 13], ['jwt', 8], ['who calls whom', 12], ['api 请求', 14], ['请求响应', 12], ['调用链', 11], ['缓存未命中', 13], ['谁调用谁', 12], ['鉴权链路', 9]], + en: { + title: 'API request chain', question: 'Who calls whom, in what order, and what returns?', + summary: 'A time-ordered request path with authentication, cache fallback, persistence, return traffic, and async trace.', + useWhen: 'API documentation, debugging request latency, auth reviews, or explaining cache fallback.', + avoidWhen: 'Order is unimportant and the audience only needs the stable service topology.', + include: ['callers and callees', 'request and return messages', 'fallback or error path', 'async side effects'], + prompt: 'Use Archify sequence mode to show this request from caller to final response. Include authentication, cache hit or miss, persistence fallback, return messages, and asynchronous trace or event emission. Keep message labels short and order unambiguous.', + }, + zh: { + title: 'API 请求链', question: '谁调用谁、顺序如何、最终返回什么?', + summary: '按时间展示鉴权、缓存回退、持久化、返回流量和异步追踪。', + useWhen: '适合 API 文档、请求耗时排查、鉴权评审和缓存回退说明。', + avoidWhen: '如果顺序不重要,受众只需要稳定的服务拓扑,请用架构图。', + include: ['调用方与被调用方', '请求与返回消息', '回退或错误路径', '异步副作用'], + prompt: '用 Archify 时序模式展示从调用方到最终响应的完整请求。包含鉴权、缓存命中或未命中、持久化回退、返回消息,以及异步 Trace 或事件上报;消息标签保持简短,顺序必须明确。', + }, + }, + { + id: 'async-roundtrip', type: 'sequence', proof: 'async-roundtrip', + presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' }, + signals: [['async roundtrip', 14], ['webhook', 10], ['callback', 10], ['acknowledgement', 8], ['timeout', 7], ['retry message', 8], ['异步回调', 14], ['回调', 10], ['确认消息', 8], ['超时', 7], ['消息重试', 9], ['webhook', 10]], + en: { + title: 'Async roundtrip', question: 'What happens after the initial request returns?', + summary: 'A sequence view of enqueue, acknowledgement, background work, callbacks, retries, timeout, and final consistency.', + useWhen: 'Webhooks, jobs, queues, payment callbacks, eventual consistency, or async API contracts.', + avoidWhen: 'The primary question is topic topology and consumer ownership rather than time order.', + include: ['initial acknowledgement', 'queue or scheduler', 'background work', 'callback, retry, and timeout'], + prompt: 'Use Archify sequence mode to explain this asynchronous roundtrip. Show the initial acknowledgement, enqueue or scheduling step, background processing, callback or polling, retry and timeout behavior, and the point where the caller can observe final consistency.', + }, + zh: { + title: '异步往返链路', question: '初始请求返回之后,后台还会发生什么?', + summary: '按时间展示入队、确认、后台处理、回调、重试、超时和最终一致。', + useWhen: '适合 Webhook、后台任务、队列、支付回调、最终一致和异步 API 契约。', + avoidWhen: '如果重点是 Topic 拓扑和消费者归属,而不是时间顺序,请用事件数据流配方。', + include: ['初始确认', '队列或调度器', '后台处理', '回调、重试与超时'], + prompt: '用 Archify 时序模式解释这段异步往返链路。展示初始确认、入队或调度、后台处理、回调或轮询、重试与超时,以及调用方何时能观察到最终一致结果。', + }, + }, + { + id: 'data-lineage', type: 'dataflow', proof: 'product-analytics', + presentation: { preset: 'classic', motion: 'trace', views: 'recommended' }, + signals: [['data lineage', 15], ['etl', 12], ['warehouse', 9], ['pii', 11], ['governance', 9], ['analytics pipeline', 12], ['数据血缘', 15], ['数据管道', 11], ['数仓', 9], ['治理', 9], ['隐私数据', 10], ['用户同意', 9]], + en: { + title: 'Data lineage', question: 'Where does data come from, how does it change, and who consumes it?', + summary: 'A governed path from sources through consent, transforms, sensitive stores, warehouse, and consumers.', + useWhen: 'Analytics architecture, ETL/ELT review, PII assessment, warehouse design, or model feature lineage.', + avoidWhen: 'The audience needs request timing or operational task ownership rather than data assets.', + include: ['sources and assets', 'transform stages', 'classification or consent', 'stores and consumers'], + prompt: 'Use Archify dataflow mode to map this data lineage. Name every data asset and transform, show consent or classification boundaries, distinguish streaming from batch paths, and identify stores plus downstream consumers. Do not use unlabeled flows.', + }, + zh: { + title: '数据血缘', question: '数据从哪里来、如何变化、最终被谁消费?', + summary: '从来源经过同意、转换、敏感存储、数仓直到消费者的治理路径。', + useWhen: '适合分析架构、ETL/ELT 评审、PII 评估、数仓设计和特征血缘。', + avoidWhen: '如果受众需要请求时序或操作负责人,而不是数据资产,请换其他配方。', + include: ['数据来源与资产', '转换阶段', '分类或同意边界', '存储与消费者'], + prompt: '用 Archify 数据流模式梳理这段数据血缘。为每个数据资产和转换命名,展示用户同意或数据分类边界,区分流式与批处理路径,并标明存储和下游消费者;所有数据流都必须有标签。', + }, + }, + { + id: 'event-stream', type: 'dataflow', proof: 'event-stream', + presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' }, + start: { + en: { descriptionPrompt: 'Use Archify dataflow mode to map this data journey: [paste the sources, data assets, transforms, stores, boundaries, and consumers]. Label every flow, distinguish streaming from batch where relevant, and mark unknown classifications or ownership instead of inventing them.' }, + zh: { descriptionPrompt: '用 Archify 数据流模式梳理下面的数据路径:[粘贴来源、数据资产、转换、存储、边界和消费者]。为每条数据流标注名称,在有意义时区分流式与批处理,未知的分类或归属要标明而不是编造。' }, + }, + signals: [['event stream', 15], ['kafka topology', 14], ['topic', 8], ['consumer group', 11], ['dead letter', 10], ['dlq', 10], ['事件流', 15], ['kafka 拓扑', 14], ['主题', 7], ['消费者组', 11], ['死信', 10], ['事件地铁图', 12]], + en: { + title: 'Event-stream topology', question: 'Which events move through which topics, processors, groups, and failure paths?', + summary: 'A stream map of producers, topics, ordered processors, consumer groups, state, replay, and DLQ.', + useWhen: 'Kafka/event-platform design, stream processing reviews, ownership, replay, and failure handling.', + avoidWhen: 'Topic names, consumer groups, and delivery semantics are not known—use a generic workflow instead.', + include: ['producers and event names', 'topics and ordering', 'processors and consumer groups', 'state, replay, and DLQ'], + prompt: 'Use Archify dataflow mode to draw this event-stream topology. Name producers, events, topics, ordered processors, consumer groups, state stores, replay paths, and the DLQ. Show ownership and delivery semantics only when supported by evidence.', + }, + zh: { + title: '事件流拓扑', question: '哪些事件经过哪些 Topic、处理器、消费者组和失败路径?', + summary: '展示生产者、Topic、有序处理器、消费者组、状态、重放和 DLQ。', + useWhen: '适合 Kafka/事件平台设计、流处理评审、归属、重放和失败处理。', + avoidWhen: '如果 Topic、消费者组和投递语义都不清楚,请先用通用工作流,不要编造事件拓扑。', + include: ['生产者与事件名', 'Topic 与顺序', '处理器与消费者组', '状态、重放与 DLQ'], + prompt: '用 Archify 数据流模式绘制这段事件流拓扑。命名生产者、事件、Topic、有序处理器、消费者组、状态存储、重放路径和 DLQ;只有在证据充分时才标注归属和投递语义。', + }, + }, + { + id: 'object-lifecycle', type: 'lifecycle', proof: 'agent-run', + presentation: { preset: 'classic', motion: 'trace', views: 'optional' }, + start: { + en: { descriptionPrompt: 'Use Archify lifecycle mode to model this object: [paste its states, transition events, waits, retries, cancellation, and terminal outcomes]. Separate active, waiting, recoverable-failure, and terminal states, and never hide an ending. No repository is required.' }, + zh: { descriptionPrompt: '用 Archify 生命周期模式建模这个对象:[粘贴它的状态、转换事件、等待、重试、取消和终态]。分开执行、等待、可恢复失败和终态,不要隐藏任何结束方式。不需要代码库。' }, + }, + signals: [['state machine', 15], ['object lifecycle', 14], ['status transition', 11], ['terminal state', 9], ['retry state', 8], ['状态机', 15], ['生命周期', 13], ['状态流转', 11], ['终态', 9], ['等待态', 8], ['重试状态', 8]], + en: { + title: 'Object lifecycle', question: 'Which states exist, what events move between them, and how does it end?', + summary: 'A state model with active work, waits, retries, cancellation, failure, and explicit terminal outcomes.', + useWhen: 'Tasks, orders, tickets, subscriptions, jobs, agent runs, or any durable object with status.', + avoidWhen: 'The object has no durable state and the real question is participant interaction over time.', + include: ['start and active states', 'event-labelled transitions', 'wait and retry states', 'all terminal outcomes'], + prompt: 'Use Archify lifecycle mode to model this object. Separate main progress, waiting or interruption states, and terminal outcomes. Label transitions with events, include retry, cancellation, timeout, success, and failure where real, and never hide an ending.', + }, + zh: { + title: '对象生命周期', question: '有哪些状态、什么事件触发流转、最终如何结束?', + summary: '展示执行、等待、重试、取消、失败以及明确终态的状态模型。', + useWhen: '适合任务、订单、工单、订阅、作业、Agent Run 等带持久状态的对象。', + avoidWhen: '对象没有持久状态,真正问题是参与者随时间的交互时,请使用时序图。', + include: ['开始与执行态', '带事件的转换', '等待与重试态', '所有终态'], + prompt: '用 Archify 生命周期模式建模这个对象。分开主进度、等待或中断状态和终态;用事件标注转换,并在真实存在时展示重试、取消、超时、成功和失败,不能隐藏任何结束方式。', + }, + }, + { + id: 'deployment-lifecycle', type: 'lifecycle', proof: 'deployment-lifecycle', + presentation: { preset: 'signal-flow', motion: 'trace', views: 'recommended' }, + signals: [['deployment lifecycle', 15], ['release state', 10], ['promotion state', 9], ['approval status', 8], ['rollback state', 10], ['部署生命周期', 15], ['发布状态', 10], ['晋级', 7], ['审批状态', 8], ['回滚状态', 10]], + en: { + title: 'Deployment lifecycle', question: 'What state is a release in, and what can happen next?', + summary: 'A deployment state model covering queued, building, verifying, approval, promotion, rollback, and terminal outcomes.', + useWhen: 'Release controllers, GitOps reconciliation, environment promotion, or deployment status APIs.', + avoidWhen: 'The question is the human/CI sequence of delivery actions rather than the deployment object state.', + include: ['queued and running states', 'verification and approval', 'promotion and rollback', 'success, failure, cancellation'], + prompt: 'Use Archify lifecycle mode to model the deployment object. Show queued, building, verifying, waiting for approval, promoting, rolling back, and every terminal outcome. Label the events and guards that permit each transition.', + }, + zh: { + title: '部署生命周期', question: '一次发布当前处于什么状态,下一步可能发生什么?', + summary: '覆盖排队、构建、验证、审批、晋级、回滚和终态的部署状态模型。', + useWhen: '适合发布控制器、GitOps 对账、环境晋级和部署状态 API。', + avoidWhen: '如果重点是人员与 CI 的交付动作顺序,而不是部署对象状态,请用交付工作流。', + include: ['排队与执行态', '验证与审批', '晋级与回滚', '成功、失败与取消'], + prompt: '用 Archify 生命周期模式建模部署对象。展示排队、构建、验证、等待审批、晋级、回滚以及所有终态,并标注允许每次状态转换的事件和守卫条件。', + }, + }, +]; + +export const SCENARIO_RECIPES = Object.freeze(RAW_RECIPES.map((recipe) => Object.freeze({ + ...recipe, + presentation: Object.freeze({ ...recipe.presentation }), + ...(recipe.start ? { start: Object.freeze({ + en: Object.freeze({ ...recipe.start.en }), + zh: Object.freeze({ ...recipe.start.zh }), + }) } : {}), + signals: Object.freeze(recipe.signals.map((signal) => Object.freeze(signal.slice()))), + en: Object.freeze({ ...recipe.en, include: Object.freeze(recipe.en.include.slice()) }), + zh: Object.freeze({ ...recipe.zh, include: Object.freeze(recipe.zh.include.slice()) }), +}))); + +export function detectGuideLanguage(value = '') { + return /[\u3400-\u9fff]/u.test(value) ? 'zh' : 'en'; +} + +export function startPromptsFor(recipe, lang = 'en') { + const language = lang === 'zh' ? 'zh' : 'en'; + const copy = recipe[language]; + const descriptionPrompt = recipe.start?.[language]?.descriptionPrompt; + if (!descriptionPrompt) { + throw new Error(`Scenario recipe ${JSON.stringify(recipe.id)} does not define a ${language} start prompt.`); + } + const repositoryPrompt = recipe.type === 'architecture' + ? copy.prompt + : language === 'zh' + ? `先检查这个仓库里的相关证据,然后${copy.prompt}不要编造代码无法支持的行为。` + : `Inspect this repository for evidence, then ${copy.prompt.charAt(0).toLowerCase()}${copy.prompt.slice(1)} Do not invent behavior that the code does not support.`; + return { descriptionPrompt, repositoryPrompt }; +} + +function normalized(value) { + return String(value || '').normalize('NFKC').toLowerCase().replace(/[\s_]+/g, ' ').trim(); +} + +function localized(recipe, lang) { + const copy = recipe[lang === 'zh' ? 'zh' : 'en']; + return { + id: recipe.id, + type: recipe.type, + proof: recipe.proof, + presentation: { ...recipe.presentation }, + ...copy, + include: copy.include.slice(), + }; +} + +export function listScenarioRecipes(lang = 'en') { + return SCENARIO_RECIPES.map((recipe) => localized(recipe, lang)); +} + +function scoreRecipe(recipe, query) { + const text = normalized(query); + if (!text) return { recipe, score: 0, matched: [] }; + if (text === recipe.id || text === recipe.id.replace(/-/g, ' ')) { + return { recipe, score: 100, matched: [recipe.id] }; + } + let score = 0; + const matched = []; + for (const [signal, weight] of recipe.signals) { + if (text.includes(normalized(signal))) { + score += weight; + matched.push(signal); + } + } + return { recipe, score, matched }; +} + +export function recommendScenario(query, options = {}) { + const lang = options.lang === 'zh' || options.lang === 'en' ? options.lang : detectGuideLanguage(query); + const ranked = SCENARIO_RECIPES.map((recipe) => scoreRecipe(recipe, query)) + .sort((left, right) => right.score - left.score || SCENARIO_RECIPES.indexOf(left.recipe) - SCENARIO_RECIPES.indexOf(right.recipe)); + const winner = ranked[0].score > 0 ? ranked[0] : { recipe: SCENARIO_RECIPES[0], score: 0, matched: [] }; + const confidence = winner.score >= 14 ? 'high' : winner.score >= 7 ? 'medium' : 'low'; + return { + ok: true, + mode: 'recommendation', + lang, + query: String(query || ''), + confidence, + matchedSignals: winner.matched.slice(), + recommendation: localized(winner.recipe, lang), + alternatives: ranked.filter((entry) => entry.recipe.id !== winner.recipe.id && entry.score > 0) + .slice(0, 2) + .map((entry) => ({ ...localized(entry.recipe, lang), score: entry.score })), + }; +} + +export function formatScenarioList(lang = 'en') { + const isZh = lang === 'zh'; + const heading = isZh + ? `Archify 场景配方(${SCENARIO_RECIPES.length})` + : `Archify scenario recipes (${SCENARIO_RECIPES.length})`; + const intro = isZh + ? '先选择你要回答的问题,再选择图表类型。可运行:archify guide "你的场景"' + : 'Choose the question before the diagram type. Run: archify guide "your scenario"'; + return [heading, '', intro, '', ...listScenarioRecipes(lang).flatMap((recipe) => [ + `${recipe.id} [${recipe.type}] ${recipe.title}`, + ` ${recipe.question}`, + ])].join('\n'); +} + +export function formatScenarioRecommendation(result) { + const isZh = result.lang === 'zh'; + const recipe = result.recommendation; + const labels = isZh ? { + heading: '推荐', question: '要回答的问题', use: '适合', avoid: '不要这样用', include: '必须包含', presentation: '表现建议', prompt: '可直接复制的提示词', alternatives: '其他可能', confidence: '置信度', + } : { + heading: 'Recommendation', question: 'Question answered', use: 'Use when', avoid: 'Avoid when', include: 'Must include', presentation: 'Presentation', prompt: 'Copy-ready prompt', alternatives: 'Other possible fits', confidence: 'Confidence', + }; + const lines = [ + `${labels.heading}: ${recipe.title} [${recipe.type}]`, + `${labels.confidence}: ${result.confidence}`, + `${labels.question}: ${recipe.question}`, + '', + `${labels.use}: ${recipe.useWhen}`, + `${labels.avoid}: ${recipe.avoidWhen}`, + `${labels.include}: ${recipe.include.join(isZh ? '、' : '; ')}`, + `${labels.presentation}: ${recipe.presentation.preset} · ${recipe.presentation.motion} · views ${recipe.presentation.views}`, + '', + `${labels.prompt}:`, + recipe.prompt, + ]; + if (result.alternatives.length) { + lines.push('', `${labels.alternatives}: ${result.alternatives.map((item) => `${item.title} [${item.type}]`).join(' · ')}`); + } + return lines.join('\n'); +} + +export function publicGuideData() { + return SCENARIO_RECIPES.map((recipe) => ({ + ...localized(recipe, 'en'), + en: recipe.en, + zh: recipe.zh, + signals: recipe.signals.map(([signal, weight]) => [signal, weight]), + })); +} diff --git a/tools/archify/references/authoring-contract.md b/tools/archify/references/authoring-contract.md new file mode 100644 index 0000000..890d5c6 --- /dev/null +++ b/tools/archify/references/authoring-contract.md @@ -0,0 +1,191 @@ +# Authoring contract + +Read this reference only after the Fast authoring path calls for more detail. The schemas and examples remain authoritative. + +## Schema lookup + +Read both the mode schema and `schemas/common.schema.json`. The mode schemas use `$ref`, so the common file is where shared enums live. + +- `componentType`: `frontend`, `backend`, `database`, `cloud`, `security`, `messagebus`, `external` +- `variant`: `default`, `emphasis`, `security`, `dashed` +- Relationship IDs use the shared identifier pattern and must be unique in their collection. + +Do not invent fields. Use the nearest matching example for structure, then author fresh IDs, wording, facts, and layout. + +## Workflow layout contracts + +Use schema v2 for new workflows and keep schema v1 when an existing source must +retain fixed geometry. In both versions, `col` stays in `0..5` and semantic +edge labels are never deleted as a spacing repair. Do not change only +`schema_version` when absolute coordinates exist: follow the canonical +[migration and layout-receipt contract](../renderers/workflow/README.md#migration-and-layout-receipt). +The complete normative invariants live in the workflow renderer's +[layout contracts](../renderers/workflow/README.md#layout-contracts). + +## Legend contract + +Omit `meta.legend` for the truthful default: `auto` lists only semantic kinds +present in typed IR. Use `mode: "all"` for a renderer reference or +`mode: "hidden"` to remove the full legend. Under `entries`, only keys listed +by the selected mode schema are valid; each key accepts `label`, `visible`, or +both. `visible: true` may show an unused supported convention, while +`visible: false` hides it. `hidden` cannot be overridden. + +A label override changes reader wording only. Never infer a kind from prose or +use the legend to compensate for missing nodes, states, messages, or flows. +Long labels are measured and wrap into deterministic rows. Architecture's +implicit automatic viewBox grows from that same measured footprint. For +backwards compatibility, a legacy document with no `meta.legend` may omit an +implicit auto legend that cannot fit its explicit viewBox; this never changes +its typed topology. Adding `meta.legend` makes the presentation intentional and +strict: if its resolved labels cannot fit the authored viewBox, shorten or hide +them, or widen the viewBox using the emitted diagnostic. + +## Language consistency + +Choose one primary authored language. An explicit user choice wins; otherwise +use the language of the request, or the conversation's dominant language when +the request itself is language-neutral. Separately choose the Viewer locale. +For supported languages, always write the matching `meta.locale`: `"en"` for +English or `"zh-CN"` for Simplified Chinese. The renderer consumes the authored +locale without inferring language from diagram strings. Documents that omit it +remain valid and default to English. + +`meta.locale` controls only renderer-owned reader surfaces: ``, the +document-title suffix, default SVG description and focus labels, default legend +labels, and fixed Viewer controls, statuses, accessibility names, and errors. +It never translates authored content. Apply the primary language separately to +titles, subtitles, node and relationship copy, boundaries, lanes, groups, +guided views, legend label overrides, and cards. A bilingual diagram still +chooses one primary locale for the Viewer; follow an explicit primary-language +request, then prompt order or conversation dominance. + +For a requested language outside `en` and `zh-CN`, do not write an unsupported +locale. Keep every reader-facing authored string in the requested language, +omit `meta.locale` so the renderer safely uses English, and explicitly tell the +user that fixed Viewer UI and `` remain English and the artifact is +not fully localized. The fallback applies only to renderer-owned surfaces; it +never permits authored copy to fall back to English. Do not silently substitute +`zh-CN` for another language or Chinese locale. + +Keep exact product names, code identifiers, commands, protocols, API paths, and +environment names intact. Those terms may remain English inside localized copy, +but surrounding explanatory prose must still use the selected language. +Renderer-owned default legend labels follow `meta.locale`; author a +`meta.legend.entries.*.label` override only when the diagram needs different +domain wording, and keep that authored override in the primary language. + +## Visual preset default + +Omit `meta.visual_preset` by default. The renderer then opens the diagram in +`classic` for both light and dark color modes. Color mode and visual preset are +independent viewer state: switching Light / Dark must preserve the current +preset. Author `signal-flow`, `blueprint`, or `editorial` only when the user +explicitly requests that visual style. + +## Engineering profile default + +Omit `meta.engineering_profile` for an ordinary system architecture. Region, +cluster, and security boundary wording do not by themselves enable an +engineering profile. Enable `deployment-ownership` only when the user +explicitly asks for a production deployment topology, ownership handoff, or +fail-closed deployment review and the source facts are known. Once enabled, +do not remove the engineering profile merely to pass validation; repair the +authored facts or report the diagnostics truthfully. + +## Title hierarchy + +Use one concise title and let the diagram carry the explanation. Omit +`meta.subtitle` by default, and never use it to restate the title, nodes, edges, +or cards. Include one short supporting line only when the user explicitly asks +for a subtitle; an omitted or blank subtitle must not leave an empty visual row +in the generated viewer. + +## Executable geometry rules + +- Node anchors start at side midpoints. `left`/`right` change the horizontal endpoint; `top`/`bottom` change the vertical endpoint. For an automatic Architecture relationship, unobstructed facing ports whose axis offset is under 16px may share one horizontal or vertical axis when both endpoints retain the 16px corner gutter. If exactly one endpoint belongs to a spread group, only its unshared counterpart moves; relationships spread at both endpoints keep their distinct ports and outside bridge. +- A side is a direction contract. The first and final route segment must be perpendicular and outward/inward in the named direction. +- Automatic Port Spread is a default renderer behavior for architecture, workflow, data-flow, and lifecycle diagrams. Shared automatic endpoints spread deterministically and symmetrically with a 16px corner gutter. It does not apply to sequence messages, single relationships, or explicit `via`, `channelX`, `channelY`, `labelAt`, or non-`auto` routes. +- Showcase route rhythm: every nonzero segment must be at least 8px; every interior segment must be at least 16px. When spread ports are nearly parallel, the router uses a 24px endpoint stub and a 16px outside bridge instead of manufacturing a tiny dogleg. +- Shared endpoint corridors are allowed only when they remain semantically unambiguous. Unrelated collinear overlap of 8px or more fails showcase. +- Container borders are intentional pass-through geometry, but a long edge running along a structural border is not. +- An edge crossing an unrelated opaque node is always a hard failure, independent of quality profile. + +### Spacing and labels + +Spacing recommendations mean clear gap between boxes, not center distance. A 200px center distance between 165px-wide nodes leaves only 35px of clear gap. + +For a relationship label, require: + +```text +clear gap > label mask width + 8px breathing room +label mask width ≈ 6.5px × ASCII units + 13px +CJK characters count as two units +``` + +Relationship labels are semantic data. If the gap is too small, move the label, +adjust the route or spacing, then shorten the wording while preserving meaning. +Omit only wording already fully implied by both endpoints and carrying no +protocol, action, direction, synchronous/asynchronous behavior, or +cross-boundary mechanism. Preserve every meaningful label. +Deleting it is not a spacing repair. If a relationship starts unlabeled because +its endpoints fully imply it, explain why the wording is redundant; this is a +semantic authoring choice, not a spacing repair. In workflow v2, let the compiler +allocate its measured mask before applying a diagnosed `labelAt`, +`labelDx`/`labelDy`, or `labelSegment`. Apply one diagnosed geometry control at +a time. + +### Repair order + +1. Fix missing/invalid `meta.quality_profile` and schema errors. +2. Fix node overlap or out-of-range placement. +3. Fix edge-through-node and endpoint-direction errors. +4. Fix crossings, ambiguous corridors, border runs, and route rhythm. +5. Fix label-to-node, label-to-label, then label-to-route clearance. + +Run `validate` after every edit. Consume `diagnostics[]` by stable `code`, exact `subject`, measured `evidence`, and `supportedFixes`. If the diagnostic gives `labelAt`, use that point instead of estimating another offset. + +## Mode placement + +### Architecture + +Use one left-to-right spine with short vertical branches. Prefer 6–12 primary components and group only real ownership, trust, process, or deployment boundaries. Boundaries do not replace relationships. + +Grid placement is preferred when the schema supports it. Free positions are appropriate for a bounded exception, not for prose-level coordinate planning. Keep external actors outside the system boundary when that is factually true. + +### Workflow + +Lanes express responsibility or phase. Columns `0..5` express logical +progression. Start new workflows on `readable-v2`; retain `fixed-v1` only for +legacy geometry compatibility. Keep the happy path monotonic, preserve semantic +edge labels, and route retries and exception returns outside the main lane +corridor. + +### Sequence + +Participants are ordered by conversation role. Messages own their vertical order. Use return/async/security variants for meaning, not decoration; sequence does not use Automatic Port Spread. + +### Dataflow + +Stages express transformation or custody. Rows separate parallel streams. Label only data contracts, classifications, or cross-boundary movement that is not obvious. + +### Lifecycle + +Main phases use columns `0..4`; event and terminal bands use columns `0..2`. +Event/terminal column `N` aligns to the same x coordinate as main column +`N + 2`. A recoverable failure needs a real transition back to an active state. +A card or guided view saying “retry” is not topology. + +## Repository evidence + +When an architecture diagram must reflect real code, inspect repository +entrypoints, runtime boundaries, storage, transports, and deployment +configuration before authoring. Record only evidence you actually verified. +`--repo-root ` is architecture-only and is accepted by architecture +`render`, `validate`, `deliver`, `preview`, and `compare`; workflow, sequence, +dataflow, and lifecycle reject it. Never infer runtime causality from file +proximity or naming alone. + +## Hand-placed fallback + +Use only when no renderer can run. Start from `assets/template.html`, keep semantic CSS classes, preserve the inline SVG/accessibility structure, and run the delivery visual checklist. Never introduce inline literal colors that break dark/light parity. diff --git a/tools/archify/references/brand-marks.md b/tools/archify/references/brand-marks.md new file mode 100644 index 0000000..5541178 --- /dev/null +++ b/tools/archify/references/brand-marks.md @@ -0,0 +1,65 @@ +# Brand marks + +Use a brand mark only when a real product, provider, model family, channel, or +service identity helps the reader. Semantic `type` still explains what the node +does; `brand` explains whose product it is. + +## Agent decision path + +1. Search the built-in catalogue when the request names a recognizable brand: + + ```bash + node bin/archify.mjs brands "Claude" --json + ``` + +2. Put the returned canonical ID in the node, participant, or state: + + ```json + { + "id": "planner", + "type": "backend", + "label": "Claude", + "brand": "claude" + } + ``` + +3. If there is no catalogue match and the user supplied the official website, + capture its icon explicitly: + + ```bash + node bin/archify.mjs brands capture "https://partner.example.com" --json + ``` + + Put the command's digest-pinned `brand` value in the authored node: + + ```json + { + "id": "partner", + "type": "external", + "label": "Partner portal", + "brand": { + "url": "https://partner.example.com", + "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + } + } + ``` + +4. If there is no match and no user-provided URL, omit `brand`. Do not invent a + URL or silently assign a visually similar company. + +Known-brand URLs resolve to the bundled vector instead of using the network. +Unknown URL capture accepts only bounded raster image formats, blocks +credentials, nonstandard public ports, and private or link-local destinations, +uses bounded concurrency and one total deadline, and returns the captured +content digest. Later render and validate operations require that exact digest; +blocked, unavailable, changed, oversized, or unsafe content fails closed instead +of silently changing the artifact. + +The final artifact never fetches a brand asset when opened. Preset vectors and +digest-verified captured site icons remain embedded in SVG, PNG, WebP, JPEG, +Share Card, and WebM exports. + +Use `node bin/archify.mjs brands --json` to inspect all canonical IDs, aliases, +categories, domains, and provenance. Current categories cover AI, cloud, +engineering, data, collaboration, business systems, channels, languages, and +frameworks. diff --git a/tools/archify/references/delivery-contract.md b/tools/archify/references/delivery-contract.md new file mode 100644 index 0000000..31f2c1d --- /dev/null +++ b/tools/archify/references/delivery-contract.md @@ -0,0 +1,112 @@ +# Delivery contract + +## Validate and deliver + +Use `validate` after every candidate edit. Use final atomic delivery only after the candidate is frozen: + +```bash +node bin/archify.mjs deliver --quality showcase --json +``` + +Deliver reads the specification once, writes those exact bytes to a private same-directory candidate snapshot, renders that snapshot, runs the complete artifact checker, and only replaces the target after all artifact checks pass. The JSON receipt includes SHA-256 and byte counts for both `specification` and `artifact`. Renderer, checker, receipt, or commit failure exits non-zero, removes private state, preserves the previous trusted artifact, and never invokes an opener. + +Run `visual-check` only after `deliver` exits zero for the current candidate. If +delivery fails and the output path already exists, that path still names the +previous trusted artifact; running `visual-check` then would measure and capture +stale output, not the rejected candidate. Report the delivery diagnostics and +repair the source before collecting new visual evidence. + +The delivery interface exposes three separate claims: + +1. `deliver` proves deterministic artifact checks and byte identity. +2. `visual-check` collects automated browser evidence from the exact artifact. +3. Perceptual visual review records a human or image-capable reviewer's judgment. + +Passing one claim never implies either of the others. Never claim that the deterministic receipt includes visual review. It does not include browser evidence either. + +## Automated browser evidence + +After delivery, inspect the exact trusted HTML without rerendering or modifying +it: + +```bash +node bin/archify.mjs visual-check --json +``` + +The zero-dependency command uses Chrome/Chromium through the DevTools pipe. It +measures light-theme containment at 1440×900, 1600×1000, 1920×1080, and +2048×1320, then captures light/dark screenshots at 1440×900 and 2048×1320. It +writes four PNG sidecars, one relative-path HTML contact sheet, and one JSON +receipt beside the artifact. The receipt binds the source artifact SHA-256 and +byte count, identifies `evidenceKind: "automated-browser"`, records READ plus +Still runtime state, and always reports `visualReview: "pending"`; automated +browser evidence cannot claim perceptual review. + +`browser_evidence` in the handoff records only the outcome of this automated command: + +- `passed` maps from exit 0 and receipt `status: "pass"` only after every required measurement and capture completes and passes. +- `failed` maps from exit 1 and receipt `status: "fail"` when the inspection finds a defect, the command fails, or a runtime/capture error leaves the evidence incomplete. +- `skipped` maps only from exit 2 and receipt `status: "skipped"` when Chrome/Chromium is unavailable and the inspection does not run. + +Runtime or capture failures leave incomplete evidence and must not be normalized to `skipped`. Failed or skipped capture runs remove stale +image/contact-sheet sidecars rather than presenting prior evidence as current. +They do not invalidate an already successful deterministic delivery, and they +do not turn a perceptual visual review into passed or failed. Retry an +environmental failure through the supported command in a browser-capable +execution context when practical. Keep the packaged transport unchanged unless +the failure reproduces through that seam in a capable environment. + +## Optional opening + +Add `--open` only when the user wants an immediate local preview. It runs after that atomic commit, uses one argument-array OS opener with a five-second bound, and records `open.status`. Keep it off for CI, unattended agents, and non-interactive environments. Failure or unsupported opening does not invalidate delivery; its status proves only whether the local opener invocation succeeded. + +## Last-Good Live Preview + +For an active desktop authoring loop only: + +```bash +node bin/archify.mjs preview .json .html --quality showcase +``` + +Preview watches one explicit input on loopback, binds each stable digest to a private snapshot, and advances only after the existing verified delivery pipeline passes. Invalid, half-written, deleted, or superseded input leaves the previous verified revision on screen and on disk. Identical bytes do not rebuild or reload. + +The preview runtime ships inside the zero-dependency Skill ZIP and must work without `node_modules`. + +Never start it by default. Do not use it for CI, unattended agents, remote sharing, or mobile use. `--no-open` is only for a user who will open the printed local URL or for loop testing. Stop it with Ctrl-C before handoff. Server state, port, source path, diagnostics, error text, and reload tokens must never enter the generated artifact or any export. + +## Perceptual delivery gate + +Automated validation and browser evidence cannot prove visual polish. After deterministic delivery, inspect the actual HTML in a capable browser or render the evidence screenshots with an image reader. Check both themes when changed, the default READ view, line crossings/corridors, label masks, node/card fit, focus/search/passport closure, and export cleanliness. + +For the default standalone desktop viewer, measure 1440×900, 1600×1000, and 1920×1080. When the artifact is intended for a large desktop display, also measure 2048×1320. A first-screen pass requires `document.documentElement.scrollWidth <= window.innerWidth` and `scrollHeight <= window.innerHeight` at every checked size. At the largest checked viewport, inspect the rendered composition for a conspicuous empty lower band: the main panel and necessary conclusion cards should use the available height as a balanced whole, not collapse into a shallow strip. If a desktop viewport overflows, repair the authored composition by removing only genuinely redundant content or compacting spacing before shrinking nodes, labels, or the main panel. Do not hide overflow, clip content, introduce an internal diagram scroller, or reduce node/label typography to make the measurement pass. Narrow/mobile containment may retain vertical page scrolling. + +A manual browser record is supplementary to the automated status. Reproducing the same coverage requires all four exact viewport measurements, both endpoint themes, and an artifact-bound record of the inspected SHA-256 and byte count. It never changes `browser_evidence`: when Chrome/Chromium is unavailable, that status remains `skipped` even when the manual browser record is complete and `visual_review: passed`; an automated `failed` result likewise remains `failed`. An unconstrained browser glance can support perceptual review only. + +Report exactly one truthful status: + +- `visual_review: passed` — only after inspecting the rendered artifact. +- `visual_review: skipped (image reader unavailable)` — when no capable visual surface exists. +- `visual_review: failed` — with the concrete visible defect. + +Use `correction_rounds: 0`, `correction_rounds: 1`, or `correction_rounds: 2`; never exceed a maximum of two focused correction rounds. Never report `visual_review: passed` without inspecting the artifact. + +If visual review changes the candidate, validation and delivery must run again because the prior frozen specification receipt is no longer current. + +## Handoff receipt + +Return: + +```text +diagram_type: architecture|workflow|sequence|dataflow|lifecycle +output: /absolute/path/to/file.html +specification_sha256: +artifact_sha256: +validation: 9/9 showcase, 0 errors, 0 warnings +browser_evidence: passed|failed|skipped +visual_review: passed|skipped (image reader unavailable)|failed +correction_rounds: 0|1|2 +``` + +Derive `browser_evidence` only from the latest artifact-bound `visual-check` receipt. Record any manual browser work separately with its artifact binding, viewport/theme scope, and observations; never use it or `visual_review` to overwrite the automated status. + +Opening, preview status, Share Cards, and other viewer exports are not validation claims. diff --git a/tools/archify/references/viewer-runtime.md b/tools/archify/references/viewer-runtime.md new file mode 100644 index 0000000..919b71b --- /dev/null +++ b/tools/archify/references/viewer-runtime.md @@ -0,0 +1,45 @@ +# Viewer Runtime reference + +Read this only when the user asks for a reader-facing capability. Ordinary generation does not require implementing or re-documenting these features; they are already in the generated HTML. + +## Exploration + +- Diagram Guide lists current actions and shortcuts. +- Reading Depth starts at READ at the default 100% scale, reveals FULL detail at 175%, and falls back to MAP only below 100%. Focus, story, route, and semantic interactions reveal their exact facts at any scale. +- Semantic Lens summarizes selected node/relationship kinds without changing authored geometry. +- Intent Trace previews a fine-pointer or keyboard target before committed focus. +- Node Finder searches labels and stable IDs. +- Semantic Passport opens on focus, shows authored upstream/downstream facts, supports a copyable deep link, has an explicit close action, closes on true outside activation and Escape, and never enters canonical export. +- Semantic Radar mirrors the visible viewport and authored graph without becoming a second source of truth. +- Direct Relationship Pin makes a unique compiled relationship operable while preserving the authored line and stable relationship identity. It must fail closed on conflicting source/target/label/ID metadata. +- Route Probe resolves exactly two endpoints over authored directed relationships. It never infers a route from geometry. + +## Guided views and story + +`meta.views` may define at most five curated chapters using stable node IDs. The Named Chapter Rail, Chapter Delta Preview, Story Beat Navigator, Story Follow Camera, Story Director Strip, Story Horizon, and Shareable Story Moment links all derive from that one authored array; none owns parallel topology or layout. + +Story transitions classify only the exact relationship between adjacent authored stops: forward, reverse, multiple, or grouped/no direct link. Never infer a transitive edge, verb, causality, or runtime behavior from proximity, kinds, or story order. Playback is reader-started, bounded, stale-safe, and motion-governed. + +## Motion and presentation + +`meta.animation: "trace"` enables a finite reader-controlled Live/Still trace. Static is the default. Still, reduced motion, page hiding, print, and canonical export preserve complete static meaning. Presentation Stage changes viewer chrome and framing, never authored geometry. This is not a mobile product feature; narrow layouts get containment only. + +## Canonical exports + +The export menu can copy/download full-diagram PNG, download JPEG/WebP, download a dual-theme SVG, and record a trace-enabled WebM. Viewer state—Guide, Lens, finder, focus, route, story, camera, radar, presentation, motion ownership, and temporary overlays—must be removed from canonical export. + +### Share Card + +The optional 1200×630 Share Card PNG is for README, release, social, or launch previews. It uses the current theme and visual preset, contains the complete canonical diagram without cropping, and never claims validation. Copy Share Card reuses the same canonical PNG when clipboard image writes are supported. + +### Route Share Card + +After a real directed Route Probe resolves, the reader may use **Export → Route Share Card**. It reuses the exact ordered route snapshot and the shared Share Card seam: `format=share-card`, `variant=route`. The isolated clone may use only static `data-share-route-*` decoration. It is download-only, fails closed for stale/unreachable/conflicting routes, and never becomes the canonical artifact. + +### Reach Share Card + +After a non-empty authored reachability query, the reader may use **Export → Reach Share Card**. It consumes the already resolved upstream/downstream node and edge set without rerunning traversal: `format=share-card`, `variant=reach`. The isolated clone may use only static `data-share-reach-*` decoration. It is download-only. Call it authored reachability—not impact, blast radius, breakage, or runtime causality. + +## Truth boundary + +Viewer exports are communication assets. They do not replace the checked HTML, the deterministic delivery receipt, or a real visual review. Do not add a hosted service, storage surface, dependency, schema branch, or mobile product surface for these viewer-only capabilities. diff --git a/tools/archify/renderers/architecture/grid.mjs b/tools/archify/renderers/architecture/grid.mjs new file mode 100644 index 0000000..807a1b8 --- /dev/null +++ b/tools/archify/renderers/architecture/grid.mjs @@ -0,0 +1,62 @@ +/** Grid placement for architecture IR (#8). Not auto-layout — fixed cell math only. */ + +export const DEFAULT_GRID = { + mode: 'grid', + origin: [40, 80], + cols: 4, + gapX: 30, + gapY: 40, + cellW: 130, + cellH: 64, +}; + +export function gridLayout(arch) { + const raw = arch.layout; + if (!raw || raw.mode !== 'grid') return null; + return { ...DEFAULT_GRID, ...raw }; +} + +export function resolveComponentPos(component, grid) { + if (Array.isArray(component.pos) && component.pos.length === 2) { + return component.pos; + } + if (!grid) return [NaN, NaN]; + if (!Number.isInteger(component.row) || !Number.isInteger(component.col)) { + return [NaN, NaN]; + } + const [ox, oy] = grid.origin; + const stepX = grid.cellW + grid.gapX; + const stepY = grid.cellH + grid.gapY; + return [ox + component.col * stepX, oy + component.row * stepY]; +} + +export function validateGridPlacement(arch, grid, problems) { + if (!grid) return; + if (arch.layout !== undefined && arch.layout.mode !== 'grid') { + problems.push('layout.mode must be "grid" when layout is set (free placement omits layout entirely).'); + return; + } + const seen = new Map(); + for (const c of arch.components ?? []) { + const hasPos = Array.isArray(c.pos) && c.pos.length === 2; + const hasCell = Number.isInteger(c.row) && Number.isInteger(c.col); + if (hasPos) continue; // pos wins; row/col are optional hints only + if (!hasPos && !hasCell) { + problems.push(`Component "${c.id}" needs pos [x,y] or grid row/col when layout.mode is "grid".`); + continue; + } + if (c.row < 0 || c.col < 0) { + problems.push(`Component "${c.id}" row/col must be non-negative integers.`); + continue; + } + if (c.col >= grid.cols) { + problems.push(`Component "${c.id}" col ${c.col} exceeds layout.cols ${grid.cols} (valid: 0..${grid.cols - 1}).`); + } + const key = `${c.row},${c.col}`; + if (seen.has(key)) { + problems.push(`Components "${seen.get(key)}" and "${c.id}" share grid cell row ${c.row} col ${c.col}.`); + } else { + seen.set(key, c.id); + } + } +} diff --git a/tools/archify/renderers/architecture/render-architecture.mjs b/tools/archify/renderers/architecture/render-architecture.mjs new file mode 100644 index 0000000..b21ac8e --- /dev/null +++ b/tools/archify/renderers/architecture/render-architecture.mjs @@ -0,0 +1,1078 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs'; +import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, loadDiagramWithBrandMarks, writeDiagram, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs'; +import { componentBox, boundaryBox, connectionPath } from '../shared/layout-report.mjs'; +import { throwDiagnosticProblems } from '../shared/diagnostics.mjs'; +import { legendFootprint, relationshipLegendObstacles, resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs'; +import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs'; +import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs'; +import { minimumReadableSourceTextPx } from '../shared/desktop-readability.mjs'; +import { translateMessage as i18nText } from '../shared/i18n.mjs'; +import { gridLayout, resolveComponentPos, validateGridPlacement } from './grid.mjs'; +import { + asArray, + isFinitePoint, + rectsOverlap, + segmentIntersectsRect, + cleanEndpointSideProblems, + cleanFlowProblems, + cleanCrossingProblems, + cleanAmbiguousCorridorProblems, + cleanBorderRunProblems, + cleanRouteRhythmProblems, + cleanLabelRouteClearanceProblems, + suggestLabelObstacleFix, + suggestComponentSeparation, + anchor, + automaticPortSpread, + automaticPortRhythmBridge, + defaultFromSide, + defaultToSide, + chosenSide, + routeHonorsEndpointSides, + normalizeRoutePoints, + polylinePath, + routePointsValue, + roundedPath, + labelPoint, + componentFill, + componentText, + arrowClassMap, + variantAccent, +} from '../shared/geometry.mjs'; + +const componentTextFit = { + sublabelPreferred: 9, + sublabelMinimum: 6, + tagPreferred: 7, + tagMinimum: 6, +}; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const layoutJsonMode = process.argv.includes('--layout-json'); +const cliArgs = process.argv.filter((arg) => arg !== '--layout-json'); +const { diagram: arch, template, outPath, sourceEvidence } = await loadDiagramWithBrandMarks({ + rendererDir: __dirname, + diagramType: 'architecture', + defaultExample: 'web-app.architecture.json', + argv: cliArgs, +}); + +const grid = gridLayout(arch); + +const layout = { + defaultW: 120, + defaultH: 60, + margin: 40, + // Boundary padding — the 30/50 rule that was a hand-arithmetic footgun + // (CHANGELOG v2.2.1): 30px on top/left/right, plus 20px extra at the bottom. + boundaryPad: 30, + boundaryExtraBottom: 20, + boundaryLabelBaseline: 18, + boundaryLabelClearance: 4, + boundaryLabelFontPreferred: 9, + boundaryLabelFontMinimum: 6, + boundaryLabelMaskHeight: 16, + boundaryLabelRailGap: 2, + boundaryLabelFrameInset: 4, + legendH: 28, +}; + +const LEGEND_CATALOG = [ + 'frontend', + 'backend', + 'database', + 'cloud', + 'security', + 'messagebus', + 'external', +].map((kind) => ({ kind, label: i18nText(arch.meta.locale, `legend.architecture.${kind}`) })); + +// ---- Measure components from free coordinates -------------------------------- +function measureComponent(c) { + const [x, y] = resolveComponentPos(c, grid); + const [w, h] = Array.isArray(c.size) ? c.size : [layout.defaultW, layout.defaultH]; + return { ...c, x, y, width: w, height: h, cx: x + w / 2, cy: y + h / 2 }; +} + +const components = new Map(asArray(arch.components).map((c) => [c.id, measureComponent(c)])); +const enforcesBoundaryTitleComposition = Boolean(arch.meta?.quality_profile); +const componentSteps = new Map(); +for (const [index, conn] of asArray(arch.connections).entries()) { + if (!componentSteps.has(conn.from)) componentSteps.set(conn.from, index); + if (!componentSteps.has(conn.to)) componentSteps.set(conn.to, index + 1); +} +for (const [index, c] of asArray(arch.components).entries()) { + if (!componentSteps.has(c.id)) componentSteps.set(c.id, index); +} + +// ---- Boundaries computed from the `wraps` id list --------------------------- +function boundaryRect(boundary) { + const members = asArray(boundary.wraps).map((id) => components.get(id)).filter(Boolean); + if (!members.length) return null; + const minX = Math.min(...members.map((m) => m.x)); + const minY = Math.min(...members.map((m) => m.y)); + const maxX = Math.max(...members.map((m) => m.x + m.width)); + const maxY = Math.max(...members.map((m) => m.y + m.height)); + const pad = boundary.pad ?? layout.boundaryPad; + const topPad = Math.max( + pad, + layout.boundaryLabelBaseline + layout.boundaryLabelClearance, + ); + return { + ...boundary, + x: minX - pad, + y: minY - topPad, + width: maxX - minX + pad * 2, + height: maxY - minY + topPad + layout.boundaryExtraBottom, + memberTop: minY, + }; +} + +function rectContains(outer, inner) { + const epsilon = 1e-9; + return outer.x <= inner.x + epsilon + && outer.y <= inner.y + epsilon + && outer.x + outer.width + epsilon >= inner.x + inner.width + && outer.y + outer.height + epsilon >= inner.y + inner.height; +} + +function boundaryLabelWidth(label, fontSize) { + return Math.max(30, textUnits(label) * fontSize * 0.6 + 10); +} + +const architectureLegendEntries = resolveLegend( + arch.meta?.legend, + LEGEND_CATALOG, + new Set([...components.values()].map((component) => component.type)), +); + +function autoViewBoxFor(candidateBoundaries) { + const maxX = Math.max( + 0, + ...[...components.values()].map((component) => component.x + component.width), + ...candidateBoundaries.map((boundary) => boundary.x + boundary.width), + ); + const maxY = Math.max( + 0, + ...[...components.values()].map((component) => component.y + component.height), + ...candidateBoundaries.map((boundary) => boundary.y + boundary.height), + ); + let width = Math.ceil(maxX + layout.margin); + let footprint = legendFootprint(architectureLegendEntries, { + width: Math.max(1, width - layout.margin * 2), + }); + if (footprint.minWidth > width - layout.margin * 2) { + width = Math.ceil(footprint.minWidth + layout.margin * 2); + footprint = legendFootprint(architectureLegendEntries, { + width: width - layout.margin * 2, + }); + } + return [ + width, + Math.ceil(maxY + layout.margin + layout.legendH + footprint.extraHeight), + ]; +} + +function resolvedViewBoxWidth(candidateBoundaries) { + if (Array.isArray(arch.meta?.viewBox) && Number.isFinite(arch.meta.viewBox[0])) { + return arch.meta.viewBox[0]; + } + return autoViewBoxFor(candidateBoundaries)[0]; +} + +function expandBoundaryForReadableTitle(boundary, minimumFontSize) { + if (!enforcesBoundaryTitleComposition) return boundary; + const requiredWidth = boundaryLabelWidth(boundary.label, minimumFontSize) + + layout.boundaryLabelFrameInset * 2; + const extra = Math.max(0, requiredWidth - boundary.width); + if (!extra) return boundary; + return { + ...boundary, + x: boundary.x - extra / 2, + width: boundary.width + extra, + }; +} + +function measureBoundaryTitle(boundary, minimumFontSize) { + const availableWidth = Math.max(0, boundary.width - layout.boundaryLabelFrameInset * 2); + const units = textUnits(boundary.label); + const fitted = units > 0 + ? (availableWidth - 10) / (units * 0.6) + : layout.boundaryLabelFontPreferred; + const preferredFontSize = Math.max(layout.boundaryLabelFontPreferred, minimumFontSize); + const fontSize = Math.max( + minimumFontSize, + Math.min(preferredFontSize, fitted), + ); + const desiredWidth = boundaryLabelWidth(boundary.label, fontSize); + const height = Math.max(layout.boundaryLabelMaskHeight, Math.ceil(fontSize + 7)); + return { + x: boundary.x + layout.boundaryLabelFrameInset, + y: boundary.memberTop + - layout.boundaryLabelClearance + - height, + width: Math.min(availableWidth, desiredWidth), + height, + fontSize, + minimumFontSize, + baselineOffset: fontSize + 4, + availableWidth, + minimumWidth: boundaryLabelWidth(boundary.label, minimumFontSize), + }; +} + +function horizontalOverlap(left, right) { + return left.x < right.x + right.width && left.x + left.width > right.x; +} + +function layoutBoundaryTitles(rawBoundaries, minimumFontSize) { + const placedTitles = []; + const measured = new Map(); + const ordered = rawBoundaries + .map((boundary, index) => ({ boundary, index })) + .sort((left, right) => { + const areaDelta = left.boundary.width * left.boundary.height + - right.boundary.width * right.boundary.height; + return areaDelta || left.index - right.index; + }); + + for (const entry of ordered) { + const { index } = entry; + const boundary = expandBoundaryForReadableTitle(entry.boundary, minimumFontSize); + const title = measureBoundaryTitle(boundary, minimumFontSize); + let guard = 0; + while (guard < rawBoundaries.length + components.size + 1) { + guard += 1; + const blockers = [ + ...placedTitles, + ...components.values(), + ].filter((candidate) => horizontalOverlap(title, candidate) && rectsOverlap(title, candidate)); + if (!blockers.length) break; + title.y = Math.min( + ...blockers.map((blocker) => blocker.y - layout.boundaryLabelRailGap - title.height), + ); + } + placedTitles.push(title); + measured.set(index, { boundary, title }); + } + + return rawBoundaries.map((_boundary, index) => { + const { boundary, title } = measured.get(index); + const bottom = boundary.y + boundary.height; + // Profile-less schema-v1 inputs keep their legacy boundary geometry. A + // quality profile opts into the stricter title-composition contract and + // may expand the frame to contain an adapted title rail. + const y = enforcesBoundaryTitleComposition + ? Math.min(boundary.y, title.y - layout.boundaryLabelFrameInset) + : boundary.y; + return { + ...boundary, + y, + height: bottom - y, + title, + }; + }); +} + +const rawBoundaries = asArray(arch.boundaries).map(boundaryRect).filter(Boolean); +function resolveBoundaryTitles() { + if (!enforcesBoundaryTitleComposition || rawBoundaries.length === 0) { + return { + boundaries: layoutBoundaryTitles(rawBoundaries, layout.boundaryLabelFontMinimum), + readabilityProblem: null, + }; + } + + const maximumIterations = 32; + let candidateBoundaries = rawBoundaries; + for (let iteration = 0; iteration < maximumIterations; iteration += 1) { + const budgetViewBoxWidth = resolvedViewBoxWidth(candidateBoundaries); + const minimumFontSize = Math.max( + layout.boundaryLabelFontMinimum, + minimumReadableSourceTextPx(budgetViewBoxWidth) + 1e-6, + ); + const nextBoundaries = layoutBoundaryTitles(rawBoundaries, minimumFontSize); + const finalViewBoxWidth = resolvedViewBoxWidth(nextBoundaries); + const finalMinimumFontSize = Math.max( + layout.boundaryLabelFontMinimum, + minimumReadableSourceTextPx(finalViewBoxWidth), + ); + if (minimumFontSize >= finalMinimumFontSize) { + return { boundaries: nextBoundaries, readabilityProblem: null }; + } + candidateBoundaries = nextBoundaries; + } + + const finalViewBoxWidth = resolvedViewBoxWidth(candidateBoundaries); + return { + boundaries: candidateBoundaries, + readabilityProblem: `[composition/desktop-readability] Boundary title layout did not converge after ${maximumIterations} iterations for the final ${finalViewBoxWidth}px viewBox — shorten boundary labels, provide a wider authored viewBox, or move wrapped components closer to the left edge.`, + }; +} + +const resolvedBoundaryTitles = resolveBoundaryTitles(); +const boundaries = resolvedBoundaryTitles.boundaries; +const compositionFrames = boundaries.map((boundary, index) => ({ + ...boundary, + id: boundary.id || index, + kind: boundary.kind || 'boundary', + radius: boundary.kind === 'security-group' ? 8 : 12, +})); + +function componentContext(component) { + const scopes = boundaries + .filter((boundary) => asArray(boundary.wraps).includes(component.id)) + .sort((a, b) => (b.width * b.height) - (a.width * a.height)) + .map((boundary) => boundary.label); + return scopes.length ? scopes.join(' › ') : i18nText(arch.meta.locale, 'node.context.architecture'); +} + +// ---- Auto viewBox: fit all geometry + the measured resolved legend ---------- +const viewBox = arch.meta?.viewBox || autoViewBoxFor(boundaries); +const legendY = () => viewBox[1] - 16; + +// ---- Validation: mechanical correctness, never layout taste ----------------- +function validateArchitecture() { + const problems = []; + if (resolvedBoundaryTitles.readabilityProblem) { + problems.push(resolvedBoundaryTitles.readabilityProblem); + } + const requiresNestedBoundaryMembership = arch.meta?.engineering_profile === 'deployment-ownership'; + if (components.size !== asArray(arch.components).length) problems.push('Component ids must be unique.'); + if (grid) { + validateGridPlacement(arch, grid, problems); + } else { + for (const c of asArray(arch.components)) { + if (!Array.isArray(c.pos) || c.pos.length !== 2) { + problems.push(`Component "${c.id}" must include pos [x, y] when layout.mode is omitted (free placement).`); + } + } + } + + for (const c of components.values()) { + if (!isFinitePoint(c.x, c.y, c.width, c.height)) { + problems.push(`Component "${c.id}" has non-finite pos/size — pos and size must be [number, number].`); + continue; + } + if (c.width <= 0 || c.height <= 0) { + problems.push(`Component "${c.id}" has invalid size ${c.width}x${c.height} — width and height must be greater than 0.`); + continue; + } + if (c.x < 0 || c.y < 0 || c.x + c.width > viewBox[0] || c.y + c.height > viewBox[1]) { + problems.push(`Component "${c.id}" falls outside the viewBox ${viewBox[0]}x${viewBox[1]} — adjust pos/size or set a larger meta.viewBox.`); + } + const estLabelW = textUnits(c.label) * 6.6; + if (estLabelW > c.width + 8) { + problems.push(`Label "${c.label}" (~${Math.round(estLabelW)}px) is wider than component "${c.id}" (${c.width}px) — shorten the label or widen size.`); + } + const brandRailProblem = brandTopRailProblem(c, c.width, 8, 'Component'); + if (brandRailProblem) problems.push(brandRailProblem); + // sublabel and tag render as single unwrapped elements; shrink-to-fit + // handles the ordinary case, this rejects what it cannot rescue. + const availableTextW = availableNodeTextWidth(c.width); + for (const [field, value, minimum] of [ + ['Sublabel', c.sublabel, componentTextFit.sublabelMinimum], + ['Tag', c.tag, componentTextFit.tagMinimum], + ]) { + if (!value) continue; + const minimumW = minimumNodeTextWidth(value, minimum); + if (minimumW > availableTextW) { + problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but component "${c.id}" provides ${availableTextW}px — shorten the ${field.toLowerCase()} or widen size.`); + } + } + } + + // Component overlap — the highest-traffic hand-placement failure mode. + const list = [...components.values()]; + for (let i = 0; i < list.length; i += 1) { + for (let j = i + 1; j < list.length; j += 1) { + if (rectsOverlap(list[i], list[j], 8)) { + problems.push(`Components "${list[i].id}" and "${list[j].id}" are less than 8px apart — move one or shrink its size.\n${suggestComponentSeparation(list[i], list[j], 8)}`); + } + } + } + + // Boundaries: every wrapped id must exist; the computed box must stay in view. + for (const boundary of asArray(arch.boundaries)) { + for (const id of asArray(boundary.wraps)) { + if (!components.has(id)) problems.push(`Boundary "${boundary.label}" wraps unknown component "${id}".`); + } + } + const viewBoxRect = { x: 0, y: 0, width: viewBox[0], height: viewBox[1] }; + for (const boundary of boundaries) { + if (!enforcesBoundaryTitleComposition) continue; + if (boundary.title.minimumWidth > boundary.title.availableWidth) { + problems.push( + `Boundary label "${boundary.label}" needs ~${Math.ceil(boundary.title.minimumWidth)}px to fit at the ` + + `${Number(boundary.title.minimumFontSize.toFixed(2))}px desktop-readable source minimum, but its frame provides ${Math.floor(boundary.title.availableWidth)}px — ` + + 'shorten the boundary label, increase pad, or widen the wrapped component layout.', + ); + } + if (!rectContains(boundary, boundary.title)) { + problems.push( + `Boundary label "${boundary.label}" extends outside its final frame — shorten the label or increase boundary pad.`, + ); + } + if (!rectContains(viewBoxRect, boundary.title)) { + problems.push( + `Boundary label "${boundary.label}" extends outside the viewBox — move wrapped components away from the canvas edge, shorten the label, or increase the viewBox.`, + ); + } + for (const component of components.values()) { + if (!rectsOverlap(boundary.title, component)) continue; + problems.push( + `Boundary label "${boundary.label}" overlaps component "${component.id}" — move the component, increase boundary title space, or shorten the label.`, + ); + } + } + for (let leftIndex = 0; leftIndex < boundaries.length; leftIndex += 1) { + const left = boundaries[leftIndex]; + const leftMembers = new Set(asArray(left.wraps)); + for (let rightIndex = leftIndex + 1; rightIndex < boundaries.length; rightIndex += 1) { + const right = boundaries[rightIndex]; + if (enforcesBoundaryTitleComposition && rectsOverlap(left.title, right.title)) { + problems.push( + `Boundary labels "${left.label}" and "${right.label}" overlap — shorten a label or increase boundary title space.`, + ); + } + // Ordinary architecture boundaries are sets, not an implied ownership + // tree: orthogonal scopes such as runtime and compliance may share some + // components while each contains others. The opt-in deployment profile + // does promise hierarchical region/private-scope membership, so only it + // receives the stricter membership-to-frame containment contract. + if (!requiresNestedBoundaryMembership) continue; + const rightMembers = new Set(asArray(right.wraps)); + const shared = [...leftMembers].filter((id) => rightMembers.has(id)); + const leftNested = [...leftMembers].every((id) => rightMembers.has(id)); + const rightNested = [...rightMembers].every((id) => leftMembers.has(id)); + if (shared.length && !leftNested && !rightNested) { + const leftOnly = [...leftMembers].filter((id) => !rightMembers.has(id)); + const rightOnly = [...rightMembers].filter((id) => !leftMembers.has(id)); + problems.push( + `Boundary "${left.label}" crosses boundary "${right.label}" because their memberships partially overlap ` + + `(shared: ${shared.map((id) => `"${id}"`).join(', ')}; ` + + `only in "${left.label}": ${leftOnly.map((id) => `"${id}"`).join(', ')}; ` + + `only in "${right.label}": ${rightOnly.map((id) => `"${id}"`).join(', ')}) — ` + + 'keep one boundary fully nested by removing outside members, or split the boundary.', + ); + continue; + } + + if (!rectsOverlap(left, right)) continue; + const leftContainsRight = rectContains(left, right); + const rightContainsLeft = rectContains(right, left); + if (!leftContainsRight && !rightContainsLeft) { + problems.push( + `Boundary "${left.label}" and boundary "${right.label}" final frames partially overlap — ` + + 'adjust wraps, pad, or component positions so the frames are disjoint or one fully contains the other.', + ); + continue; + } + + if (!shared.length) { + problems.push( + `Boundary "${left.label}" and boundary "${right.label}" final frames overlap even though their memberships are disjoint — ` + + 'adjust pad or component positions so the frames are disjoint, or make wraps express the intended nesting.', + ); + continue; + } + + const containmentMatchesMembership = (leftNested && rightContainsLeft) + || (rightNested && leftContainsRight); + if (!containmentMatchesMembership) { + problems.push( + `Boundary "${left.label}" and boundary "${right.label}" final frame containment contradicts their wraps membership — ` + + 'reduce the inner boundary pad, move its components, or correct wraps so geometry and nesting agree.', + ); + } + } + } + for (const b of boundaries) { + if (b.x < 0 || b.y < 0 || b.x + b.width > viewBox[0] || b.y + b.height > viewBox[1]) { + problems.push(`Boundary "${b.label}" extends outside the viewBox — its members sit too close to the canvas edge; add margin or enlarge meta.viewBox.`); + } + } + + for (const conn of asArray(arch.connections)) { + if (!components.has(conn.from)) problems.push(`Connection "${conn.label || conn.from}" references unknown source "${conn.from}".`); + if (!components.has(conn.to)) problems.push(`Connection "${conn.label || conn.to}" references unknown target "${conn.to}".`); + if (components.has(conn.from) && components.has(conn.to)) { + const routed = pathFor(conn); + const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]]; + const distance = Math.hypot(end[0] - start[0], end[1] - start[1]); + if (distance < 24) problems.push(`Connection "${conn.label || `${conn.from}->${conn.to}`}" is too short (${Math.round(distance)}px; minimum 24px) — place its components farther apart.`); + } + } + + problems.push(...cleanEndpointSideProblems({ + relations: arch.connections, + endpointIds: new Set(components.keys()), + pathFor, + diagramType: 'architecture', + relationCollection: 'connections', + fromSideFor: (conn) => connectionEndpointSide(conn, 'source'), + toSideFor: (conn) => connectionEndpointSide(conn, 'target'), + routeHint: 'keep automatic routing so the renderer can use a side-aware bridge, or set truthful fromSide/toSide with perpendicular via segments', + })); + problems.push(...cleanFlowProblems({ + relations: arch.connections, + obstacles: components.values(), + pathFor, + diagramType: 'architecture', + relationCollection: 'connections', + obstacleKind: 'component', + routeHint: 'adjust fromSide/toSide, set route/via, or move the component' + })); + problems.push(...cleanCrossingProblems({ + relations: arch.connections, + endpointIds: new Set(components.keys()), + pathFor, + diagramType: 'architecture', + relationCollection: 'connections', + profile: arch.meta?.quality_profile, + routeHint: 'adjust route/via or fromSide/toSide so the connections use separate corridors' + })); + problems.push(...cleanAmbiguousCorridorProblems({ + relations: arch.connections, + endpointIds: new Set(components.keys()), + pathFor, + diagramType: 'architecture', + relationCollection: 'connections', + profile: arch.meta?.quality_profile, + routeHint: 'adjust route/via or fromSide/toSide so unrelated connections do not visually merge' + })); + problems.push(...cleanBorderRunProblems({ + relations: arch.connections, + endpointIds: new Set(components.keys()), + frames: compositionFrames, + pathFor, + diagramType: 'architecture', + relationCollection: 'connections', + profile: arch.meta?.quality_profile, + routeHint: 'adjust route/via or fromSide/toSide so the connection crosses the boundary perpendicularly instead of following its border' + })); + problems.push(...cleanRouteRhythmProblems({ + relations: arch.connections, + endpointIds: new Set(components.keys()), + pathFor, + diagramType: 'architecture', + relationCollection: 'connections', + profile: arch.meta?.quality_profile, + routeHint: 'move route/via points into a wider corridor or move the component so every turn has room to read' + })); + + // Connection labels must not land on top of components. + const labelRects = []; + for (const [connectionIndex, conn] of asArray(arch.connections).entries()) { + if (!conn.label || !components.has(conn.from) || !components.has(conn.to)) continue; + const [lx, ly] = labelPoint(conn, pathFor(conn).points); + const w = Math.max(30, textUnits(conn.label) * 4.8 + 10); + labelRects.push({ relation: conn, relationIndex: connectionIndex, label: conn.label, x: lx - w / 2, y: ly - 10, width: w, height: 14, lx, ly }); + } + for (const rect of labelRects) { + for (const c of components.values()) { + if (rectsOverlap(rect, c, -2)) { + problems.push(`Label "${rect.label}" overlaps component "${c.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, c)}`); + } + } + if (enforcesBoundaryTitleComposition) { + for (const boundary of boundaries) { + if (!rectsOverlap(boundary.title, rect)) continue; + problems.push( + `Boundary label "${boundary.label}" overlaps connection label "${rect.label}" — move the boundary title rail by adjusting wrapped component positions, or move the connection label with labelAt/labelDx/labelDy/labelSegment.`, + ); + } + } + } + problems.push(...cleanLabelRouteClearanceProblems({ + relations: arch.connections, + labels: labelRects, + endpointIds: new Set(components.keys()), + pathFor, + diagramType: 'architecture', + relationCollection: 'connections', + profile: arch.meta?.quality_profile, + })); + + if (problems.length) { + throwDiagnosticProblems('Architecture layout validation failed', problems, { + subject: { diagramType: 'architecture' }, + }); + } +} + +function buildLayoutReport() { + const labels = []; + for (const conn of asArray(arch.connections)) { + if (!conn.label || !components.has(conn.from) || !components.has(conn.to)) continue; + const [lx, ly] = labelPoint(conn, pathFor(conn).points); + const w = Math.max(30, textUnits(conn.label) * 4.8 + 10); + labels.push({ + text: conn.label, + x: Math.round(lx - w / 2), + y: Math.round(ly - 10), + width: Math.round(w), + height: 14, + labelAt: [Math.round(lx), Math.round(ly)], + }); + } + return { + ok: true, + diagram_type: 'architecture', + layout: grid ? { mode: 'grid', ...grid } : { mode: 'free' }, + viewBox, + components: [...components.values()].map(componentBox), + boundaries: boundaries.map(boundaryBox), + connections: asArray(arch.connections) + .filter((conn) => components.has(conn.from) && components.has(conn.to)) + .map((conn) => { + const routed = pathFor(conn); + const labelAt = conn.label ? labelPoint(conn, routed.points) : null; + return connectionPath(conn, routed, labelAt); + }), + labels, + }; +} + +// ---- Connection routing ------------------------------------------------------ +function routeClearsComponents(conn, points, clearance = 2) { + const endpointIds = new Set([conn.from, conn.to]); + for (const component of components.values()) { + if (endpointIds.has(component.id)) continue; + for (let index = 0; index < points.length - 1; index += 1) { + if (segmentIntersectsRect({ start: points[index], end: points[index + 1] }, component, clearance)) { + return false; + } + } + } + return true; +} + +function routeClearsEndpointComponents(points, from, to) { + const lastSegment = points.length - 2; + for (let index = 0; index <= lastSegment; index += 1) { + const segment = { start: points[index], end: points[index + 1] }; + if (index > 0 && segmentIntersectsRect(segment, from)) return false; + if (index < lastSegment && segmentIntersectsRect(segment, to)) return false; + } + return true; +} + +const OUTWARD_SIDE_VECTOR = { + left: [-1, 0], + right: [1, 0], + top: [0, -1], + bottom: [0, 1], +}; + +function outwardStub(point, side, distance = 24) { + const [dx, dy] = OUTWARD_SIDE_VECTOR[side] || [0, 0]; + return [point[0] + dx * distance, point[1] + dy * distance]; +} + +function collinearBacktrack(a, b, c) { + const first = [b[0] - a[0], b[1] - a[1]]; + const second = [c[0] - b[0], c[1] - b[1]]; + const cross = first[0] * second[1] - first[1] * second[0]; + const dot = first[0] * second[0] + first[1] * second[1]; + return Math.abs(cross) <= 0.0001 && dot < -0.0001; +} + +function sideAwareBridgeCandidates(start, end, fromSide, toSide) { + const startStub = outwardStub(start, fromSide); + const endStub = outwardStub(end, toSide); + const rawCandidates = []; + const minimumBridge = 16; + const verticalSides = new Set(['top', 'bottom']); + const horizontalSides = new Set(['left', 'right']); + + // Port spreading can leave parallel-side anchors only a few pixels apart. + // Route through a bounded outside channel so we keep both endpoint normals + // without introducing a tiny, noisy connector between the two stubs. + if (verticalSides.has(fromSide) && verticalSides.has(toSide) + && Math.abs(start[0] - end[0]) < minimumBridge) { + for (const channelX of [ + Math.max(start[0], end[0]) + minimumBridge, + Math.min(start[0], end[0]) - minimumBridge, + ]) { + rawCandidates.push([ + startStub, + [channelX, startStub[1]], + [channelX, endStub[1]], + endStub, + ]); + } + } + if (horizontalSides.has(fromSide) && horizontalSides.has(toSide) + && Math.abs(start[1] - end[1]) < minimumBridge) { + for (const channelY of [ + Math.max(start[1], end[1]) + minimumBridge, + Math.min(start[1], end[1]) - minimumBridge, + ]) { + rawCandidates.push([ + startStub, + [startStub[0], channelY], + [endStub[0], channelY], + endStub, + ]); + } + } + + rawCandidates.push( + [startStub, [endStub[0], startStub[1]], endStub], + [startStub, [startStub[0], endStub[1]], endStub], + ); + return rawCandidates.map((candidate) => normalizeRoutePoints([start, ...candidate, end])) + .filter((points) => points.length >= 2) + .filter((points) => !collinearBacktrack(points[0], points[1], points[2] || points[1])) + .filter((points) => !collinearBacktrack(points.at(-3) || points.at(-2), points.at(-2), points.at(-1))) + .filter((points) => routeHonorsEndpointSides(points, fromSide, toSide)) + .map((points) => points.slice(1, -1)); +} + +const AUTOMATIC_PORT_CORNER_GUTTER = 16; +const AUTOMATIC_PORT_ALIGNMENT_DELTA = 16; + +function portHasCornerClearance(rect, side, point) { + if (side === 'left' || side === 'right') { + const inset = Math.min(AUTOMATIC_PORT_CORNER_GUTTER, rect.height / 2); + return point[1] >= rect.y + inset && point[1] <= rect.y + rect.height - inset; + } + if (side === 'top' || side === 'bottom') { + const inset = Math.min(AUTOMATIC_PORT_CORNER_GUTTER, rect.width / 2); + return point[0] >= rect.x + inset && point[0] <= rect.x + rect.width - inset; + } + return false; +} + +function alignFacingPorts(conn, from, to, start, end, fromSide, toSide, ports) { + const hasExplicitGeometry = ( + conn.via + || (conn.route && conn.route !== 'auto') + || conn.channelX !== undefined + || conn.channelY !== undefined + || conn.labelAt + ); + const horizontallyFacing = ( + (fromSide === 'right' && toSide === 'left') + || (fromSide === 'left' && toSide === 'right') + ); + const verticallyFacing = ( + (fromSide === 'bottom' && toSide === 'top') + || (fromSide === 'top' && toSide === 'bottom') + ); + if (hasExplicitGeometry || (!horizontallyFacing && !verticallyFacing)) return { start, end }; + + const fromSpread = Boolean(ports?.from); + const toSpread = Boolean(ports?.to); + if (fromSpread && toSpread) return { start, end }; + const hasExplicitSides = ( + (conn.fromSide && conn.fromSide !== 'auto') + || (conn.toSide && conn.toSide !== 'auto') + ); + if (!fromSpread && !toSpread && hasExplicitSides) return { start, end }; + + const alignmentDelta = horizontallyFacing + ? Math.abs(start[1] - end[1]) + : Math.abs(start[0] - end[0]); + if (alignmentDelta >= AUTOMATIC_PORT_ALIGNMENT_DELTA) return { start, end }; + + // Keep the shared endpoint's distinct spread slot and move only the + // relationship's unshared endpoint onto that axis. With no spread endpoint, + // retain the existing least-movement choice between the two facing sides. + // If both endpoints are shared, preserve the outside bridge so no competing + // port is silently collapsed. + const alignEndToStart = horizontallyFacing + ? { start, end: [end[0], start[1]] } + : { start, end: [start[0], end[1]] }; + const alignStartToEnd = horizontallyFacing + ? { start: [start[0], end[1]], end } + : { start: [end[0], start[1]], end }; + const candidates = fromSpread + ? [alignEndToStart] + : toSpread + ? [alignStartToEnd] + : [alignEndToStart, alignStartToEnd]; + for (const candidate of candidates) { + const points = [candidate.start, candidate.end]; + if (portHasCornerClearance(from, fromSide, candidate.start) + && portHasCornerClearance(to, toSide, candidate.end) + && routeHonorsEndpointSides(points, fromSide, toSide) + && routeClearsEndpointComponents(points, from, to) + && routeClearsComponents(conn, points)) { + return candidate; + } + } + return { start, end }; +} + +function routeVia(conn, from, to, start, end, fromSide, toSide) { + if (conn.via) return conn.via; + switch (conn.route || 'auto') { + case 'straight': + return []; + case 'orthogonal-h': { + const midX = (start[0] + end[0]) / 2; + return [[midX, start[1]], [midX, end[1]]]; + } + case 'orthogonal-v': { + const midY = (start[1] + end[1]) / 2; + return [[start[0], midY], [end[0], midY]]; + } + case 'auto': + default: { + // Direct line unless the anchors are clearly orthogonal-friendly. + const deltaX = Math.abs(start[0] - end[0]); + const deltaY = Math.abs(start[1] - end[1]); + if ((deltaX < 4 || deltaY < 4) && routeHonorsEndpointSides([start, end], fromSide, toSide)) return []; + + const rhythmBridge = automaticPortRhythmBridge(start, end, fromSide, toSide, { + accept: (points) => ( + routeClearsEndpointComponents(points, from, to) + && routeClearsComponents(conn, points) + ), + }); + if (rhythmBridge) return rhythmBridge.slice(1, -1); + + // Automatic port spreading can leave otherwise aligned endpoints only a + // few pixels apart. A midpoint route would split that tiny difference + // into two unreadable endpoint stubs, so take a bounded outside channel + // when both anchors sit on parallel component sides. + const minimumStub = 8; + const fromVerticalSide = start[1] === from.y || start[1] === from.y + from.height; + const toVerticalSide = end[1] === to.y || end[1] === to.y + to.height; + if (fromVerticalSide && toVerticalSide && deltaX < minimumStub * 2) { + const outsideChannels = [ + Math.max(start[0], end[0]) + minimumStub * 2, + Math.min(start[0], end[0]) - minimumStub * 2, + ]; + for (const channelX of outsideChannels) { + const candidate = [[channelX, start[1]], [channelX, end[1]]]; + const points = [start, ...candidate, end]; + if (routeHonorsEndpointSides(points, fromSide, toSide) && routeClearsComponents(conn, points)) return candidate; + } + } + + const fromHorizontalSide = start[0] === from.x || start[0] === from.x + from.width; + const toHorizontalSide = end[0] === to.x || end[0] === to.x + to.width; + if (fromHorizontalSide && toHorizontalSide && deltaY < minimumStub * 2) { + const outsideChannels = [ + Math.max(start[1], end[1]) + minimumStub * 2, + Math.min(start[1], end[1]) - minimumStub * 2, + ]; + for (const channelY of outsideChannels) { + const candidate = [[start[0], channelY], [end[0], channelY]]; + const points = [start, ...candidate, end]; + if (routeHonorsEndpointSides(points, fromSide, toSide) && routeClearsComponents(conn, points)) return candidate; + } + } + + const midX = (start[0] + end[0]) / 2; + const horizontalFirst = [[midX, start[1]], [midX, end[1]]]; + const midY = (start[1] + end[1]) / 2; + const verticalFirst = [[start[0], midY], [end[0], midY]]; + const candidates = [horizontalFirst, verticalFirst]; + const sideSafe = candidates.filter((candidate) => ( + routeHonorsEndpointSides([start, ...candidate, end], fromSide, toSide) + )); + const sideAware = sideAwareBridgeCandidates(start, end, fromSide, toSide); + const nearParallelPorts = ( + ((fromSide === 'top' || fromSide === 'bottom') + && (toSide === 'top' || toSide === 'bottom') + && deltaX < minimumStub * 2) + || ((fromSide === 'left' || fromSide === 'right') + && (toSide === 'left' || toSide === 'right') + && deltaY < minimumStub * 2) + ); + const ordered = [ + ...(nearParallelPorts ? sideAware : sideSafe), + ...(nearParallelPorts ? sideSafe : sideAware), + ...candidates.filter((candidate) => !sideSafe.includes(candidate)), + ]; + for (const candidate of ordered) { + const points = [start, ...candidate, end]; + if (routeClearsEndpointComponents(points, from, to) && routeClearsComponents(conn, points)) return candidate; + } + + // Both bounded doglegs are blocked. Keep the best endpoint-safe route + // when one exists so the universal Clean Flow gate reports the actual + // obstacle; otherwise preserve the historical deterministic fallback + // and let the endpoint-direction gate explain the side mismatch. + return sideSafe[0] || sideAware[0] || horizontalFirst; + } + } +} + +const pathCache = new Map(); +const automaticPorts = automaticPortSpread(arch.connections, components); +function connectionSides(conn) { + const from = components.get(conn.from); + const to = components.get(conn.to); + return { + fromSide: chosenSide(conn.fromSide, defaultFromSide(from, to)), + toSide: chosenSide(conn.toSide, defaultToSide(from, to)), + }; +} + +function connectionEndpointSide(conn, endpoint) { + const field = endpoint === 'source' ? 'fromSide' : 'toSide'; + if (conn[field] && conn[field] !== 'auto') return conn[field]; + return connectionSides(conn)[field]; +} + +function pathFor(conn) { + if (pathCache.has(conn)) return pathCache.get(conn); + const from = components.get(conn.from); + const to = components.get(conn.to); + const ports = automaticPorts.get(conn); + const { fromSide, toSide } = connectionSides(conn); + const baseStart = ports?.from || anchor(from, fromSide); + const baseEnd = ports?.to || anchor(to, toSide); + const { start, end } = alignFacingPorts( + conn, + from, + to, + baseStart, + baseEnd, + fromSide, + toSide, + ports, + ); + const points = [start, ...routeVia(conn, from, to, start, end, fromSide, toSide), end]; + const routed = { d: roundedPath(points, 8), points }; + pathCache.set(conn, routed); + return routed; +} + +// ---- Rendering --------------------------------------------------------------- +function renderBoundaryFrame(b, index) { + const cls = b.kind === 'security-group' ? 'c-security-group' : 'c-region'; + const rx = b.kind === 'security-group' ? 8 : 12; + return ` `; +} + +function renderBoundaryLabel(b, index) { + const labelCls = b.kind === 'security-group' ? 't-security' : 't-cloud'; + return ` + + ${esc(b.label)} + `; +} + +function renderConnectionPath(conn, index) { + const [cls, marker] = arrowClassMap[conn.variant || 'default'] || arrowClassMap.default; + const routed = pathFor(conn); + const strokeWidth = conn.width || (conn.variant === 'emphasis' ? 1.8 : 1.5); + return ` `; +} + +function renderConnectionLabel(conn, index) { + if (!conn.label) return ''; + const [lx, ly] = labelPoint(conn, pathFor(conn).points); + const w = Math.max(30, textUnits(conn.label) * 4.8 + 10); + return ` + + ${esc(conn.label)} + `; +} + +function renderComponent(c) { + const fill = componentFill[c.type] || 'c-external'; + const accent = componentText[c.type] || 't-muted'; + const cx = c.cx; + const hasSub = c.sublabel != null && c.sublabel !== ''; + const labelY = hasSub ? c.y + c.height / 2 - 2 : c.y + c.height / 2 + 4; + const sub = hasSub + ? `\n ${esc(c.sublabel)}` + : ''; + const tag = c.tag + ? `\n ${esc(c.tag)}` + : ''; + const brand = renderBrandMark(c, { x: c.x + c.width - 22, y: c.y + 6 }); + const labelFontSize = fittedNodeFontSize(c.label, brandLabelFitWidth(c, c.width), 11, 8); + const passport = { kind: c.type, sublabel: c.sublabel, tag: c.tag, context: componentContext(c), ...brandMetadataFor(c) }; + return ` + ${focusNodeTitle(c.label, passport)} + + + ${renderSemanticSigil(c.type, { x: c.x + 6, y: c.y + 6 })}${brand ? `\n ${brand}` : ''} + ${esc(c.label)}${sub}${tag} + `; +} + +function renderLegend() { + const entries = architectureLegendEntries; + const relationshipObstacles = relationshipLegendObstacles(arch.connections, { + pointsFor: (connection) => pathFor(connection).points, + labelRectFor: (connection) => { + if (!connection.label) return null; + const [x, y] = labelPoint(connection, pathFor(connection).points); + const width = Math.max(30, textUnits(connection.label) * 4.8 + 10); + return { x: x - width / 2, y: y - 10, width, height: 14 }; + }, + }); + const contentBottom = Math.max( + 0, + ...[...components.values()].map((component) => component.y + component.height), + ...boundaries.map((boundary) => boundary.y + boundary.height), + ); + return renderResolvedLegend({ + entries, + locale: arch.meta.locale, + layout: { + x: layout.margin, + baselineY: legendY(), + width: viewBox[0] - layout.margin * 2, + minTitleY: contentBottom + 8, + obstacles: relationshipObstacles, + unfit: arch.meta?.legend === undefined ? 'hide' : 'error', + diagramType: 'architecture', + }, + renderSwatch: (entry) => ``, + }); +} + +function renderSvg() { + return ` +${svgAccessibleText(arch.meta, 'architecture')} +${renderDefinitions()} + + + + + +${boundaries.map(renderBoundaryFrame).join('\n\n')} + + +${asArray(arch.connections).map(renderConnectionPath).join('\n')} + + +${[...components.values()].map(renderComponent).join('\n\n')} + + +${asArray(arch.connections).map(renderConnectionLabel).join('\n')} + + +${boundaries.map(renderBoundaryLabel).join('\n\n')} + + +${renderLegend()} + `; +} + +validateArchitecture(); +if (layoutJsonMode) { + console.log(JSON.stringify(buildLayoutReport(), null, 2)); + process.exit(0); +} +writeDiagram({ + outPath, + template, + diagramType: 'architecture', + meta: arch.meta, + svg: renderSvg(), + cards: arch.cards, + sourceEvidence, +}); diff --git a/tools/archify/renderers/dataflow/README.md b/tools/archify/renderers/dataflow/README.md new file mode 100644 index 0000000..d5c43fc --- /dev/null +++ b/tools/archify/renderers/dataflow/README.md @@ -0,0 +1,104 @@ +# Data Flow Renderer + +Render `diagram_type: "dataflow"` JSON files into the standard Archify HTML +template. + +```bash +node archify/renderers/dataflow/render-dataflow.mjs input.dataflow.json output.html +``` + +The renderer validates input against `archify/schemas/dataflow.schema.json` +with the bundled standalone validator. No dependency installation is required. + +If `output.html` is omitted, the renderer uses `meta.output` from the JSON file +or falls back to `dataflow.html` in the current working directory. + +## Input + +Data-flow JSON files must set: + +```json +{ + "schema_version": 1, + "diagram_type": "dataflow", + "meta": { + "title": "Product Analytics Data Flow", + "viewBox": [940, 720] + }, + "stages": [], + "nodes": [], + "flows": [], + "cards": [] +} +``` + +A complete worked example lives at +`archify/examples/product-analytics.dataflow.json`. + +The schema lives at: + +```text +archify/schemas/dataflow.schema.json +``` + +## Legend + +The default visual legend derives kinds from `flows[].variant` (omitting +`variant` means `default`) and adds `database` only when a database node exists. +Supported `meta.legend.entries` keys, in stable order, are `emphasis`, +`security`, `dashed`, `database`, and `default`. Flow variants remain +visual-only because Archify has no compiled edge-kind facts in this slice. A +present `database` entry is different: it comes from exact +`nodes[].type: "database"` facts, so it publishes the normal Semantic Legend +count, accessible name, and keyboard interaction. Forcing `database` visible +without a database node keeps it visual-only. + +## Layout budget + +| Constant | Value | +|----------|-------| +| viewBox | default `[940, 720]`; schema minimum `[360, 360]` | +| Stages (2–5) | centers at x = 100 + stage×215; stage band 168 wide, header at y 46 | +| Row tops (`row` 0–4) | y = 128, 242, 356, 470, 584 (plus `yOffset`) | +| Default node | 112×58 | +| Node area | x within `[24, width − 24]`; y within `[104, height − 74]` | +| Node spacing | ≥10px between any two nodes (checked across stages and rows) | +| Flow length | ≥34px between endpoints | +| Legend row | y = height − 36 | + +Route presets for flows: `straight`, `vertical-channel`, `bottom-channel`, +`top-channel`, explicit `via` points, or the default `auto` (midpoint elbow). + +## Design Rules + +- Use stages for data lifecycle boundaries: source, ingest, process, store, + consume. +- Place nodes by stage index and row index; do not hand-place raw SVG for the + common case. +- Use flow labels to name the data asset, not the transport primitive: + `clickstream`, `identity map`, `normalized facts`, `feature vectors`. +- Use `classification` for short sensitivity or governance context: + `PII touch`, `non-PII`, `approved only`, `batch`, `read-only`. +- Use `security` for PII, policy, consent, access-control, or restricted joins. +- Use `emphasis` for the primary data path and `dashed` for async or batch + derivations. +- Keep labels short enough to fit in narrow previews. + +Schema violations exit non-zero with path-prefixed messages annotated with the +element's id or label. The renderer additionally fails when it can detect +layout problems, including missing stages, duplicate node IDs, nodes outside +the readable diagram area, node overlap, labels colliding with nodes or other +labels, labels wider than their node, unknown flow endpoints, missing flow +labels, unreadably short flows, flows crossing unrelated nodes (2px Clean Flow +clearance), or stages that exceed the viewBox. Stage frames remain intentional +pass-through containers. Text width +is estimated CJK-aware: fullwidth glyphs count as two units. + +Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper +X crossings then fail with `composition/proper-crossing`; default `standard` +keeps them as artifact-receipt warnings. Collinear stage corridors are outside +the proper-X rule, but a separate gate warns in `standard` and fails in +`showcase` when unrelated flows overlap for at least 8px. Shared semantic +endpoints, point touches, and shorter overlaps remain valid. Showcase also +rejects any route segment below 8px and any interior turn segment below 16px; +ordinary 8–15px endpoint stubs remain valid. diff --git a/tools/archify/renderers/dataflow/render-dataflow.mjs b/tools/archify/renderers/dataflow/render-dataflow.mjs new file mode 100644 index 0000000..3eda9d4 --- /dev/null +++ b/tools/archify/renderers/dataflow/render-dataflow.mjs @@ -0,0 +1,483 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs'; +import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, loadDiagramWithBrandMarks, writeDiagram, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs'; +import { throwDiagnosticProblems } from '../shared/diagnostics.mjs'; +import { resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs'; +import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs'; +import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs'; +import { translateMessage as i18nText } from '../shared/i18n.mjs'; +import { + asArray, + isFinitePoint, + rectsOverlap, + cleanEndpointSideProblems, + cleanFlowProblems, + cleanCrossingProblems, + cleanAmbiguousCorridorProblems, + cleanBorderRunProblems, + cleanRouteRhythmProblems, + cleanLabelRouteClearanceProblems, + suggestLabelObstacleFix, + suggestLabelPairFix, + anchor, + automaticPortSpread, + defaultFromSide, + defaultToSide, + chosenSide, + polylinePath, + routePointsValue, + labelPoint, + componentFill, + componentText, + arrowClassMap, + variantAccent +} from '../shared/geometry.mjs'; + +const nodeTextFit = { + sublabelPreferred: 7, + sublabelMinimum: 6, + tagPreferred: 7, + tagMinimum: 6, +}; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const { diagram: dataflow, template, outPath } = await loadDiagramWithBrandMarks({ + rendererDir: __dirname, + diagramType: 'dataflow', + defaultExample: 'product-analytics.dataflow.json' +}); + +const viewBox = dataflow.meta?.viewBox || [940, 720]; +const layout = { + stageY: 46, + stageH: 36, + stageBottomPad: 74, + leftX: 100, + colGap: 215, + stageW: 168, + nodeW: 112, + nodeH: 58, + rowYs: [128, 242, 356, 470, 584], + labelH: 16 +}; + +function flowLabelSize(flow) { + const longestLine = Math.max(textUnits(flow.label), textUnits(flow.classification || '')); + return { + width: Math.round(Math.max(34, longestLine * 4.9 + 12) * 10) / 10, + height: flow.classification ? 27 : layout.labelH, + }; +} + +function stageX(index) { + return layout.leftX + index * layout.colGap; +} + +function stageFrame(stage, index) { + return { + id: index, + label: stage.label, + kind: 'stage', + x: stageX(index) - layout.stageW / 2, + y: layout.stageY, + width: layout.stageW, + height: viewBox[1] - layout.stageY - layout.stageBottomPad, + radius: 10, + }; +} + +const compositionFrames = asArray(dataflow.stages).map(stageFrame); + +function measureNode(node) { + const width = node.width || layout.nodeW; + const height = node.height || layout.nodeH; + const cx = stageX(node.stage); + const y = layout.rowYs[node.row] + (node.yOffset || 0); + return { + ...node, + width, + height, + cx, + cy: y + height / 2, + x: cx - width / 2, + y + }; +} + +const nodes = new Map(asArray(dataflow.nodes).map((node) => [node.id, measureNode(node)])); +const nodeSteps = new Map(); +for (const [index, flow] of asArray(dataflow.flows).entries()) { + if (!nodeSteps.has(flow.from)) nodeSteps.set(flow.from, index); + if (!nodeSteps.has(flow.to)) nodeSteps.set(flow.to, index + 1); +} +for (const [index, node] of asArray(dataflow.nodes).entries()) { + if (!nodeSteps.has(node.id)) nodeSteps.set(node.id, index); +} + +function validateDataflow() { + const problems = []; + if (nodes.size !== asArray(dataflow.nodes).length) problems.push('Node ids must be unique.'); + + const stageCount = asArray(dataflow.stages).length; + for (const node of nodes.values()) { + if (typeof node.stage !== 'number' || node.stage < 0 || node.stage >= stageCount) { + problems.push(`Node "${node.id}" uses invalid stage ${node.stage} — valid stages are 0..${stageCount - 1}.`); + } + if (typeof node.row !== 'number' || node.row < 0 || node.row >= layout.rowYs.length) { + problems.push(`Node "${node.id}" uses invalid row ${node.row} — valid rows are 0..${layout.rowYs.length - 1}.`); + } + if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) { + problems.push(`Node "${node.id}" produced non-finite coordinates — check stage, row, width, height, and yOffset are numbers.`); + continue; + } + if (node.x < 24 || node.x + node.width > viewBox[0] - 24) { + problems.push(`Node "${node.id}" exceeds the horizontal bounds of the viewBox — reduce node.width or increase meta.viewBox[0].`); + } + if (node.y < layout.stageY + layout.stageH + 22 || node.y + node.height > viewBox[1] - layout.stageBottomPad) { + problems.push(`Node "${node.id}" exceeds the readable diagram area — keep y between ${layout.stageY + layout.stageH + 22} and ${viewBox[1] - layout.stageBottomPad} (adjust row/yOffset or increase meta.viewBox[1]).`); + } + const estLabelW = textUnits(node.label) * 6.2; + if (estLabelW > node.width + 6) { + problems.push(`Label "${node.label}" (~${Math.round(estLabelW)}px) is wider than node "${node.id}" (${node.width}px) — shorten the label or increase node.width.`); + } + const brandRailProblem = brandTopRailProblem(node, node.width, 8); + if (brandRailProblem) problems.push(brandRailProblem); + // sublabel and tag render as single unwrapped elements; shrink-to-fit + // handles the ordinary case, this rejects what it cannot rescue. + const availableTextW = availableNodeTextWidth(node.width); + for (const [field, value, minimum] of [ + ['Sublabel', node.sublabel, nodeTextFit.sublabelMinimum], + ['Tag', node.tag, nodeTextFit.tagMinimum], + ]) { + if (!value) continue; + const minimumW = minimumNodeTextWidth(value, minimum); + if (minimumW > availableTextW) { + problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but node "${node.id}" provides ${availableTextW}px — shorten the ${field.toLowerCase()} or increase node.width.`); + } + } + } + + const nodeList = asArray(dataflow.nodes); + for (let i = 0; i < nodeList.length; i += 1) { + for (let j = i + 1; j < nodeList.length; j += 1) { + const a = nodes.get(nodeList[i].id); + const b = nodes.get(nodeList[j].id); + if (rectsOverlap(a, b, 10)) { + problems.push(`Nodes "${a.id}" and "${b.id}" are less than 10px apart — move one to another stage/row or adjust yOffset.`); + } + } + } + + for (const flow of asArray(dataflow.flows)) { + if (!nodes.has(flow.from)) problems.push(`Flow "${flow.label || flow.from}" references unknown source "${flow.from}".`); + if (!nodes.has(flow.to)) problems.push(`Flow "${flow.label || flow.to}" references unknown target "${flow.to}".`); + if (!flow.label) problems.push(`Flow "${flow.from}" -> "${flow.to}" must include a short data label.`); + if (nodes.has(flow.from) && nodes.has(flow.to)) { + const routed = pathFor(flow); + const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]]; + const distance = Math.hypot(end[0] - start[0], end[1] - start[1]); + if (distance < 34) problems.push(`Flow "${flow.label}" is too short (${Math.round(distance)}px; minimum 34px) — route it through a channel or spread its nodes.`); + if (Array.isArray(flow.via)) { + for (let segmentIndex = 0; segmentIndex < routed.points.length - 1; segmentIndex += 1) { + const segmentStart = routed.points[segmentIndex]; + const segmentEnd = routed.points[segmentIndex + 1]; + const isDiagonal = Math.abs(segmentStart[0] - segmentEnd[0]) > 0.01 + && Math.abs(segmentStart[1] - segmentEnd[1]) > 0.01; + if (!isDiagonal) continue; + const viaIndex = Math.min(segmentIndex, flow.via.length - 1); + problems.push(`Flow "${flow.label}" has a diagonal segment from (${segmentStart.join(', ')}) to (${segmentEnd.join(', ')}) — align via[${viaIndex}] with its adjacent point by sharing the same x or y coordinate.`); + } + } + } + } + + problems.push(...cleanEndpointSideProblems({ + relations: dataflow.flows, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'dataflow', + relationCollection: 'flows', + fromSideFor: (flow) => flowSides(flow).fromSide, + toSideFor: (flow) => flowSides(flow).toSide, + routeHint: 'keep automatic routing, or choose fromSide/toSide and via points whose first and final segments cross node borders perpendicularly', + })); + problems.push(...cleanFlowProblems({ + relations: dataflow.flows, + obstacles: nodes.values(), + pathFor, + diagramType: 'dataflow', + relationCollection: 'flows', + obstacleKind: 'node', + routeHint: 'adjust fromSide/toSide, set route/via or channelX/channelY, or move the node to another stage/row' + })); + problems.push(...cleanCrossingProblems({ + relations: dataflow.flows, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'dataflow', + relationCollection: 'flows', + profile: dataflow.meta?.quality_profile, + routeHint: 'adjust route/via or channelX/channelY so the flows use separate stage corridors' + })); + problems.push(...cleanAmbiguousCorridorProblems({ + relations: dataflow.flows, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'dataflow', + relationCollection: 'flows', + profile: dataflow.meta?.quality_profile, + routeHint: 'adjust route/via or channelX/channelY so unrelated flows do not visually merge' + })); + problems.push(...cleanBorderRunProblems({ + relations: dataflow.flows, + endpointIds: new Set(nodes.keys()), + frames: compositionFrames, + pathFor, + diagramType: 'dataflow', + relationCollection: 'flows', + profile: dataflow.meta?.quality_profile, + routeHint: 'adjust route/via or channelX/channelY so the flow crosses the stage perpendicularly instead of following its border' + })); + problems.push(...cleanRouteRhythmProblems({ + relations: dataflow.flows, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'dataflow', + relationCollection: 'flows', + profile: dataflow.meta?.quality_profile, + routeHint: 'adjust route/via or channelX/channelY so each turn uses a clear inter-stage corridor' + })); + + const labelRects = []; + for (const [flowIndex, flow] of asArray(dataflow.flows).entries()) { + if (!flow.label || !nodes.has(flow.from) || !nodes.has(flow.to)) continue; + const [lx, ly] = labelPoint(flow, pathFor(flow).points); + const { width, height } = flowLabelSize(flow); + labelRects.push({ relation: flow, relationIndex: flowIndex, label: flow.label, x: lx - width / 2, y: ly - 11, width, height, lx, ly }); + } + for (const rect of labelRects) { + for (const node of nodes.values()) { + if (rectsOverlap(rect, node, -2)) { + problems.push(`Label "${rect.label}" overlaps node "${node.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, node, 'node')}`); + } + } + } + for (let i = 0; i < labelRects.length; i += 1) { + for (let j = i + 1; j < labelRects.length; j += 1) { + if (rectsOverlap(labelRects[i], labelRects[j], -2)) { + problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}`); + } + } + } + problems.push(...cleanLabelRouteClearanceProblems({ + relations: dataflow.flows, + labels: labelRects, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'dataflow', + relationCollection: 'flows', + profile: dataflow.meta?.quality_profile, + routeHint: 'adjust labelAt, labelDx, labelDy, or labelSegment; otherwise adjust the other flow route/via/channelX/channelY' + })); + + const lastStageX = stageX(asArray(dataflow.stages).length - 1); + if (lastStageX + layout.stageW / 2 > viewBox[0] - 24) { + problems.push(`Stages exceed viewBox width — set meta.viewBox[0] to at least ${Math.ceil(lastStageX + layout.stageW / 2 + 24)}.`); + } + + if (problems.length) { + throwDiagnosticProblems('Data-flow layout validation failed', problems, { + subject: { diagramType: 'dataflow' }, + }); + } +} + +function routeVia(flow, from, to, start, end) { + if (flow.via) return flow.via; + switch (flow.route || 'auto') { + case 'straight': + return []; + case 'vertical-channel': { + const x = flow.channelX ?? start[0] + (end[0] > start[0] ? 44 : -44); + return [[x, start[1]], [x, end[1]]]; + } + case 'bottom-channel': { + const y = flow.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 26; + return [[start[0], y], [end[0], y]]; + } + case 'top-channel': { + const y = flow.channelY ?? Math.min(from.y, to.y) - 24; + return [[start[0], y], [end[0], y]]; + } + case 'auto': + default: { + if (Math.abs(start[1] - end[1]) < 4) return []; + const midX = start[0] + (end[0] - start[0]) / 2; + return [[midX, start[1]], [midX, end[1]]]; + } + } +} + +const pathCache = new Map(); + +function flowSides(flow) { + const from = nodes.get(flow.from); + const to = nodes.get(flow.to); + return { + fromSide: chosenSide(flow.fromSide, defaultFromSide(from, to)), + toSide: chosenSide(flow.toSide, defaultToSide(from, to)), + }; +} + +const automaticPorts = automaticPortSpread(dataflow.flows, nodes, { + sideFor: (flow, endpoint) => flowSides(flow)[endpoint === 'source' ? 'fromSide' : 'toSide'], +}); + +function pathFor(flow) { + if (pathCache.has(flow)) return pathCache.get(flow); + const from = nodes.get(flow.from); + const to = nodes.get(flow.to); + const ports = automaticPorts.get(flow); + const { fromSide, toSide } = flowSides(flow); + const start = ports?.from || anchor(from, fromSide); + const end = ports?.to || anchor(to, toSide); + // Drop consecutive duplicate points so a purely vertical (or horizontal) + // auto-route never emits a zero-length final segment — SVG derives + // marker-end orientation from the last segment, and a degenerate segment + // leaves the arrowhead angle undefined (see #169). + const rawPoints = [start, ...routeVia(flow, from, to, start, end), end]; + const points = []; + for (const p of rawPoints) { + const prev = points.at(-1); + if (!prev || Math.abs(p[0] - prev[0]) > 0.0001 || Math.abs(p[1] - prev[1]) > 0.0001) { + points.push(p); + } + } + // Guard against an all-degenerate route (e.g. start === end): keep both + // endpoints so the path is still well-formed even if the marker is hidden. + if (points.length < 2) points.push(end); + const routed = { d: polylinePath(points), points }; + pathCache.set(flow, routed); + return routed; +} + +function renderStage(stage, index) { + const frame = compositionFrames[index]; + const cx = stageX(index); + return ` + ${String(index + 1).padStart(2, '0')} / ${esc(stage.label)}`; +} + +function renderNode(node) { + const fill = componentFill[node.type] || 'c-external'; + const accent = componentText[node.type] || 't-muted'; + const hasSub = node.sublabel != null && node.sublabel !== ''; + const sub = hasSub + ? `\n ${esc(node.sublabel)}` + : ''; + const tag = node.tag + ? `\n ${esc(node.tag)}` + : ''; + const stage = asArray(dataflow.stages)[node.stage]; + const context = stage + ? `${String(node.stage + 1).padStart(2, '0')} / ${stage.label}` + : i18nText(dataflow.meta.locale, 'node.context.dataflow'); + const brand = renderBrandMark(node, { x: node.x + node.width - 22, y: node.y + 6 }); + const labelFontSize = fittedNodeFontSize(node.label, brandLabelFitWidth(node, node.width), 10, 8); + const passport = { kind: node.type, sublabel: node.sublabel, tag: node.tag, context, ...brandMetadataFor(node) }; + return ` + ${focusNodeTitle(node.label, passport)} + + + ${renderSemanticSigil(node.type, { x: node.x + 6, y: node.y + 6 })}${brand ? `\n ${brand}` : ''} + ${esc(node.label)}${sub}${tag} + `; +} + +function renderFlowPath(flow, index) { + const [cls, marker] = arrowClassMap[flow.variant || 'default'] || arrowClassMap.default; + const routed = pathFor(flow); + const strokeWidth = flow.width || (flow.variant === 'emphasis' ? 1.8 : 1.4); + return ` `; +} + +function renderFlowLabel(flow, index) { + const routed = pathFor(flow); + const [lx, ly] = labelPoint(flow, routed.points); + const { width: labelW, height: labelH } = flowLabelSize(flow); + const classification = flow.classification + ? `\n ${esc(flow.classification)}` + : ''; + return ` + + ${esc(flow.label)}${classification} + `; +} + +const LEGEND_CATALOG = [ + { kind: 'emphasis', className: 'a-emphasis', marker: 'arrowhead-emphasis', strokeWidth: 1.8, swatchWidth: 34, swatchGap: 9, interactive: false }, + { kind: 'security', className: 'a-security', marker: 'arrowhead-security', swatchWidth: 34, swatchGap: 9, interactive: false }, + { kind: 'dashed', className: 'a-dashed', marker: 'arrowhead-dashed', swatchWidth: 34, swatchGap: 9, interactive: false }, + { kind: 'database' }, + { kind: 'default', className: 'a-default', marker: 'arrowhead', swatchWidth: 34, swatchGap: 9, interactive: false }, +].map((entry) => ({ + ...entry, + label: i18nText(dataflow.meta.locale, `legend.dataflow.${entry.kind}`), +})); + +function renderLegend() { + const presentKinds = new Set(asArray(dataflow.flows).map((flow) => flow.variant || 'default')); + if ([...nodes.values()].some((node) => node.type === 'database')) presentKinds.add('database'); + const entries = resolveLegend(dataflow.meta?.legend, LEGEND_CATALOG, presentKinds); + return renderResolvedLegend({ + entries, + locale: dataflow.meta.locale, + layout: { + x: 40, + baselineY: viewBox[1] - 36, + width: viewBox[0] - 80, + minTitleY: viewBox[1] - 66, + unfit: dataflow.meta?.legend === undefined ? 'hide' : 'error', + diagramType: 'dataflow', + }, + renderSwatch: (entry) => entry.kind === 'database' + ? `` + : ``, + }); +} + +function renderSvg() { + return ` +${svgAccessibleText(dataflow.meta, 'dataflow')} +${renderDefinitions()} + + + + + +${dataflow.stages.map(renderStage).join('\n\n')} + + +${asArray(dataflow.flows).map(renderFlowPath).join('\n')} + + +${[...nodes.values()].map(renderNode).join('\n\n')} + + +${asArray(dataflow.flows).map(renderFlowLabel).join('\n')} + + +${renderLegend()} + `; +} + +validateDataflow(); +writeDiagram({ + outPath, + template, + diagramType: 'dataflow', + meta: dataflow.meta, + svg: renderSvg(), + cards: dataflow.cards, +}); diff --git a/tools/archify/renderers/lifecycle/README.md b/tools/archify/renderers/lifecycle/README.md new file mode 100644 index 0000000..b66a7dd --- /dev/null +++ b/tools/archify/renderers/lifecycle/README.md @@ -0,0 +1,115 @@ +# Lifecycle Renderer + +Render `diagram_type: "lifecycle"` JSON files into the standard Archify HTML +template. + +```bash +node archify/renderers/lifecycle/render-lifecycle.mjs input.lifecycle.json output.html +``` + +The renderer validates input against `archify/schemas/lifecycle.schema.json` +with the bundled standalone validator. No dependency installation is required. + +If `output.html` is omitted, the renderer uses `meta.output` from the JSON file +or falls back to `lifecycle.html` in the current working directory. + +## Input + +Lifecycle JSON files must set: + +```json +{ + "schema_version": 1, + "diagram_type": "lifecycle", + "meta": { + "title": "Agent Run Lifecycle", + "viewBox": [980, 660] + }, + "lanes": [], + "states": [], + "transitions": [], + "cards": [] +} +``` + +Lane ids are semantic and reserved: a lane with id `main` is required and maps +to the top phase band; `terminal` maps to the bottom outcome band; every other +lane id (up to 4 lanes total) shares the single middle event band. The three +band headers render from your lane labels — the middle band joins the labels of +all event lanes with ` + `. A complete worked example lives at +`archify/examples/agent-run.lifecycle.json`. + +The schema lives at: + +```text +archify/schemas/lifecycle.schema.json +``` + +## Legend + +The default legend derives kinds from `states[].type`. Supported +`meta.legend.entries` keys, in stable order, are `start`, `active`, `waiting`, +`decision`, `success`, `failure`, `neutral`, and `external`. Labels and +visibility may be overridden through the shared legend contract; only kinds +backed by rendered states receive Semantic Legend controls. + +## Layout budget + +| Band | Lane id | Top y | Column centers | Default state | +|------|---------|-------|----------------|---------------| +| Phase | `main` (required) | 126 | `col` 0–4 → x = 94, 248, 402, 556, 710 | 118×62 | +| Event | any other id | 278 | `col` 0–2 → x = 402, 556, 710 | 126×58 | +| Outcome | `terminal` | 450 | `col` 0–2 → x = 402, 556, 710 | 118×58 | + +Event and terminal columns are intentionally offset from the main rail: +event/terminal `col: N` uses the same x coordinate as main `col: N + 2`. +For example, lower-band columns 0, 1, and 2 align beneath main columns 2, 3, +and 4 respectively. + +| Constant | Value | +|----------|-------| +| viewBox | default `[980, 660]`; schema minimum `[420, 566]` | +| State area | x within `[32, width − 32]`; state bottom at or above `height − 122` | +| State spacing | ≥10px between any two states — checked across lanes, because all event lanes share one band; separate same-band states with `col` or `yOffset` | +| Transition length | ≥32px between endpoints | +| Legend row | final baseline y = height − 36; extra measured rows wrap upward | + +The primary lifecycle rail runs along the phase band and extends to the +furthest occupied phase column. Route presets for transitions: `straight`, +`drop` (bend at `channelY`, defaulting to the vertical midpoint), +`bottom-channel`, `top-channel`, `right-channel`, `left-channel`, explicit +`via` points, or the default `auto`. Multi-segment transitions get rounded +corners; tune them with `cornerRadius` (default 10, `0` for sharp bends). + +## Design Rules + +- Treat lifecycle diagrams as a phase map, not a dense state-transition graph. +- Put the primary lifecycle on one horizontal rail using the `main` lane. +- Use `step` labels for ordered phases, such as `01`, `02`, and `03`. +- Use lower lanes only for interruptions, recovery, and terminal exits. +- Keep transition labels out of the main SVG unless the label is essential; + prefer node labels, tags, legend entries, and summary cards. +- Avoid diagonal and crossing lines. Terminal exits should drop vertically from + their source event whenever possible. +- Use `success` for completion, `failure` for failure/terminal exits, + `waiting` for pauses, and `decision` for quality gates. + +Schema violations exit non-zero with path-prefixed messages annotated with the +element's id or label. The renderer additionally fails when it can detect +layout problems, including a missing `main` lane, duplicate state IDs, unknown +lanes, unknown transition endpoints, states outside the lifecycle area, +overlapping states (including across lanes), labels colliding with states or +other labels, labels wider than their state, unreadably short transitions, or +transitions crossing unrelated states (2px Clean Flow clearance). Lifecycle +bands remain intentional pass-through containers. +Text width is estimated CJK-aware: fullwidth glyphs count as two units. + +Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper +X crossings then fail with `composition/proper-crossing`; default `standard` +keeps them as artifact-receipt warnings. The final artifact check samples +rounded `Q` corners. Collinear corridors remain outside the proper-X rule, but +a separate gate warns in `standard` and fails in `showcase` when unrelated +transitions overlap for at least 8px. Shared semantic endpoints, point touches, +and shorter overlaps remain valid. Showcase also rejects any route segment +below 8px and any interior turn segment below 16px; ordinary 8–15px endpoint +stubs remain valid. diff --git a/tools/archify/renderers/lifecycle/render-lifecycle.mjs b/tools/archify/renderers/lifecycle/render-lifecycle.mjs new file mode 100644 index 0000000..eb7922c --- /dev/null +++ b/tools/archify/renderers/lifecycle/render-lifecycle.mjs @@ -0,0 +1,561 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs'; +import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, loadDiagramWithBrandMarks, writeDiagram, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs'; +import { throwDiagnosticProblems } from '../shared/diagnostics.mjs'; +import { resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs'; +import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs'; +import { brandLabelFitWidth, brandMarkFor, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs'; +import { translateMessage as i18nText } from '../shared/i18n.mjs'; +import { + asArray, + isFinitePoint, + rectsOverlap, + cleanEndpointSideProblems, + cleanFlowProblems, + cleanCrossingProblems, + cleanAmbiguousCorridorProblems, + cleanBorderRunProblems, + cleanRouteRhythmProblems, + cleanLabelRouteClearanceProblems, + suggestLabelObstacleFix, + suggestLabelPairFix, + anchor, + automaticPortSpread, + defaultFromSide, + defaultToSide, + chosenSide, + roundedPath, + routePointsValue, + labelPoint, + arrowClassMap, + variantAccent +} from '../shared/geometry.mjs'; + +const stateTextFit = { + sublabelPreferred: 7, + sublabelMinimum: 6, + tagPreferred: 7, + tagMinimum: 6, +}; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const { diagram: lifecycle, template, outPath } = await loadDiagramWithBrandMarks({ + rendererDir: __dirname, + diagramType: 'lifecycle', + defaultExample: 'agent-run.lifecycle.json' +}); + +const viewBox = lifecycle.meta?.viewBox || [980, 660]; +const layout = { + phaseY: 126, + eventY: 278, + outcomeY: 450, + phaseW: 118, + phaseH: 62, + eventW: 126, + eventH: 58, + outcomeW: 118, + outcomeH: 58, + phaseXs: [94, 248, 402, 556, 710], + eventXs: [402, 556, 710], + outcomeXs: [402, 556, 710] +}; + +const typeClass = { + start: 'c-frontend', + active: 'c-backend', + waiting: 'c-cloud', + decision: 'c-security', + success: 'c-database', + failure: 'c-security', + neutral: 'c-external', + external: 'c-external' +}; + +const textClass = { + start: 't-frontend', + active: 't-backend', + waiting: 't-cloud', + decision: 't-security', + success: 't-database', + failure: 't-security', + neutral: 't-muted', + external: 't-muted' +}; + +function legendY() { + return viewBox[1] - 36; +} + +// Keep the authored state-placement contract independent from the measured +// legend's lower baseline. Moving legend chrome must not admit new state +// geometry into the reserved outcome/legend band. +function lifecycleAreaBottom() { + return viewBox[1] - 122; +} + +// Lane semantics are fixed: lane id "main" maps to the top phase band, lane id +// "terminal" maps to the bottom outcome band, and every other lane shares the +// middle event band (separated visually via yOffset). +function bandFor(lane) { + if (lane === 'main') return 'phase'; + if (lane === 'terminal') return 'outcome'; + return 'event'; +} + +function measureState(state) { + const isPhase = bandFor(state.lane) === 'phase'; + const isOutcome = bandFor(state.lane) === 'outcome'; + const width = state.width || (isPhase ? layout.phaseW : isOutcome ? layout.outcomeW : layout.eventW); + const height = state.height || (isPhase ? layout.phaseH : isOutcome ? layout.outcomeH : layout.eventH); + const xs = isPhase ? layout.phaseXs : isOutcome ? layout.outcomeXs : layout.eventXs; + const cx = xs[state.col] ?? xs[xs.length - 1]; + const y = ( + isPhase ? layout.phaseY : + isOutcome ? layout.outcomeY : + layout.eventY + ) + (state.yOffset || 0); + return { + ...state, + width, + height, + x: cx - width / 2, + y, + cx, + cy: y + height / 2 + }; +} + +const states = new Map(asArray(lifecycle.states).map((state) => [state.id, measureState(state)])); +const laneLabels = new Map(asArray(lifecycle.lanes).map((lane) => [lane.id, lane.label])); +const stateSteps = new Map(); +for (const [index, transition] of asArray(lifecycle.transitions).entries()) { + if (!stateSteps.has(transition.from)) stateSteps.set(transition.from, index); + if (!stateSteps.has(transition.to)) stateSteps.set(transition.to, index + 1); +} +for (const [index, state] of asArray(lifecycle.states).entries()) { + if (!stateSteps.has(state.id)) stateSteps.set(state.id, index); +} + +function validateLifecycle() { + const problems = []; + if (states.size !== asArray(lifecycle.states).length) problems.push('State ids must be unique.'); + + // The three bands are fixed at y=112/264/436. Preserve the original + // outcome/legend reserve even though measured legend rows now sit lower. + if (lifecycleAreaBottom() + 4 < 448) { + problems.push(`viewBox height ${viewBox[1]} is too short for the fixed band layout — set meta.viewBox[1] to at least 566.`); + } + + const laneIds = new Set(asArray(lifecycle.lanes).map((lane) => lane.id)); + if (laneIds.size !== asArray(lifecycle.lanes).length) problems.push('Lane ids must be unique.'); + if (!laneIds.has('main')) { + problems.push('Lifecycle diagrams need a lane with id "main" (the phase rail). Lane ids "main" and "terminal" are reserved: "main" maps to the top phase band, "terminal" to the bottom outcome band, and all other lanes share the middle event band.'); + } + + for (const state of states.values()) { + if (!laneIds.has(state.lane)) { + problems.push(`State "${state.id}" uses unknown lane "${state.lane}".`); + continue; + } + const band = bandFor(state.lane); + const maxCol = band === 'phase' + ? layout.phaseXs.length + : band === 'outcome' + ? layout.outcomeXs.length + : layout.eventXs.length; + if (!Number.isInteger(state.col) || state.col < 0 || state.col >= maxCol) { + problems.push(`State "${state.id}" uses invalid column ${state.col} — the ${band} band has integer columns 0..${maxCol - 1}.`); + continue; + } + if (!isFinitePoint(state.x, state.y, state.cx, state.cy)) { + problems.push(`State "${state.id}" produced non-finite coordinates — check col, width, height, and yOffset are numbers.`); + continue; + } + if (state.x < 32 || state.x + state.width > viewBox[0] - 32) { + problems.push(`State "${state.id}" exceeds the horizontal bounds of the diagram — reduce state.width or increase meta.viewBox[0].`); + } + if (state.y < 64 || state.y + state.height > lifecycleAreaBottom()) { + problems.push(`State "${state.id}" exceeds the vertical lifecycle area — keep y between 64 and ${lifecycleAreaBottom()} (adjust yOffset or increase meta.viewBox[1]).`); + } + const estLabelW = textUnits(state.label) * 6.2; + if (estLabelW > state.width + 6) { + problems.push(`Label "${state.label}" (~${Math.round(estLabelW)}px) is wider than state "${state.id}" (${state.width}px) — shorten the label or increase state.width.`); + } + const brandRailProblem = brandTopRailProblem(state, state.width, 8, 'State'); + if (brandRailProblem) problems.push(brandRailProblem); + // sublabel and tag render as single unwrapped elements; shrink-to-fit + // handles the ordinary case, this rejects what it cannot rescue. + const availableTextW = availableNodeTextWidth(state.width); + for (const [field, value, minimum] of [ + ['Sublabel', state.sublabel, stateTextFit.sublabelMinimum], + ['Tag', state.tag, stateTextFit.tagMinimum], + ]) { + if (!value) continue; + const minimumW = minimumNodeTextWidth(value, minimum); + if (minimumW > availableTextW) { + problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but state "${state.id}" provides ${availableTextW}px — shorten the ${field.toLowerCase()} or increase state.width.`); + } + } + } + + // All non-main/non-terminal lanes share the same y band, so the overlap + // check must run across lanes — not per-lane. + const allStates = [...states.values()]; + for (let i = 0; i < allStates.length; i += 1) { + for (let j = i + 1; j < allStates.length; j += 1) { + if (rectsOverlap(allStates[i], allStates[j], 10)) { + problems.push(`States "${allStates[i].id}" and "${allStates[j].id}" are less than 10px apart — move one to another col or separate them with yOffset (lanes other than "main"/"terminal" share one band).`); + } + } + } + + for (const transition of asArray(lifecycle.transitions)) { + if (!states.has(transition.from)) problems.push(`Transition "${transition.label || transition.from}" references unknown source "${transition.from}".`); + if (!states.has(transition.to)) problems.push(`Transition "${transition.label || transition.to}" references unknown target "${transition.to}".`); + if (states.has(transition.from) && states.has(transition.to)) { + const routed = pathFor(transition); + const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]]; + const distance = Math.hypot(end[0] - start[0], end[1] - start[1]); + if (distance < 32) problems.push(`Transition "${transition.label || `${transition.from}->${transition.to}`}" is too short (${Math.round(distance)}px; minimum 32px) — route it through a channel or drop its label.`); + } + } + + // Authored via points are authoritative in schema v1, including under a + // quality profile. Preserve and render them exactly: applying the endpoint + // gate would either reject an existing typed input or require silently + // falsifying its geometry. Automatic routes still receive the side gate. + problems.push(...cleanEndpointSideProblems({ + relations: lifecycle.transitions, + endpointIds: new Set(states.keys()), + pathFor, + diagramType: 'lifecycle', + relationCollection: 'transitions', + fromSideFor: (transition) => transitionSides(transition).fromSide, + toSideFor: (transition) => transitionSides(transition).toSide, + shouldCheckRelation: (transition) => !Array.isArray(transition.via), + routeHint: 'keep automatic routing, or choose fromSide/toSide and via points whose first and final segments cross state borders perpendicularly', + })); + problems.push(...cleanFlowProblems({ + relations: lifecycle.transitions, + obstacles: states.values(), + pathFor, + diagramType: 'lifecycle', + relationCollection: 'transitions', + obstacleKind: 'state', + routeHint: 'adjust fromSide/toSide, set route/via or channelX/channelY, or move the state with col/yOffset' + })); + problems.push(...cleanCrossingProblems({ + relations: lifecycle.transitions, + endpointIds: new Set(states.keys()), + pathFor, + diagramType: 'lifecycle', + relationCollection: 'transitions', + profile: lifecycle.meta?.quality_profile, + routeHint: 'adjust route/via or channelX/channelY so the transitions use separate lifecycle corridors' + })); + problems.push(...cleanAmbiguousCorridorProblems({ + relations: lifecycle.transitions, + endpointIds: new Set(states.keys()), + pathFor, + diagramType: 'lifecycle', + relationCollection: 'transitions', + profile: lifecycle.meta?.quality_profile, + routeHint: 'adjust route/via or channelX/channelY so unrelated transitions do not visually merge' + })); + // Lifecycle bands are dashed reading guides, not closed containers. Keep the + // shared contract wired with an explicit empty frame set so future typed + // lifecycle containers cannot accidentally inherit presentation geometry. + problems.push(...cleanBorderRunProblems({ + relations: lifecycle.transitions, + endpointIds: new Set(states.keys()), + frames: [], + pathFor, + diagramType: 'lifecycle', + relationCollection: 'transitions', + profile: lifecycle.meta?.quality_profile + })); + problems.push(...cleanRouteRhythmProblems({ + relations: lifecycle.transitions, + endpointIds: new Set(states.keys()), + pathFor, + diagramType: 'lifecycle', + relationCollection: 'transitions', + profile: lifecycle.meta?.quality_profile, + routeHint: 'move route/via or channel coordinates so each lifecycle turn has a readable run-up' + })); + + const labelRects = []; + for (const [transitionIndex, transition] of asArray(lifecycle.transitions).entries()) { + if (!transition.label || !states.has(transition.from) || !states.has(transition.to)) continue; + const [lx, ly] = labelPoint(transition, pathFor(transition).points); + const longestLine = Math.max(textUnits(transition.label), textUnits(transition.note || '')); + const width = Math.max(32, longestLine * 4.9 + 12); + const height = transition.note ? 27 : 16; + labelRects.push({ relation: transition, relationIndex: transitionIndex, label: transition.label, x: lx - width / 2, y: ly - 11, width, height, lx, ly }); + } + for (const rect of labelRects) { + for (const state of states.values()) { + if (rectsOverlap(rect, state, -2)) { + problems.push(`Label "${rect.label}" overlaps state "${state.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, state, 'state')}`); + } + } + } + for (let i = 0; i < labelRects.length; i += 1) { + for (let j = i + 1; j < labelRects.length; j += 1) { + if (rectsOverlap(labelRects[i], labelRects[j], -2)) { + problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}`); + } + } + } + problems.push(...cleanLabelRouteClearanceProblems({ + relations: lifecycle.transitions, + labels: labelRects, + endpointIds: new Set(states.keys()), + pathFor, + diagramType: 'lifecycle', + relationCollection: 'transitions', + profile: lifecycle.meta?.quality_profile, + })); + + if (problems.length) { + throwDiagnosticProblems('Lifecycle layout validation failed', problems, { + subject: { diagramType: 'lifecycle' }, + }); + } +} + +function routeVia(transition, from, to, start, end, fromSide, toSide) { + if (transition.via) return transition.via; + switch (transition.route || 'auto') { + case 'straight': + return []; + case 'drop': { + const y = transition.channelY ?? (start[1] + end[1]) / 2; + return [[start[0], y], [end[0], y]]; + } + case 'bottom-channel': { + const y = transition.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 34; + return [[start[0], y], [end[0], y]]; + } + case 'top-channel': { + const y = transition.channelY ?? Math.min(from.y, to.y) - 28; + return [[start[0], y], [end[0], y]]; + } + case 'right-channel': { + const x = transition.channelX ?? Math.max(from.x + from.width, to.x + to.width) + 36; + return [[x, start[1]], [x, end[1]]]; + } + case 'left-channel': { + const x = transition.channelX ?? Math.min(from.x, to.x) - 36; + return [[x, start[1]], [x, end[1]]]; + } + case 'auto': + default: { + if (start[0] === end[0] || start[1] === end[1]) return []; + const fromVertical = fromSide === 'top' || fromSide === 'bottom'; + const toVertical = toSide === 'top' || toSide === 'bottom'; + if (fromVertical !== toVertical) { + return [fromVertical ? [start[0], end[1]] : [end[0], start[1]]]; + } + if (fromVertical) { + const y = transition.channelY ?? (start[1] + end[1]) / 2; + return [[start[0], y], [end[0], y]]; + } + const x = transition.channelX ?? (start[0] + end[0]) / 2; + return [[x, start[1]], [x, end[1]]]; + } + } +} + +const pathCache = new Map(); + +function transitionSides(transition) { + const from = states.get(transition.from); + const to = states.get(transition.to); + return { + fromSide: chosenSide(transition.fromSide, defaultFromSide(from, to)), + toSide: chosenSide(transition.toSide, defaultToSide(from, to)), + }; +} + +const automaticPorts = automaticPortSpread(lifecycle.transitions, states, { + sideFor: (transition, endpoint) => transitionSides(transition)[endpoint === 'source' ? 'fromSide' : 'toSide'], +}); + +function pathFor(transition) { + if (pathCache.has(transition)) return pathCache.get(transition); + const from = states.get(transition.from); + const to = states.get(transition.to); + const ports = automaticPorts.get(transition); + const { fromSide, toSide } = transitionSides(transition); + const start = ports?.from || anchor(from, fromSide); + const end = ports?.to || anchor(to, toSide); + let via = routeVia(transition, from, to, start, end, fromSide, toSide); + if (ports && !via.length && Math.abs(start[0] - end[0]) >= 4 && Math.abs(start[1] - end[1]) >= 4) { + const midX = (start[0] + end[0]) / 2; + via = [[midX, start[1]], [midX, end[1]]]; + } + const points = [start, ...via, end]; + const routed = { + d: roundedPath(points, transition.cornerRadius ?? 10), + points + }; + pathCache.set(transition, routed); + return routed; +} + +function bandTitles() { + const lanes = asArray(lifecycle.lanes); + const mainLane = lanes.find((lane) => lane.id === 'main'); + const terminalLane = lanes.find((lane) => lane.id === 'terminal'); + const eventLanes = lanes.filter((lane) => lane.id !== 'main' && lane.id !== 'terminal'); + return [ + mainLane?.label || 'Lifecycle phases', + eventLanes.length ? eventLanes.map((lane) => lane.label).join(' + ') : 'Interruptions + recovery', + terminalLane?.label || 'Outcomes' + ]; +} + +function renderBands() { + const right = viewBox[0] - 72; + const titles = bandTitles(); + return ` + 01 / ${esc(titles[0])} + + 02 / ${esc(titles[1])} + + 03 / ${esc(titles[2])}`; +} + +function renderState(state) { + const fill = typeClass[state.type] || typeClass.neutral; + const accent = textClass[state.type] || 't-muted'; + const hasSub = state.sublabel != null && state.sublabel !== ''; + const sub = hasSub + ? `\n ${esc(state.sublabel)}` + : ''; + const tag = state.tag + ? `\n ${esc(state.tag)}` + : ''; + const hasBrand = Boolean(brandMarkFor(state)); + const step = state.step + ? `\n ${esc(state.step)}` + : ''; + const brand = renderBrandMark(state, { x: state.x + state.width - 22, y: state.y + 6 }); + const labelFontSize = fittedNodeFontSize(state.label, brandLabelFitWidth(state, state.width), 10, 8); + const passport = { + kind: state.type, + sublabel: state.sublabel, + tag: state.tag, + context: laneLabels.get(state.lane) || i18nText(lifecycle.meta.locale, 'node.context.lifecycle'), + ...brandMetadataFor(state), + }; + return ` + ${focusNodeTitle(state.label, passport)} + + + ${renderSemanticSigil(state.type, { x: hasBrand ? state.x + 6 : state.x + state.width - 17, y: state.y + 6 })}${brand ? `\n ${brand}` : ''}${step} + ${esc(state.label)}${sub}${tag} + `; +} + +function renderTransitionPath(transition, index) { + const [cls, marker] = arrowClassMap[transition.variant || 'default'] || arrowClassMap.default; + const routed = pathFor(transition); + const strokeWidth = transition.width || (transition.variant === 'emphasis' ? 2 : 1.1); + return ` `; +} + +function renderTransitionLabel(transition, index) { + if (!transition.label) return ''; + const routed = pathFor(transition); + const [lx, ly] = labelPoint(transition, routed.points); + const longestLine = Math.max(textUnits(transition.label), textUnits(transition.note || '')); + const labelW = Math.max(32, longestLine * 4.9 + 12); + const labelH = transition.note ? 27 : 16; + const note = transition.note + ? `\n ${esc(transition.note)}` + : ''; + return ` + + ${esc(transition.label)}${note} + `; +} + +const LEGEND_CATALOG = [ + 'start', + 'active', + 'waiting', + 'decision', + 'success', + 'failure', + 'neutral', + 'external', +].map((kind) => ({ kind, label: i18nText(lifecycle.meta.locale, `legend.lifecycle.${kind}`) })); + +function renderLegend() { + const presentKinds = new Set([...states.values()].map((state) => state.type)); + const entries = resolveLegend(lifecycle.meta?.legend, LEGEND_CATALOG, presentKinds); + return renderResolvedLegend({ + entries, + locale: lifecycle.meta.locale, + layout: { + x: 40, + baselineY: legendY(), + width: viewBox[0] - 80, + minTitleY: lifecycleAreaBottom() + 8, + unfit: lifecycle.meta?.legend === undefined ? 'hide' : 'error', + diagramType: 'lifecycle', + }, + renderSwatch: (entry) => ``, + }); +} + +function renderLifecycleRail() { + const mainCols = [...states.values()] + .filter((state) => bandFor(state.lane) === 'phase') + .map((state) => state.col); + if (!mainCols.length) return ''; + const railEnd = layout.phaseXs[Math.max(...mainCols)] + 38; + return ` `; +} + +function renderSvg() { + return ` +${svgAccessibleText(lifecycle.meta, 'lifecycle')} +${renderDefinitions()} + + + + + +${renderBands()} + + +${renderLifecycleRail()} + + +${asArray(lifecycle.transitions).map(renderTransitionPath).join('\n')} + + +${[...states.values()].map(renderState).join('\n\n')} + + +${asArray(lifecycle.transitions).map(renderTransitionLabel).join('\n')} + + +${renderLegend()} + `; +} + +validateLifecycle(); +writeDiagram({ + outPath, + template, + diagramType: 'lifecycle', + meta: lifecycle.meta, + svg: renderSvg(), + cards: lifecycle.cards, +}); diff --git a/tools/archify/renderers/sequence/README.md b/tools/archify/renderers/sequence/README.md new file mode 100644 index 0000000..82cfa16 --- /dev/null +++ b/tools/archify/renderers/sequence/README.md @@ -0,0 +1,114 @@ +# Sequence Renderer + +Render `diagram_type: "sequence"` JSON files into the standard Archify HTML +template. + +```bash +node archify/renderers/sequence/render-sequence.mjs input.sequence.json output.html +``` + +The renderer validates input against `archify/schemas/sequence.schema.json` +with the bundled standalone validator. No dependency installation is required. + +If `output.html` is omitted, the renderer uses `meta.output` from the JSON file +or falls back to `sequence.html` in the current working directory. + +## Input + +Sequence JSON files must set: + +```json +{ + "schema_version": 1, + "diagram_type": "sequence", + "meta": { + "title": "Cache Miss Request Sequence", + "viewBox": [920, 760] + }, + "participants": [], + "segments": [], + "messages": [], + "activations": [], + "cards": [] +} +``` + +The timeline scales with the viewBox height: a taller `meta.viewBox` buys more +message room, a shorter one shrinks the readable band instead of clipping. A +complete worked example lives at +`archify/examples/cache-miss-request.sequence.json`. + +The schema lives at: + +```text +archify/schemas/sequence.schema.json +``` + +## Legend + +The default visual legend derives kinds from `messages[].variant` (omitting +`variant` means `default`). Supported `meta.legend.entries` keys, in stable +order, are `emphasis`, `return`, `security`, `dashed`, and `default`. These are +visual message keys, not Semantic Lens controls; label/visibility overrides do +not create edge facts. + +## Layout budget + +| Constant | Value | +|----------|-------| +| viewBox | default `[920, 760]`; schema minimum `[480, 480]` | +| Participant boxes | `fixed` (default): 86×54 at y 72; `spread`: viewBox-relative width from 86px up to 190px | +| Participant columns | `fixed`: centers at x = 62 + index×108; `spread`: columns distribute across the available viewBox width | +| Participant count | the last box must end at or before width − 40; layouts that cannot fit fail closed | +| Lifelines | from y 142 down to height − 65; band must be ≥120px tall | +| Message `y` range | `[160, height − 83]` | +| Message spacing | ≥28px vertical between messages that share horizontal space | +| Arrow span | ≥60px horizontal between the two participants | +| Segments | y pixel ranges with `to > from`, inside `[72, lifeline bottom + 20]` | +| Legend row | y = height − 54 | + +`segments[].from/to` and `activations[].from/to` are y pixel coordinates, not +participant ids; activations also require `to > from`. + +### Column fit + +Sequence diagrams use `meta.column_fit: "fixed"` by default so existing +documents keep their historical coordinates. Use `"spread"` when a wide +viewBox would otherwise leave empty space on the right or when meaningful +participant labels do not fit the fixed 86px boxes. Spread derives box width +and column distance from the viewBox while preserving participant order, +lifelines, and message semantics. + +## Design Rules + +- Put participants across the top, ordered by the story the reader should + follow. +- Time moves downward. +- Use `emphasis` for the main request path. +- Use `security` for auth, consent, permission, and policy calls. +- Use `return` for quiet response messages. +- Use `dashed` for async trace, event, logging, and non-blocking work. +- Use segments as light background guides; keep segment labels short. +- Keep labels concise, but try `meta.column_fit: "spread"` before shortening a + meaningful participant label just to fit the fixed boxes. + +Schema violations exit non-zero with path-prefixed messages annotated with the +element's id or label. The renderer additionally fails when it can detect +layout problems, including missing participants, duplicate participant IDs, +participant labels wider than their box, unknown message endpoints, messages +outside the readable timeline, overly tight vertical spacing between messages +that overlap horizontally, invalid segment or activation ranges, or +participants that exceed the viewBox. The shared Clean Flow contract treats +participant headers as semantic boxes while explicitly allowing messages to +cross intermediate lifelines, activation bars, and segment frames. Text width is estimated CJK-aware: +fullwidth glyphs count as two units. + +Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper +message X crossings then fail with `composition/proper-crossing`; default +`standard` keeps them as artifact-receipt warnings. Messages may still cross +intermediate lifelines. Collinear corridors remain outside the proper-X rule, +but a separate gate warns in `standard` and fails in `showcase` when unrelated +messages overlap for at least 8px. Shared semantic endpoints, point touches, +and shorter overlaps remain valid. Showcase also rejects any route segment +below 8px and any interior turn segment below 16px; ordinary 8–15px endpoint +stubs remain valid. diff --git a/tools/archify/renderers/sequence/render-sequence.mjs b/tools/archify/renderers/sequence/render-sequence.mjs new file mode 100644 index 0000000..02d7781 --- /dev/null +++ b/tools/archify/renderers/sequence/render-sequence.mjs @@ -0,0 +1,453 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs'; +import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, loadDiagramWithBrandMarks, writeDiagram, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs'; +import { throwDiagnosticProblems } from '../shared/diagnostics.mjs'; +import { resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs'; +import { componentFill, arrowClassMap, rectsOverlap, cleanFlowProblems, cleanCrossingProblems, cleanAmbiguousCorridorProblems, cleanBorderRunProblems, cleanRouteRhythmProblems, cleanLabelRouteClearanceProblems, routePointsValue, asArray, isFinitePoint } from '../shared/geometry.mjs'; +import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs'; +import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs'; +import { translateMessage as i18nText } from '../shared/i18n.mjs'; + +const participantTextFit = { + sublabelPreferred: 7, + sublabelMinimum: 6, +}; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const { diagram: sequence, template, outPath } = await loadDiagramWithBrandMarks({ + rendererDir: __dirname, + diagramType: 'sequence', + defaultExample: 'cache-miss-request.sequence.json' +}); + +const viewBox = sequence.meta?.viewBox || [920, 760]; +// The timeline scales with viewBox height: a taller viewBox gains message room, +// a shorter one shrinks the readable band (validated below) instead of clipping. +// `column_fit: "spread"` widens the lanes with the viewBox instead of keeping +// the fixed 108px gap, so a wide canvas gains column distance and label room +// rather than dead space on the right. The default stays "fixed" so existing +// diagrams keep their coordinates. +const columnFit = sequence.meta?.column_fit === 'spread' ? 'spread' : 'fixed'; +const participantCount = Math.max(1, asArray(sequence.participants).length); +const sideMargin = 62; +const participantW = columnFit === 'spread' + ? Math.max(86, Math.min(190, Math.round((viewBox[0] - sideMargin * 2) / participantCount) - 24)) + : 86; +const colGap = columnFit === 'spread' && participantCount > 1 + ? Math.max(108, (viewBox[0] - 40 - sideMargin - participantW) / (participantCount - 1)) + : 108; + +const layout = { + topY: 72, + participantW, + participantH: 54, + lifelineTop: 142, + lifelineBottom: viewBox[1] - 65, + legendY: viewBox[1] - 54, + leftX: columnFit === 'spread' ? sideMargin + participantW / 2 : sideMargin, + colGap, + labelH: 16 +}; + +const participantBoxWidthNote = columnFit === 'spread' + ? `participant boxes are ${participantW}px for this viewBox width and ${participantCount} participants` + : `participant boxes are a fixed ${participantW}px unless meta.column_fit is "spread"`; + +const arrowClass = { + ...arrowClassMap, + return: ['a-default', 'arrowhead'] +}; + +function participantX(index) { + return layout.leftX + index * layout.colGap; +} + +const participants = new Map(asArray(sequence.participants).map((participant, index) => [ + participant.id, + { + ...participant, + index, + cx: participantX(index), + x: participantX(index) - layout.participantW / 2, + y: layout.topY, + width: layout.participantW, + height: layout.participantH, + cy: layout.topY + layout.participantH / 2 + } +])); + +function messageGeometry(message) { + const from = participants.get(message.from); + const to = participants.get(message.to); + if (!from || !to || typeof message.y !== 'number') return null; + const direction = to.cx > from.cx ? 1 : -1; + const start = from.cx + direction * 7; + const end = to.cx - direction * 7; + return { start, end, center: (start + end) / 2 }; +} + +function messageLabelBox(message, relationIndex = null) { + const geometry = messageGeometry(message); + if (!geometry) return null; + const width = Math.max(34, textUnits(message.label) * 5.2 + 12); + return { + relation: message, + relationIndex, + label: message.label, + x: geometry.center - width / 2, + y: message.y - 20, + width, + height: layout.labelH, + }; +} + +function messageRouteBox(message) { + const geometry = messageGeometry(message); + if (!geometry) return null; + return { + x: Math.min(geometry.start, geometry.end), + y: message.y - 2, + width: Math.abs(geometry.end - geometry.start), + height: 4, + }; +} + +const compositionFrames = asArray(sequence.segments).map((segment, index) => ({ + id: index, + label: segment.label, + kind: 'segment', + x: 48, + y: segment.from, + width: viewBox[0] - 96, + height: segment.to - segment.from, + radius: 10, +})); + +function messagePath(message) { + return { + points: participants.has(message.from) && participants.has(message.to) + ? [[participants.get(message.from).cx, message.y], [participants.get(message.to).cx, message.y]] + : [] + }; +} + +function validateSequence() { + const problems = []; + if (participants.size !== asArray(sequence.participants).length) problems.push('Participant ids must be unique.'); + + if (layout.lifelineBottom - layout.lifelineTop < 120) { + problems.push(`viewBox height ${viewBox[1]} leaves under 120px of timeline — set meta.viewBox[1] to at least ${layout.lifelineTop + 120 + 65}.`); + } + + for (const participant of participants.values()) { + const estLabelW = textUnits(participant.label) * 6.8; + if (estLabelW > layout.participantW + 6) { + problems.push(`Label "${participant.label}" (~${Math.round(estLabelW)}px) is wider than the ${layout.participantW}px participant box — shorten it.`); + } + const brandRailProblem = brandTopRailProblem(participant, layout.participantW, 8, 'Participant'); + if (brandRailProblem) problems.push(brandRailProblem); + // sublabel renders as a single unwrapped ; shrink-to-fit handles the + // ordinary case, this rejects what it cannot rescue. + if (participant.sublabel) { + const availableTextW = availableNodeTextWidth(layout.participantW); + const minimumW = minimumNodeTextWidth(participant.sublabel, participantTextFit.sublabelMinimum); + if (minimumW > availableTextW) { + problems.push(`Sublabel "${participant.sublabel}" needs ~${Math.ceil(minimumW)}px at the ${participantTextFit.sublabelMinimum}px legible minimum, but participant "${participant.id}" provides ${availableTextW}px — shorten the sublabel (${participantBoxWidthNote}).`); + } + } + } + + for (const message of asArray(sequence.messages)) { + if (!participants.has(message.from)) problems.push(`Message "${message.label}" references unknown source "${message.from}".`); + if (!participants.has(message.to)) problems.push(`Message "${message.label}" references unknown target "${message.to}".`); + if (typeof message.y !== 'number') problems.push(`Message "${message.label}" must provide a numeric y.`); + if (message.y < layout.lifelineTop + 18 || message.y > layout.lifelineBottom - 18) { + problems.push(`Message "${message.label}" sits outside the readable timeline — keep y between ${layout.lifelineTop + 18} and ${layout.lifelineBottom - 18}.`); + } + if (participants.has(message.from) && participants.has(message.to)) { + const distance = Math.abs(participants.get(message.to).cx - participants.get(message.from).cx); + if (distance < 60) problems.push(`Message "${message.label}" spans ${Math.round(distance)}px (minimum 60px) — give its participants more column distance.`); + } + } + + // Participant headers are opaque nodes. Lifelines, activation bars, and + // segment bands remain intentional pass-through geometry and are excluded. + problems.push(...cleanFlowProblems({ + relations: sequence.messages, + obstacles: participants.values(), + pathFor: messagePath, + diagramType: 'sequence', + relationCollection: 'messages', + obstacleKind: 'participant header', + clearance: 0, + routeHint: 'move the message y below the participant headers or reorder participants' + })); + problems.push(...cleanCrossingProblems({ + relations: sequence.messages, + endpointIds: new Set(participants.keys()), + pathFor: messagePath, + diagramType: 'sequence', + relationCollection: 'messages', + profile: sequence.meta?.quality_profile, + routeHint: 'separate the message y values; lifeline crossings remain allowed' + })); + problems.push(...cleanAmbiguousCorridorProblems({ + relations: sequence.messages, + endpointIds: new Set(participants.keys()), + pathFor: messagePath, + diagramType: 'sequence', + relationCollection: 'messages', + profile: sequence.meta?.quality_profile, + routeHint: 'separate the message y values so unrelated messages do not visually merge' + })); + problems.push(...cleanBorderRunProblems({ + relations: sequence.messages, + endpointIds: new Set(participants.keys()), + frames: compositionFrames, + pathFor: messagePath, + diagramType: 'sequence', + relationCollection: 'messages', + profile: sequence.meta?.quality_profile, + routeHint: 'move the message y so it crosses a segment boundary perpendicularly or stays clearly inside the segment' + })); + problems.push(...cleanRouteRhythmProblems({ + relations: sequence.messages, + endpointIds: new Set(participants.keys()), + pathFor: messagePath, + diagramType: 'sequence', + relationCollection: 'messages', + profile: sequence.meta?.quality_profile, + routeHint: 'increase participant spacing or simplify message routing so every turn has room to read' + })); + + // Vertical crowding only matters when the arrows share horizontal space; + // disjoint arrows may legitimately run in parallel rows. + const placed = asArray(sequence.messages) + .filter((m) => participants.has(m.from) && participants.has(m.to)) + .map((m) => ({ + label: m.label, + y: m.y, + x1: Math.min(participants.get(m.from).cx, participants.get(m.to).cx), + x2: Math.max(participants.get(m.from).cx, participants.get(m.to).cx) + })) + .sort((a, b) => a.y - b.y); + for (let i = 0; i < placed.length; i += 1) { + for (let j = i + 1; j < placed.length && placed[j].y - placed[i].y < 28; j += 1) { + if (placed[i].x1 < placed[j].x2 && placed[j].x1 < placed[i].x2) { + problems.push(`Messages "${placed[i].label}" and "${placed[j].label}" are less than 28px apart and share horizontal space — spread their y values.`); + } + } + } + + // Label masks can extend well past the arrow span, so check the actual + // label rectangles too — tangent arrows with long labels still collide. + const labelRects = asArray(sequence.messages) + .map((m, messageIndex) => messageLabelBox(m, messageIndex)) + .filter(Boolean); + for (let i = 0; i < labelRects.length; i += 1) { + for (let j = i + 1; j < labelRects.length; j += 1) { + if (rectsOverlap(labelRects[i], labelRects[j], -2)) { + problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — spread their message y values or shorten the labels.`); + } + } + } + problems.push(...cleanLabelRouteClearanceProblems({ + relations: sequence.messages, + labels: labelRects, + endpointIds: new Set(participants.keys()), + pathFor: messagePath, + diagramType: 'sequence', + relationCollection: 'messages', + profile: sequence.meta?.quality_profile, + routeHint: 'spread the message y values, shorten the label, or reorder participants so the adjacent route stays visible' + })); + + for (const segment of asArray(sequence.segments)) { + if (segment.to <= segment.from) { + problems.push(`Segment "${segment.label}" has invalid y range (from ${segment.from} to ${segment.to}) — "to" must be greater than "from".`); + } + if (segment.from < layout.topY || segment.to > layout.lifelineBottom + 20) { + problems.push(`Segment "${segment.label}" extends outside the canvas — keep its y range between ${layout.topY} and ${layout.lifelineBottom + 20}.`); + } + } + + for (const activation of asArray(sequence.activations)) { + if (!participants.has(activation.participant)) problems.push(`Activation references unknown participant "${activation.participant}".`); + if (activation.to <= activation.from) problems.push(`Activation for "${activation.participant}" has invalid time range — "to" must be greater than "from".`); + } + + const lastParticipant = asArray(sequence.participants)[asArray(sequence.participants).length - 1]; + if (lastParticipant && participants.get(lastParticipant.id).cx + layout.participantW / 2 > viewBox[0] - 40) { + const requiredWidth = Math.ceil(participants.get(lastParticipant.id).cx + layout.participantW / 2 + 40); + problems.push(`Participants exceed viewBox width — set meta.viewBox[0] to at least ${requiredWidth} or remove a participant.`); + } + + if (problems.length) { + throwDiagnosticProblems('Sequence layout validation failed', problems, { + subject: { diagramType: 'sequence' }, + }); + } +} + +function renderParticipant(participant) { + const fill = componentFill[participant.type] || 'c-external'; + const hasSub = participant.sublabel != null && participant.sublabel !== ''; + const sub = hasSub + ? `\n ${esc(participant.sublabel)}` + : ''; + const brand = renderBrandMark(participant, { x: participant.x + layout.participantW - 22, y: layout.topY + 6 }); + const labelFontSize = fittedNodeFontSize(participant.label, brandLabelFitWidth(participant, layout.participantW), 11, 8); + const passport = { + kind: participant.type, + sublabel: participant.sublabel, + context: i18nText(sequence.meta.locale, 'node.context.sequence'), + ...brandMetadataFor(participant), + }; + return ` + ${focusNodeTitle(participant.label, passport)} + + + ${renderSemanticSigil(participant.type, { x: participant.x + 6, y: layout.topY + 6 })}${brand ? `\n ${brand}` : ''} + ${esc(participant.label)}${sub} + `; +} + +function renderLifeline(participant) { + return ` `; +} + +function renderSegment(segment, index) { + return ` `; +} + +function renderSegmentLabel(segment, index) { + const labelW = Math.max(42, textUnits(segment.label) * 5.2 + 14); + const occupied = asArray(sequence.messages) + .flatMap((message) => [messageLabelBox(message), messageRouteBox(message)]) + .filter(Boolean); + const label = { x: 56, y: segment.from - 22, width: labelW, height: 18 }; + for (let attempt = 0; attempt < 4; attempt += 1) { + if (!occupied.some((rect) => rectsOverlap(label, rect, 2))) break; + label.y -= 22; + } + return ` + + ${esc(segment.label)} + `; +} + +function renderActivation(activation) { + const participant = participants.get(activation.participant); + const fill = componentFill[activation.type] || componentFill[participant.type] || 'c-external'; + const x = participant.cx - 5; + const height = activation.to - activation.from; + return ` + `; +} + +function messageLabel(message, x1, x2) { + const box = messageLabelBox(message); + const center = box ? box.x + box.width / 2 : (x1 + x2) / 2; + const y = message.y - 10; + const labelW = box?.width || Math.max(34, textUnits(message.label) * 5.2 + 12); + const accent = message.variant === 'security' + ? 't-security' + : message.variant === 'dashed' + ? 't-messagebus' + : message.variant === 'return' + ? 't-muted' + : 't-backend'; + return ` + + ${esc(message.label)} + `; +} + +function renderMessage(message, index) { + const { start, end } = messageGeometry(message); + const [cls, marker] = arrowClass[message.variant || 'default'] || arrowClass.default; + const strokeWidth = message.variant === 'emphasis' ? 1.8 : 1.4; + const dash = message.variant === 'return' ? ' stroke-dasharray="3,5"' : ''; + const note = message.note + ? `\n ${esc(message.note)}` + : ''; + return ` + +${messageLabel(message, start, end)}${note} + `; +} + +const LEGEND_CATALOG = [ + { kind: 'emphasis', className: 'a-emphasis', marker: 'arrowhead-emphasis', strokeWidth: 1.8 }, + { kind: 'return', className: 'a-default', marker: 'arrowhead', dash: '3,5' }, + { kind: 'security', className: 'a-security', marker: 'arrowhead-security' }, + { kind: 'dashed', className: 'a-dashed', marker: 'arrowhead-dashed' }, + { kind: 'default', className: 'a-default', marker: 'arrowhead' }, +].map((entry) => ({ + ...entry, + interactive: false, + swatchWidth: 34, + swatchGap: 9, + label: i18nText(sequence.meta.locale, `legend.sequence.${entry.kind}`), +})); + +function renderLegend() { + const presentKinds = new Set(asArray(sequence.messages).map((message) => message.variant || 'default')); + const entries = resolveLegend(sequence.meta?.legend, LEGEND_CATALOG, presentKinds); + return renderResolvedLegend({ + entries, + locale: sequence.meta.locale, + layout: { + x: 40, + baselineY: layout.legendY, + width: viewBox[0] - 80, + minTitleY: layout.legendY - 30, + unfit: sequence.meta?.legend === undefined ? 'hide' : 'error', + diagramType: 'sequence', + }, + renderSwatch: (entry) => ``, + }); +} + +function renderSvg() { + const participantList = [...participants.values()]; + return ` +${svgAccessibleText(sequence.meta, 'sequence')} +${renderDefinitions()} + + + + + +${asArray(sequence.segments).map(renderSegment).join('\n\n')} + + +${participantList.map(renderLifeline).join('\n')} + + +${asArray(sequence.activations).map(renderActivation).join('\n')} + + +${asArray(sequence.messages).map(renderMessage).join('\n\n')} + + +${asArray(sequence.segments).map(renderSegmentLabel).join('\n')} + + +${participantList.map(renderParticipant).join('\n\n')} + + +${renderLegend()} + `; +} + +validateSequence(); +writeDiagram({ + outPath, + template, + diagramType: 'sequence', + meta: sequence.meta, + svg: renderSvg(), + cards: sequence.cards, +}); diff --git a/tools/archify/renderers/shared/brand-marks.mjs b/tools/archify/renderers/shared/brand-marks.mjs new file mode 100644 index 0000000..1e18e0b --- /dev/null +++ b/tools/archify/renderers/shared/brand-marks.mjs @@ -0,0 +1,563 @@ +import { createHash } from 'node:crypto'; +import { lookup } from 'node:dns/promises'; +import http from 'node:http'; +import https from 'node:https'; +import net from 'node:net'; +import { BRAND_MARKS } from './generated-brand-marks.mjs'; +import { throwDiagnosticError } from './diagnostics.mjs'; +import { esc, textUnits } from './utils.mjs'; + +const COLLECTIONS = Object.freeze({ + architecture: 'components', + workflow: 'nodes', + sequence: 'participants', + dataflow: 'nodes', + lifecycle: 'states', +}); +const MARK_BY_LOOKUP = new Map(); +const MARK_BY_DOMAIN = new Map(); +const RESOLVED_BY_NODE = new WeakMap(); +const RESOLVED_MARK = Symbol('archify.brandMark'); +const MAX_HTML_BYTES = 256 * 1024; +const MAX_IMAGE_BYTES = 1024 * 1024; +const MAX_CAPTURE_CONCURRENCY = 3; +const DEFAULT_CAPTURE_TIMEOUT_MS = 8000; +const USER_AGENT = 'Archify/2.15 brand-preview'; + +function lookupForms(value) { + const raw = String(value ?? '').trim().toLocaleLowerCase('en-US'); + if (!raw) return []; + const dashed = raw.replace(/[\s_]+/g, '-'); + const compact = raw.replace(/[\s_.-]+/g, ''); + return [...new Set([raw, dashed, compact])]; +} + +for (const mark of BRAND_MARKS) { + for (const value of [mark.id, mark.title, ...mark.aliases]) { + for (const form of lookupForms(value)) { + if (!MARK_BY_LOOKUP.has(form)) MARK_BY_LOOKUP.set(form, mark); + } + } + for (const domain of mark.domains) MARK_BY_DOMAIN.set(domain, mark); +} + +function asUrl(value) { + try { + const url = new URL(String(value)); + return ['https:', 'http:'].includes(url.protocol) ? url : null; + } catch { + return null; + } +} + +function domainMark(hostname) { + const host = hostname.toLocaleLowerCase('en-US').replace(/\.$/, ''); + const candidates = [...MARK_BY_DOMAIN.entries()] + .filter(([domain]) => host === domain || host.endsWith(`.${domain}`)) + .sort(([left], [right]) => right.length - left.length); + return candidates[0]?.[1] || null; +} + +export function findBrandMark(value) { + const url = asUrl(value); + if (url) return domainMark(url.hostname); + for (const form of lookupForms(value)) { + const mark = MARK_BY_LOOKUP.get(form); + if (mark) return mark; + } + return null; +} + +export function listBrandMarks(query = '') { + const needle = String(query).trim().toLocaleLowerCase('en-US'); + return BRAND_MARKS.filter((mark) => { + if (!needle) return true; + return [mark.id, mark.title, mark.category, ...mark.aliases, ...mark.domains] + .some((value) => String(value).toLocaleLowerCase('en-US').includes(needle)); + }).map(({ path, ...mark }) => mark); +} + +function ipv4Private(address) { + const parts = address.split('.').map(Number); + if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true; + const [a, b, c] = parts; + return a === 0 || a === 10 || a === 127 || a >= 224 + || (a === 100 && b >= 64 && b <= 127) + || (a === 169 && b === 254) + || (a === 172 && b >= 16 && b <= 31) + || (a === 192 && b === 0 && (c === 0 || c === 2)) + || (a === 192 && b === 88 && c === 99) + || (a === 192 && b === 168) + || (a === 198 && (b === 18 || b === 19)) + || (a === 198 && b === 51 && c === 100) + || (a === 203 && b === 0 && c === 113); +} + +function ipv6Private(address) { + const normalized = address.toLocaleLowerCase('en-US').split('%')[0]; + if (normalized === '::' || normalized === '::1') return true; + if (normalized.startsWith('fc') || normalized.startsWith('fd') || normalized.startsWith('ff') || /^fe[89ab]/.test(normalized)) return true; + if (normalized.startsWith('64:ff9b:') || normalized.startsWith('100:') + || normalized.startsWith('2001:db8:') || normalized.startsWith('2002:')) return true; + const mappedDotted = normalized.match(/::ffff:(\d+\.\d+\.\d+\.\d+)$/); + if (mappedDotted) return ipv4Private(mappedDotted[1]); + const mappedHex = normalized.match(/::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/); + if (mappedHex) { + const high = Number.parseInt(mappedHex[1], 16); + const low = Number.parseInt(mappedHex[2], 16); + return ipv4Private(`${high >>> 8}.${high & 255}.${low >>> 8}.${low & 255}`); + } + const compatibleHex = normalized.match(/^::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/); + if (compatibleHex) { + const high = Number.parseInt(compatibleHex[1], 16); + const low = Number.parseInt(compatibleHex[2], 16); + return ipv4Private(`${high >>> 8}.${high & 255}.${low >>> 8}.${low & 255}`); + } + return false; +} + +export function isPrivateBrandAddress(address) { + const family = net.isIP(address); + return family === 4 ? ipv4Private(address) : (family === 6 ? ipv6Private(address) : true); +} + +function validateUrlShape(url, allowPrivate = process.env.ARCHIFY_BRAND_ALLOW_PRIVATE === '1') { + if (!['https:', 'http:'].includes(url.protocol)) throw new Error('only HTTP(S) brand links are supported'); + if (url.username || url.password) throw new Error('brand links cannot contain credentials'); + const expectedPort = url.protocol === 'https:' ? '443' : '80'; + if (!allowPrivate && url.port && url.port !== expectedPort) { + throw new Error('brand links must use a standard web port'); + } + const host = url.hostname.toLocaleLowerCase('en-US').replace(/\.$/, '').replace(/^\[|\]$/g, ''); + if (!allowPrivate && (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.local'))) { + throw new Error('private brand links are not fetched'); + } + return host; +} + +function beforeDeadline(promise, deadline) { + const remaining = deadline - Date.now(); + if (remaining <= 0) return Promise.reject(new Error('brand capture timed out')); + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error('brand capture timed out')), remaining); + timer.unref?.(); + promise.then( + (value) => { clearTimeout(timer); resolve(value); }, + (error) => { clearTimeout(timer); reject(error); }, + ); + }); +} + +async function resolveRequestTarget(url, deadline) { + const allowPrivate = process.env.ARCHIFY_BRAND_ALLOW_PRIVATE === '1'; + const host = validateUrlShape(url, allowPrivate); + const directFamily = net.isIP(host); + const addresses = directFamily + ? [{ address: host, family: directFamily }] + : await beforeDeadline(lookup(host, { all: true, verbatim: true }), deadline); + if (!addresses.length || (!allowPrivate && addresses.some(({ address }) => isPrivateBrandAddress(address)))) { + throw new Error('private brand links are not fetched'); + } + return addresses[0]; +} + +function timeoutSignal(milliseconds) { + if (typeof AbortSignal.timeout === 'function') return AbortSignal.timeout(milliseconds); + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), milliseconds); + timer.unref?.(); + return controller.signal; +} + +function captureTimeoutMilliseconds() { + const configured = Number(process.env.ARCHIFY_BRAND_CAPTURE_TIMEOUT_MS); + if (!Number.isFinite(configured)) return DEFAULT_CAPTURE_TIMEOUT_MS; + return Math.max(100, Math.min(30000, Math.round(configured))); +} + +function requestPinned(url, accept, target, deadline) { + return new Promise((resolve, reject) => { + const transport = url.protocol === 'https:' ? https : http; + const request = transport.request(url, { + method: 'GET', + signal: timeoutSignal(Math.max(1, Math.min(4500, deadline - Date.now()))), + headers: { accept, 'user-agent': USER_AGENT }, + // Reuse the exact public address that passed validation. This closes the + // DNS-rebinding gap between checking a hostname and opening its socket. + lookup(_hostname, options, callback) { + if (options?.all) callback(null, [target]); + else callback(null, target.address, target.family); + }, + }, (response) => { + const status = response.statusCode || 0; + resolve({ + status, + ok: status >= 200 && status < 300, + headers: { + get(name) { + const value = response.headers[String(name).toLocaleLowerCase('en-US')]; + return Array.isArray(value) ? value.join(', ') : (value ?? null); + }, + }, + body: response, + }); + }); + request.on('error', reject); + request.end(); + }); +} + +async function checkedFetch(input, accept, deadline) { + let current = new URL(input); + for (let redirects = 0; redirects <= 3; redirects += 1) { + if (Date.now() >= deadline) throw new Error('brand capture timed out'); + const target = await resolveRequestTarget(current, deadline); + const response = await requestPinned(current, accept, target, deadline); + if ([301, 302, 303, 307, 308].includes(response.status)) { + const location = response.headers.get('location'); + response.body.resume(); + if (!location || redirects === 3) throw new Error('brand link redirected too many times'); + current = new URL(location, current); + continue; + } + if (!response.ok) { + response.body.resume(); + throw new Error(`brand link returned HTTP ${response.status}`); + } + return { response, finalUrl: current }; + } + throw new Error('brand link redirected too many times'); +} + +async function readLimited(response, maximum) { + const declared = Number(response.headers.get('content-length')); + if (Number.isFinite(declared) && declared > maximum) { + response.body?.destroy?.(); + throw new Error('brand asset is too large'); + } + if (response.body && typeof response.body[Symbol.asyncIterator] === 'function') { + const chunks = []; + let total = 0; + for await (const value of response.body) { + total += value.byteLength; + if (total > maximum) { + response.body.destroy?.(); + throw new Error('brand asset is too large'); + } + chunks.push(Buffer.from(value)); + } + return Buffer.concat(chunks, total); + } + if (!response.body?.getReader) { + const buffer = Buffer.from(await response.arrayBuffer()); + if (buffer.length > maximum) throw new Error('brand asset is too large'); + return buffer; + } + const reader = response.body.getReader(); + const chunks = []; + let total = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + total += value.byteLength; + if (total > maximum) { + await reader.cancel(); + throw new Error('brand asset is too large'); + } + chunks.push(Buffer.from(value)); + } + return Buffer.concat(chunks, total); +} + +function attribute(tag, name) { + const match = tag.match(new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, 'i')); + return match ? (match[1] ?? match[2] ?? match[3] ?? '') : ''; +} + +function iconCandidates(html, pageUrl) { + const candidates = []; + for (const match of html.matchAll(/]*>/gi)) { + const tag = match[0]; + const rel = attribute(tag, 'rel').toLocaleLowerCase('en-US').split(/\s+/); + if (!rel.some((value) => value === 'icon' || value === 'apple-touch-icon' || value === 'mask-icon')) continue; + const href = attribute(tag, 'href'); + if (!href) continue; + try { + const url = new URL(href, pageUrl); + if (!['https:', 'http:'].includes(url.protocol)) continue; + const type = attribute(tag, 'type').toLocaleLowerCase('en-US'); + const sizes = attribute(tag, 'sizes'); + const area = [...sizes.matchAll(/(\d+)x(\d+)/gi)] + .reduce((best, size) => Math.max(best, Number(size[1]) * Number(size[2])), 0); + const score = (type.includes('svg') || /\.svg(?:$|[?#])/i.test(url.href) ? 1000000 : 0) + + (rel.includes('apple-touch-icon') ? 500000 : 0) + + area; + candidates.push({ url, score }); + } catch { + // A malformed icon candidate is ignored; the deterministic fallback remains available. + } + } + candidates.sort((left, right) => right.score - left.score); + const fallback = new URL('/favicon.ico', pageUrl); + const unique = new Map(candidates.map((candidate) => [candidate.url.href, candidate])); + unique.delete(fallback.href); + return [...unique.values()].slice(0, 5).concat({ url: fallback, score: -1 }); +} + +async function imageData(response) { + const contentType = (response.headers.get('content-type') || '').split(';')[0].trim().toLocaleLowerCase('en-US'); + const allowed = new Set([ + 'image/png', + 'image/jpeg', + 'image/webp', + 'image/x-icon', + 'image/vnd.microsoft.icon', + ]); + if (!allowed.has(contentType)) { + response.body?.destroy?.(); + throw new Error(`unsupported brand image type ${contentType || 'unknown'}`); + } + const buffer = await readLimited(response, MAX_IMAGE_BYTES); + const signatureMatches = contentType === 'image/png' + ? buffer.length >= 45 + && buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) + && buffer.readUInt32BE(8) === 13 + && buffer.toString('ascii', 12, 16) === 'IHDR' + && buffer.readUInt32BE(16) > 0 + && buffer.readUInt32BE(20) > 0 + && buffer.toString('ascii', buffer.length - 8, buffer.length - 4) === 'IEND' + : (contentType === 'image/jpeg' + ? buffer.length >= 20 + && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff + && buffer.at(-2) === 0xff && buffer.at(-1) === 0xd9 + : (contentType === 'image/webp' + ? buffer.length >= 16 + && buffer.toString('ascii', 0, 4) === 'RIFF' + && buffer.toString('ascii', 8, 12) === 'WEBP' + && buffer.readUInt32LE(4) + 8 <= buffer.length + : buffer.length >= 22 + && buffer[0] === 0 && buffer[1] === 0 && buffer[2] === 1 && buffer[3] === 0 + && buffer.readUInt16LE(4) > 0 + && 6 + buffer.readUInt16LE(4) * 16 <= buffer.length)); + if (!signatureMatches) throw new Error(`brand asset bytes do not match ${contentType}`); + return { + dataUrl: `data:${contentType};base64,${buffer.toString('base64')}`, + sha256: createHash('sha256').update(buffer).digest('hex'), + contentType, + }; +} + +async function captureRemoteBrand(value, deadline = Date.now() + captureTimeoutMilliseconds()) { + const sourceUrl = new URL(value); + const fallback = (reason) => ({ + id: sourceUrl.hostname, + title: sourceUrl.hostname, + category: 'link', + kind: 'fallback', + status: 'unavailable', + sourceUrl: sourceUrl.href, + reason, + }); + try { + const page = await checkedFetch(sourceUrl, 'text/html,application/xhtml+xml,image/*;q=0.8', deadline); + const pageType = (page.response.headers.get('content-type') || '').toLocaleLowerCase('en-US'); + if (pageType.startsWith('image/')) { + const image = await imageData(page.response); + return { + id: sourceUrl.hostname, + title: sourceUrl.hostname, + category: 'link', + kind: 'remote', + status: 'captured', + sourceUrl: sourceUrl.href, + resolvedUrl: page.finalUrl.href, + ...image, + }; + } + if (!pageType.includes('text/html') && !pageType.includes('application/xhtml+xml')) { + page.response.body?.destroy?.(); + return fallback('linked page is not HTML'); + } + const html = (await readLimited(page.response, MAX_HTML_BYTES)).toString('utf8'); + const iconErrors = []; + for (const candidate of iconCandidates(html, page.finalUrl)) { + try { + const fetched = await checkedFetch(candidate.url, 'image/*', deadline); + const image = await imageData(fetched.response); + return { + id: sourceUrl.hostname, + title: sourceUrl.hostname, + category: 'link', + kind: 'remote', + status: 'captured', + sourceUrl: sourceUrl.href, + resolvedUrl: fetched.finalUrl.href, + ...image, + }; + } catch (error) { + iconErrors.push(error); + // Try the next declared favicon before using the generic link mark. + } + } + const usefulError = iconErrors.find((error) => /unsupported brand image type/i.test(error?.message)) + || iconErrors.at(-1); + return fallback(usefulError?.message || 'no usable site icon was found'); + } catch (error) { + return fallback(error.message); + } +} + +export async function captureBrandReference(value) { + const url = asUrl(value); + if (!url) throw new Error('brand capture requires one HTTP(S) URL'); + validateUrlShape(url); + const preset = findBrandMark(url.href); + if (preset) return { brand: preset.id, resolved: { ...preset, kind: 'preset', status: 'preset' } }; + const resolved = await captureRemoteBrand(url.href); + if (resolved.status !== 'captured' || !resolved.sha256) { + throw new Error(`brand capture failed: ${resolved.reason || 'no usable site icon was found'}`); + } + return { + brand: { url: url.href, sha256: resolved.sha256 }, + resolved, + }; +} + +function remoteBrand(value, cache, deadline) { + const key = new URL(value).href; + if (!cache.has(key)) cache.set(key, captureRemoteBrand(key, deadline)); + return cache.get(key); +} + +function suggestions(value) { + const needle = lookupForms(value)[0] || ''; + return BRAND_MARKS.map((mark) => ({ + id: mark.id, + score: lookupForms(mark.id).some((form) => form.includes(needle) || needle.includes(form)) ? 0 : 1, + })).sort((left, right) => left.score - right.score || left.id.localeCompare(right.id)) + .slice(0, 5) + .map((entry) => entry.id); +} + +async function mapConcurrent(values, limit, visit) { + let cursor = 0; + const workers = Array.from({ length: Math.min(limit, values.length) }, async () => { + while (cursor < values.length) { + const index = cursor; + cursor += 1; + await visit(values[index], index); + } + }); + await Promise.all(workers); +} + +export async function prepareDiagramBrandMarks(diagramType, diagram) { + const collection = COLLECTIONS[diagramType]; + const nodes = collection && Array.isArray(diagram[collection]) ? diagram[collection] : []; + const unknown = []; + const remoteByUrl = new Map(); + const deadline = Date.now() + captureTimeoutMilliseconds(); + await mapConcurrent(nodes, MAX_CAPTURE_CONCURRENCY, async (node, index) => { + if (!node.brand) return; + if (typeof node.brand === 'object') { + const url = asUrl(node.brand.url); + const resolved = url ? await remoteBrand(url.href, remoteByUrl, deadline) : null; + if (!resolved || resolved.status !== 'captured') { + unknown.push(`/${collection}/${index}/brand could not reproduce the pinned capture: ${resolved?.reason || 'invalid URL'}`); + return; + } + if (resolved.sha256 !== node.brand.sha256) { + unknown.push(`/${collection}/${index}/brand digest changed: expected ${node.brand.sha256}, received ${resolved.sha256}`); + return; + } + node[RESOLVED_MARK] = resolved; + RESOLVED_BY_NODE.set(node, resolved); + return; + } + const preset = findBrandMark(node.brand); + if (preset) { + const resolved = { ...preset, kind: 'preset', status: 'preset', sourceUrl: preset.provenance.source }; + node[RESOLVED_MARK] = resolved; + RESOLVED_BY_NODE.set(node, resolved); + return; + } + const url = asUrl(node.brand); + if (url) { + unknown.push(`/${collection}/${index}/brand ${JSON.stringify(node.brand)} is an unpinned URL; capture it first with \`archify brands capture ${url.href} --json\``); + return; + } + unknown.push(`/${collection}/${index}/brand ${JSON.stringify(node.brand)} is not a built-in brand; closest IDs: ${suggestions(node.brand).join(', ')}`); + }); + if (unknown.length) { + throwDiagnosticError(`Brand mark validation failed:\n- ${unknown.join('\n- ')}`, unknown.map((message) => ({ + code: message.includes('is an unpinned URL') ? 'brand/unpinned-url' + : (message.includes('digest changed') ? 'brand/digest-mismatch' + : (message.includes('could not reproduce') ? 'brand/capture-unavailable' : 'brand/unknown')), + severity: 'error', + message, + subject: { diagramType, collection }, + evidence: {}, + supportedFixes: message.includes('is an unpinned URL') + ? ['run `archify brands capture --json` and author the returned digest-pinned brand object'] + : ['choose an ID from `archify brands`', 'run `archify brands capture --json` for an unknown official site'], + }))); + } +} + +export function brandMarkFor(node) { + return node?.[RESOLVED_MARK] || RESOLVED_BY_NODE.get(node) || null; +} + +export function brandMetadataFor(node) { + const mark = brandMarkFor(node); + return mark ? { + brand: mark.title, + brandId: mark.id, + brandStatus: mark.status, + brandSource: mark.sourceUrl, + } : {}; +} + +export function brandLabelFitWidth(node, width) { + return brandMarkFor(node) ? Math.max(1, width - 48) : width; +} + +export function brandTopRailProblem(node, width, minimumFontSize, subject = 'Node') { + if (!brandMarkFor(node)) return null; + const available = width - 48; + const required = textUnits(node.label) * minimumFontSize * 0.6; + if (available >= required) return null; + return `${subject} "${node.id}" brand top rail leaves ${Math.max(0, available)}px for its label, but ` + + `"${node.label}" needs ~${Math.ceil(required)}px at the ${minimumFontSize}px legible minimum — widen the node or shorten the label.`; +} + +function markAttrs(mark) { + return [ + `data-brand-mark="${esc(mark.id)}"`, + `data-brand-title="${esc(mark.title)}"`, + `data-brand-status="${esc(mark.status)}"`, + mark.sourceUrl ? `data-brand-source="${esc(mark.sourceUrl)}"` : '', + mark.sha256 ? `data-brand-sha256="${esc(mark.sha256)}"` : '', + ].filter(Boolean).join(' '); +} + +export function renderBrandMark(node, { x, y, size = 16 } = {}) { + const mark = brandMarkFor(node); + if (!mark) return ''; + const inset = 3; + let content; + if (mark.kind === 'preset') { + const scale = (size - inset * 2) / mark.viewBox; + content = ``; + } else if (mark.kind === 'remote') { + content = ``; + } else { + const scale = size / 20; + content = ``; + } + return ``; +} diff --git a/tools/archify/renderers/shared/cli.mjs b/tools/archify/renderers/shared/cli.mjs new file mode 100644 index 0000000..32f6672 --- /dev/null +++ b/tools/archify/renderers/shared/cli.mjs @@ -0,0 +1,218 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { applyTemplate, renderCards, esc } from './utils.mjs'; +import { validateSchema } from './validator.mjs'; +import { verifyRepositoryEvidence } from './repository-evidence.mjs'; +import { installRendererDiagnosticBoundary, throwDiagnosticProblems } from './diagnostics.mjs'; +import { validateEngineeringProfile } from './engineering-profiles.mjs'; +import { resolveOutputPath } from './output-path.mjs'; +import { prepareDiagramBrandMarks } from './brand-marks.mjs'; +import { resolveLocale, translateMessage } from './i18n.mjs'; + +installRendererDiagnosticBoundary(); + +const outputPathGuards = new Map(); + +// Common CLI head: node render-.mjs [input.json] [output.html] +// Keep this synchronous because callers also use it to establish the guarded +// output path before testing a last-moment filesystem alias change. +export function loadDiagram({ rendererDir, diagramType, defaultExample, argv = process.argv }) { + const skillRoot = path.resolve(rendererDir, '../..'); + const inputPath = path.resolve(argv[2] || path.join(skillRoot, 'examples', defaultExample)); + const diagram = JSON.parse(fs.readFileSync(inputPath, 'utf8')); + validateSchema(diagramType, diagram); + validateGuidedViews(diagramType, diagram); + validateRelationshipIds(diagramType, diagram); + validateEngineeringProfile(diagramType, diagram); + const sourceEvidence = verifyRepositoryEvidence(diagramType, diagram, process.env.ARCHIFY_REPO_ROOT); + const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8'); + const outputRequest = { + requestedOutput: argv[3], + authoredOutput: diagram.meta?.output, + defaultOutput: `${diagramType}.html`, + inputPaths: [inputPath], + cwd: process.cwd(), + }; + const { outputPath: outPath } = resolveOutputPath(outputRequest); + outputPathGuards.set(outPath, outputRequest); + return { diagram, template, outPath, sourceEvidence }; +} + +// Brand URL capture is the only asynchronous authoring step. Typed renderers +// opt into it through this wrapper without changing loadDiagram's long-lived +// synchronous safety contract. +export async function loadDiagramWithBrandMarks(options) { + const loaded = loadDiagram(options); + await prepareDiagramBrandMarks(options.diagramType, loaded.diagram); + return loaded; +} + +const START_TYPES = new Set(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']); + +// Common CLI tail: fill the template and write the standalone HTML file. +export function writeDiagram({ outPath, template, diagramType, meta, svg, cards, sourceEvidence = null }) { + if (!START_TYPES.has(diagramType)) throw new Error(`writeDiagram: unknown diagram type ${JSON.stringify(diagramType)}`); + const outputGuard = outputPathGuards.get(outPath); + if (outputGuard) resolveOutputPath(outputGuard); + fs.mkdirSync(path.dirname(outPath), { recursive: true }); + fs.writeFileSync(outPath, applyTemplate(template, { + title: meta.title, + subtitle: meta.subtitle, + svg, + cards: renderCards(cards), + locale: meta.locale, + visualPreset: meta.visual_preset || 'classic', + guidedViews: meta.views || [], + sourceEvidence, + })); + outputPathGuards.delete(outPath); + console.log(outPath); +} + +const SEMANTIC_COLLECTIONS = { + architecture: 'components', + workflow: 'nodes', + sequence: 'participants', + dataflow: 'nodes', + lifecycle: 'states', +}; + +const RELATIONSHIP_COLLECTIONS = { + architecture: 'connections', + workflow: 'edges', + sequence: 'messages', + dataflow: 'flows', + lifecycle: 'transitions', +}; + +// Relationship IDs are optional for backwards compatibility, but once an +// author supplies one it becomes the durable identity used by viewer links. +// Keep uniqueness enforcement in the shared zero-install path so every typed +// renderer fails the same way even when development dependencies are absent. +export function validateRelationshipIds(diagramType, diagram) { + const collection = RELATIONSHIP_COLLECTIONS[diagramType]; + const relationships = collection && Array.isArray(diagram[collection]) ? diagram[collection] : []; + const seen = new Set(); + const problems = []; + + relationships.forEach((relationship, index) => { + if (relationship.id === undefined || relationship.id === null || relationship.id === '') return; + if (seen.has(relationship.id)) { + problems.push(`/${collection}/${index}/id duplicates relationship id ${JSON.stringify(relationship.id)}`); + } + seen.add(relationship.id); + }); + + if (problems.length) { + throwDiagnosticProblems('Relationship identity validation failed', problems, { + code: 'relationship/duplicate-id', + subject: { diagramType, collection }, + }); + } +} + +// JSON Schema keeps the view object bounded; this pass checks facts that span +// collections. Keeping it here makes the same contract apply to all five +// renderers, including the zero-install standalone-validator path. +export function validateGuidedViews(diagramType, diagram) { + const views = diagram.meta?.views; + if (!Array.isArray(views) || views.length === 0) return; + const collection = SEMANTIC_COLLECTIONS[diagramType]; + const semanticIds = new Set((diagram[collection] || []).map((item) => item.id)); + const seen = new Set(); + const problems = []; + + views.forEach((view, index) => { + if (seen.has(view.id)) problems.push(`/meta/views/${index}/id duplicates view id ${JSON.stringify(view.id)}`); + seen.add(view.id); + const seenFocus = new Set(); + (view.focus || []).forEach((id, focusIndex) => { + if (seenFocus.has(id)) { + problems.push(`/meta/views/${index}/focus/${focusIndex} duplicates semantic id ${JSON.stringify(id)}`); + } + seenFocus.add(id); + if (!semanticIds.has(id)) { + problems.push(`/meta/views/${index}/focus/${focusIndex} references unknown semantic id ${JSON.stringify(id)}`); + } + }); + }); + + if (problems.length) { + throwDiagnosticProblems('Guided view validation failed', problems, { + code: 'guided-view/invalid', + subject: { diagramType, collection: 'meta.views' }, + }); + } +} + +// Accessible name for the generated diagram SVG. +export function svgRootAttrs(meta) { + const animation = meta.animation === 'trace' ? ' data-animation="trace"' : ''; + const preset = ` data-preset="${esc(meta.visual_preset || 'classic')}"`; + const engineeringProfile = meta.engineering_profile + ? ` data-engineering-profile="${esc(meta.engineering_profile)}"` + : ''; + const requestedProfile = process.env.ARCHIFY_QUALITY_PROFILE || meta.quality_profile; + const qualityProfile = requestedProfile === 'showcase' ? 'showcase' : 'standard'; + const advisory = requestedProfile ? '' : ' data-quality-gates="advisory"'; + return `role="img" lang="${esc(resolveLocale(meta.locale))}" aria-labelledby="archify-diagram-title archify-diagram-description"${animation}${preset}${engineeringProfile} data-quality-profile="${esc(qualityProfile)}"${advisory}`; +} + +// Keep the accessible name inside the SVG so it survives standalone SVG +// export and embedding. The fixed IDs are deterministic because an Archify +// artifact intentionally contains one primary diagram SVG. +export function svgAccessibleText(meta, kind) { + const description = meta.subtitle || translateMessage(meta.locale, `diagram.description.${kind}`); + return ` ${esc(meta.title)}\n ${esc(description)}`; +} + +export function animateAttr(meta, kind, step) { + if (meta.animation !== 'trace') return ''; + // Ambient trace must finish inside the fixed six-second WebM capture. The + // cap affects visual delay only; authored order and semantic identity stay + // untouched in the JSON, DOM, Story, and relationship contracts. + const safeStep = Number.isFinite(step) && step >= 0 ? Math.min(12, Math.floor(step)) : 0; + return ` data-animate="${kind}" style="--step:${safeStep}"`; +} + +// Stable semantic hooks for the standalone HTML explorer. IDs already pass +// the schema's conservative identifier pattern; escape again at the markup +// boundary so these helpers remain safe if that contract expands later. +export function focusNodeAttrs(id, label, metadata = {}, locale) { + const optional = [ + ['data-node-kind', metadata.kind], + ['data-node-sublabel', metadata.sublabel], + ['data-node-tag', metadata.tag], + ['data-node-context', metadata.context], + ['data-node-brand', metadata.brand], + ['data-node-brand-id', metadata.brandId], + ['data-node-brand-status', metadata.brandStatus], + ['data-node-brand-source', metadata.brandSource], + ].filter(([, value]) => value !== undefined && value !== null && String(value).trim() !== '') + .map(([name, value]) => ` ${name}="${esc(String(value))}"`) + .join(''); + const detail = [metadata.sublabel, metadata.context, metadata.brand] + .filter((value) => value !== undefined && value !== null && String(value).trim() !== '') + .join(', '); + const aria = detail + ? translateMessage(locale, 'node.focus.detail', { label, detail }) + : translateMessage(locale, 'node.focus', { label }); + return `id="node-${esc(id)}" data-node-id="${esc(id)}" data-node-label="${esc(label)}" tabindex="0" role="button" aria-label="${esc(aria)}" aria-pressed="false"${optional}`; +} + +// Native SVG titles preserve a compact details-on-demand fallback when the +// canonical SVG is embedded inline outside the full Archify viewer. +export function focusNodeTitle(label, metadata = {}) { + const parts = [label, metadata.sublabel, metadata.context, metadata.tag, metadata.brand] + .filter((value) => value !== undefined && value !== null && String(value).trim() !== ''); + return `${esc(parts.join(' · '))}`; +} + +export function focusEdgeAttrs(from, to, label, key, id) { + const named = label ? ` data-edge-label="${esc(label)}"` : ''; + const keyed = key !== undefined && key !== null ? ` data-edge-key="${esc(String(key))}"` : ''; + const identified = id !== undefined && id !== null && String(id).trim() !== '' + ? ` data-edge-id="${esc(String(id))}"` + : ''; + return `data-edge-from="${esc(from)}" data-edge-to="${esc(to)}"${named}${keyed}${identified}`; +} diff --git a/tools/archify/renderers/shared/desktop-readability.mjs b/tools/archify/renderers/shared/desktop-readability.mjs new file mode 100644 index 0000000..1a4c249 --- /dev/null +++ b/tools/archify/renderers/shared/desktop-readability.mjs @@ -0,0 +1,26 @@ +export const DESKTOP_READABILITY_VIEWPORT = Object.freeze({ width: 1440, height: 900 }); +export const DESKTOP_READER_MIN_WIDTH = 960; +export const DESKTOP_READER_HORIZONTAL_CHROME = 30; +export const DESKTOP_READER_DIAGRAM_WIDTH = DESKTOP_READER_MIN_WIDTH - DESKTOP_READER_HORIZONTAL_CHROME; +export const MIN_PROJECTED_NODE_TEXT_PX = 6; + +export function projectedNodeTextPx(sourceFontPx, viewBoxWidth, diagramWidth = DESKTOP_READER_DIAGRAM_WIDTH) { + if (![sourceFontPx, viewBoxWidth, diagramWidth].every(Number.isFinite) || viewBoxWidth <= 0 || diagramWidth <= 0) { + return Number.NaN; + } + return sourceFontPx * Math.min(1, diagramWidth / viewBoxWidth); +} + +export function minimumReadableSourceTextPx( + viewBoxWidth, + diagramWidth = DESKTOP_READER_DIAGRAM_WIDTH, + minimumProjectedPx = MIN_PROJECTED_NODE_TEXT_PX, +) { + if (![viewBoxWidth, diagramWidth, minimumProjectedPx].every(Number.isFinite) + || viewBoxWidth <= 0 + || diagramWidth <= 0 + || minimumProjectedPx <= 0) { + return Number.NaN; + } + return minimumProjectedPx / Math.min(1, diagramWidth / viewBoxWidth); +} diff --git a/tools/archify/renderers/shared/diagnostics.mjs b/tools/archify/renderers/shared/diagnostics.mjs new file mode 100644 index 0000000..03cf6aa --- /dev/null +++ b/tools/archify/renderers/shared/diagnostics.mjs @@ -0,0 +1,127 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const DIAGNOSTIC_MODE = process.env.ARCHIFY_DIAGNOSTIC_FORMAT === 'json'; +const recorded = []; +const recordedMessages = new Set(); +const boundaryKey = Symbol.for('archify.renderer-diagnostic-boundary'); +let recordingSuppressionDepth = 0; + +function plainObject(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) return {}; + return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)); +} + +function normalizedDiagnostic(diagnostic) { + const message = String(diagnostic?.message || 'Archify could not classify this failure.').trim(); + return { + code: String(diagnostic?.code || 'internal/unclassified'), + severity: diagnostic?.severity === 'warning' ? 'warning' : 'error', + message, + subject: plainObject(diagnostic?.subject), + evidence: plainObject(diagnostic?.evidence), + supportedFixes: Array.isArray(diagnostic?.supportedFixes) + ? [...new Set(diagnostic.supportedFixes.map((fix) => String(fix).trim()).filter(Boolean))] + : [], + ...(Array.isArray(diagnostic?.suppresses) ? { + suppresses: [...new Set(diagnostic.suppresses.map((code) => String(code).trim()).filter(Boolean))], + } : {}), + }; +} + +export function recordDiagnostic(diagnostic) { + if (!DIAGNOSTIC_MODE || recordingSuppressionDepth > 0) return; + const normalized = normalizedDiagnostic(diagnostic); + if (recordedMessages.has(normalized.message)) return; + recordedMessages.add(normalized.message); + recorded.push(normalized); +} + +export function withDiagnosticRecordingSuppressed(callback) { + recordingSuppressionDepth += 1; + try { + return callback(); + } finally { + recordingSuppressionDepth -= 1; + } +} + +export function throwDiagnosticError(message, diagnostics) { + for (const diagnostic of diagnostics || []) recordDiagnostic(diagnostic); + const error = new Error(message); + error.archifyDiagnostics = (diagnostics || []).map(normalizedDiagnostic); + throw error; +} + +export function throwDiagnosticProblems(prefix, problems, { code = 'layout/constraint', subject = {} } = {}) { + const messages = (problems || []).map((problem) => String(problem)); + const diagnostics = messages.map((message) => normalizedDiagnostic({ + code, + severity: 'error', + message, + subject, + evidence: {}, + supportedFixes: [], + })); + throwDiagnosticError(`${prefix}:\n- ${messages.join('\n- ')}`, diagnostics); +} + +function fallbackDiagnostic(error) { + const input = process.argv[2] ? path.resolve(process.argv[2]) : undefined; + if (error instanceof SyntaxError) { + return normalizedDiagnostic({ + code: 'input/json-parse', + severity: 'error', + message: `Input JSON could not be parsed: ${error.message}`, + subject: { input }, + evidence: { reason: error.message }, + supportedFixes: ['repair the JSON syntax and run validation again'], + }); + } + if (error?.code === 'ENOENT' || error?.code === 'EACCES' || error?.code === 'EISDIR') { + return normalizedDiagnostic({ + code: 'input/read', + severity: 'error', + message: `Input could not be read: ${error.message}`, + subject: { input }, + evidence: { systemCode: error.code, reason: error.message }, + supportedFixes: ['provide one readable JSON input file'], + }); + } + return normalizedDiagnostic({ + code: 'internal/unclassified', + severity: 'error', + message: error?.message || 'Renderer failed without a diagnostic.', + subject: { input }, + evidence: { errorName: error?.name || 'Error' }, + supportedFixes: [], + }); +} +function rendererFailure(error) { + const attached = Array.isArray(error?.archifyDiagnostics) + ? error.archifyDiagnostics.map(normalizedDiagnostic) + : []; + const diagnostics = recorded.length ? recorded : (attached.length ? attached : [fallbackDiagnostic(error)]); + return { + schemaVersion: 1, + ok: false, + source: 'renderer', + error: error?.message || 'Renderer failed without a diagnostic.', + diagnostics, + }; +} + +export function installRendererDiagnosticBoundary() { + if (!DIAGNOSTIC_MODE || globalThis[boundaryKey]) return; + globalThis[boundaryKey] = true; + process.on('uncaughtException', (error) => { + const payload = `${JSON.stringify(rendererFailure(error))}\n`; + try { + fs.writeSync(process.stderr.fd, payload); + } catch { + // The renderer is already failing. Avoid replacing its real error with a + // secondary stream failure; the parent CLI still has the exit status. + } + process.exit(1); + }); +} diff --git a/tools/archify/renderers/shared/engineering-profiles.mjs b/tools/archify/renderers/shared/engineering-profiles.mjs new file mode 100644 index 0000000..76e3166 --- /dev/null +++ b/tools/archify/renderers/shared/engineering-profiles.mjs @@ -0,0 +1,157 @@ +import { throwDiagnosticError } from './diagnostics.mjs'; + +const DEPLOYMENT_PROFILE = 'deployment-ownership'; +const DEPLOYMENT_BOUNDARY_KINDS = new Set(['region', 'security-group']); +const PRIVATE_STATE_TYPES = new Set(['database']); + +function subject(collection, index, item = {}) { + return { + diagramType: 'architecture', + profile: DEPLOYMENT_PROFILE, + collection, + index, + ...(item.id ? { id: item.id } : {}), + }; +} + +function membership(boundaries, componentId, kind) { + return boundaries + .map((boundary, index) => ({ boundary, index })) + .filter(({ boundary }) => boundary.kind === kind && boundary.wraps.includes(componentId)); +} + +export function deploymentOwnershipDiagnostics(diagram) { + const components = Array.isArray(diagram.components) ? diagram.components : []; + const boundaries = (Array.isArray(diagram.boundaries) ? diagram.boundaries : []) + .map((boundary) => ({ ...boundary, wraps: Array.isArray(boundary.wraps) ? boundary.wraps : [] })); + const connections = Array.isArray(diagram.connections) ? diagram.connections : []; + const diagnostics = []; + + for (const kind of DEPLOYMENT_BOUNDARY_KINDS) { + const count = boundaries.filter((boundary) => boundary.kind === kind).length; + if (count > 0) continue; + diagnostics.push({ + code: 'engineering/deployment-boundary-kind', + severity: 'error', + message: `Deployment ownership requires at least one ${kind} boundary.`, + subject: subject('boundaries', -1), + evidence: { requiredKind: kind, found: count }, + supportedFixes: [`add one ${kind} boundary with an explicit wraps list`], + }); + } + + components.forEach((component, index) => { + if (component.type === 'external') return; + if (typeof component.tag !== 'string' || component.tag.trim() === '') { + diagnostics.push({ + code: 'engineering/deployment-owner-missing', + severity: 'error', + message: `Deployment component ${JSON.stringify(component.id)} does not name its owner in tag.`, + subject: subject('components', index, component), + evidence: { componentType: component.type, ownerField: 'tag' }, + supportedFixes: [`set /components/${index}/tag to the responsible team or owner`], + }); + } + + const regions = membership(boundaries, component.id, 'region'); + if (regions.length === 0) { + diagnostics.push({ + code: 'engineering/deployment-region-scope', + severity: 'error', + message: `Deployment component ${JSON.stringify(component.id)} is not assigned to a region boundary.`, + subject: subject('components', index, component), + evidence: { componentType: component.type, regionMemberships: 0 }, + supportedFixes: ['add the component id to the real region boundary wraps list'], + }); + } else if (regions.length > 1) { + diagnostics.push({ + code: 'engineering/deployment-region-ambiguous', + severity: 'error', + message: `Deployment component ${JSON.stringify(component.id)} belongs to more than one region boundary.`, + subject: subject('components', index, component), + evidence: { + componentType: component.type, + regions: regions.map(({ boundary, index: boundaryIndex }) => ({ boundaryIndex, label: boundary.label })), + }, + supportedFixes: ['keep the component id in exactly one real region boundary wraps list'], + }); + } + + if (PRIVATE_STATE_TYPES.has(component.type)) { + const privateScopes = membership(boundaries, component.id, 'security-group'); + if (privateScopes.length === 0) { + diagnostics.push({ + code: 'engineering/deployment-private-state', + severity: 'error', + message: `Stateful component ${JSON.stringify(component.id)} is not assigned to a private security-group boundary.`, + subject: subject('components', index, component), + evidence: { componentType: component.type, privateMemberships: 0 }, + supportedFixes: ['add the component id to the real private security-group boundary wraps list'], + }); + } + } + }); + + boundaries.forEach((boundary, index) => { + if (boundary.kind !== 'security-group') return; + const members = boundary.wraps.map((id) => ({ + id, + regions: membership(boundaries, id, 'region').map(({ boundary: region, index: boundaryIndex }) => ({ + boundaryIndex, + label: region.label, + })), + })); + const regionIndexes = new Set(members.flatMap((member) => member.regions.map((region) => region.boundaryIndex))); + const consistent = members.length > 0 + && members.every((member) => member.regions.length === 1) + && regionIndexes.size === 1; + if (consistent) return; + diagnostics.push({ + code: 'engineering/deployment-private-region-consistency', + severity: 'error', + message: `Private boundary ${JSON.stringify(boundary.label)} must contain components from exactly one shared region.`, + subject: subject('boundaries', index, boundary), + evidence: { boundaryKind: boundary.kind, members }, + supportedFixes: ['assign every private-boundary component to exactly one shared region boundary'], + }); + }); + + connections.forEach((connection, index) => { + const crossedBoundaries = boundaries + .map((boundary, boundaryIndex) => ({ + boundaryIndex, + kind: boundary.kind, + label: boundary.label, + fromInside: boundary.wraps.includes(connection.from), + toInside: boundary.wraps.includes(connection.to), + })) + .filter((boundary) => DEPLOYMENT_BOUNDARY_KINDS.has(boundary.kind) && boundary.fromInside !== boundary.toInside); + if (crossedBoundaries.length === 0 || (typeof connection.label === 'string' && connection.label.trim() !== '')) return; + diagnostics.push({ + code: 'engineering/deployment-crossing-mechanism', + severity: 'error', + message: `Cross-boundary connection ${JSON.stringify(connection.id || `${connection.from}->${connection.to}`)} does not name its mechanism.`, + subject: subject('connections', index, connection), + evidence: { + from: connection.from, + to: connection.to, + crossedBoundaries: crossedBoundaries.map(({ boundaryIndex, kind, label }) => ({ boundaryIndex, kind, label })), + }, + supportedFixes: [`set /connections/${index}/label to the real cross-boundary mechanism`], + }); + }); + + return diagnostics; +} + +export function validateEngineeringProfile(diagramType, diagram) { + const profile = diagram.meta?.engineering_profile; + if (!profile) return; + if (diagramType !== 'architecture' || profile !== DEPLOYMENT_PROFILE) return; + const diagnostics = deploymentOwnershipDiagnostics(diagram); + if (!diagnostics.length) return; + throwDiagnosticError( + `Engineering profile ${JSON.stringify(profile)} failed:\n${diagnostics.map((entry) => `- ${entry.message}`).join('\n')}`, + diagnostics, + ); +} diff --git a/tools/archify/renderers/shared/generated-brand-marks.mjs b/tools/archify/renderers/shared/generated-brand-marks.mjs new file mode 100644 index 0000000..69dbb7c --- /dev/null +++ b/tools/archify/renderers/shared/generated-brand-marks.mjs @@ -0,0 +1,2003 @@ +// Generated by scripts/generate-brand-marks.mjs from brand-marks/catalog.json. +// Simple Icons 16.28.0. Do not edit by hand. +export const BRAND_MARKS = Object.freeze([ + { + "id": "airtable", + "title": "Airtable", + "category": "collaboration", + "aliases": [], + "domains": [ + "airtable.com" + ], + "viewBox": 24, + "hex": "18BFFF", + "path": "M11.992 1.966c-.434 0-.87.086-1.28.257L1.779 5.917c-.503.208-.49.908.012 1.116l8.982 3.558a3.266 3.266 0 0 0 2.454 0l8.982-3.558c.503-.196.503-.908.012-1.116l-8.957-3.694a3.255 3.255 0 0 0-1.272-.257zM23.4 8.056a.589.589 0 0 0-.222.045l-10.012 3.877a.612.612 0 0 0-.38.564v8.896a.6.6 0 0 0 .821.552L23.62 18.1a.583.583 0 0 0 .38-.551V8.653a.6.6 0 0 0-.6-.596zM.676 8.095a.644.644 0 0 0-.48.19C.086 8.396 0 8.53 0 8.69v8.355c0 .442.515.737.908.54l6.27-3.006.307-.147 2.969-1.436c.466-.22.43-.908-.061-1.092L.883 8.138a.57.57 0 0 0-.207-.044z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://airtable.com/newsroom" + } + }, + { + "id": "alibaba-cloud", + "title": "Alibaba Cloud", + "category": "cloud", + "aliases": [ + "aliyun" + ], + "domains": [ + "alibabacloud.com", + "aliyun.com" + ], + "viewBox": 24, + "hex": "FF6A00", + "path": "M3.996 4.517h5.291L8.01 6.324 4.153 7.506a1.668 1.668 0 0 0-1.165 1.601v5.786a1.668 1.668 0 0 0 1.165 1.6l3.857 1.183 1.277 1.807H3.996A3.996 3.996 0 0 1 0 15.487V8.513a3.996 3.996 0 0 1 3.996-3.996m16.008 0h-5.291l1.277 1.807 3.857 1.182c.715.227 1.17.889 1.165 1.601v5.786a1.668 1.668 0 0 1-1.165 1.6l-3.857 1.183-1.277 1.807h5.291A3.996 3.996 0 0 0 24 15.487V8.513a3.996 3.996 0 0 0-3.996-3.996m-4.007 8.345H8.002v-1.804h7.995Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.alibabagroup.com/en/ir/reports" + } + }, + { + "id": "angular", + "title": "Angular", + "category": "framework", + "aliases": [], + "domains": [ + "angular.dev" + ], + "viewBox": 24, + "hex": "0F0F11", + "path": "M16.712 17.711H7.288l-1.204 2.916L12 24l5.916-3.373-1.204-2.916ZM14.692 0l7.832 16.855.814-12.856L14.692 0ZM9.308 0 .662 3.999l.814 12.856L9.308 0Zm-.405 13.93h6.198L12 6.396 8.903 13.93Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://angular.dev/press-kit", + "guidelines": "https://angular.dev/press-kit", + "license": { + "type": "CC-BY-4.0", + "url": "https://spdx.org/licenses/CC-BY-4.0" + } + } + }, + { + "id": "ansible", + "title": "Ansible", + "category": "engineering", + "aliases": [], + "domains": [ + "ansible.com" + ], + "viewBox": 24, + "hex": "EE0000", + "path": "M10.617 11.473l4.686 3.695-3.102-7.662zM12 0C5.371 0 0 5.371 0 12s5.371 12 12 12 12-5.371 12-12S18.629 0 12 0zm5.797 17.305c-.011.471-.403.842-.875.83-.236 0-.416-.09-.664-.293l-6.19-5-2.079 5.203H6.191L11.438 5.44c.124-.314.427-.52.764-.506.326-.014.63.189.742.506l4.774 11.494c.045.111.08.234.08.348-.001.009-.001.009-.001.023z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.ansible.com/logos" + } + }, + { + "id": "anthropic", + "title": "Anthropic", + "category": "ai", + "aliases": [], + "domains": [ + "anthropic.com" + ], + "viewBox": 24, + "hex": "191919", + "path": "M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.anthropic.com" + } + }, + { + "id": "apache-airflow", + "title": "Apache Airflow", + "category": "data", + "aliases": [ + "airflow" + ], + "domains": [ + "airflow.apache.org" + ], + "viewBox": 24, + "hex": "017CEE", + "path": "M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495zm-.409-9.476c.04.115.05.24.031.344-.17.96-1.593 2.538-4.304 3.87a.597.597 0 0 0-.08-.079c1.432-3.155 1.828-5.61 1.175-7.322l3.058 2.984.12.203zm-.131 9.44a.73.73 0 0 1-.347.031c-.96-.171-2.537-1.594-3.87-4.307a.656.656 0 0 0 .08-.078l-.001.001c3.155 1.432 5.61 1.83 7.324 1.174l-2.969 3.043M23.568.392a.05.05 0 0 1 .052-.011c.018.006.03.024.029.043-.065 4.655-.437 9.726-2.703 12.05-1.53 1.565-4.326 1.419-8.283-.377.006-.037.021-.07.02-.108 0-.044-.017-.082-.026-.123 2.83-1.39 4.315-3.037 4.506-4.115.057-.322-.009-.542-.102-.688l6.507-6.67V.392zM.393.43A.045.045 0 0 1 .382.38C.39.36.403.343.425.35c4.655.065 9.727.438 12.05 2.703l.002.002c1.56 1.527 1.415 4.323-.379 8.28-.033-.005-.062-.02-.097-.02h-.008c-.045.001-.084.019-.126.027-1.39-2.83-3.037-4.314-4.115-4.506-.323-.057-.542.01-.688.103L.393.43zm11.94 11.563a.331.331 0 0 1-.327.335H12a.332.332 0 0 1-.004-.661c.172.016.333.144.335.326h.002zm-5.12 4.661a.722.722 0 0 1-.03-.345c.17-.96 1.595-2.54 4.309-3.873.013.016.019.035.033.05.013.012.03.017.044.028-1.434 3.158-1.83 5.613-1.177 7.326l-3.041-2.967m-.006-9.659a.735.735 0 0 1 .345-.031c.961.17 2.54 1.594 3.871 4.306a.597.597 0 0 0-.079.08c-2.167-.983-4.007-1.484-5.498-1.484-.68 0-1.289.103-1.825.308L7.128 7.35M.43 23.607c-.018.018-.038.015-.052.01-.019-.007-.028-.021-.028-.043.065-4.654.437-9.725 2.703-12.049 1.527-1.565 4.325-1.419 8.286.378-.006.035-.02.067-.02.104 0 .043.018.083.026.124-2.831 1.391-4.317 3.04-4.51 4.117-.057.322.01.542.103.688L.43 23.607zm23.144.042c-4.655-.065-9.726-.437-12.05-2.703l-.005-.006c-1.56-1.526-1.412-4.322.383-8.279.033.005.064.02.098.02h.009c.043 0 .08-.018.122-.027 1.39 2.832 3.036 4.317 4.115 4.51.083.014.16.021.23.021a.776.776 0 0 0 .45-.133l6.68 6.516c.02.02.016.04.01.052a.042.042 0 0 1-.042.029z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://apache.org/logos", + "guidelines": "https://www.apache.org/foundation/marks", + "license": { + "type": "Apache-2.0", + "url": "https://spdx.org/licenses/Apache-2.0" + } + } + }, + { + "id": "apache-kafka", + "title": "Apache Kafka", + "category": "data", + "aliases": [ + "kafka" + ], + "domains": [ + "kafka.apache.org" + ], + "viewBox": 24, + "hex": "231F20", + "path": "M9.71 2.136a1.43 1.43 0 0 0-2.047 0h-.007a1.48 1.48 0 0 0-.421 1.042c0 .41.161.777.422 1.039l.007.007c.257.264.616.426 1.019.426.404 0 .766-.162 1.027-.426l.003-.007c.261-.262.421-.629.421-1.039 0-.408-.159-.777-.421-1.042H9.71zM8.683 22.295c.404 0 .766-.167 1.027-.429l.003-.008c.261-.261.421-.631.421-1.036 0-.41-.159-.778-.421-1.044H9.71a1.42 1.42 0 0 0-1.027-.432 1.4 1.4 0 0 0-1.02.432h-.007c-.26.266-.422.634-.422 1.044 0 .406.161.775.422 1.036l.007.008c.258.262.617.429 1.02.429zm7.89-4.462c.359-.096.683-.33.882-.684l.027-.052a1.47 1.47 0 0 0 .114-1.067 1.454 1.454 0 0 0-.675-.896l-.021-.014a1.425 1.425 0 0 0-1.078-.132c-.36.091-.684.335-.881.686-.2.349-.241.75-.146 1.119.099.363.33.691.675.896h.002c.346.203.737.239 1.101.144zm-6.405-7.342a2.083 2.083 0 0 0-1.485-.627c-.58 0-1.103.242-1.482.627-.378.385-.612.916-.612 1.507s.233 1.124.612 1.514a2.08 2.08 0 0 0 2.967 0c.379-.39.612-.923.612-1.514s-.233-1.122-.612-1.507zm-.835-2.51c.843.141 1.6.552 2.178 1.144h.004c.092.093.182.196.265.299l1.446-.851a3.176 3.176 0 0 1-.047-1.808 3.149 3.149 0 0 1 1.456-1.926l.025-.016a3.062 3.062 0 0 1 2.345-.306c.77.21 1.465.721 1.898 1.482v.002c.431.757.518 1.626.313 2.408a3.145 3.145 0 0 1-1.456 1.928l-.198.118h-.02a3.095 3.095 0 0 1-2.154.201 3.127 3.127 0 0 1-1.514-.944l-1.444.848a4.162 4.162 0 0 1 0 2.879l1.444.846c.413-.47.939-.789 1.514-.944a3.041 3.041 0 0 1 2.371.319l.048.023v.002a3.17 3.17 0 0 1 1.408 1.906 3.215 3.215 0 0 1-.313 2.405l-.026.053-.003-.005a3.147 3.147 0 0 1-1.867 1.436 3.096 3.096 0 0 1-2.371-.318v-.006a3.156 3.156 0 0 1-1.456-1.927 3.175 3.175 0 0 1 .047-1.805l-1.446-.848a3.905 3.905 0 0 1-.265.294l-.004.005a3.938 3.938 0 0 1-2.178 1.138v1.699a3.09 3.09 0 0 1 1.56.862l.002.004c.565.572.914 1.368.914 2.243 0 .873-.35 1.664-.914 2.239l-.002.009a3.1 3.1 0 0 1-2.21.931 3.1 3.1 0 0 1-2.206-.93h-.002v-.009a3.186 3.186 0 0 1-.916-2.239c0-.875.35-1.672.916-2.243v-.004h.002a3.1 3.1 0 0 1 1.558-.862v-1.699a3.926 3.926 0 0 1-2.176-1.138l-.006-.005a4.098 4.098 0 0 1-1.173-2.874c0-1.122.452-2.136 1.173-2.872h.006a3.947 3.947 0 0 1 2.176-1.144V6.289a3.137 3.137 0 0 1-1.558-.864h-.002v-.004a3.192 3.192 0 0 1-.916-2.243c0-.871.35-1.669.916-2.243l.002-.002A3.084 3.084 0 0 1 8.683 0c.861 0 1.641.355 2.21.932v.002h.002c.565.574.914 1.372.914 2.243 0 .876-.35 1.667-.914 2.243l-.002.005a3.142 3.142 0 0 1-1.56.864v1.692zm8.121-1.129l-.012-.019a1.452 1.452 0 0 0-.87-.668 1.43 1.43 0 0 0-1.103.146h.002c-.347.2-.58.529-.677.896-.095.365-.054.768.146 1.119l.007.009c.2.347.519.579.874.673.357.103.755.059 1.098-.144l.019-.009a1.47 1.47 0 0 0 .657-.885 1.493 1.493 0 0 0-.141-1.118", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://apache.org/logos", + "guidelines": "https://www.apache.org/foundation/marks", + "license": { + "type": "Apache-2.0", + "url": "https://spdx.org/licenses/Apache-2.0" + } + } + }, + { + "id": "argo", + "title": "Argo", + "category": "engineering", + "aliases": [ + "argocd", + "argo-cd" + ], + "domains": [ + "argoproj.github.io" + ], + "viewBox": 24, + "hex": "EF7B4D", + "path": "M12.581 0c.436.037.871.1 1.299.186 1.679.383 3.121 1.213 4.382 2.365 1.161 1.06 1.917 2.372 2.335 3.881.089.321.216.56.586.624.205.035.238.245.239.43.003.646.002 1.294.002 1.94l-.002 1.21c-.001.356-.116.479-.466.474-.211-.003-.293.119-.344.291-.146.489-.33.966-.552 1.426-.818 1.682-2.084 2.938-3.688 3.87-.077.045-.155.088-.233.131-.252.137-.258.146-.155.415.114.299.358.529.664.625.269.096.553.134.827.21a.672.672 0 0 1 .236.094c-.066.082-.156.067-.231.082-.36.073-.713.184-1.086.17a1.275 1.275 0 0 1-.438-.064c-.114-.045-.152-.006-.176.109a5.354 5.354 0 0 0-.084.92c-.015.617-.071 1.23-.112 1.844-.042.598-.018.651.558.842.281.094.563.187.842.286.069.024.15.038.192.117-.04.057-.098.035-.146.035-.493.003-.985.005-1.478.001-.524-.005-.806-.282-.845-.803-.055-.762-.12-1.524-.182-2.286a.947.947 0 0 0-.026-.12c-.079.455-.065.879-.084 1.298-.023.528-.008 1.057-.007 1.584 0 .27.086.388.335.483.359.135.711.295 1.114.262.141-.012.276.062.402.129.032.017.073.033.069.073-.004.043-.049.047-.084.045-.657-.019-1.317.065-1.972-.028-.323-.046-.533-.236-.631-.552-.094-.303-.114-.617-.137-.93-.046-.626-.078-1.253-.116-1.88a.222.222 0 0 0-.061-.171.282.282 0 0 0-.031.193c-.002.956-.002 1.911-.001 2.866 0 .388.123.575.494.708.481.172.976.298 1.47.423.11.028.225.047.242.192h-1.852c-.051-.01-.103-.022-.155-.03-.701-.1-1.001-.372-1.143-1.042l-.067-.331-.226-1.103c-.069.12-.118.25-.144.386-.083.399-.151.802-.243 1.2-.113.493-.444.763-.932.857l-.33.063H8.558c.057-.171.216-.185.355-.221.476-.127.96-.223 1.417-.409a.603.603 0 0 0 .397-.521c.058-.435.002-.865-.013-1.296a1.528 1.528 0 0 0-.078-.315.405.405 0 0 0-.071.207c-.026.296-.049.591-.075.886-.038.432-.273.716-.679.81a1.702 1.702 0 0 1-.37.045c-.557.003-1.115-.001-1.673-.005-.048 0-.109.019-.148-.065.178-.103.377-.168.582-.187a5.67 5.67 0 0 0 .939-.193c.42-.114.522-.249.512-.687-.023-.931-.091-1.86-.069-2.791.004-.184.001-.368.001-.551a2.387 2.387 0 0 0-.05.385 40.299 40.299 0 0 1-.186 2.623c-.052.513-.296.748-.804.805-.446.051-.889.002-1.332-.02-.108-.006-.234.012-.339-.064.043-.066.106-.07.16-.087.362-.115.725-.224 1.086-.344.246-.081.35-.235.355-.492a2.241 2.241 0 0 0-.003-.232 45.315 45.315 0 0 1-.105-2.149 5.487 5.487 0 0 0-.035-.478c-.024-.188-.131-.287-.295-.258-.505.092-.99-.006-1.473-.139-.059-.016-.134-.007-.178-.088a.986.986 0 0 1 .285-.09c.255-.052.507-.121.753-.208.312-.112.564-.347.695-.651.089-.203.056-.317-.112-.398-1.418-.683-2.512-1.73-3.391-3.017a8.152 8.152 0 0 1-1.123-2.447c-.067-.246-.156-.3-.383-.26-.306.053-.401.006-.535-.273v-3.49c.144-.303.205-.341.534-.329.235.01.247-.004.309-.242.396-1.508 1.082-2.861 2.171-3.988C6.9 1.42 8.523.631 10.34.203c.456-.108.922-.15 1.387-.203h.854Zm7.974 8.948a7.34 7.34 0 0 0-.048-.938 8.353 8.353 0 0 0-.099-.65c-.598-2.964-2.344-5.02-5.051-6.268-1.553-.715-3.21-.835-4.878-.511-3.248.633-5.396 2.583-6.539 5.652-.436 1.173-.495 2.406-.37 3.65.087.935.339 1.846.745 2.694.585 1.213 1.444 2.207 2.477 3.058.343.286.719.528 1.121.719.235.111.247.105.245-.146.006-.16.003-.32-.009-.48-.125-1.02-.142-2.045-.169-3.069a.392.392 0 0 0-.184-.353c-.385-.268-.713-.592-.921-1.019-.474-.97-.372-2.361.813-3.215.136-.097.217-.19.198-.373a1.724 1.724 0 0 1 .031-.442c.177-1.187.748-2.138 1.722-2.84.68-.492 1.442-.772 2.286-.782.483-.007.953.11 1.414.244 1.609.467 2.846 2.07 2.845 3.697a.64.64 0 0 0 .268.565c.463.371.821.83.943 1.426.22 1.077-.083 1.982-.979 2.634-.266.194-.347.406-.333.698.002.047 0 .095-.002.142l-.062 1.439c-.025.586-.138 1.165-.117 1.754.008.223.006.226.201.128a7.46 7.46 0 0 0 2.393-1.903c1.32-1.577 2.074-3.372 2.059-5.511ZM9.117 12.102c1.489.021 2.443-1.578 1.716-2.879a1.937 1.937 0 0 0-1.699-.991c-1.094-.004-1.954.822-1.958 1.881-.005 1.148.813 1.985 1.941 1.989Zm5.794 0c1.101.002 1.935-.823 1.935-1.917 0-1.091-.846-1.949-1.92-1.947-1.064.003-1.94.866-1.943 1.915-.003 1.105.831 1.948 1.928 1.949Zm-1.472 1.937c-.208.128-.407.277-.63.384-.536.257-1.063.257-1.579-.048-.158-.094-.308-.201-.464-.298-.047-.028-.092-.103-.15-.062-.044.03-.01.1-.001.151.037.179.064.362.082.544.027.565.293.992.742 1.31a.984.984 0 0 0 .791.186c.565-.119 1.025-.614 1.124-1.218.043-.266.005-.544.109-.803a.133.133 0 0 0-.024-.146Zm-8.78-4.92c-.012-1.102.143-2.055.54-2.961.633-1.443 1.642-2.553 2.98-3.374a.378.378 0 0 1 .459.067c.06.06.036.118.01.178a1.09 1.09 0 0 1-.48.51c-1.079.639-1.829 1.571-2.357 2.688a6.325 6.325 0 0 0-.618 2.986c.055 1.309.439 2.516 1.213 3.588.088.104.148.23.173.365.01.08.059.168-.031.228a.312.312 0 0 1-.288.041.502.502 0 0 1-.234-.185c-.72-.979-1.193-2.056-1.331-3.273-.036-.326-.004-.653-.036-.858ZM8.94 2.34a.373.373 0 0 1 .378-.382c.211.001.409.226.416.473.004.138-.309.39-.476.386-.189-.005-.318-.2-.318-.477Zm-.465 7.48a.609.609 0 0 1 .586-.631c.38-.003.671.271.675.633.004.356-.27.622-.639.621-.38-.002-.621-.241-.622-.623Zm6.496.623c-.381-.002-.625-.255-.621-.646a.635.635 0 0 1 .596-.613.656.656 0 0 1 .669.643c.001.354-.275.618-.644.616Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/cncf/artwork/blob/c2e619cdf85e8bac090ceca7c0834c5cfedf9426/projects/argo/icon/black/argo-icon-black.svg" + } + }, + { + "id": "asana", + "title": "Asana", + "category": "collaboration", + "aliases": [], + "domains": [ + "asana.com" + ], + "viewBox": 24, + "hex": "F06A6A", + "path": "M18.78 12.653c-2.882 0-5.22 2.336-5.22 5.22s2.338 5.22 5.22 5.22 5.22-2.34 5.22-5.22-2.336-5.22-5.22-5.22zm-13.56 0c-2.88 0-5.22 2.337-5.22 5.22s2.338 5.22 5.22 5.22 5.22-2.338 5.22-5.22-2.336-5.22-5.22-5.22zm12-6.525c0 2.883-2.337 5.22-5.22 5.22-2.882 0-5.22-2.337-5.22-5.22 0-2.88 2.338-5.22 5.22-5.22 2.883 0 5.22 2.34 5.22 5.22z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://asana.com/brand", + "guidelines": "https://asana.com/brand" + } + }, + { + "id": "bitbucket", + "title": "Bitbucket", + "category": "engineering", + "aliases": [], + "domains": [ + "bitbucket.org" + ], + "viewBox": 24, + "hex": "0052CC", + "path": "M.778 1.213a.768.768 0 00-.768.892l3.263 19.81c.084.5.515.868 1.022.873H19.95a.772.772 0 00.77-.646l3.27-20.03a.768.768 0 00-.768-.891zM14.52 15.53H9.522L8.17 8.466h7.561z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://atlassian.design/resources/logo-library", + "guidelines": "https://atlassian.design/foundations/logos" + } + }, + { + "id": "circleci", + "title": "CircleCI", + "category": "engineering", + "aliases": [ + "circle-ci" + ], + "domains": [ + "circleci.com" + ], + "viewBox": 24, + "hex": "343434", + "path": "M8.963 12c0-1.584 1.284-2.855 2.855-2.855 1.572 0 2.856 1.284 2.856 2.855 0 1.572-1.284 2.856-2.856 2.856-1.57 0-2.855-1.284-2.855-2.856zm2.855-12C6.215 0 1.522 3.84.19 9.025c-.01.036-.01.07-.01.12 0 .313.252.576.575.576H5.59c.23 0 .433-.13.517-.333.997-2.16 3.18-3.672 5.712-3.672 3.466 0 6.286 2.82 6.286 6.287 0 3.47-2.82 6.29-6.29 6.29-2.53 0-4.714-1.5-5.71-3.673-.097-.19-.29-.336-.517-.336H.755c-.312 0-.575.253-.575.576 0 .037.014.072.014.12C1.514 20.16 6.214 24 11.818 24c6.624 0 12-5.375 12-12 0-6.623-5.376-12-12-12z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://circleci.com/press" + } + }, + { + "id": "claude", + "title": "Claude", + "category": "ai", + "aliases": [ + "claude-ai" + ], + "domains": [ + "claude.ai" + ], + "viewBox": 24, + "hex": "D97757", + "path": "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://claude.ai" + } + }, + { + "id": "clickhouse", + "title": "ClickHouse", + "category": "data", + "aliases": [], + "domains": [ + "clickhouse.com" + ], + "viewBox": 24, + "hex": "FFCC01", + "path": "M21.333 10H24v4h-2.667ZM16 1.335h2.667v21.33H16Zm-5.333 0h2.666v21.33h-2.666ZM0 22.665V1.335h2.667v21.33zm5.333-21.33H8v21.33H5.333Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/ClickHouse/ClickHouse/blob/12bd453a43819176d25ecf247033f6cb1af54beb/website/images/logo-clickhouse.svg" + } + }, + { + "id": "cloudflare", + "title": "Cloudflare", + "category": "cloud", + "aliases": [], + "domains": [ + "cloudflare.com" + ], + "viewBox": 24, + "hex": "F38020", + "path": "M16.5088 16.8447c.1475-.5068.0908-.9707-.1553-1.3154-.2246-.3164-.6045-.499-1.0615-.5205l-8.6592-.1123a.1559.1559 0 0 1-.1333-.0713c-.0283-.042-.0351-.0986-.021-.1553.0278-.084.1123-.1484.2036-.1562l8.7359-.1123c1.0351-.0489 2.1601-.8868 2.5537-1.9136l.499-1.3013c.0215-.0561.0293-.1128.0147-.168-.5625-2.5463-2.835-4.4453-5.5499-4.4453-2.5039 0-4.6284 1.6177-5.3876 3.8614-.4927-.3658-1.1187-.5625-1.794-.499-1.2026.119-2.1665 1.083-2.2861 2.2856-.0283.31-.0069.6128.0635.894C1.5683 13.171 0 14.7754 0 16.752c0 .1748.0142.3515.0352.5273.0141.083.0844.1475.1689.1475h15.9814c.0909 0 .1758-.0645.2032-.1553l.12-.4268zm2.7568-5.5634c-.0771 0-.1611 0-.2383.0112-.0566 0-.1054.0415-.127.0976l-.3378 1.1744c-.1475.5068-.0918.9707.1543 1.3164.2256.3164.6055.498 1.0625.5195l1.8437.1133c.0557 0 .1055.0263.1329.0703.0283.043.0351.1074.0214.1562-.0283.084-.1132.1485-.204.1553l-1.921.1123c-1.041.0488-2.1582.8867-2.5527 1.914l-.1406.3585c-.0283.0713.0215.1416.0986.1416h6.5977c.0771 0 .1474-.0489.169-.126.1122-.4082.1757-.837.1757-1.2803 0-2.6025-2.125-4.727-4.7344-4.727", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.cloudflare.com/logo/", + "guidelines": "https://www.cloudflare.com/trademark/" + } + }, + { + "id": "cloudinary", + "title": "Cloudinary", + "category": "cloud", + "aliases": [], + "domains": [ + "cloudinary.com" + ], + "viewBox": 24, + "hex": "3448C5", + "path": "M24 14.8598c0 2.1729-1.3757 3.974-3.5903 4.6996l-.0995.0318V17.989c1.3777-.5805 2.1869-1.7275 2.1869-3.1291-.0072-2-1.6087-3.6288-3.6082-3.6699h-.5964l-.1432-.5686c-.7025-2.8996-3.2886-4.9489-6.2721-4.97a6.3915 6.3915 0 0 0-5.811 3.664l-.1828.3757-.4175.0437a4.4311 4.4311 0 0 0-3.3052 2.088c-1.2803 2.0856-.6274 4.8143 1.4583 6.0947v1.6897h-.01l-.149-.0675a5.9402 5.9402 0 0 1-3.3658-4.3494c-.5787-3.2291 1.57-6.3161 4.7991-6.8948a7.8766 7.8766 0 0 1 6.9839-4.149c3.4724.025 6.535 2.28 7.5901 5.5883 2.5789.3366 4.5138 2.5245 4.5327 5.1251zm-15.3176-1.322h.5647a.0656.0656 0 0 0 .0457-.1113L7.084 11.2158l-.0007-.0007a.0656.0656 0 0 0-.0927.0007L4.78 13.4265a.0656.0656 0 0 0 .0477.1113h.5566a.0656.0656 0 0 1 .0657.0656v5.0574c0 .6588.534 1.1928 1.1928 1.1928H9.247a.0656.0656 0 0 0 .0457-.1113l-.33-.33a1.1928 1.1928 0 0 1-.348-.839v-4.97a.0676.0676 0 0 1 .0676-.0655zm9.769 2.5466h.5667a.0655.0655 0 0 0 .0457-.1133l-2.2107-2.2087-.0015-.0015a.0636.0636 0 0 0-.0899.0015L14.551 15.971a.0657.0657 0 0 0 .0457.1133h.5567a.0656.0656 0 0 1 .0656.0656v2.5108c0 .6588.534 1.1928 1.1928 1.1928h2.6063a.0655.0655 0 0 0 .0457-.1113l-.33-.33a1.1928 1.1928 0 0 1-.348-.839V16.15a.0656.0656 0 0 1 .0657-.0656zm-4.8844-1.2743h.5646a.0656.0656 0 0 0 .0477-.1114l-2.2107-2.2027-.0006-.0006a.0656.0656 0 0 0-.0928.0006l-2.2087 2.2068a.0656.0656 0 0 0 .0457.1113h.5626a.0676.0676 0 0 1 .0657.0676v3.7791c0 .6588.534 1.1928 1.1928 1.1928h2.5983a.0656.0656 0 0 0 .0477-.1113l-.332-.33a1.193 1.193 0 0 1-.346-.839v-3.6956c0-.0366.0291-.0665.0657-.0676z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://cloudinary.com" + } + }, + { + "id": "databricks", + "title": "Databricks", + "category": "data", + "aliases": [], + "domains": [ + "databricks.com" + ], + "viewBox": 24, + "hex": "FF3621", + "path": "M.95 14.184L12 20.403l9.919-5.55v2.21L12 22.662l-10.484-5.96-.565.308v.77L12 24l11.05-6.218v-4.317l-.515-.309L12 19.118l-9.867-5.653v-2.21L12 16.805l11.05-6.218V6.32l-.515-.308L12 11.974 2.647 6.681 12 1.388l7.76 4.368.668-.411v-.566L12 0 .95 6.27v.72L12 13.207l9.919-5.55v2.26L12 15.52 1.516 9.56l-.565.308Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.databricks.com", + "guidelines": "https://brand.databricks.com/Styleguide/Guide/" + } + }, + { + "id": "datadog", + "title": "Datadog", + "category": "engineering", + "aliases": [], + "domains": [ + "datadoghq.com" + ], + "viewBox": 24, + "hex": "632CA6", + "path": "M19.57 17.04l-1.997-1.316-1.665 2.782-1.937-.567-1.706 2.604.087.82 9.274-1.71-.538-5.794zm-8.649-2.498l1.488-.204c.241.108.409.15.697.223.45.117.97.23 1.741-.16.18-.088.553-.43.704-.625l6.096-1.106.622 7.527-10.444 1.882zm11.325-2.712l-.602.115L20.488 0 .789 2.285l2.427 19.693 2.306-.334c-.184-.263-.471-.581-.96-.989-.68-.564-.44-1.522-.039-2.127.53-1.022 3.26-2.322 3.106-3.956-.056-.594-.15-1.368-.702-1.898-.02.22.017.432.017.432s-.227-.289-.34-.683c-.112-.15-.2-.199-.319-.4-.085.233-.073.503-.073.503s-.186-.437-.216-.807c-.11.166-.137.48-.137.48s-.241-.69-.186-1.062c-.11-.323-.436-.965-.343-2.424.6.421 1.924.321 2.44-.439.171-.251.288-.939-.086-2.293-.24-.868-.835-2.16-1.066-2.651l-.028.02c.122.395.374 1.223.47 1.625.293 1.218.372 1.642.234 2.204-.116.488-.397.808-1.107 1.165-.71.358-1.653-.514-1.713-.562-.69-.55-1.224-1.447-1.284-1.883-.062-.477.275-.763.445-1.153-.243.07-.514.192-.514.192s.323-.334.722-.624c.165-.109.262-.178.436-.323a9.762 9.762 0 0 0-.456.003s.42-.227.855-.392c-.318-.014-.623-.003-.623-.003s.937-.419 1.678-.727c.509-.208 1.006-.147 1.286.257.367.53.752.817 1.569.996.501-.223.653-.337 1.284-.509.554-.61.99-.688.99-.688s-.216.198-.274.51c.314-.249.66-.455.66-.455s-.134.164-.259.426l.03.043c.366-.22.797-.394.797-.394s-.123.156-.268.358c.277-.002.838.012 1.056.037 1.285.028 1.552-1.374 2.045-1.55.618-.22.894-.353 1.947.68.903.888 1.609 2.477 1.259 2.833-.294.295-.874-.115-1.516-.916a3.466 3.466 0 0 1-.716-1.562 1.533 1.533 0 0 0-.497-.85s.23.51.23.96c0 .246.03 1.165.424 1.68-.039.076-.057.374-.1.43-.458-.554-1.443-.95-1.604-1.067.544.445 1.793 1.468 2.273 2.449.453.927.186 1.777.416 1.997.065.063.976 1.197 1.15 1.767.306.994.019 2.038-.381 2.685l-1.117.174c-.163-.045-.273-.068-.42-.153.08-.143.241-.5.243-.572l-.063-.111c-.348.492-.93.97-1.414 1.245-.633.359-1.363.304-1.838.156-1.348-.415-2.623-1.327-2.93-1.566 0 0-.01.191.048.234.34.383 1.119 1.077 1.872 1.56l-1.605.177.759 5.908c-.337.048-.39.071-.757.124-.325-1.147-.946-1.895-1.624-2.332-.599-.384-1.424-.47-2.214-.314l-.05.059a2.851 2.851 0 0 1 1.863.444c.654.413 1.181 1.481 1.375 2.124.248.822.42 1.7-.248 2.632-.476.662-1.864 1.028-2.986.237.3.481.705.876 1.25.95.809.11 1.577-.03 2.106-.574.452-.464.69-1.434.628-2.456l.714-.104.258 1.834 11.827-1.424zM15.05 6.848c-.034.075-.085.125-.007.37l.004.014.013.032.032.073c.14.287.295.558.552.696.067-.011.136-.019.207-.023.242-.01.395.028.492.08.009-.048.01-.119.005-.222-.018-.364.072-.982-.626-1.308-.264-.122-.634-.084-.757.068a.302.302 0 0 1 .058.013c.186.066.06.13.027.207m1.958 3.392c-.092-.05-.52-.03-.821.005-.574.068-1.193.267-1.328.372-.247.191-.135.523.047.66.511.382.96.638 1.432.575.29-.038.546-.497.728-.914.124-.288.124-.598-.058-.698m-5.077-2.942c.162-.154-.805-.355-1.556.156-.554.378-.571 1.187-.041 1.646.053.046.096.078.137.104a4.77 4.77 0 0 1 1.396-.412c.113-.125.243-.345.21-.745-.044-.542-.455-.456-.146-.749", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.datadoghq.com/about/resources", + "guidelines": "https://www.datadoghq.com/about/resources/" + } + }, + { + "id": "deepseek", + "title": "DeepSeek", + "category": "ai", + "aliases": [], + "domains": [ + "deepseek.com" + ], + "viewBox": 24, + "hex": "5786FE", + "path": "M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.deepseek.com" + } + }, + { + "id": "digitalocean", + "title": "DigitalOcean", + "category": "cloud", + "aliases": [ + "digital-ocean" + ], + "domains": [ + "digitalocean.com" + ], + "viewBox": 24, + "hex": "0080FF", + "path": "M12.04 0C5.408-.02.005 5.37.005 11.992h4.638c0-4.923 4.882-8.731 10.064-6.855a6.95 6.95 0 014.147 4.148c1.889 5.177-1.924 10.055-6.84 10.064v-4.61H7.391v4.623h4.61V24c7.86 0 13.967-7.588 11.397-15.83-1.115-3.59-3.985-6.446-7.575-7.575A12.8 12.8 0 0012.039 0zM7.39 19.362H3.828v3.564H7.39zm-3.563 0v-2.978H.85v2.978z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.digitalocean.com/press/", + "guidelines": "https://www.digitalocean.com/press/" + } + }, + { + "id": "discord", + "title": "Discord", + "category": "collaboration", + "aliases": [], + "domains": [ + "discord.com" + ], + "viewBox": 24, + "hex": "5865F2", + "path": "M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://discord.com/branding", + "guidelines": "https://discord.com/branding" + } + }, + { + "id": "django", + "title": "Django", + "category": "framework", + "aliases": [], + "domains": [ + "djangoproject.com" + ], + "viewBox": 24, + "hex": "092E20", + "path": "M11.146 0h3.924v18.166c-2.013.382-3.491.535-5.096.535-4.791 0-7.288-2.166-7.288-6.32 0-4.002 2.65-6.6 6.753-6.6.637 0 1.121.05 1.707.203zm0 9.143a3.894 3.894 0 00-1.325-.204c-1.988 0-3.134 1.223-3.134 3.365 0 2.09 1.096 3.236 3.109 3.236.433 0 .79-.025 1.35-.102V9.142zM21.314 6.06v9.098c0 3.134-.229 4.638-.917 5.937-.637 1.249-1.478 2.039-3.211 2.905l-3.644-1.733c1.733-.815 2.574-1.53 3.109-2.625.561-1.121.739-2.421.739-5.835V6.059h3.924zM17.39.021h3.924v4.026H17.39z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.djangoproject.com/community/logos/" + } + }, + { + "id": "docker", + "title": "Docker", + "category": "engineering", + "aliases": [], + "domains": [ + "docker.com" + ], + "viewBox": 24, + "hex": "2496ED", + "path": "M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.docker.com/company/newsroom/media-resources" + } + }, + { + "id": "dotnet", + "title": ".NET", + "category": "framework", + "aliases": [ + ".net" + ], + "domains": [ + "dotnet.microsoft.com" + ], + "viewBox": 24, + "hex": "512BD4", + "path": "M24 8.77h-2.468v7.565h-1.425V8.77h-2.462V7.53H24zm-6.852 7.565h-4.821V7.53h4.63v1.24h-3.205v2.494h2.953v1.234h-2.953v2.604h3.396zm-6.708 0H8.882L4.78 9.863a2.896 2.896 0 0 1-.258-.51h-.036c.032.189.048.592.048 1.21v5.772H3.157V7.53h1.659l3.965 6.32c.167.261.275.442.323.54h.024c-.04-.233-.06-.629-.06-1.185V7.529h1.372zm-8.703-.693a.868.829 0 0 1-.869.829.868.829 0 0 1-.868-.83.868.829 0 0 1 .868-.828.868.829 0 0 1 .869.829Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/dotnet/brand/blob/c7d0f51b8ec59531332d05fb27a5b758a7a3d689/logo/dotnet-logo.svg", + "guidelines": "https://github.com/dotnet/brand/blob/c7d0f51b8ec59531332d05fb27a5b758a7a3d689/dotnet-styleGuide-2024.pdf", + "license": { + "type": "CC0-1.0", + "url": "https://spdx.org/licenses/CC0-1.0" + } + } + }, + { + "id": "elasticsearch", + "title": "Elasticsearch", + "category": "data", + "aliases": [ + "elastic" + ], + "domains": [ + "elastic.co" + ], + "viewBox": 24, + "hex": "005571", + "path": "M13.394 0C8.683 0 4.609 2.716 2.644 6.667h15.641a4.77 4.77 0 0 0 3.073-1.11c.446-.375.864-.785 1.247-1.243l.001-.002A11.974 11.974 0 0 0 13.394 0zM1.804 8.889a12.009 12.009 0 0 0 0 6.222h14.7a3.111 3.111 0 1 0 0-6.222zm.84 8.444C4.61 21.283 8.684 24 13.395 24c3.701 0 7.011-1.677 9.212-4.312l-.001-.002a9.958 9.958 0 0 0-1.247-1.243 4.77 4.77 0 0 0-3.073-1.11z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.elastic.co/brand" + } + }, + { + "id": "facebook", + "title": "Facebook", + "category": "channel", + "aliases": [], + "domains": [ + "facebook.com" + ], + "viewBox": 24, + "hex": "0866FF", + "path": "M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://about.meta.com/brand/resources/facebook/logo", + "guidelines": "https://about.meta.com/brand/resources/facebook/logo" + } + }, + { + "id": "fastapi", + "title": "FastAPI", + "category": "framework", + "aliases": [], + "domains": [ + "fastapi.tiangolo.com" + ], + "viewBox": 24, + "hex": "009688", + "path": "M12 .0387C5.3729.0384.0003 5.3931 0 11.9988c-.001 6.6066 5.372 11.9628 12 11.9625 6.628.0003 12.001-5.3559 12-11.9625-.0003-6.6057-5.3729-11.9604-12-11.96m-.829 5.4153h7.55l-7.5805 5.3284h5.1828L5.279 18.5436q2.9466-6.5444 5.892-13.0896", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/tiangolo/fastapi/blob/ffb4f77a11f83132b521ba0aac6c95792c19e797/docs/en/docs/img/icon-white.svg" + } + }, + { + "id": "figma", + "title": "Figma", + "category": "collaboration", + "aliases": [], + "domains": [ + "figma.com" + ], + "viewBox": 24, + "hex": "F24E1E", + "path": "M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.figma.com/using-the-figma-brand/", + "guidelines": "https://www.figma.com/using-the-figma-brand/" + } + }, + { + "id": "firebase", + "title": "Firebase", + "category": "cloud", + "aliases": [], + "domains": [ + "firebase.google.com" + ], + "viewBox": 24, + "hex": "DD2C00", + "path": "M19.455 8.369c-.538-.748-1.778-2.285-3.681-4.569-.826-.991-1.535-1.832-1.884-2.245a146 146 0 0 0-.488-.576l-.207-.245-.113-.133-.022-.032-.01-.005L12.57 0l-.609.488c-1.555 1.246-2.828 2.851-3.681 4.64-.523 1.064-.864 2.105-1.043 3.176-.047.241-.088.489-.121.738-.209-.017-.421-.028-.632-.033-.018-.001-.035-.002-.059-.003a7.46 7.46 0 0 0-2.28.274l-.317.089-.163.286c-.765 1.342-1.198 2.869-1.252 4.416-.07 2.01.477 3.954 1.583 5.625 1.082 1.633 2.61 2.882 4.42 3.611l.236.095.071.025.003-.001a9.59 9.59 0 0 0 2.941.568q.171.006.342.006c1.273 0 2.513-.249 3.69-.742l.008.004.313-.145a9.63 9.63 0 0 0 3.927-3.335c1.01-1.49 1.577-3.234 1.641-5.042.075-2.161-.643-4.304-2.133-6.371m-7.083 6.695c.328 1.244.264 2.44-.191 3.558-1.135-1.12-1.967-2.352-2.475-3.665-.543-1.404-.87-2.74-.974-3.975.48.157.922.366 1.315.622 1.132.737 1.914 1.902 2.325 3.461zm.207 6.022c.482.368.99.712 1.513 1.028-.771.21-1.565.302-2.369.273a8 8 0 0 1-.373-.022c.458-.394.869-.823 1.228-1.279zm1.347-6.431c-.516-1.957-1.527-3.437-3.002-4.398-.647-.421-1.385-.741-2.194-.95.011-.134.026-.268.043-.4.014-.113.03-.216.046-.313.133-.689.332-1.37.589-2.025.099-.25.206-.499.321-.74l.004-.008c.177-.358.376-.719.61-1.105l.092-.152-.003-.001c.544-.851 1.197-1.627 1.942-2.311l.288.341c.672.796 1.304 1.548 1.878 2.237 1.291 1.549 2.966 3.583 3.612 4.48 1.277 1.771 1.893 3.579 1.83 5.375-.049 1.395-.461 2.755-1.195 3.933-.694 1.116-1.661 2.05-2.8 2.708-.636-.318-1.559-.839-2.539-1.599.79-1.575.952-3.28.479-5.072zm-2.575 5.397c-.725.939-1.587 1.55-2.09 1.856-.081-.029-.163-.06-.243-.093l-.065-.026c-1.49-.616-2.747-1.656-3.635-3.01-.907-1.384-1.356-2.993-1.298-4.653.041-1.19.338-2.327.882-3.379.316-.07.638-.114.96-.131l.084-.002c.162-.003.324-.003.478 0 .227.011.454.035.677.07.073 1.513.445 3.145 1.105 4.852.637 1.644 1.694 3.162 3.144 4.515z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://firebase.google.com/brand-guidelines", + "guidelines": "https://firebase.google.com/brand-guidelines" + } + }, + { + "id": "flask", + "title": "Flask", + "category": "framework", + "aliases": [], + "domains": [ + "palletsprojects.com" + ], + "viewBox": 24, + "hex": "3BABC3", + "path": "M10.773 2.878c-.013 1.434.322 4.624.445 5.734l-8.558 3.83c-.56-.959-.98-2.304-1.237-3.38l-.06.027c-.205.09-.406.053-.494-.088l-.011-.018-.82-1.506c-.058-.105-.05-.252.024-.392a.78.78 0 0 1 .358-.331l9.824-4.207c.146-.064.299-.063.4.004.106.062.127.128.13.327Zm.68 7c.523 1.97.675 2.412.832 2.818l-7.263 3.7a19.35 19.35 0 0 1-1.81-2.83l8.24-3.689Zm12.432 8.786h.003c.283.402-.047.657-.153.698l-.947.37c.037.125.035.319-.217.414l-.736.287c-.229.09-.398-.059-.42-.2l-.025-.125c-4.427 1.784-7.94 1.685-10.696.647-1.981-.745-3.576-1.983-4.846-3.379l6.948-3.54c.721 1.431 1.586 2.454 2.509 3.178 2.086 1.638 4.415 1.712 5.793 1.563l-.047-.233c-.015-.077.007-.135.086-.165l.734-.288a.302.302 0 0 1 .342.086l.748-.288a.306.306 0 0 1 .341.086l.583.89Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/pallets/flask/blob/85c5d93cbd049c4bd0679c36fd1ddcae8c37b642/docs/_static/flask-icon.svg" + } + }, + { + "id": "fly-io", + "title": "Fly.io", + "category": "cloud", + "aliases": [ + "fly.io" + ], + "domains": [ + "fly.io" + ], + "viewBox": 24, + "hex": "24175B", + "path": "M11.987 0c-2.45-.01-5.002.925-6.541 2.897-1.17 1.502-1.664 3.474-1.49 5.356.29 2.112 1.476 3.96 2.676 5.672a41.5 41.5 0 0 0 4.216 4.831c-1.063.832-1.943 2.286-1.357 3.644.821 2.32 4.665 2.05 5.122-.372.39-1.288-.694-2.533-1.428-3.309 2.388-2.431 4.706-5.036 6.17-8.145.595-1.32.902-2.802.614-4.24-.28-2.341-1.823-4.473-3.967-5.46C14.76.266 13.364.016 11.987 0m-.236 1.577v15.534C9.881 13.483 7.724 9.266 8.73 5.069c.35-1.539 1.253-3.309 3.02-3.492m1.996.04c1.534.357 3.031 1.096 3.906 2.48 1.3 1.93 1.318 4.55.1 6.521-1.268 2.395-3.06 4.463-4.916 6.415 1.472-2.974 3.074-6.106 3.182-9.5-.043-2.08-.438-4.612-2.272-5.916M11.97 20.103c.848.342 1.597 1.983.153 2.173-.664.15-1.367-.599-.995-1.222.213-.355.488-.73.842-.95", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://fly.io/docs/about/brand", + "guidelines": "https://fly.io/docs/about/brand" + } + }, + { + "id": "github", + "title": "GitHub", + "category": "engineering", + "aliases": [], + "domains": [ + "github.com" + ], + "viewBox": 24, + "hex": "181717", + "path": "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/logos", + "guidelines": "https://github.com/logos" + } + }, + { + "id": "github-actions", + "title": "GitHub Actions", + "category": "engineering", + "aliases": [], + "domains": [], + "viewBox": 24, + "hex": "2088FF", + "path": "M10.984 13.836a.5.5 0 0 1-.353-.146l-.745-.743a.5.5 0 1 1 .706-.708l.392.391 1.181-1.18a.5.5 0 0 1 .708.707l-1.535 1.533a.504.504 0 0 1-.354.146zm9.353-.147l1.534-1.532a.5.5 0 0 0-.707-.707l-1.181 1.18-.392-.391a.5.5 0 1 0-.706.708l.746.743a.497.497 0 0 0 .706-.001zM4.527 7.452l2.557-1.585A1 1 0 0 0 7.09 4.17L4.533 2.56A1 1 0 0 0 3 3.406v3.196a1.001 1.001 0 0 0 1.527.85zm2.03-2.436L4 6.602V3.406l2.557 1.61zM24 12.5c0 1.93-1.57 3.5-3.5 3.5a3.503 3.503 0 0 1-3.46-3h-2.08a3.503 3.503 0 0 1-3.46 3 3.502 3.502 0 0 1-3.46-3h-.558c-.972 0-1.85-.399-2.482-1.042V17c0 1.654 1.346 3 3 3h.04c.244-1.693 1.7-3 3.46-3 1.93 0 3.5 1.57 3.5 3.5S13.43 24 11.5 24a3.502 3.502 0 0 1-3.46-3H8c-2.206 0-4-1.794-4-4V9.899A5.008 5.008 0 0 1 0 5c0-2.757 2.243-5 5-5s5 2.243 5 5a5.005 5.005 0 0 1-4.952 4.998A2.482 2.482 0 0 0 7.482 12h.558c.244-1.693 1.7-3 3.46-3a3.502 3.502 0 0 1 3.46 3h2.08a3.503 3.503 0 0 1 3.46-3c1.93 0 3.5 1.57 3.5 3.5zm-15 8c0 1.378 1.122 2.5 2.5 2.5s2.5-1.122 2.5-2.5-1.122-2.5-2.5-2.5S9 19.122 9 20.5zM5 9c2.206 0 4-1.794 4-4S7.206 1 5 1 1 2.794 1 5s1.794 4 4 4zm9 3.5c0-1.378-1.122-2.5-2.5-2.5S9 11.122 9 12.5s1.122 2.5 2.5 2.5 2.5-1.122 2.5-2.5zm9 0c0-1.378-1.122-2.5-2.5-2.5S18 11.122 18 12.5s1.122 2.5 2.5 2.5 2.5-1.122 2.5-2.5zm-13 8a.5.5 0 1 0 1 0 .5.5 0 0 0-1 0zm2 0a.5.5 0 1 0 1 0 .5.5 0 0 0-1 0zm12 0c0 1.93-1.57 3.5-3.5 3.5a3.503 3.503 0 0 1-3.46-3.002c-.007.001-.013.005-.021.005l-.506.017h-.017a.5.5 0 0 1-.016-.999l.506-.017c.018-.002.035.006.052.007A3.503 3.503 0 0 1 20.5 17c1.93 0 3.5 1.57 3.5 3.5zm-1 0c0-1.378-1.122-2.5-2.5-2.5S18 19.122 18 20.5s1.122 2.5 2.5 2.5 2.5-1.122 2.5-2.5z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/features/actions" + } + }, + { + "id": "gitlab", + "title": "GitLab", + "category": "engineering", + "aliases": [], + "domains": [ + "gitlab.com" + ], + "viewBox": 24, + "hex": "FC6D26", + "path": "m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://about.gitlab.com/press/press-kit/", + "guidelines": "https://about.gitlab.com/handbook/marketing/corporate-marketing/brand-activation/trademark-guidelines/" + } + }, + { + "id": "go", + "title": "Go", + "category": "language", + "aliases": [ + "golang" + ], + "domains": [ + "go.dev" + ], + "viewBox": 24, + "hex": "00ADD8", + "path": "M1.811 10.231c-.047 0-.058-.023-.035-.059l.246-.315c.023-.035.081-.058.128-.058h4.172c.046 0 .058.035.035.07l-.199.303c-.023.036-.082.07-.117.07zM.047 11.306c-.047 0-.059-.023-.035-.058l.245-.316c.023-.035.082-.058.129-.058h5.328c.047 0 .07.035.058.07l-.093.28c-.012.047-.058.07-.105.07zm2.828 1.075c-.047 0-.059-.035-.035-.07l.163-.292c.023-.035.07-.07.117-.07h2.337c.047 0 .07.035.07.082l-.023.28c0 .047-.047.082-.082.082zm12.129-2.36c-.736.187-1.239.327-1.963.514-.176.046-.187.058-.34-.117-.174-.199-.303-.327-.548-.444-.737-.362-1.45-.257-2.115.175-.795.514-1.204 1.274-1.192 2.22.011.935.654 1.706 1.577 1.835.795.105 1.46-.175 1.987-.77.105-.13.198-.27.315-.434H10.47c-.245 0-.304-.152-.222-.35.152-.362.432-.97.596-1.274a.315.315 0 01.292-.187h4.253c-.023.316-.023.631-.07.947a4.983 4.983 0 01-.958 2.29c-.841 1.11-1.94 1.8-3.33 1.986-1.145.152-2.209-.07-3.143-.77-.865-.655-1.356-1.52-1.484-2.595-.152-1.274.222-2.419.993-3.424.83-1.086 1.928-1.776 3.272-2.02 1.098-.2 2.15-.07 3.096.571.62.41 1.063.97 1.356 1.648.07.105.023.164-.117.2m3.868 6.461c-1.064-.024-2.034-.328-2.852-1.029a3.665 3.665 0 01-1.262-2.255c-.21-1.32.152-2.489.947-3.529.853-1.122 1.881-1.706 3.272-1.95 1.192-.21 2.314-.095 3.33.595.923.63 1.496 1.484 1.648 2.605.198 1.578-.257 2.863-1.344 3.962-.771.783-1.718 1.273-2.805 1.495-.315.06-.63.07-.934.106zm2.78-4.72c-.011-.153-.011-.27-.034-.387-.21-1.157-1.274-1.81-2.384-1.554-1.087.245-1.788.935-2.045 2.033-.21.912.234 1.835 1.075 2.21.643.28 1.285.244 1.905-.07.923-.48 1.425-1.228 1.484-2.233z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://blog.golang.org/go-brand", + "guidelines": "https://blog.golang.org/go-brand" + } + }, + { + "id": "google-cloud", + "title": "Google Cloud", + "category": "cloud", + "aliases": [ + "gcp", + "googlecloud" + ], + "domains": [ + "cloud.google.com" + ], + "viewBox": 24, + "hex": "4285F4", + "path": "M12.19 2.38a9.344 9.344 0 0 0-9.234 6.893c.053-.02-.055.013 0 0-3.875 2.551-3.922 8.11-.247 10.941l.006-.007-.007.03a6.717 6.717 0 0 0 4.077 1.356h5.173l.03.03h5.192c6.687.053 9.376-8.605 3.835-12.35a9.365 9.365 0 0 0-2.821-4.552l-.043.043.006-.05A9.344 9.344 0 0 0 12.19 2.38zm-.358 4.146c1.244-.04 2.518.368 3.486 1.15a5.186 5.186 0 0 1 1.862 4.078v.518c3.53-.07 3.53 5.262 0 5.193h-5.193l-.008.009v-.04H6.785a2.59 2.59 0 0 1-1.067-.23h.001a2.597 2.597 0 1 1 3.437-3.437l3.013-3.012A6.747 6.747 0 0 0 8.11 8.24c.018-.01.04-.026.054-.023a5.186 5.186 0 0 1 3.67-1.69z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://cloud.google.com" + } + }, + { + "id": "google-gemini", + "title": "Google Gemini", + "category": "ai", + "aliases": [ + "gemini" + ], + "domains": [ + "gemini.google.com" + ], + "viewBox": 24, + "hex": "8E75B2", + "path": "M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://gemini.google.com" + } + }, + { + "id": "grafana", + "title": "Grafana", + "category": "engineering", + "aliases": [], + "domains": [ + "grafana.com" + ], + "viewBox": 24, + "hex": "F46800", + "path": "M23.02 10.59a8.578 8.578 0 0 0-.862-3.034 8.911 8.911 0 0 0-1.789-2.445c.337-1.342-.413-2.505-.413-2.505-1.292-.08-2.113.4-2.416.62-.052-.02-.102-.044-.154-.064-.22-.089-.446-.172-.677-.247-.231-.073-.47-.14-.711-.197a9.867 9.867 0 0 0-.875-.161C14.557.753 12.94 0 12.94 0c-1.804 1.145-2.147 2.744-2.147 2.744l-.018.093c-.098.029-.2.057-.298.088-.138.042-.275.094-.413.143-.138.055-.275.107-.41.166a8.869 8.869 0 0 0-1.557.87l-.063-.029c-2.497-.955-4.716.195-4.716.195-.203 2.658.996 4.33 1.235 4.636a11.608 11.608 0 0 0-.607 2.635C1.636 12.677.953 15.014.953 15.014c1.926 2.214 4.171 2.351 4.171 2.351.003-.002.006-.002.006-.005.285.509.615.994.986 1.446.156.19.32.371.488.548-.704 2.009.099 3.68.099 3.68 2.144.08 3.553-.937 3.849-1.173a9.784 9.784 0 0 0 3.164.501h.08l.055-.003.107-.002.103-.005.003.002c1.01 1.44 2.788 1.646 2.788 1.646 1.264-1.332 1.337-2.653 1.337-2.94v-.058c0-.02-.003-.039-.003-.06.265-.187.52-.387.758-.6a7.875 7.875 0 0 0 1.415-1.7c1.43.083 2.437-.885 2.437-.885-.236-1.49-1.085-2.216-1.264-2.354l-.018-.013-.016-.013a.217.217 0 0 1-.031-.02c.008-.092.016-.18.02-.27.011-.162.016-.323.016-.48v-.253l-.005-.098-.008-.135a1.891 1.891 0 0 0-.01-.13c-.003-.042-.008-.083-.013-.125l-.016-.124-.018-.122a6.215 6.215 0 0 0-2.032-3.73 6.015 6.015 0 0 0-3.222-1.46 6.292 6.292 0 0 0-.85-.048l-.107.002h-.063l-.044.003-.104.008a4.777 4.777 0 0 0-3.335 1.695c-.332.4-.592.84-.768 1.297a4.594 4.594 0 0 0-.312 1.817l.003.091c.005.055.007.11.013.164a3.615 3.615 0 0 0 .698 1.82 3.53 3.53 0 0 0 1.827 1.282c.33.098.66.14.971.137.039 0 .078 0 .114-.002l.063-.003c.02 0 .041-.003.062-.003.034-.002.065-.007.099-.01.007 0 .018-.003.028-.003l.031-.005.06-.008a1.18 1.18 0 0 0 .112-.02c.036-.008.072-.013.109-.024a2.634 2.634 0 0 0 .914-.415c.028-.02.056-.041.085-.065a.248.248 0 0 0 .039-.35.244.244 0 0 0-.309-.06l-.078.042c-.09.044-.184.083-.283.116a2.476 2.476 0 0 1-.475.096c-.028.003-.054.006-.083.006l-.083.002c-.026 0-.054 0-.08-.002l-.102-.006h-.012l-.024.006c-.016-.003-.031-.003-.044-.006-.031-.002-.06-.007-.091-.01a2.59 2.59 0 0 1-.724-.213 2.557 2.557 0 0 1-.667-.438 2.52 2.52 0 0 1-.805-1.475 2.306 2.306 0 0 1-.029-.444l.006-.122v-.023l.002-.031c.003-.021.003-.04.005-.06a3.163 3.163 0 0 1 1.352-2.29 3.12 3.12 0 0 1 .937-.43 2.946 2.946 0 0 1 .776-.101h.06l.07.002.045.003h.026l.07.005a4.041 4.041 0 0 1 1.635.49 3.94 3.94 0 0 1 1.602 1.662 3.77 3.77 0 0 1 .397 1.414l.005.076.003.075c.002.026.002.05.002.075 0 .024.003.052 0 .07v.065l-.002.073-.008.174a6.195 6.195 0 0 1-.08.639 5.1 5.1 0 0 1-.267.927 5.31 5.31 0 0 1-.624 1.13 5.052 5.052 0 0 1-3.237 2.014 4.82 4.82 0 0 1-.649.066l-.039.003h-.287a6.607 6.607 0 0 1-1.716-.265 6.776 6.776 0 0 1-3.4-2.274 6.75 6.75 0 0 1-.746-1.15 6.616 6.616 0 0 1-.714-2.596l-.005-.083-.002-.02v-.056l-.003-.073v-.096l-.003-.104v-.07l.003-.163c.008-.22.026-.45.054-.678a8.707 8.707 0 0 1 .28-1.355c.128-.444.286-.872.473-1.277a7.04 7.04 0 0 1 1.456-2.1 5.925 5.925 0 0 1 .953-.763c.169-.111.343-.213.524-.306.089-.05.182-.091.273-.135.047-.02.093-.042.138-.062a7.177 7.177 0 0 1 .714-.267l.145-.045c.049-.015.098-.026.148-.041.098-.029.197-.052.296-.076.049-.013.1-.02.15-.033l.15-.032.151-.028.076-.013.075-.01.153-.024c.057-.01.114-.013.171-.023l.169-.021c.036-.003.073-.008.106-.01l.073-.008.036-.003.042-.002c.057-.003.114-.008.171-.01l.086-.006h.023l.037-.003.145-.007a7.999 7.999 0 0 1 1.708.125 7.917 7.917 0 0 1 2.048.68 8.253 8.253 0 0 1 1.672 1.09l.09.077.089.078c.06.052.114.107.171.159.057.052.112.106.166.16.052.055.107.107.159.164a8.671 8.671 0 0 1 1.41 1.978c.012.026.028.052.04.078l.04.078.075.156c.023.051.05.1.07.153l.065.15a8.848 8.848 0 0 1 .45 1.34.19.19 0 0 0 .201.142.186.186 0 0 0 .172-.184c.01-.246.002-.532-.024-.856z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://grafana.com" + } + }, + { + "id": "helm", + "title": "Helm", + "category": "engineering", + "aliases": [], + "domains": [ + "helm.sh" + ], + "viewBox": 24, + "hex": "0F1689", + "path": "M12.337 0c-.475 0-.861 1.016-.861 2.269 0 .527.069 1.011.183 1.396a8.514 8.514 0 0 0-3.961 1.22 5.229 5.229 0 0 0-.595-1.093c-.606-.866-1.34-1.436-1.79-1.43a.381.381 0 0 0-.217.066c-.39.273-.123 1.326.596 2.353.267.381.559.705.84.948a8.683 8.683 0 0 0-1.528 1.716h1.734a7.179 7.179 0 0 1 5.381-2.421 7.18 7.18 0 0 1 5.382 2.42h1.733a8.687 8.687 0 0 0-1.32-1.53c.35-.249.735-.643 1.078-1.133.719-1.027.986-2.08.596-2.353a.382.382 0 0 0-.217-.065c-.45-.007-1.184.563-1.79 1.43a4.897 4.897 0 0 0-.676 1.325 8.52 8.52 0 0 0-3.899-1.42c.12-.39.193-.887.193-1.429 0-1.253-.386-2.269-.862-2.269zM1.624 9.443v5.162h1.358v-1.968h1.64v1.968h1.357V9.443H4.62v1.838H2.98V9.443zm5.912 0v5.162h3.21v-1.108H8.893v-.95h1.64v-1.142h-1.64v-.84h1.853V9.443zm4.698 0v5.162h3.218v-1.362h-1.86v-3.8zm4.706 0v5.162h1.364v-2.643l1.357 1.225 1.35-1.232v2.65h1.365V9.443h-.614l-2.1 1.914-2.109-1.914zm-11.82 7.28a8.688 8.688 0 0 0 1.412 1.548 5.206 5.206 0 0 0-.841.948c-.719 1.027-.985 2.08-.596 2.353.39.273 1.289-.338 2.007-1.364a5.23 5.23 0 0 0 .595-1.092 8.514 8.514 0 0 0 3.961 1.219 5.01 5.01 0 0 0-.183 1.396c0 1.253.386 2.269.861 2.269.476 0 .862-1.016.862-2.269 0-.542-.072-1.04-.193-1.43a8.52 8.52 0 0 0 3.9-1.42c.121.4.352.865.675 1.327.719 1.026 1.617 1.637 2.007 1.364.39-.273.123-1.326-.596-2.353-.343-.49-.727-.885-1.077-1.135a8.69 8.69 0 0 0 1.202-1.36h-1.771a7.174 7.174 0 0 1-5.227 2.252 7.174 7.174 0 0 1-5.226-2.252z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://helm.sh" + } + }, + { + "id": "hubspot", + "title": "HubSpot", + "category": "business", + "aliases": [], + "domains": [ + "hubspot.com" + ], + "viewBox": 24, + "hex": "FF7A59", + "path": "M18.164 7.93V5.084a2.198 2.198 0 001.267-1.978v-.067A2.2 2.2 0 0017.238.845h-.067a2.2 2.2 0 00-2.193 2.193v.067a2.196 2.196 0 001.252 1.973l.013.006v2.852a6.22 6.22 0 00-2.969 1.31l.012-.01-7.828-6.095A2.497 2.497 0 104.3 4.656l-.012.006 7.697 5.991a6.176 6.176 0 00-1.038 3.446c0 1.343.425 2.588 1.147 3.607l-.013-.02-2.342 2.343a1.968 1.968 0 00-.58-.095h-.002a2.033 2.033 0 102.033 2.033 1.978 1.978 0 00-.1-.595l.005.014 2.317-2.317a6.247 6.247 0 104.782-11.134l-.036-.005zm-.964 9.378a3.206 3.206 0 113.215-3.207v.002a3.206 3.206 0 01-3.207 3.207z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.hubspot.com/style-guide", + "guidelines": "https://www.hubspot.com/style-guide" + } + }, + { + "id": "hugging-face", + "title": "Hugging Face", + "category": "ai", + "aliases": [ + "huggingface" + ], + "domains": [ + "huggingface.co" + ], + "viewBox": 24, + "hex": "FFD21E", + "path": "M12.025 1.13c-5.77 0-10.449 4.647-10.449 10.378 0 1.112.178 2.181.503 3.185.064-.222.203-.444.416-.577a.96.96 0 0 1 .524-.15c.293 0 .584.124.84.284.278.173.48.408.71.694.226.282.458.611.684.951v-.014c.017-.324.106-.622.264-.874s.403-.487.762-.543c.3-.047.596.06.787.203s.31.313.4.467c.15.257.212.468.233.542.01.026.653 1.552 1.657 2.54.616.605 1.01 1.223 1.082 1.912.055.537-.096 1.059-.38 1.572.637.121 1.294.187 1.967.187.657 0 1.298-.063 1.921-.178-.287-.517-.44-1.041-.384-1.581.07-.69.465-1.307 1.081-1.913 1.004-.987 1.647-2.513 1.657-2.539.021-.074.083-.285.233-.542.09-.154.208-.323.4-.467a1.08 1.08 0 0 1 .787-.203c.359.056.604.29.762.543s.247.55.265.874v.015c.225-.34.457-.67.683-.952.23-.286.432-.52.71-.694.257-.16.547-.284.84-.285a.97.97 0 0 1 .524.151c.228.143.373.388.43.625l.006.04a10.3 10.3 0 0 0 .534-3.273c0-5.731-4.678-10.378-10.449-10.378M8.327 6.583a1.5 1.5 0 0 1 .713.174 1.487 1.487 0 0 1 .617 2.013c-.183.343-.762-.214-1.102-.094-.38.134-.532.914-.917.71a1.487 1.487 0 0 1 .69-2.803m7.486 0a1.487 1.487 0 0 1 .689 2.803c-.385.204-.536-.576-.916-.71-.34-.12-.92.437-1.103.094a1.487 1.487 0 0 1 .617-2.013 1.5 1.5 0 0 1 .713-.174m-10.68 1.55a.96.96 0 1 1 0 1.921.96.96 0 0 1 0-1.92m13.838 0a.96.96 0 1 1 0 1.92.96.96 0 0 1 0-1.92M8.489 11.458c.588.01 1.965 1.157 3.572 1.164 1.607-.007 2.984-1.155 3.572-1.164.196-.003.305.12.305.454 0 .886-.424 2.328-1.563 3.202-.22-.756-1.396-1.366-1.63-1.32q-.011.001-.02.006l-.044.026-.01.008-.03.024q-.018.017-.035.036l-.032.04a1 1 0 0 0-.058.09l-.014.025q-.049.088-.11.19a1 1 0 0 1-.083.116 1.2 1.2 0 0 1-.173.18q-.035.029-.075.058a1.3 1.3 0 0 1-.251-.243 1 1 0 0 1-.076-.107c-.124-.193-.177-.363-.337-.444-.034-.016-.104-.008-.2.022q-.094.03-.216.087-.06.028-.125.063l-.13.074q-.067.04-.136.086a3 3 0 0 0-.135.096 3 3 0 0 0-.26.219 2 2 0 0 0-.12.121 2 2 0 0 0-.106.128l-.002.002a2 2 0 0 0-.09.132l-.001.001a1.2 1.2 0 0 0-.105.212q-.013.036-.024.073c-1.139-.875-1.563-2.317-1.563-3.203 0-.334.109-.457.305-.454m.836 10.354c.824-1.19.766-2.082-.365-3.194-1.13-1.112-1.789-2.738-1.789-2.738s-.246-.945-.806-.858-.97 1.499.202 2.362c1.173.864-.233 1.45-.685.64-.45-.812-1.683-2.896-2.322-3.295s-1.089-.175-.938.647 2.822 2.813 2.562 3.244-1.176-.506-1.176-.506-2.866-2.567-3.49-1.898.473 1.23 2.037 2.16c1.564.932 1.686 1.178 1.464 1.53s-3.675-2.511-4-1.297c-.323 1.214 3.524 1.567 3.287 2.405-.238.839-2.71-1.587-3.216-.642-.506.946 3.49 2.056 3.522 2.064 1.29.33 4.568 1.028 5.713-.624m5.349 0c-.824-1.19-.766-2.082.365-3.194 1.13-1.112 1.789-2.738 1.789-2.738s.246-.945.806-.858.97 1.499-.202 2.362c-1.173.864.233 1.45.685.64.451-.812 1.683-2.896 2.322-3.295s1.089-.175.938.647-2.822 2.813-2.562 3.244 1.176-.506 1.176-.506 2.866-2.567 3.49-1.898-.473 1.23-2.037 2.16c-1.564.932-1.686 1.178-1.464 1.53s3.675-2.511 4-1.297c.323 1.214-3.524 1.567-3.287 2.405.238.839 2.71-1.587 3.216-.642.506.946-3.49 2.056-3.522 2.064-1.29.33-4.568 1.028-5.713-.624", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://huggingface.co/brand", + "guidelines": "https://huggingface.co/brand" + } + }, + { + "id": "influxdb", + "title": "InfluxDB", + "category": "data", + "aliases": [ + "influx-db" + ], + "domains": [ + "influxdata.com" + ], + "viewBox": 24, + "hex": "22ADF6", + "path": "M23.778 14.482l-2.287-9.959c-.13-.545-.624-1.09-1.169-1.248L9.87.051C9.74 0 9.584 0 9.426 0c-.443 0-.909.18-1.222.443L.716 7.412C.3 7.776.092 8.504.222 9.024l2.445 10.662c.13.545.624 1.092 1.169 1.248l9.775 3.015c.13.051.285.051.443.051.443 0 .91-.18 1.223-.443l8.007-7.435c.418-.39.624-1.092.494-1.64zM10.962 2.417l7.175 2.21c.285.08.285.21 0 .286l-3.77.858c-.285.08-.674-.05-.883-.26l-2.626-2.834c-.235-.232-.184-.336.104-.26zm4.47 12.872c.079.286-.105.444-.39.365l-7.748-2.392c-.285-.079-.338-.313-.13-.52l5.93-5.514c.209-.209.443-.13.52.156zM2.667 8.267l6.293-5.85c.21-.209.545-.18.754.025L12.86 5.85c.209.21.18.545-.026.754l-6.293 5.85c-.21.21-.545.181-.754-.025L2.64 9.024a.536.536 0 01.026-.757zm1.536 9.284L2.54 10.244c-.08-.285.05-.34.234-.13L5.4 12.949c.209.209.285.624.209.909L4.462 17.55c-.079.285-.208.285-.26 0zm9.202 4.264l-8.217-2.522a.547.547 0 01-.364-.675l1.378-4.421a.547.547 0 01.675-.365l8.216 2.522c.285.079.443.39.364.675L14.08 21.45a.553.553 0 01-.674.365zm7.279-5.98L15.2 20.93c-.209.209-.31.13-.234-.155l1.144-3.694c.079-.285.39-.573.674-.624l3.77-.858c.288-.076.339.054.13.234zm.598-1.09l-4.523 1.039a.534.534 0 01-.65-.39l-1.922-8.372a.534.534 0 01.39-.65L19.1 5.335a.534.534 0 01.649.39l1.923 8.371c.079.31-.102.596-.39.65Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://influxdata.github.io/branding/logo/downloads/", + "guidelines": "https://influxdata.github.io/branding/logo/usage/" + } + }, + { + "id": "instagram", + "title": "Instagram", + "category": "channel", + "aliases": [], + "domains": [ + "instagram.com" + ], + "viewBox": 24, + "hex": "FF0069", + "path": "M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://about.meta.com/brand/resources/instagram", + "guidelines": "https://about.meta.com/brand/resources/instagram" + } + }, + { + "id": "intercom", + "title": "Intercom", + "category": "business", + "aliases": [], + "domains": [ + "intercom.com" + ], + "viewBox": 24, + "hex": "6AFDEF", + "path": "M21 0H3C1.343 0 0 1.343 0 3v18c0 1.658 1.343 3 3 3h18c1.658 0 3-1.342 3-3V3c0-1.657-1.342-3-3-3zm-5.801 4.399c0-.44.36-.8.802-.8.44 0 .8.36.8.8v10.688c0 .442-.36.801-.8.801-.443 0-.802-.359-.802-.801V4.399zM11.2 3.994c0-.44.357-.799.8-.799s.8.359.8.799v11.602c0 .44-.357.8-.8.8s-.8-.36-.8-.8V3.994zm-4 .405c0-.44.359-.8.799-.8.443 0 .802.36.802.8v10.688c0 .442-.36.801-.802.801-.44 0-.799-.359-.799-.801V4.399zM3.199 6c0-.442.36-.8.802-.8.44 0 .799.358.799.8v7.195c0 .441-.359.8-.799.8-.443 0-.802-.36-.802-.8V6zM20.52 18.202c-.123.105-3.086 2.593-8.52 2.593-5.433 0-8.397-2.486-8.521-2.593-.335-.288-.375-.792-.086-1.128.285-.334.79-.375 1.125-.09.047.041 2.693 2.211 7.481 2.211 4.848 0 7.456-2.186 7.479-2.207.334-.289.839-.25 1.128.086.289.336.25.84-.086 1.128zm.281-5.007c0 .441-.36.8-.801.8-.441 0-.801-.36-.801-.8V6c0-.442.361-.8.801-.8.441 0 .801.357.801.8v7.195z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.intercom.com/press", + "guidelines": "https://www.intercom.com/press" + } + }, + { + "id": "javascript", + "title": "JavaScript", + "category": "language", + "aliases": [ + "js" + ], + "domains": [], + "viewBox": 24, + "hex": "F7DF1E", + "path": "M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/voodootikigod/logo.js/blob/1544bdeed6d618a6cfe4f0650d04ab8d9cfa76d9/js.svg", + "license": { + "type": "MIT", + "url": "https://spdx.org/licenses/MIT" + } + } + }, + { + "id": "jenkins", + "title": "Jenkins", + "category": "engineering", + "aliases": [], + "domains": [ + "jenkins.io" + ], + "viewBox": 24, + "hex": "D24939", + "path": "M2.872 24h-.975a3.866 3.866 0 01-.07-.197c-.215-.666-.594-1.49-.692-2.154-.146-.984.78-1.039 1.374-1.465.915-.66 1.635-1.025 2.627-1.62.295-.179 1.182-.624 1.281-.829.201-.408-.345-.982-.49-1.3-.225-.507-.345-.937-.376-1.435-.824-.13-1.455-.627-1.844-1.185-.63-.925-1.066-2.635-.525-3.936.045-.103.254-.305.285-.463.06-.308-.105-.72-.12-1.048-.06-1.692.284-3.15 1.425-3.66.463-1.84 2.113-2.453 3.673-3.367.58-.342 1.224-.562 1.89-.807 2.372-.877 6.027-.712 7.994.783.836.633 2.176 1.97 2.656 2.939 1.262 2.555 1.17 6.825.287 9.934-.12.421-.29 1.032-.533 1.533-.168.35-.689 1.05-.625 1.36.064.314 1.19 1.17 1.432 1.395.434.422 1.26.975 1.324 1.5.07.557-.248 1.336-.41 1.875-.217.721-.436 1.441-.654 2.131H2.87zm11.104-3.54c-.545-.3-1.361-.622-2.065-.757-.87-.164-.78 1.188-.75 1.994.03.643.36 1.316.51 1.744.076.197.09.41.256.449.3.068 1.29-.326 1.575-.479.6-.328 1.064-.844 1.574-1.189.016-.17.016-.34.03-.508a2.648 2.648 0 00-1.095-.277c.314-.15.75-.15 1.035-.332l.016-.193c-.496-.03-.69-.254-1.021-.436zm7.454 2.935a17.78 17.78 0 00.465-1.752c.06-.287.215-.918.178-1.176-.059-.459-.684-.799-1.004-1.086-.584-.525-.95-.975-1.56-1.469-.249.375-.78.615-.983.914 1.447-.689 1.71 2.625 1.141 3.69.09.329.391.45.514.735l-.086.166h1.29c.013 0 .03 0 .044.014zm-6.634-.012c-.05-.074-.1-.135-.15-.209l-.301.195h.45zm2.77 0c.008-.209.018-.404.03-.598-.53.029-.825-.48-1.196-.527-.324-.045-.6.361-1.02.195-.095.105-.183.227-.284.316.154.18.295.375.424.584h.815c.014-.164.135-.285.3-.285.165 0 .284.121.284.27h.66zm2.116 0c-.314-.479-.947-.898-1.68-.555l-.03.541h1.71zm-8.51 0l-.104-.344c-.225-.72-.36-1.26-.405-1.68-.914-.436-1.875-.87-2.654-1.426-.15-.105-1.109-1.35-1.23-1.305-1.739.676-3.359 1.86-4.814 2.984.256.557.48 1.141.69 1.74h8.505zm8.265-2.113c-.029-.512-.164-1.56-.48-1.74-.66-.39-1.846.78-2.34.943.045.15.135.271.15.48.285-.074.645-.029.898.092-.299.03-.629.03-.824.164-.074.195.016.48-.029.764.69.197 1.5.303 2.385.332.164-.227.225-.645.211-1.082zm-4.08-.36c-.044.375.046.51.12.943 1.26.391 1.034-1.74-.135-.959zM8.76 19.5c-.45.457 1.27 1.082 1.814 1.115 0-.29.165-.564.135-.77-.65-.118-1.502-.042-1.945-.347zm5.565.215c0 .043-.061.03-.068.064.58.451 1.014.545 1.802.51.354-.262.67-.563 1.043-.807-.855.074-1.931.607-2.774.23zm3.42-17.726c-1.606-.906-4.35-1.591-6.076-.731-1.38.692-3.27 1.84-3.899 3.292.6 1.402-.166 2.686-.226 4.109-.018.757.36 1.42.391 2.242-.2.338-.825.38-1.26.356-.146-.729-.4-1.549-1.155-1.63-1.064-.116-1.845.764-1.89 1.683-.06 1.08.833 2.864 2.085 2.745.488-.046.608-.54 1.139-.54.285.57-.445.75-.523 1.154-.016.105.06.511.104.705.233.944.744 2.16 1.245 2.88.635.9 1.884 1.051 3.229 1.141.24-.525 1.125-.48 1.706-.346-.691-.27-1.336-.945-1.875-1.529-.615-.676-1.23-1.41-1.261-2.28 1.155 1.604 2.1 3 4.2 3.704 1.59.525 3.45-.254 4.664-1.109.51-.359.811-.93 1.17-1.439 1.35-1.936 1.98-4.71 1.846-7.394-.06-1.111-.06-2.221-.436-2.955-.389-.781-1.695-1.471-2.475-.781-.15-.764.63-1.23 1.545-.96-.66-.854-1.336-1.858-2.266-2.384zM13.58 14.896c.615 1.544 2.724 1.363 4.505 1.323-.084.194-.256.435-.465.515-.57.232-2.145.408-2.937-.012-.506-.27-.824-.873-1.102-1.227-.137-.172-.795-.608-.012-.609zm.164-.87c.893.464 2.52.517 3.731.48.066.267.066.593.068.913-1.55.08-3.386-.304-3.794-1.395h-.005zm6.675-.586c-.473.9-1.145 1.897-2.539 1.928-.023-.284-.045-.735 0-.904 1.064-.103 1.727-.646 2.543-1.017zm-.649-.667c-1.02.66-2.154 1.375-3.824 1.21-.351-.31-.485-1-.14-1.458.181.313.06.885.57.97.944.165 2.038-.579 2.73-.84.42-.713-.046-.976-.42-1.433-.782-.93-1.83-2.1-1.802-3.51.314-.224.346.346.391.45.404.96 1.424 2.175 2.174 3 .18.21.48.39.51.524.092.39-.254.854-.209 1.11zm-13.439-.675c-.314-.184-.393-.99-.768-1.01-.535-.03-.438 1.05-.436 1.68-.37-.33-.435-1.365-.164-1.89-.308-.15-.445.164-.618.284.22-1.59 2.34-.734 1.99.96zM4.713 5.995c-.685.756-.54 2.174-.459 3.188 1.244-.785 2.898.06 2.883 1.394.595-.016.223-.744.115-1.215-.353-1.528.592-3.187.041-4.59-1.064.084-1.939.52-2.578 1.215zm9.12 1.113c.307.562.404 1.148.84 1.57.195.19.574.424.387.95-.045.121-.365.391-.551.45-.674.195-2.254.03-1.721-.81.563.015 1.314.36 1.732-.045-.314-.524-.885-1.53-.674-2.13zm6.198-.013h.068c.33.668.6 1.375 1.004 1.965-.27.628-2.053 1.19-2.023.057.39-.17 1.05-.035 1.395-.25-.193-.556-.48-1.006-.434-1.771zm-6.927-1.617c-1.422-.33-2.131.592-2.56 1.553-.384-.094-.231-.615-.135-.883.255-.701 1.28-1.633 2.119-1.506.359.057.848.386.576.834zM9.642 1.593c-1.56.44-3.56 1.574-4.2 2.974.495-.07.84-.321 1.33-.351.186-.016.428.074.641.015.424-.104.78-1.065 1.102-1.41.31-.345.685-.496.94-.81.167-.09.409-.074.42-.33-.073-.075-.15-.135-.232-.105v.017z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://get.jenkins.io/art/", + "guidelines": "https://www.jenkins.io/press/", + "license": { + "type": "CC-BY-SA-3.0", + "url": "https://spdx.org/licenses/CC-BY-SA-3.0" + } + } + }, + { + "id": "jira", + "title": "Jira", + "category": "collaboration", + "aliases": [], + "domains": [ + "atlassian.com" + ], + "viewBox": 24, + "hex": "0052CC", + "path": "M11.571 11.513H0a5.218 5.218 0 0 0 5.232 5.215h2.13v2.057A5.215 5.215 0 0 0 12.575 24V12.518a1.005 1.005 0 0 0-1.005-1.005zm5.723-5.756H5.736a5.215 5.215 0 0 0 5.215 5.214h2.129v2.058a5.218 5.218 0 0 0 5.215 5.214V6.758a1.001 1.001 0 0 0-1.001-1.001zM23.013 0H11.455a5.215 5.215 0 0 0 5.215 5.215h2.129v2.057A5.215 5.215 0 0 0 24 12.483V1.005A1.001 1.001 0 0 0 23.013 0Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://atlassian.design/resources/logo-library", + "guidelines": "https://atlassian.design/foundations/logos/" + } + }, + { + "id": "kubernetes", + "title": "Kubernetes", + "category": "engineering", + "aliases": [ + "k8s" + ], + "domains": [ + "kubernetes.io" + ], + "viewBox": 24, + "hex": "326CE5", + "path": "M10.204 14.35l.007.01-.999 2.413a5.171 5.171 0 0 1-2.075-2.597l2.578-.437.004.005a.44.44 0 0 1 .484.606zm-.833-2.129a.44.44 0 0 0 .173-.756l.002-.011L7.585 9.7a5.143 5.143 0 0 0-.73 3.255l2.514-.725.002-.009zm1.145-1.98a.44.44 0 0 0 .699-.337l.01-.005.15-2.62a5.144 5.144 0 0 0-3.01 1.442l2.147 1.523.004-.002zm.76 2.75l.723.349.722-.347.18-.78-.5-.623h-.804l-.5.623.179.779zm1.5-3.095a.44.44 0 0 0 .7.336l.008.003 2.134-1.513a5.188 5.188 0 0 0-2.992-1.442l.148 2.615.002.001zm10.876 5.97l-5.773 7.181a1.6 1.6 0 0 1-1.248.594l-9.261.003a1.6 1.6 0 0 1-1.247-.596l-5.776-7.18a1.583 1.583 0 0 1-.307-1.34L2.1 5.573c.108-.47.425-.864.863-1.073L11.305.513a1.606 1.606 0 0 1 1.385 0l8.345 3.985c.438.209.755.604.863 1.073l2.062 8.955c.108.47-.005.963-.308 1.34zm-3.289-2.057c-.042-.01-.103-.026-.145-.034-.174-.033-.315-.025-.479-.038-.35-.037-.638-.067-.895-.148-.105-.04-.18-.165-.216-.216l-.201-.059a6.45 6.45 0 0 0-.105-2.332 6.465 6.465 0 0 0-.936-2.163c.052-.047.15-.133.177-.159.008-.09.001-.183.094-.282.197-.185.444-.338.743-.522.142-.084.273-.137.415-.242.032-.024.076-.062.11-.089.24-.191.295-.52.123-.736-.172-.216-.506-.236-.745-.045-.034.027-.08.062-.111.088-.134.116-.217.23-.33.35-.246.25-.45.458-.673.609-.097.056-.239.037-.303.033l-.19.135a6.545 6.545 0 0 0-4.146-2.003l-.012-.223c-.065-.062-.143-.115-.163-.25-.022-.268.015-.557.057-.905.023-.163.061-.298.068-.475.001-.04-.001-.099-.001-.142 0-.306-.224-.555-.5-.555-.275 0-.499.249-.499.555l.001.014c0 .041-.002.092 0 .128.006.177.044.312.067.475.042.348.078.637.056.906a.545.545 0 0 1-.162.258l-.012.211a6.424 6.424 0 0 0-4.166 2.003 8.373 8.373 0 0 1-.18-.128c-.09.012-.18.04-.297-.029-.223-.15-.427-.358-.673-.608-.113-.12-.195-.234-.329-.349-.03-.026-.077-.062-.111-.088a.594.594 0 0 0-.348-.132.481.481 0 0 0-.398.176c-.172.216-.117.546.123.737l.007.005.104.083c.142.105.272.159.414.242.299.185.546.338.743.522.076.082.09.226.1.288l.16.143a6.462 6.462 0 0 0-1.02 4.506l-.208.06c-.055.072-.133.184-.215.217-.257.081-.546.11-.895.147-.164.014-.305.006-.48.039-.037.007-.09.02-.133.03l-.004.002-.007.002c-.295.071-.484.342-.423.608.061.267.349.429.645.365l.007-.001.01-.003.129-.029c.17-.046.294-.113.448-.172.33-.118.604-.217.87-.256.112-.009.23.069.288.101l.217-.037a6.5 6.5 0 0 0 2.88 3.596l-.09.218c.033.084.069.199.044.282-.097.252-.263.517-.452.813-.091.136-.185.242-.268.399-.02.037-.045.095-.064.134-.128.275-.034.591.213.71.248.12.556-.007.69-.282v-.002c.02-.039.046-.09.062-.127.07-.162.094-.301.144-.458.132-.332.205-.68.387-.897.05-.06.13-.082.215-.105l.113-.205a6.453 6.453 0 0 0 4.609.012l.106.192c.086.028.18.042.256.155.136.232.229.507.342.84.05.156.074.295.145.457.016.037.043.09.062.129.133.276.442.402.69.282.247-.118.341-.435.213-.71-.02-.039-.045-.096-.065-.134-.083-.156-.177-.261-.268-.398-.19-.296-.346-.541-.443-.793-.04-.13.007-.21.038-.294-.018-.022-.059-.144-.083-.202a6.499 6.499 0 0 0 2.88-3.622c.064.01.176.03.213.038.075-.05.144-.114.28-.104.266.039.54.138.87.256.154.06.277.128.448.173.036.01.088.019.13.028l.009.003.007.001c.297.064.584-.098.645-.365.06-.266-.128-.537-.423-.608zM16.4 9.701l-1.95 1.746v.005a.44.44 0 0 0 .173.757l.003.01 2.526.728a5.199 5.199 0 0 0-.108-1.674A5.208 5.208 0 0 0 16.4 9.7zm-4.013 5.325a.437.437 0 0 0-.404-.232.44.44 0 0 0-.372.233h-.002l-1.268 2.292a5.164 5.164 0 0 0 3.326.003l-1.27-2.296h-.01zm1.888-1.293a.44.44 0 0 0-.27.036.44.44 0 0 0-.214.572l-.003.004 1.01 2.438a5.15 5.15 0 0 0 2.081-2.615l-2.6-.44-.004.005z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/kubernetes/kubernetes/tree/cac53883f4714452f3084a22e4be20d042a9df33/logo" + } + }, + { + "id": "linear", + "title": "Linear", + "category": "collaboration", + "aliases": [], + "domains": [ + "linear.app" + ], + "viewBox": 24, + "hex": "5E6AD2", + "path": "M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://linear.app" + } + }, + { + "id": "mariadb", + "title": "MariaDB", + "category": "data", + "aliases": [ + "maria-db" + ], + "domains": [ + "mariadb.org" + ], + "viewBox": 24, + "hex": "003545", + "path": "M23.157 4.412c-.676.284-.79.31-1.673.372-.65.045-.757.057-1.212.209-.75.246-1.395.75-2.02 1.59-.296.398-1.249 1.913-1.249 1.988 0 .057-.65.998-.915 1.32-.574.713-1.08 1.079-2.14 1.59-.77.36-1.224.524-4.102 1.477-1.073.353-2.133.738-2.367.864-.852.449-1.515 1.036-2.203 1.938-1.003 1.32-.972 1.313-3.042.947a12.264 12.264 0 00-.675-.063c-.644-.05-1.023.044-1.332.334L0 17.193l.177.088c.094.05.353.234.561.398.215.17.461.347.55.391.088.044.17.088.183.101.012.013-.089.17-.228.353-.435.581-.593.871-.574 1.048.019.164.032.17.43.17.517-.006.826-.056 1.261-.208.65-.233 2.058-.94 2.784-1.4.776-.5 1.717-.998 1.956-1.042.082-.02.354-.07.594-.114.58-.107 1.464-.095 2.587.05.108.013.373.045.6.064.227.025.43.057.454.076.026.012.474.037.998.056.934.026 1.104.007 1.3-.189.126-.133.385-.631.498-.985.209-.643.417-.921.366-.492-.113.966-.322 1.692-.713 2.411-.259.499-.663 1.092-.934 1.395-.322.347-.315.36.088.315.619-.063 1.471-.397 2.096-.82.827-.562 1.647-1.691 2.19-3.03.107-.27.22-.22.183.083-.013.094-.038.315-.057.498l-.031.328.353-.202c.833-.48 1.414-1.262 2.127-2.884.227-.518.877-2.922 1.073-3.976a9.64 9.64 0 01.271-1.042c.127-.429.196-.555.48-.858.183-.19.625-.555.978-.808.72-.505.953-.75 1.187-1.205.208-.417.284-1.13.132-1.357-.132-.202-.284-.196-.763.006Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://mariadb.com/about-us/logos/", + "guidelines": "https://mariadb.com/about-us/logos/" + } + }, + { + "id": "meta", + "title": "Meta", + "category": "ai", + "aliases": [ + "llama" + ], + "domains": [ + "meta.com" + ], + "viewBox": 24, + "hex": "0467DF", + "path": "M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.meta.com", + "guidelines": "https://www.facebook.com/brand/resources/meta/company-brand" + } + }, + { + "id": "miro", + "title": "Miro", + "category": "collaboration", + "aliases": [], + "domains": [ + "miro.com" + ], + "viewBox": 24, + "hex": "050038", + "path": "M17.392 0H13.9L17 4.808 10.444 0H6.949l3.102 6.3L3.494 0H0l3.05 8.131L0 24h3.494L10.05 6.985 6.949 24h3.494L17 5.494 13.899 24h3.493L24 3.672 17.392 0z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://miro.com" + } + }, + { + "id": "mistral-ai", + "title": "Mistral AI", + "category": "ai", + "aliases": [ + "mistral" + ], + "domains": [ + "mistral.ai" + ], + "viewBox": 24, + "hex": "FA520F", + "path": "M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://chat.mistral.ai" + } + }, + { + "id": "mongodb", + "title": "MongoDB", + "category": "data", + "aliases": [ + "mongo" + ], + "domains": [ + "mongodb.com" + ], + "viewBox": 24, + "hex": "47A248", + "path": "M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115-.28-.394-.53-.954-.735-1.44-.036.495-.055.685-.523 1.184-.723.566-4.438 3.682-4.74 10.02-.282 5.912 4.27 9.435 4.888 9.884l.07.05A73.49 73.49 0 0111.91 24h.481c.114-1.032.284-2.056.51-3.07.417-.296.604-.463.85-.693a11.342 11.342 0 003.639-8.464c.01-.814-.103-1.662-.197-2.218zm-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695-.381-.045-.765-1.76-.765-2.405z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.mongodb.com/pressroom" + } + }, + { + "id": "mysql", + "title": "MySQL", + "category": "data", + "aliases": [], + "domains": [ + "mysql.com" + ], + "viewBox": 24, + "hex": "4479A1", + "path": "M16.405 5.501c-.115 0-.193.014-.274.033v.013h.014c.054.104.146.18.214.273.054.107.1.214.154.32l.014-.015c.094-.066.14-.172.14-.333-.04-.047-.046-.094-.08-.14-.04-.067-.126-.1-.18-.153zM5.77 18.695h-.927a50.854 50.854 0 00-.27-4.41h-.008l-1.41 4.41H2.45l-1.4-4.41h-.01a72.892 72.892 0 00-.195 4.41H0c.055-1.966.192-3.81.41-5.53h1.15l1.335 4.064h.008l1.347-4.064h1.095c.242 2.015.384 3.86.428 5.53zm4.017-4.08c-.378 2.045-.876 3.533-1.492 4.46-.482.716-1.01 1.073-1.583 1.073-.153 0-.34-.046-.566-.138v-.494c.11.017.24.026.386.026.268 0 .483-.075.647-.222.197-.18.295-.382.295-.605 0-.155-.077-.47-.23-.944L6.23 14.615h.91l.727 2.36c.164.536.233.91.205 1.123.4-1.064.678-2.227.835-3.483zm12.325 4.08h-2.63v-5.53h.885v4.85h1.745zm-3.32.135l-1.016-.5c.09-.076.177-.158.255-.25.433-.506.648-1.258.648-2.253 0-1.83-.718-2.746-2.155-2.746-.704 0-1.254.232-1.65.697-.43.508-.646 1.256-.646 2.245 0 .972.19 1.686.574 2.14.35.41.877.615 1.583.615.264 0 .506-.033.725-.098l1.325.772.36-.622zM15.5 17.588c-.225-.36-.337-.94-.337-1.736 0-1.393.424-2.09 1.27-2.09.443 0 .77.167.977.5.224.362.336.936.336 1.723 0 1.404-.424 2.108-1.27 2.108-.445 0-.77-.167-.978-.5zm-1.658-.425c0 .47-.172.856-.516 1.156-.344.3-.803.45-1.384.45-.543 0-1.064-.172-1.573-.515l.237-.476c.438.22.833.328 1.19.328.332 0 .593-.073.783-.22a.754.754 0 00.3-.615c0-.33-.23-.61-.648-.845-.388-.213-1.163-.657-1.163-.657-.422-.307-.632-.636-.632-1.177 0-.45.157-.81.47-1.085.315-.278.72-.415 1.22-.415.512 0 .98.136 1.4.41l-.213.476a2.726 2.726 0 00-1.064-.23c-.283 0-.502.068-.654.206a.685.685 0 00-.248.524c0 .328.234.61.666.85.393.215 1.187.67 1.187.67.433.305.648.63.648 1.168zm9.382-5.852c-.535-.014-.95.04-1.297.188-.1.04-.26.04-.274.167.055.053.063.14.11.214.08.134.218.313.346.407.14.11.28.216.427.31.26.16.555.255.81.416.145.094.293.213.44.313.073.05.12.14.214.172v-.02c-.046-.06-.06-.147-.105-.214-.067-.067-.134-.127-.2-.193a3.223 3.223 0 00-.695-.675c-.214-.146-.682-.35-.77-.595l-.013-.014c.146-.013.32-.066.46-.106.227-.06.435-.047.67-.106.106-.027.213-.06.32-.094v-.06c-.12-.12-.21-.283-.334-.395a8.867 8.867 0 00-1.104-.823c-.21-.134-.476-.22-.697-.334-.08-.04-.214-.06-.26-.127-.12-.146-.19-.34-.275-.514a17.69 17.69 0 01-.547-1.163c-.12-.262-.193-.523-.34-.763-.69-1.137-1.437-1.826-2.586-2.5-.247-.14-.543-.2-.856-.274-.167-.008-.334-.02-.5-.027-.11-.047-.216-.174-.31-.235-.38-.24-1.364-.76-1.644-.072-.18.434.267.862.422 1.082.115.153.26.328.34.5.047.116.06.235.107.356.106.294.207.622.347.897.073.14.153.287.247.413.054.073.146.107.167.227-.094.136-.1.334-.154.5-.24.757-.146 1.693.194 2.25.107.166.362.534.703.393.3-.12.234-.5.32-.835.02-.08.007-.133.048-.187v.015c.094.188.188.367.274.555.206.328.566.668.867.895.16.12.287.328.487.402v-.02h-.015c-.043-.058-.1-.086-.154-.133a3.445 3.445 0 01-.35-.4 8.76 8.76 0 01-.747-1.218c-.11-.21-.202-.436-.29-.643-.04-.08-.04-.2-.107-.24-.1.146-.247.273-.32.453-.127.288-.14.642-.188 1.01-.027.007-.014 0-.027.014-.214-.052-.287-.274-.367-.46-.2-.475-.233-1.238-.06-1.785.047-.14.247-.582.167-.716-.042-.127-.174-.2-.247-.303a2.478 2.478 0 01-.24-.427c-.16-.374-.24-.788-.414-1.162-.08-.173-.22-.354-.334-.513-.127-.18-.267-.307-.368-.52-.033-.073-.08-.194-.027-.274.014-.054.042-.075.094-.09.088-.072.335.022.422.062.247.1.455.194.662.334.094.066.195.193.315.226h.14c.214.047.455.014.655.073.355.114.675.28.962.46a5.953 5.953 0 012.085 2.286c.08.154.115.295.188.455.14.33.313.663.455.982.14.315.275.636.476.897.1.14.502.213.682.286.133.06.34.115.46.188.23.14.454.3.67.454.11.076.443.243.463.378z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.mysql.com/about/legal/logos.html", + "guidelines": "https://www.mysql.com/about/legal/logos.html" + } + }, + { + "id": "neon", + "title": "Neon", + "category": "cloud", + "aliases": [], + "domains": [ + "neon.tech" + ], + "viewBox": 24, + "hex": "34D59A", + "path": "M24 0V24l-9.365-8.045V24H0V0ZM2.942 21.087h8.751V9.563l9.365 8.204V2.919L2.942 2.914Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://neon.com/brand", + "guidelines": "https://neon.com/brand" + } + }, + { + "id": "netlify", + "title": "Netlify", + "category": "cloud", + "aliases": [], + "domains": [ + "netlify.com" + ], + "viewBox": 24, + "hex": "00C7B7", + "path": "M6.49 19.04h-.23L5.13 17.9v-.23l1.73-1.71h1.2l.15.15v1.2L6.5 19.04ZM5.13 6.31V6.1l1.13-1.13h.23L8.2 6.68v1.2l-.15.15h-1.2L5.13 6.31Zm9.96 9.09h-1.65l-.14-.13v-3.83c0-.68-.27-1.2-1.1-1.23-.42 0-.9 0-1.43.02l-.07.08v4.96l-.14.14H8.9l-.13-.14V8.73l.13-.14h3.7a2.6 2.6 0 0 1 2.61 2.6v4.08l-.13.14Zm-8.37-2.44H.14L0 12.82v-1.64l.14-.14h6.58l.14.14v1.64l-.14.14Zm17.14 0h-6.58l-.14-.14v-1.64l.14-.14h6.58l.14.14v1.64l-.14.14ZM11.05 6.55V1.64l.14-.14h1.65l.14.14v4.9l-.14.14h-1.65l-.14-.13Zm0 15.81v-4.9l.14-.14h1.65l.14.13v4.91l-.14.14h-1.65l-.14-.14Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.netlify.com/press/", + "guidelines": "https://www.netlify.com/press/" + } + }, + { + "id": "next-js", + "title": "Next.js", + "category": "framework", + "aliases": [ + "nextjs", + "next.js" + ], + "domains": [ + "nextjs.org" + ], + "viewBox": 24, + "hex": "000000", + "path": "M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://vercel.com/design/brands#next-js", + "guidelines": "https://vercel.com/design/brands#next-js" + } + }, + { + "id": "node-js", + "title": "Node.js", + "category": "framework", + "aliases": [ + "node", + "nodejs" + ], + "domains": [ + "nodejs.org" + ], + "viewBox": 24, + "hex": "5FA04E", + "path": "M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://nodejs.org/en/about/branding", + "guidelines": "https://nodejs.org/en/about/branding" + } + }, + { + "id": "notion", + "title": "Notion", + "category": "collaboration", + "aliases": [], + "domains": [ + "notion.so" + ], + "viewBox": 24, + "hex": "000000", + "path": "M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.notion.so" + } + }, + { + "id": "ollama", + "title": "Ollama", + "category": "ai", + "aliases": [], + "domains": [ + "ollama.com" + ], + "viewBox": 24, + "hex": "000000", + "path": "M16.361 10.26a.894.894 0 0 0-.558.47l-.072.148.001.207c0 .193.004.217.059.353.076.193.152.312.291.448.24.238.51.3.872.205a.86.86 0 0 0 .517-.436.752.752 0 0 0 .08-.498c-.064-.453-.33-.782-.724-.897a1.06 1.06 0 0 0-.466 0zm-9.203.005c-.305.096-.533.32-.65.639a1.187 1.187 0 0 0-.06.52c.057.309.31.59.598.667.362.095.632.033.872-.205.14-.136.215-.255.291-.448.055-.136.059-.16.059-.353l.001-.207-.072-.148a.894.894 0 0 0-.565-.472 1.02 1.02 0 0 0-.474.007Zm4.184 2c-.131.071-.223.25-.195.383.031.143.157.288.353.407.105.063.112.072.117.136.004.038-.01.146-.029.243-.02.094-.036.194-.036.222.002.074.07.195.143.253.064.052.076.054.255.059.164.005.198.001.264-.03.169-.082.212-.234.15-.525-.052-.243-.042-.28.087-.355.137-.08.281-.219.324-.314a.365.365 0 0 0-.175-.48.394.394 0 0 0-.181-.033c-.126 0-.207.03-.355.124l-.085.053-.053-.032c-.219-.13-.259-.145-.391-.143a.396.396 0 0 0-.193.032zm.39-2.195c-.373.036-.475.05-.654.086-.291.06-.68.195-.951.328-.94.46-1.589 1.226-1.787 2.114-.04.176-.045.234-.045.53 0 .294.005.357.043.524.264 1.16 1.332 2.017 2.714 2.173.3.033 1.596.033 1.896 0 1.11-.125 2.064-.727 2.493-1.571.114-.226.169-.372.22-.602.039-.167.044-.23.044-.523 0-.297-.005-.355-.045-.531-.288-1.29-1.539-2.304-3.072-2.497a6.873 6.873 0 0 0-.855-.031zm.645.937a3.283 3.283 0 0 1 1.44.514c.223.148.537.458.671.662.166.251.26.508.303.82.02.143.01.251-.043.482-.08.345-.332.705-.672.957a3.115 3.115 0 0 1-.689.348c-.382.122-.632.144-1.525.138-.582-.006-.686-.01-.853-.042-.57-.107-1.022-.334-1.35-.68-.264-.28-.385-.535-.45-.946-.03-.192.025-.509.137-.776.136-.326.488-.73.836-.963.403-.269.934-.46 1.422-.512.187-.02.586-.02.773-.002zm-5.503-11a1.653 1.653 0 0 0-.683.298C5.617.74 5.173 1.666 4.985 2.819c-.07.436-.119 1.04-.119 1.503 0 .544.064 1.24.155 1.721.02.107.031.202.023.208a8.12 8.12 0 0 1-.187.152 5.324 5.324 0 0 0-.949 1.02 5.49 5.49 0 0 0-.94 2.339 6.625 6.625 0 0 0-.023 1.357c.091.78.325 1.438.727 2.04l.13.195-.037.064c-.269.452-.498 1.105-.605 1.732-.084.496-.095.629-.095 1.294 0 .67.009.803.088 1.266.095.555.288 1.143.503 1.534.071.128.243.393.264.407.007.003-.014.067-.046.141a7.405 7.405 0 0 0-.548 1.873c-.062.417-.071.552-.071.991 0 .56.031.832.148 1.279L3.42 24h1.478l-.05-.091c-.297-.552-.325-1.575-.068-2.597.117-.472.25-.819.498-1.296l.148-.29v-.177c0-.165-.003-.184-.057-.293a.915.915 0 0 0-.194-.25 1.74 1.74 0 0 1-.385-.543c-.424-.92-.506-2.286-.208-3.451.124-.486.329-.918.544-1.154a.787.787 0 0 0 .223-.531c0-.195-.07-.355-.224-.522a3.136 3.136 0 0 1-.817-1.729c-.14-.96.114-2.005.69-2.834.563-.814 1.353-1.336 2.237-1.475.199-.033.57-.028.776.01.226.04.367.028.512-.041.179-.085.268-.19.374-.431.093-.215.165-.333.36-.576.234-.29.46-.489.822-.729.413-.27.884-.467 1.352-.561.17-.035.25-.04.569-.04.319 0 .398.005.569.04a4.07 4.07 0 0 1 1.914.997c.117.109.398.457.488.602.034.057.095.177.132.267.105.241.195.346.374.43.14.068.286.082.503.045.343-.058.607-.053.943.016 1.144.23 2.14 1.173 2.581 2.437.385 1.108.276 2.267-.296 3.153-.097.15-.193.27-.333.419-.301.322-.301.722-.001 1.053.493.539.801 1.866.708 3.036-.062.772-.26 1.463-.533 1.854a2.096 2.096 0 0 1-.224.258.916.916 0 0 0-.194.25c-.054.109-.057.128-.057.293v.178l.148.29c.248.476.38.823.498 1.295.253 1.008.231 2.01-.059 2.581a.845.845 0 0 0-.044.098c0 .006.329.009.732.009h.73l.02-.074.036-.134c.019-.076.057-.3.088-.516.029-.217.029-1.016 0-1.258-.11-.875-.295-1.57-.597-2.226-.032-.074-.053-.138-.046-.141.008-.005.057-.074.108-.152.376-.569.607-1.284.724-2.228.031-.26.031-1.378 0-1.628-.083-.645-.182-1.082-.348-1.525a6.083 6.083 0 0 0-.329-.7l-.038-.064.131-.194c.402-.604.636-1.262.727-2.04a6.625 6.625 0 0 0-.024-1.358 5.512 5.512 0 0 0-.939-2.339 5.325 5.325 0 0 0-.95-1.02 8.097 8.097 0 0 1-.186-.152.692.692 0 0 1 .023-.208c.208-1.087.201-2.443-.017-3.503-.19-.924-.535-1.658-.98-2.082-.354-.338-.716-.482-1.15-.455-.996.059-1.8 1.205-2.116 3.01a6.805 6.805 0 0 0-.097.726c0 .036-.007.066-.015.066a.96.96 0 0 1-.149-.078A4.857 4.857 0 0 0 12 3.03c-.832 0-1.687.243-2.456.698a.958.958 0 0 1-.148.078c-.008 0-.015-.03-.015-.066a6.71 6.71 0 0 0-.097-.725C8.997 1.392 8.337.319 7.46.048a2.096 2.096 0 0 0-.585-.041Zm.293 1.402c.248.197.523.759.682 1.388.03.113.06.244.069.292.007.047.026.152.041.233.067.365.098.76.102 1.24l.002.475-.12.175-.118.178h-.278c-.324 0-.646.041-.954.124l-.238.06c-.033.007-.038-.003-.057-.144a8.438 8.438 0 0 1 .016-2.323c.124-.788.413-1.501.696-1.711.067-.05.079-.049.157.013zm9.825-.012c.17.126.358.46.498.888.28.854.36 2.028.212 3.145-.019.14-.024.151-.057.144l-.238-.06a3.693 3.693 0 0 0-.954-.124h-.278l-.119-.178-.119-.175.002-.474c.004-.669.066-1.19.214-1.772.157-.623.434-1.185.68-1.382.078-.062.09-.063.159-.012z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/ollama/ollama/issues/2152#issuecomment-1905286922" + } + }, + { + "id": "openai", + "title": "OpenAI", + "category": "ai", + "aliases": [ + "chatgpt", + "gpt", + "codex" + ], + "domains": [ + "openai.com", + "chatgpt.com" + ], + "viewBox": 20, + "hex": "000000", + "path": "M11.248 18.25q-.825 0-1.568-.314a4.3 4.3 0 0 1-1.32-.874 4 4 0 0 1-1.304.214 4 4 0 0 1-2.046-.544 4.27 4.27 0 0 1-1.518-1.485 4 4 0 0 1-.56-2.095q0-.48.131-1.04A4.4 4.4 0 0 1 2.04 10.71a4.07 4.07 0 0 1 .017-3.4 4.2 4.2 0 0 1 1.056-1.418 3.8 3.8 0 0 1 1.6-.842 3.9 3.9 0 0 1 .76-1.683q.593-.759 1.451-1.188a4.04 4.04 0 0 1 1.832-.429q.825 0 1.567.313.742.314 1.32.875a4 4 0 0 1 1.304-.215q1.106 0 2.046.545a4.14 4.14 0 0 1 1.501 1.485q.578.941.578 2.095 0 .48-.132 1.04.66.61 1.023 1.419.363.792.363 1.666 0 .892-.38 1.717a4.3 4.3 0 0 1-1.072 1.435 3.8 3.8 0 0 1-1.584.825 3.8 3.8 0 0 1-.775 1.683 4.06 4.06 0 0 1-1.436 1.188 4.04 4.04 0 0 1-1.832.429m-4.076-2.062q.825 0 1.435-.347l3.103-1.782a.36.36 0 0 0 .164-.313v-1.42L7.881 14.62a.67.67 0 0 1-.726 0l-3.118-1.798a.5.5 0 0 1-.017.115v.198q0 .841.396 1.551.413.693 1.139 1.089a3.2 3.2 0 0 0 1.617.412m.165-2.69a.4.4 0 0 0 .181.05q.083 0 .165-.05l1.238-.71-3.977-2.31a.7.7 0 0 1-.363-.643v-3.58q-.825.362-1.32 1.122a2.9 2.9 0 0 0-.495 1.65q0 .809.413 1.55.412.743 1.072 1.123zm3.91 3.663q.875 0 1.585-.396a2.96 2.96 0 0 0 1.534-2.64v-3.564a.32.32 0 0 0-.165-.297l-1.254-.726v4.604a.7.7 0 0 1-.363.643l-3.119 1.799a3 3 0 0 0 1.783.577m.627-6.039V8.878L10.01 7.822 8.129 8.878v2.244l1.881 1.056zM7.057 5.859a.7.7 0 0 1 .363-.644l3.119-1.798a3 3 0 0 0-1.782-.578q-.874 0-1.584.396A2.96 2.96 0 0 0 6.05 4.324a3.07 3.07 0 0 0-.396 1.551v3.547q0 .199.165.314l1.237.726zm8.383 7.887q.825-.364 1.303-1.123.495-.758.495-1.65a3.15 3.15 0 0 0-.412-1.55q-.413-.743-1.073-1.123l-3.086-1.782q-.099-.065-.181-.049a.3.3 0 0 0-.165.05l-1.238.692 3.993 2.327a.6.6 0 0 1 .264.264.64.64 0 0 1 .1.363zm-3.317-8.382a.63.63 0 0 1 .726 0l3.135 1.831v-.297q0-.792-.396-1.501a2.86 2.86 0 0 0-1.105-1.155q-.71-.43-1.65-.43-.825 0-1.436.347L8.294 5.941a.36.36 0 0 0-.165.314v1.418z", + "provenance": { + "provider": "Official brand asset", + "source": "https://openai.com/brand/", + "guidelines": "https://openai.com/brand/" + } + }, + { + "id": "openrouter", + "title": "OpenRouter", + "category": "ai", + "aliases": [ + "open-router" + ], + "domains": [ + "openrouter.ai" + ], + "viewBox": 24, + "hex": "94A3B8", + "path": "M16.778 1.844v1.919q-.569-.026-1.138-.032-.708-.008-1.415.037c-1.93.126-4.023.728-6.149 2.237-2.911 2.066-2.731 1.95-4.14 2.75-.396.223-1.342.574-2.185.798-.841.225-1.753.333-1.751.333v4.229s.768.108 1.61.333c.842.224 1.789.575 2.185.799 1.41.798 1.228.683 4.14 2.75 2.126 1.509 4.22 2.11 6.148 2.236.88.058 1.716.041 2.555.005v1.918l7.222-4.168-7.222-4.17v2.176c-.86.038-1.611.065-2.278.021-1.364-.09-2.417-.357-3.979-1.465-2.244-1.593-2.866-2.027-3.68-2.508.889-.518 1.449-.906 3.822-2.59 1.56-1.109 2.614-1.377 3.978-1.466.667-.044 1.418-.017 2.278.02v2.176L24 6.014Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://openrouter.ai" + } + }, + { + "id": "opensearch", + "title": "OpenSearch", + "category": "data", + "aliases": [ + "open-search" + ], + "domains": [ + "opensearch.org" + ], + "viewBox": 24, + "hex": "005EB8", + "path": "M23.1515 8.8125a.8484.8484 0 0 0-.8484.8485c0 6.982-5.6601 12.6421-12.6421 12.6421a.8485.8485 0 0 0 0 1.6969C17.5802 24 24 17.5802 24 9.661a.8485.8485 0 0 0-.8485-.8485Zm-5.121 5.4375c.816-1.3311 1.6051-3.1058 1.4498-5.5905-.3216-5.1468-4.9832-9.0512-9.3851-8.6281C8.372.1971 6.6025 1.6017 6.7598 4.1177c.0683 1.0934.6034 1.7386 1.473 2.2348.8279.4722 1.8914.7713 3.097 1.1104 1.4563.4096 3.1455.8697 4.4438 1.8265 1.5561 1.1467 2.6198 2.4759 2.2569 4.9606Zm-16.561-9C.6535 6.581-.1355 8.3558.0197 10.8405c.3216 5.1468 4.9832 9.0512 9.385 8.6281 1.7233-.1657 3.4927-1.5703 3.3355-4.0863-.0683-1.0934-.6034-1.7386-1.4731-2.2348-.8278-.4722-1.8913-.7713-3.0969-1.1104-1.4563-.4096-3.1455-.8697-4.4438-1.8265-1.5561-1.1467-2.6198-2.476-2.257-4.9606Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://opensearch.org/trademark-brand-policy.html", + "guidelines": "https://opensearch.org/trademark-brand-policy.html" + } + }, + { + "id": "pagerduty", + "title": "PagerDuty", + "category": "engineering", + "aliases": [ + "pager-duty" + ], + "domains": [ + "pagerduty.com" + ], + "viewBox": 24, + "hex": "06AC38", + "path": "M16.965 1.18C15.085.164 13.769 0 10.683 0H3.73v14.55h6.926c2.743 0 4.8-.164 6.61-1.37 1.975-1.303 3.004-3.484 3.004-6.007 0-2.716-1.262-4.896-3.305-5.994zm-5.5 10.326h-4.21V3.113l3.977-.027c3.62-.028 5.43 1.234 5.43 4.128 0 3.113-2.248 4.292-5.197 4.292zM3.73 17.61h3.525V24H3.73Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.pagerduty.com/brand/", + "guidelines": "https://www.pagerduty.com/brand/" + } + }, + { + "id": "paypal", + "title": "PayPal", + "category": "business", + "aliases": [], + "domains": [ + "paypal.com" + ], + "viewBox": 24, + "hex": "002991", + "path": "M15.607 4.653H8.941L6.645 19.251H1.82L4.862 0h7.995c3.754 0 6.375 2.294 6.473 5.513-.648-.478-2.105-.86-3.722-.86m6.57 5.546c0 3.41-3.01 6.853-6.958 6.853h-2.493L11.595 24H6.74l1.845-11.538h3.592c4.208 0 7.346-3.634 7.153-6.949a5.24 5.24 0 0 1 2.848 4.686M9.653 5.546h6.408c.907 0 1.942.222 2.363.541-.195 2.741-2.655 5.483-6.441 5.483H8.714Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.paypal.com/us", + "guidelines": "https://newsroom.paypal-corp.com/media-resources" + } + }, + { + "id": "perplexity", + "title": "Perplexity", + "category": "ai", + "aliases": [], + "domains": [ + "perplexity.ai" + ], + "viewBox": 24, + "hex": "1FB8CD", + "path": "M22.3977 7.0896h-2.3106V.0676l-7.5094 6.3542V.1577h-1.1554v6.1966L4.4904 0v7.0896H1.6023v10.3976h2.8882V24l6.932-6.3591v6.2005h1.1554v-6.0469l6.9318 6.1807v-6.4879h2.8882V7.0896zm-3.4657-4.531v4.531h-5.355l5.355-4.531zm-13.2862.0676 4.8691 4.4634H5.6458V2.6262zM2.7576 16.332V8.245h7.8476l-6.1149 6.1147v1.9723H2.7576zm2.8882 5.0404v-3.8852h.0001v-2.6488l5.7763-5.7764v7.0111l-5.7764 5.2993zm12.7086.0248-5.7766-5.1509V9.0618l5.7766 5.7766v6.5588zm2.8882-5.0652h-1.733v-1.9723L13.3948 8.245h7.8478v8.087z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.perplexity.ai" + } + }, + { + "id": "pinterest", + "title": "Pinterest", + "category": "channel", + "aliases": [], + "domains": [ + "pinterest.com" + ], + "viewBox": 24, + "hex": "BD081C", + "path": "M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://business.pinterest.com/en/brand-guidelines", + "guidelines": "https://business.pinterest.com/en/brand-guidelines" + } + }, + { + "id": "planetscale", + "title": "PlanetScale", + "category": "data", + "aliases": [ + "planet-scale" + ], + "domains": [ + "planetscale.com" + ], + "viewBox": 24, + "hex": "000000", + "path": "M0 12C0 5.373 5.373 0 12 0c4.873 0 9.067 2.904 10.947 7.077l-15.87 15.87a11.981 11.981 0 0 1-1.935-1.099L14.99 12H12l-8.485 8.485A11.962 11.962 0 0 1 0 12Zm12.004 12L24 12.004C23.998 18.628 18.628 23.998 12.004 24Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://planetscale.com" + } + }, + { + "id": "postgresql", + "title": "PostgreSQL", + "category": "data", + "aliases": [ + "postgres" + ], + "domains": [ + "postgresql.org" + ], + "viewBox": 24, + "hex": "4169E1", + "path": "M23.5594 14.7228a.5269.5269 0 0 0-.0563-.1191c-.139-.2632-.4768-.3418-1.0074-.2321-1.6533.3411-2.2935.1312-2.5256-.0191 1.342-2.0482 2.445-4.522 3.0411-6.8297.2714-1.0507.7982-3.5237.1222-4.7316a1.5641 1.5641 0 0 0-.1509-.235C21.6931.9086 19.8007.0248 17.5099.0005c-1.4947-.0158-2.7705.3461-3.1161.4794a9.449 9.449 0 0 0-.5159-.0816 8.044 8.044 0 0 0-1.3114-.1278c-1.1822-.0184-2.2038.2642-3.0498.8406-.8573-.3211-4.7888-1.645-7.2219.0788C.9359 2.1526.3086 3.8733.4302 6.3043c.0409.818.5069 3.334 1.2423 5.7436.4598 1.5065.9387 2.7019 1.4334 3.582.553.9942 1.1259 1.5933 1.7143 1.7895.4474.1491 1.1327.1441 1.8581-.7279.8012-.9635 1.5903-1.8258 1.9446-2.2069.4351.2355.9064.3625 1.39.3772a.0569.0569 0 0 0 .0004.0041 11.0312 11.0312 0 0 0-.2472.3054c-.3389.4302-.4094.5197-1.5002.7443-.3102.064-1.1344.2339-1.1464.8115-.0025.1224.0329.2309.0919.3268.2269.4231.9216.6097 1.015.6331 1.3345.3335 2.5044.092 3.3714-.6787-.017 2.231.0775 4.4174.3454 5.0874.2212.5529.7618 1.9045 2.4692 1.9043.2505 0 .5263-.0291.8296-.0941 1.7819-.3821 2.5557-1.1696 2.855-2.9059.1503-.8707.4016-2.8753.5388-4.1012.0169-.0703.0357-.1207.057-.1362.0007-.0005.0697-.0471.4272.0307a.3673.3673 0 0 0 .0443.0068l.2539.0223.0149.001c.8468.0384 1.9114-.1426 2.5312-.4308.6438-.2988 1.8057-1.0323 1.5951-1.6698zM2.371 11.8765c-.7435-2.4358-1.1779-4.8851-1.2123-5.5719-.1086-2.1714.4171-3.6829 1.5623-4.4927 1.8367-1.2986 4.8398-.5408 6.108-.13-.0032.0032-.0066.0061-.0098.0094-2.0238 2.044-1.9758 5.536-1.9708 5.7495-.0002.0823.0066.1989.0162.3593.0348.5873.0996 1.6804-.0735 2.9184-.1609 1.1504.1937 2.2764.9728 3.0892.0806.0841.1648.1631.2518.2374-.3468.3714-1.1004 1.1926-1.9025 2.1576-.5677.6825-.9597.5517-1.0886.5087-.3919-.1307-.813-.5871-1.2381-1.3223-.4796-.839-.9635-2.0317-1.4155-3.5126zm6.0072 5.0871c-.1711-.0428-.3271-.1132-.4322-.1772.0889-.0394.2374-.0902.4833-.1409 1.2833-.2641 1.4815-.4506 1.9143-1.0002.0992-.126.2116-.2687.3673-.4426a.3549.3549 0 0 0 .0737-.1298c.1708-.1513.2724-.1099.4369-.0417.156.0646.3078.26.3695.4752.0291.1016.0619.2945-.0452.4444-.9043 1.2658-2.2216 1.2494-3.1676 1.0128zm2.094-3.988-.0525.141c-.133.3566-.2567.6881-.3334 1.003-.6674-.0021-1.3168-.2872-1.8105-.8024-.6279-.6551-.9131-1.5664-.7825-2.5004.1828-1.3079.1153-2.4468.079-3.0586-.005-.0857-.0095-.1607-.0122-.2199.2957-.2621 1.6659-.9962 2.6429-.7724.4459.1022.7176.4057.8305.928.5846 2.7038.0774 3.8307-.3302 4.7363-.084.1866-.1633.3629-.2311.5454zm7.3637 4.5725c-.0169.1768-.0358.376-.0618.5959l-.146.4383a.3547.3547 0 0 0-.0182.1077c-.0059.4747-.054.6489-.115.8693-.0634.2292-.1353.4891-.1794 1.0575-.11 1.4143-.8782 2.2267-2.4172 2.5565-1.5155.3251-1.7843-.4968-2.0212-1.2217a6.5824 6.5824 0 0 0-.0769-.2266c-.2154-.5858-.1911-1.4119-.1574-2.5551.0165-.5612-.0249-1.9013-.3302-2.6462.0044-.2932.0106-.5909.019-.8918a.3529.3529 0 0 0-.0153-.1126 1.4927 1.4927 0 0 0-.0439-.208c-.1226-.4283-.4213-.7866-.7797-.9351-.1424-.059-.4038-.1672-.7178-.0869.067-.276.1831-.5875.309-.9249l.0529-.142c.0595-.16.134-.3257.213-.5012.4265-.9476 1.0106-2.2453.3766-5.1772-.2374-1.0981-1.0304-1.6343-2.2324-1.5098-.7207.0746-1.3799.3654-1.7088.5321a5.6716 5.6716 0 0 0-.1958.1041c.0918-1.1064.4386-3.1741 1.7357-4.4823a4.0306 4.0306 0 0 1 .3033-.276.3532.3532 0 0 0 .1447-.0644c.7524-.5706 1.6945-.8506 2.802-.8325.4091.0067.8017.0339 1.1742.081 1.939.3544 3.2439 1.4468 4.0359 2.3827.8143.9623 1.2552 1.9315 1.4312 2.4543-1.3232-.1346-2.2234.1268-2.6797.779-.9926 1.4189.543 4.1729 1.2811 5.4964.1353.2426.2522.4522.2889.5413.2403.5825.5515.9713.7787 1.2552.0696.087.1372.1714.1885.245-.4008.1155-1.1208.3825-1.0552 1.717-.0123.1563-.0423.4469-.0834.8148-.0461.2077-.0702.4603-.0994.7662zm.8905-1.6211c-.0405-.8316.2691-.9185.5967-1.0105a2.8566 2.8566 0 0 0 .135-.0406 1.202 1.202 0 0 0 .1342.103c.5703.3765 1.5823.4213 3.0068.1344-.2016.1769-.5189.3994-.9533.6011-.4098.1903-1.0957.333-1.7473.3636-.7197.0336-1.0859-.0807-1.1721-.151zm.5695-9.2712c-.0059.3508-.0542.6692-.1054 1.0017-.055.3576-.112.7274-.1264 1.1762-.0142.4368.0404.8909.0932 1.3301.1066.887.216 1.8003-.2075 2.7014a3.5272 3.5272 0 0 1-.1876-.3856c-.0527-.1276-.1669-.3326-.3251-.6162-.6156-1.1041-2.0574-3.6896-1.3193-4.7446.3795-.5427 1.3408-.5661 2.1781-.463zm.2284 7.0137a12.3762 12.3762 0 0 0-.0853-.1074l-.0355-.0444c.7262-1.1995.5842-2.3862.4578-3.4385-.0519-.4318-.1009-.8396-.0885-1.2226.0129-.4061.0666-.7543.1185-1.0911.0639-.415.1288-.8443.1109-1.3505.0134-.0531.0188-.1158.0118-.1902-.0457-.4855-.5999-1.938-1.7294-3.253-.6076-.7073-1.4896-1.4972-2.6889-2.0395.5251-.1066 1.2328-.2035 2.0244-.1859 2.0515.0456 3.6746.8135 4.8242 2.2824a.908.908 0 0 1 .0667.1002c.7231 1.3556-.2762 6.2751-2.9867 10.5405zm-8.8166-6.1162c-.025.1794-.3089.4225-.6211.4225a.5821.5821 0 0 1-.0809-.0056c-.1873-.026-.3765-.144-.5059-.3156-.0458-.0605-.1203-.178-.1055-.2844.0055-.0401.0261-.0985.0925-.1488.1182-.0894.3518-.1226.6096-.0867.3163.0441.6426.1938.6113.4186zm7.9305-.4114c.0111.0792-.049.201-.1531.3102-.0683.0717-.212.1961-.4079.2232a.5456.5456 0 0 1-.075.0052c-.2935 0-.5414-.2344-.5607-.3717-.024-.1765.2641-.3106.5611-.352.297-.0414.6111.0088.6356.1851z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://wiki.postgresql.org/wiki/Logo", + "guidelines": "https://www.postgresql.org/about/policies/trademarks/" + } + }, + { + "id": "prisma", + "title": "Prisma", + "category": "data", + "aliases": [], + "domains": [ + "prisma.io" + ], + "viewBox": 24, + "hex": "2D3748", + "path": "M21.8068 18.2848L13.5528.7565c-.207-.4382-.639-.7273-1.1286-.7541-.5023-.0293-.9523.213-1.2062.6253L2.266 15.1271c-.2773.4518-.2718 1.0091.0158 1.4555l4.3759 6.7786c.2608.4046.7127.6388 1.1823.6388.1332 0 .267-.0188.3987-.0577l12.7019-3.7568c.3891-.1151.7072-.3904.8737-.7553s.1633-.7828-.0075-1.1454zm-1.8481.7519L9.1814 22.2242c-.3292.0975-.6448-.1873-.5756-.5194l3.8501-18.4386c.072-.3448.5486-.3996.699-.0803l7.1288 15.138c.1344.2856-.019.6224-.325.7128z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/prisma/presskit/tree/4bcb64181f266723439d955d60afa1c55fefa715" + } + }, + { + "id": "prometheus", + "title": "Prometheus", + "category": "engineering", + "aliases": [], + "domains": [ + "prometheus.io" + ], + "viewBox": 24, + "hex": "E6522C", + "path": "M12 0C5.373 0 0 5.372 0 12c0 6.627 5.373 12 12 12s12-5.373 12-12c0-6.628-5.373-12-12-12zm0 22.46c-1.885 0-3.414-1.26-3.414-2.814h6.828c0 1.553-1.528 2.813-3.414 2.813zm5.64-3.745H6.36v-2.046h11.28v2.046zm-.04-3.098H6.391c-.037-.043-.075-.086-.111-.13-1.155-1.401-1.427-2.133-1.69-2.879-.005-.025 1.4.287 2.395.511 0 0 .513.119 1.262.255-.72-.843-1.147-1.915-1.147-3.01 0-2.406 1.845-4.508 1.18-6.207.648.053 1.34 1.367 1.387 3.422.689-.951.977-2.69.977-3.755 0-1.103.727-2.385 1.454-2.429-.648 1.069.168 1.984.894 4.256.272.854.237 2.29.447 3.201.07-1.892.395-4.652 1.595-5.605-.529 1.2.079 2.702.494 3.424.671 1.164 1.078 2.047 1.078 3.716a4.642 4.642 0 01-1.11 2.996c.792-.149 1.34-.283 1.34-.283l2.573-.502s-.374 1.538-1.81 3.019z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://prometheus.io" + } + }, + { + "id": "pulumi", + "title": "Pulumi", + "category": "engineering", + "aliases": [], + "domains": [ + "pulumi.com" + ], + "viewBox": 24, + "hex": "8A3391", + "path": "M11.997 0C10.226 0 8.79.83 8.79 1.856c0 1.025 1.436 1.856 3.207 1.856 1.772 0 3.208-.831 3.208-1.856C15.205.83 13.77 0 11.997 0zM5.95 3.488c-1.772 0-3.208.83-3.208 1.856C2.742 6.369 4.178 7.2 5.95 7.2c1.771 0 3.207-.831 3.207-1.856 0-1.025-1.436-1.856-3.207-1.856zm12.103 0c-1.772 0-3.208.83-3.208 1.856 0 1.025 1.436 1.856 3.208 1.856 1.771 0 3.207-.831 3.207-1.856 0-1.025-1.436-1.856-3.207-1.856zm-6.056 3.495c-1.771 0-3.207.831-3.207 1.856 0 1.025 1.436 1.856 3.207 1.856 1.772 0 3.208-.83 3.208-1.856 0-1.025-1.436-1.856-3.208-1.856zm-10.127.67a1.157 1.157 0 0 0-.55.151c-.888.513-.89 2.172-.004 3.706.886 1.534 2.324 2.362 3.211 1.85.888-.513.89-2.171.003-3.706-.72-1.246-1.803-2.027-2.66-2zm20.257.004c-.857-.026-1.941.754-2.661 2-.886 1.535-.884 3.194.003 3.707.888.512 2.325-.316 3.211-1.85.886-1.534.885-3.193-.003-3.706a1.157 1.157 0 0 0-.55-.15zm-6.048 3.492c-.857-.026-1.94.754-2.66 2-.886 1.535-.885 3.194.003 3.706.887.513 2.325-.316 3.21-1.85.887-1.534.885-3.193-.003-3.706a1.157 1.157 0 0 0-.55-.15zm-8.16.001a1.157 1.157 0 0 0-.55.151c-.888.513-.89 2.172-.004 3.706.886 1.535 2.324 2.363 3.211 1.85.888-.512.89-2.171.003-3.705-.72-1.247-1.803-2.028-2.66-2.002zm-6.047 3.494a1.157 1.157 0 0 0-.55.151c-.888.513-.89 2.172-.004 3.706.886 1.534 2.324 2.362 3.212 1.85.887-.513.888-2.172.003-3.706-.72-1.246-1.804-2.027-2.661-2.001zm20.258.002c-.857-.026-1.941.755-2.66 2.001-.887 1.535-.885 3.193.003 3.706.887.512 2.325-.316 3.21-1.85.886-1.534.885-3.193-.003-3.706a1.157 1.157 0 0 0-.55-.15zm-6.047 3.492c-.858-.026-1.942.754-2.661 2-.886 1.535-.885 3.194.003 3.706.888.513 2.325-.315 3.21-1.85.887-1.533.885-3.193-.002-3.705a1.157 1.157 0 0 0-.55-.151zm-8.163.003a1.157 1.157 0 0 0-.55.151c-.887.513-.889 2.172-.003 3.706.886 1.534 2.323 2.363 3.211 1.85.888-.512.89-2.171.004-3.706-.72-1.246-1.804-2.027-2.662-2z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.pulumi.com", + "guidelines": "https://www.pulumi.com/brand/" + } + }, + { + "id": "python", + "title": "Python", + "category": "language", + "aliases": [], + "domains": [ + "python.org" + ], + "viewBox": 24, + "hex": "3776AB", + "path": "M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.python.org/community/logos/", + "guidelines": "https://www.python.org/community/logos/" + } + }, + { + "id": "pytorch", + "title": "PyTorch", + "category": "framework", + "aliases": [], + "domains": [ + "pytorch.org" + ], + "viewBox": 24, + "hex": "EE4C2C", + "path": "M12.005 0L4.952 7.053a9.865 9.865 0 000 14.022 9.866 9.866 0 0014.022 0c3.984-3.9 3.986-10.205.085-14.023l-1.744 1.743c2.904 2.905 2.904 7.634 0 10.538s-7.634 2.904-10.538 0-2.904-7.634 0-10.538l4.647-4.646.582-.665zm3.568 3.899a1.327 1.327 0 00-1.327 1.327 1.327 1.327 0 001.327 1.328A1.327 1.327 0 0016.9 5.226 1.327 1.327 0 0015.573 3.9z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/pytorch/pytorch.github.io/blob/8f083bd12192ca12d5e1c1f3d236f4831d823d8f/assets/images/logo.svg", + "guidelines": "https://github.com/pytorch/pytorch.github.io/blob/381117ec296f002b2de475402ef29cca6c55e209/assets/brand-guidelines/PyTorch-Brand-Guidelines.pdf" + } + }, + { + "id": "qwen", + "title": "QWen", + "category": "ai", + "aliases": [], + "domains": [ + "qwen.ai" + ], + "viewBox": 24, + "hex": "6950EF", + "path": "M23.919 14.545 20.817 9.17l1.47-2.544a.56.56 0 0 0 0-.566l-1.633-2.83a.57.57 0 0 0-.49-.283h-6.207L12.487.402a.57.57 0 0 0-.49-.284H8.732a.56.56 0 0 0-.49.284L5.139 5.775h-2.94a.56.56 0 0 0-.49.284L.077 8.887a.56.56 0 0 0 0 .567L3.18 14.83l-1.47 2.545a.56.56 0 0 0 0 .566l1.634 2.83a.57.57 0 0 0 .49.283h6.205l1.47 2.545a.57.57 0 0 0 .49.284h3.266a.57.57 0 0 0 .49-.284l3.104-5.375h2.94a.57.57 0 0 0 .49-.283l1.634-2.828a.55.55 0 0 0-.004-.568M8.733.686l1.634 2.828-1.634 2.828H21.8L20.164 9.17H7.425L5.63 6.06Zm1.306 19.801-6.205-.002 1.634-2.83h3.265L2.201 6.344h3.267q3.182 5.517 6.367 11.032zm10.124-5.66L18.53 12l-6.532 11.315-1.634-2.83c2.129-3.673 4.25-7.351 6.373-11.028h3.592l3.102 5.374z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://qwen.ai" + } + }, + { + "id": "rabbitmq", + "title": "RabbitMQ", + "category": "data", + "aliases": [ + "rabbit-mq" + ], + "domains": [ + "rabbitmq.com" + ], + "viewBox": 24, + "hex": "FF6600", + "path": "M23.035 9.601h-7.677a.956.956 0 01-.962-.962V.962a.956.956 0 00-.962-.956H10.56a.956.956 0 00-.962.956V8.64a.956.956 0 01-.962.962H5.762a.956.956 0 01-.961-.962V.962A.956.956 0 003.839 0H.959a.956.956 0 00-.956.962v22.076A.956.956 0 00.965 24h22.07a.956.956 0 00.962-.962V10.58a.956.956 0 00-.962-.98zm-3.86 8.152a1.437 1.437 0 01-1.437 1.443h-1.924a1.437 1.437 0 01-1.436-1.443v-1.917a1.437 1.437 0 011.436-1.443h1.924a1.437 1.437 0 011.437 1.443z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.rabbitmq.com", + "guidelines": "https://www.rabbitmq.com/trademark-guidelines.html" + } + }, + { + "id": "railway", + "title": "Railway", + "category": "cloud", + "aliases": [], + "domains": [ + "railway.com", + "railway.app" + ], + "viewBox": 24, + "hex": "0B0D0E", + "path": "M.113 10.27A13.026 13.026 0 000 11.48h18.23c-.064-.125-.15-.237-.235-.347-3.117-4.027-4.793-3.677-7.19-3.78-.8-.034-1.34-.048-4.524-.048-1.704 0-3.555.005-5.358.01-.234.63-.459 1.24-.567 1.737h9.342v1.216H.113v.002zm18.26 2.426H.009c.02.326.05.645.094.961h16.955c.754 0 1.179-.429 1.315-.96zm-17.318 4.28s2.81 6.902 10.93 7.024c4.855 0 9.027-2.883 10.92-7.024H1.056zM11.988 0C7.5 0 3.593 2.466 1.531 6.108l4.75-.005v-.002c3.71 0 3.849.016 4.573.047l.448.016c1.563.052 3.485.22 4.996 1.364.82.621 2.007 1.99 2.712 2.965.654.902.842 1.94.396 2.934-.408.914-1.289 1.458-2.353 1.458H.391s.099.42.249.886h22.748A12.026 12.026 0 0024 12.005C24 5.377 18.621 0 11.988 0z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://railway.app" + } + }, + { + "id": "react", + "title": "React", + "category": "framework", + "aliases": [ + "reactjs" + ], + "domains": [ + "react.dev" + ], + "viewBox": 24, + "hex": "61DAFB", + "path": "M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/facebook/create-react-app/blob/282c03f9525fdf8061ffa1ec50dce89296d916bd/test/fixtures/relative-paths/src/logo.svg" + } + }, + { + "id": "reddit", + "title": "Reddit", + "category": "channel", + "aliases": [], + "domains": [ + "reddit.com" + ], + "viewBox": 24, + "hex": "FF4500", + "path": "M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286C.775 23.225 1.097 24 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0Zm4.388 3.199c1.104 0 1.999.895 1.999 1.999 0 1.105-.895 2-1.999 2-.946 0-1.739-.657-1.947-1.539v.002c-1.147.162-2.032 1.15-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363.473-.363 1.064-.58 1.707-.58 1.547 0 2.802 1.254 2.802 2.802 0 1.117-.655 2.081-1.601 2.531-.088 3.256-3.637 5.876-7.997 5.876-4.361 0-7.905-2.617-7.998-5.87-.954-.447-1.614-1.415-1.614-2.538 0-1.548 1.255-2.802 2.803-2.802.645 0 1.239.218 1.712.585 1.275-.79 2.881-1.291 4.64-1.365v-.01c0-1.663 1.263-3.034 2.88-3.207.188-.911.993-1.595 1.959-1.595Zm-8.085 8.376c-.784 0-1.459.78-1.506 1.797-.047 1.016.64 1.429 1.426 1.429.786 0 1.371-.369 1.418-1.385.047-1.017-.553-1.841-1.338-1.841Zm7.406 0c-.786 0-1.385.824-1.338 1.841.047 1.017.634 1.385 1.418 1.385.785 0 1.473-.413 1.426-1.429-.046-1.017-.721-1.797-1.506-1.797Zm-3.703 4.013c-.974 0-1.907.048-2.77.135-.147.015-.241.168-.183.305.483 1.154 1.622 1.964 2.953 1.964 1.33 0 2.47-.81 2.953-1.964.057-.137-.037-.29-.184-.305-.863-.087-1.795-.135-2.769-.135Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.redditinc.com/brand", + "guidelines": "https://www.redditinc.com/brand" + } + }, + { + "id": "redis", + "title": "Redis", + "category": "data", + "aliases": [], + "domains": [ + "redis.io" + ], + "viewBox": 24, + "hex": "FF4438", + "path": "M22.71 13.145c-1.66 2.092-3.452 4.483-7.038 4.483-3.203 0-4.397-2.825-4.48-5.12.701 1.484 2.073 2.685 4.214 2.63 4.117-.133 6.94-3.852 6.94-7.239 0-4.05-3.022-6.972-8.268-6.972-3.752 0-8.4 1.428-11.455 3.685C2.59 6.937 3.885 9.958 4.35 9.626c2.648-1.904 4.748-3.13 6.784-3.744C8.12 9.244.886 17.05 0 18.425c.1 1.261 1.66 4.648 2.424 4.648.232 0 .431-.133.664-.365a100.49 100.49 0 0 0 5.54-6.765c.222 3.104 1.748 6.898 6.014 6.898 3.819 0 7.604-2.756 9.33-8.965.2-.764-.73-1.361-1.261-.73zm-4.349-5.013c0 1.959-1.926 2.922-3.685 2.922-.941 0-1.664-.247-2.235-.568 1.051-1.592 2.092-3.225 3.21-4.973 1.972.334 2.71 1.43 2.71 2.619z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://redis.io/brand-guidelines", + "guidelines": "https://redis.io/brand-guidelines" + } + }, + { + "id": "render", + "title": "Render", + "category": "cloud", + "aliases": [], + "domains": [ + "render.com" + ], + "viewBox": 24, + "hex": "000000", + "path": "M18.263.007c-3.121-.147-5.744 2.109-6.192 5.082-.018.138-.045.272-.067.405-.696 3.703-3.936 6.507-7.827 6.507-1.388 0-2.691-.356-3.825-.979a.2024.2024 0 0 0-.302.178V24H12v-8.999c0-1.656 1.338-3 2.987-3h2.988c3.382 0 6.103-2.817 5.97-6.244-.12-3.084-2.61-5.603-5.682-5.75", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://render.com" + } + }, + { + "id": "replicate", + "title": "Replicate", + "category": "ai", + "aliases": [], + "domains": [ + "replicate.com" + ], + "viewBox": 24, + "hex": "000000", + "path": "M24 10.262v2.712h-9.518V24h-3.034V10.262zm0-5.131v2.717H8.755V24H5.722V5.131zM24 0v2.717H3.034V24H0V0z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://replicate.com" + } + }, + { + "id": "rust", + "title": "Rust", + "category": "language", + "aliases": [], + "domains": [ + "rust-lang.org" + ], + "viewBox": 24, + "hex": "000000", + "path": "M23.8346 11.7033l-1.0073-.6236a13.7268 13.7268 0 00-.0283-.2936l.8656-.8069a.3483.3483 0 00-.1154-.578l-1.1066-.414a8.4958 8.4958 0 00-.087-.2856l.6904-.9587a.3462.3462 0 00-.2257-.5446l-1.1663-.1894a9.3574 9.3574 0 00-.1407-.2622l.49-1.0761a.3437.3437 0 00-.0274-.3361.3486.3486 0 00-.3006-.154l-1.1845.0416a6.7444 6.7444 0 00-.1873-.2268l.2723-1.153a.3472.3472 0 00-.417-.4172l-1.1532.2724a14.0183 14.0183 0 00-.2278-.1873l.0415-1.1845a.3442.3442 0 00-.49-.328l-1.076.491c-.0872-.0476-.1742-.0952-.2623-.1407l-.1903-1.1673A.3483.3483 0 0016.256.955l-.9597.6905a8.4867 8.4867 0 00-.2855-.086l-.414-1.1066a.3483.3483 0 00-.5781-.1154l-.8069.8666a9.2936 9.2936 0 00-.2936-.0284L12.2946.1683a.3462.3462 0 00-.5892 0l-.6236 1.0073a13.7383 13.7383 0 00-.2936.0284L9.9803.3374a.3462.3462 0 00-.578.1154l-.4141 1.1065c-.0962.0274-.1903.0567-.2855.086L7.744.955a.3483.3483 0 00-.5447.2258L7.009 2.348a9.3574 9.3574 0 00-.2622.1407l-1.0762-.491a.3462.3462 0 00-.49.328l.0416 1.1845a7.9826 7.9826 0 00-.2278.1873L3.8413 3.425a.3472.3472 0 00-.4171.4171l.2713 1.1531c-.0628.075-.1255.1509-.1863.2268l-1.1845-.0415a.3462.3462 0 00-.328.49l.491 1.0761a9.167 9.167 0 00-.1407.2622l-1.1662.1894a.3483.3483 0 00-.2258.5446l.6904.9587a13.303 13.303 0 00-.087.2855l-1.1065.414a.3483.3483 0 00-.1155.5781l.8656.807a9.2936 9.2936 0 00-.0283.2935l-1.0073.6236a.3442.3442 0 000 .5892l1.0073.6236c.008.0982.0182.1964.0283.2936l-.8656.8079a.3462.3462 0 00.1155.578l1.1065.4141c.0273.0962.0567.1914.087.2855l-.6904.9587a.3452.3452 0 00.2268.5447l1.1662.1893c.0456.088.0922.1751.1408.2622l-.491 1.0762a.3462.3462 0 00.328.49l1.1834-.0415c.0618.0769.1235.1528.1873.2277l-.2713 1.1541a.3462.3462 0 00.4171.4161l1.153-.2713c.075.0638.151.1255.2279.1863l-.0415 1.1845a.3442.3442 0 00.49.327l1.0761-.49c.087.0486.1741.0951.2622.1407l.1903 1.1662a.3483.3483 0 00.5447.2268l.9587-.6904a9.299 9.299 0 00.2855.087l.414 1.1066a.3452.3452 0 00.5781.1154l.8079-.8656c.0972.0111.1954.0203.2936.0294l.6236 1.0073a.3472.3472 0 00.5892 0l.6236-1.0073c.0982-.0091.1964-.0183.2936-.0294l.8069.8656a.3483.3483 0 00.578-.1154l.4141-1.1066a8.4626 8.4626 0 00.2855-.087l.9587.6904a.3452.3452 0 00.5447-.2268l.1903-1.1662c.088-.0456.1751-.0931.2622-.1407l1.0762.49a.3472.3472 0 00.49-.327l-.0415-1.1845a6.7267 6.7267 0 00.2267-.1863l1.1531.2713a.3472.3472 0 00.4171-.416l-.2713-1.1542c.0628-.0749.1255-.1508.1863-.2278l1.1845.0415a.3442.3442 0 00.328-.49l-.49-1.076c.0475-.0872.0951-.1742.1407-.2623l1.1662-.1893a.3483.3483 0 00.2258-.5447l-.6904-.9587.087-.2855 1.1066-.414a.3462.3462 0 00.1154-.5781l-.8656-.8079c.0101-.0972.0202-.1954.0283-.2936l1.0073-.6236a.3442.3442 0 000-.5892zm-6.7413 8.3551a.7138.7138 0 01.2986-1.396.714.714 0 11-.2997 1.396zm-.3422-2.3142a.649.649 0 00-.7715.5l-.3573 1.6685c-1.1035.501-2.3285.7795-3.6193.7795a8.7368 8.7368 0 01-3.6951-.814l-.3574-1.6684a.648.648 0 00-.7714-.499l-1.473.3158a8.7216 8.7216 0 01-.7613-.898h7.1676c.081 0 .1356-.0141.1356-.088v-2.536c0-.074-.0536-.0881-.1356-.0881h-2.0966v-1.6077h2.2677c.2065 0 1.1065.0587 1.394 1.2088.0901.3533.2875 1.5044.4232 1.8729.1346.413.6833 1.2381 1.2685 1.2381h3.5716a.7492.7492 0 00.1296-.0131 8.7874 8.7874 0 01-.8119.9526zM6.8369 20.024a.714.714 0 11-.2997-1.396.714.714 0 01.2997 1.396zM4.1177 8.9972a.7137.7137 0 11-1.304.5791.7137.7137 0 011.304-.579zm-.8352 1.9813l1.5347-.6824a.65.65 0 00.33-.8585l-.3158-.7147h1.2432v5.6025H3.5669a8.7753 8.7753 0 01-.2834-3.348zm6.7343-.5437V8.7836h2.9601c.153 0 1.0792.1772 1.0792.8697 0 .575-.7107.7815-1.2948.7815zm10.7574 1.4862c0 .2187-.008.4363-.0243.651h-.9c-.09 0-.1265.0586-.1265.1477v.413c0 .973-.5487 1.1846-1.0296 1.2382-.4576.0517-.9648-.1913-1.0275-.4717-.2704-1.5186-.7198-1.8436-1.4305-2.4034.8817-.5599 1.799-1.386 1.799-2.4915 0-1.1936-.819-1.9458-1.3769-2.3153-.7825-.5163-1.6491-.6195-1.883-.6195H5.4682a8.7651 8.7651 0 014.907-2.7699l1.0974 1.151a.648.648 0 00.9182.0213l1.227-1.1743a8.7753 8.7753 0 016.0044 4.2762l-.8403 1.8982a.652.652 0 00.33.8585l1.6178.7188c.0283.2875.0425.577.0425.8717zm-9.3006-9.5993a.7128.7128 0 11.984 1.0316.7137.7137 0 01-.984-1.0316zm8.3389 6.71a.7107.7107 0 01.9395-.3625.7137.7137 0 11-.9405.3635z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.rust-lang.org", + "guidelines": "https://www.rust-lang.org/policies/media-guide", + "license": { + "type": "CC-BY-SA-4.0", + "url": "https://spdx.org/licenses/CC-BY-SA-4.0" + } + } + }, + { + "id": "sentry", + "title": "Sentry", + "category": "engineering", + "aliases": [], + "domains": [ + "sentry.io" + ], + "viewBox": 24, + "hex": "362D59", + "path": "M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://sentry.io/branding/" + } + }, + { + "id": "shopify", + "title": "Shopify", + "category": "business", + "aliases": [], + "domains": [ + "shopify.com" + ], + "viewBox": 24, + "hex": "7AB55C", + "path": "M15.337 23.979l7.216-1.561s-2.604-17.613-2.625-17.73c-.018-.116-.114-.192-.211-.192s-1.929-.136-1.929-.136-1.275-1.274-1.439-1.411c-.045-.037-.075-.057-.121-.074l-.914 21.104h.023zM11.71 11.305s-.81-.424-1.774-.424c-1.447 0-1.504.906-1.504 1.141 0 1.232 3.24 1.715 3.24 4.629 0 2.295-1.44 3.76-3.406 3.76-2.354 0-3.54-1.465-3.54-1.465l.646-2.086s1.245 1.066 2.28 1.066c.675 0 .975-.545.975-.932 0-1.619-2.654-1.694-2.654-4.359-.034-2.237 1.571-4.416 4.827-4.416 1.257 0 1.875.361 1.875.361l-.945 2.715-.02.01zM11.17.83c.136 0 .271.038.405.135-.984.465-2.064 1.639-2.508 3.992-.656.213-1.293.405-1.889.578C7.697 3.75 8.951.84 11.17.84V.83zm1.235 2.949v.135c-.754.232-1.583.484-2.394.736.466-1.777 1.333-2.645 2.085-2.971.193.501.309 1.176.309 2.1zm.539-2.234c.694.074 1.141.867 1.429 1.755-.349.114-.735.231-1.158.366v-.252c0-.752-.096-1.371-.271-1.871v.002zm2.992 1.289c-.02 0-.06.021-.078.021s-.289.075-.714.21c-.423-1.233-1.176-2.37-2.508-2.37h-.115C12.135.209 11.669 0 11.265 0 8.159 0 6.675 3.877 6.21 5.846c-1.194.365-2.063.636-2.16.674-.675.213-.694.232-.772.87-.075.462-1.83 14.063-1.83 14.063L15.009 24l.927-21.166z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.shopify.com/brand-assets", + "guidelines": "https://www.shopify.com/brand-assets" + } + }, + { + "id": "snowflake", + "title": "Snowflake", + "category": "data", + "aliases": [], + "domains": [ + "snowflake.com" + ], + "viewBox": 24, + "hex": "29B5E8", + "path": "M24 3.459c0 .646-.418 1.18-1.141 1.18-.723 0-1.142-.534-1.142-1.18 0-.647.419-1.18 1.142-1.18.723 0 1.141.533 1.141 1.18zm-.228 0c0-.533-.38-.951-.913-.951s-.913.38-.913.95c0 .533.38.952.913.952.57 0 .913-.419.913-.951zm-1.37-.533h.495c.266 0 .456.152.456.38 0 .153-.076.229-.19.305l.19.266v.038h-.266l-.19-.266h-.229v.266h-.266zm.495.228h-.229v.267h.229c.114 0 .152-.038.152-.114.038-.077-.038-.153-.152-.153zM7.602 12.4c.038-.151.076-.304.076-.456 0-.114-.038-.228-.038-.342-.114-.343-.304-.647-.646-.838l-4.87-2.777c-.685-.38-1.56-.152-1.94.533-.381.685-.153 1.56.532 1.94l2.701 1.56-2.701 1.56c-.685.38-.913 1.256-.533 1.94.38.685 1.256.914 1.94.533l4.832-2.777c.343-.267.571-.533.647-.876zm1.332 2.626c-.266-.038-.57.038-.837.19l-4.832 2.777c-.685.38-.913 1.256-.532 1.94.38.686 1.255.914 1.94.533l2.701-1.56v3.12c0 .8.647 1.408 1.446 1.408.799 0 1.407-.647 1.407-1.408v-5.592c0-.761-.57-1.37-1.293-1.408zm4.946-6.088c.266.038.57-.038.837-.19l4.832-2.777c.685-.38.913-1.256.532-1.94-.38-.686-1.255-.914-1.94-.533l-2.701 1.56V1.975c0-.799-.647-1.408-1.446-1.408-.799 0-1.446.609-1.446 1.408V7.53c0 .76.609 1.37 1.332 1.407zM3.265 5.97l4.832 2.777c.266.152.533.19.837.19.723-.038 1.331-.684 1.331-1.407V1.975c0-.799-.646-1.408-1.407-1.408-.799 0-1.446.647-1.446 1.408v3.12l-2.701-1.56c-.685-.38-1.56-.152-1.94.533-.419.646-.19 1.521.494 1.902zm9.093 6.011a.412.412 0 00-.114-.266l-.57-.571a.346.346 0 00-.267-.114.412.412 0 00-.266.114l-.571.57a.411.411 0 00-.114.267c0 .076.038.19.114.267l.57.57a.345.345 0 00.267.114c.076 0 .19-.038.266-.114l.571-.57a.412.412 0 00.114-.267zm1.598.533L11.94 14.53c-.039.038-.153.114-.229.114h-.608a.411.411 0 01-.267-.114L8.82 12.514a.408.408 0 01-.076-.229v-.608c0-.076.038-.19.114-.267l2.016-2.016a.41.41 0 01.267-.114h.608a.41.41 0 01.267.114l2.016 2.016a.347.347 0 01.114.267v.608c-.076.077-.114.19-.19.229zm5.593 5.44l-4.832-2.777c-.266-.152-.57-.19-.837-.152-.723.038-1.332.684-1.332 1.408v5.554c0 .8.647 1.408 1.408 1.408.799 0 1.446-.647 1.446-1.408v-3.12l2.7 1.56c.686.38 1.561.152 1.941-.533.419-.646.19-1.521-.494-1.94zm2.549-7.533l-2.701 1.56 2.7 1.56c.686.38.914 1.256.533 1.94-.38.685-1.255.913-1.94.533l-4.832-2.778a1.644 1.644 0 01-.647-.798c-.037-.153-.076-.305-.076-.457 0-.114.039-.228.039-.342.114-.343.342-.647.646-.837l4.832-2.778c.685-.38 1.56-.152 1.94.533.457.609.19 1.484-.494 1.864", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.snowflake.com/brand-guidelines/", + "guidelines": "https://www.snowflake.com/brand-guidelines/" + } + }, + { + "id": "spring", + "title": "Spring", + "category": "framework", + "aliases": [ + "spring-boot" + ], + "domains": [ + "spring.io" + ], + "viewBox": 24, + "hex": "6DB33F", + "path": "M21.8537 1.4158a10.4504 10.4504 0 0 1-1.284 2.2471A11.9666 11.9666 0 1 0 3.8518 20.7757l.4445.3951a11.9543 11.9543 0 0 0 19.6316-8.2971c.3457-3.0126-.568-6.8649-2.0743-11.458zM5.5805 20.8745a1.0174 1.0174 0 1 1-.1482-1.4323 1.0396 1.0396 0 0 1 .1482 1.4323zm16.1991-3.5806c-2.9385 3.9263-9.2601 2.5928-13.2852 2.7904 0 0-.7161.0494-1.4323.1481 0 0 .2717-.1234.6174-.2469 2.8398-.9877 4.1732-1.1853 5.9018-2.0743 3.2349-1.6545 6.4698-5.2844 7.1118-9.0379-1.2347 3.6053-4.9881 6.7167-8.3959 7.9761-2.3459.8643-6.5685 1.7039-6.5685 1.7039l-.1729-.0988c-2.8645-1.4076-2.9632-7.6304 2.2718-9.6306 2.2966-.889 4.4696-.395 6.9637-.9877 2.6422-.6174 5.7043-2.5929 6.939-5.1857 1.3828 4.1732 3.062 10.643.0493 14.6434z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://spring.io/trademarks" + } + }, + { + "id": "sqlite", + "title": "SQLite", + "category": "data", + "aliases": [], + "domains": [ + "sqlite.org" + ], + "viewBox": 24, + "hex": "003B57", + "path": "M21.678.521c-1.032-.92-2.28-.55-3.513.544a8.71 8.71 0 0 0-.547.535c-2.109 2.237-4.066 6.38-4.674 9.544.237.48.422 1.093.544 1.561a13.044 13.044 0 0 1 .164.703s-.019-.071-.096-.296l-.05-.146a1.689 1.689 0 0 0-.033-.08c-.138-.32-.518-.995-.686-1.289-.143.423-.27.818-.376 1.176.484.884.778 2.4.778 2.4s-.025-.099-.147-.442c-.107-.303-.644-1.244-.772-1.464-.217.804-.304 1.346-.226 1.478.152.256.296.698.422 1.186.286 1.1.485 2.44.485 2.44l.017.224a22.41 22.41 0 0 0 .056 2.748c.095 1.146.273 2.13.5 2.657l.155-.084c-.334-1.038-.47-2.399-.41-3.967.09-2.398.642-5.29 1.661-8.304 1.723-4.55 4.113-8.201 6.3-9.945-1.993 1.8-4.692 7.63-5.5 9.788-.904 2.416-1.545 4.684-1.931 6.857.666-2.037 2.821-2.912 2.821-2.912s1.057-1.304 2.292-3.166c-.74.169-1.955.458-2.362.629-.6.251-.762.337-.762.337s1.945-1.184 3.613-1.72C21.695 7.9 24.195 2.767 21.678.521m-18.573.543A1.842 1.842 0 0 0 1.27 2.9v16.608a1.84 1.84 0 0 0 1.835 1.834h9.418a22.953 22.953 0 0 1-.052-2.707c-.006-.062-.011-.141-.016-.2a27.01 27.01 0 0 0-.473-2.378c-.121-.47-.275-.898-.369-1.057-.116-.197-.098-.31-.097-.432 0-.12.015-.245.037-.386a9.98 9.98 0 0 1 .234-1.045l.217-.028c-.017-.035-.014-.065-.031-.097l-.041-.381a32.8 32.8 0 0 1 .382-1.194l.2-.019c-.008-.016-.01-.038-.018-.053l-.043-.316c.63-3.28 2.587-7.443 4.8-9.791.066-.069.133-.128.198-.194Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/sqlite/sqlite/blob/43e862723ec680542ca6f608f9963c0993dd7324/art/sqlite370.eps" + } + }, + { + "id": "stripe", + "title": "Stripe", + "category": "business", + "aliases": [], + "domains": [ + "stripe.com" + ], + "viewBox": 24, + "hex": "635BFF", + "path": "M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://stripe.com/newsroom/information" + } + }, + { + "id": "supabase", + "title": "Supabase", + "category": "cloud", + "aliases": [], + "domains": [ + "supabase.com" + ], + "viewBox": 24, + "hex": "3FCF8E", + "path": "M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/supabase/supabase/blob/4031a7549f5d46da7bc79c01d56be4177dc7c114/packages/common/assets/images/supabase-logo-wordmark--light.svg" + } + }, + { + "id": "svelte", + "title": "Svelte", + "category": "framework", + "aliases": [], + "domains": [ + "svelte.dev" + ], + "viewBox": 24, + "hex": "FF3E00", + "path": "M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.186 1.092l.208.063-.02.208a1.253 1.253 0 0 0 .226.83 1.337 1.337 0 0 0 1.435.533 1.231 1.231 0 0 0 .343-.15l5.59-3.562a1.164 1.164 0 0 0 .524-.778 1.242 1.242 0 0 0-.211-.937 1.338 1.338 0 0 0-1.435-.533 1.23 1.23 0 0 0-.343.15l-2.133 1.36a4.078 4.078 0 0 1-1.135.499 4.44 4.44 0 0 1-4.765-1.766 4.108 4.108 0 0 1-.702-3.108 3.855 3.855 0 0 1 1.742-2.582l5.589-3.563a4.072 4.072 0 0 1 1.135-.499 4.44 4.44 0 0 1 4.765 1.767 4.109 4.109 0 0 1 .703 3.107 3.943 3.943 0 0 1-.134.522l-.105.321-.286-.21a7.204 7.204 0 0 0-2.187-1.093l-.208-.063.02-.207a1.255 1.255 0 0 0-.226-.831 1.337 1.337 0 0 0-1.435-.532 1.231 1.231 0 0 0-.343.15L8.62 9.368a1.162 1.162 0 0 0-.524.778 1.24 1.24 0 0 0 .211.937 1.338 1.338 0 0 0 1.435.533 1.235 1.235 0 0 0 .344-.151l2.132-1.36a4.067 4.067 0 0 1 1.135-.498 4.44 4.44 0 0 1 4.765 1.766 4.108 4.108 0 0 1 .702 3.108 3.857 3.857 0 0 1-1.742 2.583l-5.589 3.562a4.072 4.072 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.412 6.412 0 0 0-2.896 4.295 6.753 6.753 0 0 0 .666 4.336 6.43 6.43 0 0 0-.96 2.396 6.833 6.833 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.41 6.41 0 0 0 2.896-4.295 6.756 6.756 0 0 0-.665-4.336 6.429 6.429 0 0 0 .958-2.396 6.831 6.831 0 0 0-1.167-5.168Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/sveltejs/branding/blob/c4dfca6743572087a6aef0e109ffe3d95596e86a/svelte-logo.svg" + } + }, + { + "id": "telegram", + "title": "Telegram", + "category": "channel", + "aliases": [], + "domains": [ + "telegram.org", + "t.me" + ], + "viewBox": 24, + "hex": "26A5E4", + "path": "M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://telegram.org/tour/screenshots" + } + }, + { + "id": "tensorflow", + "title": "TensorFlow", + "category": "framework", + "aliases": [], + "domains": [ + "tensorflow.org" + ], + "viewBox": 24, + "hex": "FF6F00", + "path": "M1.292 5.856L11.54 0v24l-4.095-2.378V7.603l-6.168 3.564.015-5.31zm21.43 5.311l-.014-5.31L12.46 0v24l4.095-2.378V14.87l3.092 1.788-.018-4.618-3.074-1.756V7.603l6.168 3.564z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.tensorflow.org" + } + }, + { + "id": "terraform", + "title": "Terraform", + "category": "engineering", + "aliases": [], + "domains": [ + "terraform.io" + ], + "viewBox": 24, + "hex": "844FBA", + "path": "M1.44 0v7.575l6.561 3.79V3.787zm21.12 4.227l-6.561 3.791v7.574l6.56-3.787zM8.72 4.23v7.575l6.561 3.787V8.018zm0 8.405v7.575L15.28 24v-7.578z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.hashicorp.com/brand", + "guidelines": "https://www.hashicorp.com/brand" + } + }, + { + "id": "tiktok", + "title": "TikTok", + "category": "channel", + "aliases": [ + "douyin", + "抖音" + ], + "domains": [ + "tiktok.com", + "douyin.com" + ], + "viewBox": 24, + "hex": "000000", + "path": "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://tiktok.com" + } + }, + { + "id": "trello", + "title": "Trello", + "category": "collaboration", + "aliases": [], + "domains": [ + "trello.com" + ], + "viewBox": 24, + "hex": "0052CC", + "path": "M21.147 0H2.853A2.86 2.86 0 000 2.853v18.294A2.86 2.86 0 002.853 24h18.294A2.86 2.86 0 0024 21.147V2.853A2.86 2.86 0 0021.147 0zM10.34 17.287a.953.953 0 01-.953.953h-4a.954.954 0 01-.954-.953V5.38a.953.953 0 01.954-.953h4a.954.954 0 01.953.953zm9.233-5.467a.944.944 0 01-.953.947h-4a.947.947 0 01-.953-.947V5.38a.953.953 0 01.953-.953h4a.954.954 0 01.953.953z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://atlassian.design/resources/logo-library", + "guidelines": "https://atlassian.design/foundations/logos" + } + }, + { + "id": "typescript", + "title": "TypeScript", + "category": "language", + "aliases": [ + "ts" + ], + "domains": [ + "typescriptlang.org" + ], + "viewBox": 24, + "hex": "3178C6", + "path": "M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.typescriptlang.org/branding", + "guidelines": "https://www.typescriptlang.org/branding" + } + }, + { + "id": "vercel", + "title": "Vercel", + "category": "cloud", + "aliases": [], + "domains": [ + "vercel.com" + ], + "viewBox": 24, + "hex": "000000", + "path": "m12 1.608 12 20.784H0Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://vercel.com/geist/brands", + "guidelines": "https://vercel.com/geist/brands" + } + }, + { + "id": "vue", + "title": "Vue.js", + "category": "framework", + "aliases": [ + "vuejs", + "vue.js" + ], + "domains": [ + "vuejs.org" + ], + "viewBox": 24, + "hex": "4FC08D", + "path": "M24,1.61H14.06L12,5.16,9.94,1.61H0L12,22.39ZM12,14.08,5.16,2.23H9.59L12,6.41l2.41-4.18h4.43Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://github.com/vuejs/art/blob/a1c78b74569b70a25300925b4eacfefcc143b8f6/logo.svg", + "guidelines": "https://github.com/vuejs/art/blob/a1c78b74569b70a25300925b4eacfefcc143b8f6/README.md", + "license": { + "type": "CC-BY-NC-SA-4.0", + "url": "https://spdx.org/licenses/CC-BY-NC-SA-4.0" + } + } + }, + { + "id": "wechat", + "title": "WeChat", + "category": "channel", + "aliases": [ + "weixin", + "微信" + ], + "domains": [ + "weixin.qq.com" + ], + "viewBox": 24, + "hex": "07C160", + "path": "M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.27-.027-.407-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://wechat.design/tool/brand", + "guidelines": "https://wechat.design/brand/main-brand" + } + }, + { + "id": "whatsapp", + "title": "WhatsApp", + "category": "channel", + "aliases": [], + "domains": [ + "whatsapp.com" + ], + "viewBox": 24, + "hex": "25D366", + "path": "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://about.meta.com/brand/resources/whatsapp/whatsapp-brand", + "guidelines": "https://about.meta.com/brand/resources/whatsapp/whatsapp-brand" + } + }, + { + "id": "woocommerce", + "title": "WooCommerce", + "category": "business", + "aliases": [ + "woo-commerce" + ], + "domains": [ + "woocommerce.com" + ], + "viewBox": 24, + "hex": "96588A", + "path": "M.754 9.58a.754.754 0 00-.754.758v2.525c0 .42.339.758.758.758h3.135l1.431.799-.326-.799h2.373a.757.757 0 00.758-.758v-2.525a.757.757 0 00-.758-.758H.754zm2.709.445h.03c.065.001.124.023.179.067a.26.26 0 01.103.19.29.29 0 01-.033.16c-.13.239-.236.64-.322 1.199-.083.541-.114.965-.094 1.267a.392.392 0 01-.039.219.213.213 0 01-.176.12c-.086.006-.177-.034-.263-.124-.31-.316-.555-.788-.735-1.416-.216.425-.375.744-.478.957-.196.376-.363.568-.502.578-.09.007-.166-.069-.233-.228-.17-.436-.352-1.277-.548-2.524a.297.297 0 01.054-.222c.047-.064.116-.095.21-.102.169-.013.265.065.288.238.103.695.217 1.284.336 1.766l.727-1.387c.066-.126.15-.192.25-.199.146-.01.237.083.273.28.083.441.188.817.315 1.136.086-.844.233-1.453.44-1.828a.255.255 0 01.218-.147zm1.293.36c.056 0 .116.006.18.02.232.05.411.177.53.386.107.18.161.395.161.654 0 .343-.087.654-.26.94-.2.332-.459.5-.781.5a.88.88 0 01-.18-.022.763.763 0 01-.531-.384 1.287 1.287 0 01-.158-.659c0-.342.085-.655.258-.937.202-.333.462-.498.78-.498zm2.084 0c.056 0 .116.006.18.02.236.05.411.177.53.386.107.18.16.395.16.654 0 .343-.086.654-.259.94-.2.332-.459.5-.781.5a.88.88 0 01-.18-.022.763.763 0 01-.531-.384 1.287 1.287 0 01-.16-.659c0-.342.087-.655.26-.937.202-.333.462-.498.78-.498zm4.437.047c-.305 0-.546.102-.718.304-.173.203-.256.49-.256.856 0 .395.086.697.256.906.17.21.418.316.744.316.315 0 .559-.107.728-.316.17-.21.256-.504.256-.883s-.087-.673-.26-.879c-.176-.202-.424-.304-.75-.304zm-1.466.002a1.13 1.13 0 00-.84.326c-.223.22-.332.499-.332.838 0 .362.108.658.328.88.22.223.505.336.861.336.103 0 .22-.016.346-.052v-.54c-.117.034-.216.051-.303.051a.545.545 0 01-.422-.177c-.106-.12-.16-.278-.16-.48 0-.19.053-.348.156-.468a.498.498 0 01.397-.181c.103 0 .212.015.332.049v-.537a1.394 1.394 0 00-.363-.045zm12.414 0a1.135 1.135 0 00-.84.326c-.223.22-.332.499-.332.838 0 .362.108.658.328.88.22.223.506.336.861.336.103 0 .22-.016.346-.052v-.54c-.116.034-.216.051-.303.051a.545.545 0 01-.422-.177c-.106-.12-.16-.278-.16-.48 0-.19.053-.348.156-.468a.498.498 0 01.397-.181c.103 0 .212.015.332.049v-.537a1.394 1.394 0 00-.363-.045zm-9.598.06l-.29 2.264h.579l.156-1.559.395 1.559h.412l.379-1.555.164 1.555h.603l-.304-2.264h-.791l-.12.508c-.03.13-.06.264-.087.4l-.067.352a29.97 29.97 0 00-.258-1.26h-.771zm2.768 0l-.29 2.264h.579l.156-1.559.396 1.559h.412l.375-1.555.165 1.555h.603l-.305-2.264h-.789l-.119.508c-.03.13-.06.264-.086.4l-.066.352c-.063-.352-.15-.771-.26-1.26h-.771zm3.988 0v2.264h.611v-1.031h.012l.494 1.03h.645l-.489-1.019a.61.61 0 00.37-.552.598.598 0 00-.25-.506c-.167-.123-.394-.186-.68-.186h-.713zm3.377 0v2.264H24v-.483h-.63v-.414h.54v-.468h-.54v-.416h.626v-.483H22.76zm-4.793.004v2.264h1.24v-.483h-.627v-.416h.541v-.468h-.54v-.415h.622v-.482h-1.236zm2.025.432c.146.003.25.025.313.072.063.046.091.12.091.227 0 .156-.135.236-.404.24v-.54zm-15.22.011c-.104 0-.205.069-.301.211a1.078 1.078 0 00-.2.639c0 .096.02.2.06.303.049.13.117.198.196.215.083.016.173-.02.27-.106.123-.11.205-.273.252-.492.016-.077.023-.16.023-.246 0-.097-.02-.2-.06-.303-.05-.13-.116-.198-.196-.215a.246.246 0 00-.045-.006zm2.083 0c-.103 0-.204.069-.3.211a1.078 1.078 0 00-.2.639c0 .096.02.2.06.303.049.13.117.198.196.215.083.016.173-.02.27-.106.123-.11.205-.273.252-.492.013-.077.023-.16.023-.246 0-.097-.02-.2-.06-.303-.05-.13-.116-.198-.196-.215a.246.246 0 00-.045-.006zm4.428.006c.233 0 .354.218.354.66-.004.273-.038.46-.098.553a.293.293 0 01-.262.139.266.266 0 01-.242-.139c-.056-.093-.084-.28-.084-.562 0-.436.11-.65.332-.65Z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://woocommerce.com/style-guide", + "guidelines": "https://woocommerce.com/trademark-guidelines" + } + }, + { + "id": "wordpress", + "title": "WordPress", + "category": "business", + "aliases": [], + "domains": [ + "wordpress.org", + "wordpress.com" + ], + "viewBox": 24, + "hex": "21759B", + "path": "M21.469 6.825c.84 1.537 1.318 3.3 1.318 5.175 0 3.979-2.156 7.456-5.363 9.325l3.295-9.527c.615-1.54.82-2.771.82-3.864 0-.405-.026-.78-.07-1.11m-7.981.105c.647-.03 1.232-.105 1.232-.105.582-.075.514-.93-.067-.899 0 0-1.755.135-2.88.135-1.064 0-2.85-.15-2.85-.15-.585-.03-.661.855-.075.885 0 0 .54.061 1.125.09l1.68 4.605-2.37 7.08L5.354 6.9c.649-.03 1.234-.1 1.234-.1.585-.075.516-.93-.065-.896 0 0-1.746.138-2.874.138-.2 0-.438-.008-.69-.015C4.911 3.15 8.235 1.215 12 1.215c2.809 0 5.365 1.072 7.286 2.833-.046-.003-.091-.009-.141-.009-1.06 0-1.812.923-1.812 1.914 0 .89.513 1.643 1.06 2.531.411.72.89 1.643.89 2.977 0 .915-.354 1.994-.821 3.479l-1.075 3.585-3.9-11.61.001.014zM12 22.784c-1.059 0-2.081-.153-3.048-.437l3.237-9.406 3.315 9.087c.024.053.05.101.078.149-1.12.393-2.325.609-3.582.609M1.211 12c0-1.564.336-3.05.935-4.39L7.29 21.709C3.694 19.96 1.212 16.271 1.211 12M12 0C5.385 0 0 5.385 0 12s5.385 12 12 12 12-5.385 12-12S18.615 0 12 0", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://wordpress.org/about/logos", + "guidelines": "https://wordpressfoundation.org/trademark-policy" + } + }, + { + "id": "x", + "title": "X", + "category": "channel", + "aliases": [ + "twitter" + ], + "domains": [ + "x.com", + "twitter.com" + ], + "viewBox": 24, + "hex": "000000", + "path": "M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://x.com", + "guidelines": "https://about.x.com/en/who-we-are/brand-toolkit" + } + }, + { + "id": "youtube", + "title": "YouTube", + "category": "channel", + "aliases": [], + "domains": [ + "youtube.com", + "youtu.be" + ], + "viewBox": 24, + "hex": "FF0000", + "path": "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors", + "guidelines": "https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors" + } + }, + { + "id": "zendesk", + "title": "Zendesk", + "category": "business", + "aliases": [], + "domains": [ + "zendesk.com" + ], + "viewBox": 24, + "hex": "03363D", + "path": "M12.914 2.904V16.29L24 2.905H12.914zM0 2.906C0 5.966 2.483 8.45 5.543 8.45s5.542-2.484 5.543-5.544H0zm11.086 4.807L0 21.096h11.086V7.713zm7.37 7.84c-3.063 0-5.542 2.48-5.542 5.543H24c0-3.06-2.48-5.543-5.543-5.543z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://brandland.zendesk.com", + "guidelines": "https://brandland.zendesk.com" + } + }, + { + "id": "zoom", + "title": "Zoom", + "category": "collaboration", + "aliases": [], + "domains": [ + "zoom.us" + ], + "viewBox": 24, + "hex": "0B5CFF", + "path": "M5.033 14.649H.743a.74.74 0 0 1-.686-.458.74.74 0 0 1 .16-.808L3.19 10.41H1.06A1.06 1.06 0 0 1 0 9.35h3.957c.301 0 .57.18.686.458a.74.74 0 0 1-.161.808L1.51 13.59h2.464c.585 0 1.06.475 1.06 1.06zM24 11.338c0-1.14-.927-2.066-2.066-2.066-.61 0-1.158.265-1.537.686a2.061 2.061 0 0 0-1.536-.686c-1.14 0-2.066.926-2.066 2.066v3.311a1.06 1.06 0 0 0 1.06-1.06v-2.251a1.004 1.004 0 0 1 2.013 0v2.251c0 .586.474 1.06 1.06 1.06v-3.311a1.004 1.004 0 0 1 2.012 0v2.251c0 .586.475 1.06 1.06 1.06zM16.265 12a2.728 2.728 0 1 1-5.457 0 2.728 2.728 0 0 1 5.457 0zm-1.06 0a1.669 1.669 0 1 0-3.338 0 1.669 1.669 0 0 0 3.338 0zm-4.82 0a2.728 2.728 0 1 1-5.458 0 2.728 2.728 0 0 1 5.457 0zm-1.06 0a1.669 1.669 0 1 0-3.338 0 1.669 1.669 0 0 0 3.338 0z", + "provenance": { + "provider": "Simple Icons", + "providerVersion": "16.28.0", + "source": "https://brand.zoom.us/media-library/", + "guidelines": "https://brand.zoom.us/usage-legal/" + } + } +]); diff --git a/tools/archify/renderers/shared/generated-validators.mjs b/tools/archify/renderers/shared/generated-validators.mjs new file mode 100644 index 0000000..3a525cc --- /dev/null +++ b/tools/archify/renderers/shared/generated-validators.mjs @@ -0,0 +1,13 @@ +// Generated by scripts/generate-validators.mjs. Do not edit by hand. +"use strict";export const workflow = validate20;const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/tt-a1i/archify/schemas/workflow.schema.json","title":"Archify Workflow Diagram","type":"object","additionalProperties":false,"required":["schema_version","diagram_type","meta","lanes","nodes","edges"],"properties":{"schema_version":{"enum":[1,2]},"diagram_type":{"const":"workflow"},"meta":{"type":"object","additionalProperties":false,"required":["title"],"properties":{"title":{"type":"string","minLength":1},"locale":{"$ref":"common.schema.json#/$defs/locale"},"subtitle":{"type":"string"},"output":{"type":"string"},"animation":{"enum":["trace","none"]},"visual_preset":{"enum":["classic","signal-flow","blueprint","editorial"]},"quality_profile":{"enum":["standard","showcase"]},"views":{"$ref":"common.schema.json#/$defs/guidedViews"},"legend":{"type":"object","additionalProperties":false,"properties":{"mode":{"$ref":"common.schema.json#/$defs/legendMode"},"entries":{"type":"object","additionalProperties":false,"properties":{"frontend":{"$ref":"common.schema.json#/$defs/legendEntry"},"backend":{"$ref":"common.schema.json#/$defs/legendEntry"},"database":{"$ref":"common.schema.json#/$defs/legendEntry"},"cloud":{"$ref":"common.schema.json#/$defs/legendEntry"},"security":{"$ref":"common.schema.json#/$defs/legendEntry"},"messagebus":{"$ref":"common.schema.json#/$defs/legendEntry"},"external":{"$ref":"common.schema.json#/$defs/legendEntry"}}}}},"viewBox":{"type":"array","prefixItems":[{"type":"number","minimum":700},{"type":"number","minimum":240}],"items":false,"minItems":2,"maxItems":2}}},"lanes":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["id","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"variant":{"enum":["normal","exception"]}}}},"phases":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["id","label","fromCol","toCol"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"fromCol":{"type":"integer","minimum":0,"maximum":5},"toCol":{"type":"integer","minimum":0,"maximum":5},"variant":{"enum":["default","emphasis","security","dashed"]}}}},"groups":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["id","label","lane","fromCol","toCol"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"lane":{"$ref":"common.schema.json#/$defs/id"},"fromCol":{"type":"integer","minimum":0,"maximum":5},"toCol":{"type":"integer","minimum":0,"maximum":5},"variant":{"enum":["default","emphasis","security","dashed"]}}}},"mainPath":{"type":"array","minItems":2,"items":{"$ref":"common.schema.json#/$defs/id"}},"semanticChecks":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"allowedRoots":{"type":"array","items":{"$ref":"common.schema.json#/$defs/id"}},"allowedTerminals":{"type":"array","items":{"$ref":"common.schema.json#/$defs/id"}},"requiredEdges":{"type":"array","items":{"$ref":"#/$defs/semanticRelation"}},"requiredPaths":{"type":"array","items":{"$ref":"#/$defs/semanticRelation"}}}},"nodes":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["id","lane","col","type","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"lane":{"$ref":"common.schema.json#/$defs/id"},"col":{"type":"integer","minimum":0,"maximum":5},"type":{"$ref":"common.schema.json#/$defs/componentType"},"label":{"type":"string","minLength":1},"sublabel":{"type":"string"},"tag":{"type":"string"},"brand":{"$ref":"common.schema.json#/$defs/brandMark"},"width":{"type":"number","minimum":32},"height":{"type":"number","minimum":32},"yOffset":{"type":"number"}}}},"edges":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string"},"variant":{"$ref":"common.schema.json#/$defs/variant"},"role":{"enum":["main","branch","async","return","error"]},"fromSide":{"$ref":"#/$defs/side"},"toSide":{"$ref":"#/$defs/side"},"route":{"enum":["auto","straight","drop","outside-right","return-left","bottom-channel","up-channel"]},"via":{"type":"array","items":{"$ref":"common.schema.json#/$defs/point"}},"labelAt":{"$ref":"common.schema.json#/$defs/point"},"labelDx":{"type":"number"},"labelDy":{"type":"number"},"labelSegment":{"type":"integer","minimum":0},"channelX":{"type":"number"},"channelY":{"type":"number"},"bias":{"type":"number","minimum":0,"maximum":1},"width":{"type":"number","minimum":0.5}}}},"cards":{"$ref":"common.schema.json#/$defs/cards"}},"$defs":{"semanticRelation":{"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"}}},"side":{"enum":["left","right","top","bottom"]}}};const schema33 = {"enum":["en","zh-CN"]};const schema37 = {"enum":["auto","all","hidden"]};const schema38 = {"type":"object","additionalProperties":false,"minProperties":1,"properties":{"label":{"type":"string","minLength":1,"maxLength":80},"visible":{"type":"boolean"}}};const schema35 = {"type":"string","pattern":"^[a-zA-Z][a-zA-Z0-9_-]*$"};const schema57 = {"enum":["frontend","backend","database","cloud","security","messagebus","external"]};const schema58 = {"oneOf":[{"type":"string","minLength":1,"maxLength":2048,"anyOf":[{"maxLength":80,"pattern":"^[^\\r\\n]+$"},{"pattern":"^https?://"}]},{"type":"object","additionalProperties":false,"required":["url","sha256"],"properties":{"url":{"type":"string","minLength":8,"maxLength":2048,"pattern":"^https?://"},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"}}}]};const schema62 = {"enum":["default","emphasis","security","dashed"]};const schema63 = {"enum":["left","right","top","bottom"]};const schema65 = {"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2};const schema67 = {"type":"array","items":{"type":"object","additionalProperties":false,"required":["dot","title","items"],"properties":{"dot":{"enum":["cyan","emerald","violet","amber","rose","orange","slate"]},"title":{"type":"string","minLength":1},"items":{"type":"array","items":{"type":"string"}}}}};const func1 = Object.prototype.hasOwnProperty;const func3 = function ucs2length(str) { + const len = str.length; + let length = 0; + let pos = 0; + while (pos < len) { + length += 1; + const value = str.charCodeAt(pos++); + if (value >= 0xd800 && value <= 0xdbff && pos < len + && (str.charCodeAt(pos) & 0xfc00) === 0xdc00) pos += 1; + } + return length; +};const schema34 = {"type":"array","maxItems":5,"items":{"type":"object","additionalProperties":false,"required":["id","label","focus"],"properties":{"id":{"$ref":"#/$defs/id"},"label":{"type":"string","minLength":1,"maxLength":48},"focus":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/id"}},"note":{"type":"string","maxLength":140}}}};const pattern4 = new RegExp("^[a-zA-Z][a-zA-Z0-9_-]*$", "u");function validate22(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate22.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(Array.isArray(data)){if(data.length > 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate22.errors = vErrors;return errors === 0;}validate22.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};const schema52 = {"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"}}};function validate24(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){let vErrors = null;let errors = 0;const evaluated0 = validate24.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.from === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "from"},message:"must have required property '"+"from"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.to === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "to"},message:"must have required property '"+"to"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}for(const key0 in data){if(!((key0 === "from") || (key0 === "to"))){const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}if(data.from !== undefined){let data0 = data.from;if(typeof data0 === "string"){if(!pattern4.test(data0)){const err3 = {instancePath:instancePath+"/from",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}else {const err4 = {instancePath:instancePath+"/from",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.to !== undefined){let data1 = data.to;if(typeof data1 === "string"){if(!pattern4.test(data1)){const err5 = {instancePath:instancePath+"/to",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}else {const err6 = {instancePath:instancePath+"/to",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}}else {const err7 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}validate24.errors = vErrors;return errors === 0;}validate24.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false};const pattern17 = new RegExp("^[^\\r\\n]+$", "u");const pattern18 = new RegExp("^https?://", "u");const pattern20 = new RegExp("^[a-f0-9]{64}$", "u");function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/workflow.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate20.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.lanes === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "lanes"},message:"must have required property '"+"lanes"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.nodes === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "nodes"},message:"must have required property '"+"nodes"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.edges === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "edges"},message:"must have required property '"+"edges"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!(func1.call(schema31.properties, key0))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema_version !== undefined){let data0 = data.schema_version;if(!((data0 === 1) || (data0 === 2))){const err7 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/enum",keyword:"enum",params:{allowedValues: schema31.properties.schema_version.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.diagram_type !== undefined){if("workflow" !== data.diagram_type){const err8 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "workflow"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err9 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}for(const key1 in data2){if(!(func1.call(schema31.properties.meta.properties, key1))){const err10 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err11 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err13 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err14 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err15 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err16 = {instancePath:instancePath+"/meta/animation",schemaPath:"#/properties/meta/properties/animation/enum",keyword:"enum",params:{allowedValues: schema31.properties.meta.properties.animation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err17 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"#/properties/meta/properties/visual_preset/enum",keyword:"enum",params:{allowedValues: schema31.properties.meta.properties.visual_preset.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err18 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"#/properties/meta/properties/quality_profile/enum",keyword:"enum",params:{allowedValues: schema31.properties.meta.properties.quality_profile.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data2.views !== undefined){if(!(validate22(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate22.errors : vErrors.concat(validate22.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data11 = data2.legend;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){for(const key2 in data11){if(!((key2 === "mode") || (key2 === "entries"))){const err19 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data11.mode !== undefined){let data12 = data11.mode;if(!(((data12 === "auto") || (data12 === "all")) || (data12 === "hidden"))){const err20 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data11.entries !== undefined){let data13 = data11.entries;if(data13 && typeof data13 == "object" && !Array.isArray(data13)){for(const key3 in data13){if(!(((((((key3 === "frontend") || (key3 === "backend")) || (key3 === "database")) || (key3 === "cloud")) || (key3 === "security")) || (key3 === "messagebus")) || (key3 === "external"))){const err21 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data13.frontend !== undefined){let data14 = data13.frontend;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){if(Object.keys(data14).length < 1){const err22 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}for(const key4 in data14){if(!((key4 === "label") || (key4 === "visible"))){const err23 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data14.label !== undefined){let data15 = data14.label;if(typeof data15 === "string"){if(func3(data15) > 80){const err24 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(func3(data15) < 1){const err25 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data14.visible !== undefined){if(typeof data14.visible !== "boolean"){const err27 = {instancePath:instancePath+"/meta/legend/entries/frontend/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data13.backend !== undefined){let data17 = data13.backend;if(data17 && typeof data17 == "object" && !Array.isArray(data17)){if(Object.keys(data17).length < 1){const err29 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key5 in data17){if(!((key5 === "label") || (key5 === "visible"))){const err30 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data17.label !== undefined){let data18 = data17.label;if(typeof data18 === "string"){if(func3(data18) > 80){const err31 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func3(data18) < 1){const err32 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data17.visible !== undefined){if(typeof data17.visible !== "boolean"){const err34 = {instancePath:instancePath+"/meta/legend/entries/backend/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data13.database !== undefined){let data20 = data13.database;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){if(Object.keys(data20).length < 1){const err36 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key6 in data20){if(!((key6 === "label") || (key6 === "visible"))){const err37 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data20.label !== undefined){let data21 = data20.label;if(typeof data21 === "string"){if(func3(data21) > 80){const err38 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(func3(data21) < 1){const err39 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data20.visible !== undefined){if(typeof data20.visible !== "boolean"){const err41 = {instancePath:instancePath+"/meta/legend/entries/database/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data13.cloud !== undefined){let data23 = data13.cloud;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(Object.keys(data23).length < 1){const err43 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key7 in data23){if(!((key7 === "label") || (key7 === "visible"))){const err44 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data23.label !== undefined){let data24 = data23.label;if(typeof data24 === "string"){if(func3(data24) > 80){const err45 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func3(data24) < 1){const err46 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data23.visible !== undefined){if(typeof data23.visible !== "boolean"){const err48 = {instancePath:instancePath+"/meta/legend/entries/cloud/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data13.security !== undefined){let data26 = data13.security;if(data26 && typeof data26 == "object" && !Array.isArray(data26)){if(Object.keys(data26).length < 1){const err50 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}for(const key8 in data26){if(!((key8 === "label") || (key8 === "visible"))){const err51 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data26.label !== undefined){let data27 = data26.label;if(typeof data27 === "string"){if(func3(data27) > 80){const err52 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(func3(data27) < 1){const err53 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data26.visible !== undefined){if(typeof data26.visible !== "boolean"){const err55 = {instancePath:instancePath+"/meta/legend/entries/security/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data13.messagebus !== undefined){let data29 = data13.messagebus;if(data29 && typeof data29 == "object" && !Array.isArray(data29)){if(Object.keys(data29).length < 1){const err57 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}for(const key9 in data29){if(!((key9 === "label") || (key9 === "visible"))){const err58 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key9},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data29.label !== undefined){let data30 = data29.label;if(typeof data30 === "string"){if(func3(data30) > 80){const err59 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(func3(data30) < 1){const err60 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}else {const err61 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data29.visible !== undefined){if(typeof data29.visible !== "boolean"){const err62 = {instancePath:instancePath+"/meta/legend/entries/messagebus/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}}else {const err63 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data13.external !== undefined){let data32 = data13.external;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){if(Object.keys(data32).length < 1){const err64 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}for(const key10 in data32){if(!((key10 === "label") || (key10 === "visible"))){const err65 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key10},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data32.label !== undefined){let data33 = data32.label;if(typeof data33 === "string"){if(func3(data33) > 80){const err66 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(func3(data33) < 1){const err67 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}else {const err68 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}if(data32.visible !== undefined){if(typeof data32.visible !== "boolean"){const err69 = {instancePath:instancePath+"/meta/legend/entries/external/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}}else {const err70 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}}else {const err71 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}}else {const err72 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data2.viewBox !== undefined){let data35 = data2.viewBox;if(Array.isArray(data35)){if(data35.length > 2){const err73 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(data35.length < 2){const err74 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}const len0 = data35.length;if(len0 > 0){let data36 = data35[0];if((typeof data36 == "number") && (isFinite(data36))){if(data36 < 700 || isNaN(data36)){const err75 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 700},message:"must be >= 700"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}else {const err76 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}if(len0 > 1){let data37 = data35[1];if((typeof data37 == "number") && (isFinite(data37))){if(data37 < 240 || isNaN(data37)){const err77 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 240},message:"must be >= 240"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}else {const err78 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}const len1 = data35.length;if(!(len1 <= 2)){const err79 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}else {const err80 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}}else {const err81 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}if(data.lanes !== undefined){let data38 = data.lanes;if(Array.isArray(data38)){if(data38.length < 1){const err82 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}const len2 = data38.length;for(let i0=0; i0 5 || isNaN(data47)){const err103 = {instancePath:instancePath+"/phases/" + i1+"/fromCol",schemaPath:"#/properties/phases/items/properties/fromCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}if(data47 < 0 || isNaN(data47)){const err104 = {instancePath:instancePath+"/phases/" + i1+"/fromCol",schemaPath:"#/properties/phases/items/properties/fromCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}}if(data44.toCol !== undefined){let data48 = data44.toCol;if(!(((typeof data48 == "number") && (!(data48 % 1) && !isNaN(data48))) && (isFinite(data48)))){const err105 = {instancePath:instancePath+"/phases/" + i1+"/toCol",schemaPath:"#/properties/phases/items/properties/toCol/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}if((typeof data48 == "number") && (isFinite(data48))){if(data48 > 5 || isNaN(data48)){const err106 = {instancePath:instancePath+"/phases/" + i1+"/toCol",schemaPath:"#/properties/phases/items/properties/toCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}if(data48 < 0 || isNaN(data48)){const err107 = {instancePath:instancePath+"/phases/" + i1+"/toCol",schemaPath:"#/properties/phases/items/properties/toCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}}if(data44.variant !== undefined){let data49 = data44.variant;if(!((((data49 === "default") || (data49 === "emphasis")) || (data49 === "security")) || (data49 === "dashed"))){const err108 = {instancePath:instancePath+"/phases/" + i1+"/variant",schemaPath:"#/properties/phases/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema31.properties.phases.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}}else {const err109 = {instancePath:instancePath+"/phases/" + i1,schemaPath:"#/properties/phases/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}}else {const err110 = {instancePath:instancePath+"/phases",schemaPath:"#/properties/phases/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}}if(data.groups !== undefined){let data50 = data.groups;if(Array.isArray(data50)){const len4 = data50.length;for(let i2=0; i2 5 || isNaN(data55)){const err124 = {instancePath:instancePath+"/groups/" + i2+"/fromCol",schemaPath:"#/properties/groups/items/properties/fromCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}if(data55 < 0 || isNaN(data55)){const err125 = {instancePath:instancePath+"/groups/" + i2+"/fromCol",schemaPath:"#/properties/groups/items/properties/fromCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}}if(data51.toCol !== undefined){let data56 = data51.toCol;if(!(((typeof data56 == "number") && (!(data56 % 1) && !isNaN(data56))) && (isFinite(data56)))){const err126 = {instancePath:instancePath+"/groups/" + i2+"/toCol",schemaPath:"#/properties/groups/items/properties/toCol/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}if((typeof data56 == "number") && (isFinite(data56))){if(data56 > 5 || isNaN(data56)){const err127 = {instancePath:instancePath+"/groups/" + i2+"/toCol",schemaPath:"#/properties/groups/items/properties/toCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}if(data56 < 0 || isNaN(data56)){const err128 = {instancePath:instancePath+"/groups/" + i2+"/toCol",schemaPath:"#/properties/groups/items/properties/toCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}}if(data51.variant !== undefined){let data57 = data51.variant;if(!((((data57 === "default") || (data57 === "emphasis")) || (data57 === "security")) || (data57 === "dashed"))){const err129 = {instancePath:instancePath+"/groups/" + i2+"/variant",schemaPath:"#/properties/groups/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema31.properties.groups.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}}}else {const err130 = {instancePath:instancePath+"/groups/" + i2,schemaPath:"#/properties/groups/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}}}else {const err131 = {instancePath:instancePath+"/groups",schemaPath:"#/properties/groups/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}if(data.mainPath !== undefined){let data58 = data.mainPath;if(Array.isArray(data58)){if(data58.length < 2){const err132 = {instancePath:instancePath+"/mainPath",schemaPath:"#/properties/mainPath/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}const len5 = data58.length;for(let i3=0; i3 5 || isNaN(data73)){const err159 = {instancePath:instancePath+"/nodes/" + i8+"/col",schemaPath:"#/properties/nodes/items/properties/col/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err159];}else {vErrors.push(err159);}errors++;}if(data73 < 0 || isNaN(data73)){const err160 = {instancePath:instancePath+"/nodes/" + i8+"/col",schemaPath:"#/properties/nodes/items/properties/col/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err160];}else {vErrors.push(err160);}errors++;}}}if(data70.type !== undefined){let data74 = data70.type;if(!(((((((data74 === "frontend") || (data74 === "backend")) || (data74 === "database")) || (data74 === "cloud")) || (data74 === "security")) || (data74 === "messagebus")) || (data74 === "external"))){const err161 = {instancePath:instancePath+"/nodes/" + i8+"/type",schemaPath:"common.schema.json#/$defs/componentType/enum",keyword:"enum",params:{allowedValues: schema57.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err161];}else {vErrors.push(err161);}errors++;}}if(data70.label !== undefined){let data75 = data70.label;if(typeof data75 === "string"){if(func3(data75) < 1){const err162 = {instancePath:instancePath+"/nodes/" + i8+"/label",schemaPath:"#/properties/nodes/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err162];}else {vErrors.push(err162);}errors++;}}else {const err163 = {instancePath:instancePath+"/nodes/" + i8+"/label",schemaPath:"#/properties/nodes/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err163];}else {vErrors.push(err163);}errors++;}}if(data70.sublabel !== undefined){if(typeof data70.sublabel !== "string"){const err164 = {instancePath:instancePath+"/nodes/" + i8+"/sublabel",schemaPath:"#/properties/nodes/items/properties/sublabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err164];}else {vErrors.push(err164);}errors++;}}if(data70.tag !== undefined){if(typeof data70.tag !== "string"){const err165 = {instancePath:instancePath+"/nodes/" + i8+"/tag",schemaPath:"#/properties/nodes/items/properties/tag/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err165];}else {vErrors.push(err165);}errors++;}}if(data70.brand !== undefined){let data78 = data70.brand;const _errs180 = errors;let valid55 = false;let passing0 = null;const _errs181 = errors;const _errs183 = errors;let valid56 = false;const _errs184 = errors;if(typeof data78 === "string"){if(func3(data78) > 80){const err166 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err166];}else {vErrors.push(err166);}errors++;}if(!pattern17.test(data78)){const err167 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err167];}else {vErrors.push(err167);}errors++;}}var _valid1 = _errs184 === errors;valid56 = valid56 || _valid1;const _errs185 = errors;if(typeof data78 === "string"){if(!pattern18.test(data78)){const err168 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err168];}else {vErrors.push(err168);}errors++;}}var _valid1 = _errs185 === errors;valid56 = valid56 || _valid1;if(!valid56){const err169 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err169];}else {vErrors.push(err169);}errors++;}else {errors = _errs183;if(vErrors !== null){if(_errs183){vErrors.length = _errs183;}else {vErrors = null;}}}if(typeof data78 === "string"){if(func3(data78) > 2048){const err170 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err170];}else {vErrors.push(err170);}errors++;}if(func3(data78) < 1){const err171 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err171];}else {vErrors.push(err171);}errors++;}}else {const err172 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err172];}else {vErrors.push(err172);}errors++;}var _valid0 = _errs181 === errors;if(_valid0){valid55 = true;passing0 = 0;}const _errs186 = errors;if(data78 && typeof data78 == "object" && !Array.isArray(data78)){if(data78.url === undefined){const err173 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err173];}else {vErrors.push(err173);}errors++;}if(data78.sha256 === undefined){const err174 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err174];}else {vErrors.push(err174);}errors++;}for(const key16 in data78){if(!((key16 === "url") || (key16 === "sha256"))){const err175 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key16},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err175];}else {vErrors.push(err175);}errors++;}}if(data78.url !== undefined){let data79 = data78.url;if(typeof data79 === "string"){if(func3(data79) > 2048){const err176 = {instancePath:instancePath+"/nodes/" + i8+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err176];}else {vErrors.push(err176);}errors++;}if(func3(data79) < 8){const err177 = {instancePath:instancePath+"/nodes/" + i8+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err177];}else {vErrors.push(err177);}errors++;}if(!pattern18.test(data79)){const err178 = {instancePath:instancePath+"/nodes/" + i8+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err178];}else {vErrors.push(err178);}errors++;}}else {const err179 = {instancePath:instancePath+"/nodes/" + i8+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err179];}else {vErrors.push(err179);}errors++;}}if(data78.sha256 !== undefined){let data80 = data78.sha256;if(typeof data80 === "string"){if(!pattern20.test(data80)){const err180 = {instancePath:instancePath+"/nodes/" + i8+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err180];}else {vErrors.push(err180);}errors++;}}else {const err181 = {instancePath:instancePath+"/nodes/" + i8+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err181];}else {vErrors.push(err181);}errors++;}}}else {const err182 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err182];}else {vErrors.push(err182);}errors++;}var _valid0 = _errs186 === errors;if(_valid0 && valid55){valid55 = false;passing0 = [passing0, 1];}else {if(_valid0){valid55 = true;passing0 = 1;}}if(!valid55){const err183 = {instancePath:instancePath+"/nodes/" + i8+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err183];}else {vErrors.push(err183);}errors++;}else {errors = _errs180;if(vErrors !== null){if(_errs180){vErrors.length = _errs180;}else {vErrors = null;}}}}if(data70.width !== undefined){let data81 = data70.width;if((typeof data81 == "number") && (isFinite(data81))){if(data81 < 32 || isNaN(data81)){const err184 = {instancePath:instancePath+"/nodes/" + i8+"/width",schemaPath:"#/properties/nodes/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 32},message:"must be >= 32"};if(vErrors === null){vErrors = [err184];}else {vErrors.push(err184);}errors++;}}else {const err185 = {instancePath:instancePath+"/nodes/" + i8+"/width",schemaPath:"#/properties/nodes/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err185];}else {vErrors.push(err185);}errors++;}}if(data70.height !== undefined){let data82 = data70.height;if((typeof data82 == "number") && (isFinite(data82))){if(data82 < 32 || isNaN(data82)){const err186 = {instancePath:instancePath+"/nodes/" + i8+"/height",schemaPath:"#/properties/nodes/items/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 32},message:"must be >= 32"};if(vErrors === null){vErrors = [err186];}else {vErrors.push(err186);}errors++;}}else {const err187 = {instancePath:instancePath+"/nodes/" + i8+"/height",schemaPath:"#/properties/nodes/items/properties/height/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err187];}else {vErrors.push(err187);}errors++;}}if(data70.yOffset !== undefined){let data83 = data70.yOffset;if(!((typeof data83 == "number") && (isFinite(data83)))){const err188 = {instancePath:instancePath+"/nodes/" + i8+"/yOffset",schemaPath:"#/properties/nodes/items/properties/yOffset/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err188];}else {vErrors.push(err188);}errors++;}}}else {const err189 = {instancePath:instancePath+"/nodes/" + i8,schemaPath:"#/properties/nodes/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err189];}else {vErrors.push(err189);}errors++;}}}else {const err190 = {instancePath:instancePath+"/nodes",schemaPath:"#/properties/nodes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err190];}else {vErrors.push(err190);}errors++;}}if(data.edges !== undefined){let data84 = data.edges;if(Array.isArray(data84)){const len11 = data84.length;for(let i9=0; i9 2){const err206 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err206];}else {vErrors.push(err206);}errors++;}if(data96.length < 2){const err207 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err207];}else {vErrors.push(err207);}errors++;}const len13 = data96.length;if(len13 > 0){let data97 = data96[0];if(!((typeof data97 == "number") && (isFinite(data97)))){const err208 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err208];}else {vErrors.push(err208);}errors++;}}if(len13 > 1){let data98 = data96[1];if(!((typeof data98 == "number") && (isFinite(data98)))){const err209 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err209];}else {vErrors.push(err209);}errors++;}}const len14 = data96.length;if(!(len14 <= 2)){const err210 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err210];}else {vErrors.push(err210);}errors++;}}else {const err211 = {instancePath:instancePath+"/edges/" + i9+"/via/" + i10,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err211];}else {vErrors.push(err211);}errors++;}}}else {const err212 = {instancePath:instancePath+"/edges/" + i9+"/via",schemaPath:"#/properties/edges/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err212];}else {vErrors.push(err212);}errors++;}}if(data85.labelAt !== undefined){let data99 = data85.labelAt;if(Array.isArray(data99)){if(data99.length > 2){const err213 = {instancePath:instancePath+"/edges/" + i9+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err213];}else {vErrors.push(err213);}errors++;}if(data99.length < 2){const err214 = {instancePath:instancePath+"/edges/" + i9+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err214];}else {vErrors.push(err214);}errors++;}const len15 = data99.length;if(len15 > 0){let data100 = data99[0];if(!((typeof data100 == "number") && (isFinite(data100)))){const err215 = {instancePath:instancePath+"/edges/" + i9+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err215];}else {vErrors.push(err215);}errors++;}}if(len15 > 1){let data101 = data99[1];if(!((typeof data101 == "number") && (isFinite(data101)))){const err216 = {instancePath:instancePath+"/edges/" + i9+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err216];}else {vErrors.push(err216);}errors++;}}const len16 = data99.length;if(!(len16 <= 2)){const err217 = {instancePath:instancePath+"/edges/" + i9+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err217];}else {vErrors.push(err217);}errors++;}}else {const err218 = {instancePath:instancePath+"/edges/" + i9+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err218];}else {vErrors.push(err218);}errors++;}}if(data85.labelDx !== undefined){let data102 = data85.labelDx;if(!((typeof data102 == "number") && (isFinite(data102)))){const err219 = {instancePath:instancePath+"/edges/" + i9+"/labelDx",schemaPath:"#/properties/edges/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err219];}else {vErrors.push(err219);}errors++;}}if(data85.labelDy !== undefined){let data103 = data85.labelDy;if(!((typeof data103 == "number") && (isFinite(data103)))){const err220 = {instancePath:instancePath+"/edges/" + i9+"/labelDy",schemaPath:"#/properties/edges/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err220];}else {vErrors.push(err220);}errors++;}}if(data85.labelSegment !== undefined){let data104 = data85.labelSegment;if(!(((typeof data104 == "number") && (!(data104 % 1) && !isNaN(data104))) && (isFinite(data104)))){const err221 = {instancePath:instancePath+"/edges/" + i9+"/labelSegment",schemaPath:"#/properties/edges/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err221];}else {vErrors.push(err221);}errors++;}if((typeof data104 == "number") && (isFinite(data104))){if(data104 < 0 || isNaN(data104)){const err222 = {instancePath:instancePath+"/edges/" + i9+"/labelSegment",schemaPath:"#/properties/edges/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err222];}else {vErrors.push(err222);}errors++;}}}if(data85.channelX !== undefined){let data105 = data85.channelX;if(!((typeof data105 == "number") && (isFinite(data105)))){const err223 = {instancePath:instancePath+"/edges/" + i9+"/channelX",schemaPath:"#/properties/edges/items/properties/channelX/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err223];}else {vErrors.push(err223);}errors++;}}if(data85.channelY !== undefined){let data106 = data85.channelY;if(!((typeof data106 == "number") && (isFinite(data106)))){const err224 = {instancePath:instancePath+"/edges/" + i9+"/channelY",schemaPath:"#/properties/edges/items/properties/channelY/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err224];}else {vErrors.push(err224);}errors++;}}if(data85.bias !== undefined){let data107 = data85.bias;if((typeof data107 == "number") && (isFinite(data107))){if(data107 > 1 || isNaN(data107)){const err225 = {instancePath:instancePath+"/edges/" + i9+"/bias",schemaPath:"#/properties/edges/items/properties/bias/maximum",keyword:"maximum",params:{comparison: "<=", limit: 1},message:"must be <= 1"};if(vErrors === null){vErrors = [err225];}else {vErrors.push(err225);}errors++;}if(data107 < 0 || isNaN(data107)){const err226 = {instancePath:instancePath+"/edges/" + i9+"/bias",schemaPath:"#/properties/edges/items/properties/bias/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err226];}else {vErrors.push(err226);}errors++;}}else {const err227 = {instancePath:instancePath+"/edges/" + i9+"/bias",schemaPath:"#/properties/edges/items/properties/bias/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err227];}else {vErrors.push(err227);}errors++;}}if(data85.width !== undefined){let data108 = data85.width;if((typeof data108 == "number") && (isFinite(data108))){if(data108 < 0.5 || isNaN(data108)){const err228 = {instancePath:instancePath+"/edges/" + i9+"/width",schemaPath:"#/properties/edges/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err228];}else {vErrors.push(err228);}errors++;}}else {const err229 = {instancePath:instancePath+"/edges/" + i9+"/width",schemaPath:"#/properties/edges/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err229];}else {vErrors.push(err229);}errors++;}}}else {const err230 = {instancePath:instancePath+"/edges/" + i9,schemaPath:"#/properties/edges/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err230];}else {vErrors.push(err230);}errors++;}}}else {const err231 = {instancePath:instancePath+"/edges",schemaPath:"#/properties/edges/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err231];}else {vErrors.push(err231);}errors++;}}if(data.cards !== undefined){let data109 = data.cards;if(Array.isArray(data109)){const len17 = data109.length;for(let i11=0; i11 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate28.errors = vErrors;return errors === 0;}validate28.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};function validate27(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/sequence.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate27.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.participants === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "participants"},message:"must have required property '"+"participants"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.messages === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "messages"},message:"must have required property '"+"messages"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema_version") || (key0 === "diagram_type")) || (key0 === "meta")) || (key0 === "participants")) || (key0 === "segments")) || (key0 === "messages")) || (key0 === "activations")) || (key0 === "cards"))){const err5 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.schema_version !== undefined){if(1 !== data.schema_version){const err6 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.diagram_type !== undefined){if("sequence" !== data.diagram_type){const err7 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "sequence"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err8 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}for(const key1 in data2){if(!(func1.call(schema68.properties.meta.properties, key1))){const err9 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err10 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err12 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err13 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err14 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err15 = {instancePath:instancePath+"/meta/animation",schemaPath:"common.schema.json#/$defs/animation/enum",keyword:"enum",params:{allowedValues: schema70.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err16 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"common.schema.json#/$defs/visualPreset/enum",keyword:"enum",params:{allowedValues: schema71.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err17 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"common.schema.json#/$defs/qualityProfile/enum",keyword:"enum",params:{allowedValues: schema72.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.column_fit !== undefined){let data10 = data2.column_fit;if(!((data10 === "fixed") || (data10 === "spread"))){const err18 = {instancePath:instancePath+"/meta/column_fit",schemaPath:"#/properties/meta/properties/column_fit/enum",keyword:"enum",params:{allowedValues: schema68.properties.meta.properties.column_fit.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data2.views !== undefined){if(!(validate28(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data12 = data2.legend;if(data12 && typeof data12 == "object" && !Array.isArray(data12)){for(const key2 in data12){if(!((key2 === "mode") || (key2 === "entries"))){const err19 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data12.mode !== undefined){let data13 = data12.mode;if(!(((data13 === "auto") || (data13 === "all")) || (data13 === "hidden"))){const err20 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data12.entries !== undefined){let data14 = data12.entries;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){for(const key3 in data14){if(!(((((key3 === "default") || (key3 === "emphasis")) || (key3 === "security")) || (key3 === "dashed")) || (key3 === "return"))){const err21 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data14.default !== undefined){let data15 = data14.default;if(data15 && typeof data15 == "object" && !Array.isArray(data15)){if(Object.keys(data15).length < 1){const err22 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}for(const key4 in data15){if(!((key4 === "label") || (key4 === "visible"))){const err23 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data15.label !== undefined){let data16 = data15.label;if(typeof data16 === "string"){if(func3(data16) > 80){const err24 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(func3(data16) < 1){const err25 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data15.visible !== undefined){if(typeof data15.visible !== "boolean"){const err27 = {instancePath:instancePath+"/meta/legend/entries/default/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data14.emphasis !== undefined){let data18 = data14.emphasis;if(data18 && typeof data18 == "object" && !Array.isArray(data18)){if(Object.keys(data18).length < 1){const err29 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key5 in data18){if(!((key5 === "label") || (key5 === "visible"))){const err30 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data18.label !== undefined){let data19 = data18.label;if(typeof data19 === "string"){if(func3(data19) > 80){const err31 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func3(data19) < 1){const err32 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data18.visible !== undefined){if(typeof data18.visible !== "boolean"){const err34 = {instancePath:instancePath+"/meta/legend/entries/emphasis/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data14.security !== undefined){let data21 = data14.security;if(data21 && typeof data21 == "object" && !Array.isArray(data21)){if(Object.keys(data21).length < 1){const err36 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key6 in data21){if(!((key6 === "label") || (key6 === "visible"))){const err37 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data21.label !== undefined){let data22 = data21.label;if(typeof data22 === "string"){if(func3(data22) > 80){const err38 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(func3(data22) < 1){const err39 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data21.visible !== undefined){if(typeof data21.visible !== "boolean"){const err41 = {instancePath:instancePath+"/meta/legend/entries/security/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data14.dashed !== undefined){let data24 = data14.dashed;if(data24 && typeof data24 == "object" && !Array.isArray(data24)){if(Object.keys(data24).length < 1){const err43 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key7 in data24){if(!((key7 === "label") || (key7 === "visible"))){const err44 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data24.label !== undefined){let data25 = data24.label;if(typeof data25 === "string"){if(func3(data25) > 80){const err45 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func3(data25) < 1){const err46 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data24.visible !== undefined){if(typeof data24.visible !== "boolean"){const err48 = {instancePath:instancePath+"/meta/legend/entries/dashed/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data14.return !== undefined){let data27 = data14.return;if(data27 && typeof data27 == "object" && !Array.isArray(data27)){if(Object.keys(data27).length < 1){const err50 = {instancePath:instancePath+"/meta/legend/entries/return",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}for(const key8 in data27){if(!((key8 === "label") || (key8 === "visible"))){const err51 = {instancePath:instancePath+"/meta/legend/entries/return",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data27.label !== undefined){let data28 = data27.label;if(typeof data28 === "string"){if(func3(data28) > 80){const err52 = {instancePath:instancePath+"/meta/legend/entries/return/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(func3(data28) < 1){const err53 = {instancePath:instancePath+"/meta/legend/entries/return/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/meta/legend/entries/return/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data27.visible !== undefined){if(typeof data27.visible !== "boolean"){const err55 = {instancePath:instancePath+"/meta/legend/entries/return/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/return",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}}else {const err57 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}}else {const err58 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data2.viewBox !== undefined){let data30 = data2.viewBox;if(Array.isArray(data30)){if(data30.length > 2){const err59 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(data30.length < 2){const err60 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}const len0 = data30.length;if(len0 > 0){let data31 = data30[0];if((typeof data31 == "number") && (isFinite(data31))){if(data31 < 480 || isNaN(data31)){const err61 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 480},message:"must be >= 480"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}else {const err62 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(len0 > 1){let data32 = data30[1];if((typeof data32 == "number") && (isFinite(data32))){if(data32 < 480 || isNaN(data32)){const err63 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 480},message:"must be >= 480"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}else {const err64 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}const len1 = data30.length;if(!(len1 <= 2)){const err65 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}else {const err66 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}}else {const err67 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}if(data.participants !== undefined){let data33 = data.participants;if(Array.isArray(data33)){if(data33.length < 2){const err68 = {instancePath:instancePath+"/participants",schemaPath:"#/properties/participants/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}const len2 = data33.length;for(let i0=0; i0 80){const err79 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}if(!pattern17.test(data39)){const err80 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}var _valid1 = _errs97 === errors;valid27 = valid27 || _valid1;const _errs98 = errors;if(typeof data39 === "string"){if(!pattern18.test(data39)){const err81 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}var _valid1 = _errs98 === errors;valid27 = valid27 || _valid1;if(!valid27){const err82 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}else {errors = _errs96;if(vErrors !== null){if(_errs96){vErrors.length = _errs96;}else {vErrors = null;}}}if(typeof data39 === "string"){if(func3(data39) > 2048){const err83 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}if(func3(data39) < 1){const err84 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}else {const err85 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}var _valid0 = _errs94 === errors;if(_valid0){valid26 = true;passing0 = 0;}const _errs99 = errors;if(data39 && typeof data39 == "object" && !Array.isArray(data39)){if(data39.url === undefined){const err86 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}if(data39.sha256 === undefined){const err87 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}for(const key10 in data39){if(!((key10 === "url") || (key10 === "sha256"))){const err88 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key10},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data39.url !== undefined){let data40 = data39.url;if(typeof data40 === "string"){if(func3(data40) > 2048){const err89 = {instancePath:instancePath+"/participants/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}if(func3(data40) < 8){const err90 = {instancePath:instancePath+"/participants/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}if(!pattern18.test(data40)){const err91 = {instancePath:instancePath+"/participants/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}else {const err92 = {instancePath:instancePath+"/participants/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}if(data39.sha256 !== undefined){let data41 = data39.sha256;if(typeof data41 === "string"){if(!pattern20.test(data41)){const err93 = {instancePath:instancePath+"/participants/" + i0+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}else {const err94 = {instancePath:instancePath+"/participants/" + i0+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}}else {const err95 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}var _valid0 = _errs99 === errors;if(_valid0 && valid26){valid26 = false;passing0 = [passing0, 1];}else {if(_valid0){valid26 = true;passing0 = 1;}}if(!valid26){const err96 = {instancePath:instancePath+"/participants/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}else {errors = _errs93;if(vErrors !== null){if(_errs93){vErrors.length = _errs93;}else {vErrors = null;}}}}}else {const err97 = {instancePath:instancePath+"/participants/" + i0,schemaPath:"#/properties/participants/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}}else {const err98 = {instancePath:instancePath+"/participants",schemaPath:"#/properties/participants/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}if(data.segments !== undefined){let data42 = data.segments;if(Array.isArray(data42)){const len3 = data42.length;for(let i1=0; i1=", limit: 160},message:"must be >= 160"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}else {const err122 = {instancePath:instancePath+"/messages/" + i2+"/y",schemaPath:"#/properties/messages/items/properties/y/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}if(data48.label !== undefined){let data53 = data48.label;if(typeof data53 === "string"){if(func3(data53) < 1){const err123 = {instancePath:instancePath+"/messages/" + i2+"/label",schemaPath:"#/properties/messages/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}else {const err124 = {instancePath:instancePath+"/messages/" + i2+"/label",schemaPath:"#/properties/messages/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}if(data48.variant !== undefined){let data54 = data48.variant;if(!(((((data54 === "default") || (data54 === "emphasis")) || (data54 === "security")) || (data54 === "dashed")) || (data54 === "return"))){const err125 = {instancePath:instancePath+"/messages/" + i2+"/variant",schemaPath:"#/properties/messages/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema68.properties.messages.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}}if(data48.note !== undefined){if(typeof data48.note !== "string"){const err126 = {instancePath:instancePath+"/messages/" + i2+"/note",schemaPath:"#/properties/messages/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}}else {const err127 = {instancePath:instancePath+"/messages/" + i2,schemaPath:"#/properties/messages/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}}else {const err128 = {instancePath:instancePath+"/messages",schemaPath:"#/properties/messages/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}if(data.activations !== undefined){let data56 = data.activations;if(Array.isArray(data56)){const len5 = data56.length;for(let i3=0; i3 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate31.errors = vErrors;return errors === 0;}validate31.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};function validate30(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/dataflow.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate30.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.stages === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "stages"},message:"must have required property '"+"stages"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.nodes === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "nodes"},message:"must have required property '"+"nodes"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.flows === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "flows"},message:"must have required property '"+"flows"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!(((((((key0 === "schema_version") || (key0 === "diagram_type")) || (key0 === "meta")) || (key0 === "stages")) || (key0 === "nodes")) || (key0 === "flows")) || (key0 === "cards"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema_version !== undefined){if(1 !== data.schema_version){const err7 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.diagram_type !== undefined){if("dataflow" !== data.diagram_type){const err8 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "dataflow"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err9 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}for(const key1 in data2){if(!(func1.call(schema91.properties.meta.properties, key1))){const err10 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err11 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err13 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err14 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err15 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err16 = {instancePath:instancePath+"/meta/animation",schemaPath:"common.schema.json#/$defs/animation/enum",keyword:"enum",params:{allowedValues: schema70.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err17 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"common.schema.json#/$defs/visualPreset/enum",keyword:"enum",params:{allowedValues: schema71.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err18 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"common.schema.json#/$defs/qualityProfile/enum",keyword:"enum",params:{allowedValues: schema72.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data2.views !== undefined){if(!(validate31(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate31.errors : vErrors.concat(validate31.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data11 = data2.legend;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){for(const key2 in data11){if(!((key2 === "mode") || (key2 === "entries"))){const err19 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data11.mode !== undefined){let data12 = data11.mode;if(!(((data12 === "auto") || (data12 === "all")) || (data12 === "hidden"))){const err20 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data11.entries !== undefined){let data13 = data11.entries;if(data13 && typeof data13 == "object" && !Array.isArray(data13)){for(const key3 in data13){if(!(((((key3 === "default") || (key3 === "emphasis")) || (key3 === "security")) || (key3 === "dashed")) || (key3 === "database"))){const err21 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data13.default !== undefined){let data14 = data13.default;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){if(Object.keys(data14).length < 1){const err22 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}for(const key4 in data14){if(!((key4 === "label") || (key4 === "visible"))){const err23 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data14.label !== undefined){let data15 = data14.label;if(typeof data15 === "string"){if(func3(data15) > 80){const err24 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(func3(data15) < 1){const err25 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/meta/legend/entries/default/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data14.visible !== undefined){if(typeof data14.visible !== "boolean"){const err27 = {instancePath:instancePath+"/meta/legend/entries/default/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/meta/legend/entries/default",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data13.emphasis !== undefined){let data17 = data13.emphasis;if(data17 && typeof data17 == "object" && !Array.isArray(data17)){if(Object.keys(data17).length < 1){const err29 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key5 in data17){if(!((key5 === "label") || (key5 === "visible"))){const err30 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data17.label !== undefined){let data18 = data17.label;if(typeof data18 === "string"){if(func3(data18) > 80){const err31 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func3(data18) < 1){const err32 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/meta/legend/entries/emphasis/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data17.visible !== undefined){if(typeof data17.visible !== "boolean"){const err34 = {instancePath:instancePath+"/meta/legend/entries/emphasis/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/emphasis",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data13.security !== undefined){let data20 = data13.security;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){if(Object.keys(data20).length < 1){const err36 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key6 in data20){if(!((key6 === "label") || (key6 === "visible"))){const err37 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data20.label !== undefined){let data21 = data20.label;if(typeof data21 === "string"){if(func3(data21) > 80){const err38 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(func3(data21) < 1){const err39 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data20.visible !== undefined){if(typeof data20.visible !== "boolean"){const err41 = {instancePath:instancePath+"/meta/legend/entries/security/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data13.dashed !== undefined){let data23 = data13.dashed;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(Object.keys(data23).length < 1){const err43 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key7 in data23){if(!((key7 === "label") || (key7 === "visible"))){const err44 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data23.label !== undefined){let data24 = data23.label;if(typeof data24 === "string"){if(func3(data24) > 80){const err45 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func3(data24) < 1){const err46 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/meta/legend/entries/dashed/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data23.visible !== undefined){if(typeof data23.visible !== "boolean"){const err48 = {instancePath:instancePath+"/meta/legend/entries/dashed/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/dashed",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data13.database !== undefined){let data26 = data13.database;if(data26 && typeof data26 == "object" && !Array.isArray(data26)){if(Object.keys(data26).length < 1){const err50 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}for(const key8 in data26){if(!((key8 === "label") || (key8 === "visible"))){const err51 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data26.label !== undefined){let data27 = data26.label;if(typeof data27 === "string"){if(func3(data27) > 80){const err52 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(func3(data27) < 1){const err53 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data26.visible !== undefined){if(typeof data26.visible !== "boolean"){const err55 = {instancePath:instancePath+"/meta/legend/entries/database/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}}else {const err57 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}}else {const err58 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data2.viewBox !== undefined){let data29 = data2.viewBox;if(Array.isArray(data29)){if(data29.length > 2){const err59 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(data29.length < 2){const err60 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}const len0 = data29.length;if(len0 > 0){let data30 = data29[0];if((typeof data30 == "number") && (isFinite(data30))){if(data30 < 360 || isNaN(data30)){const err61 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 360},message:"must be >= 360"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}else {const err62 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(len0 > 1){let data31 = data29[1];if((typeof data31 == "number") && (isFinite(data31))){if(data31 < 360 || isNaN(data31)){const err63 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 360},message:"must be >= 360"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}else {const err64 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}const len1 = data29.length;if(!(len1 <= 2)){const err65 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}else {const err66 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}}}else {const err67 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}if(data.stages !== undefined){let data32 = data.stages;if(Array.isArray(data32)){if(data32.length > 5){const err68 = {instancePath:instancePath+"/stages",schemaPath:"#/properties/stages/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}if(data32.length < 2){const err69 = {instancePath:instancePath+"/stages",schemaPath:"#/properties/stages/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}const len2 = data32.length;for(let i0=0; i0 80){const err90 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}if(!pattern17.test(data42)){const err91 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}var _valid1 = _errs105 === errors;valid30 = valid30 || _valid1;const _errs106 = errors;if(typeof data42 === "string"){if(!pattern18.test(data42)){const err92 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}var _valid1 = _errs106 === errors;valid30 = valid30 || _valid1;if(!valid30){const err93 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}else {errors = _errs104;if(vErrors !== null){if(_errs104){vErrors.length = _errs104;}else {vErrors = null;}}}if(typeof data42 === "string"){if(func3(data42) > 2048){const err94 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}if(func3(data42) < 1){const err95 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}else {const err96 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}var _valid0 = _errs102 === errors;if(_valid0){valid29 = true;passing0 = 0;}const _errs107 = errors;if(data42 && typeof data42 == "object" && !Array.isArray(data42)){if(data42.url === undefined){const err97 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}if(data42.sha256 === undefined){const err98 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}for(const key11 in data42){if(!((key11 === "url") || (key11 === "sha256"))){const err99 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}if(data42.url !== undefined){let data43 = data42.url;if(typeof data43 === "string"){if(func3(data43) > 2048){const err100 = {instancePath:instancePath+"/nodes/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}if(func3(data43) < 8){const err101 = {instancePath:instancePath+"/nodes/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}if(!pattern18.test(data43)){const err102 = {instancePath:instancePath+"/nodes/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}else {const err103 = {instancePath:instancePath+"/nodes/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}if(data42.sha256 !== undefined){let data44 = data42.sha256;if(typeof data44 === "string"){if(!pattern20.test(data44)){const err104 = {instancePath:instancePath+"/nodes/" + i1+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}else {const err105 = {instancePath:instancePath+"/nodes/" + i1+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}}else {const err106 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}var _valid0 = _errs107 === errors;if(_valid0 && valid29){valid29 = false;passing0 = [passing0, 1];}else {if(_valid0){valid29 = true;passing0 = 1;}}if(!valid29){const err107 = {instancePath:instancePath+"/nodes/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}else {errors = _errs101;if(vErrors !== null){if(_errs101){vErrors.length = _errs101;}else {vErrors = null;}}}}if(data36.stage !== undefined){let data45 = data36.stage;if(!(((typeof data45 == "number") && (!(data45 % 1) && !isNaN(data45))) && (isFinite(data45)))){const err108 = {instancePath:instancePath+"/nodes/" + i1+"/stage",schemaPath:"#/properties/nodes/items/properties/stage/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}if((typeof data45 == "number") && (isFinite(data45))){if(data45 < 0 || isNaN(data45)){const err109 = {instancePath:instancePath+"/nodes/" + i1+"/stage",schemaPath:"#/properties/nodes/items/properties/stage/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}}if(data36.row !== undefined){let data46 = data36.row;if(!(((typeof data46 == "number") && (!(data46 % 1) && !isNaN(data46))) && (isFinite(data46)))){const err110 = {instancePath:instancePath+"/nodes/" + i1+"/row",schemaPath:"#/properties/nodes/items/properties/row/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}if((typeof data46 == "number") && (isFinite(data46))){if(data46 < 0 || isNaN(data46)){const err111 = {instancePath:instancePath+"/nodes/" + i1+"/row",schemaPath:"#/properties/nodes/items/properties/row/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}}}if(data36.width !== undefined){let data47 = data36.width;if((typeof data47 == "number") && (isFinite(data47))){if(data47 < 48 || isNaN(data47)){const err112 = {instancePath:instancePath+"/nodes/" + i1+"/width",schemaPath:"#/properties/nodes/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 48},message:"must be >= 48"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}else {const err113 = {instancePath:instancePath+"/nodes/" + i1+"/width",schemaPath:"#/properties/nodes/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}if(data36.height !== undefined){let data48 = data36.height;if((typeof data48 == "number") && (isFinite(data48))){if(data48 < 36 || isNaN(data48)){const err114 = {instancePath:instancePath+"/nodes/" + i1+"/height",schemaPath:"#/properties/nodes/items/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 36},message:"must be >= 36"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}}else {const err115 = {instancePath:instancePath+"/nodes/" + i1+"/height",schemaPath:"#/properties/nodes/items/properties/height/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}if(data36.yOffset !== undefined){let data49 = data36.yOffset;if(!((typeof data49 == "number") && (isFinite(data49)))){const err116 = {instancePath:instancePath+"/nodes/" + i1+"/yOffset",schemaPath:"#/properties/nodes/items/properties/yOffset/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}}else {const err117 = {instancePath:instancePath+"/nodes/" + i1,schemaPath:"#/properties/nodes/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}}}else {const err118 = {instancePath:instancePath+"/nodes",schemaPath:"#/properties/nodes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}}if(data.flows !== undefined){let data50 = data.flows;if(Array.isArray(data50)){const len4 = data50.length;for(let i2=0; i2 2){const err138 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}if(data63.length < 2){const err139 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}const len5 = data63.length;if(len5 > 0){let data64 = data63[0];if(!((typeof data64 == "number") && (isFinite(data64)))){const err140 = {instancePath:instancePath+"/flows/" + i2+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}}if(len5 > 1){let data65 = data63[1];if(!((typeof data65 == "number") && (isFinite(data65)))){const err141 = {instancePath:instancePath+"/flows/" + i2+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}const len6 = data63.length;if(!(len6 <= 2)){const err142 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}else {const err143 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}if(data51.labelDx !== undefined){let data66 = data51.labelDx;if(!((typeof data66 == "number") && (isFinite(data66)))){const err144 = {instancePath:instancePath+"/flows/" + i2+"/labelDx",schemaPath:"#/properties/flows/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}if(data51.labelDy !== undefined){let data67 = data51.labelDy;if(!((typeof data67 == "number") && (isFinite(data67)))){const err145 = {instancePath:instancePath+"/flows/" + i2+"/labelDy",schemaPath:"#/properties/flows/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}if(data51.labelSegment !== undefined){let data68 = data51.labelSegment;if(!(((typeof data68 == "number") && (!(data68 % 1) && !isNaN(data68))) && (isFinite(data68)))){const err146 = {instancePath:instancePath+"/flows/" + i2+"/labelSegment",schemaPath:"#/properties/flows/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}if((typeof data68 == "number") && (isFinite(data68))){if(data68 < 0 || isNaN(data68)){const err147 = {instancePath:instancePath+"/flows/" + i2+"/labelSegment",schemaPath:"#/properties/flows/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}}}if(data51.via !== undefined){let data69 = data51.via;if(Array.isArray(data69)){const len7 = data69.length;for(let i3=0; i3 2){const err148 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}if(data70.length < 2){const err149 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}const len8 = data70.length;if(len8 > 0){let data71 = data70[0];if(!((typeof data71 == "number") && (isFinite(data71)))){const err150 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err150];}else {vErrors.push(err150);}errors++;}}if(len8 > 1){let data72 = data70[1];if(!((typeof data72 == "number") && (isFinite(data72)))){const err151 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err151];}else {vErrors.push(err151);}errors++;}}const len9 = data70.length;if(!(len9 <= 2)){const err152 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err152];}else {vErrors.push(err152);}errors++;}}else {const err153 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err153];}else {vErrors.push(err153);}errors++;}}}else {const err154 = {instancePath:instancePath+"/flows/" + i2+"/via",schemaPath:"#/properties/flows/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err154];}else {vErrors.push(err154);}errors++;}}if(data51.width !== undefined){let data73 = data51.width;if((typeof data73 == "number") && (isFinite(data73))){if(data73 < 0.5 || isNaN(data73)){const err155 = {instancePath:instancePath+"/flows/" + i2+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err155];}else {vErrors.push(err155);}errors++;}}else {const err156 = {instancePath:instancePath+"/flows/" + i2+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err156];}else {vErrors.push(err156);}errors++;}}}else {const err157 = {instancePath:instancePath+"/flows/" + i2,schemaPath:"#/properties/flows/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err157];}else {vErrors.push(err157);}errors++;}}}else {const err158 = {instancePath:instancePath+"/flows",schemaPath:"#/properties/flows/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err158];}else {vErrors.push(err158);}errors++;}}if(data.cards !== undefined){let data74 = data.cards;if(Array.isArray(data74)){const len10 = data74.length;for(let i4=0; i4 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate34.errors = vErrors;return errors === 0;}validate34.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};function validate33(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/lifecycle.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate33.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.lanes === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "lanes"},message:"must have required property '"+"lanes"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.states === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "states"},message:"must have required property '"+"states"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.transitions === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "transitions"},message:"must have required property '"+"transitions"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!(((((((key0 === "schema_version") || (key0 === "diagram_type")) || (key0 === "meta")) || (key0 === "lanes")) || (key0 === "states")) || (key0 === "transitions")) || (key0 === "cards"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema_version !== undefined){if(1 !== data.schema_version){const err7 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.diagram_type !== undefined){if("lifecycle" !== data.diagram_type){const err8 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "lifecycle"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err9 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}for(const key1 in data2){if(!(func1.call(schema118.properties.meta.properties, key1))){const err10 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err11 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err13 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err14 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err15 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err16 = {instancePath:instancePath+"/meta/animation",schemaPath:"common.schema.json#/$defs/animation/enum",keyword:"enum",params:{allowedValues: schema70.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err17 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"common.schema.json#/$defs/visualPreset/enum",keyword:"enum",params:{allowedValues: schema71.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err18 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"common.schema.json#/$defs/qualityProfile/enum",keyword:"enum",params:{allowedValues: schema72.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(data2.views !== undefined){if(!(validate34(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate34.errors : vErrors.concat(validate34.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data11 = data2.legend;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){for(const key2 in data11){if(!((key2 === "mode") || (key2 === "entries"))){const err19 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data11.mode !== undefined){let data12 = data11.mode;if(!(((data12 === "auto") || (data12 === "all")) || (data12 === "hidden"))){const err20 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data11.entries !== undefined){let data13 = data11.entries;if(data13 && typeof data13 == "object" && !Array.isArray(data13)){for(const key3 in data13){if(!((((((((key3 === "start") || (key3 === "active")) || (key3 === "waiting")) || (key3 === "decision")) || (key3 === "success")) || (key3 === "failure")) || (key3 === "neutral")) || (key3 === "external"))){const err21 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data13.start !== undefined){let data14 = data13.start;if(data14 && typeof data14 == "object" && !Array.isArray(data14)){if(Object.keys(data14).length < 1){const err22 = {instancePath:instancePath+"/meta/legend/entries/start",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}for(const key4 in data14){if(!((key4 === "label") || (key4 === "visible"))){const err23 = {instancePath:instancePath+"/meta/legend/entries/start",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data14.label !== undefined){let data15 = data14.label;if(typeof data15 === "string"){if(func3(data15) > 80){const err24 = {instancePath:instancePath+"/meta/legend/entries/start/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}if(func3(data15) < 1){const err25 = {instancePath:instancePath+"/meta/legend/entries/start/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}else {const err26 = {instancePath:instancePath+"/meta/legend/entries/start/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data14.visible !== undefined){if(typeof data14.visible !== "boolean"){const err27 = {instancePath:instancePath+"/meta/legend/entries/start/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}}else {const err28 = {instancePath:instancePath+"/meta/legend/entries/start",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data13.active !== undefined){let data17 = data13.active;if(data17 && typeof data17 == "object" && !Array.isArray(data17)){if(Object.keys(data17).length < 1){const err29 = {instancePath:instancePath+"/meta/legend/entries/active",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key5 in data17){if(!((key5 === "label") || (key5 === "visible"))){const err30 = {instancePath:instancePath+"/meta/legend/entries/active",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data17.label !== undefined){let data18 = data17.label;if(typeof data18 === "string"){if(func3(data18) > 80){const err31 = {instancePath:instancePath+"/meta/legend/entries/active/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func3(data18) < 1){const err32 = {instancePath:instancePath+"/meta/legend/entries/active/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/meta/legend/entries/active/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data17.visible !== undefined){if(typeof data17.visible !== "boolean"){const err34 = {instancePath:instancePath+"/meta/legend/entries/active/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/active",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data13.waiting !== undefined){let data20 = data13.waiting;if(data20 && typeof data20 == "object" && !Array.isArray(data20)){if(Object.keys(data20).length < 1){const err36 = {instancePath:instancePath+"/meta/legend/entries/waiting",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key6 in data20){if(!((key6 === "label") || (key6 === "visible"))){const err37 = {instancePath:instancePath+"/meta/legend/entries/waiting",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data20.label !== undefined){let data21 = data20.label;if(typeof data21 === "string"){if(func3(data21) > 80){const err38 = {instancePath:instancePath+"/meta/legend/entries/waiting/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(func3(data21) < 1){const err39 = {instancePath:instancePath+"/meta/legend/entries/waiting/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/meta/legend/entries/waiting/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data20.visible !== undefined){if(typeof data20.visible !== "boolean"){const err41 = {instancePath:instancePath+"/meta/legend/entries/waiting/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/waiting",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data13.decision !== undefined){let data23 = data13.decision;if(data23 && typeof data23 == "object" && !Array.isArray(data23)){if(Object.keys(data23).length < 1){const err43 = {instancePath:instancePath+"/meta/legend/entries/decision",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key7 in data23){if(!((key7 === "label") || (key7 === "visible"))){const err44 = {instancePath:instancePath+"/meta/legend/entries/decision",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data23.label !== undefined){let data24 = data23.label;if(typeof data24 === "string"){if(func3(data24) > 80){const err45 = {instancePath:instancePath+"/meta/legend/entries/decision/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func3(data24) < 1){const err46 = {instancePath:instancePath+"/meta/legend/entries/decision/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/meta/legend/entries/decision/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data23.visible !== undefined){if(typeof data23.visible !== "boolean"){const err48 = {instancePath:instancePath+"/meta/legend/entries/decision/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/decision",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data13.success !== undefined){let data26 = data13.success;if(data26 && typeof data26 == "object" && !Array.isArray(data26)){if(Object.keys(data26).length < 1){const err50 = {instancePath:instancePath+"/meta/legend/entries/success",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}for(const key8 in data26){if(!((key8 === "label") || (key8 === "visible"))){const err51 = {instancePath:instancePath+"/meta/legend/entries/success",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data26.label !== undefined){let data27 = data26.label;if(typeof data27 === "string"){if(func3(data27) > 80){const err52 = {instancePath:instancePath+"/meta/legend/entries/success/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(func3(data27) < 1){const err53 = {instancePath:instancePath+"/meta/legend/entries/success/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/meta/legend/entries/success/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data26.visible !== undefined){if(typeof data26.visible !== "boolean"){const err55 = {instancePath:instancePath+"/meta/legend/entries/success/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/success",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data13.failure !== undefined){let data29 = data13.failure;if(data29 && typeof data29 == "object" && !Array.isArray(data29)){if(Object.keys(data29).length < 1){const err57 = {instancePath:instancePath+"/meta/legend/entries/failure",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}for(const key9 in data29){if(!((key9 === "label") || (key9 === "visible"))){const err58 = {instancePath:instancePath+"/meta/legend/entries/failure",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key9},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data29.label !== undefined){let data30 = data29.label;if(typeof data30 === "string"){if(func3(data30) > 80){const err59 = {instancePath:instancePath+"/meta/legend/entries/failure/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(func3(data30) < 1){const err60 = {instancePath:instancePath+"/meta/legend/entries/failure/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}else {const err61 = {instancePath:instancePath+"/meta/legend/entries/failure/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data29.visible !== undefined){if(typeof data29.visible !== "boolean"){const err62 = {instancePath:instancePath+"/meta/legend/entries/failure/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}}else {const err63 = {instancePath:instancePath+"/meta/legend/entries/failure",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data13.neutral !== undefined){let data32 = data13.neutral;if(data32 && typeof data32 == "object" && !Array.isArray(data32)){if(Object.keys(data32).length < 1){const err64 = {instancePath:instancePath+"/meta/legend/entries/neutral",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}for(const key10 in data32){if(!((key10 === "label") || (key10 === "visible"))){const err65 = {instancePath:instancePath+"/meta/legend/entries/neutral",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key10},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data32.label !== undefined){let data33 = data32.label;if(typeof data33 === "string"){if(func3(data33) > 80){const err66 = {instancePath:instancePath+"/meta/legend/entries/neutral/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(func3(data33) < 1){const err67 = {instancePath:instancePath+"/meta/legend/entries/neutral/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}else {const err68 = {instancePath:instancePath+"/meta/legend/entries/neutral/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}if(data32.visible !== undefined){if(typeof data32.visible !== "boolean"){const err69 = {instancePath:instancePath+"/meta/legend/entries/neutral/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}}else {const err70 = {instancePath:instancePath+"/meta/legend/entries/neutral",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}if(data13.external !== undefined){let data35 = data13.external;if(data35 && typeof data35 == "object" && !Array.isArray(data35)){if(Object.keys(data35).length < 1){const err71 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}for(const key11 in data35){if(!((key11 === "label") || (key11 === "visible"))){const err72 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data35.label !== undefined){let data36 = data35.label;if(typeof data36 === "string"){if(func3(data36) > 80){const err73 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(func3(data36) < 1){const err74 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}else {const err75 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data35.visible !== undefined){if(typeof data35.visible !== "boolean"){const err76 = {instancePath:instancePath+"/meta/legend/entries/external/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}}else {const err77 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}}else {const err78 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}}else {const err79 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}if(data2.viewBox !== undefined){let data38 = data2.viewBox;if(Array.isArray(data38)){if(data38.length > 2){const err80 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}if(data38.length < 2){const err81 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}const len0 = data38.length;if(len0 > 0){let data39 = data38[0];if((typeof data39 == "number") && (isFinite(data39))){if(data39 < 420 || isNaN(data39)){const err82 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 420},message:"must be >= 420"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}else {const err83 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}if(len0 > 1){let data40 = data38[1];if((typeof data40 == "number") && (isFinite(data40))){if(data40 < 566 || isNaN(data40)){const err84 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 566},message:"must be >= 566"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}else {const err85 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}const len1 = data38.length;if(!(len1 <= 2)){const err86 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}else {const err87 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}}else {const err88 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data.lanes !== undefined){let data41 = data.lanes;if(Array.isArray(data41)){if(data41.length > 4){const err89 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/maxItems",keyword:"maxItems",params:{limit: 4},message:"must NOT have more than 4 items"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}if(data41.length < 1){const err90 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}const len2 = data41.length;for(let i0=0; i0 80){const err114 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}if(!pattern17.test(data52)){const err115 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}var _valid1 = _errs131 === errors;valid36 = valid36 || _valid1;const _errs132 = errors;if(typeof data52 === "string"){if(!pattern18.test(data52)){const err116 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}var _valid1 = _errs132 === errors;valid36 = valid36 || _valid1;if(!valid36){const err117 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}else {errors = _errs130;if(vErrors !== null){if(_errs130){vErrors.length = _errs130;}else {vErrors = null;}}}if(typeof data52 === "string"){if(func3(data52) > 2048){const err118 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}if(func3(data52) < 1){const err119 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}else {const err120 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}var _valid0 = _errs128 === errors;if(_valid0){valid35 = true;passing0 = 0;}const _errs133 = errors;if(data52 && typeof data52 == "object" && !Array.isArray(data52)){if(data52.url === undefined){const err121 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}if(data52.sha256 === undefined){const err122 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}for(const key14 in data52){if(!((key14 === "url") || (key14 === "sha256"))){const err123 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key14},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}if(data52.url !== undefined){let data53 = data52.url;if(typeof data53 === "string"){if(func3(data53) > 2048){const err124 = {instancePath:instancePath+"/states/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}if(func3(data53) < 8){const err125 = {instancePath:instancePath+"/states/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}if(!pattern18.test(data53)){const err126 = {instancePath:instancePath+"/states/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}}else {const err127 = {instancePath:instancePath+"/states/" + i1+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}if(data52.sha256 !== undefined){let data54 = data52.sha256;if(typeof data54 === "string"){if(!pattern20.test(data54)){const err128 = {instancePath:instancePath+"/states/" + i1+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}else {const err129 = {instancePath:instancePath+"/states/" + i1+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}}}else {const err130 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}var _valid0 = _errs133 === errors;if(_valid0 && valid35){valid35 = false;passing0 = [passing0, 1];}else {if(_valid0){valid35 = true;passing0 = 1;}}if(!valid35){const err131 = {instancePath:instancePath+"/states/" + i1+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}else {errors = _errs127;if(vErrors !== null){if(_errs127){vErrors.length = _errs127;}else {vErrors = null;}}}}if(data46.step !== undefined){if(typeof data46.step !== "string"){const err132 = {instancePath:instancePath+"/states/" + i1+"/step",schemaPath:"#/properties/states/items/properties/step/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}}if(data46.lane !== undefined){let data56 = data46.lane;if(typeof data56 === "string"){if(!pattern4.test(data56)){const err133 = {instancePath:instancePath+"/states/" + i1+"/lane",schemaPath:"common.schema.json#/$defs/id/pattern",keyword:"pattern",params:{pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$"},message:"must match pattern \""+"^[a-zA-Z][a-zA-Z0-9_-]*$"+"\""};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}}else {const err134 = {instancePath:instancePath+"/states/" + i1+"/lane",schemaPath:"common.schema.json#/$defs/id/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}if(data46.col !== undefined){let data57 = data46.col;if(!(((typeof data57 == "number") && (!(data57 % 1) && !isNaN(data57))) && (isFinite(data57)))){const err135 = {instancePath:instancePath+"/states/" + i1+"/col",schemaPath:"#/properties/states/items/properties/col/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}if((typeof data57 == "number") && (isFinite(data57))){if(data57 > 4 || isNaN(data57)){const err136 = {instancePath:instancePath+"/states/" + i1+"/col",schemaPath:"#/properties/states/items/properties/col/maximum",keyword:"maximum",params:{comparison: "<=", limit: 4},message:"must be <= 4"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}if(data57 < 0 || isNaN(data57)){const err137 = {instancePath:instancePath+"/states/" + i1+"/col",schemaPath:"#/properties/states/items/properties/col/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}}if(data46.width !== undefined){let data58 = data46.width;if((typeof data58 == "number") && (isFinite(data58))){if(data58 < 48 || isNaN(data58)){const err138 = {instancePath:instancePath+"/states/" + i1+"/width",schemaPath:"#/properties/states/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 48},message:"must be >= 48"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}else {const err139 = {instancePath:instancePath+"/states/" + i1+"/width",schemaPath:"#/properties/states/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}if(data46.height !== undefined){let data59 = data46.height;if((typeof data59 == "number") && (isFinite(data59))){if(data59 < 36 || isNaN(data59)){const err140 = {instancePath:instancePath+"/states/" + i1+"/height",schemaPath:"#/properties/states/items/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 36},message:"must be >= 36"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}}else {const err141 = {instancePath:instancePath+"/states/" + i1+"/height",schemaPath:"#/properties/states/items/properties/height/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}if(data46.yOffset !== undefined){let data60 = data46.yOffset;if(!((typeof data60 == "number") && (isFinite(data60)))){const err142 = {instancePath:instancePath+"/states/" + i1+"/yOffset",schemaPath:"#/properties/states/items/properties/yOffset/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}}else {const err143 = {instancePath:instancePath+"/states/" + i1,schemaPath:"#/properties/states/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}}else {const err144 = {instancePath:instancePath+"/states",schemaPath:"#/properties/states/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}}if(data.transitions !== undefined){let data61 = data.transitions;if(Array.isArray(data61)){const len4 = data61.length;for(let i2=0; i2=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err162];}else {vErrors.push(err162);}errors++;}}else {const err163 = {instancePath:instancePath+"/transitions/" + i2+"/cornerRadius",schemaPath:"#/properties/transitions/items/properties/cornerRadius/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err163];}else {vErrors.push(err163);}errors++;}}if(data62.labelAt !== undefined){let data75 = data62.labelAt;if(Array.isArray(data75)){if(data75.length > 2){const err164 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err164];}else {vErrors.push(err164);}errors++;}if(data75.length < 2){const err165 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err165];}else {vErrors.push(err165);}errors++;}const len5 = data75.length;if(len5 > 0){let data76 = data75[0];if(!((typeof data76 == "number") && (isFinite(data76)))){const err166 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err166];}else {vErrors.push(err166);}errors++;}}if(len5 > 1){let data77 = data75[1];if(!((typeof data77 == "number") && (isFinite(data77)))){const err167 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err167];}else {vErrors.push(err167);}errors++;}}const len6 = data75.length;if(!(len6 <= 2)){const err168 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err168];}else {vErrors.push(err168);}errors++;}}else {const err169 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err169];}else {vErrors.push(err169);}errors++;}}if(data62.labelDx !== undefined){let data78 = data62.labelDx;if(!((typeof data78 == "number") && (isFinite(data78)))){const err170 = {instancePath:instancePath+"/transitions/" + i2+"/labelDx",schemaPath:"#/properties/transitions/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err170];}else {vErrors.push(err170);}errors++;}}if(data62.labelDy !== undefined){let data79 = data62.labelDy;if(!((typeof data79 == "number") && (isFinite(data79)))){const err171 = {instancePath:instancePath+"/transitions/" + i2+"/labelDy",schemaPath:"#/properties/transitions/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err171];}else {vErrors.push(err171);}errors++;}}if(data62.labelSegment !== undefined){let data80 = data62.labelSegment;if(!(((typeof data80 == "number") && (!(data80 % 1) && !isNaN(data80))) && (isFinite(data80)))){const err172 = {instancePath:instancePath+"/transitions/" + i2+"/labelSegment",schemaPath:"#/properties/transitions/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err172];}else {vErrors.push(err172);}errors++;}if((typeof data80 == "number") && (isFinite(data80))){if(data80 < 0 || isNaN(data80)){const err173 = {instancePath:instancePath+"/transitions/" + i2+"/labelSegment",schemaPath:"#/properties/transitions/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err173];}else {vErrors.push(err173);}errors++;}}}if(data62.via !== undefined){let data81 = data62.via;if(Array.isArray(data81)){const len7 = data81.length;for(let i3=0; i3 2){const err174 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err174];}else {vErrors.push(err174);}errors++;}if(data82.length < 2){const err175 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err175];}else {vErrors.push(err175);}errors++;}const len8 = data82.length;if(len8 > 0){let data83 = data82[0];if(!((typeof data83 == "number") && (isFinite(data83)))){const err176 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err176];}else {vErrors.push(err176);}errors++;}}if(len8 > 1){let data84 = data82[1];if(!((typeof data84 == "number") && (isFinite(data84)))){const err177 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err177];}else {vErrors.push(err177);}errors++;}}const len9 = data82.length;if(!(len9 <= 2)){const err178 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err178];}else {vErrors.push(err178);}errors++;}}else {const err179 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err179];}else {vErrors.push(err179);}errors++;}}}else {const err180 = {instancePath:instancePath+"/transitions/" + i2+"/via",schemaPath:"#/properties/transitions/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err180];}else {vErrors.push(err180);}errors++;}}if(data62.width !== undefined){let data85 = data62.width;if((typeof data85 == "number") && (isFinite(data85))){if(data85 < 0.5 || isNaN(data85)){const err181 = {instancePath:instancePath+"/transitions/" + i2+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err181];}else {vErrors.push(err181);}errors++;}}else {const err182 = {instancePath:instancePath+"/transitions/" + i2+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err182];}else {vErrors.push(err182);}errors++;}}}else {const err183 = {instancePath:instancePath+"/transitions/" + i2,schemaPath:"#/properties/transitions/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err183];}else {vErrors.push(err183);}errors++;}}}else {const err184 = {instancePath:instancePath+"/transitions",schemaPath:"#/properties/transitions/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err184];}else {vErrors.push(err184);}errors++;}}if(data.cards !== undefined){let data86 = data.cards;if(Array.isArray(data86)){const len10 = data86.length;for(let i4=0; i4 5){const err0 = {instancePath,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}const len0 = data.length;for(let i0=0; i0 48){const err7 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}if(func3(data2) < 1){const err8 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}else {const err9 = {instancePath:instancePath+"/" + i0+"/label",schemaPath:"#/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}if(data0.focus !== undefined){let data3 = data0.focus;if(Array.isArray(data3)){if(data3.length < 1){const err10 = {instancePath:instancePath+"/" + i0+"/focus",schemaPath:"#/items/properties/focus/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}const len1 = data3.length;for(let i1=0; i1 140){const err14 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/maxLength",keyword:"maxLength",params:{limit: 140},message:"must NOT have more than 140 characters"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/" + i0+"/note",schemaPath:"#/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/" + i0,schemaPath:"#/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}}else {const err17 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}validate37.errors = vErrors;return errors === 0;}validate37.evaluated = {"items":true,"dynamicProps":false,"dynamicItems":false};function validate36(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/architecture.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate36.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.components === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "components"},message:"must have required property '"+"components"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}for(const key0 in data){if(!((((((((key0 === "schema_version") || (key0 === "diagram_type")) || (key0 === "meta")) || (key0 === "layout")) || (key0 === "components")) || (key0 === "boundaries")) || (key0 === "connections")) || (key0 === "cards"))){const err4 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}if(data.schema_version !== undefined){if(1 !== data.schema_version){const err5 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}if(data.diagram_type !== undefined){if("architecture" !== data.diagram_type){const err6 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "architecture"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err7 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}for(const key1 in data2){if(!(func1.call(schema149.properties.meta.properties, key1))){const err8 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func3(data3) < 1){const err9 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data2.locale !== undefined){let data4 = data2.locale;if(!((data4 === "en") || (data4 === "zh-CN"))){const err11 = {instancePath:instancePath+"/meta/locale",schemaPath:"common.schema.json#/$defs/locale/enum",keyword:"enum",params:{allowedValues: schema33.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err12 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err13 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.animation !== undefined){let data7 = data2.animation;if(!((data7 === "trace") || (data7 === "none"))){const err14 = {instancePath:instancePath+"/meta/animation",schemaPath:"common.schema.json#/$defs/animation/enum",keyword:"enum",params:{allowedValues: schema70.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.visual_preset !== undefined){let data8 = data2.visual_preset;if(!((((data8 === "classic") || (data8 === "signal-flow")) || (data8 === "blueprint")) || (data8 === "editorial"))){const err15 = {instancePath:instancePath+"/meta/visual_preset",schemaPath:"common.schema.json#/$defs/visualPreset/enum",keyword:"enum",params:{allowedValues: schema71.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.quality_profile !== undefined){let data9 = data2.quality_profile;if(!((data9 === "standard") || (data9 === "showcase"))){const err16 = {instancePath:instancePath+"/meta/quality_profile",schemaPath:"common.schema.json#/$defs/qualityProfile/enum",keyword:"enum",params:{allowedValues: schema72.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data2.engineering_profile !== undefined){if(!(data2.engineering_profile === "deployment-ownership")){const err17 = {instancePath:instancePath+"/meta/engineering_profile",schemaPath:"#/properties/meta/properties/engineering_profile/enum",keyword:"enum",params:{allowedValues: schema149.properties.meta.properties.engineering_profile.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(data2.repository !== undefined){let data11 = data2.repository;if(data11 && typeof data11 == "object" && !Array.isArray(data11)){if(data11.url === undefined){const err18 = {instancePath:instancePath+"/meta/repository",schemaPath:"#/properties/meta/properties/repository/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}if(data11.revision === undefined){const err19 = {instancePath:instancePath+"/meta/repository",schemaPath:"#/properties/meta/properties/repository/required",keyword:"required",params:{missingProperty: "revision"},message:"must have required property '"+"revision"+"'"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}for(const key2 in data11){if(!((key2 === "url") || (key2 === "revision"))){const err20 = {instancePath:instancePath+"/meta/repository",schemaPath:"#/properties/meta/properties/repository/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}if(data11.url !== undefined){let data12 = data11.url;if(typeof data12 === "string"){if(!pattern57.test(data12)){const err21 = {instancePath:instancePath+"/meta/repository/url",schemaPath:"#/properties/meta/properties/repository/properties/url/pattern",keyword:"pattern",params:{pattern: "^https://github\\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+(?:\\.git)?/?$"},message:"must match pattern \""+"^https://github\\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+(?:\\.git)?/?$"+"\""};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/meta/repository/url",schemaPath:"#/properties/meta/properties/repository/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data11.revision !== undefined){let data13 = data11.revision;if(typeof data13 === "string"){if(!pattern58.test(data13)){const err23 = {instancePath:instancePath+"/meta/repository/revision",schemaPath:"#/properties/meta/properties/repository/properties/revision/pattern",keyword:"pattern",params:{pattern: "^[a-fA-F0-9]{40}$"},message:"must match pattern \""+"^[a-fA-F0-9]{40}$"+"\""};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}else {const err24 = {instancePath:instancePath+"/meta/repository/revision",schemaPath:"#/properties/meta/properties/repository/properties/revision/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}}else {const err25 = {instancePath:instancePath+"/meta/repository",schemaPath:"#/properties/meta/properties/repository/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data2.views !== undefined){if(!(validate37(data2.views, {instancePath:instancePath+"/meta/views",parentData:data2,parentDataProperty:"views",rootData,dynamicAnchors}))){vErrors = vErrors === null ? validate37.errors : vErrors.concat(validate37.errors);errors = vErrors.length;}}if(data2.legend !== undefined){let data15 = data2.legend;if(data15 && typeof data15 == "object" && !Array.isArray(data15)){for(const key3 in data15){if(!((key3 === "mode") || (key3 === "entries"))){const err26 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}}if(data15.mode !== undefined){let data16 = data15.mode;if(!(((data16 === "auto") || (data16 === "all")) || (data16 === "hidden"))){const err27 = {instancePath:instancePath+"/meta/legend/mode",schemaPath:"common.schema.json#/$defs/legendMode/enum",keyword:"enum",params:{allowedValues: schema37.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}}if(data15.entries !== undefined){let data17 = data15.entries;if(data17 && typeof data17 == "object" && !Array.isArray(data17)){for(const key4 in data17){if(!(((((((key4 === "frontend") || (key4 === "backend")) || (key4 === "database")) || (key4 === "cloud")) || (key4 === "security")) || (key4 === "messagebus")) || (key4 === "external"))){const err28 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data17.frontend !== undefined){let data18 = data17.frontend;if(data18 && typeof data18 == "object" && !Array.isArray(data18)){if(Object.keys(data18).length < 1){const err29 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}for(const key5 in data18){if(!((key5 === "label") || (key5 === "visible"))){const err30 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key5},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}if(data18.label !== undefined){let data19 = data18.label;if(typeof data19 === "string"){if(func3(data19) > 80){const err31 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}if(func3(data19) < 1){const err32 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}else {const err33 = {instancePath:instancePath+"/meta/legend/entries/frontend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}}if(data18.visible !== undefined){if(typeof data18.visible !== "boolean"){const err34 = {instancePath:instancePath+"/meta/legend/entries/frontend/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}else {const err35 = {instancePath:instancePath+"/meta/legend/entries/frontend",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}if(data17.backend !== undefined){let data21 = data17.backend;if(data21 && typeof data21 == "object" && !Array.isArray(data21)){if(Object.keys(data21).length < 1){const err36 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}for(const key6 in data21){if(!((key6 === "label") || (key6 === "visible"))){const err37 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key6},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}if(data21.label !== undefined){let data22 = data21.label;if(typeof data22 === "string"){if(func3(data22) > 80){const err38 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}if(func3(data22) < 1){const err39 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/meta/legend/entries/backend/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data21.visible !== undefined){if(typeof data21.visible !== "boolean"){const err41 = {instancePath:instancePath+"/meta/legend/entries/backend/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}}else {const err42 = {instancePath:instancePath+"/meta/legend/entries/backend",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data17.database !== undefined){let data24 = data17.database;if(data24 && typeof data24 == "object" && !Array.isArray(data24)){if(Object.keys(data24).length < 1){const err43 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}for(const key7 in data24){if(!((key7 === "label") || (key7 === "visible"))){const err44 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key7},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data24.label !== undefined){let data25 = data24.label;if(typeof data25 === "string"){if(func3(data25) > 80){const err45 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(func3(data25) < 1){const err46 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}else {const err47 = {instancePath:instancePath+"/meta/legend/entries/database/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}if(data24.visible !== undefined){if(typeof data24.visible !== "boolean"){const err48 = {instancePath:instancePath+"/meta/legend/entries/database/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/meta/legend/entries/database",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data17.cloud !== undefined){let data27 = data17.cloud;if(data27 && typeof data27 == "object" && !Array.isArray(data27)){if(Object.keys(data27).length < 1){const err50 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}for(const key8 in data27){if(!((key8 === "label") || (key8 === "visible"))){const err51 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}if(data27.label !== undefined){let data28 = data27.label;if(typeof data28 === "string"){if(func3(data28) > 80){const err52 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}if(func3(data28) < 1){const err53 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/meta/legend/entries/cloud/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data27.visible !== undefined){if(typeof data27.visible !== "boolean"){const err55 = {instancePath:instancePath+"/meta/legend/entries/cloud/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/meta/legend/entries/cloud",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}if(data17.security !== undefined){let data30 = data17.security;if(data30 && typeof data30 == "object" && !Array.isArray(data30)){if(Object.keys(data30).length < 1){const err57 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}for(const key9 in data30){if(!((key9 === "label") || (key9 === "visible"))){const err58 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key9},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data30.label !== undefined){let data31 = data30.label;if(typeof data31 === "string"){if(func3(data31) > 80){const err59 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}if(func3(data31) < 1){const err60 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}else {const err61 = {instancePath:instancePath+"/meta/legend/entries/security/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data30.visible !== undefined){if(typeof data30.visible !== "boolean"){const err62 = {instancePath:instancePath+"/meta/legend/entries/security/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}}else {const err63 = {instancePath:instancePath+"/meta/legend/entries/security",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}if(data17.messagebus !== undefined){let data33 = data17.messagebus;if(data33 && typeof data33 == "object" && !Array.isArray(data33)){if(Object.keys(data33).length < 1){const err64 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}for(const key10 in data33){if(!((key10 === "label") || (key10 === "visible"))){const err65 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key10},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data33.label !== undefined){let data34 = data33.label;if(typeof data34 === "string"){if(func3(data34) > 80){const err66 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(func3(data34) < 1){const err67 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}}else {const err68 = {instancePath:instancePath+"/meta/legend/entries/messagebus/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}if(data33.visible !== undefined){if(typeof data33.visible !== "boolean"){const err69 = {instancePath:instancePath+"/meta/legend/entries/messagebus/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}}else {const err70 = {instancePath:instancePath+"/meta/legend/entries/messagebus",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}if(data17.external !== undefined){let data36 = data17.external;if(data36 && typeof data36 == "object" && !Array.isArray(data36)){if(Object.keys(data36).length < 1){const err71 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/minProperties",keyword:"minProperties",params:{limit: 1},message:"must NOT have fewer than 1 properties"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}for(const key11 in data36){if(!((key11 === "label") || (key11 === "visible"))){const err72 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key11},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}if(data36.label !== undefined){let data37 = data36.label;if(typeof data37 === "string"){if(func3(data37) > 80){const err73 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}if(func3(data37) < 1){const err74 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}else {const err75 = {instancePath:instancePath+"/meta/legend/entries/external/label",schemaPath:"common.schema.json#/$defs/legendEntry/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data36.visible !== undefined){if(typeof data36.visible !== "boolean"){const err76 = {instancePath:instancePath+"/meta/legend/entries/external/visible",schemaPath:"common.schema.json#/$defs/legendEntry/properties/visible/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}}}else {const err77 = {instancePath:instancePath+"/meta/legend/entries/external",schemaPath:"common.schema.json#/$defs/legendEntry/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}}else {const err78 = {instancePath:instancePath+"/meta/legend/entries",schemaPath:"#/properties/meta/properties/legend/properties/entries/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}}else {const err79 = {instancePath:instancePath+"/meta/legend",schemaPath:"#/properties/meta/properties/legend/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}if(data2.viewBox !== undefined){let data39 = data2.viewBox;if(Array.isArray(data39)){if(data39.length > 2){const err80 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}if(data39.length < 2){const err81 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}const len0 = data39.length;if(len0 > 0){let data40 = data39[0];if((typeof data40 == "number") && (isFinite(data40))){if(data40 < 320 || isNaN(data40)){const err82 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 320},message:"must be >= 320"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}else {const err83 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}if(len0 > 1){let data41 = data39[1];if((typeof data41 == "number") && (isFinite(data41))){if(data41 < 240 || isNaN(data41)){const err84 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 240},message:"must be >= 240"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}else {const err85 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}const len1 = data39.length;if(!(len1 <= 2)){const err86 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}else {const err87 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}}else {const err88 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}if(data.layout !== undefined){let data42 = data.layout;if(data42 && typeof data42 == "object" && !Array.isArray(data42)){if(data42.mode === undefined){const err89 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/required",keyword:"required",params:{missingProperty: "mode"},message:"must have required property '"+"mode"+"'"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}for(const key12 in data42){if(!(((((((key12 === "mode") || (key12 === "origin")) || (key12 === "cols")) || (key12 === "gapX")) || (key12 === "gapY")) || (key12 === "cellW")) || (key12 === "cellH"))){const err90 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key12},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}if(data42.mode !== undefined){if(!(data42.mode === "grid")){const err91 = {instancePath:instancePath+"/layout/mode",schemaPath:"#/properties/layout/properties/mode/enum",keyword:"enum",params:{allowedValues: schema149.properties.layout.properties.mode.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}if(data42.origin !== undefined){let data44 = data42.origin;if(Array.isArray(data44)){if(data44.length > 2){const err92 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}if(data44.length < 2){const err93 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}const len2 = data44.length;if(len2 > 0){let data45 = data44[0];if(!((typeof data45 == "number") && (isFinite(data45)))){const err94 = {instancePath:instancePath+"/layout/origin/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}if(len2 > 1){let data46 = data44[1];if(!((typeof data46 == "number") && (isFinite(data46)))){const err95 = {instancePath:instancePath+"/layout/origin/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}const len3 = data44.length;if(!(len3 <= 2)){const err96 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}}else {const err97 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}if(data42.cols !== undefined){let data47 = data42.cols;if(!(((typeof data47 == "number") && (!(data47 % 1) && !isNaN(data47))) && (isFinite(data47)))){const err98 = {instancePath:instancePath+"/layout/cols",schemaPath:"#/properties/layout/properties/cols/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}if((typeof data47 == "number") && (isFinite(data47))){if(data47 > 12 || isNaN(data47)){const err99 = {instancePath:instancePath+"/layout/cols",schemaPath:"#/properties/layout/properties/cols/maximum",keyword:"maximum",params:{comparison: "<=", limit: 12},message:"must be <= 12"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}if(data47 < 1 || isNaN(data47)){const err100 = {instancePath:instancePath+"/layout/cols",schemaPath:"#/properties/layout/properties/cols/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}}if(data42.gapX !== undefined){let data48 = data42.gapX;if((typeof data48 == "number") && (isFinite(data48))){if(data48 < 0 || isNaN(data48)){const err101 = {instancePath:instancePath+"/layout/gapX",schemaPath:"#/properties/layout/properties/gapX/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}}else {const err102 = {instancePath:instancePath+"/layout/gapX",schemaPath:"#/properties/layout/properties/gapX/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}if(data42.gapY !== undefined){let data49 = data42.gapY;if((typeof data49 == "number") && (isFinite(data49))){if(data49 < 0 || isNaN(data49)){const err103 = {instancePath:instancePath+"/layout/gapY",schemaPath:"#/properties/layout/properties/gapY/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}else {const err104 = {instancePath:instancePath+"/layout/gapY",schemaPath:"#/properties/layout/properties/gapY/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}if(data42.cellW !== undefined){let data50 = data42.cellW;if((typeof data50 == "number") && (isFinite(data50))){if(data50 < 40 || isNaN(data50)){const err105 = {instancePath:instancePath+"/layout/cellW",schemaPath:"#/properties/layout/properties/cellW/minimum",keyword:"minimum",params:{comparison: ">=", limit: 40},message:"must be >= 40"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}else {const err106 = {instancePath:instancePath+"/layout/cellW",schemaPath:"#/properties/layout/properties/cellW/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}if(data42.cellH !== undefined){let data51 = data42.cellH;if((typeof data51 == "number") && (isFinite(data51))){if(data51 < 24 || isNaN(data51)){const err107 = {instancePath:instancePath+"/layout/cellH",schemaPath:"#/properties/layout/properties/cellH/minimum",keyword:"minimum",params:{comparison: ">=", limit: 24},message:"must be >= 24"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/layout/cellH",schemaPath:"#/properties/layout/properties/cellH/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}}else {const err109 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}if(data.components !== undefined){let data52 = data.components;if(Array.isArray(data52)){if(data52.length < 1){const err110 = {instancePath:instancePath+"/components",schemaPath:"#/properties/components/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}const len4 = data52.length;for(let i0=0; i0 80){const err122 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/maxLength",keyword:"maxLength",params:{limit: 80},message:"must NOT have more than 80 characters"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}if(!pattern17.test(data59)){const err123 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/0/pattern",keyword:"pattern",params:{pattern: "^[^\\r\\n]+$"},message:"must match pattern \""+"^[^\\r\\n]+$"+"\""};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}var _valid1 = _errs143 === errors;valid35 = valid35 || _valid1;const _errs144 = errors;if(typeof data59 === "string"){if(!pattern18.test(data59)){const err124 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf/1/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}var _valid1 = _errs144 === errors;valid35 = valid35 || _valid1;if(!valid35){const err125 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}else {errors = _errs142;if(vErrors !== null){if(_errs142){vErrors.length = _errs142;}else {vErrors = null;}}}if(typeof data59 === "string"){if(func3(data59) > 2048){const err126 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}if(func3(data59) < 1){const err127 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}else {const err128 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}var _valid0 = _errs140 === errors;if(_valid0){valid34 = true;passing0 = 0;}const _errs145 = errors;if(data59 && typeof data59 == "object" && !Array.isArray(data59)){if(data59.url === undefined){const err129 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}if(data59.sha256 === undefined){const err130 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}for(const key14 in data59){if(!((key14 === "url") || (key14 === "sha256"))){const err131 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key14},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}if(data59.url !== undefined){let data60 = data59.url;if(typeof data60 === "string"){if(func3(data60) > 2048){const err132 = {instancePath:instancePath+"/components/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/maxLength",keyword:"maxLength",params:{limit: 2048},message:"must NOT have more than 2048 characters"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}if(func3(data60) < 8){const err133 = {instancePath:instancePath+"/components/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 8},message:"must NOT have fewer than 8 characters"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}if(!pattern18.test(data60)){const err134 = {instancePath:instancePath+"/components/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/pattern",keyword:"pattern",params:{pattern: "^https?://"},message:"must match pattern \""+"^https?://"+"\""};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}else {const err135 = {instancePath:instancePath+"/components/" + i0+"/brand/url",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}}if(data59.sha256 !== undefined){let data61 = data59.sha256;if(typeof data61 === "string"){if(!pattern20.test(data61)){const err136 = {instancePath:instancePath+"/components/" + i0+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/pattern",keyword:"pattern",params:{pattern: "^[a-f0-9]{64}$"},message:"must match pattern \""+"^[a-f0-9]{64}$"+"\""};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}else {const err137 = {instancePath:instancePath+"/components/" + i0+"/brand/sha256",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/properties/sha256/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}}}else {const err138 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}var _valid0 = _errs145 === errors;if(_valid0 && valid34){valid34 = false;passing0 = [passing0, 1];}else {if(_valid0){valid34 = true;passing0 = 1;}}if(!valid34){const err139 = {instancePath:instancePath+"/components/" + i0+"/brand",schemaPath:"common.schema.json#/$defs/brandMark/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}else {errors = _errs139;if(vErrors !== null){if(_errs139){vErrors.length = _errs139;}else {vErrors = null;}}}}if(data53.sources !== undefined){let data62 = data53.sources;if(Array.isArray(data62)){if(data62.length > 3){const err140 = {instancePath:instancePath+"/components/" + i0+"/sources",schemaPath:"#/properties/components/items/properties/sources/maxItems",keyword:"maxItems",params:{limit: 3},message:"must NOT have more than 3 items"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}if(data62.length < 1){const err141 = {instancePath:instancePath+"/components/" + i0+"/sources",schemaPath:"#/properties/components/items/properties/sources/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}const len5 = data62.length;for(let i1=0; i1 240){const err144 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/path",schemaPath:"#/properties/components/items/properties/sources/items/properties/path/maxLength",keyword:"maxLength",params:{limit: 240},message:"must NOT have more than 240 characters"};if(vErrors === null){vErrors = [err144];}else {vErrors.push(err144);}errors++;}if(func3(data64) < 1){const err145 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/path",schemaPath:"#/properties/components/items/properties/sources/items/properties/path/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err145];}else {vErrors.push(err145);}errors++;}}else {const err146 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/path",schemaPath:"#/properties/components/items/properties/sources/items/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err146];}else {vErrors.push(err146);}errors++;}}if(data63.line !== undefined){let data65 = data63.line;if(!(((typeof data65 == "number") && (!(data65 % 1) && !isNaN(data65))) && (isFinite(data65)))){const err147 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/line",schemaPath:"#/properties/components/items/properties/sources/items/properties/line/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err147];}else {vErrors.push(err147);}errors++;}if((typeof data65 == "number") && (isFinite(data65))){if(data65 < 1 || isNaN(data65)){const err148 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/line",schemaPath:"#/properties/components/items/properties/sources/items/properties/line/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err148];}else {vErrors.push(err148);}errors++;}}}if(data63.end_line !== undefined){let data66 = data63.end_line;if(!(((typeof data66 == "number") && (!(data66 % 1) && !isNaN(data66))) && (isFinite(data66)))){const err149 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/end_line",schemaPath:"#/properties/components/items/properties/sources/items/properties/end_line/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err149];}else {vErrors.push(err149);}errors++;}if((typeof data66 == "number") && (isFinite(data66))){if(data66 < 1 || isNaN(data66)){const err150 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/end_line",schemaPath:"#/properties/components/items/properties/sources/items/properties/end_line/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err150];}else {vErrors.push(err150);}errors++;}}}if(data63.label !== undefined){let data67 = data63.label;if(typeof data67 === "string"){if(func3(data67) > 48){const err151 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/label",schemaPath:"#/properties/components/items/properties/sources/items/properties/label/maxLength",keyword:"maxLength",params:{limit: 48},message:"must NOT have more than 48 characters"};if(vErrors === null){vErrors = [err151];}else {vErrors.push(err151);}errors++;}if(func3(data67) < 1){const err152 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/label",schemaPath:"#/properties/components/items/properties/sources/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err152];}else {vErrors.push(err152);}errors++;}}else {const err153 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1+"/label",schemaPath:"#/properties/components/items/properties/sources/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err153];}else {vErrors.push(err153);}errors++;}}}else {const err154 = {instancePath:instancePath+"/components/" + i0+"/sources/" + i1,schemaPath:"#/properties/components/items/properties/sources/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err154];}else {vErrors.push(err154);}errors++;}}}else {const err155 = {instancePath:instancePath+"/components/" + i0+"/sources",schemaPath:"#/properties/components/items/properties/sources/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err155];}else {vErrors.push(err155);}errors++;}}if(data53.row !== undefined){let data68 = data53.row;if(!(((typeof data68 == "number") && (!(data68 % 1) && !isNaN(data68))) && (isFinite(data68)))){const err156 = {instancePath:instancePath+"/components/" + i0+"/row",schemaPath:"#/properties/components/items/properties/row/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err156];}else {vErrors.push(err156);}errors++;}if((typeof data68 == "number") && (isFinite(data68))){if(data68 < 0 || isNaN(data68)){const err157 = {instancePath:instancePath+"/components/" + i0+"/row",schemaPath:"#/properties/components/items/properties/row/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err157];}else {vErrors.push(err157);}errors++;}}}if(data53.col !== undefined){let data69 = data53.col;if(!(((typeof data69 == "number") && (!(data69 % 1) && !isNaN(data69))) && (isFinite(data69)))){const err158 = {instancePath:instancePath+"/components/" + i0+"/col",schemaPath:"#/properties/components/items/properties/col/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err158];}else {vErrors.push(err158);}errors++;}if((typeof data69 == "number") && (isFinite(data69))){if(data69 < 0 || isNaN(data69)){const err159 = {instancePath:instancePath+"/components/" + i0+"/col",schemaPath:"#/properties/components/items/properties/col/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err159];}else {vErrors.push(err159);}errors++;}}}if(data53.pos !== undefined){let data70 = data53.pos;if(Array.isArray(data70)){if(data70.length > 2){const err160 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err160];}else {vErrors.push(err160);}errors++;}if(data70.length < 2){const err161 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err161];}else {vErrors.push(err161);}errors++;}const len6 = data70.length;if(len6 > 0){let data71 = data70[0];if(!((typeof data71 == "number") && (isFinite(data71)))){const err162 = {instancePath:instancePath+"/components/" + i0+"/pos/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err162];}else {vErrors.push(err162);}errors++;}}if(len6 > 1){let data72 = data70[1];if(!((typeof data72 == "number") && (isFinite(data72)))){const err163 = {instancePath:instancePath+"/components/" + i0+"/pos/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err163];}else {vErrors.push(err163);}errors++;}}const len7 = data70.length;if(!(len7 <= 2)){const err164 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err164];}else {vErrors.push(err164);}errors++;}}else {const err165 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err165];}else {vErrors.push(err165);}errors++;}}if(data53.size !== undefined){let data73 = data53.size;if(Array.isArray(data73)){if(data73.length > 2){const err166 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err166];}else {vErrors.push(err166);}errors++;}if(data73.length < 2){const err167 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err167];}else {vErrors.push(err167);}errors++;}const len8 = data73.length;if(len8 > 0){let data74 = data73[0];if((typeof data74 == "number") && (isFinite(data74))){if(data74 <= 0 || isNaN(data74)){const err168 = {instancePath:instancePath+"/components/" + i0+"/size/0",schemaPath:"#/properties/components/items/properties/size/prefixItems/0/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err168];}else {vErrors.push(err168);}errors++;}}else {const err169 = {instancePath:instancePath+"/components/" + i0+"/size/0",schemaPath:"#/properties/components/items/properties/size/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err169];}else {vErrors.push(err169);}errors++;}}if(len8 > 1){let data75 = data73[1];if((typeof data75 == "number") && (isFinite(data75))){if(data75 <= 0 || isNaN(data75)){const err170 = {instancePath:instancePath+"/components/" + i0+"/size/1",schemaPath:"#/properties/components/items/properties/size/prefixItems/1/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err170];}else {vErrors.push(err170);}errors++;}}else {const err171 = {instancePath:instancePath+"/components/" + i0+"/size/1",schemaPath:"#/properties/components/items/properties/size/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err171];}else {vErrors.push(err171);}errors++;}}const len9 = data73.length;if(!(len9 <= 2)){const err172 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err172];}else {vErrors.push(err172);}errors++;}}else {const err173 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err173];}else {vErrors.push(err173);}errors++;}}}else {const err174 = {instancePath:instancePath+"/components/" + i0,schemaPath:"#/properties/components/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err174];}else {vErrors.push(err174);}errors++;}}}else {const err175 = {instancePath:instancePath+"/components",schemaPath:"#/properties/components/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err175];}else {vErrors.push(err175);}errors++;}}if(data.boundaries !== undefined){let data76 = data.boundaries;if(Array.isArray(data76)){const len10 = data76.length;for(let i2=0; i2=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err187];}else {vErrors.push(err187);}errors++;}}else {const err188 = {instancePath:instancePath+"/boundaries/" + i2+"/pad",schemaPath:"#/properties/boundaries/items/properties/pad/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err188];}else {vErrors.push(err188);}errors++;}}}else {const err189 = {instancePath:instancePath+"/boundaries/" + i2,schemaPath:"#/properties/boundaries/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err189];}else {vErrors.push(err189);}errors++;}}}else {const err190 = {instancePath:instancePath+"/boundaries",schemaPath:"#/properties/boundaries/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err190];}else {vErrors.push(err190);}errors++;}}if(data.connections !== undefined){let data83 = data.connections;if(Array.isArray(data83)){const len12 = data83.length;for(let i4=0; i4 2){const err205 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err205];}else {vErrors.push(err205);}errors++;}if(data94.length < 2){const err206 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err206];}else {vErrors.push(err206);}errors++;}const len14 = data94.length;if(len14 > 0){let data95 = data94[0];if(!((typeof data95 == "number") && (isFinite(data95)))){const err207 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err207];}else {vErrors.push(err207);}errors++;}}if(len14 > 1){let data96 = data94[1];if(!((typeof data96 == "number") && (isFinite(data96)))){const err208 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err208];}else {vErrors.push(err208);}errors++;}}const len15 = data94.length;if(!(len15 <= 2)){const err209 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err209];}else {vErrors.push(err209);}errors++;}}else {const err210 = {instancePath:instancePath+"/connections/" + i4+"/via/" + i5,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err210];}else {vErrors.push(err210);}errors++;}}}else {const err211 = {instancePath:instancePath+"/connections/" + i4+"/via",schemaPath:"#/properties/connections/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err211];}else {vErrors.push(err211);}errors++;}}if(data84.labelAt !== undefined){let data97 = data84.labelAt;if(Array.isArray(data97)){if(data97.length > 2){const err212 = {instancePath:instancePath+"/connections/" + i4+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err212];}else {vErrors.push(err212);}errors++;}if(data97.length < 2){const err213 = {instancePath:instancePath+"/connections/" + i4+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err213];}else {vErrors.push(err213);}errors++;}const len16 = data97.length;if(len16 > 0){let data98 = data97[0];if(!((typeof data98 == "number") && (isFinite(data98)))){const err214 = {instancePath:instancePath+"/connections/" + i4+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err214];}else {vErrors.push(err214);}errors++;}}if(len16 > 1){let data99 = data97[1];if(!((typeof data99 == "number") && (isFinite(data99)))){const err215 = {instancePath:instancePath+"/connections/" + i4+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err215];}else {vErrors.push(err215);}errors++;}}const len17 = data97.length;if(!(len17 <= 2)){const err216 = {instancePath:instancePath+"/connections/" + i4+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err216];}else {vErrors.push(err216);}errors++;}}else {const err217 = {instancePath:instancePath+"/connections/" + i4+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err217];}else {vErrors.push(err217);}errors++;}}if(data84.labelDx !== undefined){let data100 = data84.labelDx;if(!((typeof data100 == "number") && (isFinite(data100)))){const err218 = {instancePath:instancePath+"/connections/" + i4+"/labelDx",schemaPath:"#/properties/connections/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err218];}else {vErrors.push(err218);}errors++;}}if(data84.labelDy !== undefined){let data101 = data84.labelDy;if(!((typeof data101 == "number") && (isFinite(data101)))){const err219 = {instancePath:instancePath+"/connections/" + i4+"/labelDy",schemaPath:"#/properties/connections/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err219];}else {vErrors.push(err219);}errors++;}}if(data84.labelSegment !== undefined){let data102 = data84.labelSegment;if(!(((typeof data102 == "number") && (!(data102 % 1) && !isNaN(data102))) && (isFinite(data102)))){const err220 = {instancePath:instancePath+"/connections/" + i4+"/labelSegment",schemaPath:"#/properties/connections/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err220];}else {vErrors.push(err220);}errors++;}if((typeof data102 == "number") && (isFinite(data102))){if(data102 < 0 || isNaN(data102)){const err221 = {instancePath:instancePath+"/connections/" + i4+"/labelSegment",schemaPath:"#/properties/connections/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err221];}else {vErrors.push(err221);}errors++;}}}if(data84.width !== undefined){let data103 = data84.width;if((typeof data103 == "number") && (isFinite(data103))){if(data103 < 0.5 || isNaN(data103)){const err222 = {instancePath:instancePath+"/connections/" + i4+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err222];}else {vErrors.push(err222);}errors++;}}else {const err223 = {instancePath:instancePath+"/connections/" + i4+"/width",schemaPath:"common.schema.json#/$defs/relationshipWidth/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err223];}else {vErrors.push(err223);}errors++;}}}else {const err224 = {instancePath:instancePath+"/connections/" + i4,schemaPath:"#/properties/connections/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err224];}else {vErrors.push(err224);}errors++;}}}else {const err225 = {instancePath:instancePath+"/connections",schemaPath:"#/properties/connections/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err225];}else {vErrors.push(err225);}errors++;}}if(data.cards !== undefined){let data104 = data.cards;if(Array.isArray(data104)){const len18 = data104.length;for(let i6=0; i6` into the output. Used by the validators as a backstop. +export function isFinitePoint(...coords) { + return coords.every((c) => Number.isFinite(c)); +} + +export function rectsOverlap(a, b, gap = 0) { + // Non-finite geometry means "unknown", not "overlapping". Every comparison + // below is false for NaN, so without this guard the negation reports a + // collision for every pair. Callers surface non-finite pos/size through their + // own diagnostic; reporting it again as an overlap buries that message under + // one bogus separation hint per pair. + if (!isFinitePoint(a.x, a.y, a.width, a.height, b.x, b.y, b.width, b.height)) { + return false; + } + return !( + a.x + a.width + gap <= b.x || + b.x + b.width + gap <= a.x || + a.y + a.height + gap <= b.y || + b.y + b.height + gap <= a.y + ); +} + +export function segmentIntersectsRect(segment, rect, gap = 0) { + const box = { + x1: rect.x - gap, + y1: rect.y - gap, + x2: rect.x + rect.width + gap, + y2: rect.y + rect.height + gap + }; + const [a, b] = [segment.start, segment.end]; + if (pointInBox(a, box) || pointInBox(b, box)) return true; + return ( + segmentsIntersect(a, b, [box.x1, box.y1], [box.x2, box.y1]) || + segmentsIntersect(a, b, [box.x2, box.y1], [box.x2, box.y2]) || + segmentsIntersect(a, b, [box.x2, box.y2], [box.x1, box.y2]) || + segmentsIntersect(a, b, [box.x1, box.y2], [box.x1, box.y1]) + ); +} + +export function segmentRectClearance(segment, rect) { + if (!segment || !rect) return null; + const { start, end } = segment; + if (!Array.isArray(start) || !Array.isArray(end) || start.length !== 2 || end.length !== 2) return null; + if (!isFinitePoint(...start, ...end, rect.x, rect.y, rect.width, rect.height)) return null; + if (rect.width < 0 || rect.height < 0) return null; + if (segmentIntersectsRect(segment, rect)) return 0; + + const corners = [ + [rect.x, rect.y], + [rect.x + rect.width, rect.y], + [rect.x + rect.width, rect.y + rect.height], + [rect.x, rect.y + rect.height], + ]; + return Math.min( + pointRectDistance(start, rect), + pointRectDistance(end, rect), + ...corners.map((corner) => pointSegmentDistance(corner, start, end)), + ); +} + +export function segmentRectIntersectionLength(segment, rect) { + if (!segment || !rect) return null; + const { start, end } = segment; + if (!Array.isArray(start) || !Array.isArray(end) || start.length !== 2 || end.length !== 2) return null; + if (!isFinitePoint(...start, ...end, rect.x, rect.y, rect.width, rect.height)) return null; + if (rect.width < 0 || rect.height < 0) return null; + + const dx = end[0] - start[0]; + const dy = end[1] - start[1]; + const length = Math.hypot(dx, dy); + if (length <= 0.0000001) return 0; + const bounds = [ + [-dx, start[0] - rect.x], + [dx, rect.x + rect.width - start[0]], + [-dy, start[1] - rect.y], + [dy, rect.y + rect.height - start[1]], + ]; + let enter = 0; + let leave = 1; + for (const [direction, distance] of bounds) { + if (Math.abs(direction) <= 0.0000001) { + if (distance < -0.0000001) return 0; + continue; + } + const ratio = distance / direction; + if (direction < 0) enter = Math.max(enter, ratio); + else leave = Math.min(leave, ratio); + if (enter > leave + 0.0000001) return 0; + } + return length * Math.max(0, leave - enter); +} + +export function collectLabelRouteClearance({ labels, routedRelations, threshold }) { + if (!Number.isFinite(threshold) || threshold < 0) return []; + const routeCandidates = asArray(routedRelations).map((entry, fallbackIndex) => { + const relation = entry?.relation || entry; + const points = normalizeRoutePoints(entry?.points || relation?.routePoints); + if (!relation || points.length < 2) return null; + return { + relation, + relationIndex: Number.isInteger(entry?.relationIndex) ? entry.relationIndex : fallbackIndex, + points, + }; + }).filter(Boolean); + const seenRoutes = new Set(); + const routes = routeCandidates.filter((route) => { + const identity = relationshipIdentity(route.relation, route.relationIndex); + if (seenRoutes.has(identity)) return false; + seenRoutes.add(identity); + return true; + }); + const hits = []; + const seenLabels = new Set(); + + for (const [fallbackIndex, label] of asArray(labels).entries()) { + const rect = label?.rect || label; + if (!rect || !isFinitePoint(rect.x, rect.y, rect.width, rect.height) || rect.width < 0 || rect.height < 0) continue; + const relationIndex = Number.isInteger(label?.relationIndex) ? label.relationIndex : fallbackIndex; + const labelIdentity = relationshipIdentity(label?.relation, relationIndex); + if (seenLabels.has(labelIdentity)) continue; + seenLabels.add(labelIdentity); + for (const route of routes) { + if (relationIndex === route.relationIndex || sameRelationship(label?.relation, route.relation)) continue; + let nearest = null; + for (let segmentIndex = 0; segmentIndex < route.points.length - 1; segmentIndex += 1) { + const start = route.points[segmentIndex]; + const end = route.points[segmentIndex + 1]; + const clearance = segmentRectClearance({ start, end }, rect); + if (clearance == null) continue; + if (!nearest || clearance < nearest.clearance) { + nearest = { + clearance, + intersectionLength: segmentRectIntersectionLength({ start, end }, rect), + segmentIndex, + start, + end, + }; + } + } + if (!nearest || nearest.clearance + 0.0001 >= threshold) continue; + hits.push({ + label, + labelRelation: label?.relation, + labelRelationIndex: relationIndex, + otherRelation: route.relation, + otherRelationIndex: route.relationIndex, + rect, + ...nearest, + threshold, + }); + } + } + return hits; +} + +function relationshipIdentity(relation, relationIndex) { + if (relation?.key !== undefined) return `key:${relation.key}`; + if (relation?.id) return `id:${relation.from || ''}\u0000${relation.to || ''}\u0000${relation.id}`; + return `index:${relationIndex}`; +} + +function sameRelationship(left, right) { + if (!left || !right) return false; + if (left === right) return true; + if (left.key !== undefined && right.key !== undefined) return left.key === right.key; + return Boolean(left.id && right.id && left.id === right.id && left.from === right.from && left.to === right.to); +} + +function relationshipSubject(diagramType, relationCollection, relationIndex, relation) { + return { + diagramType, + collection: relationCollection, + index: relationIndex, + ...(relation?.id ? { id: relation.id } : {}), + ...(relation?.from ? { from: relation.from } : {}), + ...(relation?.to ? { to: relation.to } : {}), + }; +} + +const ENDPOINT_SIDE_RULES = { + left: { + axis: 'horizontal', + sourceSign: -1, + targetSign: 1, + sourceDirection: 'leftward', + targetDirection: 'rightward from the left', + }, + right: { + axis: 'horizontal', + sourceSign: 1, + targetSign: -1, + sourceDirection: 'rightward', + targetDirection: 'leftward from the right', + }, + top: { + axis: 'vertical', + sourceSign: -1, + targetSign: 1, + sourceDirection: 'upward', + targetDirection: 'downward from above', + }, + bottom: { + axis: 'vertical', + sourceSign: 1, + targetSign: -1, + sourceDirection: 'downward', + targetDirection: 'upward from below', + }, +}; + +function endpointSideIssue(points, endpoint, side) { + const rule = ENDPOINT_SIDE_RULES[side]; + if (!rule) return null; + const normalized = normalizeRoutePoints(points); + if (normalized.length < 2) return null; + const segmentIndex = endpoint === 'source' ? 0 : normalized.length - 2; + const start = normalized[segmentIndex]; + const end = normalized[segmentIndex + 1]; + const dx = end[0] - start[0]; + const dy = end[1] - start[1]; + const along = rule.axis === 'horizontal' ? dx : dy; + const across = rule.axis === 'horizontal' ? dy : dx; + const expectedSign = endpoint === 'source' ? rule.sourceSign : rule.targetSign; + if (Math.abs(across) <= 0.0001 && along * expectedSign > 0.0001) return null; + return { + endpoint, + side, + segmentIndex, + start, + end, + expectedAxis: rule.axis, + expectedDirection: endpoint === 'source' ? rule.sourceDirection : rule.targetDirection, + }; +} + +// A side is a direction contract, not just a point on a box border. This pure +// predicate lets automatic routers prefer a dogleg whose first and final +// segments leave/enter the chosen sides perpendicularly. +export function routeHonorsEndpointSides(points, fromSide, toSide) { + return !endpointSideIssue(points, 'source', fromSide) + && !endpointSideIssue(points, 'target', toSide); +} + +// Explicit fromSide/toSide are authored geometry, so a tangent or backwards +// endpoint segment changes their meaning. Fail this universally instead of +// leaving a malformed arrow for visual review to discover. Named routes and +// authored via points already carry their own geometry semantics: when they +// omit endpoint sides, do not invent a relative-position side and then reject +// the route for disagreeing with that invention. Pure automatic routes may +// still be checked against renderer-inferred sides. +export function cleanEndpointSideProblems({ + relations, + endpointIds, + pathFor, + diagramType, + relationCollection, + fromSideFor, + toSideFor, + shouldCheckRelation = () => true, + routeHint = 'align the first/final via segment with fromSide/toSide, change the side, or remove explicit routing so auto can choose a perpendicular approach', +}) { + const problems = []; + for (const [relationIndex, relation] of asArray(relations).entries()) { + if (!relation || !endpointIds?.has(relation.from) || !endpointIds?.has(relation.to)) continue; + if (!shouldCheckRelation(relation, relationIndex)) continue; + const points = pathFor(relation)?.points; + if (!Array.isArray(points) || points.length < 2) continue; + const authoredFromSide = relation.fromSide && relation.fromSide !== 'auto' ? relation.fromSide : null; + const authoredToSide = relation.toSide && relation.toSide !== 'auto' ? relation.toSide : null; + const hasAuthoredRouteGeometry = Boolean( + (relation.route && relation.route !== 'auto') || Array.isArray(relation.via), + ); + const inferredFromSide = !hasAuthoredRouteGeometry && typeof fromSideFor === 'function' + ? fromSideFor(relation) + : null; + const inferredToSide = !hasAuthoredRouteGeometry && typeof toSideFor === 'function' + ? toSideFor(relation) + : null; + const fromSide = authoredFromSide ?? inferredFromSide; + const toSide = authoredToSide ?? inferredToSide; + const checks = [ + fromSide + ? { ...endpointSideIssue(points, 'source', fromSide), sideOrigin: authoredFromSide ? 'authored' : 'inferred' } + : null, + toSide + ? { ...endpointSideIssue(points, 'target', toSide), sideOrigin: authoredToSide ? 'authored' : 'inferred' } + : null, + ].filter((issue) => issue?.endpoint); + for (const issue of checks) { + const relationId = relation.id ? ` id "${relation.id}"` : ''; + const authoredField = issue.endpoint === 'source' ? 'fromSide' : 'toSide'; + const sideField = issue.sideOrigin === 'inferred' ? `inferred ${authoredField}` : authoredField; + const segmentRole = issue.endpoint === 'source' ? 'first' : 'final'; + const from = issue.start.map((value) => Math.round(value * 10) / 10).join(', '); + const to = issue.end.map((value) => Math.round(value * 10) / 10).join(', '); + const message = `[clean-flow/endpoint-side-direction] ${diagramType} ${relationCollection}[${relationIndex}]${relationId} "${relation.from}" -> "${relation.to}" ${segmentRole} segment ${issue.segmentIndex} [${from}] -> [${to}] does not honor ${sideField} "${issue.side}" — it must run ${issue.expectedAxis} ${issue.expectedDirection}; ${routeHint}.`; + recordDiagnostic({ + code: 'clean-flow/endpoint-side-direction', + severity: 'error', + message, + subject: relationshipSubject(diagramType, relationCollection, relationIndex, relation), + evidence: { + endpoint: issue.endpoint, + authoredField, + sideOrigin: issue.sideOrigin, + side: issue.side, + segmentIndex: issue.segmentIndex, + from: issue.start, + to: issue.end, + expectedAxis: issue.expectedAxis, + expectedDirection: issue.expectedDirection, + }, + supportedFixes: [routeHint], + }); + problems.push(message); + } + } + return problems; +} + +// One mechanical quality gate for every renderer-owned relationship path. +// A renderer supplies its semantic obstacle set; source/target boxes are +// always exempt because paths are expected to terminate on their boundaries. +// Containers, lifelines, and other intentionally pass-through geometry should +// simply not be supplied as obstacles. +export function cleanFlowProblems({ + relations, + obstacles, + pathFor, + diagramType, + relationCollection, + obstacleKind, + clearance = 2, + routeHint = 'adjust fromSide/toSide, set route/via or channel coordinates, or move the obstacle' +}) { + // A relationship hidden behind an unrelated opaque node changes the + // diagram's meaning, so this is a correctness invariant rather than an + // opt-in composition preference. Keep it active even when the author omits + // quality_profile; standard/showcase still control stricter visual budgets. + const problems = []; + const obstacleList = [...obstacles]; + const obstacleIds = new Set(obstacleList.map((obstacle) => obstacle?.id)); + for (const [relationIndex, relation] of asArray(relations).entries()) { + if (!relation || typeof relation.from !== 'string' || typeof relation.to !== 'string') continue; + if (!obstacleIds.has(relation.from) || !obstacleIds.has(relation.to)) continue; + const points = pathFor(relation)?.points; + if (!Array.isArray(points) || points.length < 2) continue; + if (!points.every((point) => Array.isArray(point) && point.length === 2 && isFinitePoint(...point))) continue; + + const endpointIds = new Set([relation.from, relation.to]); + for (const obstacle of obstacleList) { + if (!obstacle || endpointIds.has(obstacle.id)) continue; + if (!isFinitePoint(obstacle.x, obstacle.y, obstacle.width, obstacle.height)) continue; + let hitSegment = -1; + for (let segmentIndex = 0; segmentIndex < points.length - 1; segmentIndex += 1) { + if (segmentIntersectsRect({ start: points[segmentIndex], end: points[segmentIndex + 1] }, obstacle, clearance)) { + hitSegment = segmentIndex; + break; + } + } + if (hitSegment === -1) continue; + const from = points[hitSegment].map(Math.round).join(', '); + const to = points[hitSegment + 1].map(Math.round).join(', '); + const relationId = relation.id ? ` id "${relation.id}"` : ''; + const message = `[clean-flow/edge-through-node] ${diagramType} ${relationCollection}[${relationIndex}]${relationId} "${relation.from}" -> "${relation.to}" crosses ${obstacleKind} "${obstacle.id}" (unrelated to this relationship) on segment ${hitSegment} [${from}] -> [${to}] (${clearance}px clearance) — ${routeHint}.`; + recordDiagnostic({ + code: 'clean-flow/edge-through-node', + severity: 'error', + message, + subject: relationshipSubject(diagramType, relationCollection, relationIndex, relation), + evidence: { + obstacleKind, + obstacleId: obstacle.id, + segmentIndex: hitSegment, + from: points[hitSegment], + to: points[hitSegment + 1], + clearancePx: clearance, + }, + supportedFixes: [routeHint], + }); + problems.push(message); + } + } + return problems; +} + +// Build a read-only analysis copy of a polyline with straight-through +// waypoints removed. A waypoint on a forward-collinear run is not a visual +// endpoint, so treating it as one would hide a proper X that lands exactly on +// that waypoint. Reversals and real bends stay split: their shared point can +// still be an authored touch rather than a crossing. The source points are +// retained on every merged segment so diagnostics can name the authored +// segment that contains a hit without changing rendered/receipt geometry. +export function forwardCollinearAnalysisSegments(points) { + const segments = []; + for (let segmentIndex = 0; segmentIndex < asArray(points).length - 1; segmentIndex += 1) { + const authoredStart = points[segmentIndex]; + const authoredEnd = points[segmentIndex + 1]; + const start = Array.isArray(authoredStart) ? [...authoredStart] : authoredStart; + const end = Array.isArray(authoredEnd) ? [...authoredEnd] : authoredEnd; + const sourceSegment = { start, end, segmentIndex }; + const previous = segments.at(-1); + if (previous && segmentsContinueForward(previous.start, previous.end, start, end)) { + previous.end = end; + previous.sourceSegments.push(sourceSegment); + continue; + } + segments.push({ + start, + end, + segmentIndex, + sourceSegments: [sourceSegment], + }); + } + return segments; +} + +export function sourceSegmentIndexAtPoint(segment, point) { + const source = asArray(segment?.sourceSegments).find(({ start, end }) => ( + pointLiesOnSegment(point, start, end) + )); + return source?.segmentIndex ?? segment?.segmentIndex ?? 0; +} + +function authoredAnalysisSegments(points) { + return asArray(points).slice(0, -1).map((start, segmentIndex) => ({ + start, + end: points[segmentIndex + 1], + segmentIndex, + sourceSegments: [{ start, end: points[segmentIndex + 1], segmentIndex }], + })); +} + +function segmentsContinueForward(firstStart, firstEnd, secondStart, secondEnd) { + if (![firstStart, firstEnd, secondStart, secondEnd].every((point) => ( + Array.isArray(point) && point.length === 2 && isFinitePoint(...point) + ))) return false; + const epsilon = 0.0001; + if (Math.abs(firstEnd[0] - secondStart[0]) > epsilon + || Math.abs(firstEnd[1] - secondStart[1]) > epsilon) return false; + const firstVector = [firstEnd[0] - firstStart[0], firstEnd[1] - firstStart[1]]; + const secondVector = [secondEnd[0] - secondStart[0], secondEnd[1] - secondStart[1]]; + const firstLength = Math.hypot(...firstVector); + const secondLength = Math.hypot(...secondVector); + if (firstLength <= epsilon || secondLength <= epsilon) return false; + const cross = firstVector[0] * secondVector[1] - firstVector[1] * secondVector[0]; + if (Math.abs(cross) > epsilon) return false; + const dot = firstVector[0] * secondVector[0] + firstVector[1] * secondVector[1]; + return dot > epsilon; +} + +function pointLiesOnSegment(point, start, end) { + if (![point, start, end].every((candidate) => ( + Array.isArray(candidate) && candidate.length === 2 && isFinitePoint(...candidate) + ))) return false; + const epsilon = 0.0001; + const length = Math.hypot(end[0] - start[0], end[1] - start[1]); + if (length <= epsilon) return Math.hypot(point[0] - start[0], point[1] - start[1]) <= epsilon; + if (Math.abs(crossProduct(start, end, point)) > epsilon * length) return false; + return point[0] >= Math.min(start[0], end[0]) - epsilon + && point[0] <= Math.max(start[0], end[0]) + epsilon + && point[1] >= Math.min(start[1], end[1]) - epsilon + && point[1] <= Math.max(start[1], end[1]) + epsilon; +} + +// Reject only a proper interior X between relationships that share no semantic +// endpoint. Endpoint touches, branch/merge ports, and collinear shared +// corridors are intentionally outside this contract because geometry alone +// cannot tell whether those are authored junctions. +export function cleanCrossingProblems({ + relations, + endpointIds, + pathFor, + diagramType, + relationCollection, + profile = 'standard', + profileIsAuthoritative = false, + mergeForwardCollinearWaypoints = false, + routeHint = 'adjust route/via or channel coordinates so the relationships use separate corridors' +}) { + if (qualityProfileForGate(profile, profileIsAuthoritative) !== 'showcase') return []; + const routed = asArray(relations).map((relation, index) => { + if (!relation || !endpointIds.has(relation.from) || !endpointIds.has(relation.to)) return null; + const points = pathFor(relation)?.points; + if (!Array.isArray(points) || points.length < 2) return null; + if (!points.every((point) => Array.isArray(point) && point.length === 2 && isFinitePoint(...point))) return null; + return { + relation, + index, + points, + analysisSegments: mergeForwardCollinearWaypoints + ? forwardCollinearAnalysisSegments(points) + : authoredAnalysisSegments(points), + }; + }).filter(Boolean); + const problems = []; + + for (let leftIndex = 0; leftIndex < routed.length; leftIndex += 1) { + const left = routed[leftIndex]; + for (let rightIndex = leftIndex + 1; rightIndex < routed.length; rightIndex += 1) { + const right = routed[rightIndex]; + if ([left.relation.from, left.relation.to].some((id) => id === right.relation.from || id === right.relation.to)) continue; + + let hit = null; + for (const leftSegment of left.analysisSegments) { + if (hit) break; + for (const rightSegment of right.analysisSegments) { + const point = properSegmentIntersection( + leftSegment.start, + leftSegment.end, + rightSegment.start, + rightSegment.end + ); + if (point) { + hit = { + point, + leftSegment: sourceSegmentIndexAtPoint(leftSegment, point), + rightSegment: sourceSegmentIndexAtPoint(rightSegment, point), + }; + break; + } + } + } + if (!hit) continue; + + const describe = ({ relation, index }) => { + const id = relation.id ? ` id "${relation.id}"` : ''; + return `${relationCollection}[${index}]${id} "${relation.from}" -> "${relation.to}"`; + }; + const point = hit.point.map((value) => Math.round(value * 10) / 10).join(', '); + const message = `[composition/proper-crossing] showcase ${diagramType} ${describe(left)} crosses ${describe(right)} at [${point}] (segments ${hit.leftSegment} and ${hit.rightSegment}) — ${routeHint}.`; + recordDiagnostic({ + code: 'composition/proper-crossing', + severity: 'error', + message, + subject: relationshipSubject(diagramType, relationCollection, left.index, left.relation), + evidence: { + otherRelationship: relationshipSubject(diagramType, relationCollection, right.index, right.relation), + point: hit.point, + segmentIndex: hit.leftSegment, + otherSegmentIndex: hit.rightSegment, + }, + supportedFixes: [routeHint], + }); + problems.push(message); + } + } + return problems; +} + +// Two unrelated relationships that occupy the same visible corridor can read +// as one authored branch or merge even when neither relationship crosses a +// node or forms a proper X. Keep shared semantic endpoints exempt: their +// initial/final fan-out is real topology. Tiny overlaps below the route rhythm +// floor are ignored to avoid turning sub-pixel rounding into a quality debt. +export function collectAmbiguousCorridors({ + routedRelations, + minOverlapPx = 8, +}) { + const routed = asArray(routedRelations).map((entry, fallbackIndex) => { + const relation = entry?.relation; + if (!relation || typeof relation.from !== 'string' || typeof relation.to !== 'string') return null; + const points = normalizeRoutePoints(entry?.points); + if (points.length < 2) return null; + return { + relation, + relationIndex: Number.isInteger(entry.relationIndex) ? entry.relationIndex : fallbackIndex, + points, + }; + }).filter(Boolean); + const hits = []; + + for (let leftIndex = 0; leftIndex < routed.length; leftIndex += 1) { + const left = routed[leftIndex]; + for (let rightIndex = leftIndex + 1; rightIndex < routed.length; rightIndex += 1) { + const right = routed[rightIndex]; + if ([left.relation.from, left.relation.to].some((id) => id === right.relation.from || id === right.relation.to)) continue; + + let longest = null; + for (let leftSegment = 0; leftSegment < left.points.length - 1; leftSegment += 1) { + for (let rightSegment = 0; rightSegment < right.points.length - 1; rightSegment += 1) { + const overlap = collinearAxisOverlap( + left.points[leftSegment], + left.points[leftSegment + 1], + right.points[rightSegment], + right.points[rightSegment + 1], + ); + if (!overlap || overlap.length + 0.0001 < minOverlapPx) continue; + if (!longest || overlap.length > longest.overlapLength + 0.0001) { + longest = { + left, + right, + leftSegment, + rightSegment, + overlapLength: overlap.length, + overlapStart: overlap.start, + overlapEnd: overlap.end, + }; + } + } + } + if (longest) hits.push(longest); + } + } + return hits; +} + +export function cleanAmbiguousCorridorProblems({ + relations, + endpointIds, + pathFor, + diagramType, + relationCollection, + profile = 'standard', + profileIsAuthoritative = false, + routeHint = 'adjust route/via or channel coordinates so the relationships use separate corridors', + minOverlapPx = 8, +}) { + if (qualityProfileForGate(profile, profileIsAuthoritative) !== 'showcase') return []; + const routedRelations = collectEligibleRoutedRelations({ relations, endpointIds, pathFor }); + + return collectAmbiguousCorridors({ routedRelations, minOverlapPx }).map((hit) => { + const describe = ({ relation, relationIndex }) => { + const id = relation.id ? ` id "${relation.id}"` : ''; + return `${relationCollection}[${relationIndex}]${id} "${relation.from}" -> "${relation.to}"`; + }; + const length = Math.round(hit.overlapLength * 10) / 10; + const from = hit.overlapStart.map((value) => Math.round(value * 10) / 10).join(', '); + const to = hit.overlapEnd.map((value) => Math.round(value * 10) / 10).join(', '); + const message = `[composition/ambiguous-corridor] showcase ${diagramType} ${describe(hit.left)} shares a ${length}px corridor with ${describe(hit.right)} at [${from}] -> [${to}] (segments ${hit.leftSegment} and ${hit.rightSegment}; minimum ${minOverlapPx}px) — ${routeHint}.`; + recordDiagnostic({ + code: 'composition/ambiguous-corridor', + severity: 'error', + message, + subject: relationshipSubject(diagramType, relationCollection, hit.left.relationIndex, hit.left.relation), + evidence: { + otherRelationship: relationshipSubject(diagramType, relationCollection, hit.right.relationIndex, hit.right.relation), + overlapLengthPx: length, + minimumPx: minOverlapPx, + from: hit.overlapStart, + to: hit.overlapEnd, + segmentIndex: hit.leftSegment, + otherSegmentIndex: hit.rightSegment, + }, + supportedFixes: [routeHint], + }); + return message; + }); +} + +// Relationship paths may cross a structural frame, but they must not borrow a +// frame side as a routing corridor. Rounded rectangle corners are trimmed from +// the modeled straight sides so a short corner touch is not mistaken for a +// border run. Any positive straight overlap beyond the numeric epsilon is a +// hard failure in every quality profile; 16px belongs only to the separate, +// neutral short-segment metric and is not a corridor exemption. +export function collectBorderRuns({ routedRelations, frames }) { + const hits = []; + for (const routed of asArray(routedRelations)) { + const routeSegments = Array.isArray(routed?.segments) + ? routed.segments + : asArray(routed?.points).slice(0, -1).map((start, index) => ({ start, end: routed.points[index + 1] })); + if (!routeSegments.length) continue; + if (!routeSegments.every((segment) => ( + Array.isArray(segment?.start) && segment.start.length === 2 && isFinitePoint(...segment.start) + && Array.isArray(segment?.end) && segment.end.length === 2 && isFinitePoint(...segment.end) + ))) continue; + for (const [frameIndex, frame] of asArray(frames).entries()) { + for (const border of frameBorderSegments(frame)) { + const overlaps = []; + for (let segmentIndex = 0; segmentIndex < routeSegments.length; segmentIndex += 1) { + const segment = routeSegments[segmentIndex]; + const overlap = collinearAxisOverlap( + segment.start, + segment.end, + border.start, + border.end, + ); + if (!overlap || overlap.length <= 0.0001) continue; + overlaps.push({ ...overlap, segmentIndex }); + } + if (!overlaps.length) continue; + const merged = mergeBorderOverlaps(overlaps, border); + const longest = [...merged].sort((left, right) => right.length - left.length || left.low - right.low)[0]; + hits.push({ + ...routed, + frame, + frameIndex, + side: border.side, + segmentIndex: Math.min(...overlaps.map((overlap) => overlap.segmentIndex)), + overlapLength: merged.reduce((total, overlap) => total + overlap.length, 0), + overlapStart: longest.start, + overlapEnd: longest.end, + }); + } + } + } + return hits; +} + +export function cleanBorderRunProblems({ + relations, + endpointIds, + frames, + pathFor, + diagramType, + relationCollection, + profile, + profileIsAuthoritative = false, + routeHint = 'adjust route/via or channel coordinates so the relationship crosses the frame perpendicularly through a clear opening' +}) { + if (!qualityProfileForGate(profile, profileIsAuthoritative)) return []; + const routedRelations = collectEligibleRoutedRelations({ relations, endpointIds, pathFor }); + return collectBorderRuns({ routedRelations, frames }).map((hit) => { + const relation = hit.relation || {}; + const relationId = relation.id ? ` id "${relation.id}"` : ''; + const frameKind = hit.frame?.kind || hit.frame?.shape || 'frame'; + const frameIdentity = hit.frame?.label || hit.frame?.id || hit.frameIndex; + const length = Math.round(hit.overlapLength * 10) / 10; + const from = hit.overlapStart.map((value) => Math.round(value * 10) / 10).join(', '); + const to = hit.overlapEnd.map((value) => Math.round(value * 10) / 10).join(', '); + const message = `[composition/container-border-run] ${diagramType} ${relationCollection}[${hit.relationIndex}]${relationId} "${relation.from}" -> "${relation.to}" follows ${frameKind} "${frameIdentity}" ${hit.side} border for ${length}px on segment ${hit.segmentIndex} [${from}] -> [${to}] — ${routeHint}.`; + recordDiagnostic({ + code: 'composition/container-border-run', + severity: 'error', + message, + subject: relationshipSubject(diagramType, relationCollection, hit.relationIndex, relation), + evidence: { + frameKind, + frameId: hit.frame?.id, + frameLabel: hit.frame?.label, + side: hit.side, + segmentIndex: hit.segmentIndex, + overlapLengthPx: length, + from: hit.overlapStart, + to: hit.overlapEnd, + }, + supportedFixes: [routeHint], + }); + return message; + }); +} + +export function routeBudgetMetrics({ + routedRelations, + bendsPerRelationship = 2, + stretch = 1.35, + segmentPx = 16, + microSegmentPx = 8, +}) { + let maxBends = 0; + let routesOverSuggestedBends = 0; + let maxStretch = null; + let routesOverSuggestedStretch = 0; + let minSegmentPx = null; + let minInteriorSegmentPx = null; + let shortSegmentCount = 0; + let shortEndpointSegmentCount = 0; + let shortInteriorSegmentCount = 0; + let microSegmentCount = 0; + + for (const routed of asArray(routedRelations)) { + const points = normalizeRoutePoints(routed?.points); + if (points.length < 2) continue; + const bends = Math.max(0, points.length - 2); + maxBends = Math.max(maxBends, bends); + if (bends > bendsPerRelationship) routesOverSuggestedBends += 1; + + let routeLength = 0; + for (let index = 0; index < points.length - 1; index += 1) { + const length = Math.abs(points[index + 1][0] - points[index][0]) + Math.abs(points[index + 1][1] - points[index][1]); + if (length <= 0.0001) continue; + const position = segmentPosition(index, points.length - 1); + routeLength += length; + minSegmentPx = minSegmentPx == null ? length : Math.min(minSegmentPx, length); + if (position === 'interior') { + minInteriorSegmentPx = minInteriorSegmentPx == null ? length : Math.min(minInteriorSegmentPx, length); + } + if (length < segmentPx) { + shortSegmentCount += 1; + if (position === 'interior') shortInteriorSegmentCount += 1; + else shortEndpointSegmentCount += 1; + } + if (length < microSegmentPx) microSegmentCount += 1; + } + const direct = Math.abs(points.at(-1)[0] - points[0][0]) + Math.abs(points.at(-1)[1] - points[0][1]); + if (direct > 0.0001) { + const routeStretch = routeLength / direct; + maxStretch = maxStretch == null ? routeStretch : Math.max(maxStretch, routeStretch); + if (routeStretch > stretch + 0.0001) routesOverSuggestedStretch += 1; + } + } + + return { + maxBends, + routesOverSuggestedBends, + maxStretch, + routesOverSuggestedStretch, + minSegmentPx, + minInteriorSegmentPx, + shortSegmentCount, + shortEndpointSegmentCount, + shortInteriorSegmentCount, + microSegmentCount, + }; +} + +export function collectRouteRhythmIssues({ + routedRelations, + interiorSegmentPx = 16, + microSegmentPx = 8, +}) { + const issues = []; + for (const [fallbackIndex, routed] of asArray(routedRelations).entries()) { + const points = normalizeRoutePoints(routed?.points); + if (points.length < 2) continue; + for (let segmentIndex = 0; segmentIndex < points.length - 1; segmentIndex += 1) { + const start = points[segmentIndex]; + const end = points[segmentIndex + 1]; + const length = Math.abs(end[0] - start[0]) + Math.abs(end[1] - start[1]); + if (length <= 0.0001) continue; + const position = segmentPosition(segmentIndex, points.length - 1); + const code = length < microSegmentPx - 0.0001 + ? 'composition/micro-segment' + : position === 'interior' && length < interiorSegmentPx - 0.0001 + ? 'composition/short-interior-segment' + : null; + if (!code) continue; + issues.push({ + code, + relation: routed.relation, + relationIndex: Number.isInteger(routed.relationIndex) ? routed.relationIndex : fallbackIndex, + segmentIndex, + position, + length, + start, + end, + }); + } + } + return issues; +} + +export function cleanRouteRhythmProblems({ + relations, + endpointIds, + pathFor, + diagramType, + relationCollection, + profile, + profileIsAuthoritative = false, + routeHint = 'move the channel/via point to remove the cramped turn or give the route more corridor space', + interiorSegmentPx = 16, + microSegmentPx = 8, +}) { + if (qualityProfileForGate(profile, profileIsAuthoritative) !== 'showcase') return []; + const routedRelations = collectEligibleRoutedRelations({ relations, endpointIds, pathFor }); + return collectRouteRhythmIssues({ routedRelations, interiorSegmentPx, microSegmentPx }).map((hit) => { + const relation = hit.relation || {}; + const relationId = relation.id ? ` id "${relation.id}"` : ''; + const length = Math.round(hit.length * 10) / 10; + const from = hit.start.map((value) => Math.round(value * 10) / 10).join(', '); + const to = hit.end.map((value) => Math.round(value * 10) / 10).join(', '); + const rule = hit.code === 'composition/micro-segment' + ? `is below the ${microSegmentPx}px micro-segment floor` + : `is below the ${interiorSegmentPx}px interior-segment floor`; + const message = `[${hit.code}] showcase ${diagramType} ${relationCollection}[${hit.relationIndex}]${relationId} "${relation.from}" -> "${relation.to}" has a ${length}px ${hit.position} segment ${hit.segmentIndex} [${from}] -> [${to}] that ${rule} — ${routeHint}.`; + recordDiagnostic({ + code: hit.code, + severity: 'error', + message, + subject: relationshipSubject(diagramType, relationCollection, hit.relationIndex, relation), + evidence: { + segmentIndex: hit.segmentIndex, + position: hit.position, + lengthPx: length, + minimumPx: hit.code === 'composition/micro-segment' ? microSegmentPx : interiorSegmentPx, + from: hit.start, + to: hit.end, + }, + supportedFixes: [routeHint], + }); + return message; + }); +} + +export function cleanLabelRouteClearanceProblems({ + relations, + labels, + endpointIds, + pathFor, + diagramType, + relationCollection, + profile, + profileIsAuthoritative = false, + threshold = 4, + routeHint = 'adjust labelAt, labelDx, labelDy, or labelSegment; otherwise adjust the other relationship route/via/channel', +}) { + if (qualityProfileForGate(profile, profileIsAuthoritative) !== 'showcase') return []; + const routedRelations = collectEligibleRoutedRelations({ relations, endpointIds, pathFor }); + return collectLabelRouteClearance({ labels, routedRelations, threshold }).map((hit) => { + const describe = (relation, relationIndex) => { + const relationId = relation?.id ? ` id "${relation.id}"` : ''; + const relationLabel = relation?.label ? ` label "${relation.label}"` : ''; + return `${relationCollection}[${relationIndex}]${relationId} "${relation?.from}" -> "${relation?.to}"${relationLabel}`; + }; + const clearance = Math.round(hit.clearance * 10) / 10; + const from = hit.start.map((value) => Math.round(value * 10) / 10).join(', '); + const to = hit.end.map((value) => Math.round(value * 10) / 10).join(', '); + const message = `[composition/label-route-clearance] showcase ${diagramType} label "${hit.label?.label || hit.labelRelation?.label || ''}" on ${describe(hit.labelRelation, hit.labelRelationIndex)} is ${clearance}px from ${describe(hit.otherRelation, hit.otherRelationIndex)} segment ${hit.segmentIndex} [${from}] -> [${to}] (label rect ${formatRect(hit.rect)}; minimum ${threshold}px) — ${routeHint}.`; + recordDiagnostic({ + code: 'composition/label-route-clearance', + severity: 'error', + message, + subject: relationshipSubject(diagramType, relationCollection, hit.labelRelationIndex, hit.labelRelation), + evidence: { + label: hit.label?.label || hit.labelRelation?.label || '', + otherRelationship: relationshipSubject(diagramType, relationCollection, hit.otherRelationIndex, hit.otherRelation), + segmentIndex: hit.segmentIndex, + clearancePx: clearance, + minimumPx: threshold, + labelRect: hit.rect, + from: hit.start, + to: hit.end, + }, + supportedFixes: [routeHint], + }); + return message; + }); +} + +function qualityProfileForGate(profile, profileIsAuthoritative) { + return profileIsAuthoritative + ? profile + : process.env.ARCHIFY_QUALITY_PROFILE || profile; +} + +function collectEligibleRoutedRelations({ relations, endpointIds, pathFor }) { + return asArray(relations).map((relation, relationIndex) => { + if (!relation || typeof relation.from !== 'string' || typeof relation.to !== 'string') return null; + if (endpointIds && (!endpointIds.has(relation.from) || !endpointIds.has(relation.to))) return null; + return { relation, relationIndex, points: pathFor(relation)?.points }; + }).filter(Boolean); +} + +function segmentPosition(index, segmentCount) { + if (index === 0) return 'source-stub'; + if (index === segmentCount - 1) return 'target-stub'; + return 'interior'; +} + +export function normalizeRoutePoints(points) { + const finite = asArray(points).filter((point) => Array.isArray(point) && point.length === 2 && isFinitePoint(...point)); + const deduped = []; + for (const point of finite) { + const previous = deduped.at(-1); + if (!previous || Math.abs(point[0] - previous[0]) > 0.0001 || Math.abs(point[1] - previous[1]) > 0.0001) deduped.push(point); + } + const normalized = []; + for (const point of deduped) { + while (normalized.length >= 2 && collinearForward(normalized.at(-2), normalized.at(-1), point)) normalized.pop(); + normalized.push(point); + } + return normalized; +} + +function pointRectDistance(point, rect) { + const dx = Math.max(rect.x - point[0], 0, point[0] - (rect.x + rect.width)); + const dy = Math.max(rect.y - point[1], 0, point[1] - (rect.y + rect.height)); + return Math.hypot(dx, dy); +} + +function pointSegmentDistance(point, start, end) { + const dx = end[0] - start[0]; + const dy = end[1] - start[1]; + const lengthSquared = dx * dx + dy * dy; + if (lengthSquared <= 0.0000001) return Math.hypot(point[0] - start[0], point[1] - start[1]); + const projection = Math.max(0, Math.min(1, ((point[0] - start[0]) * dx + (point[1] - start[1]) * dy) / lengthSquared)); + return Math.hypot(point[0] - (start[0] + projection * dx), point[1] - (start[1] + projection * dy)); +} + +function collinearForward(a, b, c) { + if (Math.abs(crossProduct(a, b, c)) > 0.0001) return false; + return (b[0] - a[0]) * (c[0] - b[0]) + (b[1] - a[1]) * (c[1] - b[1]) >= -0.0001; +} + +function frameBorderSegments(frame) { + if (!frame || typeof frame !== 'object') return []; + if (frame.shape === 'line') { + const start = frame.start || [frame.x1, frame.y1]; + const end = frame.end || [frame.x2, frame.y2]; + return isFinitePoint(...start, ...end) ? [{ side: 'line', start, end }] : []; + } + if (!isFinitePoint(frame.x, frame.y, frame.width, frame.height) || frame.width <= 0 || frame.height <= 0) return []; + const radius = Math.max(0, Math.min(Number(frame.radius) || 0, frame.width / 2, frame.height / 2)); + const left = frame.x; + const right = frame.x + frame.width; + const top = frame.y; + const bottom = frame.y + frame.height; + return [ + { side: 'top', start: [left + radius, top], end: [right - radius, top] }, + { side: 'right', start: [right, top + radius], end: [right, bottom - radius] }, + { side: 'bottom', start: [right - radius, bottom], end: [left + radius, bottom] }, + { side: 'left', start: [left, bottom - radius], end: [left, top + radius] }, + ].filter(({ start, end }) => Math.hypot(end[0] - start[0], end[1] - start[1]) > 0.0001); +} + +function mergeBorderOverlaps(overlaps, border) { + const horizontal = Math.abs(border.start[1] - border.end[1]) <= 0.0001; + const axis = horizontal ? 0 : 1; + const fixed = horizontal ? border.start[1] : border.start[0]; + const sorted = overlaps.map((overlap) => ({ + low: Math.min(overlap.start[axis], overlap.end[axis]), + high: Math.max(overlap.start[axis], overlap.end[axis]), + })).sort((left, right) => left.low - right.low || left.high - right.high); + const merged = []; + for (const interval of sorted) { + const previous = merged.at(-1); + if (previous && interval.low <= previous.high + 0.0001) previous.high = Math.max(previous.high, interval.high); + else merged.push({ ...interval }); + } + return merged.map((interval) => ({ + ...interval, + length: interval.high - interval.low, + start: horizontal ? [interval.low, fixed] : [fixed, interval.low], + end: horizontal ? [interval.high, fixed] : [fixed, interval.high], + })); +} + +function collinearAxisOverlap(a, b, c, d) { + const epsilon = 0.0001; + const horizontal = Math.abs(a[1] - b[1]) <= epsilon + && Math.abs(c[1] - d[1]) <= epsilon + && Math.abs(a[1] - c[1]) <= epsilon; + const vertical = Math.abs(a[0] - b[0]) <= epsilon + && Math.abs(c[0] - d[0]) <= epsilon + && Math.abs(a[0] - c[0]) <= epsilon; + if (!horizontal && !vertical) return null; + const axis = horizontal ? 0 : 1; + const low = Math.max(Math.min(a[axis], b[axis]), Math.min(c[axis], d[axis])); + const high = Math.min(Math.max(a[axis], b[axis]), Math.max(c[axis], d[axis])); + if (high - low <= epsilon) return null; + const fixed = horizontal ? a[1] : a[0]; + return { + length: high - low, + start: horizontal ? [low, fixed] : [fixed, low], + end: horizontal ? [high, fixed] : [fixed, high], + }; +} + +function properSegmentIntersection(a, b, c, d) { + const abC = crossProduct(a, b, c); + const abD = crossProduct(a, b, d); + const cdA = crossProduct(c, d, a); + const cdB = crossProduct(c, d, b); + const epsilon = 0.0001; + const opposite = (left, right) => (left > epsilon && right < -epsilon) || (left < -epsilon && right > epsilon); + if (!opposite(abC, abD) || !opposite(cdA, cdB)) return null; + + const denominator = (a[0] - b[0]) * (c[1] - d[1]) - (a[1] - b[1]) * (c[0] - d[0]); + if (Math.abs(denominator) < epsilon) return null; + const ab = a[0] * b[1] - a[1] * b[0]; + const cd = c[0] * d[1] - c[1] * d[0]; + return [ + (ab * (c[0] - d[0]) - (a[0] - b[0]) * cd) / denominator, + (ab * (c[1] - d[1]) - (a[1] - b[1]) * cd) / denominator + ]; +} + +function crossProduct(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); +} + +function pointInBox(point, box) { + return point[0] >= box.x1 && point[0] <= box.x2 && point[1] >= box.y1 && point[1] <= box.y2; +} + +function segmentsIntersect(a, b, c, d) { + const o1 = orientation(a, b, c); + const o2 = orientation(a, b, d); + const o3 = orientation(c, d, a); + const o4 = orientation(c, d, b); + + if (o1 === 0 && onSegment(a, c, b)) return true; + if (o2 === 0 && onSegment(a, d, b)) return true; + if (o3 === 0 && onSegment(c, a, d)) return true; + if (o4 === 0 && onSegment(c, b, d)) return true; + + return o1 !== o2 && o3 !== o4; +} + +function orientation(a, b, c) { + const value = (b[1] - a[1]) * (c[0] - b[0]) - (b[0] - a[0]) * (c[1] - b[1]); + if (Math.abs(value) < 0.0001) return 0; + return value > 0 ? 1 : 2; +} + +function onSegment(a, b, c) { + return ( + b[0] <= Math.max(a[0], c[0]) && + b[0] >= Math.min(a[0], c[0]) && + b[1] <= Math.max(a[1], c[1]) && + b[1] >= Math.min(a[1], c[1]) + ); +} + +export function anchor(rect, side) { + switch (side) { + case 'left': return [rect.x, rect.cy]; + case 'right': return [rect.x + rect.width, rect.cy]; + case 'top': return [rect.cx, rect.y]; + case 'bottom': return [rect.cx, rect.y + rect.height]; + default: + return [rect.x + rect.width, rect.cy]; + } +} + +const PORT_OUTWARD_VECTOR = { + left: [-1, 0], + right: [1, 0], + top: [0, -1], + bottom: [0, 1], +}; + +// Automatic port spreading can put otherwise parallel anchors only a few +// pixels apart. A conventional midpoint dogleg then violates the renderer's +// own 8px/16px route-rhythm floors. Return a full outside-channel route when +// that happens, or null when the normal automatic route remains appropriate. +export function automaticPortRhythmBridge( + start, + end, + fromSide, + toSide, + { endpointStubPx = 24, interiorSegmentPx = 16, accept } = {}, +) { + if (!Array.isArray(start) || !Array.isArray(end) + || start.length !== 2 || end.length !== 2 + || !isFinitePoint(...start, ...end)) return null; + const fromVector = PORT_OUTWARD_VECTOR[fromSide]; + const toVector = PORT_OUTWARD_VECTOR[toSide]; + if (!fromVector || !toVector) return null; + + const startStub = [ + start[0] + fromVector[0] * endpointStubPx, + start[1] + fromVector[1] * endpointStubPx, + ]; + const endStub = [ + end[0] + toVector[0] * endpointStubPx, + end[1] + toVector[1] * endpointStubPx, + ]; + const candidates = []; + const verticalSides = new Set(['top', 'bottom']); + const horizontalSides = new Set(['left', 'right']); + + if (verticalSides.has(fromSide) && verticalSides.has(toSide) + && Math.abs(start[0] - end[0]) < interiorSegmentPx) { + for (const channelX of [ + Math.max(start[0], end[0]) + interiorSegmentPx, + Math.min(start[0], end[0]) - interiorSegmentPx, + ]) { + candidates.push([ + start, + startStub, + [channelX, startStub[1]], + [channelX, endStub[1]], + endStub, + end, + ]); + } + } + if (horizontalSides.has(fromSide) && horizontalSides.has(toSide) + && Math.abs(start[1] - end[1]) < interiorSegmentPx) { + for (const channelY of [ + Math.max(start[1], end[1]) + interiorSegmentPx, + Math.min(start[1], end[1]) - interiorSegmentPx, + ]) { + candidates.push([ + start, + startStub, + [startStub[0], channelY], + [endStub[0], channelY], + endStub, + end, + ]); + } + } + + return candidates + .map((points) => normalizeRoutePoints(points)) + .find((points) => ( + routeHonorsEndpointSides(points, fromSide, toSide) + && collectRouteRhythmIssues({ routedRelations: [{ points }], interiorSegmentPx }).length === 0 + && (typeof accept !== 'function' || accept(points)) + )) || null; +} + +// Keep conservative auto-routed fan-out/fan-in relationships visually +// distinct without changing authored route controls. The returned map only +// contains endpoints that belong to a shared automatic midpoint anchor. +export function automaticPortSpread(relations, boxes, { gutter = 16, maxSpacing = 14, sideFor } = {}) { + const groups = new Map(); + const spread = new Map(); + + const add = (relation, endpoint, rect, side, counterpart) => { + const key = `${rect.id}\u0000${side}`; + const items = groups.get(key) || []; + items.push({ relation, endpoint, rect, side, counterpart }); + groups.set(key, items); + }; + + for (const relation of asArray(relations)) { + if (!relation || (relation.route && relation.route !== 'auto')) continue; + if (relation.via || relation.channelX !== undefined || relation.channelY !== undefined || relation.labelAt) continue; + const from = boxes.get(relation.from); + const to = boxes.get(relation.to); + if (!from || !to) continue; + const fromSide = chosenSide( + relation.fromSide, + sideFor?.(relation, 'source') || defaultFromSide(from, to), + ); + const toSide = chosenSide( + relation.toSide, + sideFor?.(relation, 'target') || defaultToSide(from, to), + ); + add(relation, 'from', from, fromSide, to); + add(relation, 'to', to, toSide, from); + } + + for (const items of groups.values()) { + if (items.length < 2) continue; + const verticalSide = items[0].side === 'left' || items[0].side === 'right'; + items.sort((a, b) => { + const aCoordinate = verticalSide ? a.counterpart.cy : a.counterpart.cx; + const bCoordinate = verticalSide ? b.counterpart.cy : b.counterpart.cx; + if (aCoordinate !== bCoordinate) return aCoordinate - bCoordinate; + const aKey = `${a.relation.id || ''}\u0000${a.relation.from}\u0000${a.relation.to}\u0000${a.relation.label || ''}`; + const bKey = `${b.relation.id || ''}\u0000${b.relation.from}\u0000${b.relation.to}\u0000${b.relation.label || ''}`; + return aKey < bKey ? -1 : aKey > bKey ? 1 : 0; + }); + + const extent = verticalSide ? items[0].rect.height : items[0].rect.width; + const usable = Math.max(0, extent - gutter * 2); + const spacing = Math.min(maxSpacing, usable / (items.length - 1)); + if (!(spacing > 0)) continue; + + for (const [index, item] of items.entries()) { + const offset = (index - (items.length - 1) / 2) * spacing; + const point = anchor(item.rect, item.side); + if (verticalSide) point[1] += offset; + else point[0] += offset; + const endpoints = spread.get(item.relation) || {}; + endpoints[item.endpoint] = point; + spread.set(item.relation, endpoints); + } + } + + return spread; +} + +export function defaultFromSide(from, to) { + if (to.cx < from.cx) return 'left'; + if (to.cx > from.cx) return 'right'; + if (to.cy > from.cy) return 'bottom'; + return 'top'; +} + +export function defaultToSide(from, to) { + if (to.cx < from.cx) return 'right'; + if (to.cx > from.cx) return 'left'; + if (to.cy > from.cy) return 'top'; + return 'bottom'; +} + +export function chosenSide(side, fallback) { + return side && side !== 'auto' ? side : fallback; +} + +export function polylinePath(points) { + return points.map(([x, y], index) => `${index === 0 ? 'M' : 'L'} ${x} ${y}`).join(' '); +} + +export function routePointsValue(points) { + return asArray(points) + .filter((point) => Array.isArray(point) && point.length === 2 && isFinitePoint(...point)) + .map(([x, y]) => `${x},${y}`) + .join(';'); +} + +export function roundedPath(points, radius) { + if (points.length < 3 || radius <= 0) { + return polylinePath(points); + } + + const commands = [`M ${points[0][0]} ${points[0][1]}`]; + for (let i = 1; i < points.length - 1; i += 1) { + const [px, py] = points[i - 1]; + const [cx, cy] = points[i]; + const [nx, ny] = points[i + 1]; + const prevLen = Math.hypot(cx - px, cy - py); + const nextLen = Math.hypot(nx - cx, ny - cy); + const r = Math.min(radius, prevLen / 2, nextLen / 2); + if (r < 1) { + commands.push(`L ${cx} ${cy}`); + continue; + } + const before = [cx - ((cx - px) / prevLen) * r, cy - ((cy - py) / prevLen) * r]; + const after = [cx + ((nx - cx) / nextLen) * r, cy + ((ny - cy) / nextLen) * r]; + commands.push(`L ${before[0]} ${before[1]}`); + commands.push(`Q ${cx} ${cy} ${after[0]} ${after[1]}`); + } + const [endX, endY] = points[points.length - 1]; + commands.push(`L ${endX} ${endY}`); + return commands.join(' '); +} + +// Shared by edges/flows/transitions: all carry the same optional +// labelAt/labelDx/labelDy/labelSegment knobs. +export function labelPoint(item, points) { + if (item.labelAt) return item.labelAt; + if (points.length === 2) { + return [ + (points[0][0] + points[1][0]) / 2 + (item.labelDx || 0), + points[0][1] - 10 + (item.labelDy || 0) + ]; + } + const segmentIndex = Math.min(points.length - 2, Math.max(0, item.labelSegment ?? 1)); + const a = points[segmentIndex]; + const b = points[segmentIndex + 1]; + return [(a[0] + b[0]) / 2 + (item.labelDx || 0), (a[1] + b[1]) / 2 - 10 + (item.labelDy || 0)]; +} + +export const componentFill = { + frontend: 'c-frontend', + backend: 'c-backend', + database: 'c-database', + cloud: 'c-cloud', + security: 'c-security', + messagebus: 'c-messagebus', + external: 'c-external' +}; + +export const componentText = { + frontend: 't-frontend', + backend: 't-backend', + database: 't-database', + cloud: 't-cloud', + security: 't-security', + messagebus: 't-messagebus', + external: 't-external' +}; + +export const arrowClassMap = { + default: ['a-default', 'arrowhead'], + emphasis: ['a-emphasis', 'arrowhead-emphasis'], + security: ['a-security', 'arrowhead-security'], + dashed: ['a-dashed', 'arrowhead-dashed'] +}; + +// Label accent per edge variant. Workflow colors dashed (async trace) labels +// like the trace store it points at; the other renderers use the bus color. +export function variantAccent(variant, { dashed = 't-messagebus' } = {}) { + return variant === 'security' + ? 't-security' + : variant === 'emphasis' + ? 't-backend' + : variant === 'dashed' + ? dashed + : 't-muted'; +} + +export function formatRect(r) { + return `[${Math.round(r.x)}, ${Math.round(r.y)}, ${Math.round(r.width)}, ${Math.round(r.height)}]`; +} + +function formatDelta(n) { + const v = Math.round(n); + return v >= 0 ? `+${v}` : String(v); +} + +/** Actionable hint when an edge label rect hits a node/component box (#7). */ +export function suggestLabelObstacleFix(labelRect, lx, ly, obstacle, obstacleKind = 'component') { + const lxR = Math.round(lx); + const lyR = Math.round(ly); + const belowY = Math.round(obstacle.y + obstacle.height + 14); + const aboveY = Math.round(obstacle.y - 4); + return [ + ` label rect: ${formatRect(labelRect)}`, + ` ${obstacleKind} "${obstacle.id}" rect: ${formatRect(obstacle)}`, + ` Suggested fix: labelAt [${lxR}, ${belowY}] or labelDy ${formatDelta(belowY - lyR)} (below); or labelAt [${lxR}, ${aboveY}] or labelDy ${formatDelta(aboveY - lyR)} (above)`, + ].join('\n'); +} + +/** Hint when two edge labels collide. */ +export function suggestLabelPairFix(a, b) { + return [ + ` "${a.label}" ${formatRect(a)}; "${b.label}" ${formatRect(b)}`, + ' Suggested fix: adjust labelDx/labelDy/labelSegment, or route one relationship through a separate corridor', + ].join('\n'); +} + +/** Hint when two components/nodes are too close. */ +export function suggestComponentSeparation(a, b, minGap = 8) { + const rightX = Math.round(a.x + a.width + minGap); + const belowY = Math.round(a.y + a.height + minGap); + return [ + ` "${a.id}" ${formatRect(a)}; "${b.id}" ${formatRect(b)}`, + ` Suggested fix: move "${b.id}" pos to [${rightX}, ${Math.round(b.y)}] (right of "${a.id}") or [${Math.round(b.x)}, ${belowY}] (below)`, + ].join('\n'); +} diff --git a/tools/archify/renderers/shared/i18n.mjs b/tools/archify/renderers/shared/i18n.mjs new file mode 100644 index 0000000..dc69c72 --- /dev/null +++ b/tools/archify/renderers/shared/i18n.mjs @@ -0,0 +1,594 @@ +export const SUPPORTED_LOCALES = ['en', 'zh-CN']; +export const DEFAULT_LOCALE = 'en'; + +const ESCAPE_MAP = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; + +export function escapeHtml(value) { + return String(value ?? '').replace(/[&<>"']/g, (character) => ESCAPE_MAP[character]); +} + +// One catalog feeds renderer-time SVG/HTML copy and the selected runtime +// catalog embedded in each standalone artifact. Keeping every locale in one +// tuple makes missing translations impossible to hide behind an English +// fallback during development. +const MESSAGE_PAIRS = { + 'page.title': ['{title} Diagram', '{title}'], + 'diagram.description.architecture': ['An architecture diagram generated by Archify.', '由 Archify 生成的架构图。'], + 'diagram.description.workflow': ['A workflow diagram generated by Archify.', '由 Archify 生成的工作流图。'], + 'diagram.description.sequence': ['A sequence diagram generated by Archify.', '由 Archify 生成的时序图。'], + 'diagram.description.dataflow': ['A data-flow diagram generated by Archify.', '由 Archify 生成的数据流图。'], + 'diagram.description.lifecycle': ['A lifecycle diagram generated by Archify.', '由 Archify 生成的生命周期图。'], + 'node.focus': ['Focus {label}', '聚焦{label}'], + 'node.focus.detail': ['Focus {label}, {detail}', '聚焦{label},{detail}'], + 'node.context.architecture': ['Architecture component', '架构组件'], + 'node.context.workflow': ['Workflow node', '工作流节点'], + 'node.context.sequence': ['Sequence participant', '时序参与者'], + 'node.context.dataflow': ['Data-flow node', '数据流节点'], + 'node.context.lifecycle': ['Lifecycle state', '生命周期状态'], + 'legend.title': ['Legend', '图例'], + + 'legend.architecture.frontend': ['Frontend', '前端'], + 'legend.architecture.backend': ['Backend', '后端'], + 'legend.architecture.database': ['Database', '数据库'], + 'legend.architecture.cloud': ['Cloud', '云服务'], + 'legend.architecture.security': ['Security', '安全'], + 'legend.architecture.messagebus': ['Message bus', '消息总线'], + 'legend.architecture.external': ['External', '外部系统'], + 'legend.workflow.frontend': ['User UI', '用户界面'], + 'legend.workflow.backend': ['Agent logic', 'Agent 逻辑'], + 'legend.workflow.security': ['Policy', '策略'], + 'legend.workflow.messagebus': ['Tool action', '工具操作'], + 'legend.workflow.database': ['Context / trace', '上下文 / 追踪'], + 'legend.workflow.cloud': ['Cloud service', '云服务'], + 'legend.workflow.external': ['External system', '外部系统'], + 'legend.sequence.emphasis': ['request', '请求'], + 'legend.sequence.return': ['return', '返回'], + 'legend.sequence.security': ['security', '安全'], + 'legend.sequence.dashed': ['async trace', '异步追踪'], + 'legend.sequence.default': ['default message', '默认消息'], + 'legend.dataflow.emphasis': ['primary data', '主要数据'], + 'legend.dataflow.security': ['policy / PII', '策略 / PII'], + 'legend.dataflow.dashed': ['async batch', '异步批处理'], + 'legend.dataflow.database': ['data store', '数据存储'], + 'legend.dataflow.default': ['data flow', '数据流'], + 'legend.lifecycle.start': ['start', '开始'], + 'legend.lifecycle.active': ['active state', '活动状态'], + 'legend.lifecycle.waiting': ['waiting', '等待'], + 'legend.lifecycle.decision': ['decision', '决策'], + 'legend.lifecycle.success': ['terminal success', '成功终态'], + 'legend.lifecycle.failure': ['failure / exit', '失败 / 退出'], + 'legend.lifecycle.neutral': ['neutral', '中性状态'], + 'legend.lifecycle.external': ['external', '外部状态'], + + 'viewer.kind.frontend': ['Frontend', '前端'], + 'viewer.kind.backend': ['Backend', '后端'], + 'viewer.kind.database': ['Database', '数据库'], + 'viewer.kind.cloud': ['Cloud', '云服务'], + 'viewer.kind.security': ['Security', '安全'], + 'viewer.kind.messagebus': ['Message bus', '消息总线'], + 'viewer.kind.external': ['External', '外部系统'], + 'viewer.kind.neutral': ['Neutral', '中性'], + 'viewer.kind.node': ['Node', '节点'], + 'viewer.kind.start': ['Start', '开始'], + 'viewer.kind.active': ['Active', '活动'], + 'viewer.kind.waiting': ['Waiting', '等待'], + 'viewer.kind.decision': ['Decision', '决策'], + 'viewer.kind.success': ['Success', '成功'], + 'viewer.kind.failure': ['Failure', '失败'], + + 'viewer.toolbar.actions': ['Diagram actions', '图表操作'], + 'viewer.theme.toggle.title': ['Toggle theme (T)', '切换主题(T)'], + 'viewer.theme.toggle': ['Toggle color theme', '切换颜色主题'], + 'viewer.theme.dark': ['Dark', '深色'], + 'viewer.theme.light': ['Light', '浅色'], + 'viewer.preset.choose.title': ['Choose visual style (S cycles)', '选择视觉风格(S 循环切换)'], + 'viewer.preset.choose': ['Choose visual style', '选择视觉风格'], + 'viewer.preset.style': ['Style', '风格'], + 'viewer.preset.menu': ['Visual style', '视觉风格'], + 'viewer.preset.identity': ['Visual identity', '视觉表达'], + 'viewer.preset.cycles': ['S cycles', 'S 循环切换'], + 'viewer.preset.classic': ['Classic', '经典'], + 'viewer.preset.classic.short': ['Classic', '经典'], + 'viewer.preset.classic.hint': ['Stable technical default', '稳定的技术默认风格'], + 'viewer.preset.flow': ['Signal Flow', '信号流'], + 'viewer.preset.flow.short': ['Flow', '流动'], + 'viewer.preset.flow.hint': ['Motion-forward presentation', '突出动态流向'], + 'viewer.preset.blueprint': ['Blueprint', '蓝图'], + 'viewer.preset.blueprint.hint': ['Engineering review', '工程评审'], + 'viewer.preset.editorial': ['Editorial', '编辑风格'], + 'viewer.preset.editorial.hint': ['Publication and launch notes', '适合发布与上线说明'], + 'viewer.preset.badge.signalFlow': ['SIGNAL FLOW', '信号流'], + 'viewer.preset.badge.blueprint': ['BLUEPRINT / REV 01', '蓝图 / 修订 01'], + 'viewer.preset.badge.editorial': ['EDITORIAL / FIELD NOTE', '编辑风格 / 现场笔记'], + 'viewer.preset.badge.editorialPlate': ['ARCHIFY / PLATE 04', 'ARCHIFY / 图版 04'], + 'viewer.preset.current': ['Visual style: {style}. Choose visual style', '当前视觉风格:{style}。选择视觉风格'], + 'viewer.motion.live': ['Live', '动态'], + 'viewer.motion.still': ['Still', '静态'], + 'viewer.motion.pause': ['Pause motion', '暂停动效'], + 'viewer.motion.resume': ['Resume motion', '恢复动效'], + 'viewer.motion.reduced': ['Motion paused by reduced-motion preference', '已根据减少动态效果偏好暂停动效'], + 'viewer.motion.hidden': ['Motion paused while this page is hidden', '页面不可见时已暂停动效'], + 'viewer.motion.yielding': ['Pause motion; currently yielding to {owner}', '暂停动效;当前让位于{owner}'], + 'viewer.motion.yielding.title': ['Live preview enabled · yielding to {owner}', '动态预览已启用 · 正在让位于{owner}'], + 'viewer.owner.story': ['the guided story', '引导故事'], + 'viewer.owner.chapter': ['the active chapter', '当前章节'], + 'viewer.owner.chapterPreview': ['the chapter delta preview', '章节差异预览'], + 'viewer.owner.handoff': ['the chapter handoff', '章节交接'], + 'viewer.owner.route': ['Route Probe', '路径探测'], + 'viewer.owner.lens': ['Semantic Lens', '语义透镜'], + 'viewer.owner.relationship': ['Relationship Preview', '关系预览'], + 'viewer.owner.intent': ['Intent Trace', '意图追踪'], + 'viewer.owner.focus': ['semantic focus', '语义聚焦'], + 'viewer.owner.legend': ['legend preview', '图例预览'], + 'viewer.owner.reader': ['reader interaction', '读者交互'], + 'viewer.present.enter': ['Enter presentation stage', '进入演示模式'], + 'viewer.present.enter.title': ['Presentation stage (F)', '演示模式(F)'], + 'viewer.present.exit': ['Exit presentation stage', '退出演示模式'], + 'viewer.present.exit.title': ['Exit presentation stage (F or Escape)', '退出演示模式(F 或 Escape)'], + 'viewer.present.present': ['Present', '演示'], + 'viewer.present.exit.label': ['Exit', '退出'], + + 'viewer.export.button': ['Export', '导出'], + 'viewer.export.button.title': ['Export diagram (E)', '导出图表(E)'], + 'viewer.export.diagram': ['Export diagram', '导出图表'], + 'viewer.export.menu': ['Export', '导出'], + 'viewer.export.subtitle': ['Portable, clean outputs', '便携、整洁的输出'], + 'viewer.export.share': ['Share', '分享'], + 'viewer.export.shareCard': ['Share Card', '分享卡片'], + 'viewer.export.routeShareCard': ['Route Share Card', '路径分享卡片'], + 'viewer.export.reachShareCard': ['Reach Share Card', '可达范围分享卡片'], + 'viewer.export.copyShareCard': ['Copy Share Card', '复制分享卡片'], + 'viewer.export.copyDiagram': ['Copy diagram', '复制图表'], + 'viewer.export.clipboardPng': ['PNG to clipboard', '复制 PNG 到剪贴板'], + 'viewer.export.raster': ['Raster images', '位图'], + 'viewer.export.image': ['Image', '图像'], + 'viewer.export.lossless': ['Lossless image', '无损图像'], + 'viewer.export.compact': ['Compact image', '紧凑图像'], + 'viewer.export.modern': ['Modern image', '现代图像格式'], + 'viewer.export.vectorMotion': ['Vector and motion', '矢量与动效'], + 'viewer.export.vectorMotion.heading': ['Vector & motion', '矢量与动效'], + 'viewer.export.editable': ['Editable vector', '可编辑矢量图'], + 'viewer.export.motion6s': ['6s motion', '6 秒动效'], + 'viewer.export.unsupported': ['Not supported by this browser', '当前浏览器不支持'], + 'viewer.export.clipboardUnsupported': ['Clipboard image write not supported by this browser', '当前浏览器不支持写入图片剪贴板'], + 'viewer.export.clipboardUnsupported.period': ['Clipboard image write not supported by this browser.', '当前浏览器不支持写入图片剪贴板。'], + 'viewer.export.clipboardUnsupported.short': ['Clipboard image write not supported in this browser.', '此浏览器不支持写入图片剪贴板。'], + 'viewer.export.motionUnavailable': ['Motion capture unavailable in this browser', '当前浏览器无法录制动效'], + 'viewer.export.webmUnavailable': ['WebM unavailable in this browser', '当前浏览器不支持 WebM'], + 'viewer.export.failed': ['Export failed: {message}', '导出失败:{message}'], + 'viewer.export.unknownVariant': ['Unknown Share Card variant: {variant}', '未知的分享卡片类型:{variant}'], + 'viewer.export.routeRequired': ['Trace a route before exporting a Route Share Card', '请先追踪路径,再导出路径分享卡片'], + 'viewer.export.reachRequired': ['Trace authored reach before exporting a Reach Share Card', '请先追踪编写可达范围,再导出可达范围分享卡片'], + 'viewer.export.unknown': ['unknown', '未知错误'], + 'viewer.export.routeFailed': ['Route Share Card export failed: {message}', '路径分享卡片导出失败:{message}'], + 'viewer.export.reachFailed': ['Reach Share Card export failed: {message}', '可达范围分享卡片导出失败:{message}'], + 'viewer.export.copyFailed': ['Copy failed: {message}', '复制失败:{message}'], + 'viewer.export.copiedPng': ['Copied PNG to clipboard', '已将 PNG 复制到剪贴板'], + 'viewer.export.copiedShare': ['Copied Share Card', '已复制分享卡片'], + 'viewer.export.downloadedShare': ['Downloaded Share Card', '已下载分享卡片'], + 'viewer.export.downloadedRoute': ['Downloaded Route Share Card', '已下载路径分享卡片'], + 'viewer.export.downloadedReach': ['Downloaded Reach Share Card', '已下载可达范围分享卡片'], + 'viewer.export.downloadedWebm': ['Downloaded WebM', '已下载 WebM'], + 'viewer.export.recording': ['Recording 6 seconds of motion…', '正在录制 6 秒动效…'], + 'viewer.export.card.routeSummary.one': ['Route: {source} → {target} · {count} directed hop', '路径:{source} → {target} · {count} 个有向跳转'], + 'viewer.export.card.routeSummary.other': ['Route: {source} → {target} · {count} directed hops', '路径:{source} → {target} · {count} 个有向跳转'], + 'viewer.export.card.reachSummary': ['Authored {direction} from {origin} · {nodes} · {links} · max {hops}', '从{origin}开始的编写{direction} · {nodes} · {links} · 最深 {hops}'], + 'viewer.export.card.node.one': ['{count} node', '{count} 个节点'], + 'viewer.export.card.node.other': ['{count} nodes', '{count} 个节点'], + 'viewer.export.card.link.one': ['{count} link', '{count} 条连接'], + 'viewer.export.card.link.other': ['{count} links', '{count} 条连接'], + 'viewer.export.card.hop.one': ['{count} hop', '{count} 跳'], + 'viewer.export.card.hop.other': ['{count} hops', '{count} 跳'], + 'viewer.export.card.routeBadge': ['ARCHIFY · ROUTE · {hops}', 'ARCHIFY · 路径 · {hops}'], + 'viewer.export.card.reachBadge': ['ARCHIFY · {direction} REACH', 'ARCHIFY · {direction}可达范围'], + 'viewer.export.card.defaultBadge': ['ARCHIFY · {preset} · {theme}', 'ARCHIFY · {preset} · {theme}'], + 'viewer.export.direction.upstream': ['Upstream', '上游'], + 'viewer.export.direction.downstream': ['Downstream', '下游'], + 'viewer.export.error.canvasUnavailable': ['Canvas unavailable for {label}', '无法为{label}使用画布'], + 'viewer.export.error.contextUnavailable': ['2D canvas context unavailable for {label}', '无法为{label}创建二维画布上下文'], + 'viewer.export.error.toBlobUnavailable': ['canvas.toBlob unavailable for {label}', '{label}无法使用 canvas.toBlob'], + 'viewer.export.error.toBlobNull': ['canvas.toBlob returned no data for {label}', '{label}的 canvas.toBlob 未返回数据'], + 'viewer.export.error.variantsCombined': ['Share Card variants cannot be combined', '无法同时组合多种分享卡片类型'], + 'viewer.export.error.viewerState': ['Share Card export could not remove temporary viewer state', '分享卡片导出无法移除临时 Viewer 状态'], + 'viewer.export.error.routeState': ['Route Card export could not preserve the resolved route safely', '路径卡片导出无法安全保留已解析路径'], + 'viewer.export.error.reachState': ['Reach Card export could not preserve authored reach safely', '可达范围卡片导出无法安全保留编写的可达范围'], + 'viewer.export.error.webmRequirements': ['WebM motion export requires a trace animation and browser MediaRecorder support', 'WebM 动效导出需要追踪动画及浏览器 MediaRecorder 支持'], + 'viewer.export.error.mediaRecorder': ['MediaRecorder failed', 'MediaRecorder 录制失败'], + 'viewer.export.error.emptyWebm': ['MediaRecorder produced an empty WebM', 'MediaRecorder 生成了空的 WebM'], + 'viewer.export.error.webmBackground': ['SVG background could not be loaded for WebM export', '无法为 WebM 导出加载 SVG 背景'], + + 'viewer.guided.region': ['Guided diagram views', '图表引导视图'], + 'viewer.guided.previous': ['Previous guided view', '上一个引导视图'], + 'viewer.guided.previous.title': ['Previous guided view ([)', '上一个引导视图([)'], + 'viewer.guided.next': ['Next guided view', '下一个引导视图'], + 'viewer.guided.next.title': ['Next guided view (])', '下一个引导视图(])'], + 'viewer.guided.views': ['Guided views', '引导视图'], + 'viewer.guided.explore': ['Explore this system', '探索此系统'], + 'viewer.guided.intro': ['Step through curated paths without changing the source diagram.', '沿精选路径逐步查看,而不改变源图表。'], + 'viewer.guided.trail': ['Story trail', '故事轨迹'], + 'viewer.guided.beat': ['Beat', '节点'], + 'viewer.guided.nextBeat': ['Next', '下一步'], + 'viewer.guided.play': ['Play guided story', '播放引导故事'], + 'viewer.guided.play.title': ['Play guided story (P)', '播放引导故事(P)'], + 'viewer.guided.pause': ['Pause guided story', '暂停引导故事'], + 'viewer.guided.pause.title': ['Pause guided story (P)', '暂停引导故事(P)'], + 'viewer.guided.replay': ['Replay guided story', '重播引导故事'], + 'viewer.guided.replay.title': ['Replay guided story (P)', '重播引导故事(P)'], + 'viewer.guided.playStory': ['Play story', '播放故事'], + 'viewer.guided.pauseStory': ['Pause', '暂停'], + 'viewer.guided.replayStory': ['Replay story', '重播故事'], + 'viewer.guided.motionUnavailable': ['Story playback unavailable while motion is Still', '静态模式下无法播放故事'], + 'viewer.guided.enableMotion': ['Switch motion to Live to play the guided story', '切换为动态模式以播放引导故事'], + 'viewer.guided.selectBeatLink': ['Select a Story Beat to copy its exact link', '选择故事节点以复制其精确链接'], + 'viewer.guided.copyMoment': ['Copy moment', '复制此刻'], + 'viewer.guided.momentCopied': ['Moment link copied', '已复制时刻链接'], + 'viewer.guided.momentCopyFailed': ['Could not copy story moment link', '无法复制故事时刻链接'], + 'viewer.guided.copied': ['Copied', '已复制'], + 'viewer.guided.copyFailed': ['Copy failed', '复制失败'], + 'viewer.guided.showAll': ['Show all', '显示全部'], + 'viewer.guided.showAll.aria': ['Show entire diagram', '显示完整图表'], + 'viewer.guided.chapters': ['Story chapters', '故事章节'], + 'viewer.guided.storyTrail': ['Story trail for {label}: {count} beats', '{label}的故事轨迹:{count} 个节点'], + 'viewer.guided.chapter.open': ['Open chapter {index} of {total}: {label}, {count} stops', '打开第 {index}/{total} 章:{label},{count} 个停靠点'], + 'viewer.guided.chapter.current': ['Current chapter {index} of {total}: {label}, {count} stops', '当前第 {index}/{total} 章:{label},{count} 个停靠点'], + 'viewer.guided.chapter.selectedNodes': ['{count} selected nodes', '已选择 {count} 个节点'], + 'viewer.guided.chapter.stops': ['{count} stops', '{count} 个停靠点'], + 'viewer.guided.chapter.stop.one': ['{count} stop', '{count} 个停靠点'], + 'viewer.guided.chapter.stop.other': ['{count} stops', '{count} 个停靠点'], + 'viewer.guided.chapter.current.title': ['{label} — current chapter, {count} stops', '{label} — 当前章节,{count} 个停靠点'], + 'viewer.guided.chapter.delta.expanded': ['{stay} stay, {enter} enter, {leave} leave', '{stay} 个保留,{enter} 个进入,{leave} 个离开'], + 'viewer.guided.chapter.delta.aria': ['Open chapter {index} of {total}: {label}. Chapter focus delta: {delta}', '打开第 {index}/{total} 章:{label}。章节聚焦差异:{delta}'], + 'viewer.guided.chapter.delta.title': ['{label} — {delta} chapter focus', '{label} — 章节聚焦 {delta}'], + 'viewer.guided.handoff': ['{from} → {to} · via {label}', '{from} → {to} · 经由{label}'], + 'viewer.guided.share.chapter': ['Chapter {index} / {total}', '章节 {index} / {total}'], + 'viewer.guided.share.initial': ['Chapter 01 / 01', '章节 01 / 01'], + 'viewer.guided.share.default': ['Guided chapter', '引导章节'], + 'viewer.guided.state.ready': ['Ready', '就绪'], + 'viewer.guided.state.playing': ['Playing', '播放中'], + 'viewer.guided.state.settled': ['Settled', '已完成'], + 'viewer.guided.state.paused': ['Paused', '已暂停'], + 'viewer.guided.state.pinned': ['Pinned', '已固定'], + 'viewer.guided.state.still': ['Still', '静态'], + 'viewer.guided.share.step': ['Step {index} / {total} · {label}', '步骤 {index} / {total} · {label}'], + 'viewer.guided.share.staticMoment': ['{step} · Static moment', '{step} · 静态时刻'], + 'viewer.guided.share.complete': ['{count} steps complete · {note}', '{count} 个步骤已完成 · {note}'], + 'viewer.guided.share.settled': ['Path settled for reading.', '路径已稳定,可供阅读。'], + 'viewer.guided.share.staticPath': ['{count} steps · Static path', '{count} 个步骤 · 静态路径'], + 'viewer.guided.share.ready': ['{count} steps · Ready', '{count} 个步骤 · 就绪'], + 'viewer.guided.share.aria': ['{state} chapter {index} of {total}: {label}. {beat}. {route}', '{state},第 {index}/{total} 章:{label}。{beat}。{route}'], + 'viewer.guided.beat.start': ['Beat {index} / {total} · {label} · starting point', '节点 {index} / {total} · {label} · 起点'], + 'viewer.guided.beat.forward': ['Beat {index} / {total} · {from} → {to}', '节点 {index} / {total} · {from} → {to}'], + 'viewer.guided.beat.reverse': ['Beat {index} / {total} · {from} → {to} · reverse authored link', '节点 {index} / {total} · {from} → {to} · 反向编写连接'], + 'viewer.guided.beat.multiple': ['Beat {index} / {total} · {from} ⇄ {to} · {count} authored links', '节点 {index} / {total} · {from} ⇄ {to} · {count} 条编写连接'], + 'viewer.guided.beat.group': ['Beat {index} / {total} · {from} · {to} · grouped · no direct link', '节点 {index} / {total} · {from} · {to} · 分组 · 无直接连接'], + 'viewer.guided.beat.aria.prefix': ['Story beat {index} of {total}: {label}. ', '故事节点 {index}/{total}:{label}。'], + 'viewer.guided.beat.aria.start': ['Starting point.', '起点。'], + 'viewer.guided.beat.aria.forward': ['From {from} through one authored forward relationship.', '从{from}经一条正向编写关系到达。'], + 'viewer.guided.beat.aria.reverse': ['From {from}; the authored relationship points from {to} to {from}.', '从{from}出发;编写关系实际由{to}指向{from}。'], + 'viewer.guided.beat.aria.multiple': ['From {from} through {count} authored relationships; shown without arbitrary motion.', '从{from}经 {count} 条编写关系到达;不使用任意动效。'], + 'viewer.guided.beat.aria.group': ['Grouped from {from} with no direct authored relationship.', '与{from}分组展示,没有直接编写关系。'], + 'viewer.guided.caption.start': ['Starting point', '起点'], + 'viewer.guided.caption.grouped': ['Grouped transition · no direct authored link', '分组过渡 · 无直接编写连接'], + 'viewer.guided.caption.more': [' +{count} more', ' +另外 {count} 条'], + 'viewer.guided.caption.reverse': ['Reverse authored relationship', '反向编写关系'], + 'viewer.guided.caption.relationships': ['{count} authored relationships', '{count} 条编写关系'], + 'viewer.guided.caption.relationship': ['Authored relationship', '编写关系'], + 'viewer.guided.caption.direction': ['authored direction: {from} → {to}', '编写方向:{from} → {to}'], + 'viewer.guided.caption.starting': ['Authored starting point', '编写起点'], + 'viewer.guided.beatLink': ['Copy link to current story moment: Beat {index} of {total}: {label}', '复制当前故事时刻链接:第 {index}/{total} 个节点:{label}'], + 'viewer.guided.noStory': ['This diagram has no authored guided story.', '此图表没有编写引导故事。'], + + 'viewer.guide.eyebrow': ['Diagram guide', '图表指南'], + 'viewer.guide.close': ['Close diagram guide', '关闭图表指南'], + 'viewer.guide.inspecting': ['Inspecting compiled semantics', '正在检查已编译语义'], + 'viewer.guide.actions': ['Diagram exploration actions', '图表探索操作'], + 'viewer.guide.find': ['Find any node', '查找任意节点'], + 'viewer.guide.find.hint': ['Search labels, responsibilities, kinds, and stable IDs.', '搜索标签、职责、类型和稳定 ID。'], + 'viewer.guide.route': ['Trace a route', '追踪路径'], + 'viewer.guide.route.aria': ['Trace a directed route', '追踪有向路径'], + 'viewer.guide.route.hint': ['Ask how two semantic nodes connect in authored direction.', '查看两个语义节点如何按编写方向连接。'], + 'viewer.guide.map': ['See the whole system', '查看完整系统'], + 'viewer.guide.map.hint': ['Open Semantic Radar with a live viewport and stable nodes.', '打开带实时视口和稳定节点的语义雷达。'], + 'viewer.guide.lens': ['Compare semantic kinds', '比较语义类型'], + 'viewer.guide.lens.hint': ['Count roles, reveal their traffic, and compare direct authored links.', '统计角色、显示流量并比较直接编写的连接。'], + 'viewer.guide.story': ['Play the guided story', '播放引导故事'], + 'viewer.guide.story.hint': ['Walk the authored chapters and real relationships.', '浏览已编写的章节和真实关系。'], + 'viewer.guide.present': ['Enter Presentation Stage', '进入演示模式'], + 'viewer.guide.present.hint': ['Give the live diagram the viewport without changing export.', '让实时图表占满视口,同时不改变导出。'], + 'viewer.guide.shortcuts': ['Additional keyboard shortcuts', '其他键盘快捷键'], + 'viewer.guide.shortcut.export': ['Export', '导出'], + 'viewer.guide.shortcut.theme': ['Theme', '主题'], + 'viewer.guide.shortcut.style': ['Style', '风格'], + 'viewer.guide.shortcut.reset': ['Reset', '重置'], + 'viewer.guide.shortcut.zoomIn': ['Zoom in', '放大'], + 'viewer.guide.shortcut.zoomOut': ['Zoom out', '缩小'], + 'viewer.guide.shortcut.close': ['Close', '关闭'], + 'viewer.guide.facts': ['{nodes} · {relationships} · {views}', '{nodes} · {relationships} · {views}'], + 'viewer.guide.fact.node.one': ['{count} semantic node', '{count} 个语义节点'], + 'viewer.guide.fact.node.other': ['{count} semantic nodes', '{count} 个语义节点'], + 'viewer.guide.fact.relationship.one': ['{count} relationship', '{count} 条关系'], + 'viewer.guide.fact.relationship.other': ['{count} relationships', '{count} 条关系'], + 'viewer.guide.fact.view.one': ['{count} guided view', '{count} 个引导视图'], + 'viewer.guide.fact.view.other': ['{count} guided views', '{count} 个引导视图'], + 'viewer.guide.story.available.one': ['Walk {count} authored chapter and its real relationships.', '浏览 {count} 个已编写章节及其真实关系。'], + 'viewer.guide.story.available.other': ['Walk {count} authored chapters and their real relationships.', '浏览 {count} 个已编写章节及其真实关系。'], + 'viewer.guide.story.unavailable': ['No authored guided story in this diagram.', '此图表没有编写引导故事。'], + 'viewer.guide.open': ['Open diagram guide', '打开图表指南'], + 'viewer.guide.noStory': ['This diagram has no authored guided story.', '此图表没有编写引导故事。'], + + 'viewer.finder.title': ['Find a node', '查找节点'], + 'viewer.finder.close': ['Close node finder', '关闭节点查找器'], + 'viewer.finder.placeholder': ['Search labels or IDs', '搜索标签或 ID'], + 'viewer.finder.search': ['Search diagram nodes', '搜索图表节点'], + 'viewer.finder.results': ['Diagram nodes', '图表节点'], + 'viewer.finder.empty': ['No matching nodes', '没有匹配的节点'], + 'viewer.finder.result.focus': ['Focus {label}', '聚焦{label}'], + 'viewer.finder.result.routeStart': ['Choose {label} as route start', '选择{label}作为路径起点'], + 'viewer.finder.result.routeTarget': ['Choose {label} as route destination, {links}', '选择{label}作为路径终点,{links}'], + 'viewer.finder.status.empty': ['No matching nodes', '没有匹配的节点'], + 'viewer.finder.status.count.one': ['{count} matching node', '{count} 个匹配节点'], + 'viewer.finder.status.count.other': ['{count} matching nodes', '{count} 个匹配节点'], + 'viewer.finder.noun.nodes': ['nodes', '个节点'], + 'viewer.finder.link.one': ['{count} link', '{count} 条连接'], + 'viewer.finder.link.other': ['{count} links', '{count} 条连接'], + 'viewer.finder.result.focus.one': ['Focus {label}, {count} related connection', '聚焦{label},{count} 条相关连接'], + 'viewer.finder.result.focus.other': ['Focus {label}, {count} related connections', '聚焦{label},{count} 条相关连接'], + 'viewer.finder.status.filtered': ['{visible} of {available} {noun}', '{visible}/{available} {noun}'], + 'viewer.finder.status.all': ['{available} {noun}', '{available} {noun}'], + + 'viewer.passport.eyebrow': ['Semantic passport', '语义护照'], + 'viewer.passport.metadata': ['Node metadata', '节点元数据'], + 'viewer.passport.evidence': ['Verified source evidence', '已验证的源代码证据'], + 'viewer.passport.verified': ['Verified source', '已验证来源'], + 'viewer.passport.reach': ['Authored reach', '编写可达范围'], + 'viewer.passport.reach.trace': ['Trace authored reachability', '追踪编写的可达性'], + 'viewer.passport.upstream': ['Upstream', '上游'], + 'viewer.passport.downstream': ['Downstream', '下游'], + 'viewer.passport.upstream.trace': ['Trace upstream authored reachability', '追踪上游编写可达性'], + 'viewer.passport.downstream.trace': ['Trace downstream authored reachability', '追踪下游编写可达性'], + 'viewer.passport.close': ['Close semantic passport', '关闭语义护照'], + 'viewer.passport.copy': ['Copy link', '复制链接'], + 'viewer.passport.copy.focus': ['Copy link to focused node', '复制聚焦节点的链接'], + 'viewer.passport.relations': ['Relations', '关系'], + 'viewer.passport.relations.show': ['Show connected relationships', '显示关联关系'], + 'viewer.passport.relations.hide': ['Hide connected relationships', '隐藏关联关系'], + 'viewer.passport.relations.list': ['Connected relationships', '关联关系'], + 'viewer.passport.copyRelation': ['Copy relation', '复制关系'], + 'viewer.passport.copyNode': ['Copy node', '复制节点'], + 'viewer.passport.copyPinned': ['Copy link to pinned relationship', '复制固定关系的链接'], + 'viewer.passport.copySource': ['Copy link to source node', '复制来源节点的链接'], + 'viewer.passport.copy.focused.success': ['Focused node link copied', '已复制聚焦节点链接'], + 'viewer.passport.copy.pinned.success': ['Pinned relationship link copied', '已复制固定关系链接'], + 'viewer.passport.copy.focused.failed': ['Could not copy focused node link', '无法复制聚焦节点链接'], + 'viewer.passport.copy.pinned.failed': ['Could not copy pinned relationship link', '无法复制固定关系链接'], + 'viewer.passport.relationship.none': ['No connected relationships', '没有关联关系'], + 'viewer.passport.relationship.count.one': ['{count} relation', '{count} 条关系'], + 'viewer.passport.relationship.count.other': ['{count} relations', '{count} 条关系'], + 'viewer.passport.relationship.show.one': ['Show {count} connected relationship', '显示 {count} 条关联关系'], + 'viewer.passport.relationship.show.other': ['Show {count} connected relationships', '显示 {count} 条关联关系'], + 'viewer.passport.relationship.summary': ['{out} outgoing · {in} incoming{loops}', '{out} 条出向 · {in} 条入向{loops}'], + 'viewer.passport.relationship.loops': [' · {count} loop', ' · {count} 条自环'], + 'viewer.passport.relationship.explorer': ['Direct relationship explorer', '直接关系浏览器'], + 'viewer.passport.relationship.help': ['Use arrow keys to explore relationships. Press Enter or Space to pin details; Escape clears.', '使用方向键浏览关系。按 Enter 或空格键固定详情;按 Escape 清除。'], + 'viewer.passport.relationship.loopsBack': ['loops back', '回环'], + 'viewer.passport.relationship.connectsTo': ['connects to', '连接到'], + 'viewer.passport.relationship.connectsFrom': ['connects from', '连接自'], + 'viewer.passport.relationship.pinned': ['Pinned relationship · {from} → {to} · {label}', '已固定关系 · {from} → {to} · {label}'], + 'viewer.passport.relationship.inspect': ['Inspect relationship {index} of {total}: {from} to {to}, {label}. Press Enter for details.', '检查第 {index}/{total} 条关系:{from} 到 {to},{label}。按 Enter 查看详情。'], + 'viewer.passport.relationship.group.out': ['Outgoing', '出向'], + 'viewer.passport.relationship.group.in': ['Incoming', '入向'], + 'viewer.passport.relationship.group.loop': ['Self loops', '自环'], + 'viewer.passport.relationship.row': ['{group}: {relationship}, {neighbor}', '{group}:{relationship},{neighbor}'], + 'viewer.passport.relationship.direction.out': ['OUT →', '出 →'], + 'viewer.passport.relationship.direction.in': ['← IN', '← 入'], + 'viewer.passport.relationship.direction.loop': ['LOOP', '自环'], + 'viewer.passport.sourceCount.one': ['{count} verified source reference', '{count} 个已验证来源引用'], + 'viewer.passport.sourceCount.other': ['{count} verified source references', '{count} 个已验证来源引用'], + 'viewer.passport.sourceMarker': ['SRC', '来源'], + 'viewer.passport.beacon.one': ['{count} verified source; focus this node to inspect', '{count} 个已验证来源;聚焦此节点以检查'], + 'viewer.passport.beacon.other': ['{count} verified sources; focus this node to inspect', '{count} 个已验证来源;聚焦此节点以检查'], + 'viewer.passport.repository.open': ['Open verified repository revision {revision}', '打开已验证的仓库修订版本 {revision}'], + 'viewer.passport.source.open': ['Open verified source {path} at revision {revision}', '打开修订版本 {revision} 中已验证的来源 {path}'], + 'viewer.passport.source.openLink': ['Open ↗', '打开 ↗'], + 'viewer.passport.reach.upstream.one': ['Trace {count} upstream authored node', '追踪 {count} 个上游编写节点'], + 'viewer.passport.reach.upstream.other': ['Trace {count} upstream authored nodes', '追踪 {count} 个上游编写节点'], + 'viewer.passport.reach.downstream.one': ['Trace {count} downstream authored node', '追踪 {count} 个下游编写节点'], + 'viewer.passport.reach.downstream.other': ['Trace {count} downstream authored nodes', '追踪 {count} 个下游编写节点'], + 'viewer.passport.reach.noUpstream': ['No upstream authored nodes', '没有上游编写节点'], + 'viewer.passport.reach.noDownstream': ['No downstream authored nodes', '没有下游编写节点'], + 'viewer.passport.reach.status': ['{direction} · {nodes} nodes · {links} links · max {hops} hops', '{direction} · {nodes} 个节点 · {links} 条连接 · 最深 {hops} 跳'], + + 'viewer.route.eyebrow': ['Route probe', '路径探测'], + 'viewer.route.start': ['Choose a start node', '选择起点节点'], + 'viewer.route.start.find': ['Find start', '查找起点'], + 'viewer.route.start.find.aria': ['Find a route start', '查找路径起点'], + 'viewer.route.copy': ['Copy link', '复制链接'], + 'viewer.route.copy.aria': ['Copy link to traced route', '复制已追踪路径的链接'], + 'viewer.route.clear': ['Clear', '清除'], + 'viewer.route.clear.aria': ['Clear route probe', '清除路径探测'], + 'viewer.route.traced': ['Traced route', '已追踪路径'], + 'viewer.route.pickTwo': ['Pick two semantic nodes on the diagram', '在图表中选择两个语义节点'], + 'viewer.route.pickOne': ['Pick a semantic node on the diagram', '在图表中选择一个语义节点'], + 'viewer.route.controls': ['Route journey controls', '路径旅程控制'], + 'viewer.route.previous': ['Previous route position', '上一个路径位置'], + 'viewer.route.play': ['Play route journey', '播放路径旅程'], + 'viewer.route.pause': ['Pause route journey', '暂停路径旅程'], + 'viewer.route.replay': ['Replay route journey', '重播路径旅程'], + 'viewer.route.next': ['Next route position', '下一个路径位置'], + 'viewer.route.journey': ['Journey', '旅程'], + 'viewer.route.pause.label': ['Pause', '暂停'], + 'viewer.route.replay.label': ['Replay', '重播'], + 'viewer.route.overview': ['Overview', '总览'], + 'viewer.route.overview.aria': ['Show complete route overview', '显示完整路径总览'], + 'viewer.route.instructions': ['Choose the source, then the destination. Direction matters.', '先选择来源,再选择目标;方向很重要。'], + 'viewer.route.destination': ['Choose a destination from {label}', '选择从{label}出发的目标'], + 'viewer.route.destination.find': ['Find target', '查找目标'], + 'viewer.route.destination.find.aria': ['Find a reachable route destination', '查找可达的路径目标'], + 'viewer.route.differentDestination': ['Choose a different destination', '选择其他目标'], + 'viewer.route.distinct': ['A route needs two distinct semantic nodes.', '一条路径需要两个不同的语义节点。'], + 'viewer.route.unreachable': ['No directed route to {label}', '没有通往{label}的有向路径'], + 'viewer.route.unreachable.detail': ['{target} is not reachable from {source}. Pick a highlighted destination.', '从{source}无法到达{target}。请选择高亮的目标。'], + 'viewer.route.start.instructions': ['Select the source. The next step will reveal only directed destinations.', '选择来源。下一步只会显示有向可达的目标。'], + 'viewer.route.copy.success': ['Traced route link copied', '已复制路径链接'], + 'viewer.route.copy.failed': ['Could not copy traced route link', '无法复制路径链接'], + 'viewer.route.position': ['Route position {index} of {total}: {label}', '路径位置 {index}/{total}:{label}'], + 'viewer.route.step': ['Step {index} of {total} · {phase} · {label}', '第 {index}/{total} 步 · {phase} · {label}'], + 'viewer.route.motionRequired': ['Automatic journey requires Live motion', '自动旅程需要动态模式'], + 'viewer.route.trigger.clear': ['Clear traced route', '清除已追踪路径'], + 'viewer.route.overview.status': ['{nodes} · {hops} · shortest authored route', '{nodes} · {hops} · 最短编写路径'], + 'viewer.route.overview.node.one': ['{count} node', '{count} 个节点'], + 'viewer.route.overview.node.other': ['{count} nodes', '{count} 个节点'], + 'viewer.route.overview.hop.one': ['{count} directed hop', '{count} 个有向跳转'], + 'viewer.route.overview.hop.other': ['{count} directed hops', '{count} 个有向跳转'], + 'viewer.route.phase.playing': ['Playing', '播放中'], + 'viewer.route.phase.complete': ['Complete', '已完成'], + 'viewer.route.phase.inspecting': ['Inspecting', '检查中'], + 'viewer.route.destination.count.one': ['{count} directed destination available. Pick a highlighted node.', '有 {count} 个有向目标可用。请选择高亮节点。'], + 'viewer.route.destination.count.other': ['{count} directed destinations available. Pick a highlighted node.', '有 {count} 个有向目标可用。请选择高亮节点。'], + 'viewer.route.noOutgoing': ['No outgoing route starts here. Clear and choose another source.', '此处没有可用的出向路径。请清除后选择其他来源。'], + 'viewer.route.result.title': ['{source} to {target}', '{source} 到 {target}'], + 'viewer.route.finder.source.title': ['Choose route start', '选择路径起点'], + 'viewer.route.finder.source.placeholder': ['Search route sources', '搜索路径来源'], + 'viewer.route.finder.source.empty': ['No matching route sources', '没有匹配的路径来源'], + 'viewer.route.finder.source.results': ['Nodes that can start a route', '可作为路径起点的节点'], + 'viewer.route.finder.source.noun': ['route sources', '个路径来源'], + 'viewer.route.finder.source.badge': ['start', '起点'], + 'viewer.route.finder.target.title': ['Destination from {label}', '从{label}出发的目标'], + 'viewer.route.finder.target.placeholder': ['Search reachable destinations', '搜索可达目标'], + 'viewer.route.finder.target.empty': ['No matching reachable destinations', '没有匹配的可达目标'], + 'viewer.route.finder.target.results': ['Reachable route destinations', '可达路径目标'], + 'viewer.route.finder.target.noun': ['reachable destinations', '个可达目标'], + 'viewer.route.hop.one': ['{count} hop', '{count} 跳'], + 'viewer.route.hop.other': ['{count} hops', '{count} 跳'], + + 'viewer.lens.eyebrow': ['Semantic lens', '语义透镜'], + 'viewer.lens.title': ['Compare system roles', '比较系统角色'], + 'viewer.lens.close': ['Close semantic lens', '关闭语义透镜'], + 'viewer.lens.instruction': ['Choose up to two semantic kinds. One reveals its real traffic; two compare only direct authored relationships.', '最多选择两种语义类型。选择一种可显示其真实流量;选择两种只比较直接编写的关系。'], + 'viewer.lens.kinds': ['Semantic kinds', '语义类型'], + 'viewer.lens.choose': ['Choose a kind to inspect its nodes and touching relationships.', '选择一种类型以检查其节点和相连关系。'], + 'viewer.lens.copy': ['Copy link to semantic lens', '复制语义透镜链接'], + 'viewer.lens.clear': ['Clear semantic lens', '清除语义透镜'], + 'viewer.lens.open': ['Open semantic lens', '打开语义透镜'], + 'viewer.lens.openActive': ['Open active semantic lens', '打开当前语义透镜'], + 'viewer.lens.legend': ['Semantic legend', '语义图例'], + 'viewer.lens.legend.inspect.one': ['Inspect {label}, {count} node', '检查{label},{count} 个节点'], + 'viewer.lens.legend.inspect.other': ['Inspect {label}, {count} nodes', '检查{label},{count} 个节点'], + 'viewer.lens.kind.count.one': ['{label}, {count} node', '{label},{count} 个节点'], + 'viewer.lens.kind.count.other': ['{label}, {count} nodes', '{label},{count} 个节点'], + 'viewer.lens.compare.one': ['{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationship', '{first} → {second}:{forward} · {second} → {first}:{reverse} · 共 {count} 条直接关系'], + 'viewer.lens.compare.other': ['{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationships', '{first} → {second}:{forward} · {second} → {first}:{reverse} · 共 {count} 条直接关系'], + 'viewer.lens.single': ['{nodes} · {relationships} · connected peers remain visible', '{nodes} · {relationships} · 已连接节点保持可见'], + 'viewer.lens.node.one': ['{count} {label} node', '{count} 个{label}节点'], + 'viewer.lens.node.other': ['{count} {label} nodes', '{count} 个{label}节点'], + 'viewer.lens.relationship.one': ['{count} touching relationship', '{count} 条相连关系'], + 'viewer.lens.relationship.other': ['{count} touching relationships', '{count} 条相连关系'], + + 'viewer.radar.title': ['Semantic radar', '语义雷达'], + 'viewer.radar.building': ['Building overview', '正在构建总览'], + 'viewer.radar.openFull': ['Open full semantic radar', '打开完整语义雷达'], + 'viewer.radar.open': ['Open radar', '打开雷达'], + 'viewer.radar.close': ['Close semantic radar', '关闭语义雷达'], + 'viewer.radar.surface': ['Diagram overview. Click a node to focus it, or use arrow keys to pan.', '图表总览。点击节点进行聚焦,或使用方向键平移。'], + 'viewer.radar.click': ['Click node', '点击节点'], + 'viewer.radar.drag': ['Drag to pan', '拖动平移'], + 'viewer.radar.space': ['Semantic radar needs more MAP space.', '语义雷达需要更多地图可见空间。'], + 'viewer.radar.nodes': ['Semantic diagram radar nodes', '语义图表雷达节点'], + 'viewer.radar.focus': ['Focus {label} from Semantic Radar', '从语义雷达聚焦{label}'], + 'viewer.radar.status': ['{count} nodes · {viewport}', '{count} 个节点 · {viewport}'], + 'viewer.radar.fullMap': ['{count} nodes · full map', '{count} 个节点 · 完整地图'], + 'viewer.radar.compacted': ['Radar compacted to avoid covering the Semantic Passport or MAP controls.', '已收紧雷达,避免遮挡语义护照或地图控件。'], + 'viewer.radar.cancelWaiting': ['Cancel semantic radar waiting for more MAP space', '取消等待更多地图空间的语义雷达'], + 'viewer.radar.needsSpace': ['Semantic radar needs more visible MAP space', '语义雷达需要更多可见地图空间'], + 'viewer.radar.viewport.full': ['full map', '完整地图'], + 'viewer.radar.viewport.width': ['{percent}% width', '宽度 {percent}%'], + 'viewer.radar.viewport.scale': ['{percent}% viewport', '视口 {percent}%'], + + 'viewer.nav.controls': ['Diagram view controls', '图表视图控制'], + 'viewer.nav.route': ['Trace a directed route', '追踪有向路径'], + 'viewer.nav.route.title': ['Trace route (R)', '追踪路径(R)'], + 'viewer.nav.route.short': ['PATH', '路径'], + 'viewer.nav.radar': ['Open semantic radar', '打开语义雷达'], + 'viewer.nav.radar.title': ['Semantic radar (M)', '语义雷达(M)'], + 'viewer.nav.radar.short': ['MAP', '地图'], + 'viewer.nav.lens': ['Open semantic lens', '打开语义透镜'], + 'viewer.nav.lens.title': ['Semantic lens (L)', '语义透镜(L)'], + 'viewer.nav.lens.short': ['LENS', '透镜'], + 'viewer.nav.find': ['Find a node', '查找节点'], + 'viewer.nav.find.title': ['Find a node (/)', '查找节点(/)'], + 'viewer.nav.guide': ['Open diagram guide', '打开图表指南'], + 'viewer.nav.guide.title': ['Diagram guide (?)', '图表指南(?)'], + 'viewer.nav.zoomOut': ['Zoom out', '缩小'], + 'viewer.nav.zoomOut.title': ['Zoom out (-)', '缩小(-)'], + 'viewer.nav.reset': ['Reset diagram view', '重置图表视图'], + 'viewer.nav.reset.title': ['Reset view (0)', '重置视图(0)'], + 'viewer.nav.read': ['READ', '阅读'], + 'viewer.nav.zoomIn': ['Zoom in', '放大'], + 'viewer.nav.zoomIn.title': ['Zoom in (+)', '放大(+)'], + 'viewer.nav.camera': ['{hint}. Reset diagram view', '{hint}。重置图表视图'], + 'viewer.nav.camera.title': ['{semantic}{hint} · reset view (0)', '{semantic}{hint} · 重置视图(0)'], + 'viewer.nav.camera.semantic': ['Semantic camera active · ', '语义相机已启用 · '], + 'viewer.nav.level.map': ['MAP', '概览'], + 'viewer.nav.level.read': ['READ', '阅读'], + 'viewer.nav.level.full': ['FULL', '完整'], + 'viewer.nav.level.auto': ['AUTO', '自动'], + 'viewer.nav.detail.map': ['Zoom in to reveal relationship labels and node context', '放大以显示关系标签和节点上下文'], + 'viewer.nav.detail.read': ['Zoom in again to reveal tags and annotations', '再次放大以显示标签和注释'], + 'viewer.nav.detail.full': ['Full diagram detail', '完整图表详情'], + + 'viewer.intent.summary': ['{label}. {out} outgoing, {in} incoming{loops}. {total} connections. Press Enter for details.', '{label}。{out} 条出向,{in} 条入向{loops}。共 {total} 条连接。按 Enter 查看详情。'], + 'viewer.intent.loops': [', {count} self loop', ',{count} 条自环'], + + 'viewer.common.copied': ['Copied', '已复制'], + 'viewer.common.copyFailed': ['Copy failed', '复制失败'], + 'viewer.common.copyLink': ['Copy link', '复制链接'], + 'viewer.common.clear': ['Clear', '清除'], + 'viewer.common.close': ['Close', '关闭'], +}; + +for (const [key, messages] of Object.entries(MESSAGE_PAIRS)) { + if (messages.length !== SUPPORTED_LOCALES.length || messages.some((message) => typeof message !== 'string')) { + throw new Error(`Incomplete Archify i18n tuple ${JSON.stringify(key)}`); + } +} + +const CATALOGS = Object.fromEntries(SUPPORTED_LOCALES.map((locale, index) => [ + locale, + Object.fromEntries(Object.entries(MESSAGE_PAIRS).map(([key, pair]) => [key, pair[index]])), +])); + +export function resolveLocale(locale) { + return SUPPORTED_LOCALES.includes(locale) ? locale : DEFAULT_LOCALE; +} + +export function formatMessage(template, values = {}) { + return String(template).replace(/\{([a-zA-Z0-9_]+)\}/g, (match, key) => ( + Object.hasOwn(values, key) ? String(values[key]) : match + )); +} + +export function translateMessage(locale, key, values = {}) { + const resolved = resolveLocale(locale); + if (!Object.hasOwn(CATALOGS[resolved], key)) { + throw new Error(`Missing Archify i18n message ${JSON.stringify(key)} for ${resolved}`); + } + return formatMessage(CATALOGS[resolved][key], values); +} + +export function translateCount(locale, key, count, values = {}) { + const suffix = count === 1 ? 'one' : 'other'; + return translateMessage(locale, `${key}.${suffix}`, { ...values, count }); +} + +export function viewerCatalog(locale) { + const resolved = resolveLocale(locale); + return Object.fromEntries(Object.entries(CATALOGS[resolved]).filter(([key]) => key.startsWith('viewer.'))); +} + +export function localizeTemplate(template, locale) { + return template.replace(/\{\{i18n:([a-zA-Z0-9_.-]+)\}\}/g, (_match, key) => escapeHtml(translateMessage(locale, key))); +} + +export function catalogKeys() { + return Object.keys(MESSAGE_PAIRS); +} diff --git a/tools/archify/renderers/shared/layout-report.mjs b/tools/archify/renderers/shared/layout-report.mjs new file mode 100644 index 0000000..3128a65 --- /dev/null +++ b/tools/archify/renderers/shared/layout-report.mjs @@ -0,0 +1,40 @@ +/** Serialize computed layout for dry-run / inspect (#9). */ + +export function componentBox(c) { + return { + id: c.id, + type: c.type, + label: c.label, + x: Math.round(c.x), + y: Math.round(c.y), + width: c.width, + height: c.height, + ...(Number.isInteger(c.row) ? { row: c.row } : {}), + ...(Number.isInteger(c.col) ? { col: c.col } : {}), + ...(Array.isArray(c.pos) ? { pos: c.pos.map(Math.round) } : {}), + }; +} + +export function boundaryBox(b) { + return { + kind: b.kind, + label: b.label, + x: Math.round(b.x), + y: Math.round(b.y), + width: Math.round(b.width), + height: Math.round(b.height), + wraps: b.wraps, + }; +} + +export function connectionPath(conn, routed, labelAt) { + return { + from: conn.from, + to: conn.to, + label: conn.label ?? null, + variant: conn.variant ?? 'default', + route: conn.route ?? 'auto', + points: routed.points.map(([x, y]) => [Math.round(x), Math.round(y)]), + ...(labelAt ? { labelAt: labelAt.map(Math.round) } : {}), + }; +} diff --git a/tools/archify/renderers/shared/legend.mjs b/tools/archify/renderers/shared/legend.mjs new file mode 100644 index 0000000..e9e80d6 --- /dev/null +++ b/tools/archify/renderers/shared/legend.mjs @@ -0,0 +1,217 @@ +import { throwDiagnosticError } from './diagnostics.mjs'; +import { rectsOverlap, segmentIntersectsRect } from './geometry.mjs'; +import { esc, textUnits } from './utils.mjs'; +import { translateMessage } from './i18n.mjs'; + +const DEFAULT_FONT_SIZE = 8; +const DEFAULT_ITEM_GAP = 22; +const DEFAULT_LINE_GAP = 22; +const DEFAULT_SWATCH_GAP = 8; +const TEXT_ADVANCE_EM = 0.62; +const INTERACTIVE_BADGE_ALLOWANCE = 21; + +export function relationshipLegendObstacles(relations, { pointsFor, labelRectFor } = {}) { + const obstacles = []; + for (const [index, relation] of (Array.isArray(relations) ? relations : []).entries()) { + const points = typeof pointsFor === 'function' ? pointsFor(relation, index) : []; + const finitePoints = (Array.isArray(points) ? points : []).filter((point) => ( + Array.isArray(point) && point.length === 2 && point.every(Number.isFinite) + )); + for (let pointIndex = 0; pointIndex < finitePoints.length - 1; pointIndex += 1) { + obstacles.push({ + kind: 'relationship-segment', + start: finitePoints[pointIndex], + end: finitePoints[pointIndex + 1], + }); + } + const labelRect = typeof labelRectFor === 'function' ? labelRectFor(relation, index) : null; + if (labelRect && [labelRect.x, labelRect.y, labelRect.width, labelRect.height].every(Number.isFinite)) { + obstacles.push({ kind: 'relationship-label', ...labelRect }); + } + } + return obstacles; +} + +export function resolveLegend(config, catalog, presentKinds) { + const mode = config?.mode || 'auto'; + if (mode === 'hidden') return []; + const present = presentKinds instanceof Set ? presentKinds : new Set(presentKinds || []); + const overrides = config?.entries || {}; + + return catalog.flatMap((catalogEntry) => { + const override = overrides[catalogEntry.kind] || {}; + const selectedByMode = mode === 'all' || present.has(catalogEntry.kind); + const visible = override.visible === true || (selectedByMode && override.visible !== false); + if (!visible) return []; + return [{ + ...catalogEntry, + label: override.label || catalogEntry.label, + present: present.has(catalogEntry.kind), + interactive: catalogEntry.interactive !== false && present.has(catalogEntry.kind), + }]; + }); +} + +function measuredEntryWidth(entry, fontSize, swatchGap) { + const swatchWidth = entry.swatchWidth ?? 14; + return Math.ceil( + swatchWidth + + swatchGap + + textUnits(entry.label) * fontSize * TEXT_ADVANCE_EM + + (entry.interactive ? INTERACTIVE_BADGE_ALLOWANCE : 0), + ); +} + +// One pure footprint calculation owns both auto-viewBox sizing and final SVG +// placement. Callers must not maintain a second approximation of legend width +// or row count; that would make generated geometry disagree with validation. +export function legendFootprint(entries, { + width, + fontSize = DEFAULT_FONT_SIZE, + itemGap = DEFAULT_ITEM_GAP, + lineGap = DEFAULT_LINE_GAP, + swatchGap = DEFAULT_SWATCH_GAP, +} = {}) { + if (!entries.length) { + return { measured: [], rows: [], rowCount: 0, minWidth: 0, extraHeight: 0 }; + } + const measured = entries.map((entry) => ({ + ...entry, + width: measuredEntryWidth(entry, fontSize, entry.swatchGap ?? swatchGap), + })); + const rows = [[]]; + let cursor = 0; + for (const entry of measured) { + const row = rows.at(-1); + const required = (row.length ? itemGap : 0) + entry.width; + if (row.length && cursor + required > width) { + rows.push([entry]); + cursor = entry.width; + } else { + row.push(entry); + cursor += required; + } + } + return { + measured, + rows, + rowCount: rows.length, + minWidth: Math.max(...measured.map((entry) => entry.width)), + extraHeight: (rows.length - 1) * lineGap, + }; +} + +export function measureLegend(entries, { + x, + baselineY, + width, + fontSize = DEFAULT_FONT_SIZE, + itemGap = DEFAULT_ITEM_GAP, + lineGap = DEFAULT_LINE_GAP, + swatchGap = DEFAULT_SWATCH_GAP, + minTitleY = 0, + obstacles = [], + unfit = 'error', + diagramType = 'diagram', +} = {}) { + if (!entries.length) return { entries: [], rowCount: 0, titleY: null }; + const footprint = legendFootprint(entries, { width, fontSize, itemGap, lineGap, swatchGap }); + const tooWide = footprint.measured.find((entry) => entry.width > width); + if (tooWide) { + if (unfit === 'hide') return null; + const message = `[legend/label-too-wide] ${diagramType} legend label for "${tooWide.kind}" needs ${tooWide.width}px but only ${width}px is available.`; + throwDiagnosticError(message, [{ + code: 'legend/label-too-wide', + severity: 'error', + message, + subject: { diagramType, path: `/meta/legend/entries/${tooWide.kind}/label` }, + evidence: { kind: tooWide.kind, measuredWidthPx: tooWide.width, availableWidthPx: width }, + supportedFixes: ['shorten the legend label or use a wider viewBox'], + }]); + } + + const titleY = baselineY - footprint.extraHeight - 20; + const legendTopY = titleY - 10; + if (legendTopY < minTitleY) { + if (unfit === 'hide') return null; + const message = `[legend/vertical-overflow] ${diagramType} legend needs ${footprint.rowCount} rows, which would start at y=${legendTopY} above the available legend band at y=${minTitleY}.`; + throwDiagnosticError(message, [{ + code: 'legend/vertical-overflow', + severity: 'error', + message, + subject: { diagramType, path: '/meta/legend' }, + evidence: { rowCount: footprint.rowCount, requiredTopY: legendTopY, availableTopY: minTitleY }, + supportedFixes: ['shorten legend labels, hide nonessential entries, or use a wider viewBox'], + }]); + } + + const positioned = []; + footprint.rows.forEach((row, rowIndex) => { + let entryX = x; + const baseline = baselineY - (footprint.rowCount - rowIndex - 1) * lineGap; + for (const entry of row) { + positioned.push({ ...entry, x: entryX, baseline, row: rowIndex }); + entryX += entry.width + itemGap; + } + }); + + const legendRects = [ + { kind: 'title', x, y: legendTopY, width: 48, height: 14 }, + ...positioned.map((entry) => ({ + kind: entry.kind, + x: entry.x, + y: entry.baseline - 10, + width: entry.width, + height: 14, + })), + ]; + const collision = legendRects.find((legendRect) => obstacles.some((obstacle) => ( + Array.isArray(obstacle.start) && Array.isArray(obstacle.end) + ? segmentIntersectsRect({ start: obstacle.start, end: obstacle.end }, legendRect) + : rectsOverlap(obstacle, legendRect) + ))); + if (collision) { + if (unfit === 'hide') return null; + const message = `[legend/content-overlap] ${diagramType} legend entry "${collision.kind}" overlaps authored relationship geometry.`; + throwDiagnosticError(message, [{ + code: 'legend/content-overlap', + severity: 'error', + message, + subject: { diagramType, path: '/meta/legend' }, + evidence: { legendKind: collision.kind, legendRect: collision }, + supportedFixes: ['shorten or hide legend entries, use a wider viewBox, or move the authored relationship route/label out of the legend band'], + }]); + } + + return { + entries: positioned, + rowCount: footprint.rowCount, + titleY, + fontSize, + }; +} + +export function renderLegend({ entries, layout, renderSwatch, locale }) { + if (!entries.length) return ''; + const measured = measureLegend(entries, layout); + if (!measured) return ''; + const hasInteractiveEntries = measured.entries.some((entry) => entry.interactive); + const renderedFontSize = measured.fontSize < 8 ? measured.fontSize + 0.5 : measured.fontSize + 2; + const rootAttributes = hasInteractiveEntries ? ' data-legend="" data-legend-bridge=""' : ' data-legend=""'; + const parts = [ + ` `, + ` ${esc(translateMessage(locale, 'legend.title'))}`, + ]; + + for (const entry of measured.entries) { + const interactive = entry.interactive + ? ` data-legend-kind="${esc(entry.kind)}" data-legend-label="${esc(entry.label)}"` + : ''; + parts.push(` `); + parts.push(` ${renderSwatch(entry)}`); + parts.push(` ${esc(entry.label)}`); + parts.push(' '); + } + parts.push(' '); + return parts.join('\n'); +} diff --git a/tools/archify/renderers/shared/output-path.mjs b/tools/archify/renderers/shared/output-path.mjs new file mode 100644 index 0000000..ca91196 --- /dev/null +++ b/tools/archify/renderers/shared/output-path.mjs @@ -0,0 +1,321 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const MAX_SYMLINK_DEPTH = 64; +const directorySemanticsCache = new Map(); +let semanticsProbeSequence = 0; + +function splitAbsolute(absolutePath) { + const root = path.parse(absolutePath).root; + return { + root, + segments: absolutePath.slice(root.length).split(path.sep).filter(Boolean), + }; +} + +function canonicalize(targetPath, depth) { + const absolutePath = path.resolve(targetPath); + const { root, segments } = splitAbsolute(absolutePath); + let current = root; + + for (let index = 0; index < segments.length; index += 1) { + const candidate = path.join(current, segments[index]); + let stat; + try { + stat = fs.lstatSync(candidate); + } catch (error) { + if (error.code === 'ENOENT' || error.code === 'ENOTDIR') { + return path.resolve(current, ...segments.slice(index)); + } + throw error; + } + + if (stat.isSymbolicLink()) { + if (depth >= MAX_SYMLINK_DEPTH) { + const error = new Error(`Could not resolve path because a symbolic-link cycle includes "${candidate}".`); + error.code = 'ELOOP'; + error.path = candidate; + throw error; + } + const link = fs.readlinkSync(candidate); + const linkTarget = path.isAbsolute(link) ? link : path.resolve(path.dirname(candidate), link); + return canonicalize(path.join(linkTarget, ...segments.slice(index + 1)), depth + 1); + } + + current = fs.realpathSync.native(candidate); + } + + return path.normalize(current); +} + +export function canonicalFuturePath(targetPath) { + try { + return canonicalize(targetPath, 0); + } catch (error) { + if (error?.code !== 'ELOOP') throw error; + const output = path.resolve(targetPath); + throw new OutputPathError(`Output path contains a symbolic-link cycle: "${output}".`, { + code: 'output/symlink-cycle', + message: 'Output path could not be resolved because it contains a symbolic-link cycle.', + subject: { output }, + evidence: { + systemCode: 'ELOOP', + ...(error.path ? { cycleAt: path.resolve(error.path) } : {}), + }, + supportedFixes: ['remove the symbolic-link cycle or choose an output path outside it'], + }); + } +} + +function hasFileIdentity(stat) { + return stat.ino !== 0 && stat.ino !== 0n; +} + +function sameFileIdentity(left, right) { + return hasFileIdentity(left) + && hasFileIdentity(right) + && left.dev === right.dev + && left.ino === right.ino; +} + +function nearestExistingDirectory(targetPath) { + let directory = path.dirname(targetPath); + while (true) { + try { + const stat = fs.statSync(directory); + if (stat.isDirectory()) { + return { + path: fs.realpathSync.native(directory), + stat, + }; + } + } catch (error) { + if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') return null; + } + const parent = path.dirname(directory); + if (parent === directory) return null; + directory = parent; + } +} + +function directoryIdentityKey(directory) { + if (!hasFileIdentity(directory.stat)) return null; + return `${directory.stat.dev}:${directory.stat.ino}`; +} + +function probeNamesAlias(directoryPath, authoredName, lookupName) { + let fileDescriptor; + let created = false; + let result = null; + let cleaned = true; + const authoredPath = path.join(directoryPath, authoredName); + const lookupPath = path.join(directoryPath, lookupName); + try { + fileDescriptor = fs.openSync(authoredPath, 'wx', 0o600); + created = true; + fs.closeSync(fileDescriptor); + fileDescriptor = undefined; + + let authored; + let lookup; + try { + authored = fs.statSync(authoredPath); + lookup = fs.statSync(lookupPath); + } catch (error) { + if (error.code === 'ENOENT') result = false; + } + if (authored && lookup) { + if (sameFileIdentity(authored, lookup)) { + result = true; + } else { + try { + result = fs.realpathSync.native(authoredPath) === fs.realpathSync.native(lookupPath); + } catch { + result = null; + } + } + } + } catch { + result = null; + } finally { + if (fileDescriptor !== undefined) { + try { + fs.closeSync(fileDescriptor); + } catch { + cleaned = false; + } + } + if (created) { + try { + fs.unlinkSync(authoredPath); + } catch { + cleaned = false; + } + } + } + return cleaned ? result : null; +} + +function probeDirectorySemantics(directory) { + const cacheKey = directoryIdentityKey(directory); + if (cacheKey && directorySemanticsCache.has(cacheKey)) { + return directorySemanticsCache.get(cacheKey); + } + + semanticsProbeSequence += 1; + const suffix = `${process.pid}-${Date.now().toString(36)}-${semanticsProbeSequence}`; + const caseAuthored = `.archify-Case-Probe-${suffix}`; + const normalizationAuthored = `.archify-norm-\u00e9-probe-${suffix}`; + const semantics = { + caseInsensitive: probeNamesAlias( + directory.path, + caseAuthored, + caseAuthored.toLowerCase(), + ), + normalizationInsensitive: probeNamesAlias( + directory.path, + normalizationAuthored, + normalizationAuthored.normalize('NFD'), + ), + }; + if ( + cacheKey + && semantics.caseInsensitive !== null + && semantics.normalizationInsensitive !== null + ) { + directorySemanticsCache.set(cacheKey, semantics); + } + return semantics; +} + +function sameDirectory(left, right) { + return left.path === right.path || sameFileIdentity(left.stat, right.stat); +} + +function futurePathsAlias(leftPath, rightPath) { + const left = canonicalFuturePath(leftPath); + const right = canonicalFuturePath(rightPath); + if (left === right) return true; + + const leftDirectory = nearestExistingDirectory(left); + const rightDirectory = nearestExistingDirectory(right); + if (!leftDirectory || !rightDirectory || !sameDirectory(leftDirectory, rightDirectory)) { + return false; + } + + const semantics = probeDirectorySemantics(leftDirectory); + let comparableLeft = path.relative(leftDirectory.path, left); + let comparableRight = path.relative(rightDirectory.path, right); + if (semantics.normalizationInsensitive !== false) { + comparableLeft = comparableLeft.normalize('NFC'); + comparableRight = comparableRight.normalize('NFC'); + } + if (semantics.caseInsensitive !== false) { + comparableLeft = comparableLeft.toLowerCase(); + comparableRight = comparableRight.toLowerCase(); + } + return comparableLeft === comparableRight; +} + +export function pathsAlias(leftPath, rightPath) { + if (futurePathsAlias(leftPath, rightPath)) return true; + try { + const left = fs.statSync(leftPath); + const right = fs.statSync(rightPath); + return sameFileIdentity(left, right); + } catch { + return false; + } +} + +function pathIsInside(directoryPath, targetPath) { + const relative = path.relative(canonicalFuturePath(directoryPath), canonicalFuturePath(targetPath)); + return relative === '' || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`)); +} + +export class OutputPathError extends Error { + constructor(message, diagnostic) { + super(message); + this.name = 'OutputPathError'; + this.archifyDiagnostics = [{ + severity: 'error', + subject: {}, + evidence: {}, + supportedFixes: [], + ...diagnostic, + }]; + } +} + +export function resolveOutputPath({ + requestedOutput, + authoredOutput, + defaultOutput, + inputPaths = [], + inputDescription = 'an input', + otherOutputPaths = [], + cwd = process.cwd(), +}) { + const rawOutput = requestedOutput || authoredOutput || defaultOutput; + const source = requestedOutput ? 'cli' : (authoredOutput ? 'meta' : 'default'); + if ( + source === 'meta' + && (path.isAbsolute(rawOutput) || path.posix.isAbsolute(rawOutput) || path.win32.isAbsolute(rawOutput)) + ) { + throw new OutputPathError('meta.output must be a relative path.', { + code: 'output/meta-absolute', + message: 'meta.output must be a relative path resolved from the current working directory.', + subject: { output: rawOutput }, + supportedFixes: ['set meta.output to a relative .html path inside the current working directory'], + }); + } + if (source === 'meta' && path.extname(rawOutput).toLowerCase() !== '.html') { + throw new OutputPathError('meta.output must target an .html file.', { + code: 'output/meta-extension', + message: 'meta.output must target an .html file.', + subject: { output: rawOutput }, + supportedFixes: ['change meta.output to a path ending in .html'], + }); + } + const outputPath = path.resolve(cwd, rawOutput); + if (source === 'meta' && path.extname(canonicalFuturePath(outputPath)).toLowerCase() !== '.html') { + throw new OutputPathError('meta.output must resolve to an .html file.', { + code: 'output/meta-resolved-extension', + message: 'meta.output must resolve to an .html file after symbolic links are followed.', + subject: { output: rawOutput }, + supportedFixes: ['remove the symbolic-link alias or point it to an .html target inside the current working directory'], + }); + } + if (source === 'meta' && !pathIsInside(cwd, outputPath)) { + throw new OutputPathError('meta.output must stay inside the current working directory.', { + code: 'output/meta-outside-cwd', + message: 'meta.output must stay inside the current working directory after symbolic links are resolved.', + subject: { output: rawOutput, cwd: path.resolve(cwd) }, + supportedFixes: ['set meta.output to a relative .html path inside the current working directory'], + }); + } + + for (const inputPath of inputPaths) { + if (!pathsAlias(outputPath, inputPath)) continue; + throw new OutputPathError(`Output must not replace ${inputDescription}.`, { + code: 'output/input-alias', + message: `Output must not replace ${inputDescription}, including through a symbolic-link or future-path alias.`, + subject: { output: outputPath, input: path.resolve(inputPath) }, + supportedFixes: ['choose an output path that is distinct from every input path'], + }); + } + for (const otherOutputPath of otherOutputPaths) { + if (!pathsAlias(outputPath, otherOutputPath)) continue; + throw new OutputPathError('Output targets must use distinct paths.', { + code: 'output/target-alias', + message: 'Output targets must use distinct paths, including symbolic-link and future-path aliases.', + subject: { output: outputPath, conflictingOutput: path.resolve(otherOutputPath) }, + supportedFixes: ['choose distinct paths for every generated output'], + }); + } + + return { + outputPath, + source, + }; +} diff --git a/tools/archify/renderers/shared/repository-evidence.mjs b/tools/archify/renderers/shared/repository-evidence.mjs new file mode 100644 index 0000000..23cb638 --- /dev/null +++ b/tools/archify/renderers/shared/repository-evidence.mjs @@ -0,0 +1,235 @@ +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { throwDiagnosticError } from './diagnostics.mjs'; + +const FULL_SHA_RE = /^[a-f0-9]{40}$/i; +const CONTROL_CHARACTER_RE = /[\u0000-\u001f\u007f]/; + +function evidenceFailure(code, message, { subject = {}, evidence = {}, supportedFixes = [] } = {}) { + throwDiagnosticError(message, [{ + code, + severity: 'error', + message, + subject: { surface: 'repository-evidence', ...subject }, + evidence, + supportedFixes, + }]); +} + +function runGit(repoRoot, args) { + const result = spawnSync('git', ['-C', repoRoot, ...args], { + encoding: 'utf8', + maxBuffer: 16 * 1024 * 1024, + }); + if (result.error) evidenceFailure('repository-evidence/git-unavailable', `Could not run Git: ${result.error.message}`, { + evidence: { reason: result.error.message }, + supportedFixes: ['install Git and ensure it is available on PATH'], + }); + return result; +} + +function gitValue(repoRoot, args, failure) { + const result = runGit(repoRoot, args); + if (result.status !== 0) evidenceFailure('repository-evidence/git-command', failure, { + evidence: { gitArgs: args, exitCode: result.status }, + supportedFixes: ['use the intended local Git repository and verify its origin and revision'], + }); + return result.stdout.trim(); +} + +function githubSlug(value) { + const raw = String(value || '').trim(); + const match = raw.match(/^(?:https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i); + return match ? `${match[1]}/${match[2]}`.toLowerCase() : null; +} + +function verifiedSourcePath(value, where) { + const sourcePath = String(value || ''); + if (!sourcePath || sourcePath.startsWith('/') || sourcePath.includes('\\') || CONTROL_CHARACTER_RE.test(sourcePath)) { + evidenceFailure('repository-evidence/path-invalid', `${where} must be a repo-relative POSIX path.`, { + subject: { path: where }, + evidence: { authoredPath: sourcePath }, + supportedFixes: ['use a repository-relative path with forward slashes'], + }); + } + const segments = sourcePath.split('/'); + if (segments.some((segment) => !segment || segment === '.' || segment === '..') || segments[0] === '.git') { + evidenceFailure('repository-evidence/path-escape', `${where} must stay inside the repository and may not address .git.`, { + subject: { path: where }, + evidence: { authoredPath: sourcePath }, + supportedFixes: ['remove empty, dot, parent, or .git path segments'], + }); + } + return segments.join('/'); +} + +function sourceHref(repositoryUrl, revision, source) { + const encodedPath = source.path.split('/').map(encodeURIComponent).join('/'); + const lineFragment = source.line + ? `#L${source.line}${source.endLine && source.endLine !== source.line ? `-L${source.endLine}` : ''}` + : ''; + return `${repositoryUrl}/blob/${revision}/${encodedPath}${lineFragment}`; +} + +function sourceLineCount(content) { + if (!content.length) return 0; + const lines = content.split(/\r\n|\n|\r/); + return lines.length - (/(?:\r\n|\n|\r)$/.test(content) ? 1 : 0); +} + +export function hasRepositoryEvidence(diagramType, diagram) { + if (diagramType !== 'architecture') return false; + const components = Array.isArray(diagram?.components) ? diagram.components : []; + return Boolean(diagram?.meta?.repository) || components.some((component) => Array.isArray(component?.sources) && component.sources.length); +} + +export function verifyRepositoryEvidence(diagramType, diagram, repoRootInput) { + if (!hasRepositoryEvidence(diagramType, diagram)) return null; + if (diagramType !== 'architecture') evidenceFailure('repository-evidence/type-unsupported', 'Repository evidence is currently supported for architecture diagrams only.', { + subject: { diagramType }, + supportedFixes: ['use architecture mode or remove repository evidence'], + }); + + const repository = diagram.meta?.repository; + if (!repository) evidenceFailure('repository-evidence/repository-required', 'Repository evidence requires /meta/repository.', { + subject: { path: '/meta/repository' }, + supportedFixes: ['add the pinned public repository metadata or remove component sources'], + }); + if (!FULL_SHA_RE.test(repository.revision || '')) { + evidenceFailure('repository-evidence/revision-invalid', '/meta/repository/revision must be a full 40-character commit SHA.', { + subject: { path: '/meta/repository/revision' }, + evidence: { revision: repository.revision }, + supportedFixes: ['pin one full 40-character commit SHA'], + }); + } + const authoredSlug = githubSlug(repository.url); + if (!authoredSlug || !String(repository.url).startsWith('https://github.com/')) { + evidenceFailure('repository-evidence/url-invalid', '/meta/repository/url must be a public https://github.com owner/repository URL.', { + subject: { path: '/meta/repository/url' }, + evidence: { repositoryUrl: repository.url }, + supportedFixes: ['use the canonical public GitHub HTTPS repository URL'], + }); + } + if (!repoRootInput) { + evidenceFailure('repository-evidence/root-required', 'This diagram declares source evidence. Pass --repo-root so Archify can verify it before rendering.', { + subject: { path: '/meta/repository' }, + supportedFixes: ['pass --repo-root with the matching local Git checkout'], + }); + } + + const requestedRoot = path.resolve(repoRootInput); + let realRoot; + try { + realRoot = fs.realpathSync(requestedRoot); + } catch (error) { + evidenceFailure('repository-evidence/root-unreadable', `Could not resolve evidence repository root "${requestedRoot}": ${error.message}`, { + subject: { repoRoot: requestedRoot }, + evidence: { reason: error.message }, + supportedFixes: ['pass one readable local repository directory'], + }); + } + const gitRoot = gitValue(realRoot, ['rev-parse', '--show-toplevel'], `Evidence root "${realRoot}" is not a Git repository.`); + if (fs.realpathSync(gitRoot) !== realRoot) { + evidenceFailure('repository-evidence/root-not-top-level', `Evidence root must be the Git top-level directory: ${gitRoot}`, { + subject: { repoRoot: realRoot }, + evidence: { gitTopLevel: gitRoot }, + supportedFixes: [`pass --repo-root ${gitRoot}`], + }); + } + const origin = gitValue(realRoot, ['remote', 'get-url', 'origin'], 'Evidence repository must have an origin remote.'); + if (githubSlug(origin) !== authoredSlug) { + evidenceFailure('repository-evidence/origin-mismatch', `Evidence repository origin ${JSON.stringify(origin)} does not match ${JSON.stringify(repository.url)}.`, { + subject: { repoRoot: realRoot }, + evidence: { localOrigin: origin, authoredRepository: repository.url }, + supportedFixes: ['use the matching local checkout or correct the authored repository URL'], + }); + } + + const revision = repository.revision.toLowerCase(); + const commit = runGit(realRoot, ['cat-file', '-e', `${revision}^{commit}`]); + if (commit.status !== 0) { + evidenceFailure('repository-evidence/revision-unavailable', `Evidence revision ${revision} is not available in the local repository.`, { + subject: { repoRoot: realRoot }, + evidence: { revision }, + supportedFixes: ['fetch the pinned commit or pin an available full commit SHA'], + }); + } + + const nodes = Object.create(null); + let referenceCount = 0; + const components = Array.isArray(diagram.components) ? diagram.components : []; + for (const [componentIndex, component] of components.entries()) { + if (!Array.isArray(component.sources) || component.sources.length === 0) continue; + const verified = []; + for (const [sourceIndex, authored] of component.sources.entries()) { + const where = `/components/${componentIndex}/sources/${sourceIndex}/path`; + const source = { + path: verifiedSourcePath(authored.path, where), + ...(authored.line ? { line: authored.line } : {}), + ...(authored.end_line ? { endLine: authored.end_line } : {}), + ...(authored.label ? { label: authored.label } : {}), + }; + if (source.endLine && !source.line) { + evidenceFailure('repository-evidence/line-required', `/components/${componentIndex}/sources/${sourceIndex}/end_line requires line.`, { + subject: { path: `/components/${componentIndex}/sources/${sourceIndex}/end_line`, componentId: component.id }, + supportedFixes: ['add line or remove end_line'], + }); + } + if (source.endLine && source.endLine < source.line) { + evidenceFailure('repository-evidence/line-range-invalid', `/components/${componentIndex}/sources/${sourceIndex}/end_line must be greater than or equal to line.`, { + subject: { path: `/components/${componentIndex}/sources/${sourceIndex}`, componentId: component.id }, + evidence: { line: source.line, endLine: source.endLine }, + supportedFixes: ['use an end_line greater than or equal to line'], + }); + } + const object = `${revision}:${source.path}`; + const type = runGit(realRoot, ['cat-file', '-t', object]); + if (type.status !== 0 || type.stdout.trim() !== 'blob') { + evidenceFailure('repository-evidence/file-missing', `${where} does not identify a file at revision ${revision}.`, { + subject: { path: where, componentId: component.id }, + evidence: { sourcePath: source.path, revision }, + supportedFixes: ['use a file path that exists at the pinned revision'], + }); + } + if (source.line) { + const content = runGit(realRoot, ['show', object]); + if (content.status !== 0) evidenceFailure('repository-evidence/file-unreadable', `${where} could not be read at revision ${revision}.`, { + subject: { path: where, componentId: component.id }, + evidence: { sourcePath: source.path, revision }, + supportedFixes: ['verify the pinned blob is readable in the local checkout'], + }); + const lineCount = sourceLineCount(content.stdout); + const requestedLine = source.endLine || source.line; + if (requestedLine > lineCount) { + evidenceFailure('repository-evidence/line-out-of-range', `/components/${componentIndex}/sources/${sourceIndex} requests line ${requestedLine}, but ${source.path} has ${lineCount} lines at revision ${revision}.`, { + subject: { path: `/components/${componentIndex}/sources/${sourceIndex}`, componentId: component.id }, + evidence: { sourcePath: source.path, requestedLine, lineCount, revision }, + supportedFixes: ['use a line range that exists at the pinned revision'], + }); + } + } + verified.push({ ...source, href: sourceHref(repository.url.replace(/\.git\/?$/i, '').replace(/\/$/, ''), revision, source) }); + referenceCount += 1; + } + nodes[component.id] = verified; + } + if (referenceCount === 0) { + evidenceFailure('repository-evidence/source-required', '/meta/repository requires at least one component source reference.', { + subject: { path: '/meta/repository' }, + supportedFixes: ['add at least one verified component source or remove repository metadata'], + }); + } + + return { + schemaVersion: 1, + verified: true, + repository: { + url: repository.url.replace(/\.git\/?$/i, '').replace(/\/$/, ''), + revision, + shortRevision: revision.slice(0, 7), + }, + referenceCount, + nodes, + }; +} diff --git a/tools/archify/renderers/shared/text-fit.mjs b/tools/archify/renderers/shared/text-fit.mjs new file mode 100644 index 0000000..31c0b17 --- /dev/null +++ b/tools/archify/renderers/shared/text-fit.mjs @@ -0,0 +1,49 @@ +// Single-line node text fitting, shared by every renderer. +// +// Node text (`label`, `sublabel`, `tag`) renders as one element with +// text-anchor="middle" and is never wrapped. Left unmeasured, an over-long +// value silently spills across its neighbours while validation still reports +// a clean receipt — the failure mode this module exists to close. +// +// Two halves, always used together: +// - fittedNodeFontSize shrinks the text toward a legible minimum at render +// time, so ordinary overruns simply get smaller instead of overlapping. +// - minimumNodeTextWidth reports the width the text still needs once it has +// shrunk as far as it may, so validation can reject what shrinking cannot +// save. +// +// The geometry constants below are shared; the per-field `preferred` and +// `minimum` font sizes are not, because renderers set node text at different +// sizes (architecture sublabels are 9px, the rest are 7px). + +import { textUnits } from './utils.mjs'; + +// widthFactor: px of advance width per text unit, per px of font size. +// horizontalPadding: total px reserved inside the box so text never touches +// the border. +export const nodeTextFit = { + widthFactor: 0.6, + horizontalPadding: 8, +}; + +// Largest font size at or below `preferred` that fits `text` inside `width`, +// floored at `minimum` — below that the text is no longer legible and the +// caller should be reporting a problem instead. +export function fittedNodeFontSize(text, width, preferred, minimum) { + const units = Math.max(1, textUnits(text)); + const available = Math.max(1, width - nodeTextFit.horizontalPadding); + const fitted = Math.min(preferred, available / (units * nodeTextFit.widthFactor)); + return Math.max(minimum, Math.floor(fitted * 10) / 10); +} + +// Width `text` occupies at its legible minimum. Compare against +// `width - nodeTextFit.horizontalPadding` to decide whether shrink-to-fit can +// rescue it. +export function minimumNodeTextWidth(text, minimum) { + return textUnits(text) * minimum * nodeTextFit.widthFactor; +} + +// Available text width inside a box of `width`. +export function availableNodeTextWidth(width) { + return width - nodeTextFit.horizontalPadding; +} diff --git a/tools/archify/renderers/shared/utils.mjs b/tools/archify/renderers/shared/utils.mjs new file mode 100644 index 0000000..8307518 --- /dev/null +++ b/tools/archify/renderers/shared/utils.mjs @@ -0,0 +1,232 @@ +import { + escapeHtml as esc, + localizeTemplate, + resolveLocale, + translateMessage, + viewerCatalog, +} from './i18n.mjs'; + +export { esc }; + +export function renderDefinitions() { + return ` + + + + + + + + + + + + + + + + + `; +} + +const SIGIL_TONE = { + frontend: 'frontend', + start: 'frontend', + backend: 'backend', + active: 'backend', + database: 'database', + success: 'database', + cloud: 'cloud', + waiting: 'cloud', + security: 'security', + failure: 'security', + messagebus: 'messagebus', + external: 'external', + neutral: 'external', +}; + +const SIGIL_SHAPE = { + frontend: ` + + + `, + backend: ``, + database: ` + `, + cloud: ``, + security: ` + `, + messagebus: ` + + + `, + external: ` + `, + start: ` + `, + active: ``, + waiting: ``, + success: ` + `, + failure: ` + `, + neutral: ` + `, +}; + +// A quiet, renderer-owned role stamp. It is authored SVG content rather than a +// viewer overlay, so it survives canonical export while adding no focus target, +// accessible name, layout box, or interaction state of its own. +export function renderSemanticSigil(kind, { x, y, size = 11 } = {}) { + const normalized = Object.hasOwn(SIGIL_SHAPE, kind) ? kind : 'neutral'; + const tone = SIGIL_TONE[normalized] || 'external'; + const scale = size / 16; + return ``; +} + +export function renderCards(cards) { + const list = Array.isArray(cards) ? cards : []; + return ` +
    +${list.map((card) => `
    +
    +
    +

    ${esc(card.title)}

    +
    +
      +${card.items.map((item) => `
    • • ${esc(item)}
    • `).join('\n')} +
    +
    `).join('\n\n')} +
    `; +} + +const SVG_SLOT_RE = / [\s\S]*? /; +const CARDS_SLOT_RE = / [\s\S]*? /; +const SUBTITLE_SLOT_RE = /^([ \t]*)

    \[Subtitle description\]<\/p>[ \t]*(\r?\n)?/m; +const GUIDED_VIEWS_PLACEHOLDER = ''; +const SOURCE_EVIDENCE_PLACEHOLDER = ' '; +const I18N_PLACEHOLDER = ' '; + +function serializeScriptJson(value) { + return JSON.stringify(value) + .replaceAll('<', '\\u003c') + .replaceAll('>', '\\u003e') + .replaceAll('&', '\\u0026'); +} + +const TEMPLATE_PLACEHOLDERS = [ + '', + '[PROJECT NAME] Architecture Diagram', + '

    [PROJECT NAME] Architecture

    ', + GUIDED_VIEWS_PLACEHOLDER, +]; + +export function applyTemplate(template, { + title, + subtitle, + svg, + cards, + locale, + visualPreset = 'classic', + guidedViews = [], + sourceEvidence = null, +}) { + if (!SVG_SLOT_RE.test(template)) { + throw new Error('applyTemplate: template missing ARCHIFY:SVG_SLOT sentinel'); + } + if (!CARDS_SLOT_RE.test(template)) { + throw new Error('applyTemplate: template missing ARCHIFY:CARDS_SLOT sentinel'); + } + if (!SUBTITLE_SLOT_RE.test(template)) { + throw new Error('applyTemplate: template missing subtitle placeholder'); + } + for (const ph of TEMPLATE_PLACEHOLDERS) { + if (!template.includes(ph)) { + throw new Error(`applyTemplate: template missing placeholder ${JSON.stringify(ph)}`); + } + } + // Keep existing custom templates compatible when evidence is not requested. + // Silently dropping verified evidence would be misleading, so the new slot + // becomes mandatory only for the opt-in evidence path. + if (sourceEvidence && !template.includes(SOURCE_EVIDENCE_PLACEHOLDER)) { + throw new Error(`applyTemplate: repository evidence requires placeholder ${JSON.stringify(SOURCE_EVIDENCE_PLACEHOLDER)}`); + } + // Function replacers: a literal `$&`, `$'`, `$\`` or `$$` in titles, labels, + // or rendered SVG must not be interpreted as a replacement pattern. + const guidedViewsJson = serializeScriptJson(guidedViews); + const sourceEvidenceJson = serializeScriptJson(sourceEvidence); + const resolvedLocale = resolveLocale(locale); + const i18nJson = serializeScriptJson({ locale: resolvedLocale, messages: viewerCatalog(resolvedLocale) }); + const renderedSubtitle = typeof subtitle === 'string' && subtitle.trim() + ? `

    ${esc(subtitle)}

    ` + : ''; + const i18nData = ` `; + const localizedTemplate = localizeTemplate(template, resolvedLocale); + const templateWithI18n = localizedTemplate.includes(I18N_PLACEHOLDER) + ? localizedTemplate.replace(I18N_PLACEHOLDER, () => i18nData) + : localizedTemplate.replace(GUIDED_VIEWS_PLACEHOLDER, () => `${i18nData}\n ${GUIDED_VIEWS_PLACEHOLDER}`); + return templateWithI18n + .replace(TEMPLATE_PLACEHOLDERS[0], () => ``) + .replace(TEMPLATE_PLACEHOLDERS[1], () => `${esc(translateMessage(resolvedLocale, 'page.title', { title }))}`) + .replace(TEMPLATE_PLACEHOLDERS[2], () => `

    ${esc(title)}

    `) + .replace(SUBTITLE_SLOT_RE, (_match, indent, newline = '') => renderedSubtitle + ? `${indent}${renderedSubtitle}${newline}` + : '') + .replace(SVG_SLOT_RE, () => svg) + .replace(CARDS_SLOT_RE, () => cards) + .replace(GUIDED_VIEWS_PLACEHOLDER, () => ``) + .replace(SOURCE_EVIDENCE_PLACEHOLDER, () => sourceEvidence + ? ` ` + : ''); +} + +// CJK and other wide/fullwidth glyphs render at roughly twice the advance +// width of ASCII in the monospace stacks the template uses. Keep halfwidth +// forms (notably U+FF61–U+FF9F Katakana) out of this set. The explicit ranges +// also cover vertical punctuation and supplementary East Asian scripts that +// literal glyph ranges made difficult to audit. +// Code points that take two columns of advance width: East Asian Wide and +// Fullwidth per UAX #11, tracking Unicode 17.0. That takes in the BMP symbols +// carrying emoji presentation (U+2705, U+2B50, U+26A1, U+231B, ...), which +// render at the same square advance as the supplementary-plane emoji already +// listed here, and Hangul Jamo Extended-A. Two boundary calls worth naming: +// Unicode 16.0 reclassified the trigrams (U+2630-U+2637) and the monogram / +// digram symbols (U+268A-U+268F) from Neutral to Wide, so both are in; and +// Hangul Jamo Extended-A stops at U+A97C, its last assigned jamo, because +// U+A97D-U+A97F are unassigned, and unassigned code points outside the CJK +// ranges UAX #11 names default to Neutral rather than Wide. Spelled out as +// ranges because V8 has no \p{East_Asian_Width=W} property escape. +const FULLWIDTH_RE = /[\u1100-\u115F\u231A-\u231B\u2329-\u232A\u23E9-\u23EC\u23F0\u23F3\u25FD-\u25FE\u2614-\u2615\u2630-\u2637\u2648-\u2653\u267F\u268A-\u268F\u2693\u26A1\u26AA-\u26AB\u26BD-\u26BE\u26C4-\u26C5\u26CE\u26D4\u26EA\u26F2-\u26F3\u26F5\u26FA\u26FD\u2705\u270A-\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B-\u2B1C\u2B50\u2B55\u2E80-\uA4CF\uA960-\uA97C\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE6F\uFF01-\uFF60\uFFE0-\uFFE6\u{16FE0}-\u{18DFF}\u{1AFF0}-\u{1AFFF}\u{1B000}-\u{1B2FF}\u{1F000}-\u{1FAFF}\u{20000}-\u{3FFFD}]/u; + +// A variation selector (U+FE00-U+FE0F) carries no advance of its own: it +// re-presents the character before it. VS15 (U+FE0E) asks for text +// presentation, which renders narrow; VS16 (U+FE0F) asks for emoji +// presentation, which renders at the square emoji advance. So a base plus a +// selector is measured from the selector, not from the base -- otherwise +// widening the emoji-presentation bases above turns U+2B50 U+FE0F from two +// units into three while the glyph on screen stays one square, and leaves +// U+2708 U+FE0F at two only because its base happens to be narrow. +// +// A selector following a base that cannot take emoji presentation is +// malformed input; measuring it wide is the safe direction here, since +// over-measuring pads a box while under-measuring spills the label out of it. +const VARIATION_SELECTOR_FIRST = 0xfe00; +const VARIATION_SELECTOR_LAST = 0xfe0f; +const VARIATION_SELECTOR_TEXT = 0xfe0e; +const VARIATION_SELECTOR_EMOJI = 0xfe0f; + +export function textUnits(text) { + const chars = Array.from(String(text ?? '')); + let units = 0; + for (let i = 0; i < chars.length; i += 1) { + const codePoint = chars[i].codePointAt(0); + if (codePoint >= VARIATION_SELECTOR_FIRST && codePoint <= VARIATION_SELECTOR_LAST) continue; + const next = i + 1 < chars.length ? chars[i + 1].codePointAt(0) : -1; + if (next === VARIATION_SELECTOR_EMOJI) units += 2; + else if (next === VARIATION_SELECTOR_TEXT) units += 1; + else units += FULLWIDTH_RE.test(chars[i]) ? 2 : 1; + } + return units; +} diff --git a/tools/archify/renderers/shared/validator.mjs b/tools/archify/renderers/shared/validator.mjs new file mode 100644 index 0000000..7664657 --- /dev/null +++ b/tools/archify/renderers/shared/validator.mjs @@ -0,0 +1,86 @@ +import * as validators from './generated-validators.mjs'; +import { throwDiagnosticError } from './diagnostics.mjs'; + +// "/nodes/3/label" reads much better as "/nodes/3 (id: "router") /label" for the +// LLM fixing the JSON; resolve the nearest enclosing element's id or label. +function annotatedPath(instancePath, data) { + if (!instancePath) return { path: '/', identity: null }; + let node = data; + let hint = null; + for (const seg of instancePath.split('/').slice(1)) { + if (node == null || typeof node !== 'object') break; + node = node[/^\d+$/.test(seg) ? Number(seg) : seg]; + if (node && typeof node === 'object' && !Array.isArray(node)) { + const tag = node.id ?? node.label; + if (tag != null) hint = String(tag); + } + } + return { path: instancePath, identity: hint }; +} + +function annotatePath(instancePath, data) { + const annotated = annotatedPath(instancePath, data); + return annotated.identity != null + ? `${annotated.path} (id/label: ${JSON.stringify(annotated.identity)})` + : annotated.path; +} + +function formatErrors(errors, data) { + return errors.map((e) => { + const where = annotatePath(e.instancePath, data); + const detail = e.params && Object.keys(e.params).length + ? ' ' + JSON.stringify(e.params) + : ''; + return ` ${where} ${e.message}${detail}`; + }).join('\n'); +} + +export function validateSchema(diagramType, data) { + const validate = validators[diagramType]; + if (!validate) { + throw new Error(`validateSchema: unknown diagram type "${diagramType}"`); + } + if (!validate(data)) { + const diagnostics = validate.errors.map((error) => { + const annotated = annotatedPath(error.instancePath, data); + const subject = { + diagramType, + path: annotated.path, + ...(annotated.identity != null ? { identity: String(annotated.identity) } : {}), + }; + const evidence = { + keyword: error.keyword, + expected: error.schema, + ...error.params, + }; + const supportedFixes = { + additionalProperties: [`remove unsupported property ${JSON.stringify(error.params?.additionalProperty)}`], + required: [`add required property ${JSON.stringify(error.params?.missingProperty)}`], + type: [`use ${JSON.stringify(error.params?.type)} at ${annotated.path}`], + enum: [`choose one of ${JSON.stringify(error.params?.allowedValues || [])}`], + pattern: [`match the required pattern ${JSON.stringify(error.params?.pattern)}`], + minimum: [`use a value ${error.params?.comparison || '>='} ${error.params?.limit}`], + maximum: [`use a value ${error.params?.comparison || '<='} ${error.params?.limit}`], + minItems: [`provide at least ${error.params?.limit} item(s)`], + maxItems: [`provide at most ${error.params?.limit} item(s)`], + minLength: [`provide at least ${error.params?.limit} character(s)`], + maxLength: [`provide at most ${error.params?.limit} character(s)`], + }[error.keyword] || []; + const detail = error.params && Object.keys(error.params).length + ? ` ${JSON.stringify(error.params)}` + : ''; + return { + code: `schema/${error.keyword}`, + severity: 'error', + message: `${annotatePath(error.instancePath, data)} ${error.message}${detail}`, + subject, + evidence, + supportedFixes, + }; + }); + throwDiagnosticError( + `${diagramType} schema validation failed:\n${formatErrors(validate.errors, data)}`, + diagnostics, + ); + } +} diff --git a/tools/archify/renderers/workflow/README.md b/tools/archify/renderers/workflow/README.md new file mode 100644 index 0000000..186d7b6 --- /dev/null +++ b/tools/archify/renderers/workflow/README.md @@ -0,0 +1,223 @@ +# Workflow Renderer + +Render `diagram_type: "workflow"` JSON files into the standard Archify HTML +template. + +```bash +node archify/renderers/workflow/render-workflow.mjs input.workflow.json output.html +``` + +The renderer validates input against `archify/schemas/workflow.schema.json` +with the bundled standalone validator. No dependency installation is required. + +If `output.html` is omitted, the renderer uses `meta.output` from the JSON file +or falls back to `workflow.html` in the current working directory. + +After rendering, run the artifact checker: + +```bash +node archify/scripts/check-render-output.mjs output.html +``` + +It catches final-SVG issues that are easiest to see in a browser: non-finite +SVG values, accidental two-point diagonal arrows, and arrows crossing the +legend. + +## Input + +Workflow JSON files must set: + +```json +{ + "schema_version": 2, + "diagram_type": "workflow", + "meta": { + "title": "Agent Tool Call Workflow" + }, + "lanes": [], + "phases": [], + "groups": [], + "mainPath": [], + "nodes": [], + "edges": [], + "cards": [] +} +``` + +Use `schema_version: 2` for new workflows. Its readable layout compiler treats +every `col` as a logical rank in `0..5` and derives geometry from the measured +document. `schema_version: 1` remains the fixed legacy contract for existing +sources; valid v1 output is preserved byte-for-byte and never silently +reinterpreted as v2. + +Omit `meta.viewBox` for the common v2 case so the compiler can use intrinsic +measured bounds. In v1, the omitted width remains fixed at 720 and height is +derived from lane count. A complete worked example lives at +`archify/examples/agent-tool-call.workflow.json`; its `schema_version` selects +the applicable contract. + +The schema lives at: + +```text +archify/schemas/workflow.schema.json +``` + +## Migration and layout receipt + +Migrate an existing v1 source into a separate v2 file: + +```bash +node archify/bin/archify.mjs migrate workflow old.json new.json --to-schema 2 --json +``` + +Running the command again with its schema-v2 output as the new source is an +idempotent verification pass: the destination bytes and geometry stay unchanged. + +The command never overwrites the source by default. It maps absolute +`via[*][0]`, `labelAt[0]`, and `channelX` values from legacy to solved rank +space, preserves y coordinates unless a reported vertical constraint needs +author input, expands an explicit viewBox only for an unambiguous containment +repair, and writes the destination only after v2 compilation and artifact +checks pass. Ambiguous explicit pins fail without producing the destination. + +Inspect the stable author-facing v2 plan with: + +```bash +node archify/bin/archify.mjs validate workflow input.workflow.json --layout-json +``` + +The receipt reports the selected contract, measured `viewBox` and +`requiredViewBox`, solved columns, nodes, edges, labels, and causal diagnostics. +It deliberately omits solver iterations and candidate scores. + +## Legend + +The default legend derives component kinds from `nodes[].type`. Supported +`meta.legend.entries` keys, in stable order, are `frontend`, `backend`, +`security`, `messagebus`, `database`, `cloud`, and `external`. Labels and +visibility may be overridden through the shared legend contract; only kinds +backed by rendered nodes receive Semantic Legend controls. + +## Layout contracts + +### Fixed v1 + +| Constant | Value | +|----------|-------| +| viewBox | default `[720, auto]` — auto height = 52 + lanes×104 + (lanes−1)×20 + 124 | +| Lane frame | x 40, width 640, height 104, gap 20; first lane top at y 52 | +| Lane title strip | top 30px of each lane; node boxes must stay below it | +| Column centers (`col` 0–5) | x = 88, 220, 300, 430, 500, 625 | +| Phase headers | Optional `phases[]` render above the first lane, spanning `fromCol..toCol` | +| Lane groups | Optional `groups[]` frame parallel work or branch work inside one lane | +| Exception lanes | Set `lane.variant: "exception"` for retry, denial, fallback, or failure paths | +| Main path lint | Optional `mainPath[]` checks that happy-path steps have matching edges and do not move backward | +| Default node | 92×52 (height 68 when `tag` is set) | +| Node spacing | ≥8px between nodes in the same lane | +| Edge length | straight segments must span ≥28px | +| Legend row | y = lane bottom + 44; viewBox height must be ≥ legend y + 18 | + +Column-center gaps are 132 / 80 / 130 / 70 / 125 px: columns 1↔2 (80px) and +3↔4 (70px) cannot both hold default-width 92px nodes in the same lane. Such an +invalid v1 source receives one causal `workflow/column-capacity` diagnostic and +a verified migration-to-v2 repair; v1 never falls through to adaptive layout. + +### Readable v2 + +| Invariant | Contract | +|----------|----------| +| Logical columns | `col` is an integer in `0..5`; pixel centers are measured output | +| Adjacent-rank baseline | 120px center distance before document-specific constraints | +| Same-lane node clearance | ≥8px when vertical node intervals overlap | +| Facing direct edge | clear gap ≥`max(28px, measured label mask width + 8px)` | +| Automatic route rhythm | direct segment ≥28px; endpoint stub ≥8px; interior turn segment ≥16px | +| Implicit viewBox | intrinsic content bounds plus contract padding | +| Explicit viewBox | containment capacity; too-small input reports exact `requiredViewBox` and contributors | + +The compiler applies constraints only to actual related or overlapping +same-lane nodes, so a wide node in an unrelated lane does not expand every +rank. Legacy centers are a soft preference after correctness constraints, not +a geometry promise. Phase and group frames derive from the solved rank bands. +Automatic routes are normalized once and the same final scene drives +validation and SVG serialization. Long automatic labels compare direct-gutter +growth with a legal channel instead of widening every downstream rank. Measured +multi-row legends participate in intrinsic height and explicit viewBox +capacity. + +Authored `via`, `labelAt`, `channelX`, and `channelY` are absolute hard pins in +v2; an infeasible pin returns `workflow/explicit-pin-conflict` rather than being +silently moved. `fromSide` and `toSide` remain direction constraints. A route +preset restricts the automatic candidate family but is not itself an absolute +coordinate pin. When either endpoint side is omitted, the v2 compiler chooses +a feasible side; an authored side restricts that endpoint to the named port. + +## Design Rules + +- Use lanes for ownership or runtime boundaries. +- Use phase headers for high-level story beats such as Intake, Plan, Execute, and Report. +- Use groups for parallel checks, branch handling, or bounded work within a lane; every group must contain at least one node. +- Use `lane.variant: "exception"` for human wait, denial, retry, fallback, and failure lanes instead of mixing those paths into the happy path. +- Set `mainPath` when the diagram has a clear happy path; the renderer validates that consecutive ids have matching edges and move left-to-right. +- Place nodes with lane IDs and `col` indexes in `0..5`, not raw SVG coordinates. +- Preserve semantic edge labels. Readable v2 allocates measured label clearance; + when a label does not fit, repair the reported capacity or route constraint + instead of deleting meaning. +- Use labels for decisions, approvals, protocols, async traces, return paths, + and any other relationship meaning not fully implied by its endpoints. +- Prefer route presets — `drop` (bend between lanes; `bias` 0–1 picks where), + `outside-right`, `return-left`, `bottom-channel`, and `up-channel` — before + using raw `via` points. `straight` and the default `auto` cover the rest. +- Keep workflow examples compact enough to render well in narrow chat/browser + previews. + +### Optional semantic checks + +Layout validation cannot infer domain truth from labels or cards. When source +evidence establishes roots, terminals, mandatory direct relationships, or +mandatory directed reachability, encode those facts in `semanticChecks`: + +```json +"semanticChecks": { + "allowedRoots": ["request", "resource_catalog"], + "allowedTerminals": ["reply", "audit_log"], + "requiredEdges": [ + { "from": "dispatch", "to": "dispatch_ledger" } + ], + "requiredPaths": [ + { "from": "event_ledger", "to": "runtime_host" } + ] +} +``` + +When `allowedRoots` or `allowedTerminals` is present, it is the complete allow +list for zero-incoming or zero-outgoing nodes respectively. `requiredEdges` +requires one exact authored direction; `requiredPaths` permits intermediate +nodes but follows authored edge direction. These checks run before layout, do +not alter SVG or receipt bytes, and must not be weakened merely to resolve a +route or composition diagnostic. Omit fields whose domain facts are unknown. + +Schema violations exit non-zero with path-prefixed messages annotated with the +element's id or label. The renderer additionally fails when it can detect +layout problems, including node overlap, nodes outside their lanes, invalid +phase/group column ranges, empty groups, broken `mainPath` steps, unknown edge +targets, labels colliding with nodes or other labels, labels wider than their +node, legends outside the viewBox, or straight arrows that are too short to +read cleanly. The shared Clean Flow Gate also rejects edges crossing unrelated +nodes with 2px clearance; lanes, phases, and groups remain intentional +pass-through containers. Text width is estimated CJK-aware: fullwidth glyphs +count as two units. + +Diagnostics are causal: a rank-capacity failure suppresses derivative short +edge, endpoint-direction, and label-overlap findings. Every +`supportedFixes[]` entry is verified by replanning the proposed edit, and a +diagnostic never proposes removing a semantic label when label presence does +not cause the failed invariant. + +Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper +X crossings then fail with `composition/proper-crossing`; default `standard` +keeps them as artifact-receipt warnings. Collinear lane corridors are outside +the proper-X rule, but a separate gate warns in `standard` and fails in +`showcase` when unrelated edges overlap for at least 8px. Shared semantic +endpoints, point touches, and shorter overlaps remain valid. Showcase also +rejects any route segment below 8px and any interior turn segment below 16px; +ordinary 8–15px endpoint stubs remain valid for fixed lane gaps. diff --git a/tools/archify/renderers/workflow/render-workflow.mjs b/tools/archify/renderers/workflow/render-workflow.mjs new file mode 100644 index 0000000..27bfa14 --- /dev/null +++ b/tools/archify/renderers/workflow/render-workflow.mjs @@ -0,0 +1,35 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { loadDiagramWithBrandMarks, writeDiagram } from '../shared/cli.mjs'; +import { throwDiagnosticError } from '../shared/diagnostics.mjs'; +import { compileWorkflow } from './workflow-compiler.mjs'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const { diagram: workflow, template, outPath } = await loadDiagramWithBrandMarks({ + rendererDir: __dirname, + diagramType: 'workflow', + defaultExample: 'agent-tool-call.workflow.json' +}); + +const compiled = compileWorkflow({ + workflow, + qualityProfile: process.env.ARCHIFY_QUALITY_PROFILE || workflow.meta?.quality_profile, +}); + +const layoutJson = process.argv.includes('--layout-json'); + +if (layoutJson) { + process.stdout.write(`${JSON.stringify(compiled.receipt, null, 2)}\n`); + if (!compiled.ok) process.exitCode = 1; +} else if (!compiled.ok) { + throwDiagnosticError(compiled.error || 'Workflow compilation failed.', compiled.diagnostics); +} else { + writeDiagram({ + outPath, + template, + diagramType: 'workflow', + meta: workflow.meta, + svg: compiled.svg, + cards: workflow.cards, + }); +} diff --git a/tools/archify/renderers/workflow/workflow-compiler.mjs b/tools/archify/renderers/workflow/workflow-compiler.mjs new file mode 100644 index 0000000..81ba79a --- /dev/null +++ b/tools/archify/renderers/workflow/workflow-compiler.mjs @@ -0,0 +1,4400 @@ +import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs'; +import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs'; +import { + throwDiagnosticError, + throwDiagnosticProblems, + withDiagnosticRecordingSuppressed, +} from '../shared/diagnostics.mjs'; +import { validateSchema } from '../shared/validator.mjs'; +import { + legendFootprint, + measureLegend, + relationshipLegendObstacles, + resolveLegend, + renderLegend as renderResolvedLegend, +} from '../shared/legend.mjs'; +import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs'; +import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs'; +import { translateMessage as i18nText } from '../shared/i18n.mjs'; +import { + createMappedWorkflowCandidate, + intrinsicWorkflow, + planningWorkflow, +} from './workflow-migration-geometry.mjs'; +import { + asArray, + isFinitePoint, + rectsOverlap, + segmentIntersectsRect, + segmentRectClearance, + cleanEndpointSideProblems, + cleanFlowProblems, + cleanCrossingProblems, + cleanAmbiguousCorridorProblems, + cleanBorderRunProblems, + cleanRouteRhythmProblems, + cleanLabelRouteClearanceProblems, + collectAmbiguousCorridors, + collectLabelRouteClearance, + collectBorderRuns, + forwardCollinearAnalysisSegments, + sourceSegmentIndexAtPoint, + suggestLabelObstacleFix, + suggestLabelPairFix, + anchor, + automaticPortSpread, + defaultFromSide, + defaultToSide, + chosenSide, + normalizeRoutePoints, + routeHonorsEndpointSides, + polylinePath, + routePointsValue, + labelPoint, + componentFill, + componentText, + arrowClassMap, + variantAccent +} from '../shared/geometry.mjs'; + +const LEGACY_COLUMN_CENTERS = Object.freeze([88, 220, 300, 430, 500, 625]); +const READABLE_CANDIDATE_COST_PRIORITY = Object.freeze([ + 'automaticForwardReversePx', + 'properCrossingCount', + 'sharedCorridorPx', + 'labelRouteClearanceDeficit', + 'interiorPreferred28Deficit', + 'bendCount', + 'stretchMilli', + 'canvasGrowthPx', + 'portDisplacementMilli', + 'legacyCoordinateDisplacement', + 'stableCandidateOrdinal', +]); +const MAX_READABLE_LAYOUT_FEEDBACK_ROUNDS = 3; +const GROUP_FRAME_TOP_INSET = 8; +const GROUP_FRAME_BOTTOM_INSET = 4; +const GROUP_LABEL_BASELINE_OFFSET = -2; +const GROUP_LABEL_MASK_ASCENT = 10; +const GROUP_LABEL_MASK_H = 14; +const GROUP_NODE_INSET = 4; + +class WorkflowLayoutFeedback extends Error { + constructor(request) { + super(`Workflow layout requires ${request.kind} feedback.`); + this.name = 'WorkflowLayoutFeedback'; + this.request = request; + } +} + +function createLegacyLayout() { + return { + contract: 'fixed-v1', + laneX: 40, + laneY: 52, + laneW: 640, + laneH: 104, + laneGap: 20, + laneTitleH: 30, + colXs: [...LEGACY_COLUMN_CENTERS], + nodeW: 92, + nodeH: 52, + defaultViewBoxWidth: 720, + }; +} + +function authoredNodeWidth(node) { + return Number.isFinite(node?.width) ? node.width : 92; +} + +function nodeWidthContributor(node) { + return `node ${node.id} width ${authoredNodeWidth(node)}px`; +} + +function authoredNodeHeight(node) { + if (Number.isFinite(node?.height)) return node.height; + return node?.tag ? 68 : 52; +} + +function workflowLabelWidth(label) { + return Math.max(30, textUnits(label) * 4.8 + 10); +} + +function readableGroupBounds(workflow, group, colXs) { + if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol) + || group.fromCol < 0 || group.fromCol > group.toCol || group.toCol >= colXs.length) { + return { x: 0, width: 0, cx: 0 }; + } + const start = colXs[group.fromCol] - 50; + const end = colXs[group.toCol] + 50; + const naturalWidth = end - start; + const minimumWidth = textUnits(group.label) * 5.6 + 20; + let width = Math.max(naturalWidth, minimumWidth); + let left = group.fromCol === group.toCol && width > naturalWidth + ? start + : (start + end - width) / 2; + let right = left + width; + for (const node of asArray(workflow.nodes)) { + if (node.lane !== group.lane + || !Number.isInteger(node.col) + || node.col < group.fromCol + || node.col > group.toCol + || node.col < 0 + || node.col >= colXs.length) continue; + const halfWidth = authoredNodeWidth(node) / 2; + left = Math.min(left, colXs[node.col] - halfWidth - GROUP_NODE_INSET); + right = Math.max(right, colXs[node.col] + halfWidth + GROUP_NODE_INSET); + } + width = right - left; + return { x: left, width, cx: left + width / 2 }; +} + +function verticalIntervalsOverlap(a, b, clearance = 0) { + const aCenter = Number(a?.yOffset) || 0; + const bCenter = Number(b?.yOffset) || 0; + return Math.abs(aCenter - bCenter) + < authoredNodeHeight(a) / 2 + authoredNodeHeight(b) / 2 + clearance; +} + +function createReadableLayout(workflow, layoutFeedback = {}) { + const columnCount = 6; + const baselinePitch = 120; + const columnStart = 94; + const maxLayoutIterations = 3; + const channelDetourBudgetPx = 4 * 28; + const constraints = []; + const feedbackConstraints = []; + const channelLabelEdgeKeys = new Set(); + const widthContributors = new Set(); + const heightContributors = new Set(); + const nodes = asArray(workflow.nodes); + const nodesById = new Map(nodes.map((node) => [node.id, node])); + + for (let col = 0; col < columnCount - 1; col += 1) { + constraints.push({ from: col, to: col + 1, minimum: baselinePitch }); + } + for (const [key, minimum] of Object.entries(layoutFeedback.rankGapMinimums || {}).sort()) { + const [from, to] = key.split(':').map(Number); + constraints.push({ + from, + to, + minimum, + contributors: layoutFeedback.rankGapContributors?.[key] + || [`rank ${from}→${to} route clearance`], + }); + } + + for (let leftIndex = 0; leftIndex < nodes.length; leftIndex += 1) { + for (let rightIndex = leftIndex + 1; rightIndex < nodes.length; rightIndex += 1) { + const leftNode = nodes[leftIndex]; + const rightNode = nodes[rightIndex]; + if (leftNode.lane !== rightNode.lane || leftNode.col === rightNode.col) continue; + if (!verticalIntervalsOverlap(leftNode, rightNode, 8)) continue; + const fromNode = leftNode.col < rightNode.col ? leftNode : rightNode; + const toNode = fromNode === leftNode ? rightNode : leftNode; + constraints.push({ + from: fromNode.col, + to: toNode.col, + minimum: authoredNodeWidth(fromNode) / 2 + 8 + authoredNodeWidth(toNode) / 2, + contributors: [ + `rank ${fromNode.col}→${toNode.col} node width clearance`, + nodeWidthContributor(fromNode), + nodeWidthContributor(toNode), + ], + }); + } + } + + for (const edge of asArray(workflow.edges)) { + const fromNode = nodesById.get(edge.from); + const toNode = nodesById.get(edge.to); + if (!fromNode || !toNode || fromNode.lane !== toNode.lane || fromNode.col === toNode.col) continue; + if (edge.via || edge.channelX !== undefined || edge.channelY !== undefined + || !['auto', 'straight'].includes(edge.route || 'auto')) continue; + if ((Number(fromNode.yOffset) || 0) !== (Number(toNode.yOffset) || 0)) continue; + const earlier = fromNode.col < toNode.col ? fromNode : toNode; + const later = earlier === fromNode ? toNode : fromNode; + const labeledDirectClearance = edge.label && !edge.labelAt + ? Math.max(28, workflowLabelWidth(edge.label) + 8) + : 28; + const directLabelExpansionCost = Math.max(0, labeledDirectClearance - 28); + const canUseAutomaticLabelChannel = edge.label + && !edge.labelAt + && (edge.route || 'auto') === 'auto' + && !edge.fromSide + && !edge.toSide + && edge.channelX === undefined + && edge.channelY === undefined; + const preferLabelChannel = canUseAutomaticLabelChannel + && directLabelExpansionCost > channelDetourBudgetPx; + if (preferLabelChannel) channelLabelEdgeKeys.add(stableValueKey(edge)); + constraints.push({ + from: earlier.col, + to: later.col, + minimum: authoredNodeWidth(earlier) / 2 + 28 + authoredNodeWidth(later) / 2, + contributors: [ + `rank ${earlier.col}→${later.col} direct clearance`, + `rank ${earlier.col}→${later.col} node width clearance`, + nodeWidthContributor(earlier), + nodeWidthContributor(later), + ], + }); + if (!preferLabelChannel && labeledDirectClearance > 28) { + const labelConstraintMinimum = authoredNodeWidth(earlier) / 2 + + labeledDirectClearance + + authoredNodeWidth(later) / 2; + feedbackConstraints.push({ + from: earlier.col, + to: later.col, + minimum: labelConstraintMinimum, + contributors: [ + `rank ${earlier.col}→${later.col} direct clearance`, + `edge ${workflowEdgeName(edge)} label mask`, + nodeWidthContributor(earlier), + nodeWidthContributor(later), + ], + }); + } + } + + for (const phase of asArray(workflow.phases)) { + if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol) + || phase.fromCol < 0 || phase.fromCol > phase.toCol || phase.toCol >= columnCount) continue; + const minimumWidth = textUnits(phase.label) * 5.6 + 8; + if (phase.fromCol === phase.toCol) { + if (phase.toCol < columnCount - 1) { + constraints.push({ + from: phase.toCol, + to: phase.toCol + 1, + minimum: baselinePitch + Math.max(0, minimumWidth - 92), + contributors: [`phase ${phase.id || phase.label} label span`], + }); + } + continue; + } + constraints.push({ + from: phase.fromCol, + to: phase.toCol, + minimum: Math.max(0, minimumWidth - 92), + contributors: [`phase ${phase.id || phase.label} label span`], + }); + } + + for (const group of asArray(workflow.groups)) { + if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol) + || group.fromCol < 0 || group.fromCol > group.toCol || group.toCol >= columnCount) continue; + const minimumWidth = textUnits(group.label) * 5.6 + 20; + if (group.fromCol === group.toCol) { + if (group.toCol < columnCount - 1) { + constraints.push({ + from: group.toCol, + to: group.toCol + 1, + minimum: baselinePitch + Math.max(0, minimumWidth - 100), + contributors: [`group ${group.id || group.label} label span`], + }); + } + continue; + } + constraints.push({ + from: group.fromCol, + to: group.toCol, + minimum: Math.max(0, minimumWidth - 100), + contributors: [`group ${group.id || group.label} label span`], + }); + } + + let activeConstraints = [...constraints]; + let colXs; + let colProvenance; + for (let iteration = 0; iteration < maxLayoutIterations; iteration += 1) { + colXs = Array.from({ length: columnCount }, (_, col) => columnStart + col * baselinePitch); + colProvenance = Array.from({ length: columnCount }, () => new Set()); + const orderedConstraints = activeConstraints + .filter(({ from, to, minimum }) => ( + Number.isInteger(from) && Number.isInteger(to) + && from >= 0 && from < to && to < columnCount + && Number.isFinite(minimum) + )) + .sort((a, b) => a.to - b.to || a.from - b.from || a.minimum - b.minimum); + for (let to = 1; to < columnCount; to += 1) { + for (const constraint of orderedConstraints) { + if (constraint.to !== to) continue; + const candidate = colXs[constraint.from] + constraint.minimum; + const candidateProvenance = new Set([ + ...colProvenance[constraint.from], + ...asArray(constraint.contributors), + ]); + if (candidate > colXs[to] + 0.0001) { + colXs[to] = candidate; + colProvenance[to] = candidateProvenance; + } else if (Math.abs(candidate - colXs[to]) <= 0.0001 + && candidate > columnStart + to * baselinePitch + 0.0001) { + for (const contributor of candidateProvenance) colProvenance[to].add(contributor); + } + } + } + if (iteration > 0 || !feedbackConstraints.length) break; + activeConstraints = [...activeConstraints, ...feedbackConstraints]; + } + + const firstRankNodes = nodes.filter((node) => node.col === 0); + const firstExtent = firstRankNodes.reduce( + (maximum, node) => Math.max(maximum, authoredNodeWidth(node) / 2), + 46, + ); + const leftInset = 8; + const leftShift = Math.max(0, 40 + leftInset + firstExtent - colXs[0]); + if (leftShift) { + for (let col = 0; col < colXs.length; col += 1) colXs[col] += leftShift; + for (const node of firstRankNodes) { + if (Math.abs(authoredNodeWidth(node) / 2 - firstExtent) > 0.0001) continue; + for (const provenance of colProvenance) provenance.add(nodeWidthContributor(node)); + } + } + + const unpinnedTopEndpointIds = new Set(); + for (const edge of asArray(workflow.edges)) { + const preservesHorizontalPins = Array.isArray(edge.via) || edge.channelX !== undefined; + if (preservesHorizontalPins) continue; + if (edge.fromSide === 'top') unpinnedTopEndpointIds.add(edge.from); + if (edge.toSide === 'top') unpinnedTopEndpointIds.add(edge.to); + } + const laneOrder = new Map(asArray(workflow.lanes).map((lane, index) => [lane.id, index])); + let laneHeaderShift = 0; + const laneHeaderShiftContributors = new Set(); + for (const nodeId of unpinnedTopEndpointIds) { + const node = nodesById.get(nodeId); + if (!node || !Number.isInteger(node.col) || node.col < 0 || node.col >= columnCount) continue; + const lanePosition = laneOrder.get(node.lane); + const lane = asArray(workflow.lanes)[lanePosition]; + if (!lane) continue; + const prefix = lane.variant === 'exception' + ? 'EX' + : String(lanePosition + 1).padStart(2, '0'); + const laneHeaderRight = 40 + 14 + textUnits(`${prefix} / ${lane.label}`) * 6.2; + const requiredShift = laneHeaderRight + 2 - colXs[node.col]; + if (requiredShift > laneHeaderShift + 0.0001) { + laneHeaderShift = requiredShift; + laneHeaderShiftContributors.clear(); + laneHeaderShiftContributors.add(`lane ${lane.id} label width`); + } else if (requiredShift > 0 && Math.abs(requiredShift - laneHeaderShift) <= 0.0001) { + laneHeaderShiftContributors.add(`lane ${lane.id} label width`); + } + } + if (laneHeaderShift > 0) { + for (let col = 0; col < colXs.length; col += 1) colXs[col] += laneHeaderShift; + for (const provenance of colProvenance) { + for (const contributor of laneHeaderShiftContributors) provenance.add(contributor); + } + } + + let measuredContentLeftShift = asArray(workflow.edges).reduce((maximum, edge) => { + if (!channelLabelEdgeKeys.has(stableValueKey(edge))) return maximum; + const fromNode = nodesById.get(edge.from); + const toNode = nodesById.get(edge.to); + if (!fromNode || !toNode) return maximum; + const labelCenter = (colXs[fromNode.col] + colXs[toNode.col]) / 2; + const labelLeft = labelCenter - workflowLabelWidth(edge.label) / 2; + return Math.max(maximum, 16 - labelLeft); + }, 0); + for (const phase of asArray(workflow.phases)) { + if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol)) continue; + const width = Math.max( + colXs[phase.toCol] - colXs[phase.fromCol] + 92, + textUnits(phase.label) * 5.6 + 8, + ); + const left = phase.fromCol === phase.toCol + ? colXs[phase.fromCol] - 46 + : (colXs[phase.fromCol] + colXs[phase.toCol] - width) / 2; + measuredContentLeftShift = Math.max(measuredContentLeftShift, 16 - left); + } + for (const group of asArray(workflow.groups)) { + if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)) continue; + const bounds = readableGroupBounds(workflow, group, colXs); + measuredContentLeftShift = Math.max(measuredContentLeftShift, 44 - bounds.x); + } + if (measuredContentLeftShift > 0) { + for (let col = 0; col < colXs.length; col += 1) colXs[col] += measuredContentLeftShift; + } + + let rightmost = colXs.at(-1) + 50; + let rightmostContributors = new Set(colProvenance.at(-1)); + for (const node of nodes) { + if (!Number.isInteger(node.col) || node.col < 0 || node.col >= columnCount) continue; + const nodeRight = colXs[node.col] + authoredNodeWidth(node) / 2; + const nodeContributors = new Set([ + ...colProvenance[node.col], + nodeWidthContributor(node), + ]); + if (nodeRight > rightmost + 0.0001) { + rightmost = nodeRight; + rightmostContributors = nodeContributors; + } else if (Math.abs(nodeRight - rightmost) <= 0.0001) { + for (const contributor of nodeContributors) rightmostContributors.add(contributor); + } + } + for (const group of asArray(workflow.groups)) { + if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)) continue; + const bounds = readableGroupBounds(workflow, group, colXs); + const groupRight = bounds.x + bounds.width; + const groupContributors = new Set([ + ...colProvenance[group.fromCol], + ...colProvenance[group.toCol], + `group ${group.id || group.label} label span`, + ...nodes + .filter((node) => node.lane === group.lane + && node.col >= group.fromCol && node.col <= group.toCol) + .map(nodeWidthContributor), + ]); + if (groupRight > rightmost + 0.0001) { + rightmost = groupRight; + rightmostContributors = groupContributors; + } else if (Math.abs(groupRight - rightmost) <= 0.0001) { + for (const contributor of groupContributors) rightmostContributors.add(contributor); + } + } + const widestLaneLabel = asArray(workflow.lanes).reduce((widest, lane, index) => { + const width = textUnits(`${String(index + 1).padStart(2, '0')} / ${lane.label}`) * 6.2 + 30; + return width > widest.width ? { width, lane } : widest; + }, { width: 0, lane: null }); + const laneLabelWidth = widestLaneLabel.width; + const rightmostLaneWidth = Math.ceil(rightmost - 40 + 8); + const laneW = Math.max( + 640, + rightmostLaneWidth, + Math.ceil(laneLabelWidth), + ); + if (laneW > 640) { + if (rightmostLaneWidth === laneW) { + for (const contributor of rightmostContributors) widthContributors.add(contributor); + } + if (Math.ceil(laneLabelWidth) === laneW && widestLaneLabel.lane) { + widthContributors.add(`lane ${widestLaneLabel.lane.id || widestLaneLabel.lane.label} label width`); + } + } + let maxVerticalExtent = 0; + const verticalExtentContributors = new Set(); + for (const node of nodes) { + const yOffset = Number(node.yOffset) || 0; + const extent = authoredNodeHeight(node) / 2 + Math.abs(yOffset); + const contributor = `node ${node.id} height ${authoredNodeHeight(node)}px${yOffset ? ` with yOffset ${yOffset}px` : ''}`; + if (extent > maxVerticalExtent + 0.0001) { + maxVerticalExtent = extent; + verticalExtentContributors.clear(); + verticalExtentContributors.add(contributor); + } else if (Math.abs(extent - maxVerticalExtent) <= 0.0001) { + verticalExtentContributors.add(contributor); + } + } + const baseContentH = Math.max(74, Math.ceil(maxVerticalExtent * 2 + 8)); + const laneH = 30 + baseContentH; + const groupsByLane = new Map(); + for (const group of asArray(workflow.groups)) { + groupsByLane.set(group.lane, [...(groupsByLane.get(group.lane) || []), group]); + } + const groupLaneReserves = asArray(workflow.lanes).map((lane) => { + let header = 0; + let footer = 0; + for (const group of groupsByLane.get(lane.id) || []) { + const bounds = readableGroupBounds(workflow, group, colXs); + const labelLeft = bounds.x + 10; + const labelRight = labelLeft + textUnits(group.label) * 5.6; + for (const node of nodes) { + if (node.lane !== group.lane + || !Number.isInteger(node.col) + || node.col < group.fromCol + || node.col > group.toCol + || node.col < 0 + || node.col >= colXs.length) continue; + const halfWidth = authoredNodeWidth(node) / 2; + const nodeLeft = colXs[node.col] - halfWidth; + const nodeRight = colXs[node.col] + halfWidth; + const overlapsLabel = nodeRight > labelLeft && nodeLeft < labelRight; + const topOffset = (baseContentH - authoredNodeHeight(node)) / 2 + + (Number(node.yOffset) || 0); + const minimumTopOffset = overlapsLabel ? 11 : 9; + header = Math.max(header, Math.ceil(minimumTopOffset - topOffset)); + const bottomMargin = baseContentH - GROUP_FRAME_BOTTOM_INSET + - topOffset - authoredNodeHeight(node); + footer = Math.max(footer, Math.ceil(1 - bottomMargin)); + } + } + return { header: Math.max(0, header), footer: Math.max(0, footer) }; + }); + const groupHeaderHeights = groupLaneReserves.map(({ header }) => header); + const groupFooterHeights = groupLaneReserves.map(({ footer }) => footer); + const laneHeights = groupLaneReserves.map(({ header, footer }) => laneH + header + footer); + const laneGap = Math.max(20, Math.ceil(layoutFeedback.laneGapMin || 0)); + for (const [index, reserve] of groupHeaderHeights.entries()) { + if (!reserve) continue; + const lane = asArray(workflow.lanes)[index]; + heightContributors.add(`lane ${lane.id || lane.label} group label clearance ${reserve}px`); + } + for (const [index, reserve] of groupFooterHeights.entries()) { + if (!reserve) continue; + const lane = asArray(workflow.lanes)[index]; + heightContributors.add(`lane ${lane.id || lane.label} group frame containment ${reserve}px`); + } + if (laneH > 104) { + for (const contributor of verticalExtentContributors) heightContributors.add(contributor); + } + if (laneGap > 20) { + for (const contributor of asArray(layoutFeedback.laneGapContributors)) { + heightContributors.add(contributor); + } + } + const requiredWidth = 40 + laneW + 16; + + return { + contract: 'readable-v2', + laneX: 40, + laneY: 52, + laneW, + laneH, + laneHeights, + laneGap, + laneTitleH: 30, + groupHeaderHeights, + groupFooterHeights, + colXs, + nodeW: 92, + nodeH: 52, + defaultViewBoxWidth: requiredWidth, + channelLabelEdgeKeys, + widthContributors: [...widthContributors].sort(stableCompare), + heightContributors: [...heightContributors].sort(stableCompare), + }; +} + +function compilerFailure(contract, diagnostics, error = diagnostics.map(({ message }) => message).join('\n')) { + return { + ok: false, + error, + diagnostics, + receipt: { contract, diagnostics }, + }; +} + +function workflowEdgeName(edge) { + return edge.id || `${edge.from}->${edge.to}`; +} + +function stableText(value) { + return value == null ? '' : String(value); +} + +function stableCompare(left, right) { + const a = stableText(left); + const b = stableText(right); + return a < b ? -1 : a > b ? 1 : 0; +} + +function stableValueKey(value) { + if (Array.isArray(value)) return `[${value.map(stableValueKey).join(',')}]`; + if (value && typeof value === 'object') { + return `{${Object.keys(value).sort(stableCompare).map((key) => `${JSON.stringify(key)}:${stableValueKey(value[key])}`).join(',')}}`; + } + return JSON.stringify(value); +} + +function cloneWorkflow(value) { + return JSON.parse(JSON.stringify(value)); +} + +function canonicalReadableWorkflow(workflow) { + if (workflow.schema_version !== 2) return workflow; + const laneOrder = new Map(asArray(workflow.lanes).map((lane, index) => [lane.id, index])); + const nodes = [...asArray(workflow.nodes)].sort((left, right) => ( + (laneOrder.get(left.lane) ?? Number.MAX_SAFE_INTEGER) - (laneOrder.get(right.lane) ?? Number.MAX_SAFE_INTEGER) + || left.col - right.col + || stableCompare(left.id, right.id) + )); + const edges = [...asArray(workflow.edges)].sort((left, right) => ( + stableCompare(left.id, right.id) + || stableCompare(left.from, right.from) + || stableCompare(left.to, right.to) + || stableCompare(left.label, right.label) + || stableCompare(left.route, right.route) + || stableCompare(stableValueKey(left), stableValueKey(right)) + )); + const phases = workflow.phases === undefined ? undefined : [...asArray(workflow.phases)].sort((left, right) => ( + left.fromCol - right.fromCol || left.toCol - right.toCol + || stableCompare(left.id, right.id) + )); + const groups = workflow.groups === undefined ? undefined : [...asArray(workflow.groups)].sort((left, right) => ( + (laneOrder.get(left.lane) ?? Number.MAX_SAFE_INTEGER) - (laneOrder.get(right.lane) ?? Number.MAX_SAFE_INTEGER) + || left.fromCol - right.fromCol || left.toCol - right.toCol + || stableCompare(left.id, right.id) + )); + return { + ...workflow, + nodes, + edges, + ...(phases ? { phases } : {}), + ...(groups ? { groups } : {}), + }; +} + +function semanticContractDiagnostics(workflow) { + const checks = workflow.semanticChecks; + if (!checks) return []; + + const nodeIds = new Set(asArray(workflow.nodes).map((node) => node.id)); + const incoming = new Map([...nodeIds].map((id) => [id, 0])); + const outgoing = new Map([...nodeIds].map((id) => [id, 0])); + const adjacency = new Map([...nodeIds].map((id) => [id, new Set()])); + for (const edge of asArray(workflow.edges)) { + if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) continue; + outgoing.set(edge.from, outgoing.get(edge.from) + 1); + incoming.set(edge.to, incoming.get(edge.to) + 1); + adjacency.get(edge.from).add(edge.to); + } + + const diagnostics = []; + const diagnostic = (code, message, subject, evidence, supportedFixes) => ({ + code, + severity: 'error', + message, + subject: { diagramType: 'workflow', ...subject }, + evidence, + supportedFixes, + }); + const referencedNodes = [ + ...asArray(checks.allowedRoots).map((id, index) => ({ id, path: `/semanticChecks/allowedRoots/${index}` })), + ...asArray(checks.allowedTerminals).map((id, index) => ({ id, path: `/semanticChecks/allowedTerminals/${index}` })), + ...asArray(checks.requiredEdges).flatMap((relation, index) => [ + { id: relation.from, path: `/semanticChecks/requiredEdges/${index}/from` }, + { id: relation.to, path: `/semanticChecks/requiredEdges/${index}/to` }, + ]), + ...asArray(checks.requiredPaths).flatMap((relation, index) => [ + { id: relation.from, path: `/semanticChecks/requiredPaths/${index}/from` }, + { id: relation.to, path: `/semanticChecks/requiredPaths/${index}/to` }, + ]), + ]; + for (const { id, path } of referencedNodes) { + if (nodeIds.has(id)) continue; + diagnostics.push(diagnostic( + 'workflow/semantic-node-reference', + `Workflow semantic contract references unknown node "${id}" at ${path}.`, + { node: id, path }, + { knownNodes: [...nodeIds] }, + [`replace "${id}" with an existing node id`, 'add the missing node before compiling'], + )); + } + if (diagnostics.length) return diagnostics; + + if (checks.allowedRoots !== undefined) { + const allowed = new Set(checks.allowedRoots); + for (const [node, count] of incoming) { + if (count > 0 || allowed.has(node)) continue; + diagnostics.push(diagnostic( + 'workflow/unexpected-root', + `Workflow node "${node}" has no incoming edge and is not declared in semanticChecks.allowedRoots.`, + { node, path: '/semanticChecks/allowedRoots' }, + { incomingEdges: 0, allowedRoots: [...allowed] }, + [`add the missing incoming edge to "${node}"`, `declare "${node}" in semanticChecks.allowedRoots if it is an intentional source`], + )); + } + } + + if (checks.allowedTerminals !== undefined) { + const allowed = new Set(checks.allowedTerminals); + for (const [node, count] of outgoing) { + if (count > 0 || allowed.has(node)) continue; + diagnostics.push(diagnostic( + 'workflow/unexpected-terminal', + `Workflow node "${node}" has no outgoing edge and is not declared in semanticChecks.allowedTerminals.`, + { node, path: '/semanticChecks/allowedTerminals' }, + { outgoingEdges: 0, allowedTerminals: [...allowed] }, + [`add the missing outgoing edge from "${node}"`, `declare "${node}" in semanticChecks.allowedTerminals if it is an intentional sink`], + )); + } + } + + const authoredEdges = new Set(asArray(workflow.edges).map((edge) => `${edge.from}\u0000${edge.to}`)); + for (const [index, relation] of asArray(checks.requiredEdges).entries()) { + if (authoredEdges.has(`${relation.from}\u0000${relation.to}`)) continue; + diagnostics.push(diagnostic( + 'workflow/required-edge', + `Workflow semantic contract requires edge "${relation.from}" -> "${relation.to}", but no authored edge matches it.`, + { from: relation.from, to: relation.to, path: `/semanticChecks/requiredEdges/${index}` }, + { authoredEdgeCount: asArray(workflow.edges).length }, + [`add an edge from "${relation.from}" to "${relation.to}" without deleting the semantic requirement`], + )); + } + + function reachable(from, to) { + const visited = new Set([from]); + const pending = [from]; + while (pending.length) { + const current = pending.shift(); + if (current === to) return true; + for (const next of adjacency.get(current) || []) { + if (visited.has(next)) continue; + visited.add(next); + pending.push(next); + } + } + return false; + } + + for (const [index, relation] of asArray(checks.requiredPaths).entries()) { + if (reachable(relation.from, relation.to)) continue; + diagnostics.push(diagnostic( + 'workflow/required-path', + `Workflow semantic contract requires a directed path from "${relation.from}" to "${relation.to}", but none exists.`, + { from: relation.from, to: relation.to, path: `/semanticChecks/requiredPaths/${index}` }, + { reachableNodes: [...new Set([relation.from, ...(adjacency.get(relation.from) || [])])] }, + [`restore a directed path from "${relation.from}" to "${relation.to}" without weakening the semantic requirement`], + )); + } + + return diagnostics; +} + +function compileWorkflowInternal({ + workflow: inputWorkflow, + qualityProfile, + discoverFixes = true, + layoutFeedback = {}, +} = {}) { + if (!inputWorkflow || typeof inputWorkflow !== 'object' || Array.isArray(inputWorkflow)) { + const diagnostics = [{ + code: 'workflow/input-contract', + severity: 'error', + message: 'compileWorkflow requires one parsed workflow document object.', + subject: { diagramType: 'workflow', path: '/' }, + evidence: {}, + supportedFixes: [], + }]; + return compilerFailure('fixed-v1', diagnostics, diagnostics[0].message); + } + const resolvedQualityProfile = qualityProfile || inputWorkflow.meta?.quality_profile; + const authoredQualityProfile = inputWorkflow.meta?.quality_profile; + const qualityResolvedWorkflow = resolvedQualityProfile && resolvedQualityProfile !== inputWorkflow.meta?.quality_profile + ? { ...inputWorkflow, meta: { ...inputWorkflow.meta, quality_profile: resolvedQualityProfile } } + : inputWorkflow; + let inputDiagnostics = []; + try { + validateSchema('workflow', qualityResolvedWorkflow); + } catch (error) { + inputDiagnostics = Array.isArray(error?.archifyDiagnostics) + ? error.archifyDiagnostics.map((diagnostic) => ({ + ...diagnostic, + supportedFixes: [], + })) + : [{ + code: 'workflow/input-contract', + severity: 'error', + message: 'Workflow schema validation failed unexpectedly.', + subject: { diagramType: 'workflow', path: '/' }, + evidence: { reason: error?.message || String(error) }, + supportedFixes: [], + }]; + } + if (inputDiagnostics.length) { + return compilerFailure( + inputWorkflow.schema_version === 2 ? 'readable-v2' : 'fixed-v1', + inputDiagnostics, + ); + } + const workflow = canonicalReadableWorkflow(qualityResolvedWorkflow); + const semanticDiagnostics = semanticContractDiagnostics(workflow); + if (semanticDiagnostics.length) { + return compilerFailure( + workflow.schema_version === 2 ? 'readable-v2' : 'fixed-v1', + semanticDiagnostics, + ); + } + const sourceIndexes = { + lanes: new Map(asArray(qualityResolvedWorkflow.lanes).map((lane, index) => [lane, index])), + nodes: new Map(asArray(qualityResolvedWorkflow.nodes).map((node, index) => [node, index])), + edges: new Map(asArray(qualityResolvedWorkflow.edges).map((edge, index) => [edge, index])), + }; + const layout = workflow.schema_version === 2 + ? createReadableLayout(workflow, layoutFeedback) + : createLegacyLayout(); + +const LEGEND_CATALOG = [ + 'frontend', + 'backend', + 'security', + 'messagebus', + 'database', + 'cloud', + 'external', +].map((kind) => ({ kind, label: i18nText(workflow.meta.locale, `legend.workflow.${kind}`) })); +const presentLegendKinds = new Set(asArray(workflow.nodes).map((node) => node.type)); +const workflowLegendEntries = resolveLegend( + workflow.meta?.legend, + LEGEND_CATALOG, + presentLegendKinds, +); +const legendFootprintOptions = { fontSize: 7, itemGap: 7 }; +const oneRowLegendFootprint = legendFootprint(workflowLegendEntries, { + ...legendFootprintOptions, + width: Number.MAX_SAFE_INTEGER, +}); +const minimumCanvasWidth = workflow.schema_version === 2 + ? Math.max(layout.defaultViewBoxWidth, oneRowLegendFootprint.minWidth + 40) + : layout.defaultViewBoxWidth; +const legendPackingWidth = Math.max( + 1, + (workflow.schema_version === 2 + ? minimumCanvasWidth + : (workflow.meta?.viewBox?.[0] ?? minimumCanvasWidth)) - 40, +); +const packedLegendFootprint = legendFootprint(workflowLegendEntries, { + ...legendFootprintOptions, + width: legendPackingWidth, +}); +const legendExtraHeight = workflow.schema_version === 2 + ? packedLegendFootprint.extraHeight + : 0; + +// Content is 680px wide (laneX + laneW); auto height fits the lanes plus legend. +const autoHeight = layout.laneY + + (layout.laneHeights?.reduce((total, height) => total + height, 0) + ?? (workflow.lanes?.length || 1) * layout.laneH) + + ((workflow.lanes?.length || 1) - 1) * layout.laneGap + + 124 + + legendExtraHeight; +let viewBox = workflow.meta?.viewBox || [minimumCanvasWidth, autoHeight]; +let requiredViewBox = [...viewBox]; + +const laneIndex = new Map(asArray(workflow.lanes).map((lane, index) => [lane.id, index])); +const laneLabels = new Map(asArray(workflow.lanes).map((lane) => [lane.id, lane.label])); + +function nodeContext(node) { + const group = asArray(workflow.groups).find((candidate) => ( + candidate.lane === node.lane && node.col >= candidate.fromCol && node.col <= candidate.toCol + )); + const phase = asArray(workflow.phases).find((candidate) => ( + node.col >= candidate.fromCol && node.col <= candidate.toCol + )); + return [laneLabels.get(node.lane), group?.label, phase?.label].filter(Boolean).join(' › ') + || i18nText(workflow.meta.locale, 'node.context.workflow'); +} + +function laneHeight(idOrIndex) { + const index = typeof idOrIndex === 'number' ? idOrIndex : laneIndex.get(idOrIndex); + return layout.laneHeights?.[index] ?? layout.laneH; +} + +function laneGroupHeaderH(idOrIndex) { + const index = typeof idOrIndex === 'number' ? idOrIndex : laneIndex.get(idOrIndex); + return layout.groupHeaderHeights?.[index] ?? 0; +} + +function laneGroupFooterH(idOrIndex) { + const index = typeof idOrIndex === 'number' ? idOrIndex : laneIndex.get(idOrIndex); + return layout.groupFooterHeights?.[index] ?? 0; +} + +function laneTop(id) { + const index = laneIndex.get(id); + const precedingHeight = asArray(workflow.lanes).slice(0, index) + .reduce((total, _lane, lanePosition) => total + laneHeight(lanePosition), 0); + return layout.laneY + precedingHeight + index * layout.laneGap; +} + +function lastLaneBottom() { + return layout.laneY + + asArray(workflow.lanes).reduce((total, _lane, index) => total + laneHeight(index), 0) + + (workflow.lanes.length - 1) * layout.laneGap; +} + +function legendY() { + return lastLaneBottom() + 44 + legendExtraHeight; +} + +function workflowLegendLayout(obstacles = []) { + return { + x: 20, + baselineY: legendY(), + width: workflow.schema_version === 2 ? legendPackingWidth : viewBox[0] - 40, + fontSize: 7, + itemGap: 7, + minTitleY: lastLaneBottom() + 8, + obstacles, + unfit: workflow.meta?.legend === undefined ? 'hide' : 'error', + diagramType: 'workflow', + }; +} + +function workflowLegendRects() { + if (!workflowLegendEntries.length) return []; + const measured = measureLegend(workflowLegendEntries, workflowLegendLayout()); + if (!measured) return []; + return [ + { kind: 'title', x: 20, y: measured.titleY - 10, width: 48, height: 14 }, + ...measured.entries.map((entry) => ({ + kind: entry.kind, + x: entry.x, + y: entry.baseline - 10, + width: entry.width, + height: 14, + })), + ]; +} + +function measureNode(node) { + const width = node.width || layout.nodeW; + const height = node.height || (node.tag ? 68 : layout.nodeH); + const cx = layout.colXs[node.col]; + const groupHeaderH = laneGroupHeaderH(node.lane); + const contentH = laneHeight(node.lane) - layout.laneTitleH + - groupHeaderH - laneGroupFooterH(node.lane); + const y = laneTop(node.lane) + layout.laneTitleH + groupHeaderH + + (contentH - height) / 2 + (node.yOffset || 0); + return { + ...node, + width, + height, + x: cx - width / 2, + y, + cx, + cy: y + height / 2 + }; +} + +// Font sizes for this renderer's node text; the fitting geometry is shared. +const nodeTextFit = { + labelPreferred: 11, + labelMinimum: 9, + sublabelPreferred: 8, + sublabelMinimum: 6, + tagPreferred: 7, + tagMinimum: 6, +}; + +const nodes = new Map(asArray(workflow.nodes).map((node) => [node.id, measureNode(node)])); + +function workflowCompositionFrames() { + const frames = []; + for (const [index, lane] of asArray(workflow.lanes).entries()) { + const y = laneTop(lane.id); + const height = laneHeight(index); + frames.push({ id: `lane-${index}`, label: lane.label, kind: 'lane', x: layout.laneX, y, width: layout.laneW, height, radius: 10 }); + if (lane.variant === 'exception') { + frames.push({ id: `lane-${index}-exception`, label: `${lane.label} exception`, kind: 'exception-lane', x: layout.laneX + 6, y: y + 6, width: layout.laneW - 12, height: height - 12, radius: 8 }); + } + } + for (const [index, group] of asArray(workflow.groups).entries()) { + const span = groupSpan(group); + frames.push({ + id: `group-${index}`, + label: group.label, + kind: 'group', + x: span.x, + y: laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET, + width: span.width, + height: workflow.schema_version === 2 + ? laneHeight(group.lane) - layout.laneTitleH + - GROUP_FRAME_TOP_INSET - GROUP_FRAME_BOTTOM_INSET + : layout.laneH - layout.laneTitleH - 16, + radius: 9, + }); + } + return frames; +} + +function workflowSceneLabelObstacles() { + const obstacles = []; + for (const [index, lane] of asArray(workflow.lanes).entries()) { + const prefix = lane.variant === 'exception' ? 'EX' : String(index + 1).padStart(2, '0'); + const label = `${prefix} / ${lane.label}`; + obstacles.push({ + kind: 'lane-header', + id: lane.id, + x: layout.laneX + 14, + y: laneTop(lane.id) + 12, + width: textUnits(label) * 6.2, + height: 14, + }); + } + for (const phase of asArray(workflow.phases)) { + if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol) + || phase.fromCol < 0 || phase.toCol >= layout.colXs.length || phase.fromCol > phase.toCol) continue; + const span = phaseSpan(phase); + obstacles.push({ + kind: 'phase-header', + id: phase.id ?? null, + x: span.x, + y: 27, + width: span.width, + height: 16, + }); + } + for (const group of asArray(workflow.groups)) { + if (!laneIndex.has(group.lane) + || !Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol) + || group.fromCol < 0 || group.toCol >= layout.colXs.length || group.fromCol > group.toCol) continue; + const span = groupSpan(group); + const frameY = laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET; + const labelBaseline = frameY + GROUP_LABEL_BASELINE_OFFSET; + obstacles.push({ + kind: 'group-label', + id: group.id ?? null, + x: span.x + 10, + y: labelBaseline - GROUP_LABEL_MASK_ASCENT, + width: textUnits(group.label) * 5.6, + height: GROUP_LABEL_MASK_H, + }); + } + return obstacles; +} + +const mainPathSteps = new Map(asArray(workflow.mainPath).map((id, index) => [id, index])); +const edgeSteps = new Map(asArray(workflow.edges).map((edge, index) => { + const fromStep = mainPathSteps.get(edge.from); + const toStep = mainPathSteps.get(edge.to); + const mainStep = Number.isInteger(fromStep) && toStep === fromStep + 1 ? fromStep : null; + return [edge, mainStep ?? asArray(workflow.mainPath).length + index]; +})); + +function nodeStep(node) { + return mainPathSteps.get(node.id) ?? asArray(workflow.mainPath).length + asArray(workflow.nodes).findIndex((item) => item.id === node.id); +} + + function acceptsFix(mutator) { + if (!discoverFixes) return false; + const candidate = cloneWorkflow(workflow); + mutator(candidate); + return withDiagnosticRecordingSuppressed(() => compileWorkflowWithFeedback({ + workflow: candidate, + qualityProfile: resolvedQualityProfile, + discoverFixes: false, + }).ok); + } + + function verifiedLegacyAlternative(edge, from, to, requiredClearance) { + const occupied = [...nodes.values()].filter((node) => node.lane === to.lane && node.id !== to.id); + const candidates = layout.colXs.map((center, col) => ({ center, col })) + .filter(({ col }) => col !== to.col) + .sort((a, b) => Math.abs(a.col - to.col) - Math.abs(b.col - to.col) || a.col - b.col); + for (const candidate of candidates) { + const candidateRect = { ...to, col: candidate.col, cx: candidate.center, x: candidate.center - to.width / 2 }; + if (occupied.some((node) => rectsOverlap(candidateRect, node, 8))) continue; + const centerDistance = Math.abs(candidate.center - from.cx); + const signedClearance = centerDistance - from.width / 2 - to.width / 2; + if (signedClearance < requiredClearance) continue; + if (acceptsFix((document) => { + document.nodes.find((node) => node.id === to.id).col = candidate.col; + })) return candidate.col; + } + return null; + } + + function readableMigrationProvidesCapacity(from, to, requiredClearance) { + const readable = createReadableLayout({ ...workflow, schema_version: 2 }); + const centerDistance = Math.abs(readable.colXs[to.col] - readable.colXs[from.col]); + if (centerDistance - from.width / 2 - to.width / 2 < requiredClearance) return false; + if (!discoverFixes) return false; + + return withDiagnosticRecordingSuppressed(() => { + const migrationQualityProfile = authoredQualityProfile; + let planned = compileWorkflowWithFeedback({ + workflow: intrinsicWorkflow(workflow), + qualityProfile: migrationQualityProfile, + discoverFixes: false, + }); + if (!planned.ok) { + planned = compileWorkflowWithFeedback({ + workflow: planningWorkflow(workflow), + qualityProfile: migrationQualityProfile, + discoverFixes: false, + }); + } + if (!planned.ok || !Array.isArray(planned.receipt?.columns)) return false; + + let candidate; + try { + candidate = createMappedWorkflowCandidate( + workflow, + LEGACY_COLUMN_CENTERS, + planned.receipt.columns, + ).document; + } catch { + return false; + } + let compiled = compileWorkflowWithFeedback({ + workflow: candidate, + qualityProfile: migrationQualityProfile, + discoverFixes: false, + }); + const requiredViewBox = compiled.diagnostics?.length + && compiled.diagnostics.every(({ code }) => code === 'workflow/viewbox-capacity') + ? compiled.diagnostics.find(({ evidence }) => Array.isArray(evidence?.requiredViewBox)) + ?.evidence.requiredViewBox + : null; + if (!compiled.ok && Array.isArray(candidate.meta?.viewBox) && requiredViewBox) { + candidate.meta.viewBox = [ + Math.max(candidate.meta.viewBox[0], requiredViewBox[0]), + Math.max(candidate.meta.viewBox[1], requiredViewBox[1]), + ]; + compiled = compileWorkflowWithFeedback({ + workflow: candidate, + qualityProfile: migrationQualityProfile, + discoverFixes: false, + }); + } + return compiled.ok; + }); + } + +function verifiedReducedWidths(from, to, requiredClearance) { + const widthBudget = 2 * (Math.abs(to.cx - from.cx) - requiredClearance); + if (widthBudget < 64) return null; + const widths = [from.width, to.width]; + let excess = widths[0] + widths[1] - widthBudget; + for (const index of widths[0] >= widths[1] ? [0, 1] : [1, 0]) { + const reduction = Math.min(excess, widths[index] - 32); + widths[index] -= reduction; + excess -= reduction; + } + if (excess > 0.0001) return null; + const candidates = [from, to]; + const labelsFit = candidates.every((node, index) => ( + textUnits(node.label) * 6.8 <= widths[index] + 6 + && (!node.sublabel || minimumNodeTextWidth(node.sublabel, nodeTextFit.sublabelMinimum) <= availableNodeTextWidth(widths[index])) + && (!node.tag || minimumNodeTextWidth(node.tag, nodeTextFit.tagMinimum) <= availableNodeTextWidth(widths[index])) + )); + if (!labelsFit) return null; + const serializedWidths = widths.map((width) => Math.floor((width + 1e-9) * 100) / 100); + const signedClearance = Math.abs(to.cx - from.cx) + - serializedWidths[0] / 2 - serializedWidths[1] / 2; + if (signedClearance + 0.0001 < requiredClearance) return null; + const accepted = acceptsFix((document) => { + document.nodes.find((node) => node.id === from.id).width = serializedWidths[0]; + document.nodes.find((node) => node.id === to.id).width = serializedWidths[1]; + }); + return accepted ? serializedWidths : null; +} + +function enforceLegacyColumnCapacity() { + if (workflow.schema_version !== 1) return; + for (const edge of workflow.edges) { + const from = nodes.get(edge.from); + const to = nodes.get(edge.to); + if (!from || !to || from.lane !== to.lane || from.col === to.col) continue; + if (!verticalIntervalsOverlap(from, to, 8)) continue; + const centerDistance = Math.abs(to.cx - from.cx); + const actualSignedClearance = centerDistance - from.width / 2 - to.width / 2; + const direct = !edge.via && ['auto', 'straight'].includes(edge.route || 'auto') + && Math.abs(from.cy - to.cy) < 0.0001; + const requiredDirectClearance = direct ? 28 : 8; + if (actualSignedClearance >= requiredDirectClearance) continue; + const alternative = verifiedLegacyAlternative(edge, from, to, requiredDirectClearance); + const reducedWidths = verifiedReducedWidths(from, to, requiredDirectClearance); + const capacity = actualSignedClearance < 0 + ? `overlap by ${Math.abs(Math.round(actualSignedClearance))}px` + : `leave only ${Math.round(actualSignedClearance)}px of direct clearance`; + const message = `Workflow columns ${from.col}→${to.col} place nodes "${from.id}" and "${to.id}" so they ${capacity} under the fixed-v1 layout.`; + const supportedFixes = []; + if (readableMigrationProvidesCapacity(from, to, requiredDirectClearance)) { + supportedFixes.push('migrate this workflow to schema_version 2'); + } + if (alternative !== null) supportedFixes.push(`move node "${to.id}" to verified free column ${alternative}`); + if (reducedWidths) { + supportedFixes.push(`set node widths "${from.id}"=${Math.round(reducedWidths[0] * 100) / 100}px and "${to.id}"=${Math.round(reducedWidths[1] * 100) / 100}px`); + } + throwDiagnosticError(message, [{ + code: 'workflow/column-capacity', + severity: 'error', + message, + subject: { + diagramType: 'workflow', + edge: edge.id ?? null, + from: edge.from, + to: edge.to, + fromCol: from.col, + toCol: to.col, + }, + evidence: { + centerDistancePx: centerDistance, + nodeWidthsPx: [from.width, to.width], + actualSignedClearancePx: actualSignedClearance, + requiredDirectClearancePx: requiredDirectClearance, + }, + supportedFixes, + suppresses: [ + 'workflow/short-edge', + 'clean-flow/endpoint-side-direction', + 'workflow/label-node-overlap', + ], + }]); + } +} + +function verifiedEdgeFix(edge, message, mutator) { + const edgeIndex = workflow.edges.indexOf(edge); + if (edgeIndex < 0) return null; + const accepted = acceptsFix((document) => mutator(document.edges[edgeIndex], document)); + return accepted ? message : null; +} + +function verifiedAutomaticRouteFix(edge, { clearSides = false } = {}) { + const edgeName = workflowEdgeName(edge); + return verifiedEdgeFix( + edge, + clearSides + ? `remove explicit route geometry and endpoint sides from edge "${edgeName}" so readable-v2 can use its verified automatic candidate` + : `remove explicit route geometry from edge "${edgeName}" so readable-v2 can use its verified automatic candidate`, + (candidate) => { + delete candidate.via; + delete candidate.channelX; + delete candidate.channelY; + delete candidate.route; + if (clearSides) { + delete candidate.fromSide; + delete candidate.toSide; + } + }, + ); +} + +function authoredPinEvidence(edge, field) { + const authoredEdgeIndex = sourceIndexes.edges.get(edge); + const value = Array.isArray(edge[field]) + ? edge[field].map((item) => (Array.isArray(item) ? [...item] : item)) + : edge[field]; + return { + edge: workflowEdgeName(edge), + field, + ...(Number.isInteger(authoredEdgeIndex) ? { path: `/edges/${authoredEdgeIndex}/${field}` } : {}), + value, + }; +} + +function combinations(values, size, start = 0, prefix = [], output = []) { + if (prefix.length === size) { + output.push([...prefix]); + return output; + } + for (let index = start; index <= values.length - (size - prefix.length); index += 1) { + prefix.push(values[index]); + combinations(values, size, index + 1, prefix, output); + prefix.pop(); + } + return output; +} + +function verifiedPinRemovalAlternatives(edge, fields, reason) { + if (!discoverFixes) return { removalSets: [], supportedFixes: [] }; + const edgeIndex = workflow.edges.indexOf(edge); + if (edgeIndex < 0) return { removalSets: [], supportedFixes: [] }; + const uniqueFields = [...new Set(fields.filter((field) => edge[field] !== undefined))]; + for (let size = 1; size <= uniqueFields.length; size += 1) { + const removalSets = combinations(uniqueFields, size).filter((fieldSet) => ( + acceptsFix((document) => { + for (const field of fieldSet) delete document.edges[edgeIndex][field]; + }) + )); + if (!removalSets.length) continue; + const edgeName = workflowEdgeName(edge); + return { + removalSets, + supportedFixes: removalSets.map((fieldSet) => ( + `remove ${fieldSet.join(' and ')} from edge "${edgeName}" ${reason}` + )), + }; + } + return { removalSets: [], supportedFixes: [] }; +} + +function conflictPinsFromRemovalSets(edge, removalSets, fallbackFields = []) { + const fields = removalSets.length + ? [...new Set(removalSets.flat())] + : [...new Set(fallbackFields)]; + return fields.map((field) => authoredPinEvidence(edge, field)); +} + +function authoredRouteAssertionFields(edge) { + return [ + ...(Array.isArray(edge?.via) ? ['via'] : []), + ...(edge?.channelX !== undefined ? ['channelX'] : []), + ...(edge?.channelY !== undefined ? ['channelY'] : []), + ...(edge?.route && edge.route !== 'auto' ? ['route'] : []), + ...(edge?.fromSide && edge.fromSide !== 'auto' ? ['fromSide'] : []), + ...(edge?.toSide && edge.toSide !== 'auto' ? ['toSide'] : []), + ]; +} + +function hasAuthoredRouteAssertions(edge) { + return authoredRouteAssertionFields(edge).length > 0; +} + +function verifiedPinReferenceAlternatives(candidateRefs, reason) { + const seenRefs = new Set(); + const refs = candidateRefs.filter(({ edge, edgeIndex, field }) => { + if (edgeIndex < 0 || edge?.[field] === undefined) return false; + const key = `${edgeIndex}:${field}`; + if (seenRefs.has(key)) return false; + seenRefs.add(key); + return true; + }); + const fallbackPins = refs.map(({ edge, field }) => authoredPinEvidence(edge, field)); + if (!discoverFixes) { + return { + removalSets: [], conflictingRefs: refs, conflictingPins: fallbackPins, repairs: [], supportedFixes: [], + }; + } + + for (let size = 1; size <= refs.length; size += 1) { + const removalSets = combinations(refs, size).filter((removalSet) => ( + acceptsFix((document) => { + for (const { edgeIndex, field } of removalSet) delete document.edges[edgeIndex][field]; + }) + )); + if (!removalSets.length) continue; + const conflictingRefs = []; + const conflictingPins = []; + const seenPins = new Set(); + for (const removalSet of removalSets) { + for (const { edge, field } of removalSet) { + const key = `${workflow.edges.indexOf(edge)}:${field}`; + if (seenPins.has(key)) continue; + seenPins.add(key); + conflictingRefs.push({ edge, edgeIndex: workflow.edges.indexOf(edge), field }); + conflictingPins.push(authoredPinEvidence(edge, field)); + } + } + const repairs = removalSets.map((removalSet) => { + const grouped = []; + for (const ref of removalSet) { + let group = grouped.find(({ edge }) => edge === ref.edge); + if (!group) { + group = { edge: ref.edge, fields: [] }; + grouped.push(group); + } + group.fields.push(ref.field); + } + const removals = grouped.map(({ edge, fields }) => ( + `remove ${fields.join(' and ')} from edge "${workflowEdgeName(edge)}"` + )); + return { removalSet, message: `${removals.join(' and ')} ${reason}` }; + }); + return { + removalSets, + conflictingRefs, + conflictingPins, + repairs, + supportedFixes: repairs.map(({ message }) => message), + }; + } + return { + removalSets: [], conflictingRefs: refs, conflictingPins: fallbackPins, repairs: [], supportedFixes: [], + }; +} + +function verifiedRoutePairPinAlternatives(leftEdge, rightEdge, reason) { + const refs = [leftEdge, rightEdge].flatMap((edge) => { + const edgeIndex = workflow.edges.indexOf(edge); + return authoredRouteAssertionFields(edge).map((field) => ({ edge, edgeIndex, field })); + }); + return verifiedPinReferenceAlternatives(refs, reason); +} + +function verifiedLabelRoutePinAlternatives(labelEdge, routeEdge) { + const refs = []; + const labelEdgeIndex = workflow.edges.indexOf(labelEdge); + if (Array.isArray(labelEdge?.labelAt)) { + refs.push({ edge: labelEdge, edgeIndex: labelEdgeIndex, field: 'labelAt' }); + } + const routeEdgeIndex = workflow.edges.indexOf(routeEdge); + for (const field of authoredRouteAssertionFields(routeEdge)) { + refs.push({ edge: routeEdge, edgeIndex: routeEdgeIndex, field }); + } + return verifiedPinReferenceAlternatives( + refs, + Array.isArray(labelEdge?.labelAt) + ? 'so readable-v2 can replan the remaining authored label-route pins' + : 'so readable-v2 can replan the remaining authored route assertions', + ); +} + +function verifiedLabelPairPinAlternatives(leftEdge, rightEdge) { + return verifiedPinReferenceAlternatives( + [leftEdge, rightEdge].flatMap((edge) => ( + Array.isArray(edge?.labelAt) + ? [{ edge, edgeIndex: workflow.edges.indexOf(edge), field: 'labelAt' }] + : [] + )), + 'so readable-v2 can replan the remaining authored label pins', + ); +} + +function verifiedRepairsWithLabelNudges(alternatives) { + return alternatives.repairs.flatMap(({ removalSet, message }) => { + if (removalSet.length !== 1 || removalSet[0].field !== 'labelAt') return [message]; + const nudges = verifiedLabelAtAlternatives(removalSet[0].edge); + return nudges.length ? nudges : [message]; + }); +} + +function throwExplicitPinConflict(edge, invariant, evidence, supportedFixes = []) { + const message = `Workflow edge "${workflowEdgeName(edge)}" has explicit geometry that violates ${invariant}.`; + const [onlyPin] = asArray(evidence?.conflictingPins); + const authoredEdgeIndex = sourceIndexes.edges.get(edge); + const pinPath = asArray(evidence?.conflictingPins).length === 1 + && Number.isInteger(authoredEdgeIndex) + && onlyPin?.field + ? onlyPin.path || `/edges/${authoredEdgeIndex}/${onlyPin.field}` + : null; + throwDiagnosticError(message, [{ + code: 'workflow/explicit-pin-conflict', + severity: 'error', + message, + subject: { + diagramType: 'workflow', + edge: edge.id ?? null, + from: edge.from, + to: edge.to, + ...(pinPath ? { path: pinPath } : {}), + }, + evidence: { invariant, ...evidence }, + supportedFixes: supportedFixes.filter(Boolean), + }]); +} + +function hasAbsoluteRoutePins(edge) { + return Array.isArray(edge?.via) + || edge?.channelX !== undefined + || edge?.channelY !== undefined; +} + +function presentRouteGeometryFields(edge) { + return [ + ...(Array.isArray(edge?.via) ? ['via'] : []), + ...(edge?.channelX !== undefined ? ['channelX'] : []), + ...(edge?.channelY !== undefined ? ['channelY'] : []), + ]; +} + +function verifiedRouteGeometryPinAlternatives( + edge, + reason = 'so readable-v2 can replan the remaining explicit route assertions', +) { + const edgeIndex = workflow.edges.indexOf(edge); + return verifiedPinReferenceAlternatives( + authoredRouteAssertionFields(edge).map((field) => ({ edge, edgeIndex, field })), + reason, + ); +} + +function properOrthogonalIntersection(leftStart, leftEnd, rightStart, rightEnd) { + const leftOrientation = segmentOrientation(leftStart, leftEnd); + const rightOrientation = segmentOrientation(rightStart, rightEnd); + if (leftOrientation === rightOrientation + || leftOrientation === 'diagonal' + || rightOrientation === 'diagonal') return null; + const horizontalStart = leftOrientation === 'horizontal' ? leftStart : rightStart; + const horizontalEnd = leftOrientation === 'horizontal' ? leftEnd : rightEnd; + const verticalStart = leftOrientation === 'vertical' ? leftStart : rightStart; + const verticalEnd = leftOrientation === 'vertical' ? leftEnd : rightEnd; + const point = [verticalStart[0], horizontalStart[1]]; + const epsilon = 0.0001; + const insideHorizontal = point[0] > Math.min(horizontalStart[0], horizontalEnd[0]) + epsilon + && point[0] < Math.max(horizontalStart[0], horizontalEnd[0]) - epsilon; + const insideVertical = point[1] > Math.min(verticalStart[1], verticalEnd[1]) + epsilon + && point[1] < Math.max(verticalStart[1], verticalEnd[1]) - epsilon; + return insideHorizontal && insideVertical ? point : null; +} + +function verifiedLabelAtAlternatives(edge) { + if (!Array.isArray(edge.labelAt)) return []; + const [x, y] = edge.labelAt; + return [ + [0, 24], [0, -24], [24, 0], [-24, 0], + [0, 48], [0, -48], [48, 0], [-48, 0], + ].map(([dx, dy]) => { + const next = [x + dx, y + dy]; + return verifiedEdgeFix( + edge, + `set labelAt on edge "${workflowEdgeName(edge)}" to [${next[0]}, ${next[1]}]`, + (candidate) => { candidate.labelAt = next; }, + ); + }).filter(Boolean); +} + +function verifiedLabelAtNudge(edge) { + const [alternative] = verifiedLabelAtAlternatives(edge); + if (alternative) return alternative; + return verifiedEdgeFix( + edge, + `remove labelAt from edge "${workflowEdgeName(edge)}" so readable-v2 can use verified automatic label placement`, + (candidate) => { delete candidate.labelAt; }, + ); +} + +function throwReadableLabelRoutePinConflict(hit, routePoints = null) { + const labelEdge = hit.labelRelation; + const routeEdge = hit.otherRelation; + const labelPinned = Array.isArray(labelEdge?.labelAt); + const routePinned = hasAuthoredRouteAssertions(routeEdge); + if (!labelPinned && !routePinned) return false; + const alternatives = verifiedLabelRoutePinAlternatives(labelEdge, routeEdge); + const actualRoutePoints = routePoints + || pathCache.get(routeEdge)?.points + || pathFor(routeEdge).points; + const diagnosticEdge = alternatives.conflictingRefs[0]?.edge + || (labelPinned ? labelEdge : routeEdge); + throwExplicitPinConflict(diagnosticEdge, 'explicit label-route clearance', { + conflictingPins: alternatives.conflictingPins, + ...(labelPinned ? { labelAt: [...labelEdge.labelAt] } : {}), + labelRect: { + x: hit.rect.x, + y: hit.rect.y, + width: hit.rect.width, + height: hit.rect.height, + }, + collidedRoute: { + edge: routeEdge.id || `${routeEdge.from}->${routeEdge.to}`, + from: routeEdge.from, + to: routeEdge.to, + points: actualRoutePoints.map((point) => [...point]), + }, + routeSegmentIndex: hit.segmentIndex, + routeSegment: { from: [...hit.start], to: [...hit.end] }, + clearancePx: Math.round(hit.clearance * 10) / 10, + minimumPx: hit.threshold, + }, [ + ...verifiedRepairsWithLabelNudges(alternatives), + ]); + return true; +} + +function throwReadableLabelLabelPinConflict(left, right) { + const leftEdge = left.relation; + const rightEdge = right.relation; + const pinnedEdges = [leftEdge, rightEdge].filter((edge) => Array.isArray(edge?.labelAt)); + if (!pinnedEdges.length) return false; + const alternatives = verifiedLabelPairPinAlternatives(leftEdge, rightEdge); + const causalLabelEdges = [...new Set(alternatives.conflictingRefs.map(({ edge }) => edge))]; + const diagnosticEdge = causalLabelEdges[0] || pinnedEdges[0]; + throwExplicitPinConflict(diagnosticEdge, 'explicit label-label clearance', { + conflictingPins: alternatives.conflictingPins, + labelRects: [left, right].map((rect) => ({ + edge: rect.relation.id ?? null, + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + })), + minimumGapPx: -2, + }, verifiedRepairsWithLabelNudges(alternatives)); + return true; +} + +function classifyFailedAutomaticCandidatePins(edge, rawCandidates) { + const relationIndex = workflow.edges.indexOf(edge); + const priorRoutes = [...pathCache.entries()] + .filter(([otherEdge]) => otherEdge !== edge) + .map(([relation, routed]) => ({ + relation, + relationIndex: workflow.edges.indexOf(relation), + points: routed.points, + })); + if (!priorRoutes.length) return; + const priorLabels = priorRoutes.map(({ relation, relationIndex }) => ( + labelRectFor(relation, relationIndex) + )).filter(Boolean); + + for (const { points } of rawCandidates) { + const candidateRect = candidateLabelRect(edge, points); + const candidateLabel = candidateRect + ? { ...candidateRect, relation: edge, relationIndex, label: edge.label } + : null; + if (candidateLabel) { + const priorLabel = priorLabels.find((otherLabel) => ( + rectsOverlap(candidateLabel, otherLabel, -2) + && (Array.isArray(edge.labelAt) || Array.isArray(otherLabel.relation?.labelAt)) + )); + if (priorLabel) throwReadableLabelLabelPinConflict(candidateLabel, priorLabel); + + const labelRouteHit = collectLabelRouteClearance({ + labels: [candidateLabel], + routedRelations: priorRoutes, + threshold: 4, + }).find((hit) => ( + Array.isArray(edge.labelAt) || hasAbsoluteRoutePins(hit.otherRelation) + )); + if (labelRouteHit) { + const collidedRoute = priorRoutes.find(({ relation }) => relation === labelRouteHit.otherRelation); + throwReadableLabelRoutePinConflict(labelRouteHit, collidedRoute?.points); + } + } + + const reverseHit = collectLabelRouteClearance({ + labels: priorLabels, + routedRelations: [{ relation: edge, relationIndex, points }], + threshold: 4, + }).find((hit) => Array.isArray(hit.labelRelation?.labelAt)); + if (reverseHit) throwReadableLabelRoutePinConflict(reverseHit, points); + } +} + +function validateReadablePairwisePinConflicts() { + const labels = workflow.edges.map((edge, relationIndex) => ( + labelRectFor(edge, relationIndex) + )).filter(Boolean); + const routedRelations = workflow.edges.map((edge, relationIndex) => ( + nodes.has(edge.from) && nodes.has(edge.to) + ? { relation: edge, relationIndex, points: pathFor(edge).points } + : null + )).filter(Boolean); + + const labelRouteHit = collectLabelRouteClearance({ + labels, + routedRelations, + threshold: 4, + }).find((hit) => ( + Array.isArray(hit.labelRelation?.labelAt) || hasAuthoredRouteAssertions(hit.otherRelation) + )); + if (labelRouteHit) throwReadableLabelRoutePinConflict(labelRouteHit); + + for (let leftIndex = 0; leftIndex < labels.length; leftIndex += 1) { + for (let rightIndex = leftIndex + 1; rightIndex < labels.length; rightIndex += 1) { + const left = labels[leftIndex]; + const right = labels[rightIndex]; + if (!rectsOverlap(left, right, -2)) continue; + throwReadableLabelLabelPinConflict(left, right); + } + } + + const requestedProfile = workflow.meta?.quality_profile; + if (requestedProfile !== 'showcase') return; + for (let leftIndex = 0; leftIndex < routedRelations.length; leftIndex += 1) { + const left = routedRelations[leftIndex]; + for (let rightIndex = leftIndex + 1; rightIndex < routedRelations.length; rightIndex += 1) { + const right = routedRelations[rightIndex]; + const leftPinned = hasAuthoredRouteAssertions(left.relation); + const rightPinned = hasAuthoredRouteAssertions(right.relation); + if (!leftPinned && !rightPinned) continue; + if ([left.relation.from, left.relation.to].some((id) => ( + id === right.relation.from || id === right.relation.to + ))) continue; + const leftAnalysis = forwardCollinearAnalysisSegments(left.points); + const rightAnalysis = forwardCollinearAnalysisSegments(right.points); + for (const leftSegment of leftAnalysis) { + for (const rightSegment of rightAnalysis) { + const point = properOrthogonalIntersection( + leftSegment.start, + leftSegment.end, + rightSegment.start, + rightSegment.end, + ); + if (!point) continue; + const leftSourceIndex = sourceSegmentIndexAtPoint(leftSegment, point); + const rightSourceIndex = sourceSegmentIndexAtPoint(rightSegment, point); + const leftSource = { + from: left.points[leftSourceIndex], + to: left.points[leftSourceIndex + 1], + }; + const rightSource = { + from: right.points[rightSourceIndex], + to: right.points[rightSourceIndex + 1], + }; + const alternatives = verifiedRoutePairPinAlternatives( + left.relation, + right.relation, + 'so readable-v2 can replan the remaining authored route assertions', + ); + const diagnosticEdge = leftPinned ? left.relation : right.relation; + throwExplicitPinConflict(diagnosticEdge, 'explicit route-route crossing', { + conflictingPins: alternatives.conflictingPins, + point, + segmentIndex: leftSourceIndex, + otherSegmentIndex: rightSourceIndex, + routeSegments: [ + { edge: left.relation.id ?? null, from: [...leftSegment.start], to: [...leftSegment.end] }, + { edge: right.relation.id ?? null, from: [...rightSegment.start], to: [...rightSegment.end] }, + ], + sourceRouteSegments: [ + { edge: left.relation.id ?? null, from: [...leftSource.from], to: [...leftSource.to] }, + { edge: right.relation.id ?? null, from: [...rightSource.from], to: [...rightSource.to] }, + ], + }, alternatives.supportedFixes); + } + } + } + } + + const corridorHit = collectAmbiguousCorridors({ + routedRelations, + minOverlapPx: 8, + }).find((hit) => ( + hasAuthoredRouteAssertions(hit.left.relation) + || hasAuthoredRouteAssertions(hit.right.relation) + )); + if (corridorHit) { + const leftSegment = { + from: corridorHit.left.points[corridorHit.leftSegment], + to: corridorHit.left.points[corridorHit.leftSegment + 1], + }; + const rightSegment = { + from: corridorHit.right.points[corridorHit.rightSegment], + to: corridorHit.right.points[corridorHit.rightSegment + 1], + }; + const leftPinned = hasAuthoredRouteAssertions(corridorHit.left.relation); + const alternatives = verifiedRoutePairPinAlternatives( + corridorHit.left.relation, + corridorHit.right.relation, + 'so readable-v2 can replan the remaining authored route assertions', + ); + const diagnosticEdge = leftPinned ? corridorHit.left.relation : corridorHit.right.relation; + throwExplicitPinConflict(diagnosticEdge, 'explicit route-route corridor clearance', { + conflictingPins: alternatives.conflictingPins, + segmentIndex: corridorHit.leftSegment, + otherSegmentIndex: corridorHit.rightSegment, + routeSegments: [ + { + edge: corridorHit.left.relation.id ?? null, + from: [...leftSegment.from], + to: [...leftSegment.to], + }, + { + edge: corridorHit.right.relation.id ?? null, + from: [...rightSegment.from], + to: [...rightSegment.to], + }, + ], + overlapStart: [...corridorHit.overlapStart], + overlapEnd: [...corridorHit.overlapEnd], + overlapLengthPx: corridorHit.overlapLength, + minimumClearancePx: 8, + }, alternatives.supportedFixes); + } +} + +const READABLE_PRESET_PIN_FIELDS = Object.freeze({ + straight: [], + drop: ['channelY'], + 'outside-right': ['channelX'], + 'return-left': ['channelX'], + 'bottom-channel': ['channelY'], + 'up-channel': ['channelY'], +}); + +function presentChannelPins(edge) { + return ['channelX', 'channelY'].filter((field) => edge[field] !== undefined); +} + +function validateReadableRouteControls(edge) { + const channelPins = presentChannelPins(edge); + const preset = edge.route || 'auto'; + if (preset === 'auto') return; + const allowedPins = new Set(READABLE_PRESET_PIN_FIELDS[preset] || []); + const conflictingPins = channelPins.filter((field) => !allowedPins.has(field)); + if (!conflictingPins.length) return; + const edgeIndex = workflow.edges.indexOf(edge); + const alternatives = verifiedPinReferenceAlternatives([ + { edge, edgeIndex, field: 'route' }, + ...conflictingPins.map((field) => ({ edge, edgeIndex, field })), + ], 'and keep the remaining verified route assertions'); + throwExplicitPinConflict(edge, 'route preset compatibility', { + route: preset, + allowedPins: [...allowedPins], + conflictingPins: alternatives.conflictingPins, + }, alternatives.supportedFixes); +} + +function segmentOrientation(start, end) { + if (Math.abs(start[0] - end[0]) <= 0.0001) return 'vertical'; + if (Math.abs(start[1] - end[1]) <= 0.0001) return 'horizontal'; + return 'diagonal'; +} + +function routeSegments(points) { + return points.slice(0, -1).map((start, index) => ({ + start, + end: points[index + 1], + orientation: segmentOrientation(start, points[index + 1]), + })); +} + +function endpointSideIsHonored(points, side, endpoint) { + if (!side || side === 'auto' || points.length < 2) return true; + const source = endpoint === 'source'; + const from = source ? points[0] : points.at(-2); + const to = source ? points[1] : points.at(-1); + const dx = to[0] - from[0]; + const dy = to[1] - from[1]; + if (source) { + if (side === 'right') return dx > 0 && Math.abs(dy) <= 0.0001; + if (side === 'left') return dx < 0 && Math.abs(dy) <= 0.0001; + if (side === 'bottom') return dy > 0 && Math.abs(dx) <= 0.0001; + if (side === 'top') return dy < 0 && Math.abs(dx) <= 0.0001; + return false; + } + if (side === 'right') return dx < 0 && Math.abs(dy) <= 0.0001; + if (side === 'left') return dx > 0 && Math.abs(dy) <= 0.0001; + if (side === 'bottom') return dy < 0 && Math.abs(dx) <= 0.0001; + if (side === 'top') return dy > 0 && Math.abs(dx) <= 0.0001; + return false; +} + +function corridorTopologyMatches(points, axis, coordinate) { + const collapsed = normalizeRoutePoints(points.map((point) => [...point])); + const start = collapsed[0]; + const end = collapsed.at(-1); + const via = axis === 'x' + ? [[coordinate, start[1]], [coordinate, end[1]]] + : [[start[0], coordinate], [end[0], coordinate]]; + const expected = normalizeRoutePoints([start, ...via, end]); + const actualPattern = routeSegments(collapsed).map(({ orientation }) => orientation); + const expectedPattern = routeSegments(expected).map(({ orientation }) => orientation); + return actualPattern.length === expectedPattern.length + && actualPattern.every((orientation, index) => orientation === expectedPattern[index]) + && routeContainsChannelPin( + collapsed, + axis === 'x' ? 'channelX' : 'channelY', + coordinate, + ); +} + +function routeMatchesPresetFamily(preset, points, from, to) { + const collapsed = normalizeRoutePoints(points.map((point) => [...point])); + const segments = routeSegments(collapsed); + if (preset === 'straight') return collapsed.length === 2; + if (preset === 'drop') { + if (from.lane === to.lane) return false; + if (collapsed.length === 2 && segments[0]?.orientation === 'vertical') return true; + const upper = from.cy <= to.cy ? from : to; + const lower = upper === from ? to : from; + return segments.some(({ start, orientation }) => ( + orientation === 'horizontal' + && start[1] >= upper.y + upper.height - 0.0001 + && start[1] <= lower.y + 0.0001 + && corridorTopologyMatches(points, 'y', start[1]) + )); + } + if (preset === 'outside-right' || preset === 'return-left') { + const boundary = preset === 'outside-right' + ? Math.max(from.x + from.width, to.x + to.width) + : Math.min(from.x, to.x); + return segments.some(({ start, orientation }) => ( + orientation === 'vertical' + && (preset === 'outside-right' + ? start[0] > boundary + 0.0001 + : start[0] < boundary - 0.0001) + && corridorTopologyMatches(points, 'x', start[0]) + )); + } + if (preset === 'bottom-channel' || preset === 'up-channel') { + const boundary = preset === 'bottom-channel' + ? Math.max(from.y + from.height, to.y + to.height) + : Math.min(from.y, to.y); + return segments.some(({ start, orientation }) => ( + orientation === 'horizontal' + && (preset === 'bottom-channel' + ? start[1] > boundary + 0.0001 + : start[1] < boundary - 0.0001) + && corridorTopologyMatches(points, 'y', start[1]) + )); + } + return false; +} + +function routeContainsChannelPin(points, field, value) { + return points.slice(0, -1).some((start, index) => { + const end = points[index + 1]; + if (field === 'channelX') { + return start[0] === value + && end[0] === value + && Math.abs(end[1] - start[1]) > 0.0001; + } + return start[1] === value + && end[1] === value + && Math.abs(end[0] - start[0]) > 0.0001; + }); +} + +function validateReadablePinnedGeometry() { + if (workflow.schema_version !== 2) return; + for (const edge of workflow.edges) { + if (!nodes.has(edge.from) || !nodes.has(edge.to)) continue; + validateReadableRouteControls(edge); + const edgeName = workflowEdgeName(edge); + const edgeIndex = sourceIndexes.edges.get(edge); + if (Array.isArray(edge.labelAt)) { + const rect = labelRectFor(edge, workflow.edges.indexOf(edge)); + if (rect && (rect.x < 0 || rect.y < 0)) { + throwExplicitPinConflict(edge, 'viewBox-origin containment', { + conflictingPins: [{ + edge: edgeName, + field: 'labelAt', + path: `/edges/${edgeIndex}/labelAt`, + value: [...edge.labelAt], + }], + offendingRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, + minimumCoordinate: 0, + }, [verifiedLabelAtNudge(edge)]); + } + } + const negativeViaIndex = asArray(edge.via).findIndex(([x, y]) => x < 0 || y < 0); + const negativeRoutePin = negativeViaIndex >= 0 + ? { + field: 'via', + path: `/edges/${edgeIndex}/via/${negativeViaIndex}`, + value: [...edge.via[negativeViaIndex]], + } + : edge.channelX < 0 + ? { field: 'channelX', path: `/edges/${edgeIndex}/channelX`, value: edge.channelX } + : edge.channelY < 0 + ? { field: 'channelY', path: `/edges/${edgeIndex}/channelY`, value: edge.channelY } + : null; + if (negativeRoutePin) { + throwExplicitPinConflict(edge, 'viewBox-origin containment', { + conflictingPins: [{ edge: edgeName, ...negativeRoutePin }], + minimumCoordinate: 0, + }, [ + verifiedAutomaticRouteFix(edge), + verifiedAutomaticRouteFix(edge, { clearSides: true }), + ]); + } + const hasPinnedRoute = Array.isArray(edge.via) + || edge.channelX !== undefined + || edge.channelY !== undefined; + const points = pathFor(edge).points; + if (hasPinnedRoute) { + const invalidPointIndex = points.findIndex((point) => ( + !Array.isArray(point) || point.length !== 2 || !isFinitePoint(...point) + )); + if (invalidPointIndex !== -1) { + const alternatives = verifiedRouteGeometryPinAlternatives(edge); + throwExplicitPinConflict(edge, 'finite route coordinates', { + conflictingPins: alternatives.conflictingPins, + pointIndex: invalidPointIndex, + point: points[invalidPointIndex], + }, alternatives.supportedFixes); + } + for (let segmentIndex = 0; segmentIndex < points.length - 1; segmentIndex += 1) { + const start = points[segmentIndex]; + const end = points[segmentIndex + 1]; + const dx = Math.abs(end[0] - start[0]); + const dy = Math.abs(end[1] - start[1]); + if (dx <= 0.0001 && dy <= 0.0001) { + const duplicateFix = Array.isArray(edge.via) && edge.via.length + ? verifiedEdgeFix( + edge, + `remove duplicate via[${Math.min(segmentIndex, edge.via.length - 1)}] and keep the remaining authored pins unchanged`, + (candidate) => candidate.via.splice(Math.min(segmentIndex, candidate.via.length - 1), 1), + ) + : verifiedAutomaticRouteFix(edge); + const alternatives = Array.isArray(edge.via) + ? null + : verifiedRouteGeometryPinAlternatives(edge); + throwExplicitPinConflict(edge, 'non-zero route segments', { + conflictingPins: alternatives?.conflictingPins + || [authoredPinEvidence(edge, 'via')], + segmentIndex, + from: start, + to: end, + }, alternatives?.supportedFixes || [duplicateFix]); + } + if (dx > 0.0001 && dy > 0.0001) { + const alternatives = verifiedRouteGeometryPinAlternatives(edge); + throwExplicitPinConflict(edge, 'orthogonal route segments', { + conflictingPins: alternatives.conflictingPins, + segmentIndex, + from: start, + to: end, + }, alternatives.supportedFixes); + } + const endpoint = segmentIndex === 0 || segmentIndex === points.length - 2; + const minimumPx = points.length === 2 ? 28 : endpoint ? 8 : 16; + const lengthPx = dx + dy; + if (lengthPx + 0.0001 < minimumPx) { + const alternatives = verifiedRouteGeometryPinAlternatives(edge); + throwExplicitPinConflict(edge, endpoint ? '8px endpoint stub clearance' : '16px interior turn clearance', { + conflictingPins: alternatives.conflictingPins, + segmentIndex, + position: segmentIndex === 0 ? 'source-stub' : segmentIndex === points.length - 2 ? 'target-stub' : 'interior', + from: start, + to: end, + lengthPx, + minimumPx, + }, alternatives.supportedFixes); + } + } + const { fromSide, toSide } = edgeSides(edge); + if (Array.isArray(edge.via)) { + const missingChannelPins = presentChannelPins(edge).filter((field) => ( + !routeContainsChannelPin(points, field, edge[field]) + )); + if (missingChannelPins.length) { + const candidateFields = ['via', ...missingChannelPins]; + const alternatives = verifiedPinRemovalAlternatives( + edge, + candidateFields, + 'and replan the remaining explicit route assertions', + ); + throwExplicitPinConflict(edge, 'channel pin preservation', { + route: edge.route || 'auto', + conflictingPins: conflictPinsFromRemovalSets( + edge, + alternatives.removalSets, + candidateFields, + ), + points: points.map((point) => [...point]), + }, alternatives.supportedFixes); + } + } + if (edge.route + && edge.route !== 'auto' + && !routeMatchesPresetFamily( + edge.route, + points, + nodes.get(edge.from), + nodes.get(edge.to), + )) { + const authoredEdgeIndex = workflow.edges.indexOf(edge); + const alternatives = verifiedPinReferenceAlternatives([ + { edge, edgeIndex: authoredEdgeIndex, field: 'route' }, + ...presentRouteGeometryFields(edge) + .map((field) => ({ edge, edgeIndex: authoredEdgeIndex, field })), + ], 'and keep the remaining verified route assertions'); + throwExplicitPinConflict(edge, 'route preset compatibility', { + route: edge.route, + conflictingPins: alternatives.conflictingPins, + points: points.map((point) => [...point]), + }, alternatives.supportedFixes); + } + if (!routeHonorsEndpointSides(points, fromSide, toSide)) { + const mismatchedSideFields = [ + ...(edge.fromSide && edge.fromSide !== 'auto' + && !endpointSideIsHonored(points, fromSide, 'source') ? ['fromSide'] : []), + ...(edge.toSide && edge.toSide !== 'auto' + && !endpointSideIsHonored(points, toSide, 'target') ? ['toSide'] : []), + ]; + const candidateFields = [ + ...mismatchedSideFields, + ...presentRouteGeometryFields(edge), + ]; + const alternatives = verifiedPinRemovalAlternatives( + edge, + candidateFields, + 'and replan the remaining explicit pins', + ); + throwExplicitPinConflict(edge, 'perpendicular endpoint-side direction', { + conflictingPins: conflictPinsFromRemovalSets( + edge, + alternatives.removalSets, + candidateFields, + ), + points: points.map((point) => [...point]), + fromSide, + toSide, + }, alternatives.supportedFixes); + } + const nodeCollision = firstRouteNodeCollision(edge, points); + if (nodeCollision) { + const alternatives = verifiedRouteGeometryPinAlternatives(edge); + throwExplicitPinConflict(edge, 'node clearance', { + conflictingPins: alternatives.conflictingPins, + ...nodeCollision, + }, alternatives.supportedFixes); + } + const legendObstacle = workflowLegendRects().find((rect) => points.slice(0, -1).some((point, index) => ( + segmentIntersectsRect({ start: point, end: points[index + 1] }, rect) + ))); + if (legendObstacle) { + const alternatives = verifiedRouteGeometryPinAlternatives(edge); + throwExplicitPinConflict(edge, 'legend clearance', { + conflictingPins: alternatives.conflictingPins, + points: points.map((point) => [...point]), + legendObstacle, + }, alternatives.supportedFixes); + } + const compositionObstacle = workflowSceneLabelObstacles().find((rect) => ( + points.slice(0, -1).some((point, index) => ( + segmentIntersectsRect({ start: point, end: points[index + 1] }, rect) + )) + )); + if (compositionObstacle) { + const alternatives = verifiedRouteGeometryPinAlternatives(edge); + throwExplicitPinConflict(edge, 'lane/phase/group label clearance', { + conflictingPins: alternatives.conflictingPins, + points: points.map((point) => [...point]), + compositionObstacle, + }, alternatives.supportedFixes); + } + const [frameRun] = collectBorderRuns({ + routedRelations: [{ points }], + frames: workflowCompositionFrames(), + }); + if (frameRun) { + const alternatives = verifiedRouteGeometryPinAlternatives(edge); + throwExplicitPinConflict(edge, 'structural-frame border clearance', { + conflictingPins: alternatives.conflictingPins, + points: points.map((point) => [...point]), + frame: frameRun.frame?.id ?? frameRun.frameIndex, + side: frameRun.side, + overlapLengthPx: frameRun.overlapLength, + }, alternatives.supportedFixes); + } + } + + if (edge.labelAt) { + const rect = labelRectFor(edge, workflow.edges.indexOf(edge)); + const obstacle = rect && [...nodes.values()].find((node) => rectsOverlap(rect, node, -2)); + if (obstacle) { + throwExplicitPinConflict(edge, 'edge-label node clearance', { + conflictingPins: [authoredPinEvidence(edge, 'labelAt')], + labelAt: [...edge.labelAt], + labelRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, + obstacleNode: obstacle.id, + }, [verifiedEdgeFix( + edge, + 'remove labelAt so readable-v2 can use its verified automatic label placement', + (candidate) => { delete candidate.labelAt; }, + )]); + } + const legendObstacle = rect && workflowLegendRects().find((legendRect) => ( + rectsOverlap(rect, legendRect) + )); + if (legendObstacle) { + throwExplicitPinConflict(edge, 'edge-label legend clearance', { + conflictingPins: [authoredPinEvidence(edge, 'labelAt')], + labelAt: [...edge.labelAt], + labelRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, + legendObstacle, + }, [verifiedEdgeFix( + edge, + 'remove labelAt so readable-v2 can use its verified automatic label placement', + (candidate) => { delete candidate.labelAt; }, + )]); + } + const compositionObstacle = rect && workflowSceneLabelObstacles().find((candidate) => ( + rectsOverlap(rect, candidate) + )); + if (compositionObstacle) { + throwExplicitPinConflict(edge, 'edge-label lane/phase/group clearance', { + conflictingPins: [authoredPinEvidence(edge, 'labelAt')], + labelAt: [...edge.labelAt], + labelRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, + compositionObstacle, + }, [verifiedEdgeFix( + edge, + `remove labelAt from edge "${workflowEdgeName(edge)}" so readable-v2 can use its verified automatic label placement`, + (candidate) => { delete candidate.labelAt; }, + )]); + } + } + } + validateReadablePairwisePinConflicts(); +} + +function validateWorkflow() { + const problems = []; + if (workflow.schema_version !== 1 && workflow.schema_version !== 2) { + problems.push('Workflow files must set "schema_version" to 1 or 2.'); + } + if (workflow.diagram_type !== 'workflow') { + problems.push(`Unsupported diagram_type "${workflow.diagram_type}". Expected "workflow".`); + } + if (!workflow.meta || !workflow.meta.title) { + problems.push('Workflow files must include meta.title.'); + } + if (!Array.isArray(workflow.lanes) || !workflow.lanes.length) { + problems.push('Workflow files must include at least one lane.'); + } + if (!Array.isArray(workflow.nodes)) { + problems.push('Workflow files must include a nodes array.'); + } + if (!Array.isArray(workflow.edges)) { + problems.push('Workflow files must include an edges array.'); + } + if (workflow.phases !== undefined && !Array.isArray(workflow.phases)) { + problems.push('Workflow "phases" must be an array.'); + } + if (workflow.groups !== undefined && !Array.isArray(workflow.groups)) { + problems.push('Workflow "groups" must be an array.'); + } + if (workflow.mainPath !== undefined && !Array.isArray(workflow.mainPath)) { + problems.push('Workflow "mainPath" must be an array of node ids.'); + } + if (workflow.cards !== undefined && !Array.isArray(workflow.cards)) { + problems.push('Workflow "cards" must be an array.'); + } + if (problems.length) { + throwDiagnosticProblems('Workflow layout validation failed', problems, { + subject: { diagramType: 'workflow' }, + }); + } + + enforceLegacyColumnCapacity(); + + const laneIds = new Set(workflow.lanes.map((lane) => lane.id)); + if (laneIds.size !== workflow.lanes.length) { + problems.push('Lane ids must be unique.'); + } + if (nodes.size !== workflow.nodes.length) { + problems.push('Node ids must be unique.'); + } + const phaseIds = new Set(asArray(workflow.phases).map((phase) => phase.id)); + if (phaseIds.size !== asArray(workflow.phases).length) { + problems.push('Phase ids must be unique.'); + } + const groupIds = new Set(asArray(workflow.groups).map((group) => group.id)); + if (groupIds.size !== asArray(workflow.groups).length) { + problems.push('Group ids must be unique.'); + } + + for (const node of nodes.values()) { + if (!laneIds.has(node.lane)) { + problems.push(`Node "${node.id}" uses unknown lane "${node.lane}".`); + continue; + } + if (!Number.isInteger(node.col) || node.col < 0 || node.col >= layout.colXs.length) { + problems.push(`Node "${node.id}" uses column ${node.col}, but valid columns are integers 0..${layout.colXs.length - 1}.`); + continue; + } + if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) { + problems.push(`Node "${node.id}" produced non-finite coordinates — check col, width, height, and yOffset are numbers.`); + continue; + } + const estLabelW = textUnits(node.label) * 6.8; + if (estLabelW > node.width + 6) { + problems.push(`Label "${node.label}" (~${Math.round(estLabelW)}px) is wider than node "${node.id}" (${node.width}px) — shorten the label or increase node.width.`); + } + const brandRailProblem = brandTopRailProblem(node, node.width, nodeTextFit.labelMinimum); + if (brandRailProblem) problems.push(brandRailProblem); + const availableTextW = availableNodeTextWidth(node.width); + for (const [field, value, minimum] of [ + ['Sublabel', node.sublabel, nodeTextFit.sublabelMinimum], + ['Tag', node.tag, nodeTextFit.tagMinimum], + ]) { + if (!value) continue; + const minimumW = minimumNodeTextWidth(value, minimum); + if (minimumW > availableTextW) { + problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but node "${node.id}" provides ${availableTextW}px — shorten the ${field.toLowerCase()} or increase node.width.`); + } + } + + const top = laneTop(node.lane); + const contentTop = top + layout.laneTitleH + laneGroupHeaderH(node.lane); + const laneRight = layout.laneX + layout.laneW; + if (node.x < layout.laneX || node.x + node.width > laneRight) { + problems.push(`Node "${node.id}" exceeds the horizontal bounds of lane "${node.lane}".`); + } + if (node.y < contentTop || node.y + node.height > top + laneHeight(node.lane)) { + problems.push(`Node "${node.id}" collides with the title or boundary of lane "${node.lane}".`); + } + } + + const phaseRanges = []; + for (const phase of asArray(workflow.phases)) { + if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol)) { + problems.push(`Phase "${phase.id}" must use integer fromCol/toCol values.`); + continue; + } + if (phase.fromCol < 0 || phase.toCol >= layout.colXs.length || phase.fromCol > phase.toCol) { + problems.push(`Phase "${phase.id}" uses invalid columns ${phase.fromCol}..${phase.toCol}; use an ordered range within 0..${layout.colXs.length - 1}.`); + } else { + phaseRanges.push(phase); + } + const estLabelW = textUnits(phase.label) * 5.6; + const width = phaseSpan(phase).width; + if (estLabelW > width + 8) { + problems.push(`Phase label "${phase.label}" (~${Math.round(estLabelW)}px) is wider than its ${Math.round(width)}px span — shorten the label or widen the phase range.`); + } + } + phaseRanges.sort((a, b) => a.fromCol - b.fromCol || a.toCol - b.toCol); + for (let i = 0; i < phaseRanges.length; i += 1) { + for (let j = i + 1; j < phaseRanges.length; j += 1) { + const earlier = phaseRanges[i]; + const later = phaseRanges[j]; + if (later.fromCol > earlier.toCol) break; + problems.push(`Phase "${later.id}" (${later.fromCol}..${later.toCol}) overlaps phase "${earlier.id}" (${earlier.fromCol}..${earlier.toCol}) — start at col ${earlier.toCol + 1} or later, or end the earlier phase at col ${later.fromCol - 1}.`); + } + } + + for (const group of asArray(workflow.groups)) { + if (!laneIds.has(group.lane)) { + problems.push(`Group "${group.id}" uses unknown lane "${group.lane}".`); + continue; + } + if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)) { + problems.push(`Group "${group.id}" must use integer fromCol/toCol values.`); + continue; + } + if (group.fromCol < 0 || group.toCol >= layout.colXs.length || group.fromCol > group.toCol) { + problems.push(`Group "${group.id}" uses invalid columns ${group.fromCol}..${group.toCol}; use an ordered range within 0..${layout.colXs.length - 1}.`); + } + const contained = [...nodes.values()].some((node) => node.lane === group.lane && node.col >= group.fromCol && node.col <= group.toCol); + if (!contained) { + problems.push(`Group "${group.id}" does not contain any nodes — align its lane/columns with the parallel or branch work it frames.`); + } + } + + const byLane = new Map(); + for (const node of nodes.values()) { + byLane.set(node.lane, [...(byLane.get(node.lane) || []), node]); + } + for (const [lane, laneNodes] of byLane) { + for (let i = 0; i < laneNodes.length; i += 1) { + for (let j = i + 1; j < laneNodes.length; j += 1) { + if (rectsOverlap(laneNodes[i], laneNodes[j], 8)) { + problems.push(`Nodes "${laneNodes[i].id}" and "${laneNodes[j].id}" are less than 8px apart in lane "${lane}" — move one to another col, adjust yOffset, or reduce width/height.`); + } + } + } + } + + for (const edge of workflow.edges) { + if (!nodes.has(edge.from)) problems.push(`Edge "${edge.label || edge.from}" references unknown source "${edge.from}".`); + if (!nodes.has(edge.to)) problems.push(`Edge "${edge.label || edge.to}" references unknown target "${edge.to}".`); + if (nodes.has(edge.from) && nodes.has(edge.to)) { + const routed = pathFor(edge); + if (routed.points.length === 2) { + const [start, end] = routed.points; + const segmentLength = Math.hypot(end[0] - start[0], end[1] - start[1]); + if (segmentLength < 28) { + problems.push(`Edge "${edge.from}" -> "${edge.to}" is too short (${Math.round(segmentLength)}px; minimum 28px) — move the nodes farther apart or use a verified orthogonal route with readable clearance.`); + } + } + } + } + + problems.push(...cleanEndpointSideProblems({ + relations: workflow.edges, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'workflow', + relationCollection: 'edges', + fromSideFor: (edge) => edgeSides(edge).fromSide, + toSideFor: (edge) => edgeSides(edge).toSide, + routeHint: 'keep automatic routing, or choose fromSide/toSide and via points whose first and final segments cross node borders perpendicularly', + })); + problems.push(...cleanFlowProblems({ + relations: workflow.edges, + obstacles: nodes.values(), + pathFor, + diagramType: 'workflow', + relationCollection: 'edges', + obstacleKind: 'node', + routeHint: 'adjust fromSide/toSide, set route/via or channel coordinates, or move the node to a clearer lane/column' + })); + problems.push(...cleanCrossingProblems({ + relations: workflow.edges, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'workflow', + relationCollection: 'edges', + profile: workflow.meta?.quality_profile, + profileIsAuthoritative: true, + mergeForwardCollinearWaypoints: workflow.schema_version === 2, + routeHint: 'adjust route/via, bias, or channel coordinates so the edges use separate lane corridors' + })); + problems.push(...cleanAmbiguousCorridorProblems({ + relations: workflow.edges, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'workflow', + relationCollection: 'edges', + profile: workflow.meta?.quality_profile, + profileIsAuthoritative: true, + routeHint: 'adjust route/via, bias, or channel coordinates so unrelated edges do not visually merge' + })); + problems.push(...cleanBorderRunProblems({ + relations: workflow.edges, + endpointIds: new Set(nodes.keys()), + frames: workflowCompositionFrames(), + pathFor, + diagramType: 'workflow', + relationCollection: 'edges', + profile: workflow.meta?.quality_profile, + profileIsAuthoritative: true, + routeHint: 'adjust route/via, bias, or channel coordinates so the edge crosses the lane or group perpendicularly instead of following its border' + })); + problems.push(...cleanRouteRhythmProblems({ + relations: workflow.edges, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'workflow', + relationCollection: 'edges', + profile: workflow.meta?.quality_profile, + profileIsAuthoritative: true, + routeHint: 'adjust route/via, bias, or channel coordinates so each turn has a readable run-up' + })); + + if (Array.isArray(workflow.mainPath)) { + for (const id of workflow.mainPath) { + if (!nodes.has(id)) { + problems.push(`mainPath references unknown node "${id}".`); + } + } + for (let i = 0; i < workflow.mainPath.length - 1; i += 1) { + const fromId = workflow.mainPath[i]; + const toId = workflow.mainPath[i + 1]; + const from = nodes.get(fromId); + const to = nodes.get(toId); + if (!from || !to) continue; + const linked = workflow.edges.some((edge) => edge.from === fromId && edge.to === toId); + if (!linked) { + problems.push(`mainPath step "${fromId}" -> "${toId}" has no matching edge — add the edge or remove the pair from mainPath.`); + } + if (to.col < from.col) { + problems.push(`mainPath step "${fromId}" -> "${toId}" moves backward from col ${from.col} to ${to.col} — use a return edge outside mainPath for loops.`); + } + } + } + + const labelRects = []; + for (const [edgeIndex, edge] of workflow.edges.entries()) { + const labelRect = labelRectFor(edge, edgeIndex); + if (labelRect) labelRects.push(labelRect); + } + for (const rect of labelRects) { + for (const node of nodes.values()) { + if (rectsOverlap(rect, node, -2)) { + problems.push(`Label "${rect.label}" overlaps node "${node.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, node, 'node')}`); + } + } + } + for (let i = 0; i < labelRects.length; i += 1) { + for (let j = i + 1; j < labelRects.length; j += 1) { + if (rectsOverlap(labelRects[i], labelRects[j], -2)) { + problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy/labelSegment or route one relationship through a separate corridor.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}`); + } + } + } + problems.push(...cleanLabelRouteClearanceProblems({ + relations: workflow.edges, + labels: labelRects, + endpointIds: new Set(nodes.keys()), + pathFor, + diagramType: 'workflow', + relationCollection: 'edges', + profile: workflow.meta?.quality_profile, + profileIsAuthoritative: true, + })); + + if (workflow.schema_version === 1) { + if (viewBox[0] < layout.laneX + layout.laneW + 16) { + problems.push(`viewBox width ${viewBox[0]} clips the ${layout.laneW}px lanes — set meta.viewBox[0] to at least ${layout.laneX + layout.laneW + 16}.`); + } + if (legendY() + 18 > viewBox[1]) { + problems.push(`Legend exceeds viewBox height ${viewBox[1]} — set meta.viewBox[1] to at least ${legendY() + 18}.`); + } + } + + if (problems.length) { + throwDiagnosticProblems('Workflow layout validation failed', problems, { + subject: { diagramType: 'workflow' }, + }); + } +} + +function validateReadableInputsBeforeRouting() { + if (workflow.schema_version !== 2) return; + const fail = (diagnostic) => throwDiagnosticError(diagnostic.message, [diagnostic]); + const unusedId = (base, used) => { + for (let suffix = 2; ; suffix += 1) { + const candidate = `${base}-${suffix}`; + if (!used.has(candidate)) return candidate; + } + }; + const authoredLanes = [...workflow.lanes].sort((left, right) => ( + sourceIndexes.lanes.get(left) - sourceIndexes.lanes.get(right) + )); + const authoredNodes = [...workflow.nodes].sort((left, right) => ( + sourceIndexes.nodes.get(left) - sourceIndexes.nodes.get(right) + )); + const authoredEdges = [...workflow.edges].sort((left, right) => ( + sourceIndexes.edges.get(left) - sourceIndexes.edges.get(right) + )); + + const firstLaneIndex = new Map(); + for (const lane of authoredLanes) { + const laneIndex = sourceIndexes.lanes.get(lane); + if (firstLaneIndex.has(lane.id)) { + const message = `Workflow lane id "${lane.id}" is duplicated.`; + const replacement = unusedId(lane.id, new Set(workflow.lanes.map(({ id }) => id))); + const canonicalLaneIndex = workflow.lanes.indexOf(lane); + const supportedFixes = acceptsFix((document) => { + document.lanes[canonicalLaneIndex].id = replacement; + }) ? [`rename /lanes/${laneIndex}/id to verified unique id "${replacement}"`] : []; + fail({ + code: 'workflow/duplicate-lane-id', + severity: 'error', + message, + subject: { diagramType: 'workflow', lane: lane.id, path: `/lanes/${laneIndex}/id` }, + evidence: { + duplicateLaneId: lane.id, + firstPath: `/lanes/${firstLaneIndex.get(lane.id)}/id`, + duplicatePath: `/lanes/${laneIndex}/id`, + }, + supportedFixes, + }); + } + firstLaneIndex.set(lane.id, laneIndex); + } + + const firstNodeIndex = new Map(); + for (const node of authoredNodes) { + const nodeIndex = sourceIndexes.nodes.get(node); + if (firstNodeIndex.has(node.id)) { + const message = `Workflow node id "${node.id}" is duplicated.`; + const replacement = unusedId(node.id, new Set(workflow.nodes.map(({ id }) => id))); + const canonicalNodeIndex = workflow.nodes.indexOf(node); + const supportedFixes = acceptsFix((document) => { + document.nodes[canonicalNodeIndex].id = replacement; + }) ? [`rename /nodes/${nodeIndex}/id to verified unique id "${replacement}"`] : []; + fail({ + code: 'workflow/duplicate-node-id', + severity: 'error', + message, + subject: { diagramType: 'workflow', node: node.id, path: `/nodes/${nodeIndex}/id` }, + evidence: { + duplicateNodeId: node.id, + firstPath: `/nodes/${firstNodeIndex.get(node.id)}/id`, + duplicatePath: `/nodes/${nodeIndex}/id`, + }, + supportedFixes, + }); + } + firstNodeIndex.set(node.id, nodeIndex); + } + + const availableNodeIds = [...nodes.keys()].sort(stableCompare); + for (const edge of authoredEdges) { + const edgeIndex = sourceIndexes.edges.get(edge); + for (const [field, endpoint] of [['from', 'source'], ['to', 'target']]) { + if (nodes.has(edge[field])) continue; + const message = `Workflow edge "${workflowEdgeName(edge)}" references unknown ${endpoint} "${edge[field]}".`; + const canonicalEdgeIndex = workflow.edges.indexOf(edge); + const supportedFixes = availableNodeIds.flatMap((nodeId) => ( + acceptsFix((document) => { + document.edges[canonicalEdgeIndex][field] = nodeId; + }) + ? [`set /edges/${edgeIndex}/${field} to verified node id "${nodeId}"`] + : [] + )); + fail({ + code: 'workflow/unknown-edge-endpoint', + severity: 'error', + message, + subject: { + diagramType: 'workflow', + edge: edge.id ?? null, + path: `/edges/${edgeIndex}/${field}`, + from: edge.from, + to: edge.to, + }, + evidence: { + endpoint, + unknownNodeId: edge[field], + availableNodeIds, + }, + supportedFixes, + }); + } + } + const laneIds = new Set(workflow.lanes.map((lane) => lane.id)); + const availableLaneIds = [...laneIds].sort(stableCompare); + const nodeSourceIndexes = new Map(authoredNodes.map((node) => [ + node.id, + sourceIndexes.nodes.get(node), + ])); + + const byLane = new Map(); + for (const authoredNode of authoredNodes) { + const nodeIndex = sourceIndexes.nodes.get(authoredNode); + const node = nodes.get(authoredNode.id); + if (!laneIds.has(node.lane)) { + const message = `Workflow node "${node.id}" uses unknown lane "${node.lane}".`; + const canonicalNodeIndex = workflow.nodes.findIndex((candidate) => candidate.id === node.id); + const supportedFixes = availableLaneIds.flatMap((laneId) => ( + acceptsFix((document) => { + document.nodes[canonicalNodeIndex].lane = laneId; + }) + ? [`set /nodes/${nodeIndex}/lane to verified lane id "${laneId}"`] + : [] + )); + fail({ + code: 'workflow/unknown-node-lane', + severity: 'error', + message, + subject: { diagramType: 'workflow', node: node.id, path: `/nodes/${nodeIndex}/lane` }, + evidence: { unknownLaneId: node.lane, availableLaneIds }, + supportedFixes, + }); + } + if (!Number.isInteger(node.col) || node.col < 0 || node.col >= layout.colXs.length) { + const message = `Workflow node "${node.id}" uses column ${node.col}, but valid columns are integers 0..${layout.colXs.length - 1}.`; + const canonicalNodeIndex = workflow.nodes.findIndex((candidate) => candidate.id === node.id); + const supportedFixes = layout.colXs.flatMap((_x, col) => ( + acceptsFix((document) => { + document.nodes[canonicalNodeIndex].col = col; + }) + ? [`set /nodes/${nodeIndex}/col to verified column ${col}`] + : [] + )); + fail({ + code: 'workflow/invalid-node-column', + severity: 'error', + message, + subject: { diagramType: 'workflow', node: node.id, path: `/nodes/${nodeIndex}/col` }, + evidence: { actualColumn: node.col, minimumColumn: 0, maximumColumn: layout.colXs.length - 1 }, + supportedFixes, + }); + } + if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) { + const message = `Workflow node "${node.id}" produced non-finite coordinates.`; + fail({ + code: 'workflow/non-finite-node-geometry', + severity: 'error', + message, + subject: { diagramType: 'workflow', node: node.id, path: `/nodes/${nodeIndex}` }, + evidence: { + measuredRect: { x: node.x, y: node.y, width: node.width, height: node.height }, + authored: { + col: authoredNode.col, + width: authoredNode.width ?? null, + height: authoredNode.height ?? null, + yOffset: authoredNode.yOffset ?? null, + }, + }, + supportedFixes: [], + }); + } + byLane.set(node.lane, [...(byLane.get(node.lane) || []), node]); + } + for (const [lane, laneNodes] of byLane) { + for (let left = 0; left < laneNodes.length; left += 1) { + for (let right = left + 1; right < laneNodes.length; right += 1) { + if (rectsOverlap(laneNodes[left], laneNodes[right], 8)) { + const leftNode = laneNodes[left]; + const rightNode = laneNodes[right]; + const rightIndex = nodeSourceIndexes.get(rightNode.id); + const canonicalNodeIndex = workflow.nodes.findIndex((candidate) => ( + candidate.id === rightNode.id + )); + const supportedFixes = layout.colXs.flatMap((_x, col) => { + if (col === rightNode.col) return []; + return acceptsFix((document) => { + document.nodes[canonicalNodeIndex].col = col; + }) + ? [`set /nodes/${rightIndex}/col to verified free column ${col}`] + : []; + }); + const message = `Workflow nodes "${leftNode.id}" and "${rightNode.id}" are less than 8px apart in lane "${lane}".`; + fail({ + code: 'workflow/node-overlap', + severity: 'error', + message, + subject: { diagramType: 'workflow', node: rightNode.id, path: `/nodes/${rightIndex}` }, + evidence: { + lane, + minimumClearancePx: 8, + nodes: [ + { id: leftNode.id, rect: { x: leftNode.x, y: leftNode.y, width: leftNode.width, height: leftNode.height } }, + { id: rightNode.id, rect: { x: rightNode.x, y: rightNode.y, width: rightNode.width, height: rightNode.height } }, + ], + }, + supportedFixes, + }); + } + } + } + } +} + +function gapYBetween(fromLane, toLane, bias = 0.5) { + const a = laneTop(fromLane) + laneHeight(fromLane); + const b = laneTop(toLane); + return a + (b - a) * bias; +} + +function spanForCols(fromCol, toCol, pad = 46, minimumWidth = 0) { + const start = layout.colXs[fromCol] - pad; + const end = layout.colXs[toCol] + pad; + const width = Math.max(end - start, minimumWidth); + if (fromCol === toCol && width > end - start) { + return { x: start, width, cx: start + width / 2 }; + } + const cx = (start + end) / 2; + return { x: cx - width / 2, width, cx }; +} + +function phaseSpan(phase) { + return spanForCols( + phase.fromCol, + phase.toCol, + 46, + workflow.schema_version === 2 ? textUnits(phase.label) * 5.6 + 8 : 0, + ); +} + +function groupSpan(group) { + if (workflow.schema_version === 2) { + return readableGroupBounds(workflow, group, layout.colXs); + } + return spanForCols( + group.fromCol, + group.toCol, + 50, + 0, + ); +} + +function sameLaneAutoVia(start, end) { + if (start[0] === end[0] || start[1] === end[1]) return []; + const midX = (start[0] + end[0]) / 2; + return [[midX, start[1]], [midX, end[1]]]; +} + +function routeClearsUnrelatedNodes(edge, points, clearance = 2) { + const endpointIds = new Set([edge.from, edge.to]); + for (const node of nodes.values()) { + if (endpointIds.has(node.id)) continue; + for (let index = 0; index < points.length - 1; index += 1) { + if (segmentIntersectsRect({ start: points[index], end: points[index + 1] }, node, clearance)) { + return false; + } + } + } + return true; +} + +function firstRouteNodeCollision(edge, points) { + const lastSegment = points.length - 2; + for (const node of nodes.values()) { + const endpointRole = node.id === edge.from + ? 'source-endpoint' + : node.id === edge.to ? 'target-endpoint' : 'unrelated'; + for (let segmentIndex = 0; segmentIndex <= lastSegment; segmentIndex += 1) { + if (endpointRole === 'source-endpoint' && segmentIndex === 0) continue; + if (endpointRole === 'target-endpoint' && segmentIndex === lastSegment) continue; + const clearancePx = endpointRole === 'unrelated' ? 2 : 0; + const from = points[segmentIndex]; + const to = points[segmentIndex + 1]; + if (segmentIntersectsRect({ start: from, end: to }, node, clearancePx)) { + return { + obstacleNode: node.id, + obstacleRole: endpointRole, + segmentIndex, + from: [...from], + to: [...to], + clearancePx, + }; + } + } + } + return null; +} + +function oneBendCrossLaneVia(edge, start, end, fromSide, toSide) { + const fromVertical = fromSide === 'top' || fromSide === 'bottom'; + const toVertical = toSide === 'top' || toSide === 'bottom'; + if (fromVertical === toVertical) return null; + + const corner = fromVertical ? [start[0], end[1]] : [end[0], start[1]]; + const points = normalizeRoutePoints([start, corner, end]); + if (points.length !== 3 || !routeHonorsEndpointSides(points, fromSide, toSide)) return null; + + const segmentsAreReadable = points.slice(0, -1).every((point, index) => ( + Math.hypot( + points[index + 1][0] - point[0], + points[index + 1][1] - point[1], + ) >= 8 + )); + if (!segmentsAreReadable || !routeClearsUnrelatedNodes(edge, points)) return null; + return points.slice(1, -1); +} + +const pathCache = new Map(); +const readableSideCache = new Map(); + +function legacyAutomaticOneBendSides(edge, from, to) { + const automaticRoute = !edge.via && (!edge.route || edge.route === 'auto'); + const automaticFrom = !edge.fromSide || edge.fromSide === 'auto'; + const automaticTo = !edge.toSide || edge.toSide === 'auto'; + if (!automaticRoute || !automaticFrom || !automaticTo || from.lane === to.lane) return null; + if (from.cx === to.cx || from.cy === to.cy) return null; + const verticalFrom = to.cy < from.cy ? 'top' : 'bottom'; + const horizontalTo = to.cx < from.cx ? 'right' : 'left'; + const horizontalFrom = to.cx < from.cx ? 'left' : 'right'; + const verticalTo = to.cy < from.cy ? 'bottom' : 'top'; + const candidates = [ + { fromSide: verticalFrom, toSide: horizontalTo }, + { fromSide: horizontalFrom, toSide: verticalTo }, + ]; + + return candidates.find(({ fromSide, toSide }) => { + const start = anchor(from, fromSide); + const end = anchor(to, toSide); + return oneBendCrossLaneVia(edge, start, end, fromSide, toSide); + }) || null; +} + +function readableAutomaticSides(edge, from, to) { + const automaticRoute = !edge.via + && edge.channelX === undefined + && edge.channelY === undefined + && (!edge.route || edge.route === 'auto'); + const authoredFrom = edge.fromSide && edge.fromSide !== 'auto' ? edge.fromSide : null; + const authoredTo = edge.toSide && edge.toSide !== 'auto' ? edge.toSide : null; + if (!automaticRoute || (authoredFrom && authoredTo)) return null; + if (readableSideCache.has(edge)) return readableSideCache.get(edge); + + const preferred = []; + const legacyPreferred = legacyAutomaticOneBendSides(edge, from, to); + if (legacyPreferred) preferred.push(legacyPreferred); + preferred.push({ + fromSide: authoredFrom || defaultFromSide(from, to), + toSide: authoredTo || defaultToSide(from, to), + }); + const sideOrder = ['right', 'bottom', 'left', 'top']; + for (const fromSide of authoredFrom ? [authoredFrom] : sideOrder) { + for (const toSide of authoredTo ? [authoredTo] : sideOrder) { + preferred.push({ fromSide, toSide }); + } + } + + const seen = new Set(); + const sidePairs = []; + for (const candidate of preferred) { + if (authoredFrom && candidate.fromSide !== authoredFrom) continue; + if (authoredTo && candidate.toSide !== authoredTo) continue; + const key = `${candidate.fromSide}:${candidate.toSide}`; + if (seen.has(key)) continue; + seen.add(key); + sidePairs.push(candidate); + } + + const naturalFromSide = authoredFrom || defaultFromSide(from, to); + const naturalToSide = authoredTo || defaultToSide(from, to); + const planFor = (candidate, pairOrdinal) => { + const start = anchor(from, candidate.fromSide); + const end = anchor(to, candidate.toSide); + return { + start, + end, + planned: readableAutomaticCandidateSet( + edge, + from, + to, + start, + end, + candidate.fromSide, + candidate.toSide, + { + ordinalOffset: pairOrdinal * 9, + naturalFromSide, + naturalToSide, + }, + ), + }; + }; + + const primary = sidePairs[0]; + if (primary) { + const { planned } = planFor(primary, 0); + if (planned.candidates.length) { + readableSideCache.set(edge, primary); + return primary; + } + } + + const candidates = []; + for (const [pairOrdinal, candidate] of sidePairs.entries()) { + const { planned } = planFor(candidate, pairOrdinal); + candidates.push(...planned.candidates.map((route) => ({ ...route, ...candidate }))); + } + candidates.sort((left, right) => compareCost(left.cost, right.cost)); + if (candidates.length) { + const selected = { + fromSide: candidates[0].fromSide, + toSide: candidates[0].toSide, + }; + readableSideCache.set(edge, selected); + return selected; + } + readableSideCache.set(edge, null); + return null; +} + +function automaticOneBendSides(edge, from, to) { + return workflow.schema_version === 2 + ? readableAutomaticSides(edge, from, to) + : legacyAutomaticOneBendSides(edge, from, to); +} + +const OUTWARD_SIDE_VECTOR = Object.freeze({ + left: [-1, 0], + right: [1, 0], + top: [0, -1], + bottom: [0, 1], +}); + +function outwardStub(point, side, distance = 16) { + const [dx, dy] = OUTWARD_SIDE_VECTOR[side] || [0, 0]; + return [point[0] + dx * distance, point[1] + dy * distance]; +} + +function orthogonalRoute(points) { + return points.every((point, index) => { + if (!Array.isArray(point) || point.length !== 2 || !isFinitePoint(...point)) return false; + if (index === 0) return true; + const previous = points[index - 1]; + const dx = Math.abs(point[0] - previous[0]); + const dy = Math.abs(point[1] - previous[1]); + return (dx <= 0.0001) !== (dy <= 0.0001); + }); +} + +function routeClearsEndpointNodes(points, from, to) { + const lastSegment = points.length - 2; + for (let index = 0; index <= lastSegment; index += 1) { + const segment = { start: points[index], end: points[index + 1] }; + if (index > 0 && segmentIntersectsRect(segment, from)) return false; + if (index < lastSegment && segmentIntersectsRect(segment, to)) return false; + } + return true; +} + +function routeMeetsHardRhythm(points) { + if (points.length === 2) { + return Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]) + 0.0001 >= 28; + } + return points.slice(0, -1).every((point, index) => { + const length = Math.abs(points[index + 1][0] - point[0]) + Math.abs(points[index + 1][1] - point[1]); + const endpoint = index === 0 || index === points.length - 2; + return length + 0.0001 >= (endpoint ? 8 : 16); + }); +} + +function routeLabelClearsNodes(edge, points) { + if (!edge.label || edge.labelAt) return true; + const [lx, ly] = workflowEdgeLabelPoint(edge, points); + const rect = { + x: lx - workflowLabelWidth(edge.label) / 2, + y: ly - 10, + width: workflowLabelWidth(edge.label), + height: 14, + }; + return [...nodes.values()].every((node) => !rectsOverlap(rect, node, -2)); +} + +function candidateLabelRect(edge, points) { + if (!edge.label) return null; + const [lx, ly] = workflowEdgeLabelPoint(edge, points); + const width = workflowLabelWidth(edge.label); + return { x: lx - width / 2, y: ly - 10, width, height: 14 }; +} + +function labelRouteClearanceDeficit(edge, points, threshold = 8) { + const candidateLabel = candidateLabelRect(edge, points); + let deficit = 0; + for (const [otherEdge, routed] of pathCache) { + const otherIndex = workflow.edges.indexOf(otherEdge); + const otherLabel = labelRectFor(otherEdge, otherIndex); + if (candidateLabel) { + for (let index = 0; index < routed.points.length - 1; index += 1) { + const clearance = segmentRectClearance({ + start: routed.points[index], + end: routed.points[index + 1], + }, candidateLabel); + if (clearance != null) deficit += Math.max(0, threshold - clearance); + } + } + if (otherLabel) { + for (let index = 0; index < points.length - 1; index += 1) { + const clearance = segmentRectClearance({ + start: points[index], + end: points[index + 1], + }, otherLabel); + if (clearance != null) deficit += Math.max(0, threshold - clearance); + } + } + } + return deficit; +} + +function routeClearsPlacedLabels(edge, points) { + const candidateLabel = candidateLabelRect(edge, points); + for (const [otherEdge, routed] of pathCache) { + const otherIndex = workflow.edges.indexOf(otherEdge); + const otherLabel = labelRectFor(otherEdge, otherIndex); + if (candidateLabel && otherLabel && rectsOverlap(candidateLabel, otherLabel, -2)) return false; + if (candidateLabel) { + for (let index = 0; index < routed.points.length - 1; index += 1) { + const clearance = segmentRectClearance({ + start: routed.points[index], + end: routed.points[index + 1], + }, candidateLabel); + if (clearance != null && clearance + 0.0001 < 4) return false; + } + } + if (otherLabel) { + for (let index = 0; index < points.length - 1; index += 1) { + const clearance = segmentRectClearance({ + start: points[index], + end: points[index + 1], + }, otherLabel); + if (clearance != null && clearance + 0.0001 < 4) return false; + } + } + } + return true; +} + +function routeClearsLegend(edge, points) { + if (!workflowLegendEntries.length) return true; + const legendRects = workflowLegendRects(); + for (const rect of legendRects) { + for (let index = 0; index < points.length - 1; index += 1) { + if (segmentIntersectsRect({ start: points[index], end: points[index + 1] }, rect)) return false; + } + const label = candidateLabelRect(edge, points); + if (label && rectsOverlap(label, rect)) return false; + } + return true; +} + +function routeClearsSceneLabelObstacles(edge, points) { + const label = candidateLabelRect(edge, points); + for (const obstacle of workflowSceneLabelObstacles()) { + for (let index = 0; index < points.length - 1; index += 1) { + if (segmentIntersectsRect({ start: points[index], end: points[index + 1] }, obstacle)) { + return false; + } + } + if (label && rectsOverlap(label, obstacle)) return false; + } + return true; +} + +function routeClearsFrameBorders(points) { + return collectBorderRuns({ + routedRelations: [{ points }], + frames: workflowCompositionFrames(), + }).length === 0; +} + +function routeExtentCoordinates(edge, points) { + const coordinates = [...points]; + if (!edge.labelAt) { + const label = candidateLabelRect(edge, points); + if (label) { + coordinates.push([label.x, label.y], [label.x + label.width, label.y + label.height]); + } + } + return coordinates; +} + +function routeFitsCanvasOrigin(edge, points) { + return routeExtentCoordinates(edge, points).every(([x, y]) => x >= 0 && y >= 0); +} + +function readableCandidateIsFeasible(edge, points, from, to, fromSide, toSide) { + return points.length >= 2 + && orthogonalRoute(points) + && routeHonorsEndpointSides(points, fromSide, toSide) + && routeMeetsHardRhythm(points) + && routeClearsEndpointNodes(points, from, to) + && routeClearsUnrelatedNodes(edge, points) + && routeLabelClearsNodes(edge, points) + && routeClearsPlacedLabels(edge, points) + && routeClearsLegend(edge, points) + && routeClearsSceneLabelObstacles(edge, points) + && routeClearsFrameBorders(points) + && routeFitsCanvasOrigin(edge, points); +} + +function corridorViaY(start, end, fromSide, toSide, y) { + const startStub = outwardStub(start, fromSide); + const endStub = outwardStub(end, toSide); + return [startStub, [startStub[0], y], [endStub[0], y], endStub]; +} + +function corridorViaX(start, end, fromSide, toSide, x) { + const startStub = outwardStub(start, fromSide); + const endStub = outwardStub(end, toSide); + return [startStub, [x, startStub[1]], [x, endStub[1]], endStub]; +} + +function axisOverlapLength(a, b, c, d) { + const horizontal = Math.abs(a[1] - b[1]) <= 0.0001 + && Math.abs(c[1] - d[1]) <= 0.0001 + && Math.abs(a[1] - c[1]) <= 0.0001; + const vertical = Math.abs(a[0] - b[0]) <= 0.0001 + && Math.abs(c[0] - d[0]) <= 0.0001 + && Math.abs(a[0] - c[0]) <= 0.0001; + if (!horizontal && !vertical) return 0; + const axis = horizontal ? 0 : 1; + return Math.max(0, Math.min(Math.max(a[axis], b[axis]), Math.max(c[axis], d[axis])) + - Math.max(Math.min(a[axis], b[axis]), Math.min(c[axis], d[axis]))); +} + +function properAxisCrossing(a, b, c, d) { + const firstHorizontal = Math.abs(a[1] - b[1]) <= 0.0001; + const secondHorizontal = Math.abs(c[1] - d[1]) <= 0.0001; + if (firstHorizontal === secondHorizontal) return false; + const horizontal = firstHorizontal ? [a, b] : [c, d]; + const vertical = firstHorizontal ? [c, d] : [a, b]; + const x = vertical[0][0]; + const y = horizontal[0][1]; + return x > Math.min(horizontal[0][0], horizontal[1][0]) + 0.0001 + && x < Math.max(horizontal[0][0], horizontal[1][0]) - 0.0001 + && y > Math.min(vertical[0][1], vertical[1][1]) + 0.0001 + && y < Math.max(vertical[0][1], vertical[1][1]) - 0.0001; +} + +function routeInteractionMetrics(edge, points) { + let properCrossingCount = 0; + let sharedCorridorPx = 0; + for (const [otherEdge, routed] of pathCache) { + if ([edge.from, edge.to].some((id) => id === otherEdge.from || id === otherEdge.to)) continue; + for (let left = 0; left < points.length - 1; left += 1) { + for (let right = 0; right < routed.points.length - 1; right += 1) { + if (properAxisCrossing(points[left], points[left + 1], routed.points[right], routed.points[right + 1])) { + properCrossingCount += 1; + } + sharedCorridorPx += axisOverlapLength( + points[left], points[left + 1], routed.points[right], routed.points[right + 1], + ); + } + } + } + return { properCrossingCount, sharedCorridorPx }; +} + +function automaticForwardReversePx(edge, points) { + const from = nodes.get(edge.from); + const to = nodes.get(edge.to); + if (!from || !to || ['return', 'error'].includes(edge.role) || to.col <= from.col) return 0; + return points.slice(0, -1).reduce((total, point, index) => ( + total + Math.max(0, point[0] - points[index + 1][0]) + ), 0); +} + +function readableCandidateCost( + edge, + points, + ordinal, + naturalFromSide, + naturalToSide, +) { + const interaction = routeInteractionMetrics(edge, points); + const segmentLengths = points.slice(0, -1).map((point, index) => ( + Math.abs(points[index + 1][0] - point[0]) + Math.abs(points[index + 1][1] - point[1]) + )); + const routeLength = segmentLengths.reduce((total, length) => total + length, 0); + const directLength = Math.abs(points.at(-1)[0] - points[0][0]) + Math.abs(points.at(-1)[1] - points[0][1]); + const interiorPreferred28Deficit = segmentLengths.slice(1, -1) + .reduce((total, length) => total + Math.max(0, 28 - length), 0); + const xs = points.map(([x]) => x); + const ys = points.map(([, y]) => y); + const canvasGrowthPx = Math.max(0, -Math.min(...xs)) + + Math.max(0, Math.max(...xs) - minimumCanvasWidth) + + Math.max(0, -Math.min(...ys)) + + Math.max(0, Math.max(...ys) - autoHeight); + const from = nodes.get(edge.from); + const to = nodes.get(edge.to); + const naturalStart = anchor(from, naturalFromSide); + const naturalEnd = anchor(to, naturalToSide); + const portDisplacementPx = Math.abs(points[0][0] - naturalStart[0]) + + Math.abs(points[0][1] - naturalStart[1]) + + Math.abs(points.at(-1)[0] - naturalEnd[0]) + + Math.abs(points.at(-1)[1] - naturalEnd[1]); + const legacyCoordinateDisplacement = Math.abs(from.cx - LEGACY_COLUMN_CENTERS[from.col]) + + Math.abs(to.cx - LEGACY_COLUMN_CENTERS[to.col]); + return { + automaticForwardReversePx: automaticForwardReversePx(edge, points), + properCrossingCount: interaction.properCrossingCount, + sharedCorridorPx: interaction.sharedCorridorPx, + labelRouteClearanceDeficit: labelRouteClearanceDeficit(edge, points), + interiorPreferred28Deficit, + bendCount: Math.max(0, points.length - 2), + stretchMilli: Math.round((directLength > 0 ? routeLength / directLength : 1) * 1000), + canvasGrowthPx, + portDisplacementMilli: Math.round(portDisplacementPx * 1000), + legacyCoordinateDisplacement, + stableCandidateOrdinal: ordinal, + }; +} + +function compareCost(left, right) { + for (const dimension of READABLE_CANDIDATE_COST_PRIORITY) { + if ((left[dimension] || 0) !== (right[dimension] || 0)) { + return (left[dimension] || 0) - (right[dimension] || 0); + } + } + return 0; +} + +function readableAutomaticCandidateSet( + edge, + from, + to, + start, + end, + fromSide, + toSide, + { + ordinalOffset = 0, + naturalFromSide = fromSide, + naturalToSide = toSide, + } = {}, +) { + const midX = (start[0] + end[0]) / 2; + const laneGapY = from.lane === to.lane + ? laneTop(from.lane) - 16 + : gapYBetween(from.lane, to.lane, edge.bias ?? 0.5); + const topY = Math.max(8, Math.min(laneTop(from.lane), laneTop(to.lane)) - 16); + const bottomY = Math.max( + laneTop(from.lane) + laneHeight(from.lane), + laneTop(to.lane) + laneHeight(to.lane), + ) + 16; + const outsideLeft = layout.laneX - 20; + const outsideRight = layout.laneX + layout.laneW + 12; + const rawCandidates = [ + { family: 'facing-straight', via: [] }, + { family: 'horizontal-then-vertical', via: [[end[0], start[1]]] }, + { family: 'vertical-then-horizontal', via: [[start[0], end[1]]] }, + { family: 'lane-gap-corridor', via: corridorViaY(start, end, fromSide, toSide, laneGapY) }, + { family: 'column-gap-corridor', via: corridorViaX(start, end, fromSide, toSide, midX) }, + { family: 'outside-left', via: corridorViaX(start, end, fromSide, toSide, outsideLeft) }, + { family: 'outside-right', via: corridorViaX(start, end, fromSide, toSide, outsideRight) }, + { family: 'top-corridor', via: corridorViaY(start, end, fromSide, toSide, topY) }, + { family: 'bottom-corridor', via: corridorViaY(start, end, fromSide, toSide, bottomY) }, + ]; + const candidates = rawCandidates.map((candidate, ordinal) => ({ + ...candidate, + ordinal: ordinalOffset + ordinal, + points: normalizeRoutePoints([start, ...candidate.via, end]), + })).filter(({ points }) => ( + readableCandidateIsFeasible(edge, points, from, to, fromSide, toSide) + )).map((candidate) => ({ + ...candidate, + cost: readableCandidateCost( + edge, + candidate.points, + candidate.ordinal, + naturalFromSide, + naturalToSide, + ), + })).sort((left, right) => compareCost(left.cost, right.cost)); + return { rawCandidates, candidates, outsideRight }; +} + +function readableAutomaticVia(edge, from, to, start, end, fromSide, toSide) { + const { rawCandidates, candidates, outsideRight } = readableAutomaticCandidateSet( + edge, + from, + to, + start, + end, + fromSide, + toSide, + ); + + if (candidates.length) return candidates[0].points.slice(1, -1); + const outsideRightCandidate = rawCandidates.find(({ family }) => family === 'outside-right'); + if (outsideRightCandidate) { + const currentPoints = normalizeRoutePoints([start, ...outsideRightCandidate.via, end]); + const labelRect = candidateLabelRect(edge, currentPoints); + let outsideRightMinX = outsideRight; + for (const node of nodes.values()) { + if (!labelRect || !rectsOverlap(labelRect, node, -2)) continue; + const rightwardLabelDeficit = node.x + node.width - 2 - labelRect.x; + if (rightwardLabelDeficit > 0) { + outsideRightMinX = Math.max( + outsideRightMinX, + outsideRight + rightwardLabelDeficit * 2, + ); + } + } + for (const [otherEdge, routed] of pathCache) { + const otherIndex = workflow.edges.indexOf(otherEdge); + const otherLabel = labelRectFor(otherEdge, otherIndex); + if (labelRect && otherLabel && rectsOverlap(labelRect, otherLabel, -2)) { + const rightwardLabelDeficit = otherLabel.x + otherLabel.width - 2 - labelRect.x; + if (rightwardLabelDeficit > 0) { + outsideRightMinX = Math.max( + outsideRightMinX, + outsideRight + rightwardLabelDeficit * 2, + ); + } + } + if (!labelRect) continue; + for (let index = 0; index < routed.points.length - 1; index += 1) { + const segment = { + start: routed.points[index], + end: routed.points[index + 1], + }; + const clearance = segmentRectClearance(segment, labelRect); + if (clearance == null || clearance + 0.0001 >= 4) continue; + const rightwardLabelDeficit = Math.max(segment.start[0], segment.end[0]) + + 4 - labelRect.x; + if (rightwardLabelDeficit > 0) { + outsideRightMinX = Math.max( + outsideRightMinX, + outsideRight + rightwardLabelDeficit * 2, + ); + } + } + } + outsideRightMinX = Math.ceil(outsideRightMinX * 1000) / 1000; + let rightmostPlacedX = outsideRight; + for (const node of nodes.values()) { + rightmostPlacedX = Math.max(rightmostPlacedX, node.x + node.width); + } + for (const [otherEdge, routed] of pathCache) { + for (const [x] of routed.points) rightmostPlacedX = Math.max(rightmostPlacedX, x); + const otherLabel = labelRectFor(otherEdge, workflow.edges.indexOf(otherEdge)); + if (otherLabel) { + rightmostPlacedX = Math.max(rightmostPlacedX, otherLabel.x + otherLabel.width); + } + } + let probeGrowth = Math.max( + 32, + labelRect?.width ?? 0, + rightmostPlacedX + 16 - outsideRightMinX, + ); + let lastInfeasibleX = outsideRight; + for (let probe = 0; probe < 7; probe += 1) { + if (outsideRightMinX > outsideRight + 0.0001) { + const expandedPoints = normalizeRoutePoints([ + start, + ...corridorViaX(start, end, fromSide, toSide, outsideRightMinX), + end, + ]); + if (readableCandidateIsFeasible(edge, expandedPoints, from, to, fromSide, toSide)) { + let feasibleX = outsideRightMinX; + let feasiblePoints = expandedPoints; + let infeasibleX = lastInfeasibleX; + for (let refinement = 0; + refinement < 53 && feasibleX - infeasibleX > 0.001; + refinement += 1) { + const midpointX = Math.ceil(((infeasibleX + feasibleX) / 2) * 1000) / 1000; + if (midpointX >= feasibleX - 0.0001) break; + const midpointPoints = normalizeRoutePoints([ + start, + ...corridorViaX(start, end, fromSide, toSide, midpointX), + end, + ]); + if (readableCandidateIsFeasible( + edge, + midpointPoints, + from, + to, + fromSide, + toSide, + )) { + feasibleX = midpointX; + feasiblePoints = midpointPoints; + } else { + infeasibleX = midpointX; + } + } + return feasiblePoints.slice(1, -1); + } + lastInfeasibleX = outsideRightMinX; + } + outsideRightMinX = Math.ceil((outsideRightMinX + probeGrowth) * 1000) / 1000; + probeGrowth *= 2; + } + } + const hasRelevantAbsolutePin = Array.isArray(edge.labelAt) + || [...pathCache.keys()].some((otherEdge) => ( + Array.isArray(otherEdge.labelAt) || hasAbsoluteRoutePins(otherEdge) + )); + if (hasRelevantAbsolutePin) classifyFailedAutomaticCandidatePins(edge, rawCandidates); + const horizontallyFacing = ( + fromSide === 'right' && toSide === 'left' && end[0] > start[0] + ) || ( + fromSide === 'left' && toSide === 'right' && start[0] > end[0] + ); + if (horizontallyFacing && from.col !== to.col) { + const fromCol = Math.min(from.col, to.col); + const toCol = Math.max(from.col, to.col); + const requiredRankGap = from.width / 2 + 32 + to.width / 2; + const actualRankGap = layout.colXs[toCol] - layout.colXs[fromCol]; + if (actualRankGap + 0.0001 < requiredRankGap) { + throw new WorkflowLayoutFeedback({ + kind: 'rank-gap-minimum', + fromCol, + toCol, + minimum: Math.ceil(requiredRankGap * 1000) / 1000, + edge: edge.id ?? null, + from: edge.from, + to: edge.to, + attemptedCandidateFamilies: rawCandidates.map(({ family }) => family), + candidateCount: rawCandidates.length, + }); + } + } + if (from.lane !== to.lane && layout.laneGap < 32) { + throw new WorkflowLayoutFeedback({ + kind: 'lane-gap-minimum', + minimum: 32, + edge: edge.id ?? null, + from: edge.from, + to: edge.to, + attemptedCandidateFamilies: rawCandidates.map(({ family }) => family), + candidateCount: rawCandidates.length, + }); + } + const message = `Workflow edge "${workflowEdgeName(edge)}" has no feasible readable-v2 automatic route.`; + throwDiagnosticError(message, [{ + code: 'workflow/solver-budget-exhausted', + severity: 'error', + message, + subject: { + diagramType: 'workflow', + edge: edge.id ?? null, + from: edge.from, + to: edge.to, + }, + evidence: { + attemptedCandidateFamilies: rawCandidates.map(({ family }) => family), + candidateCount: rawCandidates.length, + }, + supportedFixes: [], + }]); +} + +function readablePresetVia(edge, from, to, start, end, fromSide, toSide) { + const preset = edge.route; + let via; + switch (preset) { + case 'straight': + via = []; + break; + case 'drop': { + const y = gapYBetween(from.lane, to.lane, edge.bias ?? 0.5); + via = [[start[0], y], [end[0], y]]; + break; + } + case 'outside-right': { + const x = layout.laneX + layout.laneW + 12; + via = [[x, start[1]], [x, end[1]]]; + break; + } + case 'return-left': { + const x = Math.min(from.x, to.x) - 28; + via = [[x, start[1]], [x, end[1]]]; + break; + } + case 'bottom-channel': { + const y = Math.max(from.y + from.height, to.y + to.height) + 32; + via = [[start[0], y], [end[0], y]]; + break; + } + case 'up-channel': { + const y = Math.min(from.y, to.y) - 28; + via = [[start[0], y], [end[0], y]]; + break; + } + default: + return readableAutomaticVia(edge, from, to, start, end, fromSide, toSide); + } + const points = normalizeRoutePoints([start, ...via, end]); + if (readableCandidateIsFeasible(edge, points, from, to, fromSide, toSide) + && routeMatchesPresetFamily(preset, points, from, to)) { + return points.slice(1, -1); + } + const message = `Workflow edge "${workflowEdgeName(edge)}" cannot satisfy route preset "${preset}" under readable-v2 constraints (minimum 8px endpoint stubs, 16px interior turns, and 28px direct clearance).`; + const edgeIndex = workflow.edges.indexOf(edge); + const edgeName = workflowEdgeName(edge); + const supportedFixes = []; + for (const candidatePreset of ['straight', 'drop', 'outside-right', 'return-left', 'bottom-channel', 'up-channel']) { + if (candidatePreset === preset) continue; + if (acceptsFix((document) => { + document.edges[edgeIndex].route = candidatePreset; + })) { + supportedFixes.push(`set edge "${edgeName}" route to verified preset "${candidatePreset}"`); + } + } + if (acceptsFix((document) => { + delete document.edges[edgeIndex].route; + })) { + supportedFixes.push(`remove route from edge "${edgeName}" so readable-v2 can use its verified automatic candidate`); + } + throwDiagnosticError(message, [{ + code: 'workflow/route-preset-conflict', + severity: 'error', + message, + subject: { + diagramType: 'workflow', + edge: edge.id ?? null, + from: edge.from, + to: edge.to, + route: preset, + }, + evidence: { + attemptedCandidateFamily: preset, + points, + fromSide, + toSide, + requiredEndpointStubPx: 8, + requiredInteriorSegmentPx: 16, + requiredDirectClearancePx: 28, + }, + supportedFixes, + }]); +} + +function routeVia( + edge, + from, + to, + start, + end, + fromSide, + toSide, + { validateReadablePreset = true } = {}, +) { + if (edge.via) return edge.via; + const hasCoordinatePins = edge.channelX !== undefined || edge.channelY !== undefined; + if (workflow.schema_version === 2 + && edge.route + && edge.route !== 'auto' + && !hasCoordinatePins + && validateReadablePreset) { + return readablePresetVia(edge, from, to, start, end, fromSide, toSide); + } + switch (edge.route || 'auto') { + case 'straight': + return []; + case 'drop': { + const y = edge.channelY ?? gapYBetween(from.lane, to.lane, edge.bias ?? 0.5); + return [[start[0], y], [end[0], y]]; + } + case 'outside-right': { + const x = edge.channelX ?? layout.laneX + layout.laneW + 12; + return [[x, start[1]], [x, end[1]]]; + } + case 'return-left': { + const x = edge.channelX ?? Math.min(from.x, to.x) - 28; + return [[x, start[1]], [x, end[1]]]; + } + case 'bottom-channel': { + const y = edge.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 32; + return [[start[0], y], [end[0], y]]; + } + case 'up-channel': { + const y = edge.channelY ?? Math.min(from.y, to.y) - 28; + return [[start[0], y], [end[0], y]]; + } + case 'auto': + default: { + if (workflow.schema_version === 2) { + if (edge.channelX !== undefined && edge.channelY !== undefined) { + return [[edge.channelX, start[1]], [edge.channelX, edge.channelY], [end[0], edge.channelY]]; + } + if (edge.channelX !== undefined) return [[edge.channelX, start[1]], [edge.channelX, end[1]]]; + if (edge.channelY !== undefined) return [[start[0], edge.channelY], [end[0], edge.channelY]]; + return readableAutomaticVia(edge, from, to, start, end, fromSide, toSide); + } + if (from.lane === to.lane) return sameLaneAutoVia(start, end); + const oneBendVia = oneBendCrossLaneVia(edge, start, end, fromSide, toSide); + if (oneBendVia) return oneBendVia; + const y = gapYBetween(from.lane, to.lane, edge.bias ?? 0.5); + return [[start[0], y], [end[0], y]]; + } + } +} + +function workflowEdgeLabelPoint(edge, points) { + if (workflow.schema_version === 1) { + if (edge.labelAt || Number.isInteger(edge.labelSegment) || points.length !== 3) { + return labelPoint(edge, points); + } + const segmentLengths = [0, 1].map((index) => Math.hypot( + points[index + 1][0] - points[index][0], + points[index + 1][1] - points[index][1], + )); + const labelSegment = segmentLengths[0] >= segmentLengths[1] ? 0 : 1; + const point = labelPoint({ ...edge, labelSegment }, points); + if (points[labelSegment][0] === points[labelSegment + 1][0]) point[1] += 10; + return point; + } + if (edge.labelAt || Number.isInteger(edge.labelSegment) || points.length <= 2) { + return labelPoint(edge, points); + } + const segments = points.slice(0, -1).map((point, index) => ({ + index, + horizontal: Math.abs(points[index + 1][1] - point[1]) <= 0.0001, + length: Math.hypot( + points[index + 1][0] - point[0], + points[index + 1][1] - point[1], + ), + })).sort((left, right) => ( + Number(right.horizontal) - Number(left.horizontal) + || right.length - left.length + || left.index - right.index + )); + const labelSegment = segments[0]?.index ?? 0; + const point = labelPoint({ ...edge, labelSegment }, points); + if (points[labelSegment][0] === points[labelSegment + 1][0]) point[1] += 10; + return point; +} + +function edgeSides(edge) { + const from = nodes.get(edge.from); + const to = nodes.get(edge.to); + const resolved = workflow.schema_version === 2 ? readableSideCache.get(edge) : null; + if (resolved) return resolved; + const oneBendSides = automaticOneBendSides(edge, from, to); + if (oneBendSides) return oneBendSides; + if (workflow.schema_version === 2 + && layout.channelLabelEdgeKeys?.has(stableValueKey(edge)) + && !edge.fromSide + && !edge.toSide) { + return { fromSide: 'top', toSide: 'top' }; + } + return { + fromSide: chosenSide(edge.fromSide, defaultFromSide(from, to)), + toSide: chosenSide(edge.toSide, defaultToSide(from, to)), + }; +} + +const automaticPorts = automaticPortSpread(workflow.edges, nodes, { + sideFor: (edge, endpoint) => edgeSides(edge)[endpoint === 'source' ? 'fromSide' : 'toSide'], +}); + +function readableAutomaticRoute(edge, from, to, primarySides, primaryPorts) { + const authoredFrom = edge.fromSide && edge.fromSide !== 'auto' ? edge.fromSide : null; + const authoredTo = edge.toSide && edge.toSide !== 'auto' ? edge.toSide : null; + const sideOrder = ['right', 'bottom', 'left', 'top']; + const sidePairs = [primarySides]; + for (const fromSide of authoredFrom ? [authoredFrom] : sideOrder) { + for (const toSide of authoredTo ? [authoredTo] : sideOrder) { + sidePairs.push({ fromSide, toSide }); + } + } + + const naturalFromSide = authoredFrom || defaultFromSide(from, to); + const naturalToSide = authoredTo || defaultToSide(from, to); + const seen = new Set(); + const plans = []; + const feedback = []; + let firstFailure = null; + for (const candidateSides of sidePairs) { + if (authoredFrom && candidateSides.fromSide !== authoredFrom) continue; + if (authoredTo && candidateSides.toSide !== authoredTo) continue; + const key = `${candidateSides.fromSide}:${candidateSides.toSide}`; + if (seen.has(key)) continue; + const pairOrdinal = seen.size; + seen.add(key); + const primary = pairOrdinal === 0; + const start = primaryPorts?.from && primary + ? primaryPorts.from + : anchor(from, candidateSides.fromSide); + const end = primaryPorts?.to && primary + ? primaryPorts.to + : anchor(to, candidateSides.toSide); + const planned = readableAutomaticCandidateSet( + edge, + from, + to, + start, + end, + candidateSides.fromSide, + candidateSides.toSide, + { + ordinalOffset: pairOrdinal * 9, + naturalFromSide, + naturalToSide, + }, + ); + plans.push(...planned.candidates.map((candidate) => ({ + ...candidate, + ...candidateSides, + }))); + if (planned.candidates.length) continue; + try { + const expandedVia = withDiagnosticRecordingSuppressed(() => readableAutomaticVia( + edge, + from, + to, + start, + end, + candidateSides.fromSide, + candidateSides.toSide, + )); + const expandedPoints = normalizeRoutePoints([start, ...expandedVia, end]); + const outsideRightOrdinal = planned.rawCandidates.findIndex(({ family }) => ( + family === 'outside-right' + )); + const ordinal = pairOrdinal * 9 + Math.max(0, outsideRightOrdinal); + plans.push({ + family: 'outside-right', + ordinal, + points: expandedPoints, + cost: readableCandidateCost( + edge, + expandedPoints, + ordinal, + naturalFromSide, + naturalToSide, + ), + ...candidateSides, + }); + } catch (error) { + if (error instanceof WorkflowLayoutFeedback) { + feedback.push({ error, pairOrdinal }); + } else if (!firstFailure) { + firstFailure = error; + } + } + } + + plans.sort((left, right) => compareCost(left.cost, right.cost)); + if (plans.length) { + const selected = plans[0]; + return { + points: selected.points, + fromSide: selected.fromSide, + toSide: selected.toSide, + }; + } + + const feedbackPriority = { + 'rank-gap-minimum': 0, + 'lane-gap-minimum': 1, + }; + feedback.sort((left, right) => ( + (feedbackPriority[left.error.request?.kind] ?? 99) + - (feedbackPriority[right.error.request?.kind] ?? 99) + || left.pairOrdinal - right.pairOrdinal + )); + if (feedback.length) throw feedback[0].error; + const authoredSideFields = [ + ...(authoredFrom ? ['fromSide'] : []), + ...(authoredTo ? ['toSide'] : []), + ]; + if (authoredSideFields.length) { + const alternatives = verifiedPinRemovalAlternatives( + edge, + authoredSideFields, + 'so readable-v2 can replan the remaining endpoint-side pins', + ); + const sourceAnchor = primaryPorts?.from || anchor(from, primarySides.fromSide); + const targetAnchor = primaryPorts?.to || anchor(to, primarySides.toSide); + const attemptedEvidence = firstFailure?.archifyDiagnostics?.[0]?.evidence || {}; + throwExplicitPinConflict(edge, 'readable route feasibility with authored endpoint sides', { + conflictingPins: conflictPinsFromRemovalSets( + edge, + alternatives.removalSets, + authoredSideFields, + ), + actualCoordinates: { + sourceAnchor: [...sourceAnchor], + targetAnchor: [...targetAnchor], + }, + fromSide: primarySides.fromSide, + toSide: primarySides.toSide, + ...(attemptedEvidence.attemptedCandidateFamilies + ? { attemptedCandidateFamilies: attemptedEvidence.attemptedCandidateFamilies } + : {}), + ...(attemptedEvidence.candidateCount !== undefined + ? { candidateCount: attemptedEvidence.candidateCount } + : {}), + }, alternatives.supportedFixes); + } + if (firstFailure) throw firstFailure; + throw new Error('readable-v2 automatic route enumeration produced no result'); +} + +function isReadableControlledRoute(edge) { + return workflow.schema_version === 2 && ( + Array.isArray(edge.via) + || edge.channelX !== undefined + || edge.channelY !== undefined + || (edge.route && edge.route !== 'auto') + ); +} + +function readableControlledRoute(edge, from, to) { + const authoredFrom = edge.fromSide && edge.fromSide !== 'auto' ? edge.fromSide : null; + const authoredTo = edge.toSide && edge.toSide !== 'auto' ? edge.toSide : null; + const naturalFromSide = authoredFrom || defaultFromSide(from, to); + const naturalToSide = authoredTo || defaultToSide(from, to); + const sideOrder = ['right', 'bottom', 'left', 'top']; + const preferredPairs = [{ + fromSide: naturalFromSide, + toSide: naturalToSide, + }]; + for (const fromSide of authoredFrom ? [authoredFrom] : sideOrder) { + for (const toSide of authoredTo ? [authoredTo] : sideOrder) { + preferredPairs.push({ fromSide, toSide }); + } + } + + const seen = new Set(); + const sidePairs = preferredPairs.filter(({ fromSide, toSide }) => { + if (authoredFrom && fromSide !== authoredFrom) return false; + if (authoredTo && toSide !== authoredTo) return false; + const key = `${fromSide}:${toSide}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + const hasAbsoluteRoutePins = Array.isArray(edge.via) + || edge.channelX !== undefined + || edge.channelY !== undefined; + const candidates = []; + const diagnosticCandidates = []; + const materializedCandidates = []; + for (const [ordinal, { fromSide, toSide }] of sidePairs.entries()) { + const start = anchor(from, fromSide); + const end = anchor(to, toSide); + const via = routeVia( + edge, + from, + to, + start, + end, + fromSide, + toSide, + { validateReadablePreset: false }, + ); + const authoredPoints = [start, ...via, end]; + const points = hasAbsoluteRoutePins + ? authoredPoints + : normalizeRoutePoints(authoredPoints); + const materialized = { points, fromSide, toSide, ordinal }; + materializedCandidates.push(materialized); + if (points.length >= 2 + && points.every((point) => ( + Array.isArray(point) && point.length === 2 && isFinitePoint(...point) + )) + && routeHonorsEndpointSides(points, fromSide, toSide)) { + diagnosticCandidates.push(materialized); + } + if (!readableCandidateIsFeasible(edge, points, from, to, fromSide, toSide)) continue; + if (edge.route && edge.route !== 'auto' && !routeMatchesPresetFamily( + edge.route, + points, + from, + to, + )) continue; + if (presentChannelPins(edge).some((field) => ( + !routeContainsChannelPin(points, field, edge[field]) + ))) continue; + candidates.push({ + points, + fromSide, + toSide, + cost: readableCandidateCost( + edge, + points, + ordinal, + naturalFromSide, + naturalToSide, + ), + }); + } + candidates.sort((left, right) => compareCost(left.cost, right.cost)); + if (candidates.length) return candidates[0]; + + // Absolute geometry is authoritative even when it is invalid. Preserve the + // best endpoint-side inference so validation can diagnose the authored + // segment or preset that actually failed instead of silently falling back to + // default sides and changing the route's meaning. + if (hasAbsoluteRoutePins) { + return diagnosticCandidates[0] || materializedCandidates[0] || null; + } + + // Preset-only routes retain their dedicated typed conflict (and verified + // alternative search) when exhaustive side inference found no valid plan. + const fallback = materializedCandidates[0]; + if (!fallback) return null; + const fallbackVia = readablePresetVia( + edge, + from, + to, + fallback.points[0], + fallback.points.at(-1), + fallback.fromSide, + fallback.toSide, + ); + return { + ...fallback, + points: normalizeRoutePoints([ + fallback.points[0], + ...fallbackVia, + fallback.points.at(-1), + ]), + }; +} + +function pathFor(edge) { + if (pathCache.has(edge)) return pathCache.get(edge); + const from = nodes.get(edge.from); + const to = nodes.get(edge.to); + if (isReadableControlledRoute(edge)) { + const planned = readableControlledRoute(edge, from, to); + if (planned) { + readableSideCache.set(edge, { + fromSide: planned.fromSide, + toSide: planned.toSide, + }); + const routed = { d: polylinePath(planned.points), points: planned.points }; + pathCache.set(edge, routed); + return routed; + } + } + const ports = automaticPorts.get(edge); + const { fromSide, toSide } = edgeSides(edge); + const readableAutomatic = workflow.schema_version === 2 + && !Array.isArray(edge.via) + && edge.channelX === undefined + && edge.channelY === undefined + && (!edge.route || edge.route === 'auto'); + if (readableAutomatic) { + const planned = readableAutomaticRoute( + edge, + from, + to, + { fromSide, toSide }, + ports, + ); + readableSideCache.set(edge, { + fromSide: planned.fromSide, + toSide: planned.toSide, + }); + const routed = { d: polylinePath(planned.points), points: planned.points }; + pathCache.set(edge, routed); + return routed; + } + const start = ports?.from || anchor(from, fromSide); + const end = ports?.to || anchor(to, toSide); + const authoredPoints = [start, ...routeVia(edge, from, to, start, end, fromSide, toSide), end]; + const hasAbsoluteRoutePins = Array.isArray(edge.via) + || edge.channelX !== undefined + || edge.channelY !== undefined; + const points = workflow.schema_version === 2 && !hasAbsoluteRoutePins + ? normalizeRoutePoints(authoredPoints) + : authoredPoints; + const routed = { d: polylinePath(points), points }; + pathCache.set(edge, routed); + return routed; +} + +function labelRectFor(edge, relationIndex) { + if (!edge.label || !nodes.has(edge.from) || !nodes.has(edge.to)) return null; + const [lx, ly] = workflowEdgeLabelPoint(edge, pathFor(edge).points); + const width = workflowLabelWidth(edge.label); + return { + relation: edge, + relationIndex, + label: edge.label, + x: lx - width / 2, + y: ly - 10, + width, + height: 14, + lx, + ly, + }; +} + +function measuredContentBounds() { + let left = layout.laneX; + let top = 27; + let right = layout.laneX + layout.laneW; + let bottom = legendY() + 18; + const owners = { + left: 'workflow lanes', + top: asArray(workflow.phases).length ? 'phase header band' : 'workflow top padding', + right: 'workflow lanes', + bottom: workflowLegendEntries.length ? 'legend' : 'workflow lanes and bottom padding', + }; + const includePoint = ([x, y], contributor) => { + if (x < left) { + left = x; + owners.left = contributor; + } + if (y < top) { + top = y; + owners.top = contributor; + } + if (x > right) { + right = x; + owners.right = contributor; + } + if (y > bottom) { + bottom = y; + owners.bottom = contributor; + } + }; + const includeRect = (rect, contributor) => { + includePoint([rect.x, rect.y], contributor); + includePoint([rect.x + rect.width, rect.y + rect.height], contributor); + }; + + for (const node of nodes.values()) includeRect(node, `node ${node.id}`); + for (const [index, edge] of workflow.edges.entries()) { + if (!nodes.has(edge.from) || !nodes.has(edge.to)) continue; + for (const point of pathFor(edge).points) includePoint(point, `edge ${edge.id || index}`); + const label = labelRectFor(edge, index); + if (label) includeRect(label, `edge ${edge.id || index} label mask`); + } + for (const phase of asArray(workflow.phases)) { + if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol) + || phase.fromCol < 0 || phase.toCol >= layout.colXs.length || phase.fromCol > phase.toCol) continue; + const span = phaseSpan(phase); + includeRect({ x: span.x, y: 27, width: span.width, height: 16 }, `phase ${phase.id}`); + } + for (const group of asArray(workflow.groups)) { + if (!laneIndex.has(group.lane) || !Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol) + || group.fromCol < 0 || group.toCol >= layout.colXs.length || group.fromCol > group.toCol) continue; + const span = groupSpan(group); + includeRect({ + x: span.x, + y: laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET, + width: span.width, + height: workflow.schema_version === 2 + ? laneHeight(group.lane) - layout.laneTitleH + - GROUP_FRAME_TOP_INSET - GROUP_FRAME_BOTTOM_INSET + : layout.laneH - layout.laneTitleH - 16, + }, `group ${group.id}`); + if (workflow.schema_version === 2) { + const frameY = laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET; + const labelBaseline = frameY + GROUP_LABEL_BASELINE_OFFSET; + includeRect({ + x: span.x + 10, + y: labelBaseline - GROUP_LABEL_MASK_ASCENT, + width: textUnits(group.label) * 5.6, + height: GROUP_LABEL_MASK_H, + }, `group ${group.id} label`); + } + } + if (workflowLegendEntries.length) { + for (const rect of workflowLegendRects()) includeRect(rect, `legend ${rect.kind}`); + } + return { + left, + top, + right, + bottom, + contributors: [...new Set([ + ...Object.values(owners), + ...asArray(layout.widthContributors), + ...asArray(layout.heightContributors), + ])], + }; +} + +function finalizeReadableViewBox() { + if (workflow.schema_version !== 2) { + requiredViewBox = [...viewBox]; + return; + } + const bounds = measuredContentBounds(); + requiredViewBox = [ + Math.max(minimumCanvasWidth, Math.ceil(bounds.right + 16)), + Math.max(autoHeight, Math.ceil(bounds.bottom + 18)), + ]; + const outsideOrigin = bounds.left < 0 || bounds.top < 0; + if (outsideOrigin) { + const hasAbsolutePins = workflow.edges.some((edge) => ( + Array.isArray(edge.via) + || Array.isArray(edge.labelAt) + || edge.channelX !== undefined + || edge.channelY !== undefined + )); + const message = `Workflow geometry extends above or left of the viewBox origin (${Math.round(bounds.left)}, ${Math.round(bounds.top)}).`; + throwDiagnosticError(message, [{ + code: hasAbsolutePins ? 'workflow/explicit-pin-conflict' : 'workflow/solver-budget-exhausted', + severity: 'error', + message, + subject: { diagramType: 'workflow', path: '/meta/viewBox' }, + evidence: { + actualViewBox: [...viewBox], + requiredViewBox: [...requiredViewBox], + contentBounds: [bounds.left, bounds.top, bounds.right, bounds.bottom], + contributors: bounds.contributors, + }, + supportedFixes: [], + }]); + } + if (!workflow.meta?.viewBox) { + viewBox = [...requiredViewBox]; + return; + } + const tooNarrow = viewBox[0] < requiredViewBox[0]; + const tooShort = viewBox[1] < requiredViewBox[1]; + if (!tooNarrow && !tooShort) return; + const message = `Workflow viewBox ${viewBox[0]}×${viewBox[1]} cannot contain the readable-v2 layout; minimum ${requiredViewBox[0]}×${requiredViewBox[1]}.`; + const supportedFixes = []; + if (acceptsFix((document) => { + document.meta.viewBox = [...requiredViewBox]; + })) { + supportedFixes.push(`set meta.viewBox to at least [${requiredViewBox[0]}, ${requiredViewBox[1]}]`); + } + if (acceptsFix((document) => { + delete document.meta.viewBox; + })) { + supportedFixes.push('omit meta.viewBox so the compiler can use its measured intrinsic canvas'); + } + throwDiagnosticError(message, [{ + code: 'workflow/viewbox-capacity', + severity: 'error', + message, + subject: { diagramType: 'workflow', path: '/meta/viewBox' }, + evidence: { + actualViewBox: [...viewBox], + requiredViewBox: [...requiredViewBox], + contentBounds: [bounds.left, bounds.top, bounds.right, bounds.bottom], + contributors: bounds.contributors, + }, + supportedFixes, + }]); +} + +function renderLane(lane, index) { + const y = laneTop(lane.id); + const height = laneHeight(index); + const exception = lane.variant === 'exception' + ? `\n ` + : ''; + const labelClass = lane.variant === 'exception' ? 't-security' : 't-dim'; + const prefix = lane.variant === 'exception' ? 'EX' : String(index + 1).padStart(2, '0'); + return ` ${exception} + ${prefix} / ${esc(lane.label)}`; +} + +function renderPhase(phase) { + const span = phaseSpan(phase); + const accent = variantAccent(phase.variant); + const [lineClass] = arrowClassMap[phase.variant || 'default'] || arrowClassMap.default; + return ` + + ${esc(phase.label)}`; +} + +function renderGroup(group, index) { + const span = groupSpan(group); + const y = laneTop(group.lane) + layout.laneTitleH + GROUP_FRAME_TOP_INSET; + const height = workflow.schema_version === 2 + ? laneHeight(group.lane) - layout.laneTitleH + - GROUP_FRAME_TOP_INSET - GROUP_FRAME_BOTTOM_INSET + : layout.laneH - layout.laneTitleH - 16; + const cls = group.variant === 'security' ? 'c-security-group' : 'c-lane'; + const textClass = variantAccent(group.variant); + const labelY = workflow.schema_version === 2 ? y + GROUP_LABEL_BASELINE_OFFSET : y + 14; + return ` + ${esc(group.label)}`; +} + +function renderNode(node) { + const fill = componentFill[node.type] || 'c-external'; + const accent = componentText[node.type] || 't-muted'; + const hasSub = node.sublabel != null && node.sublabel !== ''; + const labelFontSize = fittedNodeFontSize(node.label, brandLabelFitWidth(node, node.width), nodeTextFit.labelPreferred, nodeTextFit.labelMinimum); + const sublabelFontSize = hasSub + ? fittedNodeFontSize(node.sublabel, node.width, nodeTextFit.sublabelPreferred, nodeTextFit.sublabelMinimum) + : nodeTextFit.sublabelPreferred; + const sub = hasSub + ? `\n ${esc(node.sublabel)}` + : ''; + const tag = node.tag + ? `\n ${esc(node.tag)}` + : ''; + const brand = renderBrandMark(node, { x: node.x + node.width - 22, y: node.y + 6 }); + const passport = { kind: node.type, sublabel: node.sublabel, tag: node.tag, context: nodeContext(node), ...brandMetadataFor(node) }; + return ` + ${focusNodeTitle(node.label, passport)} + + + ${renderSemanticSigil(node.type, { x: node.x + 6, y: node.y + 6 })}${brand ? `\n ${brand}` : ''} + ${esc(node.label)}${sub}${tag} + `; +} + +function renderEdgePath(edge, index) { + const [cls, marker] = arrowClassMap[edge.variant || 'default'] || arrowClassMap.default; + const routed = pathFor(edge); + const strokeWidth = edge.width || (edge.variant === 'emphasis' ? 1.8 : 1.4); + return ` `; +} + +function renderEdgeLabel(edge, index) { + if (!edge.label) return ''; + const routed = pathFor(edge); + const [lx, ly] = workflowEdgeLabelPoint(edge, routed.points); + const labelW = workflowLabelWidth(edge.label); + return ` + + ${esc(edge.label)} + `; +} + +function renderLegend() { + const obstacles = workflow.schema_version === 2 + ? relationshipLegendObstacles(workflow.edges, { + pointsFor: (edge) => pathFor(edge).points, + labelRectFor, + }) + : []; + return renderResolvedLegend({ + entries: workflowLegendEntries, + locale: workflow.meta.locale, + layout: workflowLegendLayout(obstacles), + renderSwatch: (entry) => ``, + }); +} + +function renderSvg() { + return ` +${svgAccessibleText(workflow.meta, 'workflow')} +${renderDefinitions()} + + + + + +${workflow.lanes.map(renderLane).join('\n\n')} + + +${asArray(workflow.phases).map(renderPhase).join('\n')} + + +${asArray(workflow.groups).map(renderGroup).join('\n')} + + +${workflow.edges.map(renderEdgePath).join('\n')} + + +${[...nodes.values()].map(renderNode).join('\n\n')} + + +${workflow.edges.map(renderEdgeLabel).join('\n')} + + +${renderLegend()} + `; +} + + + try { + validateReadableInputsBeforeRouting(); + validateReadablePinnedGeometry(); + validateWorkflow(); + finalizeReadableViewBox(); + const svg = renderSvg(); + const receipt = { + contract: layout.contract, + viewBox: [...viewBox], + requiredViewBox: [...requiredViewBox], + columns: [...layout.colXs], + nodes: [...nodes.values()].map((node) => ({ + id: node.id, + lane: node.lane, + col: node.col, + x: node.x, + y: node.y, + width: node.width, + height: node.height, + })), + edges: workflow.edges.map((edge) => ({ + id: edge.id ?? null, + from: edge.from, + to: edge.to, + points: pathFor(edge).points.map((point) => [...point]), + })), + labels: workflow.edges.flatMap((edge) => { + if (!edge.label || !nodes.has(edge.from) || !nodes.has(edge.to)) return []; + const [x, y] = workflowEdgeLabelPoint(edge, pathFor(edge).points); + return [{ edge: edge.id ?? null, label: edge.label, x, y, width: workflowLabelWidth(edge.label), height: 14 }]; + }), + diagnostics: [], + }; + return { ok: true, svg, receipt }; + } catch (error) { + if (!Array.isArray(error?.archifyDiagnostics)) throw error; + const diagnostics = error.archifyDiagnostics.map((diagnostic) => ({ ...diagnostic })); + return compilerFailure(layout.contract, diagnostics, error.message); + } +} + +function feedbackFailure(request) { + const message = `Workflow edge "${request.edge || `${request.from}->${request.to}`}" exhausted bounded readable-v2 layout feedback without a feasible automatic route.`; + const diagnostics = [{ + code: 'workflow/solver-budget-exhausted', + severity: 'error', + message, + subject: { + diagramType: 'workflow', + edge: request.edge, + from: request.from, + to: request.to, + }, + evidence: { + attemptedCandidateFamilies: request.attemptedCandidateFamilies, + candidateCount: request.candidateCount, + }, + supportedFixes: [], + }]; + return compilerFailure('readable-v2', diagnostics, message); +} + +function compileWorkflowWithFeedback({ workflow, qualityProfile, discoverFixes = true } = {}) { + let layoutFeedback = {}; + for (let attempt = 0; attempt <= MAX_READABLE_LAYOUT_FEEDBACK_ROUNDS; attempt += 1) { + try { + return compileWorkflowInternal({ + workflow, + qualityProfile, + discoverFixes, + layoutFeedback, + }); + } catch (error) { + if (!(error instanceof WorkflowLayoutFeedback)) throw error; + const request = error.request; + let nextFeedback = null; + if (request.kind === 'rank-gap-minimum' + && Number.isInteger(request.fromCol) + && Number.isInteger(request.toCol) + && Number.isFinite(request.minimum)) { + const key = `${request.fromCol}:${request.toCol}`; + const current = layoutFeedback.rankGapMinimums?.[key] ?? -Infinity; + if (request.minimum > current + 0.0001) { + nextFeedback = { + ...layoutFeedback, + rankGapMinimums: { + ...(layoutFeedback.rankGapMinimums || {}), + [key]: request.minimum, + }, + rankGapContributors: { + ...(layoutFeedback.rankGapContributors || {}), + [key]: [ + `rank ${request.fromCol}→${request.toCol} route clearance`, + `edge ${request.edge || `${request.from}->${request.to}`} route`, + ], + }, + }; + } + } else if (request.kind === 'lane-gap-minimum' + && Number.isFinite(request.minimum) + && request.minimum > (layoutFeedback.laneGapMin ?? -Infinity) + 0.0001) { + nextFeedback = { + ...layoutFeedback, + laneGapMin: request.minimum, + laneGapContributors: [ + `edge ${request.edge || `${request.from}->${request.to}`} lane-gap route clearance`, + ], + }; + } + if (!nextFeedback || attempt === MAX_READABLE_LAYOUT_FEEDBACK_ROUNDS) { + return feedbackFailure(request); + } + layoutFeedback = nextFeedback; + } + } + throw new Error('unreachable readable-v2 layout feedback state'); +} + +export function compileWorkflow({ workflow, qualityProfile } = {}) { + return compileWorkflowWithFeedback({ workflow, qualityProfile }); +} diff --git a/tools/archify/renderers/workflow/workflow-migration-geometry.mjs b/tools/archify/renderers/workflow/workflow-migration-geometry.mjs new file mode 100644 index 0000000..0053387 --- /dev/null +++ b/tools/archify/renderers/workflow/workflow-migration-geometry.mjs @@ -0,0 +1,144 @@ +const TARGET_SCHEMA_VERSION = 2; + +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + +/** + * Return the authored workflow as a schema-v2 document without its capacity + * override. The compiler can use this projection to discover the intrinsic v2 + * rank plan before deciding whether an explicit viewBox needs to grow. + */ +export function intrinsicWorkflow(workflow) { + const intrinsic = clone(workflow); + intrinsic.schema_version = TARGET_SCHEMA_VERSION; + intrinsic.meta = { ...intrinsic.meta }; + delete intrinsic.meta.viewBox; + return intrinsic; +} + +/** + * Return a schema-v2 planning projection that removes authored route geometry + * which may only become valid after its legacy X coordinates are remapped. + * Rank-affecting automatic and straight relationships remain in the projection. + */ +export function planningWorkflow(workflow) { + const planned = intrinsicWorkflow(workflow); + planned.edges = planned.edges.flatMap((edge) => { + const hasRoutedGeometry = Array.isArray(edge.via) + || (edge.route && !['auto', 'straight'].includes(edge.route)) + || edge.channelX !== undefined + || edge.channelY !== undefined; + if (hasRoutedGeometry) return []; + + const automatic = {}; + for (const property of ['id', 'from', 'to', 'variant', 'role', 'width']) { + if (edge[property] !== undefined) automatic[property] = edge[property]; + } + if (edge.route === 'straight') automatic.route = 'straight'; + if (edge.labelAt === undefined && edge.label !== undefined) automatic.label = edge.label; + return [automatic]; + }); + + if (Array.isArray(planned.mainPath)) { + const projectedPairs = new Set(planned.edges.map((edge) => `${edge.from}\u0000${edge.to}`)); + const projectionBreaksMainPath = planned.mainPath.some((from, index) => ( + index < planned.mainPath.length - 1 + && !projectedPairs.has(`${from}\u0000${planned.mainPath[index + 1]}`) + )); + if (projectionBreaksMainPath) delete planned.mainPath; + } + + return planned; +} + +function mappedNumber(value) { + return Number(value.toFixed(6)); +} + +/** + * Build a deterministic piecewise-linear mapping between corresponding legacy + * and readable rank centers. Coordinates outside the rank span are extrapolated + * using the nearest segment so explicitly authored outside corridors retain + * their relative offset. + */ +export function createHorizontalRankMapper(oldColumns, newColumns) { + if ( + !Array.isArray(oldColumns) + || !Array.isArray(newColumns) + || oldColumns.length !== newColumns.length + || oldColumns.length < 2 + || !oldColumns.every(Number.isFinite) + || !newColumns.every(Number.isFinite) + ) { + throw new TypeError('Horizontal rank mapping requires matching finite column arrays.'); + } + for (let index = 1; index < oldColumns.length; index += 1) { + if (oldColumns[index] <= oldColumns[index - 1] || newColumns[index] <= newColumns[index - 1]) { + throw new TypeError('Horizontal rank mapping requires strictly increasing columns.'); + } + } + + return (x) => { + if (!Number.isFinite(x)) throw new TypeError('Horizontal rank mapping requires a finite x coordinate.'); + let segment = oldColumns.length - 2; + if (x <= oldColumns[0]) { + segment = 0; + } else { + for (let index = 0; index < oldColumns.length - 1; index += 1) { + if (x <= oldColumns[index + 1]) { + segment = index; + break; + } + } + } + const oldSpan = oldColumns[segment + 1] - oldColumns[segment]; + const newSpan = newColumns[segment + 1] - newColumns[segment]; + const ratio = (x - oldColumns[segment]) / oldSpan; + return mappedNumber(newColumns[segment] + ratio * newSpan); + }; +} + +/** + * Apply one horizontal coordinate mapping to every schema-v1 absolute X pin. + * The caller owns the supplied workflow; this function reports an audit trail + * for each changed coordinate in stable document order. + */ +export function mapExplicitCoordinates(workflow, mapX) { + const changedCoordinates = []; + const record = (path, owner, property) => { + const from = owner[property]; + const to = mapX(from); + owner[property] = to; + if (to !== from) changedCoordinates.push({ path, from, to }); + }; + + for (const [edgeIndex, edge] of workflow.edges.entries()) { + if (Array.isArray(edge.via)) { + for (const [pointIndex, point] of edge.via.entries()) { + if (Array.isArray(point) && Number.isFinite(point[0])) { + record(`/edges/${edgeIndex}/via/${pointIndex}/0`, point, 0); + } + } + } + if (Array.isArray(edge.labelAt) && Number.isFinite(edge.labelAt[0])) { + record(`/edges/${edgeIndex}/labelAt/0`, edge.labelAt, 0); + } + if (Number.isFinite(edge.channelX)) { + record(`/edges/${edgeIndex}/channelX`, edge, 'channelX'); + } + } + return changedCoordinates; +} + +/** + * Construct an independently owned schema-v2 candidate with all authored + * absolute X pins mapped to the readable rank plan. + */ +export function createMappedWorkflowCandidate(workflow, oldColumns, newColumns) { + const document = clone(workflow); + document.schema_version = TARGET_SCHEMA_VERSION; + const mapX = createHorizontalRankMapper(oldColumns, newColumns); + const changedCoordinates = mapExplicitCoordinates(document, mapX); + return { document, changedCoordinates }; +} diff --git a/tools/archify/schemas/README.md b/tools/archify/schemas/README.md new file mode 100644 index 0000000..4a43011 --- /dev/null +++ b/tools/archify/schemas/README.md @@ -0,0 +1,211 @@ +# Archify JSON IR Schemas + +Each typed renderer consumes a JSON intermediate representation (IR) validated +against one of the schemas in this folder before any layout work happens. + +## Files + +| Schema | Governs | Structural arrays | +|--------|---------|-------------------| +| `workflow.schema.json` | `diagram_type: "workflow"` | `lanes`, `phases`, `groups`, `mainPath`, `nodes`, `edges` | +| `sequence.schema.json` | `diagram_type: "sequence"` | `participants`, `segments`, `messages`, `activations` | +| `dataflow.schema.json` | `diagram_type: "dataflow"` | `stages`, `nodes`, `flows` | +| `lifecycle.schema.json` | `diagram_type: "lifecycle"` | `lanes`, `states`, `transitions` | +| `architecture.schema.json` | `diagram_type: "architecture"` | `components`, `boundaries`, `connections` | +| `common.schema.json` | shared `$defs` only (no top-level document) | — | + +Every diagram schema requires `schema_version`, `diagram_type`, `meta` (with +`title`), and its structural arrays — except `segments`, `activations`, and +`cards`, which are optional — and sets `additionalProperties: false` at every +level, so unknown fields are rejected rather than silently ignored. + +Every `meta` object also accepts `animation: "trace"` for opt-in SVG/CSS motion +in generated HTML. Omit it, or set `"none"`, for the default static output. +It also accepts `locale: "en" | "zh-CN"`. The field selects the fixed Viewer +UI, renderer-owned default legend and accessibility copy, document-title +suffix, and `` value; it does not translate authored strings. +Omitting it preserves legacy behavior and resolves to English. Unsupported +locale values fail schema validation instead of being guessed or silently +rewritten. +`visual_preset` accepts `classic` (the stable default), `signal-flow` (luminous +motion-forward presentation), `blueprint` (high-contrast engineering review), +or `editorial` (warm publication-style design review and documentation). +Presets change only viewer styling; they do not alter semantic IDs or geometry. +Sequence `meta` additionally accepts `column_fit`. The default `fixed` keeps +the historical 108px column gap and 86px participant boxes, so an authored +diagram renders at the same coordinates no matter how wide its viewBox is. +`spread` derives the gap and box width from the viewBox instead, which turns a +wide canvas into column distance and label room rather than empty space on the +right. Lane order, IDs, and message semantics are unchanged either way. + +It may also include up to five guided `views`. Each view has a unique `id`, a +reader-facing `label`, a non-empty `focus` list of existing semantic node IDs, +and an optional short `note`. + +### Legend presentation contract + +Every `meta` object accepts the same optional legend shape without changing +the schema version already selected for that renderer: + +```json +"legend": { + "mode": "auto", + "entries": { + "security": { "label": "restricted data", "visible": true } + } +} +``` + +`mode` is `auto` (the default), `all`, or `hidden`. `auto` includes only kinds +present in typed IR; `all` includes the renderer's full stable catalog; +`hidden` removes the complete legend and takes precedence over entry overrides. +Architecture documents that omit an explicit `viewBox` size that automatic +viewBox from the same measured resolved legend footprint used for final SVG +layout. Across all renderers, legacy documents that omit `meta.legend` use a +compatibility-safe implicit `auto`: if the resolved legend cannot fit an +explicit authored viewBox without overlap, Archify omits the complete legend +instead of turning a previously valid schema-v1 document into a hard failure. +Once an author adds `meta.legend` (including explicit `mode: "auto"`), the +layout is intentional and unfit labels or bands fail with a path-prefixed +diagnostic. An entry may set a non-empty, bounded `label`, boolean `visible`, +or both. +`visible: false` removes a resolved entry and `visible: true` forces a supported +but unused kind into the visual legend. Unknown kinds and properties fail +strict validation. + +Supported keys are renderer-owned: + +| Renderer | `meta.legend.entries` keys | +|---|---| +| Architecture | `frontend`, `backend`, `database`, `cloud`, `security`, `messagebus`, `external` | +| Workflow | `frontend`, `backend`, `security`, `messagebus`, `database`, `cloud`, `external` | +| Sequence | `emphasis`, `return`, `security`, `dashed`, `default` | +| Dataflow | `emphasis`, `security`, `dashed`, `database`, `default` | +| Lifecycle | `start`, `active`, `waiting`, `decision`, `success`, `failure`, `neutral`, `external` | + +Labels are presentation only: they do not rename the stable kind, change +nodes/relationships, or create Semantic Lens edge facts. Sequence message and +Dataflow flow-variant entries are visual keys. Component/state entries backed +by exact compiled node facts receive the interactive Semantic Legend bridge; +this includes Dataflow `database` when a real `nodes[].type: "database"` fact +exists. + +Every relationship collection (`connections`, `edges`, `messages`, `flows`, and +`transitions`) accepts an optional author-controlled `id` using the shared ID +pattern. The renderer keeps its source-order runtime key separately, while the +authored ID enables a stable `#relation=` viewer link that survives array +reordering. ID-less documents remain valid and their relationship pins stay +local to the current page. + +Every semantic node collection (`components`, `nodes`, `participants`, and +`states`) also accepts one optional `brand`: either a canonical string returned +by `archify brands --json`, or a digest-pinned `{ "url", "sha256" }` object +returned by `archify brands capture --json`. Known IDs and known-brand +domains use the bundled vector catalogue. Unknown URLs must be captured in that +explicit command before authoring; render and validate never perform an +unpinned network capture. Unsafe, unavailable, changed, or unsupported content +fails closed with a brand diagnostic. Omitted `brand` preserves the prior +output. + +## schema_version policy + +Workflow supports schema versions 1 and 2. Version 1 remains the fixed-layout +compatibility contract; version 2 opts into the readable workflow compiler and +can be produced explicitly with `archify migrate workflow ... --to-schema 2`. +The other four diagram schemas keep `schema_version` pinned to `1`. + +Workflow also accepts optional `semanticChecks`. `allowedRoots` and +`allowedTerminals` close the set of intentional graph sources and sinks; +`requiredEdges` requires exact authored relationships; and `requiredPaths` +requires directed reachability while allowing intermediate nodes. The compiler +evaluates these facts before layout and returns typed `workflow/*` diagnostics. +The field is additive and geometry-neutral: omitting it preserves existing +workflow behavior and including a satisfied contract does not change SVG or +layout-receipt bytes. + +A file that validates today must keep validating and rendering within its +declared version throughout the 2.x release line. Additive viewer, +accessibility, and presentation improvements may enhance generated HTML, but +they must not reinterpret authored IR or turn a previously valid profile-less +v1 file into a new hard layout failure. Breaking IR changes require a new +version; additive, backwards-compatible fields do not. + +## Shared definitions (common.schema.json) + +The five diagram schemas reference `common.schema.json#/$defs/...`: + +- `id` — element identifiers, pattern `^[a-zA-Z][a-zA-Z0-9_-]*$` +- `point` — an `[x, y]` pair of numbers (used by `via` and `labelAt`) +- `componentType` — `frontend`, `backend`, `database`, `cloud`, `security`, + `messagebus`, `external` +- `locale` — the bounded renderer locale, `en` or `zh-CN` +- `brandMark` — one optional built-in brand ID or explicit HTTP(S) site URL +- `variant` — `default`, `emphasis`, `security`, `dashed` (sequence messages + extend this list locally with `return`) +- `legendMode` and `legendEntry` — the shared strict mode and label/visibility + override shapes used by each renderer-owned key map +- `guidedViews` — the bounded, read-only reader paths accepted by `meta.views` +- `cards` — the summary-card blocks rendered below the SVG + +Lifecycle state `type` is mode-specific (`start`/`active`/`waiting`/...) and +stays in `lifecycle.schema.json`. + +## Runtime validation + +At development time, `scripts/generate-validators.mjs` compiles all five +schemas with ajv's draft 2020-12 standalone generator using `strict: true` and +`allErrors: true`. The generated `renderers/shared/generated-validators.mjs` +is committed and shipped with the skill, so runtime validation has no npm or +network dependency. `renderers/shared/validator.mjs` applies the matching +standalone validator before the renderer's own layout checks. +The shared loader then checks cross-collection facts that JSON Schema cannot +express cleanly here: duplicate view IDs, duplicate focus IDs, focus IDs that do +not exist in the diagram's semantic collection, and duplicate authored +relationship IDs within the mode's relationship collection. + +Architecture additionally supports opt-in, revision-pinned repository evidence. +`meta.repository` names a public GitHub URL and full commit SHA; a component may +carry one to three `sources` with repo-relative POSIX paths, optional line +ranges, and optional labels. Shape is schema-checked, then the renderer requires +`--repo-root`: the local Git origin must match, and Git must prove the commit, +blobs, and requested lines. Verified evidence is embedded outside the canonical +SVG for the Semantic Passport and Node Finder; ordinary documents and visual +exports carry no repository evidence. + +## Visual quality and engineering truth + +`meta.quality_profile` and `meta.engineering_profile` answer different +questions. `quality_profile` is available in all five modes and controls how +strictly Archify judges composition. `engineering_profile` is an optional +Architecture-only semantic contract; omitting it preserves the ordinary v1 +behavior. + +The first engineering profile is `deployment-ownership`. Enable it only when +the user wants a fail-closed deployment review and the source facts are known. +It requires every non-external component to name an owner in `tag` and belong +to exactly one `region`; the document must contain both `region` and +`security-group` boundaries; every `database` must be inside a +`security-group`; each security group must contain members from one shared +region; and every connection whose region or security-group membership changes +must name the real crossing mechanism in `label`. + +The profile validates only authored IR. It does not discover infrastructure, +infer owners, or prove that a diagram matches a live environment. If a fact is +unknown, leave the profile unset or obtain the fact instead of inventing it. + +`npm test` runs the generator in check mode and fails when the committed +validators drift from their schemas. + +## Error format + +Schema violations exit non-zero. Each ajv error is reported on its own line as +the instance path — annotated with the nearest enclosing element's `id` or +`label` — followed by the message and parameters: + +```text +workflow schema validation failed: + /nodes/3 (id/label: "router") must NOT have additional properties {"additionalProperty":"colour"} +``` + +Schemas catch shape errors (types, enums, ranges, unknown fields); geometry +problems such as overlaps and label collisions are the renderers' job. diff --git a/tools/archify/schemas/architecture.schema.json b/tools/archify/schemas/architecture.schema.json new file mode 100644 index 0000000..b4cc68c --- /dev/null +++ b/tools/archify/schemas/architecture.schema.json @@ -0,0 +1,176 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/architecture.schema.json", + "title": "Archify Architecture Diagram", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "diagram_type", "meta", "components"], + "properties": { + "schema_version": { "const": 1 }, + "diagram_type": { "const": "architecture" }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": ["title"], + "properties": { + "title": { "type": "string", "minLength": 1 }, + "locale": { "$ref": "common.schema.json#/$defs/locale" }, + "subtitle": { "type": "string" }, + "output": { "type": "string" }, + "animation": { "$ref": "common.schema.json#/$defs/animation" }, + "visual_preset": { "$ref": "common.schema.json#/$defs/visualPreset" }, + "quality_profile": { "$ref": "common.schema.json#/$defs/qualityProfile" }, + "engineering_profile": { "enum": ["deployment-ownership"] }, + "repository": { + "type": "object", + "additionalProperties": false, + "required": ["url", "revision"], + "properties": { + "url": { + "type": "string", + "pattern": "^https://github\\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+(?:\\.git)?/?$" + }, + "revision": { "type": "string", "pattern": "^[a-fA-F0-9]{40}$" } + } + }, + "views": { "$ref": "common.schema.json#/$defs/guidedViews" }, + "legend": { + "type": "object", + "additionalProperties": false, + "properties": { + "mode": { "$ref": "common.schema.json#/$defs/legendMode" }, + "entries": { + "type": "object", + "additionalProperties": false, + "properties": { + "frontend": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "backend": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "database": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "cloud": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "security": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "messagebus": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "external": { "$ref": "common.schema.json#/$defs/legendEntry" } + } + } + } + }, + "viewBox": { + "type": "array", + "prefixItems": [ + { "type": "number", "minimum": 320 }, + { "type": "number", "minimum": 240 } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "layout": { + "type": "object", + "additionalProperties": false, + "required": ["mode"], + "properties": { + "mode": { "enum": ["grid"] }, + "origin": { "$ref": "common.schema.json#/$defs/point" }, + "cols": { "type": "integer", "minimum": 1, "maximum": 12 }, + "gapX": { "type": "number", "minimum": 0 }, + "gapY": { "type": "number", "minimum": 0 }, + "cellW": { "type": "number", "minimum": 40 }, + "cellH": { "type": "number", "minimum": 24 } + } + }, + "components": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "label"], + "properties": { + "id": { "$ref": "common.schema.json#/$defs/id" }, + "type": { "$ref": "common.schema.json#/$defs/componentType" }, + "label": { "type": "string", "minLength": 1 }, + "sublabel": { "type": "string" }, + "tag": { "type": "string" }, + "brand": { "$ref": "common.schema.json#/$defs/brandMark" }, + "sources": { + "type": "array", + "minItems": 1, + "maxItems": 3, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["path"], + "properties": { + "path": { "type": "string", "minLength": 1, "maxLength": 240 }, + "line": { "type": "integer", "minimum": 1 }, + "end_line": { "type": "integer", "minimum": 1 }, + "label": { "type": "string", "minLength": 1, "maxLength": 48 } + } + } + }, + "row": { "type": "integer", "minimum": 0 }, + "col": { "type": "integer", "minimum": 0 }, + "pos": { "$ref": "common.schema.json#/$defs/point" }, + "size": { + "type": "array", + "prefixItems": [ + { "type": "number", "exclusiveMinimum": 0 }, + { "type": "number", "exclusiveMinimum": 0 } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + } + }, + "boundaries": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "label", "wraps"], + "properties": { + "kind": { "enum": ["region", "security-group"] }, + "label": { "type": "string", "minLength": 1 }, + "wraps": { + "type": "array", + "minItems": 1, + "items": { "$ref": "common.schema.json#/$defs/id" } + }, + "pad": { "type": "number", "minimum": 0 } + } + } + }, + "connections": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["from", "to"], + "properties": { + "id": { "$ref": "common.schema.json#/$defs/id" }, + "from": { "$ref": "common.schema.json#/$defs/id" }, + "to": { "$ref": "common.schema.json#/$defs/id" }, + "label": { "type": "string" }, + "variant": { "$ref": "common.schema.json#/$defs/variant" }, + "fromSide": { "$ref": "common.schema.json#/$defs/side" }, + "toSide": { "$ref": "common.schema.json#/$defs/side" }, + "route": { "enum": ["auto", "straight", "orthogonal-h", "orthogonal-v"] }, + "via": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/point" } + }, + "labelAt": { "$ref": "common.schema.json#/$defs/point" }, + "labelDx": { "type": "number" }, + "labelDy": { "type": "number" }, + "labelSegment": { "type": "integer", "minimum": 0 }, + "width": { "$ref": "common.schema.json#/$defs/relationshipWidth" } + } + } + }, + "cards": { "$ref": "common.schema.json#/$defs/cards" } + } +} diff --git a/tools/archify/schemas/common.schema.json b/tools/archify/schemas/common.schema.json new file mode 100644 index 0000000..20a8515 --- /dev/null +++ b/tools/archify/schemas/common.schema.json @@ -0,0 +1,115 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/common.schema.json", + "title": "Archify Shared Definitions", + "$defs": { + "id": { + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$" + }, + "locale": { + "enum": ["en", "zh-CN"] + }, + "animation": { + "enum": ["trace", "none"] + }, + "visualPreset": { + "enum": ["classic", "signal-flow", "blueprint", "editorial"] + }, + "qualityProfile": { + "enum": ["standard", "showcase"] + }, + "side": { + "enum": ["left", "right", "top", "bottom"] + }, + "relationshipWidth": { + "type": "number", + "minimum": 0.5 + }, + "point": { + "type": "array", + "prefixItems": [ + { "type": "number" }, + { "type": "number" } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + }, + "componentType": { + "enum": ["frontend", "backend", "database", "cloud", "security", "messagebus", "external"] + }, + "brandMark": { + "oneOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "anyOf": [ + { "maxLength": 80, "pattern": "^[^\\r\\n]+$" }, + { "pattern": "^https?://" } + ] + }, + { + "type": "object", + "additionalProperties": false, + "required": ["url", "sha256"], + "properties": { + "url": { "type": "string", "minLength": 8, "maxLength": 2048, "pattern": "^https?://" }, + "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" } + } + } + ] + }, + "variant": { + "enum": ["default", "emphasis", "security", "dashed"] + }, + "legendMode": { + "enum": ["auto", "all", "hidden"] + }, + "legendEntry": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "label": { "type": "string", "minLength": 1, "maxLength": 80 }, + "visible": { "type": "boolean" } + } + }, + "guidedViews": { + "type": "array", + "maxItems": 5, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "label", "focus"], + "properties": { + "id": { "$ref": "#/$defs/id" }, + "label": { "type": "string", "minLength": 1, "maxLength": 48 }, + "focus": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/id" } + }, + "note": { "type": "string", "maxLength": 140 } + } + } + }, + "cards": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["dot", "title", "items"], + "properties": { + "dot": { "enum": ["cyan", "emerald", "violet", "amber", "rose", "orange", "slate"] }, + "title": { "type": "string", "minLength": 1 }, + "items": { + "type": "array", + "items": { "type": "string" } + } + } + } + } + } +} diff --git a/tools/archify/schemas/dataflow.schema.json b/tools/archify/schemas/dataflow.schema.json new file mode 100644 index 0000000..00f5148 --- /dev/null +++ b/tools/archify/schemas/dataflow.schema.json @@ -0,0 +1,243 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/dataflow.schema.json", + "title": "Archify Data Flow Diagram", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "diagram_type", + "meta", + "stages", + "nodes", + "flows" + ], + "properties": { + "schema_version": { + "const": 1 + }, + "diagram_type": { + "const": "dataflow" + }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "locale": { + "$ref": "common.schema.json#/$defs/locale" + }, + "subtitle": { + "type": "string" + }, + "output": { + "type": "string" + }, + "animation": { + "$ref": "common.schema.json#/$defs/animation" + }, + "visual_preset": { + "$ref": "common.schema.json#/$defs/visualPreset" + }, + "quality_profile": { + "$ref": "common.schema.json#/$defs/qualityProfile" + }, + "views": { + "$ref": "common.schema.json#/$defs/guidedViews" + }, + "legend": { + "type": "object", + "additionalProperties": false, + "properties": { + "mode": { "$ref": "common.schema.json#/$defs/legendMode" }, + "entries": { + "type": "object", + "additionalProperties": false, + "properties": { + "default": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "emphasis": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "security": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "dashed": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "database": { "$ref": "common.schema.json#/$defs/legendEntry" } + } + } + } + }, + "viewBox": { + "type": "array", + "prefixItems": [ + { + "type": "number", + "minimum": 360 + }, + { + "type": "number", + "minimum": 360 + } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "stages": { + "type": "array", + "minItems": 2, + "maxItems": 5, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string", + "minLength": 1 + } + } + } + }, + "nodes": { + "type": "array", + "minItems": 2, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "label", + "stage", + "row" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "type": { + "$ref": "common.schema.json#/$defs/componentType" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "sublabel": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "brand": { + "$ref": "common.schema.json#/$defs/brandMark" + }, + "stage": { + "type": "integer", + "minimum": 0 + }, + "row": { + "type": "integer", + "minimum": 0 + }, + "width": { + "type": "number", + "minimum": 48 + }, + "height": { + "type": "number", + "minimum": 36 + }, + "yOffset": { + "type": "number" + } + } + } + }, + "flows": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "from", + "to", + "label" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "from": { + "$ref": "common.schema.json#/$defs/id" + }, + "to": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "classification": { + "type": "string" + }, + "variant": { + "$ref": "common.schema.json#/$defs/variant" + }, + "route": { + "enum": [ + "auto", + "straight", + "vertical-channel", + "bottom-channel", + "top-channel" + ] + }, + "fromSide": { + "$ref": "common.schema.json#/$defs/side" + }, + "toSide": { + "$ref": "common.schema.json#/$defs/side" + }, + "channelX": { + "type": "number" + }, + "channelY": { + "type": "number" + }, + "labelAt": { + "$ref": "common.schema.json#/$defs/point" + }, + "labelDx": { + "type": "number" + }, + "labelDy": { + "type": "number" + }, + "labelSegment": { + "type": "integer", + "minimum": 0 + }, + "via": { + "type": "array", + "items": { + "$ref": "common.schema.json#/$defs/point" + } + }, + "width": { + "$ref": "common.schema.json#/$defs/relationshipWidth" + } + } + } + }, + "cards": { + "$ref": "common.schema.json#/$defs/cards" + } + } +} diff --git a/tools/archify/schemas/lifecycle.schema.json b/tools/archify/schemas/lifecycle.schema.json new file mode 100644 index 0000000..017c155 --- /dev/null +++ b/tools/archify/schemas/lifecycle.schema.json @@ -0,0 +1,266 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/lifecycle.schema.json", + "title": "Archify Lifecycle Diagram", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "diagram_type", + "meta", + "lanes", + "states", + "transitions" + ], + "properties": { + "schema_version": { + "const": 1 + }, + "diagram_type": { + "const": "lifecycle" + }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "locale": { + "$ref": "common.schema.json#/$defs/locale" + }, + "subtitle": { + "type": "string" + }, + "output": { + "type": "string" + }, + "animation": { + "$ref": "common.schema.json#/$defs/animation" + }, + "visual_preset": { + "$ref": "common.schema.json#/$defs/visualPreset" + }, + "quality_profile": { + "$ref": "common.schema.json#/$defs/qualityProfile" + }, + "views": { + "$ref": "common.schema.json#/$defs/guidedViews" + }, + "legend": { + "type": "object", + "additionalProperties": false, + "properties": { + "mode": { "$ref": "common.schema.json#/$defs/legendMode" }, + "entries": { + "type": "object", + "additionalProperties": false, + "properties": { + "start": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "active": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "waiting": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "decision": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "success": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "failure": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "neutral": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "external": { "$ref": "common.schema.json#/$defs/legendEntry" } + } + } + } + }, + "viewBox": { + "type": "array", + "prefixItems": [ + { + "type": "number", + "minimum": 420 + }, + { + "type": "number", + "minimum": 566 + } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "lanes": { + "type": "array", + "minItems": 1, + "maxItems": 4, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "label" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + } + } + } + }, + "states": { + "type": "array", + "minItems": 2, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "label", + "lane", + "col" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "type": { + "enum": [ + "start", + "active", + "waiting", + "decision", + "success", + "failure", + "neutral", + "external" + ] + }, + "label": { + "type": "string", + "minLength": 1 + }, + "sublabel": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "brand": { + "$ref": "common.schema.json#/$defs/brandMark" + }, + "step": { + "type": "string" + }, + "lane": { + "$ref": "common.schema.json#/$defs/id" + }, + "col": { + "type": "integer", + "minimum": 0, + "maximum": 4 + }, + "width": { + "type": "number", + "minimum": 48 + }, + "height": { + "type": "number", + "minimum": 36 + }, + "yOffset": { + "type": "number" + } + } + } + }, + "transitions": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "from", + "to" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "from": { + "$ref": "common.schema.json#/$defs/id" + }, + "to": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string" + }, + "note": { + "type": "string" + }, + "variant": { + "$ref": "common.schema.json#/$defs/variant" + }, + "route": { + "enum": [ + "auto", + "straight", + "drop", + "bottom-channel", + "top-channel", + "right-channel", + "left-channel" + ] + }, + "fromSide": { + "$ref": "common.schema.json#/$defs/side" + }, + "toSide": { + "$ref": "common.schema.json#/$defs/side" + }, + "channelX": { + "type": "number" + }, + "channelY": { + "type": "number" + }, + "cornerRadius": { + "type": "number", + "minimum": 0 + }, + "labelAt": { + "$ref": "common.schema.json#/$defs/point" + }, + "labelDx": { + "type": "number" + }, + "labelDy": { + "type": "number" + }, + "labelSegment": { + "type": "integer", + "minimum": 0 + }, + "via": { + "type": "array", + "items": { + "$ref": "common.schema.json#/$defs/point" + } + }, + "width": { + "$ref": "common.schema.json#/$defs/relationshipWidth" + } + } + } + }, + "cards": { + "$ref": "common.schema.json#/$defs/cards" + } + } +} diff --git a/tools/archify/schemas/sequence.schema.json b/tools/archify/schemas/sequence.schema.json new file mode 100644 index 0000000..446d0ec --- /dev/null +++ b/tools/archify/schemas/sequence.schema.json @@ -0,0 +1,223 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/sequence.schema.json", + "title": "Archify Sequence Diagram", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "diagram_type", + "meta", + "participants", + "messages" + ], + "properties": { + "schema_version": { + "const": 1 + }, + "diagram_type": { + "const": "sequence" + }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "locale": { + "$ref": "common.schema.json#/$defs/locale" + }, + "subtitle": { + "type": "string" + }, + "output": { + "type": "string" + }, + "animation": { + "$ref": "common.schema.json#/$defs/animation" + }, + "visual_preset": { + "$ref": "common.schema.json#/$defs/visualPreset" + }, + "quality_profile": { + "$ref": "common.schema.json#/$defs/qualityProfile" + }, + "column_fit": { + "description": "Horizontal participant layout. Omit this field or use fixed for the stable 86px boxes and 108px gap. Use spread when a wide viewBox would leave unused horizontal space or meaningful participant labels do not fit the fixed boxes; spread derives wider boxes and gaps from the viewBox without changing participant order or message semantics.", + "enum": ["fixed", "spread"] + }, + "views": { + "$ref": "common.schema.json#/$defs/guidedViews" + }, + "legend": { + "type": "object", + "additionalProperties": false, + "properties": { + "mode": { "$ref": "common.schema.json#/$defs/legendMode" }, + "entries": { + "type": "object", + "additionalProperties": false, + "properties": { + "default": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "emphasis": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "security": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "dashed": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "return": { "$ref": "common.schema.json#/$defs/legendEntry" } + } + } + } + }, + "viewBox": { + "type": "array", + "prefixItems": [ + { + "type": "number", + "minimum": 480 + }, + { + "type": "number", + "minimum": 480 + } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "participants": { + "type": "array", + "minItems": 2, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "label" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "type": { + "$ref": "common.schema.json#/$defs/componentType" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "sublabel": { + "type": "string" + }, + "brand": { + "$ref": "common.schema.json#/$defs/brandMark" + } + } + } + }, + "segments": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "from", + "to", + "label" + ], + "properties": { + "from": { + "type": "number" + }, + "to": { + "type": "number" + }, + "label": { + "type": "string", + "minLength": 1 + } + } + } + }, + "messages": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "from", + "to", + "y", + "label" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "from": { + "$ref": "common.schema.json#/$defs/id" + }, + "to": { + "$ref": "common.schema.json#/$defs/id" + }, + "y": { + "type": "number", + "minimum": 160 + }, + "label": { + "type": "string", + "minLength": 1 + }, + "variant": { + "enum": [ + "default", + "emphasis", + "security", + "dashed", + "return" + ] + }, + "note": { + "type": "string" + } + } + } + }, + "activations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "participant", + "from", + "to" + ], + "properties": { + "participant": { + "$ref": "common.schema.json#/$defs/id" + }, + "from": { + "type": "number" + }, + "to": { + "type": "number" + }, + "type": { + "$ref": "common.schema.json#/$defs/componentType" + } + } + } + }, + "cards": { + "$ref": "common.schema.json#/$defs/cards" + } + } +} diff --git a/tools/archify/schemas/workflow.schema.json b/tools/archify/schemas/workflow.schema.json new file mode 100644 index 0000000..1ccaf17 --- /dev/null +++ b/tools/archify/schemas/workflow.schema.json @@ -0,0 +1,428 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/workflow.schema.json", + "title": "Archify Workflow Diagram", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "diagram_type", + "meta", + "lanes", + "nodes", + "edges" + ], + "properties": { + "schema_version": { + "enum": [ + 1, + 2 + ] + }, + "diagram_type": { + "const": "workflow" + }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "locale": { + "$ref": "common.schema.json#/$defs/locale" + }, + "subtitle": { + "type": "string" + }, + "output": { + "type": "string" + }, + "animation": { + "enum": [ + "trace", + "none" + ] + }, + "visual_preset": { + "enum": [ + "classic", + "signal-flow", + "blueprint", + "editorial" + ] + }, + "quality_profile": { + "enum": [ + "standard", + "showcase" + ] + }, + "views": { + "$ref": "common.schema.json#/$defs/guidedViews" + }, + "legend": { + "type": "object", + "additionalProperties": false, + "properties": { + "mode": { "$ref": "common.schema.json#/$defs/legendMode" }, + "entries": { + "type": "object", + "additionalProperties": false, + "properties": { + "frontend": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "backend": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "database": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "cloud": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "security": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "messagebus": { "$ref": "common.schema.json#/$defs/legendEntry" }, + "external": { "$ref": "common.schema.json#/$defs/legendEntry" } + } + } + } + }, + "viewBox": { + "type": "array", + "prefixItems": [ + { + "type": "number", + "minimum": 700 + }, + { + "type": "number", + "minimum": 240 + } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "lanes": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "label" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "variant": { + "enum": [ + "normal", + "exception" + ] + } + } + } + }, + "phases": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "label", + "fromCol", + "toCol" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "fromCol": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "toCol": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "variant": { + "enum": [ + "default", + "emphasis", + "security", + "dashed" + ] + } + } + } + }, + "groups": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "label", + "lane", + "fromCol", + "toCol" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "lane": { + "$ref": "common.schema.json#/$defs/id" + }, + "fromCol": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "toCol": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "variant": { + "enum": [ + "default", + "emphasis", + "security", + "dashed" + ] + } + } + } + }, + "mainPath": { + "type": "array", + "minItems": 2, + "items": { + "$ref": "common.schema.json#/$defs/id" + } + }, + "semanticChecks": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "allowedRoots": { + "type": "array", + "items": { + "$ref": "common.schema.json#/$defs/id" + } + }, + "allowedTerminals": { + "type": "array", + "items": { + "$ref": "common.schema.json#/$defs/id" + } + }, + "requiredEdges": { + "type": "array", + "items": { + "$ref": "#/$defs/semanticRelation" + } + }, + "requiredPaths": { + "type": "array", + "items": { + "$ref": "#/$defs/semanticRelation" + } + } + } + }, + "nodes": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "lane", + "col", + "type", + "label" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "lane": { + "$ref": "common.schema.json#/$defs/id" + }, + "col": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "type": { + "$ref": "common.schema.json#/$defs/componentType" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "sublabel": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "brand": { + "$ref": "common.schema.json#/$defs/brandMark" + }, + "width": { + "type": "number", + "minimum": 32 + }, + "height": { + "type": "number", + "minimum": 32 + }, + "yOffset": { + "type": "number" + } + } + } + }, + "edges": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "from", + "to" + ], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "from": { + "$ref": "common.schema.json#/$defs/id" + }, + "to": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string" + }, + "variant": { + "$ref": "common.schema.json#/$defs/variant" + }, + "role": { + "enum": [ + "main", + "branch", + "async", + "return", + "error" + ] + }, + "fromSide": { + "$ref": "#/$defs/side" + }, + "toSide": { + "$ref": "#/$defs/side" + }, + "route": { + "enum": [ + "auto", + "straight", + "drop", + "outside-right", + "return-left", + "bottom-channel", + "up-channel" + ] + }, + "via": { + "type": "array", + "items": { + "$ref": "common.schema.json#/$defs/point" + } + }, + "labelAt": { + "$ref": "common.schema.json#/$defs/point" + }, + "labelDx": { + "type": "number" + }, + "labelDy": { + "type": "number" + }, + "labelSegment": { + "type": "integer", + "minimum": 0 + }, + "channelX": { + "type": "number" + }, + "channelY": { + "type": "number" + }, + "bias": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "width": { + "type": "number", + "minimum": 0.5 + } + } + } + }, + "cards": { + "$ref": "common.schema.json#/$defs/cards" + } + }, + "$defs": { + "semanticRelation": { + "type": "object", + "additionalProperties": false, + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "$ref": "common.schema.json#/$defs/id" + }, + "to": { + "$ref": "common.schema.json#/$defs/id" + } + } + }, + "side": { + "enum": [ + "left", + "right", + "top", + "bottom" + ] + } + } +} diff --git a/tools/archify/scripts/check-render-output.mjs b/tools/archify/scripts/check-render-output.mjs new file mode 100644 index 0000000..704b965 --- /dev/null +++ b/tools/archify/scripts/check-render-output.mjs @@ -0,0 +1,835 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { collectAmbiguousCorridors, collectBorderRuns, collectLabelRouteClearance, collectRouteRhythmIssues, routeBudgetMetrics } from '../renderers/shared/geometry.mjs'; +import { + DESKTOP_READABILITY_VIEWPORT, + DESKTOP_READER_DIAGRAM_WIDTH, + MIN_PROJECTED_NODE_TEXT_PX, + projectedNodeTextPx, +} from '../renderers/shared/desktop-readability.mjs'; + +const input = process.argv[2]; + +if (!input || input === '-h' || input === '--help') { + console.error('Usage: node scripts/check-render-output.mjs '); + process.exit(input ? 0 : 2); +} + +const htmlPath = path.resolve(input); +let html; +try { + html = fs.readFileSync(htmlPath, 'utf8'); +} catch (err) { + console.error(JSON.stringify({ + ok: false, + file: htmlPath, + checks: [{ name: 'file_readable', ok: false, details: [err.message] }], + }, null, 2)); + process.exit(1); +} + +const checks = []; +let composition = { + schemaVersion: 1, + profile: 'standard', + status: 'pass', + summary: { errors: 0, warnings: 0 }, + metrics: { + properCrossings: 0, + ambiguousCorridors: 0, + containerBorderRuns: 0, + labelRouteClearanceIssues: 0, + minLabelRouteClearance: null, + maxBends: 0, + routesOverSuggestedBends: 0, + maxStretch: null, + routesOverSuggestedStretch: 0, + minSegmentPx: null, + minInteriorSegmentPx: null, + shortSegmentCount: 0, + shortEndpointSegmentCount: 0, + shortInteriorSegmentCount: 0, + microSegmentCount: 0, + desktopReadabilityIssues: 0, + minProjectedNodeTextPx: null, + }, + suggestedLimits: { bendsPerRelationship: 2, stretch: 1.35, segmentPx: 16, microSegmentPx: 8 }, + issues: [], +}; + +function addCheck(name, ok, details = []) { + checks.push({ name, ok, details }); +} + +const svgMatches = [...html.matchAll(//gi)]; +addCheck('single_svg', svgMatches.length === 1, [`found ${svgMatches.length} block(s)`]); + +if (svgMatches.length === 1) { + const svg = svgMatches[0][0]; + const svgRoot = svg.match(/]*>/i)?.[0] || ''; + const svgAttrs = parseAttrs(svgRoot); + const qualityProfile = svgAttrs['data-quality-profile'] || 'standard'; + const qualityGatesEnforced = svgAttrs['data-quality-gates'] !== 'advisory'; + addCheck('finite_svg', !/\b(?:NaN|undefined|Infinity|-Infinity)\b/.test(svg)); + const legendStart = svg.indexOf(''); + const beforeLegend = legendStart >= 0 ? svg.slice(0, legendStart) : svg; + const desktopReadabilityIssue = collectDesktopReadability(svgAttrs, beforeLegend); + const arrows = collectArrows(beforeLegend); + const diagonal = arrows.flatMap((arrow) => diagonalStraightSegments(arrow).map((segment) => ({ arrow, ...segment }))); + addCheck( + 'orthogonal_arrows', + diagonal.length === 0, + diagonal.map(({ arrow, segmentIndex }) => `${arrow.kind} ${arrow.index} segment ${segmentIndex + 1}: ${arrow.raw}`), + ); + const relationshipCrossings = collectRelationshipCrossings(arrows); + const compositionFrames = collectCompositionFrames(beforeLegend); + const containerBorderRuns = collectBorderRuns({ + routedRelations: arrows + .filter((arrow) => arrow.from && arrow.to && arrow.borderSegments.length) + .map((arrow) => ({ + relation: arrow, + relationIndex: arrow.index, + segments: arrow.borderSegments, + })), + frames: compositionFrames, + }); + const routedRelationships = arrows + .filter((arrow) => arrow.from && arrow.to && arrow.routePoints.length) + .map((arrow) => ({ relation: arrow, relationIndex: arrow.index, points: arrow.routePoints })); + const routeMetrics = routeBudgetMetrics({ routedRelations: routedRelationships }); + const routeRhythmIssues = collectRouteRhythmIssues({ routedRelations: routedRelationships }); + const ambiguousCorridors = collectAmbiguousCorridors({ routedRelations: routedRelationships }); + const relationshipLabels = collectRelationshipLabelMasks(beforeLegend, arrows); + const labelClearanceThreshold = qualityProfile === 'showcase' ? 4 : 2; + const labelRouteMeasurements = collectLabelRouteClearance({ + labels: relationshipLabels, + routedRelations: arrows.map((arrow) => ({ relation: arrow, relationIndex: arrow.index, points: arrow.routePoints })), + threshold: Number.MAX_VALUE, + }); + const labelRouteClearance = collectLabelRouteClearance({ + labels: relationshipLabels, + routedRelations: arrows.map((arrow) => ({ relation: arrow, relationIndex: arrow.index, points: arrow.routePoints })), + threshold: labelClearanceThreshold, + }); + const crossingIsError = qualityProfile === 'showcase'; + const corridorIsError = qualityProfile === 'showcase'; + const rhythmIsError = qualityProfile === 'showcase'; + const labelClearanceIsError = qualityProfile === 'showcase'; + const desktopReadabilityIsError = qualityProfile === 'showcase'; + const compositionErrors = (qualityGatesEnforced ? containerBorderRuns.length : 0) + + (crossingIsError ? relationshipCrossings.length : 0) + + (corridorIsError ? ambiguousCorridors.length : 0) + + (labelClearanceIsError ? labelRouteClearance.length : 0) + + (rhythmIsError ? routeRhythmIssues.length : 0) + + (desktopReadabilityIsError && desktopReadabilityIssue ? 1 : 0); + const compositionWarnings = (qualityGatesEnforced ? 0 : containerBorderRuns.length) + + (crossingIsError ? 0 : relationshipCrossings.length) + + (corridorIsError ? 0 : ambiguousCorridors.length) + + (labelClearanceIsError ? 0 : labelRouteClearance.length) + + (rhythmIsError ? 0 : routeRhythmIssues.length) + + (desktopReadabilityIsError || !desktopReadabilityIssue ? 0 : 1); + composition = { + schemaVersion: 1, + profile: qualityProfile, + status: compositionErrors ? 'fail' : 'pass', + summary: { + errors: compositionErrors, + warnings: compositionWarnings, + }, + metrics: { + properCrossings: relationshipCrossings.length, + ambiguousCorridors: ambiguousCorridors.length, + containerBorderRuns: containerBorderRuns.length, + labelRouteClearanceIssues: labelRouteClearance.length, + minLabelRouteClearance: labelRouteMeasurements.length + ? Math.round(Math.min(...labelRouteMeasurements.map((hit) => hit.clearance)) * 10) / 10 + : null, + desktopReadabilityIssues: desktopReadabilityIssue ? 1 : 0, + minProjectedNodeTextPx: desktopReadabilityIssue?.projectedFontPx ?? null, + ...roundedRouteMetrics(routeMetrics), + }, + suggestedLimits: { bendsPerRelationship: 2, stretch: 1.35, segmentPx: 16, microSegmentPx: 8 }, + issues: [ + ...containerBorderRuns.map((hit) => ({ + severity: qualityGatesEnforced ? 'error' : 'warning', + code: 'composition/container-border-run', + relationship: relationshipRecord(hit.relation), + frame: frameRecord(hit.frame), + side: hit.side, + segmentIndex: hit.segmentIndex, + overlapLength: Math.round(hit.overlapLength * 10) / 10, + from: hit.overlapStart.map((value) => Math.round(value * 10) / 10), + to: hit.overlapEnd.map((value) => Math.round(value * 10) / 10), + })), + ...labelRouteClearance.map((hit) => ({ + severity: labelClearanceIsError ? 'error' : 'warning', + code: 'composition/label-route-clearance', + label: hit.label?.label || hit.labelRelation?.label || '', + labelRelationship: relationshipRecord(hit.labelRelation), + otherRelationship: relationshipRecord(hit.otherRelation), + segmentIndex: hit.segmentIndex, + labelRect: roundedRect(hit.rect), + clearance: Math.round(hit.clearance * 10) / 10, + intersectionLength: Math.round((hit.intersectionLength || 0) * 10) / 10, + threshold: hit.threshold, + from: hit.start.map((value) => Math.round(value * 10) / 10), + to: hit.end.map((value) => Math.round(value * 10) / 10), + })), + ...relationshipCrossings.map((hit) => ({ + severity: crossingIsError ? 'error' : 'warning', + code: 'composition/proper-crossing', + relationship: relationshipRecord(hit.left), + otherRelationship: relationshipRecord(hit.right), + point: hit.point.map((value) => Math.round(value * 10) / 10), + })), + ...ambiguousCorridors.map((hit) => ({ + severity: corridorIsError ? 'error' : 'warning', + code: 'composition/ambiguous-corridor', + relationship: relationshipRecord(hit.left.relation), + otherRelationship: relationshipRecord(hit.right.relation), + segmentIndex: hit.leftSegment, + otherSegmentIndex: hit.rightSegment, + overlapLength: Math.round(hit.overlapLength * 10) / 10, + from: hit.overlapStart.map((value) => Math.round(value * 10) / 10), + to: hit.overlapEnd.map((value) => Math.round(value * 10) / 10), + })), + ...routeRhythmIssues.map((hit) => ({ + severity: rhythmIsError ? 'error' : 'warning', + code: hit.code, + relationship: relationshipRecord(hit.relation), + segmentIndex: hit.segmentIndex, + position: hit.position, + length: Math.round(hit.length * 10) / 10, + from: hit.start.map((value) => Math.round(value * 10) / 10), + to: hit.end.map((value) => Math.round(value * 10) / 10), + })), + ...(desktopReadabilityIssue ? [{ + severity: desktopReadabilityIsError ? 'error' : 'warning', + code: 'composition/desktop-readability', + viewportWidth: DESKTOP_READABILITY_VIEWPORT.width, + viewportHeight: DESKTOP_READABILITY_VIEWPORT.height, + availableDiagramWidth: DESKTOP_READER_DIAGRAM_WIDTH, + viewBoxWidth: desktopReadabilityIssue.viewBoxWidth, + scale: desktopReadabilityIssue.scale, + text: desktopReadabilityIssue.text, + detail: desktopReadabilityIssue.detail, + sourceFontPx: desktopReadabilityIssue.sourceFontPx, + projectedFontPx: desktopReadabilityIssue.projectedFontPx, + minimumProjectedFontPx: MIN_PROJECTED_NODE_TEXT_PX, + }] : []), + ], + }; + addCheck( + 'label_route_clearance', + !labelClearanceIsError || labelRouteClearance.length === 0, + labelRouteClearance.map((hit) => ( + `[composition/label-route-clearance] ${qualityProfile} label "${hit.label?.label || hit.labelRelation?.label || ''}" on ${relationshipName(hit.labelRelation)} is ${Math.round(hit.clearance * 10) / 10}px from ${relationshipName(hit.otherRelation)} segment ${hit.segmentIndex} [${formatPoint(hit.start)}] -> [${formatPoint(hit.end)}]${hit.intersectionLength > 0 ? ` with ${Math.round(hit.intersectionLength * 10) / 10}px hidden by the mask` : ''} (minimum ${hit.threshold}px) — use renderer-supported label controls (message y for sequence; otherwise labelAt, labelDx, labelDy, or labelSegment), or adjust the other relationship route/via/channel.` + )), + ); + addCheck( + 'relationship_crossings', + !crossingIsError || relationshipCrossings.length === 0, + relationshipCrossings.map((hit) => ( + `[composition/proper-crossing] ${qualityProfile} ${relationshipName(hit.left)} crosses ${relationshipName(hit.right)} at [${formatPoint(hit.point)}]` + )), + ); + addCheck( + 'relationship_corridors', + !corridorIsError || ambiguousCorridors.length === 0, + ambiguousCorridors.map((hit) => ( + `[composition/ambiguous-corridor] ${qualityProfile} ${relationshipName(hit.left.relation)} shares a ${Math.round(hit.overlapLength * 10) / 10}px corridor with ${relationshipName(hit.right.relation)} at [${formatPoint(hit.overlapStart)}] -> [${formatPoint(hit.overlapEnd)}]` + )), + ); + addCheck( + 'container_border_runs', + !qualityGatesEnforced || containerBorderRuns.length === 0, + containerBorderRuns.map((hit) => ( + `[composition/container-border-run] ${relationshipName(hit.relation)} follows ${frameName(hit.frame)} ${hit.side} border for ${Math.round(hit.overlapLength * 10) / 10}px on segment ${hit.segmentIndex} [${formatPoint(hit.overlapStart)}] -> [${formatPoint(hit.overlapEnd)}]` + )), + ); + addCheck( + 'route_rhythm', + !rhythmIsError || routeRhythmIssues.length === 0, + routeRhythmIssues.map((hit) => ( + `[${hit.code}] ${qualityProfile} ${relationshipName(hit.relation)} has a ${Math.round(hit.length * 10) / 10}px ${hit.position} segment ${hit.segmentIndex} [${formatPoint(hit.start)}] -> [${formatPoint(hit.end)}]` + )), + ); + + if (legendStart >= 0) { + const legendFragment = svg.slice(legendStart); + const legendBoxes = collectLegendBoxes(legendFragment); + const collisions = collectLegendCollisions(arrows, legendBoxes); + addCheck( + 'legend_clearance', + collisions.length === 0, + collisions.map((hit) => `${hit.arrow.kind} ${hit.arrow.index} crosses legend ${hit.box.label}`), + ); + } else { + addCheck('legend_clearance', true, ['no legend marker found']); + } +} + +const ok = checks.every((check) => check.ok) && composition.status !== 'fail'; +console.log(JSON.stringify({ ok, file: htmlPath, checks, composition }, null, 2)); +process.exit(ok ? 0 : 1); + +function collectArrows(fragment) { + const arrows = []; + let index = 0; + + for (const tag of fragment.matchAll(/<(path|line)\b[^>]*>/gi)) { + const raw = tag[0]; + if (!/\bclass="[^"]*\ba-(?:default|emphasis|security|dashed)\b/.test(raw)) continue; + if (!/\bmarker-end=/.test(raw)) continue; + const attrs = parseAttrs(raw); + const segments = tag[1].toLowerCase() === 'line' + ? lineSegments(attrs) + : pathSegments(attrs.d || ''); + const borderSegments = tag[1].toLowerCase() === 'line' + ? segments + : straightPathSegments(attrs.d || ''); + arrows.push({ + kind: tag[1].toLowerCase(), + index: index += 1, + raw, + segments, + borderSegments, + routePoints: parseRoutePoints(attrs['data-composition-points']) || ( + borderSegments.length ? [borderSegments[0].start, ...borderSegments.map((segment) => segment.end)] : [] + ), + from: attrs['data-edge-from'] || attrs['data-composition-edge-from'], + to: attrs['data-edge-to'] || attrs['data-composition-edge-to'], + id: attrs['data-edge-id'] || attrs['data-composition-edge-id'], + key: attrs['data-edge-key'], + label: attrs['data-edge-label'], + offset: tag.index, + }); + } + + return arrows; +} + +function collectRelationshipLabelMasks(fragment, arrows) { + const labels = []; + for (const match of fragment.matchAll(/]*\bdata-edge-(?:key|id|from)="[^"]*"[^>]*>[\s\S]*?<\/g>/gi)) { + const group = match[0]; + const groupAttrs = parseAttrs(group.match(/]*>/i)?.[0] || ''); + const rectTag = [...group.matchAll(/]*>/gi)] + .map((item) => item[0]) + .find((tag) => /\bclass="[^"]*\bc-mask\b/.test(tag)); + if (!rectTag) continue; + const attrs = parseAttrs(rectTag); + const rect = { + x: numberAttr(attrs, 'x'), + y: numberAttr(attrs, 'y'), + width: numberAttr(attrs, 'width'), + height: numberAttr(attrs, 'height'), + }; + if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) continue; + const groupStart = match.index; + const groupEnd = groupStart + group.length; + const containedOwner = arrows.find((arrow) => ( + arrow.offset > groupStart + && arrow.offset < groupEnd + && arrow.from === groupAttrs['data-edge-from'] + && arrow.to === groupAttrs['data-edge-to'] + && (!groupAttrs['data-edge-id'] || !arrow.id || arrow.id === groupAttrs['data-edge-id']) + )); + const owner = arrows.find((arrow) => ( + groupAttrs['data-edge-key'] !== undefined && arrow.key === groupAttrs['data-edge-key'] + )) || containedOwner || arrows.find((arrow) => ( + arrow.id === groupAttrs['data-edge-id'] + && arrow.from === groupAttrs['data-edge-from'] + && arrow.to === groupAttrs['data-edge-to'] + )); + if (!owner) continue; + if (owner.key === undefined && groupAttrs['data-edge-key'] !== undefined) owner.key = groupAttrs['data-edge-key']; + if (!owner.id && groupAttrs['data-edge-id']) owner.id = groupAttrs['data-edge-id']; + if (!owner.label && groupAttrs['data-edge-label']) owner.label = groupAttrs['data-edge-label']; + labels.push({ + relation: owner, + relationIndex: owner.index, + label: groupAttrs['data-edge-label'] || '', + rect, + }); + } + return labels; +} + +function roundedRect(rect) { + return Object.fromEntries(Object.entries(rect).map(([key, value]) => [key, Math.round(value * 10) / 10])); +} + +function roundedRouteMetrics(metrics) { + return { + ...metrics, + maxStretch: metrics.maxStretch == null ? null : Math.round(metrics.maxStretch * 1000) / 1000, + minSegmentPx: metrics.minSegmentPx == null ? null : Math.round(metrics.minSegmentPx * 10) / 10, + minInteriorSegmentPx: metrics.minInteriorSegmentPx == null ? null : Math.round(metrics.minInteriorSegmentPx * 10) / 10, + }; +} + +function parseRoutePoints(value) { + if (!value) return null; + const points = value.split(';').map((pair) => pair.split(',').map(Number)); + return points.length >= 2 && points.every(isPoint) ? points : null; +} + +function collectRelationshipCrossings(arrows) { + const relationships = arrows.filter((arrow) => arrow.from && arrow.to && arrow.segments.length); + const crossings = []; + for (let leftIndex = 0; leftIndex < relationships.length; leftIndex += 1) { + const left = relationships[leftIndex]; + for (let rightIndex = leftIndex + 1; rightIndex < relationships.length; rightIndex += 1) { + const right = relationships[rightIndex]; + if ([left.from, left.to].some((id) => id === right.from || id === right.to)) continue; + let point = null; + for (const leftSegment of left.segments) { + for (const rightSegment of right.segments) { + point = properSegmentIntersection(leftSegment.start, leftSegment.end, rightSegment.start, rightSegment.end); + if (point) break; + } + if (point) break; + } + if (point) crossings.push({ left, right, point }); + } + } + return crossings; +} + +function relationshipName(arrow) { + return arrow.id + ? `relationship id "${arrow.id}" ("${arrow.from}" -> "${arrow.to}")` + : `relationship "${arrow.from}" -> "${arrow.to}"`; +} + +function relationshipRecord(arrow) { + const stableIndex = Number(arrow.key); + return { + id: arrow.id, + from: arrow.from, + to: arrow.to, + label: arrow.label || '', + collectionIndex: Number.isInteger(stableIndex) && stableIndex >= 0 ? stableIndex : arrow.index - 1, + artifactIndex: arrow.index, + }; +} + +function collectCompositionFrames(fragment) { + const frames = []; + for (const match of fragment.matchAll(/<(rect|path|line)\b[^>]*>/gi)) { + const attrs = parseAttrs(match[0]); + const kind = attrs['data-composition-frame-kind']; + if (!kind) continue; + const identity = attrs['data-composition-frame-id'] || frames.length; + if (match[1].toLowerCase() === 'rect') { + const frame = { + kind, + id: identity, + x: numberAttr(attrs, 'x'), + y: numberAttr(attrs, 'y'), + width: numberAttr(attrs, 'width'), + height: numberAttr(attrs, 'height'), + radius: numberAttr(attrs, 'rx') || 0, + }; + if ([frame.x, frame.y, frame.width, frame.height].every(Number.isFinite)) frames.push(frame); + continue; + } + const segments = match[1].toLowerCase() === 'line' + ? lineSegments(attrs) + : pathSegments(attrs.d || ''); + for (const [segmentIndex, segment] of segments.entries()) { + frames.push({ + kind, + id: segments.length > 1 ? `${identity}:${segmentIndex}` : identity, + shape: 'line', + start: segment.start, + end: segment.end, + }); + } + } + return frames; +} + +function frameName(frame) { + return `${frame.kind || 'frame'} "${frame.id}"`; +} + +function frameRecord(frame) { + return { kind: frame.kind, id: frame.id }; +} + +function formatPoint(point) { + return point.map((value) => Math.round(value * 10) / 10).join(', '); +} + +function lineSegments(attrs) { + const start = [numberAttr(attrs, 'x1'), numberAttr(attrs, 'y1')]; + const end = [numberAttr(attrs, 'x2'), numberAttr(attrs, 'y2')]; + if (!isPoint(start) || !isPoint(end)) return []; + return [{ start, end }]; +} + +function pathSegments(d) { + const points = pointsFromPath(d); + const segments = []; + for (let i = 1; i < points.length; i += 1) { + segments.push({ start: points[i - 1], end: points[i] }); + } + return segments; +} + +// Border runs use exact visible primitives. Non-collinear Q curves are never +// flattened into chords here: a tangent or sampled near-horizontal curve is +// not a structural border run. A fully collinear Q remains a straight visible +// primitive and is included. +function straightPathSegments(d) { + const tokens = d.match(/[MLHVQZmlhvqz]|[-+]?(?:\d*\.)?\d+(?:e[-+]?\d+)?/g) || []; + const segments = []; + let i = 0; + let command = ''; + let current = [0, 0]; + let start = null; + while (i < tokens.length) { + if (isCommand(tokens[i])) command = tokens[i++]; + if (!command) break; + const absolute = command === command.toUpperCase(); + switch (command.toUpperCase()) { + case 'M': + case 'L': { + let first = true; + while (i + 1 < tokens.length && !isCommand(tokens[i])) { + const point = [Number.parseFloat(tokens[i++]), Number.parseFloat(tokens[i++])]; + if (!point.every(Number.isFinite)) break; + const next = absolute ? point : [current[0] + point[0], current[1] + point[1]]; + if (command.toUpperCase() === 'L' || !first) segments.push({ start: current, end: next }); + current = next; + if (!start) start = current; + first = false; + } + break; + } + case 'H': { + while (i < tokens.length && !isCommand(tokens[i])) { + const value = Number.parseFloat(tokens[i++]); + if (!Number.isFinite(value)) break; + const next = [absolute ? value : current[0] + value, current[1]]; + segments.push({ start: current, end: next }); + current = next; + } + break; + } + case 'V': { + while (i < tokens.length && !isCommand(tokens[i])) { + const value = Number.parseFloat(tokens[i++]); + if (!Number.isFinite(value)) break; + const next = [current[0], absolute ? value : current[1] + value]; + segments.push({ start: current, end: next }); + current = next; + } + break; + } + case 'Q': { + while (i + 3 < tokens.length && !isCommand(tokens[i])) { + const values = [0, 0, 0, 0].map(() => Number.parseFloat(tokens[i++])); + if (!values.every(Number.isFinite)) break; + const control = absolute ? values.slice(0, 2) : [current[0] + values[0], current[1] + values[1]]; + const end = absolute ? values.slice(2, 4) : [current[0] + values[2], current[1] + values[3]]; + if (Math.abs(crossProduct(current, control, end)) <= 1e-9) segments.push({ start: current, end }); + current = end; + } + break; + } + case 'Z': + if (start) segments.push({ start: current, end: start }); + current = start || current; + command = ''; + break; + default: + return []; + } + } + return segments.filter(({ start: a, end: b }) => isPoint(a) && isPoint(b)); +} + +function diagonalStraightSegments(arrow) { + return arrow.borderSegments.flatMap(({ start, end }, segmentIndex) => ( + Math.abs(start[0] - end[0]) > 0.01 && Math.abs(start[1] - end[1]) > 0.01 + ? [{ segmentIndex, start, end }] + : [] + )); +} + +function collectLegendBoxes(fragment) { + const boxes = []; + + for (const match of fragment.matchAll(/]*>/gi)) { + const attrs = parseAttrs(match[0]); + const x = numberAttr(attrs, 'x'); + const y = numberAttr(attrs, 'y'); + const width = numberAttr(attrs, 'width'); + const height = numberAttr(attrs, 'height'); + if ([x, y, width, height].every(Number.isFinite)) { + boxes.push({ x1: x, y1: y, x2: x + width, y2: y + height, label: `rect@${x},${y}` }); + } + } + + for (const match of fragment.matchAll(/]*)>([\s\S]*?)<\/text>/gi)) { + const attrs = parseAttrs(match[1]); + const box = textBox(attrs, stripTags(match[2]).trim()); + if (box) boxes.push(box); + } + + return boxes; +} + +function collectLegendCollisions(arrows, boxes) { + const collisions = []; + for (const arrow of arrows) { + for (const segment of arrow.segments) { + for (const box of boxes) { + if (segmentIntersectsBox(segment, padBox(box, 2))) { + collisions.push({ arrow, box }); + } + } + } + } + return collisions; +} + +function textBox(attrs, text) { + const x = numberAttr(attrs, 'x'); + const y = numberAttr(attrs, 'y'); + const fontSize = Number.parseFloat(attrs['font-size'] || '10'); + if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(fontSize)) return null; + const width = estimatedTextWidth(text, fontSize); + const anchor = attrs['text-anchor'] || 'start'; + let x1 = x; + if (anchor === 'middle') x1 = x - width / 2; + if (anchor === 'end') x1 = x - width; + return { + x1, + y1: y - fontSize, + x2: x1 + width, + y2: y + fontSize * 0.25, + label: text || `text@${x},${y}`, + }; +} + +function collectDesktopReadability(svgAttrs, fragment) { + const viewBox = String(svgAttrs.viewBox || '').trim().split(/[\s,]+/).map(Number); + const viewBoxWidth = viewBox.length === 4 ? viewBox[2] : Number.NaN; + if (!Number.isFinite(viewBoxWidth) || viewBoxWidth <= 0) return null; + const scale = Math.min(1, DESKTOP_READER_DIAGRAM_WIDTH / viewBoxWidth); + let worst = null; + for (const match of fragment.matchAll(/]*)>([\s\S]*?)<\/text>/gi)) { + const primary = /\bdata-node-label(?:\s*=|\s|$)/i.test(match[1]); + const boundary = /\bdata-boundary-label(?:\s*=|\s|$)/i.test(match[1]); + const context = /\bdata-detail\s*=\s*"context"/i.test(match[1]); + if (!primary && !boundary && !context) continue; + const attrs = parseAttrs(match[1]); + const fontSize = Number.parseFloat(attrs['font-size'] || ''); + if (!Number.isFinite(fontSize)) continue; + const projected = projectedNodeTextPx(fontSize, viewBoxWidth); + if (projected >= MIN_PROJECTED_NODE_TEXT_PX) continue; + const candidate = { + viewBoxWidth, + scale, + text: stripTags(match[2]).trim(), + detail: primary + ? 'primary' + : boundary ? 'boundary' : 'context', + sourceFontPx: fontSize, + projectedFontPx: projected, + }; + if (!worst || candidate.projectedFontPx < worst.projectedFontPx) worst = candidate; + } + return worst; +} + +function estimatedTextWidth(text, fontSize) { + let units = 0; + for (const char of text) units += char.charCodeAt(0) > 255 ? 1.8 : 0.62; + return Math.max(fontSize, units * fontSize); +} + +function pointsFromPath(d) { + const tokens = d.match(/[MLHVQZmlhvqz]|[-+]?(?:\d*\.)?\d+(?:e[-+]?\d+)?/g) || []; + const points = []; + let i = 0; + let command = ''; + let current = [0, 0]; + let start = null; + + while (i < tokens.length) { + if (isCommand(tokens[i])) command = tokens[i++]; + if (!command) break; + + const absolute = command === command.toUpperCase(); + switch (command.toUpperCase()) { + case 'M': + case 'L': { + while (i + 1 < tokens.length && !isCommand(tokens[i])) { + const x = Number.parseFloat(tokens[i++]); + const y = Number.parseFloat(tokens[i++]); + if (!Number.isFinite(x) || !Number.isFinite(y)) break; + current = absolute ? [x, y] : [current[0] + x, current[1] + y]; + points.push(current); + if (!start) start = current; + } + break; + } + case 'H': { + while (i < tokens.length && !isCommand(tokens[i])) { + const x = Number.parseFloat(tokens[i++]); + if (!Number.isFinite(x)) break; + current = absolute ? [x, current[1]] : [current[0] + x, current[1]]; + points.push(current); + } + break; + } + case 'V': { + while (i < tokens.length && !isCommand(tokens[i])) { + const y = Number.parseFloat(tokens[i++]); + if (!Number.isFinite(y)) break; + current = absolute ? [current[0], y] : [current[0], current[1] + y]; + points.push(current); + } + break; + } + case 'Q': { + while (i + 3 < tokens.length && !isCommand(tokens[i])) { + const controlX = Number.parseFloat(tokens[i++]); + const controlY = Number.parseFloat(tokens[i++]); + const endX = Number.parseFloat(tokens[i++]); + const endY = Number.parseFloat(tokens[i++]); + if (![controlX, controlY, endX, endY].every(Number.isFinite)) break; + const control = absolute + ? [controlX, controlY] + : [current[0] + controlX, current[1] + controlY]; + const end = absolute + ? [endX, endY] + : [current[0] + endX, current[1] + endY]; + const startPoint = current; + for (let step = 1; step <= 8; step += 1) { + const amount = step / 8; + const remaining = 1 - amount; + points.push([ + remaining * remaining * startPoint[0] + 2 * remaining * amount * control[0] + amount * amount * end[0], + remaining * remaining * startPoint[1] + 2 * remaining * amount * control[1] + amount * amount * end[1], + ]); + } + current = end; + } + break; + } + case 'Z': { + if (start) points.push(start); + break; + } + default: + return []; + } + } + + return points.filter(isPoint); +} + +function properSegmentIntersection(a, b, c, d) { + const abC = crossProduct(a, b, c); + const abD = crossProduct(a, b, d); + const cdA = crossProduct(c, d, a); + const cdB = crossProduct(c, d, b); + const epsilon = 1e-9; + const opposite = (left, right) => (left > epsilon && right < -epsilon) || (left < -epsilon && right > epsilon); + if (!opposite(abC, abD) || !opposite(cdA, cdB)) return null; + const denominator = (a[0] - b[0]) * (c[1] - d[1]) - (a[1] - b[1]) * (c[0] - d[0]); + if (Math.abs(denominator) < epsilon) return null; + const ab = a[0] * b[1] - a[1] * b[0]; + const cd = c[0] * d[1] - c[1] * d[0]; + return [ + (ab * (c[0] - d[0]) - (a[0] - b[0]) * cd) / denominator, + (ab * (c[1] - d[1]) - (a[1] - b[1]) * cd) / denominator, + ]; +} + +function crossProduct(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); +} + +function segmentIntersectsBox(segment, box) { + const { start, end } = segment; + if (pointInsideBox(start, box) || pointInsideBox(end, box)) return true; + const edges = [ + [[box.x1, box.y1], [box.x2, box.y1]], + [[box.x2, box.y1], [box.x2, box.y2]], + [[box.x2, box.y2], [box.x1, box.y2]], + [[box.x1, box.y2], [box.x1, box.y1]], + ]; + return edges.some(([a, b]) => segmentsIntersect(start, end, a, b)); +} + +function segmentsIntersect(a, b, c, d) { + const o1 = orientation(a, b, c); + const o2 = orientation(a, b, d); + const o3 = orientation(c, d, a); + const o4 = orientation(c, d, b); + + if (o1 !== o2 && o3 !== o4) return true; + if (o1 === 0 && onSegment(a, c, b)) return true; + if (o2 === 0 && onSegment(a, d, b)) return true; + if (o3 === 0 && onSegment(c, a, d)) return true; + if (o4 === 0 && onSegment(c, b, d)) return true; + return false; +} + +function orientation(a, b, c) { + const value = (b[1] - a[1]) * (c[0] - b[0]) - (b[0] - a[0]) * (c[1] - b[1]); + if (Math.abs(value) < 1e-9) return 0; + return value > 0 ? 1 : 2; +} + +function onSegment(a, b, c) { + return b[0] <= Math.max(a[0], c[0]) + 1e-9 + && b[0] + 1e-9 >= Math.min(a[0], c[0]) + && b[1] <= Math.max(a[1], c[1]) + 1e-9 + && b[1] + 1e-9 >= Math.min(a[1], c[1]); +} + +function pointInsideBox(point, box) { + return point[0] >= box.x1 && point[0] <= box.x2 && point[1] >= box.y1 && point[1] <= box.y2; +} + +function padBox(box, padding) { + return { + ...box, + x1: box.x1 - padding, + y1: box.y1 - padding, + x2: box.x2 + padding, + y2: box.y2 + padding, + }; +} + +function parseAttrs(tag) { + const attrs = {}; + for (const match of tag.matchAll(/([\w:-]+)\s*=\s*"([^"]*)"/g)) attrs[match[1]] = match[2]; + return attrs; +} + +function numberAttr(attrs, name) { + return Number.parseFloat(attrs[name]); +} + +function isCommand(token) { + return /^[A-Za-z]$/.test(token); +} + +function isPoint(point) { + return Array.isArray(point) && point.length === 2 && point.every(Number.isFinite); +} + +function stripTags(value) { + return value.replace(/<[^>]*>/g, ''); +} diff --git a/tools/archify/scripts/check-update.mjs b/tools/archify/scripts/check-update.mjs new file mode 100644 index 0000000..dd12e4b --- /dev/null +++ b/tools/archify/scripts/check-update.mjs @@ -0,0 +1,1667 @@ +#!/usr/bin/env node + +import crypto from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { performance } from 'node:perf_hooks'; +import { fileURLToPath } from 'node:url'; + +import { + DEFAULT_MANIFEST_URL, + SKILL_ID, + UpdateContractError, + compareSemver, + isStableCoreVersion, + parseSemver, + validateLocalRelease, + validateReleaseNotesUrl, + validateStableUpdateManifest, +} from './update-contract.mjs'; + +const OPERATION_STATE_FILE = 'state.json'; +const OPERATION_OWNER_FILE = 'owner.json'; +const ACTIVE_CLAIM_DIRECTORY = 'active-claim'; +const OPERATION_NAME = /^(reserved|pending|committed|fenced|cancelled)-(\d{1,20})$/; +const MAX_OPERATION_GENERATION = (10n ** 20n) - 1n; +const CHECK_TTL_MS = 72 * 60 * 60 * 1_000; +const MAX_CACHE_HORIZON_MS = Math.ceil(CHECK_TTL_MS * 1.2); +const FIRST_FAILURE_DELAY_MS = 6 * 60 * 60 * 1_000; +const LATER_FAILURE_DELAY_MS = 24 * 60 * 60 * 1_000; +const DEFAULT_TIMEOUT_MS = 1_000; +const MAX_RESPONSE_BYTES = 32 * 1_024; +const MAX_LOCAL_RELEASE_BYTES = 4 * 1_024; +const MAX_CACHE_STATE_BYTES = 64 * 1_024; +const MAX_CLAIM_OWNER_BYTES = 1_024; +const LONGEST_ISO_TIMESTAMP = '+275760-09-13T00:00:00.000Z'; +const LOCK_STALE_MS = 30_000; +const ACK_LOCK_WAIT_MS = 1_200; +const LOCK_RETRY_DELAY_MS = 20; +const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/; +const scriptDirectory = path.dirname(fileURLToPath(import.meta.url)); +const defaultReleasePath = path.resolve(scriptDirectory, '..', 'skill-release.json'); + +class FileIdentityChangedError extends Error {} + +function silent(reason) { + return { status: 'silent', reason }; +} + +function isPlainObject(value) { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} + +function hasOnlyKeys(value, allowed) { + return Object.keys(value).every((key) => allowed.has(key)); +} + +function validateManifest(value) { + const manifest = validateStableUpdateManifest(value); + return { + version: manifest.version, + targetDigest: `sha256:${manifest.artifact.sha256}`, + severity: manifest.severity, + releaseNotes: manifest.releaseNotes, + }; +} + +function validateCachedCandidate(value) { + if (!isPlainObject(value) + || !hasOnlyKeys(value, new Set([ + 'version', 'targetDigest', 'severity', 'releaseNotes', + ]))) return null; + try { + if (!isStableCoreVersion(value.version)) return null; + if (!DIGEST_PATTERN.test(value.targetDigest) + || !['normal', 'security'].includes(value.severity)) return null; + validateReleaseNotesUrl(value.releaseNotes, value.version); + return { ...value }; + } catch { + return null; + } +} + +function emptyState(installedVersion = null) { + return { + schemaVersion: 1, + skillId: SKILL_ID, + installedVersion, + check: { + nextCheckAt: null, + consecutiveFailures: 0, + }, + notification: { + offeredDigests: [], + acknowledgedDigests: [], + }, + }; +} + +function encodeBoundedState(state) { + const encoded = Buffer.from(`${JSON.stringify(state)}\n`, 'utf8'); + return encoded.length <= MAX_CACHE_STATE_BYTES ? encoded : null; +} + +function stateAfterAcknowledgement(state, targetDigest) { + const alreadyAcknowledged = state.notification.acknowledgedDigests.includes(targetDigest); + return { + ...state, + notification: { + offeredDigests: state.notification.offeredDigests + .filter((digest) => digest !== targetDigest), + acknowledgedDigests: alreadyAcknowledged + ? [...state.notification.acknowledgedDigests] + : [...state.notification.acknowledgedDigests, targetDigest], + }, + }; +} + +function stateWithFailureCheck(state, nextCheckAt, consecutiveFailures, withdrawCandidate = false) { + const failedState = { + ...state, + check: { nextCheckAt, consecutiveFailures }, + }; + if (withdrawCandidate) delete failedState.candidate; + return failedState; +} + +function encodeRecoverableState(state) { + const encoded = encodeBoundedState(state); + if (!encoded) return null; + if (!encodeBoundedState(stateWithFailureCheck(state, LONGEST_ISO_TIMESTAMP, 2))) return null; + let acknowledgementState = state; + for (const targetDigest of state.notification.offeredDigests) { + acknowledgementState = stateAfterAcknowledgement(acknowledgementState, targetDigest); + if (!encodeBoundedState(acknowledgementState) + || !encodeBoundedState(stateWithFailureCheck( + acknowledgementState, + LONGEST_ISO_TIMESTAMP, + 2, + ))) return null; + } + return encoded; +} + +function versionCacheDirectory(cacheDirectory, installedVersion) { + const partition = crypto.createHash('sha256').update(installedVersion).digest('hex').slice(0, 24); + return path.join(cacheDirectory, `version-${partition}`); +} + +function normalizeState(value, installedVersion) { + const effectiveInstalledVersion = installedVersion ?? value?.installedVersion ?? null; + if (!isPlainObject(value) || value.schemaVersion !== 1 || value.skillId !== SKILL_ID + || (installedVersion !== null && value.installedVersion !== installedVersion) + || (value.installedVersion !== null && typeof value.installedVersion !== 'string') + || !isPlainObject(value.check) + || !(value.check.nextCheckAt === null || typeof value.check.nextCheckAt === 'string') + || !Number.isSafeInteger(value.check.consecutiveFailures) + || value.check.consecutiveFailures < 0 + || !isPlainObject(value.notification)) return null; + if (effectiveInstalledVersion !== null) { + try { + parseSemver(effectiveInstalledVersion); + } catch { + return null; + } + } + + const state = emptyState(effectiveInstalledVersion); + state.check.nextCheckAt = value.check.nextCheckAt; + state.check.consecutiveFailures = value.check.consecutiveFailures; + const { offeredDigests, acknowledgedDigests } = value.notification; + if (!Array.isArray(offeredDigests) + || offeredDigests.some((digest) => typeof digest !== 'string' || !DIGEST_PATTERN.test(digest))) { + return null; + } + state.notification.offeredDigests = [...new Set(offeredDigests)]; + + if (!Array.isArray(acknowledgedDigests) + || acknowledgedDigests.some((digest) => typeof digest !== 'string' || !DIGEST_PATTERN.test(digest))) { + return null; + } + state.notification.acknowledgedDigests = [...new Set(acknowledgedDigests)]; + const hasCandidate = Object.hasOwn(value, 'candidate'); + const candidate = validateCachedCandidate(value.candidate); + if (hasCandidate && !candidate) return null; + if (candidate) { + if (effectiveInstalledVersion !== null + && compareSemver(candidate.version, effectiveInstalledVersion) > 0 + && !state.notification.offeredDigests.includes(candidate.targetDigest) + && !state.notification.acknowledgedDigests.includes(candidate.targetDigest)) return null; + state.candidate = candidate; + } + return encodeRecoverableState(state) ? state : null; +} + +function isSameFile(left, right) { + if (left.dev !== right.dev || left.ino !== right.ino) return false; + if (left.ino !== 0n) return true; + if (left.birthtimeNs === 0n || right.birthtimeNs === 0n) return false; + return left.birthtimeNs === right.birthtimeNs + && left.mode === right.mode; +} + +function assertBoundedRegularFile(metadata, maxBytes) { + if (!metadata.isFile() + || metadata.isSymbolicLink() + || metadata.size > BigInt(maxBytes)) { + throw new Error('unsafe JSON file'); + } +} + +async function readJsonFile(target, maxBytes, expectedMetadata = null) { + const pathMetadata = await fs.lstat(target, { bigint: true }); + if (expectedMetadata && !isSameFile(expectedMetadata, pathMetadata)) { + throw new FileIdentityChangedError('JSON file identity changed'); + } + assertBoundedRegularFile(pathMetadata, maxBytes); + const flags = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); + const handle = await fs.open(target, flags); + try { + const handleMetadata = await handle.stat({ bigint: true }); + const currentPathMetadata = await fs.lstat(target, { bigint: true }); + assertBoundedRegularFile(handleMetadata, maxBytes); + assertBoundedRegularFile(currentPathMetadata, maxBytes); + if (!isSameFile(pathMetadata, handleMetadata) + || !isSameFile(currentPathMetadata, handleMetadata)) { + throw new FileIdentityChangedError('JSON file changed while opening'); + } + + const chunks = []; + let size = 0; + while (size <= maxBytes) { + const chunk = Buffer.allocUnsafe(Math.min(8 * 1_024, (maxBytes + 1) - size)); + const { bytesRead } = await handle.read(chunk, 0, chunk.length, size); + if (bytesRead === 0) break; + chunks.push(chunk.subarray(0, bytesRead)); + size += bytesRead; + } + if (size > maxBytes) throw new Error('JSON file is too large'); + return JSON.parse(Buffer.concat(chunks, size).toString('utf8')); + } finally { + await handle.close(); + } +} + +function isWithinDirectory(directory, target) { + const relative = path.relative(directory, target); + return relative === '' + || (!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`)); +} + +async function canonicalizeTrustedDirectoryPrefix(target) { + const absoluteTarget = path.resolve(target); + const trustedDirectories = [...new Set([os.homedir(), os.tmpdir()].map((directory) => ( + path.resolve(directory) + )))].sort((left, right) => right.length - left.length); + for (const trustedDirectory of trustedDirectories) { + if (!isWithinDirectory(trustedDirectory, absoluteTarget)) continue; + try { + const canonicalDirectory = await fs.realpath(trustedDirectory); + return path.resolve(canonicalDirectory, path.relative(trustedDirectory, absoluteTarget)); + } catch { + // Fall back to validating the absolute path from its filesystem root. + } + } + return target; +} + +function assertSafeDirectory(metadata) { + if (!metadata.isDirectory() || metadata.isSymbolicLink()) { + throw new Error('unsafe cache directory'); + } +} + +class CachePathChangedError extends Error {} +class CacheOperationRaceError extends Error {} +class OperationFencedError extends Error {} + +const preparedCacheDirectories = new Map(); + +function invalidateCacheToken(token, message, cause) { + token.trusted = false; + if (cause instanceof CachePathChangedError) return cause; + const error = new CachePathChangedError(message); + if (cause) error.cause = cause; + return error; +} + +async function verifyDirectorySnapshots(token, snapshots, description) { + if (!token.trusted) throw new CachePathChangedError('cache directory is no longer trusted'); + try { + for (const snapshot of snapshots) { + const metadata = await fs.lstat(snapshot.directory, { bigint: true }); + assertSafeDirectory(metadata); + if (!isSameFile(snapshot.metadata, metadata)) { + throw new Error(`${description} changed`); + } + } + } catch (error) { + throw invalidateCacheToken(token, `${description} changed`, error); + } +} + +async function verifyCacheToken(token) { + await verifyDirectorySnapshots(token, token.ancestorSnapshots, 'cache directory'); +} + +async function guardedCacheRead(token, read) { + await verifyCacheToken(token); + let result; + try { + result = await read(); + } catch (error) { + await verifyCacheToken(token); + throw error; + } + await verifyCacheToken(token); + return result; +} + +function cacheTokenFor(cacheDirectory) { + const token = preparedCacheDirectories.get(path.resolve(cacheDirectory)); + if (!token || !token.trusted) { + throw new CachePathChangedError('cache directory has not been prepared safely'); + } + return token; +} + +function resolveCacheTarget(token, target) { + const resolved = path.resolve(target); + if (resolved === token.directory || !isWithinDirectory(token.directory, resolved)) { + throw invalidateCacheToken(token, 'cache mutation escaped its prepared directory'); + } + return resolved; +} + +async function captureMutationParentSnapshots(token, targets) { + await verifyCacheToken(token); + const snapshots = new Map(); + try { + for (const target of targets) { + const parent = path.dirname(resolveCacheTarget(token, target)); + const relative = path.relative(token.directory, parent); + let current = token.directory; + for (const component of relative.split(path.sep).filter(Boolean)) { + current = path.join(current, component); + if (snapshots.has(current)) continue; + const metadata = await fs.lstat(current, { bigint: true }); + assertSafeDirectory(metadata); + snapshots.set(current, { directory: current, metadata }); + } + } + } catch (error) { + throw invalidateCacheToken(token, 'cache mutation parent changed', error); + } + const captured = [...snapshots.values()]; + await verifyMutationParentSnapshots(token, captured); + await verifyCacheToken(token); + return captured; +} + +async function verifyMutationParentSnapshots(token, snapshots) { + try { + for (const snapshot of snapshots) { + const metadata = await fs.lstat(snapshot.directory, { bigint: true }); + assertSafeDirectory(metadata); + if (!isSameFile(snapshot.metadata, metadata)) { + throw new Error('cache mutation parent changed'); + } + } + } catch (error) { + const raced = new CacheOperationRaceError('cache mutation parent changed'); + raced.cause = error; + throw raced; + } +} + +async function verifyMutationContext(token, parentSnapshots) { + await verifyCacheToken(token); + await verifyMutationParentSnapshots(token, parentSnapshots); + await verifyCacheToken(token); +} + +function assertSafeCacheEntry(metadata) { + if (metadata.isSymbolicLink() || (!metadata.isDirectory() && !metadata.isFile())) { + throw new Error('unsafe cache entry'); + } +} + +function assertSafeRegularFile(metadata) { + if (!metadata.isFile() || metadata.isSymbolicLink()) { + throw new Error('unsafe cache file'); + } +} + +function assertRenameableCacheEntry(metadata) { + if (!metadata.isDirectory() && !metadata.isFile() && !metadata.isSymbolicLink()) { + throw new Error('unsafe cache rename entry'); + } +} + +async function stableCacheEntry( + token, + target, + validate, + expectedMetadata = null, + missingIsOperationRace = false, +) { + const resolved = resolveCacheTarget(token, target); + try { + const first = await fs.lstat(resolved, { bigint: true }); + validate(first); + if (expectedMetadata && !isSameFile(expectedMetadata, first)) { + throw new Error('cache entry identity changed'); + } + await verifyCacheToken(token); + const second = await fs.lstat(resolved, { bigint: true }); + validate(second); + if (!isSameFile(first, second) + || (expectedMetadata && !isSameFile(expectedMetadata, second))) { + throw new Error('cache entry identity changed'); + } + return second; + } catch (error) { + if (missingIsOperationRace && error?.code === 'ENOENT') { + await verifyCacheToken(token); + throw error; + } + throw invalidateCacheToken(token, 'cache mutation produced an untrusted object', error); + } +} + +async function guardedCacheEntryRead( + token, + target, + validate, + read, + expectedMetadata = null, +) { + const metadata = await stableCacheEntry( + token, + target, + validate, + expectedMetadata, + true, + ); + let result; + try { + result = await read(metadata); + } catch (error) { + await stableCacheEntry(token, target, validate, metadata, true); + throw error; + } + await stableCacheEntry(token, target, validate, metadata, true); + return result; +} + +async function stablePreparedDirectory(token, target) { + try { + const first = await fs.lstat(target, { bigint: true }); + assertSafeDirectory(first); + await verifyCacheToken(token); + const second = await fs.lstat(target, { bigint: true }); + assertSafeDirectory(second); + if (!isSameFile(first, second)) throw new Error('prepared directory identity changed'); + return second; + } catch (error) { + throw invalidateCacheToken(token, 'prepared directory could not be verified', error); + } +} + +async function assertCacheTargetAbsent(token, target) { + const resolved = resolveCacheTarget(token, target); + try { + await fs.lstat(resolved, { bigint: true }); + } catch (error) { + if (error?.code === 'ENOENT') return; + throw invalidateCacheToken(token, 'cache mutation target could not be verified', error); + } + throw invalidateCacheToken(token, 'cache mutation left an unexpected object'); +} + +async function guardedCacheMutation(token, parentSnapshots, { + mutate, + verifyBefore = null, + verifyAfter, +}) { + await verifyMutationContext(token, parentSnapshots); + if (verifyBefore) await verifyBefore(); + let result; + try { + result = await mutate(); + } catch (error) { + await verifyMutationContext(token, parentSnapshots); + throw error; + } + try { + await verifyMutationContext(token, parentSnapshots); + const verified = await verifyAfter(result); + await verifyMutationContext(token, parentSnapshots); + return verified ?? result; + } catch (error) { + if (error instanceof CacheOperationRaceError) throw error; + throw invalidateCacheToken(token, 'cache mutation could not be verified', error); + } +} + +async function cacheMkdirWithToken(token, target, options, parentSnapshots = null) { + const resolved = path.resolve(target); + const parents = parentSnapshots ?? await captureMutationParentSnapshots(token, [resolved]); + return guardedCacheMutation(token, parents, { + mutate: () => fs.mkdir(resolved, options), + verifyAfter: () => (parentSnapshots === null + ? stableCacheEntry(token, resolved, assertSafeDirectory) + : stablePreparedDirectory(token, resolved)), + }); +} + +async function cacheMkdir(cacheDirectory, target, options) { + return cacheMkdirWithToken(cacheTokenFor(cacheDirectory), target, options); +} + +async function cacheWriteFile(cacheDirectory, target, data, options) { + if (options?.flag !== 'wx') { + throw new Error('cache files must be created exclusively'); + } + const token = cacheTokenFor(cacheDirectory); + const resolved = resolveCacheTarget(token, target); + const parents = await captureMutationParentSnapshots(token, [resolved]); + return guardedCacheMutation(token, parents, { + mutate: async () => { + const flags = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); + const handle = await fs.open(resolved, flags, options.mode); + try { + const openedMetadata = await handle.stat({ bigint: true }); + if (!openedMetadata.isFile() || openedMetadata.isSymbolicLink()) { + throw new Error('cache write did not open a regular file'); + } + await handle.writeFile(data, options.encoding ? { encoding: options.encoding } : undefined); + const writtenMetadata = await handle.stat({ bigint: true }); + const expectedBytes = Buffer.isBuffer(data) + ? data.length + : Buffer.byteLength(data, options.encoding || 'utf8'); + if (!isSameFile(openedMetadata, writtenMetadata) + || writtenMetadata.size !== BigInt(expectedBytes)) { + throw new Error('cache write did not preserve the opened file identity and size'); + } + return writtenMetadata; + } finally { + await handle.close(); + } + }, + verifyAfter: (writtenMetadata) => stableCacheEntry(token, resolved, (metadata) => { + if (!metadata.isFile() || metadata.isSymbolicLink()) { + throw new Error('cache write did not create a regular file'); + } + }, writtenMetadata), + }); +} + +async function cacheRename( + cacheDirectory, + source, + destination, + validate, + expectedSourceMetadata = null, +) { + if (typeof validate !== 'function') { + throw new TypeError('cache rename requires an explicit entry validator'); + } + const token = cacheTokenFor(cacheDirectory); + const resolvedSource = resolveCacheTarget(token, source); + const resolvedDestination = resolveCacheTarget(token, destination); + const parents = await captureMutationParentSnapshots( + token, + [resolvedSource, resolvedDestination], + ); + const sourceMetadata = await stableCacheEntry( + token, + resolvedSource, + validate, + expectedSourceMetadata, + true, + ); + return guardedCacheMutation(token, parents, { + verifyBefore: () => stableCacheEntry( + token, + resolvedSource, + validate, + sourceMetadata, + true, + ), + mutate: () => fs.rename(resolvedSource, resolvedDestination), + verifyAfter: async () => { + await assertCacheTargetAbsent(token, resolvedSource); + return stableCacheEntry( + token, + resolvedDestination, + validate, + sourceMetadata, + ); + }, + }); +} + +async function cacheRm(cacheDirectory, target, options) { + if (options?.recursive) { + throw new Error('recursive cache removal is not allowed'); + } + const token = cacheTokenFor(cacheDirectory); + const resolved = resolveCacheTarget(token, target); + const parents = await captureMutationParentSnapshots(token, [resolved]); + let initialMetadata; + try { + initialMetadata = await fs.lstat(resolved, { bigint: true }); + } catch (error) { + if (error?.code === 'ENOENT' && options?.force) { + await verifyMutationContext(token, parents); + return; + } + throw error; + } + try { + assertSafeCacheEntry(initialMetadata); + } catch (error) { + throw invalidateCacheToken(token, 'cache removal target is unsafe', error); + } + const targetMetadata = await stableCacheEntry( + token, + resolved, + assertSafeCacheEntry, + initialMetadata, + ); + return guardedCacheMutation(token, parents, { + verifyBefore: () => stableCacheEntry( + token, + resolved, + assertSafeCacheEntry, + targetMetadata, + ), + mutate: () => fs.rm(resolved, options), + verifyAfter: () => assertCacheTargetAbsent(token, resolved), + }); +} + +async function prepareCacheDirectory(cacheDirectory) { + const absoluteTarget = path.resolve(cacheDirectory); + const target = await canonicalizeTrustedDirectoryPrefix(absoluteTarget); + const parsed = path.parse(target); + const components = target.slice(parsed.root.length).split(path.sep).filter(Boolean); + let current = parsed.root; + const token = { + directory: target, + ancestorSnapshots: [], + trusted: true, + }; + + for (const component of components) { + current = path.join(current, component); + let metadata; + try { + metadata = await fs.lstat(current, { bigint: true }); + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + try { + await cacheMkdirWithToken(token, current, { mode: 0o700 }, []); + } catch (mkdirError) { + if (mkdirError?.code !== 'EEXIST') throw mkdirError; + } + metadata = await fs.lstat(current, { bigint: true }); + } + assertSafeDirectory(metadata); + token.ancestorSnapshots.push({ directory: current, metadata }); + } + + await verifyCacheToken(token); + preparedCacheDirectories.set(target, token); + return target; +} + +function operationName(kind, generation) { + return `${kind}-${generation.toString().padStart(20, '0')}`; +} + +function parseOperationName(name) { + const match = OPERATION_NAME.exec(name); + if (!match) return null; + return { kind: match[1], generation: BigInt(match[2]) }; +} + +async function listOperations(cacheDirectory, token = cacheTokenFor(cacheDirectory)) { + return guardedCacheRead(token, async () => { + try { + const entries = await fs.readdir(cacheDirectory, { withFileTypes: true }); + return entries.flatMap((entry) => { + const parsed = parseOperationName(entry.name); + if (!parsed) return []; + if (!entry.isDirectory() || entry.isSymbolicLink()) { + throw invalidateCacheToken( + token, + `cache operation ${entry.name} is not a safe directory`, + ); + } + return [{ ...parsed, name: entry.name, directory: path.join(cacheDirectory, entry.name) }]; + }); + } catch (error) { + if (error?.code === 'ENOENT') return []; + throw error; + } + }); +} + +async function readState(cacheDirectory, installedVersion = null) { + const token = cacheTokenFor(cacheDirectory); + return guardedCacheRead(token, async () => { + const operations = (await listOperations(cacheDirectory, token)) + .filter((operation) => operation.kind === 'committed') + .sort((left, right) => (left.generation > right.generation ? -1 : 1)); + for (const operation of operations) { + try { + const state = await guardedCacheEntryRead( + token, + operation.directory, + assertSafeDirectory, + async () => normalizeState( + await readJsonFile( + path.join(operation.directory, OPERATION_STATE_FILE), + MAX_CACHE_STATE_BYTES, + ), + installedVersion, + ), + ); + if (state) return state; + } catch (error) { + if (error instanceof FileIdentityChangedError + || error instanceof CachePathChangedError + || error instanceof CacheOperationRaceError) throw error; + // An incomplete or corrupt generation is ignored in favor of the previous commit. + } + } + return emptyState(installedVersion); + }); +} + +async function operationIsActive( + cacheDirectory, + operation, + token = cacheTokenFor(cacheDirectory), +) { + try { + return await guardedCacheEntryRead( + token, + operation.directory, + assertSafeDirectory, + async (directoryMetadata) => { + try { + const ownerMetadata = await fs.lstat( + path.join(operation.directory, OPERATION_OWNER_FILE), + { bigint: true }, + ); + if (!ownerMetadata.isFile() || ownerMetadata.isSymbolicLink()) return false; + return metadataIsWithinLease(ownerMetadata); + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + return metadataIsWithinLease(directoryMetadata); + } + }, + ); + } catch (error) { + if (error?.code === 'ENOENT') return false; + throw error; + } +} + +function validateClaimOwner(value) { + if (!isPlainObject(value) + || !hasOnlyKeys(value, new Set(['pid', 'token', 'generation'])) + || !Number.isSafeInteger(value.pid) + || value.pid <= 0 + || typeof value.token !== 'string' + || !/^[a-f0-9]{32}$/.test(value.token) + || typeof value.generation !== 'string' + || !/^[1-9]\d*$/.test(value.generation)) return null; + return { ...value, generation: BigInt(value.generation) }; +} + +function metadataIsWithinLease(metadata) { + if (typeof metadata.mtimeNs === 'bigint') { + const ageNs = (BigInt(Date.now()) * 1_000_000n) - metadata.mtimeNs; + const absoluteAgeNs = ageNs < 0n ? -ageNs : ageNs; + return absoluteAgeNs <= BigInt(LOCK_STALE_MS) * 1_000_000n; + } + return Math.abs(Date.now() - metadata.mtimeMs) <= LOCK_STALE_MS; +} + +function corruptClaimStatus(directory, directoryMetadata, ownerMetadata = directoryMetadata) { + if (metadataIsWithinLease(ownerMetadata)) { + return { state: 'busy', directory, metadata: directoryMetadata, owner: null }; + } + const fingerprint = crypto.createHash('sha256') + .update(JSON.stringify({ + directory: { + dev: directoryMetadata.dev.toString(), + ino: directoryMetadata.ino.toString(), + mode: directoryMetadata.mode.toString(), + birthtimeNs: directoryMetadata.birthtimeNs.toString(), + }, + })) + .digest('hex') + .slice(0, 24); + return { + state: 'releasable', + directory, + metadata: directoryMetadata, + key: `corrupt-${fingerprint}`, + owner: null, + }; +} + +async function inspectActiveClaim(cacheDirectory) { + const token = cacheTokenFor(cacheDirectory); + return guardedCacheRead(token, async () => { + const directory = path.join(cacheDirectory, ACTIVE_CLAIM_DIRECTORY); + let directoryMetadata; + try { + directoryMetadata = await fs.lstat(directory, { bigint: true }); + } catch (error) { + if (error?.code === 'ENOENT') return { state: 'absent', directory }; + throw error; + } + if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink()) { + return corruptClaimStatus(directory, directoryMetadata); + } + return guardedCacheEntryRead( + token, + directory, + assertSafeDirectory, + async (stableDirectoryMetadata) => { + const ownerPath = path.join(directory, OPERATION_OWNER_FILE); + let ownerMetadata = stableDirectoryMetadata; + let owner = null; + try { + ownerMetadata = await fs.lstat(ownerPath, { bigint: true }); + if (!ownerMetadata.isFile() + || ownerMetadata.isSymbolicLink() + || ownerMetadata.size > BigInt(MAX_CLAIM_OWNER_BYTES)) { + return corruptClaimStatus(directory, stableDirectoryMetadata, ownerMetadata); + } + const ownerSource = await readJsonFile( + ownerPath, + MAX_CLAIM_OWNER_BYTES, + ownerMetadata, + ); + owner = validateClaimOwner(ownerSource); + } catch (error) { + if (error instanceof FileIdentityChangedError) throw error; + // A malformed claim is recoverable only after the same hard lease as a crashed owner. + } + + if (owner) { + const operations = await listOperations(cacheDirectory, token); + if (operations.some((operation) => operation.generation === owner.generation + && ['committed', 'fenced', 'cancelled'].includes(operation.kind))) { + return { + state: 'releasable', + directory, + metadata: stableDirectoryMetadata, + key: owner.generation.toString(), + owner, + }; + } + const pending = operations.find((operation) => operation.kind === 'pending' + && operation.generation === owner.generation); + if (pending) { + if (await operationIsActive(cacheDirectory, pending, token)) { + return { + state: 'busy', + directory, + metadata: stableDirectoryMetadata, + owner, + }; + } + return { + state: 'releasable', + directory, + metadata: stableDirectoryMetadata, + key: owner.generation.toString(), + owner, + }; + } + } + + return corruptClaimStatus(directory, stableDirectoryMetadata, ownerMetadata); + }, + directoryMetadata, + ); + }); +} + +async function retireActiveClaim(cacheDirectory, claim) { + const retirement = path.join(cacheDirectory, `retired-claim-${claim.key}`); + await cacheMkdir(cacheDirectory, retirement, { mode: 0o700 }).catch((error) => { + if (error?.code !== 'EEXIST') throw error; + }); + try { + const retirementMetadata = await fs.lstat(retirement); + if (!retirementMetadata.isDirectory() || retirementMetadata.isSymbolicLink()) return false; + } catch (error) { + if (error instanceof CacheOperationRaceError) return false; + if (error?.code === 'ENOENT') return false; + throw error; + } + try { + const metadata = await fs.lstat(claim.directory); + if (metadata.isDirectory() && !metadata.isSymbolicLink()) { + await cacheWriteFile( + cacheDirectory, + path.join(claim.directory, '.retirement-guard'), + `${claim.key}\n`, + { encoding: 'utf8', flag: 'wx', mode: 0o600 }, + ).catch((error) => { + if (error?.code !== 'EEXIST') throw error; + }); + } + } catch (error) { + if (error instanceof CacheOperationRaceError) return false; + if (error?.code === 'ENOENT') return false; + throw error; + } + try { + await cacheRename( + cacheDirectory, + claim.directory, + path.join(retirement, 'active'), + assertRenameableCacheEntry, + claim.metadata, + ); + return true; + } catch (error) { + if (error instanceof CacheOperationRaceError) return false; + if (['ENOENT', 'EEXIST', 'ENOTEMPTY', 'EPERM'].includes(error?.code)) return false; + throw error; + } +} + +async function prepareOperationClaim(cacheDirectory, operation) { + const directory = path.join(cacheDirectory, operationName('claim', operation.generation)); + await cacheMkdir(cacheDirectory, directory, { mode: 0o700 }); + await cacheWriteFile( + cacheDirectory, + path.join(directory, OPERATION_OWNER_FILE), + `${JSON.stringify({ + pid: process.pid, + token: operation.token, + generation: operation.generation.toString(), + })}\n`, + { encoding: 'utf8', flag: 'wx', mode: 0o600 }, + ); + operation.preparedClaimDirectory = directory; +} + +async function discardPreparedClaim(cacheDirectory, operation) { + if (!operation.preparedClaimDirectory) return; + const discarded = path.join( + cacheDirectory, + `discarded-claim-${operation.generation.toString().padStart(20, '0')}-${operation.token}`, + ); + try { + await cacheRename( + cacheDirectory, + operation.preparedClaimDirectory, + discarded, + assertSafeDirectory, + ); + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + delete operation.preparedClaimDirectory; +} + +async function promoteOperationClaim(cacheDirectory, operation) { + await prepareOperationClaim(cacheDirectory, operation); + const activeDirectory = path.join(cacheDirectory, ACTIVE_CLAIM_DIRECTORY); + for (let attempt = 0; attempt < 8; attempt += 1) { + if (await operationIsSuperseded(cacheDirectory, operation)) { + await discardPreparedClaim(cacheDirectory, operation); + return false; + } + const active = await inspectActiveClaim(cacheDirectory); + if (active.state === 'busy') { + await discardPreparedClaim(cacheDirectory, operation); + return false; + } + if (active.state === 'releasable') { + if (active.owner?.generation > operation.generation) { + await discardPreparedClaim(cacheDirectory, operation); + return false; + } + await retireActiveClaim(cacheDirectory, active); + continue; + } + try { + await cacheRename( + cacheDirectory, + operation.preparedClaimDirectory, + activeDirectory, + assertSafeDirectory, + ); + delete operation.preparedClaimDirectory; + if (!await operationIsActive(cacheDirectory, operation)) return false; + return true; + } catch (promotionError) { + const raced = await inspectActiveClaim(cacheDirectory); + if (raced.state === 'absent') { + if (['EEXIST', 'ENOTEMPTY', 'EPERM'].includes(promotionError?.code)) continue; + await discardPreparedClaim(cacheDirectory, operation); + throw promotionError; + } + continue; + } + } + await discardPreparedClaim(cacheDirectory, operation); + return false; +} + +async function operationOwnsActiveClaim(cacheDirectory, operation) { + const active = await inspectActiveClaim(cacheDirectory); + return active.state === 'busy' + && active.owner?.generation === operation.generation + && active.owner.token === operation.token; +} + +async function hasActivePendingOperation(cacheDirectory) { + const token = cacheTokenFor(cacheDirectory); + return guardedCacheRead(token, async () => { + const pending = (await listOperations(cacheDirectory, token)) + .filter((operation) => operation.kind === 'pending'); + return (await Promise.all(pending.map( + (operation) => operationIsActive(cacheDirectory, operation, token), + ))).some(Boolean); + }); +} + +async function reserveOperation(cacheDirectory) { + cacheDirectory = await prepareCacheDirectory(cacheDirectory); + const operations = await listOperations(cacheDirectory); + const occupied = new Set(operations.map((operation) => operation.generation.toString())); + let generation = 1n; + while (true) { + while (occupied.has(generation.toString())) generation += 1n; + if (generation > MAX_OPERATION_GENERATION) throw new Error('operation generation space exhausted'); + const reservation = path.join(cacheDirectory, operationName('reserved', generation)); + try { + await cacheMkdir(cacheDirectory, reservation, { mode: 0o700 }); + } catch (error) { + if (error?.code !== 'EEXIST') throw error; + occupied.add(generation.toString()); + generation += 1n; + continue; + } + const directory = path.join(cacheDirectory, operationName('pending', generation)); + try { + await cacheMkdir(cacheDirectory, directory, { mode: 0o700 }); + const operation = { + generation, + token: crypto.randomBytes(16).toString('hex'), + directory, + }; + try { + await cacheWriteFile( + cacheDirectory, + path.join(directory, OPERATION_OWNER_FILE), + `${JSON.stringify({ pid: process.pid, token: operation.token })}\n`, + { encoding: 'utf8', flag: 'wx', mode: 0o600 }, + ); + } catch (error) { + if (await operationWasFenced(cacheDirectory, operation)) { + const fencedError = new OperationFencedError('operation was fenced while reserving'); + fencedError.cause = error; + throw fencedError; + } + await cacheRename( + cacheDirectory, + directory, + path.join(cacheDirectory, operationName('cancelled', generation)), + assertSafeDirectory, + ).catch(() => {}); + throw error; + } + return operation; + } catch (error) { + if (error?.code !== 'EEXIST') throw error; + occupied.add(generation.toString()); + generation += 1n; + } + } +} + +async function operationIsSuperseded(cacheDirectory, operation) { + return (await listOperations(cacheDirectory)) + .some((candidate) => candidate.generation > operation.generation); +} + +async function operationWasFenced(cacheDirectory, operation) { + const token = cacheTokenFor(cacheDirectory); + return guardedCacheRead(token, async () => { + try { + await fs.lstat(operation.directory, { bigint: true }); + return false; + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + const fencedDirectory = path.join( + cacheDirectory, + operationName('fenced', operation.generation), + ); + try { + await stableCacheEntry( + token, + fencedDirectory, + assertSafeDirectory, + null, + true, + ); + return true; + } catch (error) { + if (error?.code === 'ENOENT') return false; + throw error; + } + }); +} + +async function transitionOperation(cacheDirectory, operation, kind) { + const destination = path.join(cacheDirectory, operationName(kind, operation.generation)); + await cacheRename(cacheDirectory, operation.directory, destination, assertSafeDirectory); +} + +async function cancelOperation(cacheDirectory, operation) { + try { + await transitionOperation(cacheDirectory, operation, 'cancelled'); + } catch (error) { + // A higher generation may already have fenced this unique pending directory. + if (error?.code !== 'ENOENT') throw error; + } +} + +async function acquireOperation(cacheDirectory) { + cacheDirectory = await prepareCacheDirectory(cacheDirectory); + if ((await inspectActiveClaim(cacheDirectory)).state === 'busy') return null; + if (await hasActivePendingOperation(cacheDirectory)) return null; + let operation; + try { + operation = await reserveOperation(cacheDirectory); + } catch (error) { + if (error instanceof OperationFencedError) return null; + throw error; + } + if (!await operationIsSuperseded(cacheDirectory, operation) + && await promoteOperationClaim(cacheDirectory, operation)) return operation; + await cancelOperation(cacheDirectory, operation); + return null; +} + +async function fenceLowerOperations(cacheDirectory, operation) { + const lower = (await listOperations(cacheDirectory)) + .filter((candidate) => candidate.kind === 'pending' + && candidate.generation < operation.generation) + .sort((left, right) => (left.generation < right.generation ? -1 : 1)); + for (const candidate of lower) { + try { + await cacheRename( + cacheDirectory, + candidate.directory, + path.join(cacheDirectory, operationName('fenced', candidate.generation)), + assertSafeDirectory, + ); + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + } +} + +async function commitOperation(cacheDirectory, operation, installedVersion, mutate) { + if (!await operationOwnsActiveClaim(cacheDirectory, operation)) { + await cancelOperation(cacheDirectory, operation); + return { fenced: true, result: silent('check-in-progress') }; + } + await fenceLowerOperations(cacheDirectory, operation); + if (!await operationOwnsActiveClaim(cacheDirectory, operation)) { + await cancelOperation(cacheDirectory, operation); + return { fenced: true, result: silent('check-in-progress') }; + } + const state = await readState(cacheDirectory, installedVersion); + const decision = mutate(state); + if (!decision.state) { + await cancelOperation(cacheDirectory, operation); + return { fenced: false, result: decision.result }; + } + let committedState = decision.state; + let committedResult = decision.result; + let encodedState = encodeRecoverableState(committedState); + if (!encodedState && decision.capacityFallback) { + committedState = decision.capacityFallback.state; + committedResult = decision.capacityFallback.result; + encodedState = encodeRecoverableState(committedState); + } + if (!encodedState) { + await cancelOperation(cacheDirectory, operation); + return { fenced: false, result: silent('cache-unavailable') }; + } + const temporary = path.join( + operation.directory, + `.state.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`, + ); + try { + await cacheWriteFile(cacheDirectory, temporary, encodedState, { + flag: 'wx', + mode: 0o600, + }); + await cacheRename( + cacheDirectory, + temporary, + path.join(operation.directory, OPERATION_STATE_FILE), + assertSafeRegularFile, + ); + await transitionOperation(cacheDirectory, operation, 'committed'); + return { fenced: false, result: committedResult }; + } catch (error) { + if (await operationWasFenced(cacheDirectory, operation)) { + return { fenced: true, result: silent('check-in-progress') }; + } + await cacheRm(cacheDirectory, temporary, { force: true }).catch(() => {}); + throw error; + } +} + +function nextSuccessfulCheck(nowMs, random) { + const boundedRandom = Math.min(1, Math.max(0, Number(random()) || 0)); + const multiplier = 0.8 + (boundedRandom * 0.4); + return new Date(nowMs + Math.round(CHECK_TTL_MS * multiplier)).toISOString(); +} + +function nextFailedCheck(nowMs, failures) { + const delay = failures <= 1 ? FIRST_FAILURE_DELAY_MS : LATER_FAILURE_DELAY_MS; + return new Date(nowMs + delay).toISOString(); +} + +function stateAfterFailedCheck(state, nowMs, withdrawCandidate = false) { + const failures = Math.min(state.check.consecutiveFailures + 1, 2); + return stateWithFailureCheck( + state, + nextFailedCheck(nowMs, failures), + failures, + withdrawCandidate, + ); +} + +function eventKeyForDigest(targetDigest) { + return `${SKILL_ID}@${targetDigest}`; +} + +function digestForEventKey(eventKey) { + const prefix = `${SKILL_ID}@`; + if (typeof eventKey !== 'string' || !eventKey.startsWith(prefix)) return null; + const digest = eventKey.slice(prefix.length); + return DIGEST_PATTERN.test(digest) ? digest : null; +} + +function notification(localRelease, candidate) { + return { + status: 'update_available', + eventKey: eventKeyForDigest(candidate.targetDigest), + installedVersion: localRelease.version, + latestVersion: candidate.version, + targetDigest: candidate.targetDigest, + severity: candidate.severity, + summary: `Archify ${candidate.version} is available; see the official release notes for details.`, + releaseNotes: candidate.releaseNotes, + }; +} + +function resultForCandidate(localRelease, state) { + if (!state.candidate) return silent('cache-valid'); + const comparison = compareSemver(state.candidate.version, localRelease.version); + if (comparison <= 0) return silent('current'); + if (state.notification.acknowledgedDigests.includes(state.candidate.targetDigest)) { + return silent('already-notified'); + } + return notification(localRelease, state.candidate); +} + +async function readBoundedBody(response) { + const contentLength = Number(response.headers.get('content-length')); + if (Number.isFinite(contentLength) && contentLength > MAX_RESPONSE_BYTES) { + await cancelResponseBody(response); + throw new UpdateContractError('manifest response is too large'); + } + if (!response.body || typeof response.body.getReader !== 'function') { + throw new UpdateContractError('manifest response has no bounded stream'); + } + const reader = response.body.getReader(); + const chunks = []; + let size = 0; + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + size += value.byteLength; + if (size > MAX_RESPONSE_BYTES) { + await reader.cancel().catch(() => {}); + throw new UpdateContractError('manifest response is too large'); + } + chunks.push(value); + } + } finally { + reader.releaseLock(); + } + const joined = new Uint8Array(size); + let offset = 0; + for (const chunk of chunks) { + joined.set(chunk, offset); + offset += chunk.byteLength; + } + try { + return new TextDecoder('utf-8', { fatal: true }).decode(joined); + } catch { + throw new UpdateContractError('manifest is not valid UTF-8'); + } +} + +async function cancelResponseBody(response) { + try { + if (response.body && typeof response.body.cancel === 'function') { + await response.body.cancel(); + } + } catch { + // Network cleanup is best-effort; the original response classification wins. + } +} + +async function fetchCandidate({ fetchImpl, manifestUrl, timeoutMs }) { + if (manifestUrl !== DEFAULT_MANIFEST_URL) throw new UpdateContractError('unexpected manifest URL'); + const controller = new AbortController(); + let timer; + const timeout = new Promise((_resolve, reject) => { + timer = setTimeout(() => { + controller.abort(); + reject(new Error('update check timed out')); + }, timeoutMs); + }); + const request = (async () => { + const headers = { accept: 'application/json' }; + const response = await fetchImpl(manifestUrl, { + method: 'GET', + headers, + redirect: 'error', + signal: controller.signal, + }); + if (response.status !== 200) { + await cancelResponseBody(response); + throw new Error(`manifest returned HTTP ${response.status}`); + } + const mediaType = response.headers.get('content-type')?.split(';', 1)[0].trim().toLowerCase(); + if (mediaType !== 'application/json') { + await cancelResponseBody(response); + throw new UpdateContractError('manifest is not JSON'); + } + const source = await readBoundedBody(response); + let decoded; + try { + decoded = JSON.parse(source); + } catch { + throw new UpdateContractError('manifest is not valid bounded JSON'); + } + return { candidate: validateManifest(decoded) }; + })(); + try { + return await Promise.race([request, timeout]); + } finally { + clearTimeout(timer); + } +} + +function defaultCacheDirectory() { + const homeDirectory = os.homedir(); + if (process.platform === 'win32') { + const localData = process.env.LOCALAPPDATA; + return path.join(localData && path.isAbsolute(localData) ? localData : path.join(homeDirectory, 'AppData', 'Local'), 'archify-skill'); + } + if (process.platform === 'darwin') return path.join(homeDirectory, 'Library', 'Caches', 'archify-skill'); + const xdgCache = process.env.XDG_CACHE_HOME; + return path.join(xdgCache && path.isAbsolute(xdgCache) ? xdgCache : path.join(homeDirectory, '.cache'), 'archify-skill'); +} + +function freshStateResult(localRelease, state, nowMs) { + const nextCheckAt = Date.parse(state.check.nextCheckAt || ''); + if (Number.isFinite(nextCheckAt) && nextCheckAt > nowMs + && nextCheckAt <= nowMs + MAX_CACHE_HORIZON_MS) { + return resultForCandidate(localRelease, state); + } + return null; +} + +async function resultAfterLosingOperation(cacheDirectory, operation, localRelease) { + await cancelOperation(cacheDirectory, operation); + const cached = await readState(cacheDirectory, localRelease.version); + if (cached.candidate + && cached.notification.offeredDigests.includes(cached.candidate.targetDigest)) { + return resultForCandidate(localRelease, cached); + } + return silent('check-in-progress'); +} + +async function waitUntilRetry(deadline, monotonicNow) { + let remaining; + try { + remaining = deadline - Number(monotonicNow()); + } catch { + return false; + } + if (!Number.isFinite(remaining) || remaining <= 0) return false; + await new Promise((resolve) => setTimeout( + resolve, + Math.min(LOCK_RETRY_DELAY_MS, remaining), + )); + return true; +} + +export async function checkForUpdate({ + releasePath = defaultReleasePath, + cacheDirectory = defaultCacheDirectory(), + fetchImpl = globalThis.fetch, + now = Date.now, + random = Math.random, + timeoutMs = DEFAULT_TIMEOUT_MS, +} = {}) { + if (typeof fetchImpl !== 'function') return silent('runtime-unavailable'); + let localRelease; + try { + localRelease = validateLocalRelease(await readJsonFile(releasePath, MAX_LOCAL_RELEASE_BYTES)); + } catch { + return silent('invalid-local-release'); + } + + const nowMs = Number(now()); + if (!Number.isFinite(nowMs)) return silent('invalid-clock'); + let stateDirectory = versionCacheDirectory(cacheDirectory, localRelease.version); + let state; + try { + stateDirectory = await prepareCacheDirectory(stateDirectory); + state = await readState(stateDirectory, localRelease.version); + } catch { + return silent('cache-unavailable'); + } + const cachedResult = freshStateResult(localRelease, state, nowMs); + if (cachedResult) return cachedResult; + + let operation; + try { + operation = await acquireOperation(stateDirectory); + } catch { + return silent('cache-unavailable'); + } + if (!operation) { + try { + const cached = await readState(stateDirectory, localRelease.version); + if (cached.candidate + && cached.notification.offeredDigests.includes(cached.candidate.targetDigest)) { + return resultForCandidate(localRelease, cached); + } + } catch { + return silent('cache-unavailable'); + } + return silent('check-in-progress'); + } + + try { + state = await readState(stateDirectory, localRelease.version); + const racedResult = freshStateResult(localRelease, state, nowMs); + if (racedResult) { + await cancelOperation(stateDirectory, operation); + return racedResult; + } + + if (!await operationOwnsActiveClaim(stateDirectory, operation)) { + return await resultAfterLosingOperation(stateDirectory, operation, localRelease); + } + + await verifyCacheToken(cacheTokenFor(stateDirectory)); + if (!await operationOwnsActiveClaim(stateDirectory, operation)) { + return await resultAfterLosingOperation(stateDirectory, operation, localRelease); + } + let fetched; + try { + fetched = await fetchCandidate({ + fetchImpl, + manifestUrl: localRelease.updateManifestUrl, + timeoutMs, + }); + } catch (error) { + const reason = error instanceof UpdateContractError ? 'invalid-manifest' : 'check-failed'; + try { + const committed = await commitOperation( + stateDirectory, + operation, + localRelease.version, + (current) => ({ + state: stateAfterFailedCheck(current, nowMs), + result: silent(reason), + }), + ); + return committed.result; + } catch { + await cancelOperation(stateDirectory, operation).catch(() => {}); + return silent('cache-unavailable'); + } + } + + try { + const committed = await commitOperation( + stateDirectory, + operation, + localRelease.version, + (current) => { + const next = { + ...current, + candidate: fetched.candidate, + check: { + nextCheckAt: nextSuccessfulCheck(nowMs, random), + consecutiveFailures: 0, + }, + notification: { + offeredDigests: [...current.notification.offeredDigests], + acknowledgedDigests: [...current.notification.acknowledgedDigests], + }, + }; + const result = resultForCandidate(localRelease, next); + if (result.status === 'update_available') { + if (!next.notification.offeredDigests.includes(next.candidate.targetDigest)) { + next.notification.offeredDigests.push(next.candidate.targetDigest); + } + } + return { + state: next, + result, + capacityFallback: { + state: stateAfterFailedCheck(current, nowMs, true), + result: silent('cache-unavailable'), + }, + }; + }, + ); + return committed.result; + } catch { + await cancelOperation(stateDirectory, operation).catch(() => {}); + return silent('cache-unavailable'); + } + } catch { + await cancelOperation(stateDirectory, operation).catch(() => {}); + return silent('cache-unavailable'); + } +} + +export async function acknowledgeUpdate({ + releasePath = defaultReleasePath, + cacheDirectory = defaultCacheDirectory(), + eventKey, + monotonicNow = () => performance.now(), +} = {}) { + const targetDigest = digestForEventKey(eventKey); + if (!targetDigest || typeof monotonicNow !== 'function') { + return silent('invalid-acknowledgement'); + } + let localRelease; + try { + localRelease = validateLocalRelease(await readJsonFile(releasePath, MAX_LOCAL_RELEASE_BYTES)); + } catch { + return silent('invalid-local-release'); + } + let stateDirectory = versionCacheDirectory(cacheDirectory, localRelease.version); + try { + stateDirectory = await prepareCacheDirectory(stateDirectory); + } catch { + return silent('cache-unavailable'); + } + let deadline; + try { + deadline = Number(monotonicNow()) + ACK_LOCK_WAIT_MS; + } catch { + return silent('invalid-acknowledgement'); + } + if (!Number.isFinite(deadline)) return silent('invalid-acknowledgement'); + while (true) { + let operation; + try { + operation = await acquireOperation(stateDirectory); + } catch { + return silent('cache-unavailable'); + } + if (!operation) { + if (!await waitUntilRetry(deadline, monotonicNow)) return silent('check-in-progress'); + continue; + } + try { + const committed = await commitOperation( + stateDirectory, + operation, + localRelease.version, + (state) => { + const wasOffered = state.notification.offeredDigests.includes(targetDigest); + const wasAcknowledged = state.notification.acknowledgedDigests.includes(targetDigest); + if (!wasOffered && !wasAcknowledged) { + return { state: null, result: silent('invalid-acknowledgement') }; + } + return { + state: stateAfterAcknowledgement(state, targetDigest), + result: { status: 'acknowledged', eventKey }, + }; + }, + ); + if (!committed.fenced) return committed.result; + if (!await waitUntilRetry(deadline, monotonicNow)) return silent('check-in-progress'); + } catch { + await cancelOperation(stateDirectory, operation).catch(() => {}); + return silent('cache-unavailable'); + } + } +} + +async function runCli() { + if (process.env.ARCHIFY_UPDATE_CHECK_DISABLED === '1') return silent('disabled'); + const argumentsList = process.argv.slice(2); + if (argumentsList.length === 0) return checkForUpdate(); + if (argumentsList.length === 2 && argumentsList[0] === '--ack') { + return acknowledgeUpdate({ eventKey: argumentsList[1] }); + } + return silent('invalid-arguments'); +} + +async function isMainModule() { + if (!process.argv[1]) return false; + try { + const [entryPath, modulePath] = await Promise.all([ + fs.realpath(path.resolve(process.argv[1])), + fs.realpath(fileURLToPath(import.meta.url)), + ]); + return entryPath === modulePath; + } catch { + return path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url)); + } +} + +if (await isMainModule()) { + let result; + try { + result = await runCli(); + } catch { + result = silent('check-failed'); + } + process.stdout.write(`${JSON.stringify(result)}\n`); +} diff --git a/tools/archify/scripts/generate-brand-marks.mjs b/tools/archify/scripts/generate-brand-marks.mjs new file mode 100644 index 0000000..73490ba --- /dev/null +++ b/tools/archify/scripts/generate-brand-marks.mjs @@ -0,0 +1,141 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import * as simpleIcons from 'simple-icons'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(here, '..'); +const catalogPath = path.join(root, 'brand-marks', 'catalog.json'); +const outputPath = path.join(root, 'renderers', 'shared', 'generated-brand-marks.mjs'); +const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8')); +const simpleIconsVersion = JSON.parse(fs.readFileSync( + path.join(root, 'node_modules', 'simple-icons', 'package.json'), + 'utf8', +)).version; +const simpleBySlug = new Map(Object.values(simpleIcons) + .filter((icon) => icon && typeof icon === 'object' && icon.slug && icon.path) + .map((icon) => [icon.slug, icon])); + +function normalizedList(value) { + return [...new Set((Array.isArray(value) ? value : []) + .map((item) => String(item).trim()) + .filter(Boolean))]; +} + +function lookupForms(value) { + const raw = String(value ?? '').trim().toLocaleLowerCase('en-US'); + if (!raw) return []; + return [...new Set([ + raw, + raw.replace(/[\s_]+/g, '-'), + raw.replace(/[\s_.-]+/g, ''), + ])]; +} + +function fail(message) { + console.error(`brand catalog: ${message}`); + process.exit(1); +} + +if (catalog.schemaVersion !== 1 || !Array.isArray(catalog.marks) || catalog.marks.length === 0) { + fail('catalog.json must contain a non-empty schemaVersion 1 marks array'); +} + +const ids = new Set(); +const lookupKeys = new Map(); +const domains = new Map(); +const generated = catalog.marks.map((entry, index) => { + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.id || '')) fail(`marks[${index}] has an invalid id`); + if (ids.has(entry.id)) fail(`duplicate id ${entry.id}`); + ids.add(entry.id); + + const aliases = normalizedList(entry.aliases); + const entryDomains = normalizedList(entry.domains).map((domain) => domain.toLowerCase()); + for (const key of [entry.id, ...aliases]) { + for (const form of lookupForms(key)) { + if (lookupKeys.has(form) && lookupKeys.get(form) !== entry.id) { + fail(`lookup key ${JSON.stringify(key)} is shared by ${lookupKeys.get(form)} and ${entry.id}`); + } + lookupKeys.set(form, entry.id); + } + } + for (const domain of entryDomains) { + if (domains.has(domain) && domains.get(domain) !== entry.id) { + fail(`domain ${domain} is shared by ${domains.get(domain)} and ${entry.id}`); + } + domains.set(domain, entry.id); + } + + let mark; + if (entry.simpleIcon) { + const icon = simpleBySlug.get(entry.simpleIcon); + if (!icon) fail(`${entry.id} references missing Simple Icons slug ${entry.simpleIcon}`); + mark = { + id: entry.id, + title: entry.title || icon.title, + category: entry.category, + aliases, + domains: entryDomains, + viewBox: 24, + hex: icon.hex, + path: icon.path, + provenance: { + provider: 'Simple Icons', + providerVersion: simpleIconsVersion, + source: icon.source, + ...(icon.guidelines ? { guidelines: icon.guidelines } : {}), + ...(icon.license ? { license: icon.license } : {}), + }, + }; + } else if (entry.custom) { + const custom = entry.custom; + if (!entry.title || !custom.path || !custom.source || !/^[0-9A-F]{6}$/i.test(custom.hex || '')) { + fail(`${entry.id} custom mark requires title, path, source, and six-digit hex`); + } + mark = { + id: entry.id, + title: entry.title, + category: entry.category, + aliases, + domains: entryDomains, + viewBox: custom.viewBox || 24, + hex: custom.hex.toUpperCase(), + path: custom.path, + provenance: { + provider: 'Official brand asset', + source: custom.source, + ...(custom.guidelines ? { guidelines: custom.guidelines } : {}), + }, + }; + } else { + fail(`${entry.id} must provide simpleIcon or custom`); + } + if (!mark.category || !mark.title) fail(`${entry.id} is missing category or title`); + for (const form of lookupForms(mark.title)) { + if (lookupKeys.has(form) && lookupKeys.get(form) !== entry.id) { + fail(`title ${JSON.stringify(mark.title)} is shared by ${lookupKeys.get(form)} and ${entry.id}`); + } + lookupKeys.set(form, entry.id); + } + return mark; +}).sort((left, right) => left.id.localeCompare(right.id)); + +const banner = `// Generated by scripts/generate-brand-marks.mjs from brand-marks/catalog.json.\n// Simple Icons ${simpleIconsVersion}. Do not edit by hand.\n`; +const source = `${banner}export const BRAND_MARKS = Object.freeze(${JSON.stringify(generated, null, 2)});\n`; + +if (process.argv.includes('--check')) { + const current = fs.existsSync(outputPath) + ? fs.readFileSync(outputPath, 'utf8').replace(/\r\n?/g, '\n') + : ''; + if (current !== source) { + console.error('generated brand marks are stale — run npm run generate:brand-marks'); + process.exit(1); + } +} else { + const temporary = `${outputPath}.${process.pid}.tmp`; + fs.writeFileSync(temporary, source); + fs.renameSync(temporary, outputPath); + console.log(`generated ${path.relative(root, outputPath)} (${generated.length} marks)`); +} diff --git a/tools/archify/scripts/generate-validators.mjs b/tools/archify/scripts/generate-validators.mjs new file mode 100644 index 0000000..ab205bd --- /dev/null +++ b/tools/archify/scripts/generate-validators.mjs @@ -0,0 +1,66 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import Ajv2020 from 'ajv/dist/2020.js'; +import standaloneCode from 'ajv/dist/standalone/index.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, '..'); +const schemasDir = path.join(root, 'schemas'); +const output = path.join(root, 'renderers/shared/generated-validators.mjs'); +const diagramTypes = ['workflow', 'sequence', 'dataflow', 'lifecycle', 'architecture']; + +const ajv = new Ajv2020({ + allErrors: true, + strict: true, + code: { source: true, esm: true }, +}); +ajv.addSchema(JSON.parse(fs.readFileSync(path.join(schemasDir, 'common.schema.json'), 'utf8'))); + +const schemaIds = {}; +for (const type of diagramTypes) { + const schema = JSON.parse(fs.readFileSync(path.join(schemasDir, `${type}.schema.json`), 'utf8')); + ajv.addSchema(schema); + schemaIds[type] = schema.$id; +} + +const banner = '// Generated by scripts/generate-validators.mjs. Do not edit by hand.\n'; +const ajvUcs2Import = 'require("ajv/dist/runtime/ucs2length").default'; +const inlineUcs2Length = `function ucs2length(str) { + const len = str.length; + let length = 0; + let pos = 0; + while (pos < len) { + length += 1; + const value = str.charCodeAt(pos++); + if (value >= 0xd800 && value <= 0xdbff && pos < len + && (str.charCodeAt(pos) & 0xfc00) === 0xdc00) pos += 1; + } + return length; +}`; +let validatorCode = standaloneCode(ajv, schemaIds); +if (!validatorCode.includes(ajvUcs2Import)) { + throw new Error('AJV standalone output no longer contains the expected ucs2length helper'); +} +validatorCode = validatorCode.replaceAll(ajvUcs2Import, inlineUcs2Length); +if (validatorCode.includes('require(')) { + throw new Error('AJV standalone output contains an unexpected runtime dependency'); +} +const generated = `${banner}${validatorCode}\n`; + +if (process.argv.includes('--check')) { + const current = fs.existsSync(output) + ? fs.readFileSync(output, 'utf8').replace(/\r\n?/g, '\n') + : ''; + if (current !== generated) { + console.error('generated validators are stale — run npm run generate:validators'); + process.exit(1); + } +} else { + const temporary = `${output}.${process.pid}.tmp`; + fs.writeFileSync(temporary, generated); + fs.renameSync(temporary, output); + console.log(`generated ${path.relative(root, output)}`); +} diff --git a/tools/archify/scripts/render-examples.mjs b/tools/archify/scripts/render-examples.mjs new file mode 100644 index 0000000..9889a2f --- /dev/null +++ b/tools/archify/scripts/render-examples.mjs @@ -0,0 +1,26 @@ +// Re-render every bundled example from its JSON IR. Installed skills keep HTML +// beside the JSON examples; the development script passes the golden directory. + +import { execFileSync } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const skillRoot = path.resolve(__dirname, '..'); +const outputRoot = path.resolve(process.argv[2] || path.join(skillRoot, 'examples')); + +const TARGETS = [ + ['workflow', 'agent-tool-call.workflow.json', 'workflow-agent-tool-call-rendered.html'], + ['sequence', 'cache-miss-request.sequence.json', 'sequence-cache-miss-request.html'], + ['dataflow', 'product-analytics.dataflow.json', 'dataflow-product-analytics.html'], + ['lifecycle', 'agent-run.lifecycle.json', 'lifecycle-agent-run.html'], + ['architecture', 'web-app.architecture.json', 'web-app-rendered.html'], +]; + +for (const [mode, input, output] of TARGETS) { + execFileSync(process.execPath, [ + path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), + path.join(skillRoot, 'examples', input), + path.join(outputRoot, output), + ], { stdio: 'inherit' }); +} diff --git a/tools/archify/scripts/update-contract.mjs b/tools/archify/scripts/update-contract.mjs new file mode 100644 index 0000000..527ae8a --- /dev/null +++ b/tools/archify/scripts/update-contract.mjs @@ -0,0 +1,182 @@ +export const SKILL_ID = 'archify'; +export const EXPECTED_REPOSITORY = 'https://github.com/tt-a1i/archify'; +export const DEFAULT_MANIFEST_URL = 'https://tt-a1i.github.io/archify/skill-updates/archify/stable.json'; + +const CONTROL_OR_BIDI = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u; +const HEX_40 = /^[a-f0-9]{40}$/; +const HEX_64 = /^[a-f0-9]{64}$/; +const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/; +const UTC_SECONDS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/; + +export class UpdateContractError extends Error { + constructor(message) { + super(message); + this.name = 'UpdateContractError'; + } +} + +function isPlainObject(value) { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} + +function hasExactKeys(value, expected) { + return isPlainObject(value) + && Object.keys(value).sort().join('\0') === [...expected].sort().join('\0'); +} + +export function parseSemver(value) { + if (typeof value !== 'string' || value.length > 128) { + throw new UpdateContractError(`invalid SemVer: ${JSON.stringify(value)}`); + } + const match = SEMVER.exec(value); + if (!match) throw new UpdateContractError(`invalid SemVer: ${JSON.stringify(value)}`); + const prerelease = match[4]?.split('.') ?? null; + if (prerelease?.some((identifier) => /^\d+$/.test(identifier) + && identifier.length > 1 && identifier.startsWith('0'))) { + throw new UpdateContractError(`invalid SemVer: ${JSON.stringify(value)}`); + } + return { + core: match.slice(1, 4), + prerelease, + build: match[5]?.split('.') ?? null, + }; +} + +function compareNumericIdentifiers(left, right) { + if (left.length !== right.length) return left.length < right.length ? -1 : 1; + if (left === right) return 0; + return left < right ? -1 : 1; +} + +function comparePrerelease(left, right) { + if (left === null && right === null) return 0; + if (left === null) return 1; + if (right === null) return -1; + const length = Math.max(left.length, right.length); + for (let index = 0; index < length; index += 1) { + if (left[index] === undefined) return -1; + if (right[index] === undefined) return 1; + if (left[index] === right[index]) continue; + const leftNumeric = /^\d+$/.test(left[index]); + const rightNumeric = /^\d+$/.test(right[index]); + if (leftNumeric && rightNumeric) return compareNumericIdentifiers(left[index], right[index]); + if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1; + return left[index] < right[index] ? -1 : 1; + } + return 0; +} + +export function compareSemver(leftValue, rightValue) { + const left = parseSemver(leftValue); + const right = parseSemver(rightValue); + for (let index = 0; index < left.core.length; index += 1) { + const comparison = compareNumericIdentifiers(left.core[index], right.core[index]); + if (comparison !== 0) return comparison; + } + return comparePrerelease(left.prerelease, right.prerelease); +} + +export function releaseChannelForVersion(value) { + return parseSemver(value).prerelease ? 'development' : 'stable'; +} + +export function isStableCoreVersion(value) { + try { + const parsed = parseSemver(value); + return parsed.prerelease === null && parsed.build === null; + } catch { + return false; + } +} + +export function validateCanonicalUtcTimestamp(value) { + if (typeof value !== 'string' || !UTC_SECONDS.test(value)) { + throw new UpdateContractError('publication time must use YYYY-MM-DDTHH:mm:ssZ'); + } + const timestamp = Date.parse(value); + if (!Number.isFinite(timestamp) + || new Date(timestamp).toISOString().replace('.000Z', 'Z') !== value) { + throw new UpdateContractError('publication time is not a real UTC calendar instant'); + } + return value; +} + +export function validateLocalRelease(value) { + if (!hasExactKeys(value, [ + 'schemaVersion', 'skillId', 'channel', 'version', 'source', 'updateManifestUrl', + ]) + || value.schemaVersion !== 1 + || value.skillId !== SKILL_ID + || !hasExactKeys(value.source, ['repository']) + || value.source.repository !== EXPECTED_REPOSITORY + || value.updateManifestUrl !== DEFAULT_MANIFEST_URL) { + throw new UpdateContractError('invalid local release identity'); + } + const expectedChannel = releaseChannelForVersion(value.version); + if (value.channel !== expectedChannel) { + throw new UpdateContractError('local release channel does not match its version'); + } + return { + schemaVersion: value.schemaVersion, + skillId: value.skillId, + channel: value.channel, + version: value.version, + source: { repository: value.source.repository }, + updateManifestUrl: value.updateManifestUrl, + }; +} + +export function validateReleaseNotesUrl(value, version) { + if (!isStableCoreVersion(version)) { + throw new UpdateContractError('release notes require a stable core version'); + } + const expected = `https://github.com/tt-a1i/archify/releases/tag/v${version}`; + if (value !== expected) { + throw new UpdateContractError('release notes URL is outside the exact trusted release path'); + } + return value; +} + +export function validateStableUpdateManifest(value) { + if (!hasExactKeys(value, [ + 'schemaVersion', 'skillId', 'channel', 'version', 'publishedAt', 'source', + 'artifact', 'summary', 'releaseNotes', 'severity', + ]) + || value.schemaVersion !== 1 + || value.skillId !== SKILL_ID + || value.channel !== 'stable' + || !isStableCoreVersion(value.version) + || !hasExactKeys(value.source, ['repository', 'ref', 'treeSha']) + || value.source.repository !== EXPECTED_REPOSITORY + || value.source.ref !== `v${value.version}` + || !HEX_40.test(value.source.treeSha) + || !hasExactKeys(value.artifact, ['sha256']) + || !HEX_64.test(value.artifact.sha256)) { + throw new UpdateContractError('invalid immutable stable release identity'); + } + validateCanonicalUtcTimestamp(value.publishedAt); + if (typeof value.summary !== 'string' || value.summary.length < 1 || value.summary.length > 160 + || CONTROL_OR_BIDI.test(value.summary)) { + throw new UpdateContractError('invalid release summary'); + } + validateReleaseNotesUrl(value.releaseNotes, value.version); + if (!['normal', 'security'].includes(value.severity)) { + throw new UpdateContractError('invalid update severity'); + } + return { + schemaVersion: value.schemaVersion, + skillId: value.skillId, + channel: value.channel, + version: value.version, + publishedAt: value.publishedAt, + source: { + repository: value.source.repository, + ref: value.source.ref, + treeSha: value.source.treeSha, + }, + artifact: { sha256: value.artifact.sha256 }, + summary: value.summary, + releaseNotes: value.releaseNotes, + severity: value.severity, + }; +} diff --git a/tools/archify/skill-release.json b/tools/archify/skill-release.json new file mode 100644 index 0000000..aa40d67 --- /dev/null +++ b/tools/archify/skill-release.json @@ -0,0 +1,10 @@ +{ + "schemaVersion": 1, + "skillId": "archify", + "channel": "development", + "version": "2.17.0-dev.1", + "source": { + "repository": "https://github.com/tt-a1i/archify" + }, + "updateManifestUrl": "https://tt-a1i.github.io/archify/skill-updates/archify/stable.json" +} diff --git a/workload/README.md b/workload/README.md deleted file mode 100644 index e58d872..0000000 --- a/workload/README.md +++ /dev/null @@ -1 +0,0 @@ -repo workload such as source code, terraform , bicep etc go here From e6d897d04f748bd218f3d2c2d0c59f3e54d39ea4 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Sun, 11 Oct 2026 16:21:57 +0100 Subject: [PATCH 2/2] docs: verify product architecture and Windows agent migration --- README.md | 2 + diagrams/manifest.yaml | 63 ++- diagrams/sources/agent-core.json | 146 ++++--- diagrams/sources/guestweave-macos.json | 187 ++++++-- .../guestweave-windows-development.json | 38 ++ diagrams/sources/guestweave-windows.json | 137 +----- diagrams/sources/hostweave.json | 133 ++++-- diagrams/sources/imageweave-construction.json | 77 ++-- diagrams/sources/module-startup.json | 52 +-- diagrams/sources/platform-context.json | 203 ++++++++- docs/development/documentation.md | 21 +- docs/platform/architecture.mdx | 75 +++- docs/platform/compatibility.md | 67 ++- docs/platform/glossary.md | 25 +- docs/products/agent-core/hld.mdx | 105 ++++- docs/products/agent-modules/hld.mdx | 97 ++++- docs/products/guestweave/index.md | 45 +- docs/products/guestweave/macos.mdx | 201 ++++++++- .../guestweave/windows-development.mdx | 161 +++++++ docs/products/guestweave/windows.mdx | 121 +++++- docs/products/hostweave/hld.mdx | 120 +++++- docs/products/imageweave/hld.mdx | 130 +++++- docs/reference/index.md | 38 +- docs/start-here/index.md | 5 +- scripts/check-browser.mjs | 2 +- scripts/source-refresh.mjs | 2 +- site/sidebars.ts | 2 +- sources/reviews/pr11-code-audit.md | 55 +++ sources/sources.lock.yaml | 398 ++++++++++++++++++ 29 files changed, 2214 insertions(+), 494 deletions(-) create mode 100644 diagrams/sources/guestweave-windows-development.json create mode 100644 docs/products/guestweave/windows-development.mdx create mode 100644 sources/reviews/pr11-code-audit.md diff --git a/README.md b/README.md index 98a1f21..e9d4b02 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This repository is the canonical source for public Weave documentation and its w The first release covers the platform HLD and GuestWeave, HostWeave, Imageweave, agent-core and agent-modules. Diagrams use **Archify**. Claims follow inspected code at exact commits; the compatibility register makes incomplete integrations explicit. +Windows GuestWeave's main and development architectures have separate HLDs and source pins. The [PR11 implementation audit](sources/reviews/pr11-code-audit.md) records the corrected product relationships and remaining integration gaps. + ## Local use Use Node **22.23.3** and npm: diff --git a/diagrams/manifest.yaml b/diagrams/manifest.yaml index 3c139cb..a30f1d7 100644 --- a/diagrams/manifest.yaml +++ b/diagrams/manifest.yaml @@ -19,58 +19,91 @@ diagrams: - documentation:.github/workflows/deploy.yml - id: agent-core type: architecture - title: 'Weave agent: supervision and host services' + title: 'Agent core inside the guest' source: diagrams/sources/agent-core.json audience: public evidence: - agent-core:internal/core/core.go - - agent-core:cmd/weaveboot/main.go + - agent-core:internal/supervise/launch.go + - agent-core:internal/hostserv/hostserv.go + - agent-core:internal/transport/hypervisor.go + - agent-core:internal/weaveboot/weaveboot.go - id: guestweave-macos type: architecture - title: GuestWeave on a macOS host + title: "GuestWeave on macOS: host runtime and guest components" source: diagrams/sources/guestweave-macos.json audience: public evidence: - - guestweave-macos:internal/httpapi/server.go - - guestweave-macos:go.mod + - guestweave-macos:internal/vm/run/session.go + - guestweave-macos:internal/vm/run/guestchannel.go + - guestweave-macos:internal/hypervisor/hypervisor.go + - guestweave-macos:internal/vm/guestagent/packages.go + - guestweave-macos:internal/vm/guestagent/windows.go + - guestweave-macos:internal/imagesource/oci/registry.go - id: guestweave-windows type: architecture title: GuestWeave on a Windows host source: diagrams/sources/guestweave-windows.json audience: public evidence: - - guestweave-windows:internal/serve/http.go - - guestweave-windows:internal/oci/oci.go + - guestweave-windows:internal/command/command.go + - guestweave-windows:internal/hcs/engine.go + - guestweave-windows:internal/guestweaveagent/client/client.go + - guestweave-windows:internal/guestweaveagent/proto/proto.go + - guestweave-windows:cmd/weaveagent/modulebus.go + - guestweave-windows:cmd/weaveguestd/main.go +- id: guestweave-windows-development + type: architecture + title: 'GuestWeave Windows: development agent handover' + source: diagrams/sources/guestweave-windows-development.json + audience: public + evidence: + - guestweave-windows-development:internal/command/run_channel.go + - guestweave-windows-development:internal/guestchannel/channel.go + - guestweave-windows-development:internal/guestchannel/dial_windows.go + - guestweave-windows-development:internal/vm/guestctl/socket.go + - guestweave-windows-development:internal/vm/channelkey/private_windows.go + - guestweave-windows-development:internal/vm/guestagent/packages.go - id: hostweave type: architecture - title: 'HostWeave: placement to VM execution' + title: 'HostWeave: control plane, host agent and execution backends' source: diagrams/sources/hostweave.json audience: public evidence: - - hostweave:internal/server/server.go + - hostweave:internal/app/app.go + - hostweave:internal/agentgw/gateway.go + - hostweave:agent/supervisor/supervisor.go + - hostweave:internal/cli/agentcmd/vms.go + - hostweave:internal/cli/agentcmd/containers.go - hostweave:agent/runtime/guestweave/guestweave.go - id: imageweave-construction type: workflow - title: 'Imageweave: construction is not runtime qualification' + title: 'Imageweave construction and runtime acceptance' source: diagrams/sources/imageweave-construction.json audience: public evidence: - - imageweave:pkg/delivery/native.go - imageweave:pkg/plan/plan.go + - imageweave:pkg/nativebuild/build.go + - imageweave:pkg/delivery/native.go + - imageweave:pkg/delivery/build.go - id: module-startup type: sequence - title: Capability module startup + title: Module startup and local IPC source: diagrams/sources/module-startup.json audience: public evidence: + - agent-core:internal/supervise/launch.go - agent-modules:sdk/modulesdk/serve.go - - agent-modules:sdk/modulesdk/module.go + - agent-modules:sdk/modulesdk/lifecycle.go - id: platform-context type: architecture - title: 'Weave platform: execution and image construction' + title: 'GuestWeave composition and platform relationships' source: diagrams/sources/platform-context.json audience: public evidence: - hostweave:agent/runtime/guestweave/guestweave.go - - imageweave:pkg/delivery/native.go + - guestweave-macos:internal/vm/run/guestchannel.go + - guestweave-windows:cmd/weaveagent/modulebus.go + - guestweave-windows:internal/guestweaveagent/proto/proto.go + - imageweave:pkg/nativebuild/build.go - agent-core:internal/core/core.go diff --git a/diagrams/sources/agent-core.json b/diagrams/sources/agent-core.json index 2905dc2..df20fae 100644 --- a/diagrams/sources/agent-core.json +++ b/diagrams/sources/agent-core.json @@ -2,122 +2,156 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "Weave agent: supervision and host services", + "title": "Agent core inside the guest", "locale": "en", "quality_profile": "showcase", "viewBox": [ - 1240, - 720 + 1360, + 780 ] }, "components": [ { - "id": "manager", + "id": "hostclient", "type": "external", - "label": "Service manager", - "sublabel": "launchd, systemd or SCM", + "label": "GuestWeave SDK client", + "sublabel": "Physical host owns stream", "pos": [ 40, - 150 + 160 ], "size": [ - 220, - 84 + 250, + 90 ] }, { - "id": "boot", + "id": "core", "type": "backend", - "label": "weaveboot", - "sublabel": "Stage, supervise and roll back", + "label": "weave-agent core", + "sublabel": "Registry + channel router", "pos": [ - 350, - 150 + 530, + 160 ], "size": [ - 220, - 84 + 250, + 90 ] }, { - "id": "core", + "id": "module", "type": "backend", - "label": "Agent core", - "sublabel": "Verify and supervise modules", + "label": "Capability process", + "sublabel": "OS-specific module backend", "pos": [ - 660, - 150 + 1050, + 160 ], "size": [ - 220, - 84 + 270, + 90 ] }, { - "id": "module", + "id": "services", + "type": "security", + "label": "Module host services", + "sublabel": "Core-owned local RPC server", + "pos": [ + 1050, + 450 + ], + "size": [ + 270, + 90 + ] + }, + { + "id": "boot", "type": "backend", - "label": "Capability module", - "sublabel": "Separate module process", + "label": "weaveboot", + "sublabel": "Core selection + crash recovery", "pos": [ - 970, - 150 + 530, + 450 ], "size": [ - 220, - 84 + 250, + 90 ] }, { - "id": "store", - "type": "security", - "label": "Host services", - "sublabel": "Identity, policy, store and bus", + "id": "manager", + "type": "external", + "label": "Guest service manager", + "sublabel": "launchd, systemd or SCM", "pos": [ - 660, - 410 + 530, + 620 ], "size": [ - 220, - 84 + 250, + 90 ] } ], - "connections": [ + "boundaries": [ { - "to": "boot", - "label": "Start service", - "from": "manager" - }, + "kind": "region", + "label": "Guest / device operating system", + "wraps": [ + "core", + "module", + "services", + "boot", + "manager" + ] + } + ], + "connections": [ { + "from": "hostclient", "to": "core", - "label": "Supervise", - "from": "boot" + "label": "Authenticated frames", + "variant": "security" }, { + "from": "core", "to": "module", - "label": "Verify and launch", - "from": "core" + "label": "Lifecycle + delivery", + "variant": "emphasis" }, { - "to": "store", - "label": "Serve RPC", - "from": "core", + "from": "module", + "to": "services", + "label": "Token RPC", "labelDy": 24 + }, + { + "from": "manager", + "to": "boot", + "label": "Start service" + }, + { + "from": "boot", + "to": "core", + "label": "Supervise" } ], "cards": [ { - "dot": "rose", - "title": "Verification", + "dot": "violet", + "title": "Ownership", "items": [ - "Core refuses module execution when verification is unavailable." + "Host services live in core inside the guest. Each capability runs as its own process." ] }, { - "dot": "violet", - "title": "Ownership", + "dot": "rose", + "title": "Separate trust", "items": [ - "Service manager owns weaveboot; core owns module supervision." + "Channel host keys, local launch tokens and release verification govern different boundaries." ] } ] diff --git a/diagrams/sources/guestweave-macos.json b/diagrams/sources/guestweave-macos.json index c54370b..1eedcc8 100644 --- a/diagrams/sources/guestweave-macos.json +++ b/diagrams/sources/guestweave-macos.json @@ -2,23 +2,23 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "GuestWeave on a macOS host", + "title": "GuestWeave on macOS: host runtime and guest components", "locale": "en", "quality_profile": "showcase", "viewBox": [ - 1240, - 720 + 1350, + 760 ] }, "components": [ { - "id": "client", + "id": "interfaces", "type": "external", - "label": "User or integration", - "sublabel": "CLI, HTTP or MCP", + "label": "CLI + lifecycle APIs", + "sublabel": "HTTP loopback / MCP stdio", "pos": [ 40, - 150 + 235 ], "size": [ 220, @@ -26,98 +26,193 @@ ] }, { - "id": "commands", + "id": "operations", "type": "backend", - "label": "Command layer", - "sublabel": "Shared lifecycle operations", + "label": "Local operations", + "sublabel": "Commands + VM service helpers", "pos": [ 350, - 150 + 235 ], "size": [ - 220, + 230, 84 ] }, { - "id": "vmservice", + "id": "run", "type": "backend", - "label": "VM service", - "sublabel": "Configuration and state", + "label": "Per-VM run process", + "sublabel": "guest.sock + SDK weaveclient", "pos": [ - 660, - 150 + 650, + 235 ], "size": [ - 220, + 230, 84 ] }, { - "id": "backend", + "id": "core", "type": "backend", - "label": "Host backend", - "sublabel": "Apple virtualization APIs", + "label": "weave-agent core", + "sublabel": "Guest daemon / authenticated router", "pos": [ - 970, - 150 + 1010, + 235 ], "size": [ - 220, + 280, + 84 + ] + }, + { + "id": "modules", + "type": "backend", + "label": "OS capability modules", + "sublabel": "exec, power, presence, clipboard", + "pos": [ + 1010, + 440 + ], + "size": [ + 280, + 84 + ] + }, + { + "id": "guestOS", + "type": "external", + "label": "Guest operating system", + "sublabel": "macOS, Linux or Windows", + "pos": [ + 1010, + 645 + ], + "size": [ + 280, + 84 + ] + }, + { + "id": "engines", + "type": "backend", + "label": "Virtualization engines", + "sublabel": "VZ: macOS/Linux; HV: Windows", + "pos": [ + 650, + 645 + ], + "size": [ + 230, 84 ] }, { - "id": "storage", + "id": "state", "type": "database", - "label": "VM files", - "sublabel": "Disks and identity state", + "label": "Local VM state", + "sublabel": "Disk, config, identity + channel key", "pos": [ - 660, - 410 + 650, + 30 ], "size": [ - 220, + 230, 84 ] + }, + { + "id": "images", + "type": "cloud", + "label": "OCI image adapter", + "sublabel": "Shared OCI library + base cache", + "pos": [ + 350, + 30 + ], + "size": [ + 230, + 84 + ] + } + ], + "boundaries": [ + { + "kind": "region", + "label": "Guest VM", + "wraps": [ + "core", + "modules", + "guestOS" + ], + "pad": 18 } ], "connections": [ { - "to": "commands", - "label": "Invoke", - "from": "client" + "from": "interfaces", + "to": "operations", + "label": "Lifecycle" + }, + { + "from": "operations", + "to": "run", + "label": "Spawn / IPC" }, { - "to": "vmservice", - "label": "Lifecycle", - "from": "commands" + "from": "run", + "to": "core", + "label": "Agent channel" }, { - "to": "backend", - "label": "Native VM calls", - "from": "vmservice" + "from": "core", + "to": "modules", + "label": "Route operations", + "labelDy": 24 + }, + { + "from": "modules", + "to": "guestOS", + "label": "OS capabilities", + "labelDy": 24 }, { - "to": "storage", - "label": "Persist", - "from": "vmservice", + "from": "run", + "to": "engines", + "label": "In-process VM", "labelDy": 24 + }, + { + "from": "engines", + "to": "guestOS", + "label": "Boot guest" + }, + { + "from": "run", + "to": "state", + "label": "VM files + key" + }, + { + "from": "operations", + "to": "images", + "label": "Pull / push" } ], "cards": [ { "dot": "cyan", - "title": "Host responsibility", + "title": "Product composition", "items": [ - "GuestWeave owns local VM state and lifecycle." + "GuestWeave hosts the VM and the SDK client. Agent core and modules run inside the guest." ] }, { "dot": "amber", - "title": "Guest matrix", + "title": "Entry-point difference", "items": [ - "Host architecture and guest OS constrain backend selection." + "CLI exec uses the agent channel. HTTP and MCP exec still connect to the guest over SSH." ] } ] diff --git a/diagrams/sources/guestweave-windows-development.json b/diagrams/sources/guestweave-windows-development.json new file mode 100644 index 0000000..93911ee --- /dev/null +++ b/diagrams/sources/guestweave-windows-development.json @@ -0,0 +1,38 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "GuestWeave Windows: development handover", + "locale": "en", + "quality_profile": "showcase", + "viewBox": [1390, 760] + }, + "components": [ + {"id": "operation-callers", "type": "external", "label": "Users and integrations", "sublabel": "CLI · REST · MCP stdio", "pos": [40, 150], "size": [240, 92]}, + {"id": "run-supervisor", "type": "backend", "label": "Run supervisor", "sublabel": "Owner-only guestctl pipe", "pos": [390, 150], "size": [240, 92]}, + {"id": "sdk-session", "type": "security", "label": "Host SDK session", "sublabel": "weaveclient · per-VM Ed25519", "pos": [740, 150], "size": [240, 92]}, + {"id": "guest-core", "type": "backend", "label": "Guest agent core", "sublabel": "Service inside Linux / Windows", "pos": [1090, 150], "size": [240, 92]}, + {"id": "release-assets", "type": "external", "label": "Release assets", "sublabel": "Core + eight OS modules", "pos": [40, 460], "size": [240, 92]}, + {"id": "vm-state", "type": "database", "label": "VM store and payloads", "sublabel": "Disks · keys · trust · seeds", "pos": [390, 460], "size": [240, 92]}, + {"id": "windows-runtime", "type": "backend", "label": "Windows HCS / HCN", "sublabel": "Guest compute and networks", "pos": [740, 460], "size": [240, 92]}, + {"id": "guest-module-processes", "type": "backend", "label": "Capability modules", "sublabel": "Separate guest executables", "pos": [1090, 460], "size": [240, 92]} + ], + "boundaries": [ + {"kind": "region", "label": "Physical Windows host", "wraps": ["run-supervisor", "sdk-session", "vm-state", "windows-runtime"], "pad": 26}, + {"kind": "region", "label": "Guest operating system", "wraps": ["guest-core", "guest-module-processes"], "pad": 26} + ], + "connections": [ + {"id": "request-owner", "from": "operation-callers", "to": "run-supervisor", "label": "Guest operations"}, + {"id": "invoke-sdk", "from": "run-supervisor", "to": "sdk-session", "label": "SDK calls"}, + {"id": "authenticate-core", "from": "sdk-session", "to": "guest-core", "label": "HvSocket 2010", "variant": "security"}, + {"id": "supervise-modules", "from": "guest-core", "to": "guest-module-processes", "label": "Local gRPC", "labelDy": 24}, + {"id": "persist-host-state", "from": "run-supervisor", "to": "vm-state", "label": "Persist state", "labelDy": 24}, + {"id": "manage-runtime", "from": "run-supervisor", "to": "windows-runtime", "label": "Boot / lifecycle"}, + {"id": "acquire-payloads", "from": "release-assets", "to": "vm-state", "label": "Fetch / verify"} + ], + "cards": [ + {"dot": "cyan", "title": "Trust provisioning", "items": ["CIDATA / WEAVEPROV supplies the public key out of band.", "The private key stays on the physical Windows host."]}, + {"dot": "emerald", "title": "Process composition", "items": ["Host links SDK v0.2.7; guest core owns module lifecycle.", "Legacy bundled agents and their local bus are removed."]}, + {"dot": "amber", "title": "Development boundary", "items": ["PR170 source inspection does not prove runtime acceptance.", "The HostWeave capabilities command is still absent."]} + ] +} diff --git a/diagrams/sources/guestweave-windows.json b/diagrams/sources/guestweave-windows.json index eef8cde..a118d52 100644 --- a/diagrams/sources/guestweave-windows.json +++ b/diagrams/sources/guestweave-windows.json @@ -1,124 +1,33 @@ { "schema_version": 1, "diagram_type": "architecture", - "meta": { - "title": "GuestWeave on a Windows host", - "locale": "en", - "quality_profile": "showcase", - "viewBox": [ - 1240, - 720 - ] - }, + "meta": {"title": "GuestWeave on a Windows host", "locale": "en", "quality_profile": "showcase", "viewBox": [1390, 760]}, "components": [ - { - "id": "client", - "type": "external", - "label": "User or integration", - "sublabel": "CLI or HTTP", - "pos": [ - 40, - 150 - ], - "size": [ - 220, - 84 - ] - }, - { - "id": "commands", - "type": "backend", - "label": "Command layer", - "sublabel": "VM lifecycle operations", - "pos": [ - 350, - 150 - ], - "size": [ - 220, - 84 - ] - }, - { - "id": "hcs", - "type": "backend", - "label": "HCS backend", - "sublabel": "Compute-system management", - "pos": [ - 660, - 150 - ], - "size": [ - 220, - 84 - ] - }, - { - "id": "guest", - "type": "backend", - "label": "Guest VM", - "sublabel": "Windows or Linux", - "pos": [ - 970, - 150 - ], - "size": [ - 220, - 84 - ] - }, - { - "id": "network", - "type": "cloud", - "label": "HCN networking", - "sublabel": "Host compute networks", - "pos": [ - 660, - 410 - ], - "size": [ - 220, - 84 - ] - } + {"id": "entrypoints", "type": "external", "label": "User or integration", "sublabel": "CLI · HTTP · MCP stdio", "pos": [40, 150], "size": [240, 92]}, + {"id": "commands", "type": "backend", "label": "GuestWeave commands", "sublabel": "Host-side lifecycle and tools", "pos": [390, 150], "size": [240, 92]}, + {"id": "hcs-engine", "type": "backend", "label": "GuestWeave HCS engine", "sublabel": "HCS compute · HCN networks", "pos": [740, 150], "size": [240, 92]}, + {"id": "guest-vm", "type": "backend", "label": "Guest VM", "sublabel": "Windows or Linux", "pos": [1090, 150], "size": [240, 92]}, + {"id": "oci-registry", "type": "external", "label": "OCI registry", "sublabel": "GuestWeave VHDX v2 artifacts", "pos": [40, 460], "size": [240, 92]}, + {"id": "host-state", "type": "database", "label": "VM store and cache", "sublabel": "Config · VHDX · saved state", "pos": [390, 460], "size": [240, 92]}, + {"id": "module-client", "type": "backend", "label": "GuestWeave bus client", "sublabel": "Hello · exec · clipboard", "pos": [740, 460], "size": [240, 92]}, + {"id": "bundled-agent", "type": "backend", "label": "Bundled guest agent", "sublabel": "Repository-local Go modules", "pos": [1090, 460], "size": [240, 92]} + ], + "boundaries": [ + {"kind": "region", "label": "Windows host: GuestWeave implementation", "wraps": ["commands", "hcs-engine", "host-state", "module-client"], "pad": 26}, + {"kind": "region", "label": "Guest operating system", "wraps": ["guest-vm", "bundled-agent"], "pad": 26} ], "connections": [ - { - "to": "commands", - "label": "Invoke", - "from": "client" - }, - { - "to": "hcs", - "label": "Host calls", - "from": "commands" - }, - { - "to": "guest", - "label": "Create and start", - "from": "hcs" - }, - { - "to": "network", - "label": "Network setup", - "from": "hcs", - "labelDy": 24 - } + {"id": "invoke-command", "from": "entrypoints", "to": "commands", "label": "Invoke"}, + {"id": "manage-compute", "from": "commands", "to": "hcs-engine", "label": "Lifecycle"}, + {"id": "boot-guest", "from": "hcs-engine", "to": "guest-vm", "label": "Boot / stop"}, + {"id": "persist-state", "from": "commands", "to": "host-state", "label": "Read / write", "labelDy": 24}, + {"id": "registry-transfer", "from": "commands", "to": "oci-registry", "label": "OCI push / pull"}, + {"id": "guest-exchange", "from": "hcs-engine", "to": "module-client", "label": "Native exec", "labelDy": 24}, + {"id": "module-transport", "from": "module-client", "to": "bundled-agent", "label": "HvSocket"} ], "cards": [ - { - "dot": "cyan", - "title": "Implementation", - "items": [ - "Windows host execution uses HCS and HCN." - ] - }, - { - "dot": "amber", - "title": "Image contract", - "items": [ - "The current Windows OCI path uses GuestWeave VHDX v2." - ] - } + {"dot": "cyan", "title": "Bundled agents", "items": ["Linux: weave-agent serves exec and clipboard.", "Windows: weave-guestd serves clipboard."]}, + {"dot": "emerald", "title": "Guest protocol", "items": ["Port 2010 carries length-prefixed JSON envelopes.", "This bus is separate from agent-core gRPC modules."]}, + {"dot": "amber", "title": "Other tool path", "items": ["Agent install/call/tools invoke a supplied binary over SSH.", "That path is separate from the resident module bus."]} ] } diff --git a/diagrams/sources/hostweave.json b/diagrams/sources/hostweave.json index 71a853f..a0c5e40 100644 --- a/diagrams/sources/hostweave.json +++ b/diagrams/sources/hostweave.json @@ -2,54 +2,54 @@ "schema_version": 1, "diagram_type": "architecture", "meta": { - "title": "HostWeave: placement to VM execution", + "title": "HostWeave: control plane, host agent and execution backends", "locale": "en", "quality_profile": "showcase", "viewBox": [ - 1240, - 720 + 1320, + 800 ] }, "components": [ { "id": "caller", "type": "external", - "label": "Operator", - "sublabel": "hwctl, web UI or API", + "label": "Operator / client", + "sublabel": "hwctl, web UI, HTTP API", "pos": [ 40, - 150 + 155 ], "size": [ - 220, + 230, 84 ] }, { "id": "server", "type": "backend", - "label": "Control plane", - "sublabel": "API, placement and leases", + "label": "HostWeave control plane", + "sublabel": "API, broker, providers, gateway", "pos": [ - 350, - 150 + 370, + 155 ], "size": [ - 220, + 230, 84 ] }, { "id": "agent", "type": "backend", - "label": "Host agent", - "sublabel": "Work execution and health", + "label": "hostweave-agent", + "sublabel": "Fingerprint + supervisor", "pos": [ - 660, - 150 + 700, + 155 ], "size": [ - 220, + 230, 84 ] }, @@ -57,67 +57,126 @@ "id": "cli", "type": "backend", "label": "GuestWeave CLI", - "sublabel": "Pinned runtime binary", + "sublabel": "macOS / Windows VM driver", "pos": [ - 970, - 150 + 1030, + 155 ], "size": [ - 220, + 230, 84 ] }, { "id": "state", "type": "database", - "label": "Control-plane state", - "sublabel": "Configured store and blobs", + "label": "SQL store + lease queue", + "sublabel": "SQLite, PostgreSQL or MySQL", "pos": [ - 350, - 410 + 370, + 475 ], "size": [ - 220, + 230, + 84 + ] + }, + { + "id": "providers", + "type": "cloud", + "label": "Capacity providers", + "sublabel": "Devices, AWS, Azure, GCP", + "pos": [ + 40, + 475 + ], + "size": [ + 230, + 84 + ] + }, + { + "id": "backends", + "type": "backend", + "label": "Other runtime drivers", + "sublabel": "Moby, QEMU, opt-in exec", + "pos": [ + 700, + 475 + ], + "size": [ + 230, + 84 + ] + }, + { + "id": "vm", + "type": "external", + "label": "Local guest VM", + "sublabel": "Platform-specific execution", + "pos": [ + 1030, + 475 + ], + "size": [ + 230, 84 ] } ], "connections": [ { + "from": "caller", "to": "server", - "label": "HTTPS", - "from": "caller" + "label": "HTTP API" }, { + "from": "server", "to": "agent", - "label": "Work leases", - "from": "server" + "label": "Assignments" }, { + "from": "agent", "to": "cli", - "label": "Subprocess calls", - "from": "agent" + "label": "Subprocess argv" }, { + "from": "server", "to": "state", - "label": "Read and write", + "label": "State + queue", + "labelDy": 24 + }, + { "from": "server", + "to": "providers", + "label": "Provider actions" + }, + { + "from": "agent", + "to": "backends", + "label": "Runtime lifecycle", + "labelDy": 24 + }, + { + "from": "cli", + "to": "vm", + "label": "Lifecycle + exec", "labelDy": 24 } ], "cards": [ { "dot": "cyan", - "title": "Outbound connection", + "title": "Connection ownership", "items": [ - "The host agent initiates its connection to the control plane." + "The host agent dials out; assignments and status share its bidirectional session." ] }, { "dot": "amber", - "title": "Boundary", + "title": "Execution boundary", "items": [ - "The guest runtime adapter invokes the CLI; it does not import it." + "The GuestWeave driver shells out to weave. Other drivers do not use GuestWeave." ] } ] diff --git a/diagrams/sources/imageweave-construction.json b/diagrams/sources/imageweave-construction.json index b5b7eb6..67c49d7 100644 --- a/diagrams/sources/imageweave-construction.json +++ b/diagrams/sources/imageweave-construction.json @@ -2,23 +2,22 @@ "schema_version": 2, "diagram_type": "workflow", "meta": { - "title": "Imageweave: construction is not runtime qualification", + "title": "Imageweave construction and runtime acceptance", "locale": "en", "quality_profile": "showcase" }, "lanes": [ { "id": "construction", - "label": "Construction" + "label": "Construction + packaging" }, { - "id": "verification", - "label": "Artifact verification" + "id": "qualification", + "label": "Runtime evidence" } ], "mainPath": [ "inputs", - "plan", "packer", "import", "inspect" @@ -29,81 +28,87 @@ "lane": "construction", "col": 0, "type": "external", - "label": "Pinned inputs", - "sublabel": "Media + recipe" + "label": "Pinned scenario", + "sublabel": "Media or parent image", + "width": 165 }, { - "id": "plan", + "id": "packer", "lane": "construction", "col": 1, "type": "backend", - "label": "Resolve plan", - "sublabel": "Scenario and template" + "label": "Packer / builder", + "sublabel": "Native APIs or QEMU", + "width": 165 }, { - "id": "packer", + "id": "import", "lane": "construction", "col": 2, "type": "backend", - "label": "Packer build", - "sublabel": "Install or restore" + "label": "OCI packaging", + "sublabel": "Declared disk + state", + "width": 165 }, { - "id": "import", - "lane": "verification", + "id": "inspect", + "lane": "construction", "col": 3, - "type": "backend", - "label": "OCI handoff", - "sublabel": "Manifest-listed outputs" + "type": "security", + "label": "Checked OCI layout", + "sublabel": "Content conformance", + "width": 165 }, { - "id": "inspect", - "lane": "verification", - "col": 4, + "id": "accept", + "lane": "qualification", + "col": 3, "type": "security", - "label": "Inspect", - "sublabel": "Deep + strict" + "label": "Runtime acceptance", + "sublabel": "Fresh clones + reboot", + "width": 165, + "yOffset": 30 } ], "edges": [ { "from": "inputs", - "to": "plan", - "label": "Resolve", - "role": "main" - }, - { - "from": "plan", "to": "packer", - "label": "Build", + "label": "Resolve + build", "role": "main" }, { "from": "packer", "to": "import", - "label": "Manifest", + "label": "Manifest / bundle", "role": "main" }, { "from": "import", "to": "inspect", - "label": "Inspect", + "label": "Deep check", "role": "main" + }, + { + "from": "inspect", + "to": "accept", + "label": "Selected validator", + "role": "branch" } ], "cards": [ { "dot": "amber", - "title": "Qualification boundary", + "title": "Acceptance differs by path", "items": [ - "A constructed and inspected artifact still needs runtime evidence." + "Native delivery returns unverified. Linux delivery also runs its runtime validator." ] }, { "dot": "cyan", - "title": "Publication boundary", + "title": "Other paths", "items": [ - "Native construction does not publish or promote an image." + "Target export and candidate publication are separate commands and workflows described in the HLD." ] } ] diff --git a/diagrams/sources/module-startup.json b/diagrams/sources/module-startup.json index c70855a..079c6cb 100644 --- a/diagrams/sources/module-startup.json +++ b/diagrams/sources/module-startup.json @@ -2,7 +2,7 @@ "schema_version": 1, "diagram_type": "sequence", "meta": { - "title": "Capability module startup", + "title": "Module startup and local IPC", "locale": "en", "quality_profile": "showcase", "column_fit": "spread", @@ -29,73 +29,73 @@ } ], "messages": [ + { + "from": "core", + "to": "services", + "y": 170, + "label": "After verify: create listener", + "variant": "security" + }, { "from": "core", "to": "module", - "y": 180, - "label": "Launch verified binary", + "y": 220, + "label": "Launch verified executable", "variant": "security" }, { "from": "module", "to": "core", - "y": 230, - "label": "Handshake: protocol and address", + "y": 270, + "label": "Handshake: protocol and endpoint", "variant": "return" }, { "from": "core", "to": "module", - "y": 280, - "label": "Init with host services and config" + "y": 320, + "label": "Init: identity, config and capabilities" }, { "from": "module", "to": "services", - "y": 330, - "label": "Authenticated host RPC", + "y": 370, + "label": "Connect with launch token", "variant": "security" }, { "from": "module", "to": "core", - "y": 380, - "label": "Init completes", + "y": 420, + "label": "Init result: requirements and surfaces", "variant": "return" }, { "from": "core", "to": "module", - "y": 430, - "label": "Start" + "y": 470, + "label": "Check requirements, then Start" }, { "from": "core", "to": "module", - "y": 480, - "label": "Health" - }, - { - "from": "module", - "to": "core", - "y": 530, - "label": "Module health", - "variant": "return" + "y": 520, + "label": "Poll rich Health; monitor watchdog" } ], "cards": [ { "dot": "rose", - "title": "Before lifecycle", + "title": "Launch boundary", "items": [ - "Unsupported protocol windows are rejected before module Init." + "The host-services listener exists before the child. The SDK connects during Init." ] }, { "dot": "violet", - "title": "SDK ownership", + "title": "Readiness", "items": [ - "The SDK serves lifecycle RPCs and brokers the Host interface." + "Start and rich health are separate from the standard gRPC server SERVING status." ] } ] diff --git a/diagrams/sources/platform-context.json b/diagrams/sources/platform-context.json index b5768be..63ee7f6 100644 --- a/diagrams/sources/platform-context.json +++ b/diagrams/sources/platform-context.json @@ -1,26 +1,197 @@ { "schema_version": 1, "diagram_type": "architecture", - "meta": {"title": "Weave platform: execution and image construction", "locale": "en", "quality_profile": "showcase", "viewBox": [1240, 720]}, + "meta": { + "title": "GuestWeave composition and platform relationships", + "locale": "en", + "quality_profile": "showcase", + "viewBox": [ + 1240, + 780 + ] + }, "components": [ - {"id": "operator", "type": "external", "label": "Operator", "sublabel": "API, UI or hwctl", "pos": [40, 140], "size": [190, 84]}, - {"id": "orchestrator", "type": "backend", "label": "HostWeave", "sublabel": "Placement and work leases", "pos": [360, 140], "size": [230, 84]}, - {"id": "hostagent", "type": "backend", "label": "Host agent", "sublabel": "Runtime adapter", "pos": [690, 140], "size": [220, 84]}, - {"id": "vmmanager", "type": "backend", "label": "GuestWeave", "sublabel": "Host VM lifecycle", "pos": [1020, 140], "size": [190, 84]}, - {"id": "guestagent", "type": "backend", "label": "Guest agent core", "sublabel": "Supervised capabilities", "pos": [1020, 400], "size": [190, 84]}, - {"id": "imagebuilder", "type": "backend", "label": "Imageweave", "sublabel": "Packer construction", "pos": [40, 400], "size": [190, 84]}, - {"id": "artifacttools", "type": "backend", "label": "Weave OCI", "sublabel": "Artifact handoff and checks", "pos": [360, 400], "size": [230, 84]} + { + "id": "hostadapter", + "type": "backend", + "label": "HostWeave host agent", + "sublabel": "Optional CLI driver", + "pos": [ + 40, + 130 + ], + "size": [ + 210, + 90 + ] + }, + { + "id": "macmanager", + "type": "backend", + "label": "GuestWeave on macOS", + "sublabel": "Host VM session + SDK client", + "pos": [ + 350, + 130 + ], + "size": [ + 240, + 90 + ] + }, + { + "id": "guestcore", + "type": "backend", + "label": "Agent core", + "sublabel": "Guest process and router", + "pos": [ + 690, + 130 + ], + "size": [ + 220, + 90 + ] + }, + { + "id": "capabilities", + "type": "backend", + "label": "OS modules", + "sublabel": "Guest capability processes", + "pos": [ + 1010, + 130 + ], + "size": [ + 200, + 90 + ] + }, + { + "id": "winmanager", + "type": "backend", + "label": "GuestWeave on Windows", + "sublabel": "Host HCS + local bus client", + "pos": [ + 350, + 400 + ], + "size": [ + 240, + 90 + ] + }, + { + "id": "winresident", + "type": "backend", + "label": "Resident guest agent", + "sublabel": "Repository-local module bus", + "pos": [ + 690, + 400 + ], + "size": [ + 260, + 90 + ] + }, + { + "id": "imagebuilder", + "type": "backend", + "label": "Imageweave", + "sublabel": "Native / derived image paths", + "pos": [ + 40, + 650 + ], + "size": [ + 210, + 90 + ] + }, + { + "id": "artifacttools", + "type": "database", + "label": "OCI layouts and tooling", + "sublabel": "Build, inspect and target export", + "pos": [ + 350, + 650 + ], + "size": [ + 260, + 90 + ] + } + ], + "boundaries": [ + { + "kind": "region", + "label": "Guest managed from macOS", + "wraps": [ + "guestcore", + "capabilities" + ] + }, + { + "kind": "region", + "label": "Guest managed from Windows", + "wraps": [ + "winresident" + ] + } ], "connections": [ - {"from": "operator", "to": "orchestrator", "label": "HTTPS API", "variant": "emphasis"}, - {"from": "orchestrator", "to": "hostagent", "label": "Work leases", "variant": "emphasis"}, - {"from": "hostagent", "to": "vmmanager", "label": "CLI adapter", "variant": "emphasis"}, - {"from": "vmmanager", "to": "guestagent", "label": "Guest channel", "labelDy": 24}, - {"from": "imagebuilder", "to": "artifacttools", "label": "Build manifest"} + { + "from": "hostadapter", + "to": "macmanager", + "label": "CLI", + "variant": "dashed" + }, + { + "from": "hostadapter", + "to": "winmanager", + "label": "CLI", + "variant": "dashed" + }, + { + "from": "macmanager", + "to": "guestcore", + "label": "Frames", + "variant": "emphasis" + }, + { + "from": "guestcore", + "to": "capabilities", + "label": "IPC", + "variant": "emphasis" + }, + { + "from": "winmanager", + "to": "winresident", + "label": "Local wire", + "variant": "emphasis" + }, + { + "from": "imagebuilder", + "to": "artifacttools", + "label": "OCI tools" + } ], "cards": [ - {"dot": "cyan", "title": "Execution", "items": ["HostWeave places work; GuestWeave owns VM lifecycle."]}, - {"dot": "amber", "title": "Compatibility", "items": ["Image construction does not prove runtime compatibility."]}, - {"dot": "violet", "title": "Agent roles", "items": ["The host agent and guest agent core are separate processes."]} + { + "dot": "cyan", + "title": "Composition", + "items": [ + "The SDK client runs on the macOS host; core and capability modules run in its guest." + ] + }, + { + "dot": "amber", + "title": "Different contracts", + "items": [ + "Windows uses its own guest agent. Exported disks still need consumer admission and runtime acceptance." + ] + } ] } diff --git a/docs/development/documentation.md b/docs/development/documentation.md index 3b0ddbf..b990302 100644 --- a/docs/development/documentation.md +++ b/docs/development/documentation.md @@ -44,16 +44,35 @@ Pages declare title, description, product, audience, owner, status, verification Use `code-inspected` for static implementation review. Use `runtime-verified` only when a reproducible acceptance record exists. A changed upstream file does not automatically reverify an existing page. +Inspect complete source trees at the locked commits, then follow entrypoints through dispatch, adapters and handlers. Check both ends of a wire and both producer and consumer of an artifact. Package manifests and comments are clues; they do not override executed code. Separately record dependency versions, installer-selected releases and embedded payload provenance. A current source checkout does not certify an older released binary. + +The validator proves that evidence references resolve to registered paths and that contract snapshots match their checksums. It does not prove that a sentence accurately describes those files. That requires code review. + +## HLD content requirements + +An HLD should let a reader follow a real operation and identify the cause of failure. Include: + +- Process ownership and placement: physical host, guest, control plane, linked library or separate executable. +- Actual product dependencies and optional adapters, including differences between platform implementations. +- A request traced from its entrypoint through transport, authentication, dispatch and reply. +- Startup, readiness, shutdown, update/recovery and durable state, with their distinct success conditions. +- Storage/artifact contracts, configuration and credential boundaries, plus implemented failure behaviour. +- Source anchors for mechanisms and a clear statement of acceptance/provenance gaps. + +Do not infer shared semantics from an `agent`, `module` or `host` name. Do not infer HTTP/CLI parity from a shared command structure. Describe what the selected code and inputs permit, including incomplete checked-in inputs. + ## Diagrams Use the pinned Archify bundle. Author the appropriate architecture, workflow or sequence JSON with stable IDs, a clear main path and `meta.quality_profile: showcase`. Diagram evidence references identify the actual code behind the relationships. The build requires nine showcase checks, zero composition errors and zero warnings. Visual checks bind browser screenshots and measurements to the delivered HTML. Inspect the screenshots in both themes before approval; machine containment checks do not establish visual polish. +Every relationship needs a code-backed mechanism and direction. Boundaries describe actual process/deployment placement. A diagram is a selected view of the design; the accompanying HLD must explain the omitted paths and the meaning of each arrow. Separate artifact construction, consumer admission, runtime acceptance and publication/promotion rather than implying one guaranteed pipeline. + Generation receipts and browser screenshots remain build artifacts. The website includes only delivered viewers and approved content. ## Pull request review Explain the user-visible knowledge change, source baseline, cross-checks and limitations. Reviewers confirm the evidence, inspect diagrams, and approve any public export from a private implementation. Keep proposed behaviour separate from implemented behaviour. -Upstream refresh produces a review report. It does not rewrite prose, change verification dates or silently move the baseline. +Upstream refresh resolves each source's declared branch or tag and produces a review report. It does not rewrite prose, change verification dates or silently move the baseline. diff --git a/docs/platform/architecture.mdx b/docs/platform/architecture.mdx index 7d1befb..3f84b58 100644 --- a/docs/platform/architecture.mdx +++ b/docs/platform/architecture.mdx @@ -1,6 +1,7 @@ --- title: Platform architecture -description: The implemented responsibilities and connections between Weave products. +description: Product composition, deployment boundaries and the implemented + connections between Weave components. product: platform audience: - developer @@ -12,36 +13,78 @@ evidence: - hostweave:agent/runtime/guestweave/guestweave.go - imageweave:pkg/delivery/native.go - agent-core:internal/core/core.go +- guestweave-macos:internal/vm/run/guestchannel.go +- guestweave-macos:internal/vm/guestagent/packages.go +- guestweave-windows:cmd/weaveagent/modulebus.go +- guestweave-windows:internal/guestweaveagent/proto/proto.go +- agent-modules:sdk/weaveclient/weaveclient.go +- imageweave:pkg/nativebuild/build.go +- imageweave:internal/imagebuild/export.go +- imageweave:.github/workflows/macos-candidate.yml +- channels:scripts/promote.sh +- channels:channels/stable.json +- agent-core:internal/lifecycle/lifecycle.go +- guestweave-windows:internal/cli/root.go +- hostweave:agent/runtime/guestweave/capabilities.go +- guestweave-windows-development:go.mod +- guestweave-windows-development:internal/command/run_channel.go --- -The platform separates **placement**, **VM lifecycle**, **image construction** and **guest capabilities**. Those responsibilities connect through explicit interfaces; a common product family does not make every artifact or protocol interchangeable. +The platform has both product relationships and deployment relationships. GuestWeave owns local VM management **and guest integration**. In the macOS implementation, separately maintained agent-core and agent-modules supply the guest runtime and capabilities, and the agent-modules SDK supplies the host-side client. Windows GuestWeave's main baseline has its own resident agent; its separately inspected development branch migrates to the shared SDK/core/modules. HostWeave defines macOS and Windows CLI adapters, subject to companion compatibility. Imageweave builds/prepares artifacts through native platform libraries or Linux QEMU/Packer paths and OCI tooling. -HostWeave assigns work to a host agent. Its GuestWeave adapter invokes a local CLI. Imageweave hands construction output to OCI tooling through a build manifest. Guest capabilities live behind agent core and its modules. +This view describes the locked main baselines. The macOS host manager reaches guest-resident core and OS capability modules through its SDK client; Windows main reaches its own resident guest bus. The Windows development composition is shown separately in the migration HLD. HostWeave's CLI adapter is optional orchestration; Imageweave and OCI form an artifact path with separate admission and acceptance boundaries. ## Responsibilities | Area | Owns | Important boundary | | --- | --- | --- | -| GuestWeave | Local VM lifecycle and host-specific execution | Separate macOS and Windows implementations | -| HostWeave | Control-plane APIs, placement and work execution coordination | Host agents execute through runtime adapters | -| Imageweave | Reviewed image scenarios, construction and qualification paths | Construction alone does not grant runtime acceptance | -| Agent core | Authenticated host services, module verification and supervision | Capability modules execute as separate processes | -| Agent modules | The module SDK and OS-specific capabilities | SDK and module releases have their own versions | +| GuestWeave | Host VM lifecycle, guest channels/payload setup, local state/images and integration features | macOS and Windows have different guest architectures and contracts | +| HostWeave | Control plane, scheduling/leases, host capacity/providers and workload execution | Its host agent chooses configured runtime adapters; GuestWeave is one VM path | +| Imageweave | Image construction, preparation, agent payloads, OCI layouts, export and selected candidate workflows | Artifact build/export/publication and runtime acceptance are separate operations | +| Agent core | Guest/device-side module process owner, local services, trust and capability routing | Used by macOS GuestWeave and the Windows migration branch; distinct from HostWeave's host agent | +| Agent modules | Host client SDK, guest capability services, OS module binaries and packaging | SDK library and installed module processes have different locations/lifecycles | -## Execution and agent roles +## Relationships proved by implementation -The HostWeave host agent is the process responsible for executing work assigned by the orchestration control plane. Its GuestWeave VM adapter invokes a `weave` binary and interprets the result. The agent core running in a guest is a different process: it supervises capability modules and brokers host/guest communication. +| Relationship | Mechanism | What it establishes | +| --- | --- | --- | +| HostWeave → GuestWeave | Host-agent adapter invokes local CLI operations; capability checks depend on platform and image path | Optional orchestration; the pinned Windows CLI lacks the required companion capability command | +| macOS GuestWeave → agent-modules SDK | Host run process imports weaveclient and owns one guest stream | Typed host capability calls, authentication, registry/events | +| macOS GuestWeave → agent-core/module releases | Guest package installers fetch selected assets; provisioning media supplies trust | Guest-side runtime payloads; Linux's key seed alone does not install them | +| Agent core → modules | Verify staged executable, create host-services listener, launch child, negotiate and call lifecycle RPCs | Guest process ownership and runtime admission | +| Windows GuestWeave → local guest agent | HCS boot payload plus own client/module registry and framed JSON wire | A separate resident guest implementation, with different capability coverage | +| Windows development → SDK/core/modules | Linked host SDK, HCS guest channel and provisioned shared release payloads | Migration implemented on the pinned branch; see the [development HLD](../products/guestweave/windows-development.mdx) for remaining parity gaps | +| Imageweave → native platform libraries | Native builders and preparation code call host-specific libraries | Construction does not depend on invoking GuestWeave CLI | +| Imageweave → GuestWeave target disks | Export verified OCI layout sectors to raw/VHDX outputs | Conversion, not VM registration, boot success or registry-format equivalence | +| Imageweave → core/module releases | Prepare pinned guest payloads for separate derived agent images | macOS base/prepared acceptance requires agent absence | +| OCI / release channels | Contracts, libraries/CLI and reviewed promotion data | Artifact shape and release selection, with consumer/version-specific checks | + +## Where the processes run + +For macOS GuestWeave, the physical host runs the CLI/VM session and its linked SDK client. The guest runs an OS service manager, weaveboot, core and separately launched OS capability modules. Core's “host services” are services offered to a module **inside that guest**. They are not a second physical-host daemon. + +For Windows GuestWeave's main baseline, the Windows host owns HCS state and the detached VM supervisor; the guest runs the repository's resident Linux agent or Windows guest daemon. Its module registry is local to that agent. A similarly named executable does not establish use of agent-core or the shared module SDK. + +The Windows migration branch replaces those legacy guest components. Its host supervisor owns the SDK connection over HvSocket and guest core owns the capability module processes. That convergence changes guest integration while HCS remains the Windows host VM engine. + +HostWeave's host agent is a third role, with its own outbound control-plane protocol and runtime adapters. It executes assigned work on the physical host. The GuestWeave adapter delegates VM operations to a selected CLI; it does not supervise the guest's core processes. + +## Following a request + +A locally issued macOS CLI exec request reaches the running VM session through its control socket, then the SDK client, authenticated guest channel, core router and guest OS exec module. The HTTP/MCP exec routes take an SSH path instead. On Windows main, the native Linux exec path reaches the resident agent through HvSocket/AF_VSOCK and the repository-local module bus. The Windows migration branch routes CLI, REST and MCP exec through its supervisor and authenticated SDK/core channel. These paths have different readiness, credentials and failure conditions. + +For orchestrated work, HostWeave first assigns/leases work to its own host agent. The selected adapter prepares and starts the runtime, then executes and reports the result. Its GuestWeave-specific CLI/capability checks are an additional integration contract, separate from the guest wire protocol. See [HostWeave](../products/hostweave/hld.mdx) for scheduling, persistence and runtime selection. -Document these roles separately. A box labelled only “agent” hides two different lifecycles and trust boundaries. +## Image and promotion boundaries -## Image and release dependencies +Imageweave has multiple paths: native construction, prepared/agent image work, OCI inspection/export and a guarded macOS candidate publication workflow. The candidate workflow can publish a base artifact with attestations; it does not thereby promote it into a release channel. Native construction alone carries no runtime acceptance result. -The shared OCI layer owns the Weave artifact format and artifact tooling. Release-channel data identifies promoted module and image versions. They appear in this first release as dependencies of the selected products rather than separate product documentation areas. +Release-channel promotion records selected module/image entries through a reviewed change. Signing is conditional; the pinned stable document has no checked-in signature or core artifacts. Its module entries use OCI references, while stock core's channel installer requires a signed HTTP bundle and downloads artifacts through HTTP. Promotion data is therefore not a directly usable core-update feed at these baselines. -A build manifest, registry artifact, qualification result and promoted release are distinct objects. There is no verified universal build → publish → promote → run path in this baseline. The [compatibility register](compatibility.md) explains the current image-format gap. +A built layout, exported disk, published candidate, accepted runtime and promoted release are distinct objects. Windows's VHDX v2 contract, HostWeave's parser and the shared raw-chunk OCI family cannot be drawn as one guaranteed interoperable pipeline. See [compatibility](compatibility.md). -## Design baseline +## Evidence scope -This HLD follows the pinned implementation baseline dated 11 October 2026. [Imageweave’s native construction path](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/pkg/delivery/native.go) and [agent-core assembly](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/core/core.go) provide public evidence. The HostWeave adapter evidence is retained in the internal knowledge base. +The platform context diagram describes source at the locked main commits, checked against complete trees during PR11 review. The Windows migration HLD and diagram use their separately locked development commit and SDK release. Installer release pins and checked-in embedded binaries are separate baselines. No real-machine acceptance is claimed. Public source anchors include [SDK host client](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/weaveclient/weaveclient.go), [core assembly](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/core/core.go), [Imageweave native builder](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/pkg/nativebuild/build.go) and [target export](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/internal/imagebuild/export.go). Private implementation paths/checksums are recorded without exporting their raw contracts. diff --git a/docs/platform/compatibility.md b/docs/platform/compatibility.md index 23a3487..a0b0177 100644 --- a/docs/platform/compatibility.md +++ b/docs/platform/compatibility.md @@ -1,7 +1,7 @@ --- title: Compatibility and known gaps -description: Track verified integration boundaries without treating intended architecture - as runtime support. +description: Track verified integration boundaries without treating intended + architecture as runtime support. product: platform audience: - developer @@ -15,17 +15,43 @@ evidence: - guestweave-windows:internal/oci/oci.go - imageweave:pkg/delivery/native.go - agent-modules:sdk/buf.gen.yaml +- agent-modules:.github/agent-core-version +- guestweave-macos:go.mod +- guestweave-macos:internal/vm/guestagent/packages.go +- guestweave-macos:internal/vm/guestagent/windows.go +- guestweave-macos:internal/imagesource/oci/registry.go +- guestweave-windows:internal/guestweaveagent/proto/proto.go +- guestweave-windows:internal/nativebuild/assets/weave-agent +- imageweave:internal/imagebuild/catalogue.go +- imageweave:images/packages.lock.json +- imageweave:internal/imagebuild/export.go +- imageweave:.github/workflows/macos-candidate.yml +- channels:.github/agent-core-version +- guestweave-windows:internal/cli/root.go +- hostweave:agent/runtime/guestweave/capabilities.go +- hostweave:agent/runtime/guestweave/guestweave.go +- channels:scripts/promote.sh +- channels:.github/workflows/promote.yml +- channels:channels/stable.json +- agent-core:internal/lifecycle/lifecycle.go +- guestweave-windows-development:go.mod +- guestweave-windows-development:internal/cli/root.go --- Compatibility claims apply to an exact source or release baseline, host platform and artifact contract. The table below records inspected code boundaries; its status does not imply a successful real-machine acceptance run. | Integration | Inspected behaviour | Status | | --- | --- | --- | -| HostWeave → GuestWeave | VM adapter shells out to the GuestWeave CLI on macOS or Windows | Implemented adapter; runtime combinations require acceptance | -| Imageweave → Weave OCI | Native construction invokes manifest import, packing and deep inspection | Implemented construction handoff | -| Agent core → modules | Version-window negotiation and lifecycle RPC | Implemented protocol boundary | +| HostWeave → GuestWeave | VM adapter invokes the selected CLI; companion checks vary by platform/image path | Pinned Windows CLI lacks required `capabilities`; driver is unhealthy. Other combinations need contract checks and acceptance | +| macOS GuestWeave → SDK/core/modules | Host links the SDK; guest installers select independently pinned core and module assets | Source composition established; installed release binaries require separate verification | +| Windows GuestWeave → resident agent | Repository-local client, guest agent and framed module bus | Separate implementation; no dependency on shared agent-core/modules found | +| Windows development → SDK/core/modules | Migration branch replaces the local guest agents and routes exec through the shared SDK | Implemented on the pinned development branch; not current main or a guarantee of full parity | +| Imageweave → Weave OCI | Construction invokes manifest import, packing and deep inspection | Implemented handoff; not runtime qualification | +| Imageweave → GuestWeave disks | Target export writes raw or VHDX disks from a verified layout | Conversion implemented; registration, native boot inputs and acceptance remain consumer-specific | +| Agent core → modules | Version-window negotiation and local gRPC lifecycle; separate external capability wire | Implemented boundaries; lifecycle Protocol `1` alone does not establish host-wire compatibility | | Shared OCI images → HostWeave | HostWeave’s parser lists different formats from the shared Weave contract | Compatibility gap; do not claim universal admission | | Shared OCI images → Windows GuestWeave | Windows OCI code implements GuestWeave VHDX v2 media types | Compatibility gap with shared raw-chunk contract | +| Release channels → core module downloads | Promotion entries contain OCI references; stock core downloads artifact URLs through HTTP | Unresolved delivery contract; promotion data is not a directly usable update feed | | Linux GuestWeave host implementation | No corresponding current repository in the inspected organisation inventory | Outside this documentation baseline | ## Image formats @@ -34,12 +60,37 @@ The shared OCI contract defines `application/vnd.weave.guest.config.v1+json` and These are different formats. Successful registry transport does not prove that a consumer recognises the artifact, can construct a VM from it or can boot it. Before adding an end-to-end guide, exercise the selected producer and consumer versions together and record the artifact digest and runtime evidence. +## Companion CLI and channel delivery + +The pinned Windows CLI has no `capabilities` command. HostWeave invokes that command and marks its Windows driver unhealthy on failure, so these exact baselines have an implementation incompatibility before VM acceptance. macOS fingerprinting tolerates a failed capability query for legacy paths, but resolved-image format/platform admission, resolved OCI pulls and Windows execution enforce additional companion contracts. + +The inspected Windows migration branch also lacks that companion command, despite its SDK/core/module convergence. Its new guest architecture does not by itself repair HostWeave admission. See the [development-branch HLD](../products/guestweave/windows-development.mdx). + +Release-channel promotion signs only when a signing key is configured. The checked-in stable document has no accompanying signature and its core version/artifact list is empty. Core's channel fetcher instead requires `manifest.json`, `manifest.json.sig`, `signing.pub` and `signing.pub.sig`, verifies them against its root, then downloads selected artifact URLs through its HTTP client. Current promoted module URLs are bare GHCR OCI references with platform fragments, not HTTP download URLs. A serving/resolution and signed-bundle contract is needed before those entries can drive stock core installation. The promotion workflow's success alone proves neither signing nor installability. + ## Construction and qualification -Imageweave’s native `Construction` result deliberately carries no runtime acceptance field. Its plan can be unverified. Inspection establishes artifact structure; qualification must independently establish the required runtime behaviour. +Imageweave's native `Construction` result carries no runtime acceptance field. Linux delivery and selected macOS/agent validators have separate runtime acceptance paths. The macOS candidate workflow can publish accepted base artifacts with attestations; that is separate from channel promotion. This documentation review did not execute those paths. + +The default `images/packages.lock.json` lacks the signed checksum evidence required for every module, and Linux module entries lack installers. `RequirePackages` rejects this lock before default `image prepare` and agent construction can download or install packages. A reviewed, complete replacement lock is required. This is a finding about checked-in inputs, not evidence that upstream release assets are missing. See the [Imageweave HLD](../products/imageweave/hld.mdx). + +Windows GuestWeave's native Linux boot directory supplies kernel, initrd and manifest separately from its VHDX. The OCI push path does not package that directory. A transferred disk alone therefore does not establish a portable native guest. + +macOS GuestWeave enforces sealed template trust rules in local clone paths, but its inspected OCI push copies the raw disk without performing that sealing/trust check or scrubbing guest trust. A new host-side key after pull is not proof that the published guest disk is free of prior trust or state. Review the [macOS image/trust paths](../products/guestweave/macos.mdx) before defining a publication procedure. + +## Independent version baselines + +| Input | Inspected pin | Meaning | +| --- | --- | --- | +| macOS GuestWeave host SDK | `v0.2.7` | Linked host client; separately locked from SDK main | +| Windows development host SDK | `v0.2.7` | Same selected SDK release; guest core/modules resolve stable releases dynamically | +| macOS guest core installer | `0.9.8` | Selected macOS payload | +| Windows-on-mac core installer | `0.9.11` | Selected Windows payload in the macOS implementation | +| Module SDK protocol generation | core `v0.9.2` | Source for its generated lifecycle/host-service protobuf copy | +| Release-channel verification tooling | core `v0.9.9` | Independent verifier pin | -## Protocol versions +Module releases have additional OS-specific pins, recorded in the [macOS HLD](../products/guestweave/macos.mdx). These are installer/dependency declarations, not proof that downloaded binaries behave like the independently locked current source. Verify lifecycle windows, external framing, capability vocabulary and actual installed assets together. -The module SDK pins a core release for protocol generation. The release-channel verifier has its own core pin. Do not replace these independent pins with a platform-wide “latest” version. +Windows GuestWeave's checked-in native Linux agent reports build revision `3c7d4dfe09303bceea9d30801af5730d164929dd` and `vcs.modified=true`, while its inspected source is `48fe4242d4d8b884e1bc464287f9a6c8f72ca275`. Current source cannot certify that embedded payload. The [Windows HLD](../products/guestweave/windows.mdx) records its build metadata and the runtime checks still needed. Evidence: [shared OCI media types](https://github.com/weaveplatform/weaveplatform-oci/blob/2f088490822cf14a5e9b6824bffe07e932753715/pkg/spec/mediatypes.go), [native construction boundary](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/pkg/delivery/native.go), [SDK protocol generation](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/buf.gen.yaml). Private parser and adapter evidence is retained internally. diff --git a/docs/platform/glossary.md b/docs/platform/glossary.md index f7e2c80..d267a19 100644 --- a/docs/platform/glossary.md +++ b/docs/platform/glossary.md @@ -11,21 +11,32 @@ verified_at: '2026-10-11' evidence: - agent-modules:sdk/modulesdk/module.go - imageweave:pkg/delivery/native.go +- guestweave-macos:internal/vm/run/guestchannel.go +- guestweave-windows:cmd/weaveagent/modulebus.go +- hostweave:agent/agent.go +- agent-modules:sdk/weaveclient/weaveclient.go +- agent-core:internal/hostserv/hostserv.go +- channels:scripts/promote.sh +- guestweave-windows-development:go.mod --- | Term | Meaning | | --- | --- | | Host | A machine that supplies a runtime and capacity for work | | Guest | A VM running on a host | -| GuestWeave | The host-specific VM manager | -| Host agent | HostWeave’s work-execution process; separate from guest agent core | -| Agent core | The runtime that verifies and supervises capability modules | -| Module | A separately launched capability process using the module protocol | -| Host services | The identity, policy, storage, event and other services core offers to a module | +| GuestWeave | Host-specific VM management plus guest payloads, channels and integration features; macOS and Windows compose these differently | +| HostWeave host agent | Physical-host workload executor with its own enrollment/session protocol and runtime supervisor | +| Guest agent core | Guest/device process that verifies and supervises separate capability module executables; used by macOS GuestWeave | +| Windows main resident agent | Repository-owned Linux init/agent or interactive Windows daemon; uses its own framed module bus | +| Host client SDK | Linked agent-modules client library on the physical VM host; authenticates and exchanges capability frames with guest core | +| Capability module | In agent-core, a separate OS executable using SDK lifecycle/host-service RPC; Windows main's local Go registry has different semantics | +| Host services | Services core supplies as a module's runtime host, inside the guest/device; these do not run on the physical VM host | | Image construction | Producing VM image artifacts from reviewed inputs | -| Qualification | Evidence that a candidate meets the required runtime conditions | +| Runtime acceptance | Observed boot, identity, capability and trust results for the exact candidate and acceptance profile | +| Qualification | Status supported by the required evidence; a construction-only result can remain unverified | +| Candidate publication | Registry upload and attestations for a candidate; separate from release-channel promotion | | OCI artifact | Registry-transported content described by media types and digests | -| Release channel | Signed data declaring promoted versions and digests | +| Release channel | Versioned promotion data declaring selected versions and digests; signing is conditional, and core consumption requires a verified signed bundle | | Source baseline | An exact implementation commit used to verify documentation | | HLD | High-level design: responsibilities, components, interfaces and boundaries | diff --git a/docs/products/agent-core/hld.mdx b/docs/products/agent-core/hld.mdx index 5205ffe..f31e3c5 100644 --- a/docs/products/agent-core/hld.mdx +++ b/docs/products/agent-core/hld.mdx @@ -1,6 +1,7 @@ --- title: 'Agent core: HLD' -description: Verification, supervision, local host services and the guest channel. +description: Guest-side process ownership, module supervision, capability + routing, trust and recovery. product: agent-core audience: - developer @@ -13,34 +14,108 @@ evidence: - agent-core:cmd/weave-agent/main.go - agent-core:internal/core/core.go - agent-core:proto/weave/agent/v1/module.proto +- agent-core:internal/weaveboot/weaveboot.go +- agent-core:internal/supervise/launch.go +- agent-core:internal/supervise/supervisor.go +- agent-core:internal/hostserv/hostserv.go +- agent-core:internal/transport/hypervisor.go +- agent-core:internal/transport/hypervisor_auth.go +- agent-core:internal/protocol/hvchannel/hvchannel.go +- agent-core:internal/lifecycle/lifecycle.go +- agent-core:internal/layout/layout.go +- agent-core:internal/capability/channel.go +- agent-core:internal/verify/release_linux.go +- agent-core:internal/controlsock/authorize_unix.go +- guestweave-windows-development:internal/command/run_channel.go --- -Agent core is the supervised capability runtime on a device or guest. It verifies and launches modules, offers host services and assembles the communication and policy boundaries those modules use. +Agent core is the guest-side process owner and capability router used by the current macOS GuestWeave integration. The physical host's GuestWeave run process owns the VM and the other end of the channel. Inside the VM, `weave-agent` owns module processes, routes capability messages and supplies services to those modules. - -The OS service manager runs weaveboot. weaveboot supervises core, and core verifies and supervises capability modules. Core also provides the host-service RPC surface. +Core also supports device/container contexts, including a Unix socket channel for containers. Windows GuestWeave's main baseline uses its own guest agent, described in its [HLD](../guestweave/windows.mdx). The separately inspected [Windows migration branch](../guestweave/windows-development.mdx) provisions shared core and OS modules instead. + + +GuestWeave's host client reaches core through the capability channel. Inside the guest, weaveboot supervises core, core supervises separate capability processes, and modules call core's per-module host services over local IPC. ## Process ownership -The installed service entrypoint is `weaveboot`. It stages and supervises core and provides rollback behaviour. The core binary is `weave-agent`; local control is exposed through `weavectl`. These names replace the speculative binary names in the original repository scaffold. +| Component | Runs where | Owns | +| --- | --- | --- | +| OS service manager | Guest/device OS | Starts the installed weaveboot service | +| `weaveboot` | Guest/device OS | Selects the current core, verifies staged updates, launches core and handles early crash-loop rollback | +| `weave-agent` | Guest/device OS | State, module discovery/reconciliation, registry, policy, identity, local control and capability-channel routing | +| Module supervisor | Inside core | Placement/privilege checks, verified launch, lifecycle, health/watchdog and bounded restarts | +| Capability module | Separate guest process | OS-specific feature implementation, such as command execution or clipboard | +| `weavectl` | Local authorised user | Calls core's control endpoint for status and lifecycle operations; Unix permits root or core's own UID | +| GuestWeave SDK client | Physical VM host | One channel owner per VM run; capability commands, registry and events | + +“Host services” means core acting as the **module's runtime host**. These services execute inside the guest alongside core. Identity, storage and module supervision are not thereby moved onto the physical VM host. + +## Interfaces and transport + +| Interface | Direction | Encoding and purpose | +| --- | --- | --- | +| Module lifecycle | Core → module | Local gRPC/protobuf over Unix socket or Windows named pipe: Init, Start, Stop, Health, Shutdown | +| Host services | Module → core | Per-module local gRPC server: identity, store, policy, events, logging, registry, transport and watchdog | +| Capability channel | VM/container host ↔ core | Length-prefixed JSON envelopes: module address, kind, opaque payload and correlation ID | + +The capability channel is a different wire from the lifecycle protobuf protocol. Protocol `1` does not establish compatibility with every host-channel vocabulary. Core's `internal/protocol/hvchannel` and the SDK's corresponding copy must encode compatible frames. + +Configured socket transports are `vsock:` on Linux, `hvsocket:` on Windows and `unix:`. Default/auto probes the supported virtio-serial path. Core does not infer a socket listener merely because a socket facility exists. Invalid channel configuration disables the channel while allowing core to continue. + +## Startup and module admission + +Core creates the layout, probes capabilities, opens persistent storage and initialises identity. It assembles policy, registry, transport and host services, then reconciles the installed modules directory. Directory watches, reload signals and a configurable rescan trigger later reconciliation; the CLI's default rescan is one minute. + +Admission follows this order: + +1. Check the manifest's placement and required capabilities. Console-user placement requires user privilege; `per-user-all` placement is refused. +2. Stage the executable for its target identity and verify the actual path that will run. +3. Create its private run directory, random launch token and host-services listener. The listener is ready **before** the child starts. +4. Launch with the supported protocol window and IPC information. Read the single stdout handshake line and validate the selected protocol. +5. Dial its lifecycle endpoint and call Init with identity, configuration, capabilities, privilege and watchdog cadence. Recheck the requirements returned by Init. +6. Call Start and publish running state, protocol and surfaces to the registry. Monitor health, watchdog and process exit. + +Core refuses duplicate channel addresses: delivery fans out to receivers at an address, so two owners of `weave.exec` could execute one request twice. An OS-specific installation ID such as `weave-linux-exec` can serve the stable `weave.exec` address. + +## A capability request through the guest + +GuestWeave's SDK client sends an addressed envelope. Core authenticates the external peer, resolves the module address and delivers the opaque payload through that module's transport receive stream. The capability service decodes the operation and calls its OS backend. Replies/events return through core to the SDK client, which correlates them with pending calls. + +Core answers registry requests itself and publishes revisioned changes after authentication. Missing, non-running and overloaded modules can produce `delivery.failed`. A powered-on VM and a reachable core do not prove that the requested module is installed or serving. + +## Trust boundaries + +- **Executable admission:** the CLI supplies a platform verifier or signed-channel verifier. The lower-level `core.Run` refuses every module if no verifier is supplied. Linux's default release verifier checks root ownership and group/world write bits of the executable and its immediate parent. It assumes trusted installation provenance; it does not query package provenance or perform an exec-time cryptographic signature check. +- **Local module identity:** each host-services server binds a module identity to a random launch token, checked for unary and streaming RPCs. Store namespaces are fixed to that module. +- **External host authority:** the guest is provisioned with a public key and the VM host retains the private key. Core requires proof of possession before capability traffic and registry disclosure. Missing/malformed keys leave core running but operations refused. New connections authenticate again. + +The external host key differs from the manifest root that verifies release/channel material. A successful hello establishes neither module provenance nor authority to execute commands. -Core owns the module registry and supervisor. A module is a separate process, with its own negotiated protocol and lifecycle. It is not an in-process product plugin. +## Durable state and updates -## Runtime assembly +The layout separates durable state, module versions, staging, logs, runtime sockets and execution copies. `--state-dir`/`WEAVE_STATE_DIR` relocates the layout. `--modules-dir` changes both discovery and lifecycle installation. Installed modules have `current`, `previous` and `versions/` entries. -Core assembles identity, policy management, namespaced storage, host services, the event bus, local control and guest transport. The host-service surface lets modules obtain capabilities without owning core’s private keys or another module’s namespace. +The lifecycle manager serialises installation per module. Signed-channel installation checks channel signatures, artifact digest/size, expiry and sequence before staging and replacing a process. Local installation is a separate path using local files and the configured executable verifier; it does not perform those remote-channel freshness checks. -Transport selection depends on host capabilities and configuration. Unsupported selection disables that channel rather than inventing a transport. The guest channel’s host-authentication key is a separate trust input from the keys used to verify module/channel data. +Channel fetching requires four HTTP-served bundle files: `manifest.json`, its signature, `signing.pub` and that key's signature. Selected artifacts are downloaded through the HTTP client. The separately inspected release-channel repository currently records bare OCI references and permits unsigned promotions; those entries do not directly satisfy this installer contract. See the [compatibility register](../../platform/compatibility.md). -## Verification and failure handling +For running modules, the default health gate requires healthy status for three seconds within a 30-second window. A console module waiting for a user session can be promoted **without a runtime health proof**; it starts when the session becomes available. Previous-version retention supports rollback. An unavailable sequence store refuses channel installs; a file marker alone cannot override that failure. -When no module verifier is configured, the fallback refuses execution. Storage failure can affect identity and queue services. The inspected core distinguishes a locked store that stops startup from other failures that expose degraded/unavailable services; this is a concrete condition to preserve in future recovery guides. +Core updates use a separate loop. weaveboot verifies a staged core, changes current/previous markers, then launches it. A readiness marker or sufficient stable uptime clears the early-crash counter; repeated early failures can restore the previous core. Readiness affects crash-loop recovery, rather than delaying the initial marker promotion. Core writes the marker after registering module runners and starting the control server goroutine; it does not wait for every module to start or confirm that the control listener has bound. The marker does not certify capability health. -weaveboot’s readiness and promotion behaviour is separate from a module’s health. A core process being ready does not mean every capability module is healthy. +## Failure behaviour -## Protocol authority +| Condition | Implemented response | Operational distinction | +| --- | --- | --- | +| Store already locked | Startup fails | Another core may own the store/channel/modules | +| Other store or identity failure | Core can run degraded, with dependent features unavailable | Liveness does not restore identity or durable queues | +| Modules directory unreadable on initial reconciliation | Startup fails | Broken installation differs from an empty directory | +| One invalid/unlaunchable module | Record its condition; other modules can start | Read the registry's state/detail for that module | +| No console user | Console-scoped module waits for a session | Clipboard/display absence can be expected placement | +| Repeated unhealthy polls or watchdog silence | Restart under supervisor policy and limits | Degraded health resets strikes rather than requesting restart | +| Channel/key unavailable | Guest operations unavailable; core can remain alive | Separate transport/authentication from module health | -Core’s `proto/` owns the agent wire contract. Core generates a private implementation copy; the module SDK generates its own copy against a pinned release. Channel and module JSON schemas are also published from core. +This design follows the source lock. GuestWeave's installed payload versions have separate installer pins; no release-binary or host/guest runtime acceptance is claimed. -Evidence: [weaveboot entrypoint](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/cmd/weaveboot/main.go), [core assembly and failure handling](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/core/core.go), [module lifecycle protocol](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/proto/weave/agent/v1/module.proto). +Source anchors: [assembly](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/core/core.go), [launch order](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/supervise/launch.go), [placement and supervision](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/supervise/supervisor.go), [channel framing](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/protocol/hvchannel/hvchannel.go), [core rollback loop](https://github.com/weaveplatform/weaveplatform-agent-core/blob/58f13ffcdba1641a692f985b3b26d053bb467da5/internal/weaveboot/weaveboot.go). diff --git a/docs/products/agent-modules/hld.mdx b/docs/products/agent-modules/hld.mdx index 5bfa43c..c26d492 100644 --- a/docs/products/agent-modules/hld.mdx +++ b/docs/products/agent-modules/hld.mdx @@ -1,6 +1,7 @@ --- title: 'Agent modules and SDK: HLD' -description: The module runtime, Host interface and independently released OS capabilities. +description: Host clients, guest capability services, module processes and their + two wire boundaries. product: agent-modules audience: - developer @@ -13,32 +14,98 @@ evidence: - agent-modules:sdk/modulesdk/serve.go - agent-modules:sdk/buf.gen.yaml - agent-modules:go.work +- agent-modules:sdk/modulesdk/lifecycle.go +- agent-modules:sdk/modulesdk/hostclient.go +- agent-modules:sdk/weavemodule/module.go +- agent-modules:sdk/weaveclient/weaveclient.go +- agent-modules:sdk/weaveclient/auth.go +- agent-modules:sdk/weaveclient/transport.go +- agent-modules:sdk/protocol/hvchannel/hvchannel.go +- agent-modules:sdk/weavewire/capability.go +- agent-modules:modules/weave-linux-exec/main.go +- agent-modules:modules/weave-linux-clipboard/module.manifest.json +- agent-modules:.github/agent-core-version +- guestweave-windows-development:go.mod --- -The agent-modules repository owns the current module SDK and OS-specific capability modules. Modules are independent Go modules rather than a single application with one release number. +The agent-modules repository owns both sides of the capability programming model: a **host-side client SDK** and **guest-side capability implementations**. macOS GuestWeave imports `sdk/weaveclient` on the physical host. Inside a guest, agent-core launches selected OS modules as separate executables; those modules use `modulesdk`, `weavemodule` and shared capability services from the same repository. - -Core launches a verified module. The SDK negotiates the protocol, serves lifecycle RPCs and connects to authenticated host services during Init. Unsupported protocol windows are rejected before the module lifecycle begins. +These components implement GuestWeave's macOS guest-integration path. The SDK is linked into host/module binaries; each running capability module is a process. Installed console-scoped modules may be waiting for a session. Windows GuestWeave's main baseline has its own agent/module bus and does not import this SDK; its separately inspected [migration branch](../guestweave/windows-development.mdx) does. + +## Repository and runtime layers + +| Layer | Location | Responsibility | +| --- | --- | --- | +| Host API | `sdk/weaveclient` | Typed calls, correlation, authentication, registry, events and streaming over one owned channel | +| Capability vocabulary | `sdk/weavewire` | Stable addresses, operation names and payload types | +| Guest adapter | `sdk/weavemodule` | Implements modulesdk.Module and binds one capability service to core's transport | +| Capability services | `sdk/weaveexec`, `sdk/weaveclipboard` and peers | OS-neutral decoding, policy, streaming and operation handling | +| Module runtime | `sdk/modulesdk` | Handshake, lifecycle gRPC, host-service client, configuration and recurring jobs | +| OS module | `modules/weave--` | Executable entrypoint, manifest and OS backend | + +For example, `weave-linux-exec/main.go` constructs an exec service with a Unix process starter, wraps it in `weavemodule.New`, then calls `modulesdk.Serve`. Other OS variants substitute their backend while serving the same address. Core supervises processes; the capability service interprets feature operations. + +## Host-to-guest command flow + +The two wire boundaries are distinct. Host ↔ guest core uses a four-byte big-endian length prefix and JSON envelopes containing module, kind, data and correlation ID. Guest module ↔ core uses local gRPC/protobuf over Unix sockets or named pipes, with launch-token metadata on host-service calls. Core and SDK maintain separate compatible copies of the external framing; its wire compatibility has no negotiation and is not established by modulesdk's lifecycle Protocol `1`. + +The host wraps an already-open runtime/hypervisor byte stream in `weaveclient.Client`. The caller owns key provisioning and invokes Authenticate; opening a client does not automatically authenticate it, and Dial with no key returns an unauthenticated client. The client owns one reader, serialises writes and correlates concurrent calls by ID. Two clients reading one connection corrupt a stream whose framing has no resynchronisation. + +An execution request follows this path: + +1. GuestWeave calls the SDK's typed execution method. +2. The client encodes the operation under `weave.exec`, with correlation ID, and sends it after the required authentication. +3. Guest core routes that address to the installed exec module through its transport service. +4. `weavemodule` dispatches to the exec service, which calls the OS backend. +5. Replies and streaming events return through core; the client matches them to the waiting operation. + +Installation ID and channel address differ: `weave-linux-exec`, `weave-macos-exec` and `weave-windows-exec` can all declare `weave.exec`. Core admits one owner of an address in a guest. The host does not select an OS-specific executable for each call. + +The client obtains registry snapshots/changes and distinguishes delivery failures. A healthy presence response does not guarantee an exec or clipboard module. Registry support depends on the core release; a recorded core version lets the SDK distinguish unsupported registry operations from timeouts on a version that should answer. + +## Module launch and lifecycle + + +Core verifies the executable and creates its per-module host-services listener before launch. The SDK returns the lifecycle endpoint, connects back during Init and returns requirements for core to check before Start. -## Module and Host interfaces +A module implements `ID`, `Requires`, `Init`, `Start`, `Stop` and `Health`. `modulesdk.Serve` checks the advertised protocol window, opens its local gRPC lifecycle server and writes exactly one stdout handshake line. An unsupported window exits with the dedicated refusal status before Init. + +Init checks module identity and protocol. Optional `ConfigReceiver.SetConfig` runs before the module's Init. The SDK connects to the existing host-services listener with the launch token, calls Init and returns requirements/surfaces. Core rechecks requirements before Start. + +Start begins the module's work plus SDK-owned recurring jobs/watchdog. Stop cancels SDK jobs and calls the module's Stop hook; draining in-flight work is the module/service implementation's responsibility. Shutdown ends the process. The SDK also exits on lost core connection, signal or lifecycle-server failure, keeping the child attached to its supervisor. + +## What Host supplies + +`Host` connects a module to **core inside the same guest/device**: + +| Facility | Boundary | +| --- | --- | +| Identity | Core brokers identity/credentials; the module does not own core's private keys | +| Store | Fixed module namespace, with subdivisions inside it | +| Policy | Read/watch core's policy | +| Transport | Send/receive addressed payloads through core's external channel | +| Events | Event bus, with manifest-declared subscription allow-list | +| Registry | Read/watch installed modules and runtime condition | +| Logging/watchdog | Attributed logs and supervisor liveness notification | +| UI | Surface data declared during Init and returned in its response | -A module implements `ID`, `Requires`, `Init`, `Start`, `Stop` and `Health`. `modulesdk.Serve` handles its negotiated startup and lifecycle serving. +OS bindings are linked into the module backend. They are not remote platform objects offered through Host. Recurring jobs are optional module-side declarations managed by the SDK. UI declaration records data; it does not draw a portal or prove an external renderer is deployed. -The `Host` interface exposes identity, transport, policy, namespaced storage, events, UI brokerage, logging and the module registry. It is deliberately bounded. Configuration and recurring work are optional module-side interfaces, rather than reasons to widen the wire contract. +## Capabilities and session placement -## Startup and lifecycle +The implemented vocabulary includes presence, exec, power, time, metrics, clipboard, session and display. Names such as files, tunnel, network and software are reserved in `weavewire`; reserved names have no operation set and do not imply an implemented capability module. -The SDK reads the core-advertised protocol window and refuses an unsupported protocol before initialisation. It opens its lifecycle server, writes one handshake line containing protocol and endpoint information, and then stops using stdout for ordinary output. +Clipboard/display target the console user's session. Their manifests declare user privilege and `per-user-console`. Core waits for that session and replaces/stops the module when the console user changes. Other capability modules use system placement. A running VM without a desktop user can therefore offer exec/presence while clipboard/display is unavailable. -Host-service connection is deferred until Init, when core’s host listener is available. The SDK exits on shutdown, loss of core connection, signal or server failure, avoiding a detached module continuing without its supervisor. +Operation/session errors, closed channels and registry-support failures are distinct outcomes. ErrNoSession can result from a console capability's caller deadline as well as actual session absence; consult registry/session information before asserting that nobody is logged in. An application should not translate every unanswered call into “VM down”. Backend availability and a module's installed/running/healthy condition need separate evaluation. -Health has a rich module-level status alongside the standard gRPC health service. Operational guidance must identify which surface it is checking. +## Health and releases -## Versioning and capability scope +Core uses rich `ModuleService.Health`, including degraded/unhealthy. The SDK also registers standard gRPC health and marks it SERVING when that server is created. That status does not prove Init/Start completed or the OS capability is healthy. Rich Health itself has no lifecycle-state gate; the generic weavemodule adapter returns healthy when its service has no HealthReporter. Capability acceptance therefore needs registry/lifecycle state and an actual operation check as well as the reported health result. -The repository’s SDK, packaging tools and OS capability modules have independent module paths and releases. The SDK’s protocol-generation core pin is separate from whichever core release a device installs. Compatibility follows the negotiated window and tested release combination. +SDK, packaging tools and OS modules have independent Go modules/releases. The SDK generates its private protobuf copy from `.github/agent-core-version`, pinned to `v0.9.2` here. GuestWeave's installer pins and the release-channel verifier pin are separate. Lifecycle protocol window, channel framing, capability vocabulary and installed versions all matter. -OS-specific capabilities include execution, display, clipboard, power, presence, session, metrics and time. A module’s presence is different from its health and the capabilities it can actually serve in the current session. +This HLD inspects source. It does not certify every backend/session or the installer-selected release binaries against the current source baselines. -Evidence: [Module and Host interfaces](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/modulesdk/module.go), [SDK serve loop](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/modulesdk/serve.go), [protocol generation](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/buf.gen.yaml). +Source anchors: [host client](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/weaveclient/weaveclient.go), [guest adapter](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/weavemodule/module.go), [lifecycle](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/modulesdk/lifecycle.go), [capability vocabulary](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/sdk/weavewire/capability.go), [Linux exec assembly](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/733ce935024428cadafc68957d2d49419c28d4fb/modules/weave-linux-exec/main.go). diff --git a/docs/products/guestweave/index.md b/docs/products/guestweave/index.md index dcf0f42..1da3955 100644 --- a/docs/products/guestweave/index.md +++ b/docs/products/guestweave/index.md @@ -1,6 +1,7 @@ --- title: GuestWeave -description: Understand the separate macOS and Windows host VM managers. +description: Host VM managers, guest payloads and the different integrations + that compose GuestWeave. product: guestweave audience: - consumer @@ -13,22 +14,52 @@ evidence: - guestweave-macos:internal/httpapi/server.go - guestweave-windows:internal/serve/http.go - hostweave:agent/runtime/guestweave/guestweave.go +- guestweave-macos:internal/vm/run/guestchannel.go +- guestweave-macos:internal/vm/guestagent/packages.go +- guestweave-windows:cmd/weaveagent/modulebus.go +- guestweave-windows:internal/guestweaveagent/proto/proto.go +- agent-modules:sdk/weaveclient/weaveclient.go +- guestweave-windows-development:go.mod +- guestweave-windows-development:internal/command/commands_exec.go slug: /products/guestweave --- -GuestWeave manages guest virtual machines on a local host. The current documentation covers two implementations: macOS and Windows. Their command surfaces, HTTP routes, backend APIs and image formats must be documented separately. +GuestWeave includes the host VM manager and the mechanisms that make a guest usable from that host: guest installation, command execution, clipboard, readiness, channels and image/state handling. The macOS and Windows source trees implement different compositions. A product name does not imply a shared guest agent, identical API semantics or an interchangeable image. | Host implementation | Design | Key interface boundary | | --- | --- | --- | -| macOS | [macOS HLD](macos.mdx) | CLI, HTTP adapters and a separate MCP server | -| Windows | [Windows HLD](windows.mdx) | CLI and guarded HTTP server over Windows host services | +| macOS | [macOS HLD](macos.mdx) | Virtualization.framework for macOS/Linux guests; native Hypervisor.framework path for Windows-on-mac; SDK client reaches guest core/modules | +| Windows main | [Windows HLD](windows.mdx) | HCS VM management; repository-owned resident guest agent/module bus; separate SSH-supplied agent path | +| Windows development | [Migration HLD](windows-development.mdx) | Shared SDK/core/module guest integration; independently pinned branch, with remaining companion/parity gaps | -HostWeave can drive GuestWeave through a runtime adapter that invokes the local CLI. GuestWeave remains responsible for VM lifecycle; HostWeave remains responsible for placement and orchestration. +## What belongs where + +| Component | macOS main implementation | Windows main implementation | +| --- | --- | --- | +| Physical-host VM manager | `weave` run process owns the VM, state and hypervisor/native runtime | `weave` commands and detached supervisor manage HCS compute systems and state | +| Host-side guest client | Agent-modules SDK `weaveclient`, one channel owner per running guest | Local `internal/guestweaveagent/client` and related client paths | +| Guest capability runtime | Selected agent-core release; current core source uses weaveboot → weave-agent supervision | Repository-owned Linux weaveagent / Windows weaveguestd resident binaries | +| Capability implementation | Agent-modules OS executables supervised by core | Native agent's local module registry; capability coverage differs by guest OS | +| Guest command wire | SDK/core framed capability envelopes over console/AgentLink | Local framed JSON protocol over HvSocket/AF_VSOCK; a separate supplied agent can run over SSH | + +Agent-core and agent-modules are separately maintained repositories/releases that compose the current macOS GuestWeave integration: host SDK library plus guest-side processes. The Windows main implementation at the recorded commit does not import those products. Its similarly named agent is not evidence that it uses core's supervisor or lifecycle protocol. + +Windows development branch `feat/weave-agent-handover-168` changes that composition: its host imports the same SDK family and provisions shared core/OS modules in the guest. CLI, REST and MCP exec use its run-supervisor/SDK channel. Architectural convergence is implemented there; full surface parity and release readiness require separate checks. The [migration HLD](windows-development.mdx) distinguishes branch mechanisms from the legacy main baseline above. + +## Relationships to other products + +HostWeave is an optional orchestrator. Its host-agent runtime adapter drives the local GuestWeave CLI; GuestWeave does not require HostWeave to create or run a VM. The adapter has its own command and companion-capability requirements, which must match the selected CLI version. + +Imageweave constructs/prepares image artifacts and can export disks for GuestWeave targets. It also prepares selected core/module release payloads for agent-bearing image tiers. It does not use GuestWeave's CLI as the universal image-build engine or register a VM merely by exporting a disk. The [Imageweave HLD](../imageweave/hld.mdx) separates these operations. + +OCI supplies image contracts/tooling. macOS GuestWeave directly uses shared Weave OCI libraries. Windows GuestWeave retains a different VHDX v2 artifact contract and native boot assets. The [compatibility register](../../platform/compatibility.md) records the consequences. ## Consumer contract -The implementations expose VM creation and lifecycle operations, but similar operation names do not guarantee identical requests. For example, the inspected macOS router uses a VM `run` route, while Windows uses `start`. The [reference index](../../reference/index.md) points to the two contracts independently. +Read the HLD for the actual interface path. On macOS, CLI execution uses the guest channel, while HTTP/MCP execution uses SSH; CLI stop defaults to a graceful request, while the inspected HTTP/MCP handlers construct a hard stop. On Windows main, native Linux execution uses the resident module bus and command coverage differs for Windows guests. + +A running host process, powered-on VM, authenticated guest channel, installed module and healthy capability are separate conditions. A guide must state which one it waits for. The [reference index](../../reference/index.md) records the distinct contracts. ## Documentation baseline -Both implementations have private source repositories. This public knowledge base describes their approved product responsibilities and interfaces; detailed evidence references live in the private companion. Host/guest combinations need runtime acceptance before receiving a support claim. +These designs trace the two private implementation repositories at the source lock. Public prose describes those mechanisms; raw private contracts remain in the companion. Installer-selected releases and checked-in binaries can differ from the inspected source, and host/guest combinations require separate runtime acceptance. diff --git a/docs/products/guestweave/macos.mdx b/docs/products/guestweave/macos.mdx index a3a38c9..39c1d10 100644 --- a/docs/products/guestweave/macos.mdx +++ b/docs/products/guestweave/macos.mdx @@ -1,6 +1,6 @@ --- title: 'GuestWeave on macOS: HLD' -description: VM lifecycle, host backend and HTTP boundaries on macOS. +description: Host processes, native virtualization, guest agent composition, execution paths and image state on macOS. product: guestweave audience: - consumer @@ -10,39 +10,200 @@ owner: ShocOne status: code-inspected verified_at: '2026-10-11' evidence: +- guestweave-macos:main.go +- guestweave-macos:internal/cli/root.go +- guestweave-macos:internal/cli/serve.go +- guestweave-macos:internal/cli/stop.go +- guestweave-macos:internal/serve/serve.go - guestweave-macos:internal/httpapi/server.go +- guestweave-macos:internal/httpapi/vms.go +- guestweave-macos:internal/httpapi/guest.go +- guestweave-macos:internal/httpapi/ws.go - guestweave-macos:internal/httpapi/router_schema_test.go +- guestweave-macos:internal/mcp/server_mcp.go +- guestweave-macos:internal/vm/service/service.go +- guestweave-macos:internal/vm/run/session.go +- guestweave-macos:internal/vm/run/windows.go +- guestweave-macos:internal/vm/run/guestchannel.go +- guestweave-macos:internal/vm/run/clipboard.go +- guestweave-macos:internal/vm/run/firstboot.go +- guestweave-macos:internal/vm/guestctl/socket.go +- guestweave-macos:internal/vm/guestagent/packages.go +- guestweave-macos:internal/vm/guestagent/windows.go +- guestweave-macos:internal/vm/guestagent/install.go +- guestweave-macos:internal/vm/guestagent/resident.go +- guestweave-macos:internal/vm/channelkey/channelkey.go +- guestweave-macos:internal/vm/layout/layout.go +- guestweave-macos:internal/hypervisor/hypervisor.go +- guestweave-macos:internal/hypervisor/bootfirmware.go +- guestweave-macos:internal/hypervisor/netbackend.go +- guestweave-macos:internal/hypervisor/hardware/net/softnet.go +- guestweave-macos:internal/network/network_shared.go +- guestweave-macos:internal/config/config.go +- guestweave-macos:internal/command/commands_exec.go +- guestweave-macos:internal/command/commands_stop.go +- guestweave-macos:internal/command/commands_shutdown.go +- guestweave-macos:internal/command/commands_restart.go +- guestweave-macos:internal/command/commands_clone.go +- guestweave-macos:internal/command/commands_push.go +- guestweave-macos:internal/imagesource/oci/registry.go +- guestweave-macos:internal/imagesource/oci/base.go +- guestweave-macos:internal/imagesource/oci/push.go +- guestweave-macos:internal/imagesource/oci/profile.go - guestweave-macos:go.mod +- guestweave-macos:internal/ssh/sshclient.go +- hostweave:agent/runtime/guestweave/guestweave.go --- -GuestWeave on macOS brings CLI and HTTP operations into a shared command and VM service layer. The HTTP API is an adapter over those operations. MCP is a separate server surface, so it must receive its own reference and authentication review. +GuestWeave's macOS implementation manages VMs on a local macOS host and supplies the host half of guest integration. Its runtime combines a host CLI/server, native virtualization code, a host SDK client, and guest-installed `weave-agent` core plus OS-specific capability modules. Those guest components implement execution, clipboard, presence and power operations inside the VM; they are part of the working GuestWeave stack. A VM can boot without them, but its agent-backed features cannot work until they are installed, answering and authenticated. - -Requests enter through a product interface, reach shared lifecycle operations and invoke the host backend. VM files retain the local state needed by later operations. +This HLD describes implementation at commit `f77fdaafd218cfa55b12dc1d4e814d4e2d874e46`. Installed agent releases and Go dependencies have their own pins; they do not automatically follow the corresponding repositories' `main` branches. + + +Lifecycle requests create or address a per-VM host run process. That process owns the virtualization instance and the sole SDK connection to guest-resident agent core, which routes capability operations to modules. HTTP and MCP execution currently use a separate SSH path, described below. -## Components and boundaries +## Deployment and component ownership + +The executable uses the `weave` command vocabulary. `main.go` locks the main OS thread before CLI dispatch because AppKit and Virtualization.framework require it. Each running VM has a separate `weave run` process. HTTP and MCP start runs by spawning the same executable in a new session; VM lifetime therefore outlasts the initiating request and server. `weave serve` runs the optional HTTP server; `weave serve --mcp` instead selects the stdio MCP server. The two are alternative modes, and the MCP flag does not add an HTTP MCP endpoint. [Process spawning][process], [server mode dispatch][serve]. + +| Location | Component | What it owns | +| --- | --- | --- | +| Host | CLI, HTTP API and MCP adapter | Request parsing, selected command calls, listings and distinct execution adapters | +| Host | `internal/command` and `internal/vm/service` | Local VM operations, configuration, inventory and detached-run helpers | +| Host, one process per VM | `internal/vm/run.Session` | Live virtualization instance, VM lock, display, guest-control socket and agent connection | +| Host | Virtualization.framework path and native Hypervisor.framework VMM | Guest CPU/memory/device lifecycle; the implementation differs by guest OS | +| Host | Agent-modules SDK `weaveclient` and `weavewire` | Multiplex guest operations through one framed channel, authenticate the host and query module availability | +| Guest | Agent-core release, installed as `weave-agent` | Receive the guest channel, authenticate requests and route them to supervised modules | +| Guest | OS-specific agent-module releases | Perform guest OS operations such as exec, power, clipboard and presence | +| Host | Shared OCI library through `internal/imagesource/oci` | Fetch, verify, cache, unpack and publish VM image artifacts | +| Host filesystem | VM directories and caches | Persistent configuration, disks, firmware state, identity, trust and source lineage | + +HostWeave can invoke this host CLI from its own host agent. That host orchestration agent and the VM's guest-resident `weave-agent` have different owners and lifecycles. GuestWeave itself operates locally without HostWeave. Image construction and registry publication are separate from a live VM's agent connection. [HostWeave runtime adapter][hostweave]. + +## Two virtualization implementations + +The dispatch in `Session.runMainThread` chooses by the VM configuration's guest OS. It is not a generic backend selector that sends every operation through one framework. [Engine dispatch][engines]. + +| Guest OS on the macOS host | Implementation | Important boundary | +| --- | --- | --- | +| macOS | Apple's Virtualization.framework through Go platform bindings and `internal/vm` | macOS hardware-model/identity and auxiliary storage belong to this path | +| Linux | Virtualization.framework through the same VM implementation | EFI state, virtual devices and optional first-boot cloud-init seed | +| Windows on ARM | GuestWeave's `internal/hypervisor` VMM over Hypervisor.framework | GuestWeave implements the hosted VMM, firmware/device model and lifecycle itself | + +The Windows VMM owns guest address space, vCPU loops/traps, interrupts and timers, UEFI firmware, storage and virtual devices. It uses an embedded TPM responder through `go-sdk-vtpm2`; Windows runs in process, with native framebuffer/input integration. Its current path does not launch QEMU or use a VNC server. Legacy QEMU-named layout helpers and comments remain in the source and do not establish a live QEMU runtime dependency. macOS/Linux runs use the VZ/AppKit path, with display and VNC options governed by their own code. [Native VMM implementation][vmm]. + +Host OS/framework availability, guest architecture, firmware and device configuration still constrain execution. For example, CLI exec is gated on macOS 14 or later and the native VMM is ARM64. Repository paths are evidence of implementation, not a certificate that every media/device combination boots successfully. + +Network attachments also differ by engine. VZ NAT uses the framework's NAT attachment; the native VMM's default NAT uses a Go user-mode network backend. Bridged/vmnet modes depend on their host permissions, while softnet launches an optional external helper over a Unix datagram socket pair. On the native VMM, an unavailable network backend becomes a logged null peer while the NIC and VM remain present. A successful boot therefore does not prove guest network reachability, and SSH-based API operations can fail while agent-channel operations remain available. [VZ NAT][vz-network], [native network selection and fallback][hv-network], [softnet subprocess][softnet]. + +## Request paths and readiness + +The adapters share some VM operations, but their guest-execution and stop semantics have diverged. Integrators must select a surface deliberately. + +| Operation | Actual path at this revision | Prerequisites / result meaning | +| --- | --- | --- | +| HTTP or MCP start | Adapter → `SpawnDetachedRun` → child `weave run` → engine | Adapter waits for the VM's PID-lock based running state, not guest-agent or application readiness | +| CLI `weave exec` | CLI → host `guest.sock` → run-owned SDK client → guest core → exec module | Requires a running VM, answering authenticated agent and exec module; no guest IP/SSH dependency | +| HTTP `/weave/vms/{name}/exec` | Resolve guest IP → SSH → guest command | Uses default `weave` credentials and joins command arguments into a shell command | +| HTTP exec/SSH WebSocket | Resolve guest IP → WebSocket bridge → guest SSH session | Requires guest networking and SSH; this does not bridge the agent channel | +| MCP `weave_exec` | Resolve guest IP → GuestWeave SSH client → guest command | Defaults to `weave` credentials; caller may supply credentials and timeout | +| CLI stop | Host guest-control socket → agent power request → platform power request → forced stop after grace | CLI defaults to graceful first; `--force` skips the asking steps | +| HTTP or MCP stop | Adapter → `StopCommand` with `Graceful` left false | Hard stop; it is not the CLI's default graceful-stop policy | +| Shutdown | Onboarding-specific guest path when available, otherwise guest-control shutdown request | Never forces; a non-cooperating guest remains running and the command reports failure | +| Restart | Guest-control request → guest/platform reboot ladder → backend restart | Keeps the run process and its sockets alive; forced restart is separately reported | + +The detached-run helper returns once the child starts. HTTP then polls for up to 30 seconds. `Running()` reads the VM's PID lock, so a successful response says the host run process has acquired the VM, not that the guest OS has finished booting or that any module is ready. [Run/readiness][readiness], [CLI execution][exec], [HTTP execution][http-exec], [MCP execution][mcp-exec], [stop policy][stop]. + +The REST server binds to `127.0.0.1`, defaults to port `7777`, and mounts routes under `/weave`; `/weave/openapi.yaml` serves the embedded specification. Its router installs recovery and request logging, without an API authentication middleware. The agent channel's authentication does not secure this HTTP surface. MCP uses stdio. Remote publication of the local REST service would require a separate exposure design. [HTTP router][http], [MCP transport][mcp-transport]. + +GuestWeave's SSH wrapper defaults a missing host-key callback to `ssh.InsecureIgnoreHostKey`; MCP constructs that wrapper without a pinned callback. Guest credentials therefore do not establish SSH server identity on this path, and authenticated guest-channel trust does not extend to it. A guide using SSH needs an explicit server-identity verification procedure. + +## Guest-agent transport and provisioning + +Each run owns exactly one SDK client for the VM's guest channel. CLI control requests arrive through the host Unix `guest.sock`; they do not open competing readers on the framed guest transport. On the Virtualization.framework path, two host pipes back a dedicated named console port. On the native Windows VMM path, `AgentLinker.AgentLink()` supplies the reboot-stable host connection. This channel is created independently of clipboard policy. [Channel ownership][channel], [VZ attachment][attachment]. + +The run probes until agent core answers, then authenticates with the VM's Ed25519 private key. It retains the client across guest reboots and repeats authentication as required. The module registry exposes installed/running capability state; it is not a universal preflight gate. Exec sends its request directly, while clipboard consults registry state. Core being present does not imply that exec, power or clipboard modules are usable. + +Guest components enter the VM through different provisioning paths: + +| Guest OS | Agent delivery | Channel-key provisioning | +| --- | --- | --- | +| macOS | Core `.pkg` first, then `weave-macos-*` module packages during onboarding or a later SSH installation | Read-only `WEAVEPROV` volume exposes the public key for core to consume | +| Linux | Agent core/modules supplied by the image or the operator's first-boot provisioning | NoCloud seed writes the public key and restarts core; the generated key seed alone does not install the agent | +| Windows on the macOS host | Core Windows ZIP plus `weave-windows-*` module ZIPs injected into install media; code also supports later SSH installation/upgrade | `WEAVEPROV` optical media after installation | + +The macOS default core package is **0.9.8** and its eight default module packages cover clipboard, display, exec, metrics, power, presence, session and time. The Windows-on-mac core ZIP is **0.9.11**, with eight Windows module ZIPs for those capabilities. The host Go module separately pins the agent-modules SDK to **0.2.7**. These are three different dependency choices. Released package bytes are checked against their configured digests; local package overrides use separate paths. Statements in comments about historical signature/notarization checks are not a fresh verification of those release assets. [macOS package pins][mac-packages], [Windows package pins][windows-packages], [host dependencies][dependencies]. + +| Guest payload | Clipboard | Display | Exec, metrics, power, presence, session and time | +| --- | --- | --- | --- | +| macOS modules | `0.2.5` | `0.2.1` | `0.2.1` each | +| Windows-on-mac modules | `0.2.6` | `0.2.3` | `0.2.2` each | + +Windows install media supplies the core's channel mode as `WEAVE_CHANNEL=virtio-serial`, because this native VMM is not a Hyper-V guest. The bundle installer records installation failures but exits zero so a guest-agent failure does not abort Windows Setup. Windows completing Setup is therefore separate from core/modules being installed successfully. [Windows bundle installer][windows-installer]. + +First boot provisions trust out of band: `channel.key` stays on the host with mode `0600`; `channel.pub` goes into the guest. Provisioning media remains attached on subsequent runs until the guest accepts its key and the host records that acceptance. A guest with no agent or cloud-init can simply ignore its provisioning disk. [First boot][firstboot], [per-VM keys][keys]. + +## Persistent state, images and cloning + +Storage resolves from explicit `storage.home` / `GUESTWEAVE_STORAGE_HOME`, then a configured default location, then `~/.weave`. Each VM has its own directory; image content and unpacked bases are caches, not the live VM's mutable disks. [Storage root][storage]. + +| State | Files / mechanism | Consequence | +| --- | --- | --- | +| VM configuration and system disk | `config.json`, `disk.img` | Survive host commands and run-process exit | +| Running ownership | PID lock associated with `config.json` | Prevents concurrent ownership; used by listings/readiness polling | +| VZ firmware and suspend | `nvram.bin`, `state.vzvmsave` | Framework-specific state | +| Native Windows VMM firmware and suspend | `NVRAM.dat`, `state.wsar`, TPM state | Different formats; VZ and native suspend images cannot be exchanged | +| Host control surfaces | `guest.sock`, plus display/control/clipboard sockets as implemented | Live sockets belong to the run process | +| Trust and provenance | `channel.key`, `channel.pub`, trust records, `agent-core-version`, `lineage.json` | Distinguish channel identity, installed core and clone origin | + +A suspended VM is detected from its backend-specific image after checking running ownership. Resume consumes that image only after successful restoration. Stop on an already suspended VM discards the saved state; shutdown rejects it because there is no running guest to ask. [VM layout and state][layout], [stop and shutdown][stop]. + +The OCI adapter uses the shared Weave guest-artifact contract. Pull selects the guest platform, applies the registry profile's verification mode, caches content, unpacks a base and constructs GuestWeave's local config/state. The default is not universally signed-image enforcement: profiles without an explicit verification block or channel use `none` and warn. A profile can require `channel`, `signature` or `both`. Clones are independent APFS copies and do not need a cache pin for their lifetime. [OCI pull/cache][oci], [verification policy][policy]. -| Component | Responsibility | -| --- | --- | -| CLI and HTTP adapters | Parse product requests and return operation results | -| Command layer | Share lifecycle behaviour between entrypoints | -| VM service | Coordinate VM configuration and local state | -| Host backend | Invoke macOS virtualization facilities | -| VM files | Persist disks, configuration and VM state | +Push constructs a bundle from a stopped VM, requires a raw system disk, uses the shared publishing library and can emit promotion metadata. It does not publish the VM's MAC or machine identifier. Image consumption constructs fresh local identity; local cloning also checks channel trust. A provisioned local VM normally must be sealed before it is used as a template, because copying its trusted disk would make both guests accept the source key. `--share-channel-key` is an explicit exception. Do not treat a disk clone as a complete isolation boundary. [OCI publishing][push], [clone trust checks][clone]. -Backend selection is host-architecture and guest-OS dependent. The presence of a code path does not certify every possible guest combination. +The push implementation copies the raw guest disk without checking the channel-trust record or scrubbing its guest filesystem. It therefore does not prove that a provisioned guest's trusted public key has been removed. Fresh host-side identity on pull cannot repair a disk that still trusts the source VM's key. Sealing and first-boot trust acceptance need their own image qualification before an artifact is treated as a reusable template. -## HTTP interface +## Failure and trust boundaries -The inspected server binds to `127.0.0.1` and mounts product routes under `/weave`. VM operations include creation, `run`, stop, shutdown, restart, snapshots and guest execution. Its specification is served at `/weave/openapi.yaml`. +Missing agent core does not prevent ordinary VM boot. The run continues probing, while CLI execution reports that no agent is answering. A failed authentication keeps the VM alive but refuses authenticated guest operations; provisioning, rather than the hypervisor, is then the boundary to repair. Missing/non-running modules are separate failures. A host `guest.sock` bind failure also leaves the run alive and makes its control verbs unavailable. The socket checks peer credentials and allows root or the user running the VM; REST loopback access follows different rules. [Channel failure behavior][channel], [local IPC authorization][ipc]. -This loopback boundary is material to deployment guidance. Any future remote exposure guide must establish authentication, transport protection and origin handling for that specific setup. +A graceful request being accepted is not proof the guest has powered off. Stop waits for the guest and run lock to be released, with a kill backstop if the host process stalls. Shutdown reports a timeout rather than converting it into a power cut. The HTTP/MCP stop adapters bypass the graceful ladder at this revision. Those distinctions matter to filesystem consistency and automation that starts another run immediately after stopping one. -## Orchestration integration +## Verification limits -HostWeave’s GuestWeave runtime adapter calls the local CLI. Keep binary version, arguments, output parsing and readiness behaviour in the integration contract. The local HTTP API is a separate integration surface. +The source baseline was inspected across host dispatch, handlers, guest transport, package delivery, state and OCI integration. The router/spec test checks method/path parity; it does not establish semantic parity between CLI, REST and MCP. No VM was booted, no package release signature was independently checked, and no hypervisor/guest-module acceptance was run during this documentation audit. -## Verification and limitations +Acceptance should separately exercise each supported guest OS/backend, authenticated CLI exec with guest networking disabled, REST/MCP SSH execution with changed credentials, missing modules, first-boot key acceptance and rejection, graceful versus forced stop, both suspend formats, and OCI verification/clone identity. A successful route test or static diagram cannot stand in for those results. -The repository includes a router/spec test that compares served HTTP methods and paths with OpenAPI operations. This HLD is code-inspected at the recorded `main` baseline; macOS VM acceptance was not exercised for this documentation release. API reference must also verify request and response semantics, beyond route parity. +[process]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/service/service.go#L227-L269 +[serve]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/serve/serve.go#L23-L28 +[hostweave]: https://github.com/weaveplatform/hostweave/blob/5080abf8d1a51232246b178d4be34dc9db21e4a0/agent/runtime/guestweave/guestweave.go +[engines]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/run/session.go#L521-L545 +[vmm]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/hypervisor/hypervisor.go#L3-L18 +[vz-network]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/network/network_shared.go#L1-L15 +[hv-network]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/hypervisor/netbackend.go#L46-L121 +[softnet]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/hypervisor/hardware/net/softnet.go#L78-L143 +[readiness]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/httpapi/vms.go#L169-L184 +[exec]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/command/commands_exec.go#L30-L59 +[http-exec]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/httpapi/guest.go#L25-L74 +[mcp-exec]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/mcp/server_mcp.go#L283-L322 +[stop]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/command/commands_stop.go#L19-L36 +[http]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/httpapi/server.go#L36-L154 +[mcp-transport]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/mcp/server_mcp.go#L374-L378 +[channel]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/run/guestchannel.go#L95-L180 +[attachment]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/run/clipboard.go#L101-L143 +[mac-packages]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/guestagent/packages.go#L23-L100 +[windows-packages]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/guestagent/windows.go#L27-L91 +[windows-installer]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/guestagent/windows.go#L309-L376 +[dependencies]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/go.mod#L1-L28 +[firstboot]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/run/firstboot.go#L3-L21 +[keys]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/channelkey/channelkey.go#L1-L77 +[storage]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/config/config.go#L27-L68 +[layout]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/layout/layout.go#L48-L207 +[oci]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/imagesource/oci/registry.go#L107-L147 +[policy]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/imagesource/oci/profile.go#L119-L159 +[push]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/imagesource/oci/push.go#L27-L134 +[clone]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/command/commands_clone.go#L222-L265 +[ipc]: https://github.com/weaveplatform/guestweave-cli-macos/blob/f77fdaafd218cfa55b12dc1d4e814d4e2d874e46/internal/vm/guestctl/socket.go#L123-L149 diff --git a/docs/products/guestweave/windows-development.mdx b/docs/products/guestweave/windows-development.mdx new file mode 100644 index 0000000..ce6dccf --- /dev/null +++ b/docs/products/guestweave/windows-development.mdx @@ -0,0 +1,161 @@ +--- +title: 'GuestWeave Windows development: agent handover HLD' +description: The PR170 SDK/core/modules migration, provisioning and lifecycle boundaries, and remaining compatibility gaps. +product: guestweave +audience: +- developer +- operator +owner: ShocOne +status: code-inspected +verified_at: '2026-10-11' +evidence: +- guestweave-windows-development:go.mod +- guestweave-windows-development:internal/cli/root.go +- guestweave-windows-development:internal/cli/exec.go +- guestweave-windows-development:internal/cli/stop.go +- guestweave-windows-development:internal/command/commands_create.go +- guestweave-windows-development:internal/command/commands_run.go +- guestweave-windows-development:internal/command/run_detached.go +- guestweave-windows-development:internal/command/run_channel.go +- guestweave-windows-development:internal/command/run_guest.go +- guestweave-windows-development:internal/command/run_bootstrap.go +- guestweave-windows-development:internal/command/run_upgrade.go +- guestweave-windows-development:internal/command/run_network.go +- guestweave-windows-development:internal/command/run_clipboard.go +- guestweave-windows-development:internal/command/run_clipboard_guest.go +- guestweave-windows-development:internal/command/commands_exec.go +- guestweave-windows-development:internal/command/exec_result.go +- guestweave-windows-development:internal/command/commands_modules.go +- guestweave-windows-development:internal/command/commands_stop.go +- guestweave-windows-development:internal/command/commands_shutdown.go +- guestweave-windows-development:internal/command/commands_restart.go +- guestweave-windows-development:internal/command/commands_clone.go +- guestweave-windows-development:internal/command/commands_seal.go +- guestweave-windows-development:internal/command/seal_linux_script.go +- guestweave-windows-development:internal/command/seal_windows_script.go +- guestweave-windows-development:internal/command/commands_oci.go +- guestweave-windows-development:internal/command/commands_import.go +- guestweave-windows-development:internal/guestchannel/channel.go +- guestweave-windows-development:internal/guestchannel/dial_windows.go +- guestweave-windows-development:internal/vm/guestctl/socket.go +- guestweave-windows-development:internal/vm/guestctl/exec.go +- guestweave-windows-development:internal/vm/channelkey/channelkey.go +- guestweave-windows-development:internal/vm/channelkey/private_windows.go +- guestweave-windows-development:internal/vm/channelkey/trust.go +- guestweave-windows-development:internal/vm/guestagent/releases.go +- guestweave-windows-development:internal/vm/guestagent/packages.go +- guestweave-windows-development:internal/vm/guestagent/download.go +- guestweave-windows-development:internal/vm/guestagent/signature.go +- guestweave-windows-development:internal/vm/guestagent/windows.go +- guestweave-windows-development:internal/vm/guestagent/authenticode_windows.go +- guestweave-windows-development:internal/provmedia/seed_windows.go +- guestweave-windows-development:internal/nativebuild/nativebuild.go +- guestweave-windows-development:internal/nativebuild/scripts/build-weave-rootfs.sh +- guestweave-windows-development:internal/hcs/engine.go +- guestweave-windows-development:internal/hcs/native.go +- guestweave-windows-development:internal/linuxmedia/ubuntu-desktop.yaml +- guestweave-windows-development:internal/winmedia/sidecar.go +- guestweave-windows-development:internal/serve/http.go +- guestweave-windows-development:internal/serve/auth.go +- guestweave-windows-development:internal/serve/vmcreate.go +- guestweave-windows-development:internal/serve/mcp.go +- guestweave-windows-development:internal/oci/oci.go +- agent-modules-sdk-027:sdk/protocol/hvchannel/modules.go +- agent-core:proto/weave/agent/v1/module.proto +- hostweave:agent/runtime/guestweave/capabilities.go +- hostweave:agent/runtime/guestweave/guestweave.go +--- + +The Windows development branch replaces the repository-local guest agents with the shared agent SDK on the physical host and separately released core/module processes inside the guest. This establishes a different product relationship from the [Windows main HLD](windows.mdx). + +This page describes [PR170](https://github.com/weaveplatform/guestweave-cli-windows/pull/170), branch `feat/weave-agent-handover-168`, at immutable commit `651ec7d5d0459ece11526c5ee34e0774bc6d2f52`. The comparison baseline for main is `48fe4242d4d8b884e1bc464287f9a6c8f72ca275`. It records implementation inspected in this development snapshot; it does not establish merged, released or independently accepted behavior. Upstream validation narratives were not used as runtime evidence. + + +The Windows run supervisor owns an authenticated SDK session and the host control pipe. HCS/HvSocket connects that host client to guest core, which supervises separate capability module executables inside the guest. Public trust enters through provisioning media; the private key remains in the host VM store. + + +## Composition changes + +| Boundary | Pinned main | PR170 development snapshot | +| --- | --- | --- | +| Host capability client | Repository-local guest bus client | Linked `agent-modules/sdk v0.2.7` and `weaveclient` | +| Guest implementation | Linux `cmd/weaveagent` init/agent; Windows `cmd/weaveguestd` interactive clipboard daemon | Released guest core plus separate OS-specific module executables | +| Capability transport | Repository-owned module/op envelopes and `weave-guestd/1` hello | SDK/core capability frames authenticated with a per-VM Ed25519 key | +| Host supervision | Detached compute can outlive the initiating process | `--detach` spawns a separate run supervisor holding the VM lock and compute handle | +| Exec | Bundled native Linux execution path | CLI, REST and MCP use the run supervisor's SDK exec path for Linux/Windows guests | +| Legacy SSH agent commands | Caller-supplied binary installation/call/tools | Removed; SSH remains for explicit SSH access and delayed legacy bootstrap | +| Native Linux boot | Embedded local agent as init or in an initrd | Ubuntu systemd rootfs with released core/modules; `init=/sbin/init` | + +The development tree removes `cmd/weaveagent`, `cmd/weaveguestd`, the local `internal/guestweaveagent` bus, its embedded native agent and the legacy `agent` command implementation. The SDK is a host library. Guest core and modules are installed guest processes; they are not libraries linked into GuestWeave. HCS remains the VM backend, and HCN/HNS remains responsible for host networking. + +The resolver selects eight OS-specific capability families: clipboard, display, exec, metrics, power, presence, session and time. Selection does not prove installation, an available interactive session or healthy operation. Host clipboard synchronization calls the SDK and reports the guest registry state; module delivery failures can distinguish a missing module, a stopped module and a missing console session. + +## Run ownership and lifecycle + +`runHCS` holds the VM mutation lock, starts the compute system and creates a run context, SDK channel and `guestctl` control server. It supervises unattended installation through poweroff and a fresh boot of the installed disk. The same supervisor owns clipboard synchronization and port forwards. Detached run starts another `weave run --supervisor --no-graphics` process, then waits up to 30 seconds for its control pipe to accept connections. + +That return condition is host control availability. It does not require successful guest authentication, an available exec module or a completed unattended install. `guestctl.Serve` treats a pipe-binding failure as best effort; the VM can run while its control verbs remain unavailable. + +The local control server uses a Windows named pipe with a protected DACL granting access to the supervisor's current-user SID. The host private channel key likewise uses an owner-only protected DACL. This is a concrete Windows access boundary; Unix-root language in copied comments does not describe its implementation. + +| Operation | Development implementation | +| --- | --- | +| Stop | Request SDK power shutdown when deliverable, then HCS integration-component shutdown, then hard terminate if the allowed grace expires; `--force` skips to terminate. If the supervisor pipe is absent, the command falls back to backend stop. | +| Shutdown | Use the supervisor's guest shutdown path without the hard-terminate fallback; a guest can remain running. | +| Restart | A normal restart asks the SDK power capability and waits for a newer authenticated channel epoch. Forced restart terminates, recreates the compute system and waits for authentication. | +| IP | Prefer authenticated guest inventory matched to the configured MAC, then fall back to the existing resolver. A configured reservation is not proof the guest holds it. | +| Modules | Request the SDK registry through the supervisor; report module version, state, health and restart count. | + +CLI stop supplies a 30-second default. A zero timeout in `StopCommand` uses 60 seconds through the supervisor, while its backend fallback uses 30 seconds. HTTP/MCP omit a timeout as zero. These interfaces therefore do not have identical defaults even within this branch. + +## Channel and trust flow + +`guestchannel.Session` loads or creates `channel.key` and `channel.pub` in the VM directory. Its dialer reads the live HCS `RuntimeId`, then opens HvSocket service port 2010. Linux core is configured for `vsock:2010`; Windows core for `hvsocket:2010`. The shared local core/module gRPC lifecycle is a separate protocol inside the guest. + +Each connection creates a fresh SDK client, tries presence hello, then authenticates using the private Ed25519 key. Hello failure alone does not prevent core authentication. Successful authentication records the accepted public-key fingerprint in `channel.json`, clears the host seal mark and exposes the client to callers. Module-registry retrieval can fail after authentication without failing the connection. Reconnect repeats authentication; an authentication loss during a call triggers one serialized reauthentication retry. + +An explicit authentication refusal retries for up to five minutes on each connection and identifies the need for out-of-band provisioning. After that connection closes, the session loop can reconnect and start another retry budget for the lifetime of the run. A modern hello, successful authentication or explicit refusal disables the delayed SSH bootstrap path, so a refused key is not repaired by silently replacing guest trust. + +Linux provisioning media is a `CIDATA` cloud-init seed carrying `/etc/weave/channel.pub`, channel environment and NIC configuration. Windows uses a `WEAVEPROV` volume containing `weave/channel.pub`. The host attaches this seed when trust is unprovisioned or sealed and attempts to detach it after authentication; installer mode defers detachment. Installation media carrying packages is separate from this post-install trust seed. + +For an existing guest that shows no modern agent, a one-time bootstrap starts after three minutes. It requires SSH reachability and usable credentials, uploads an OS-specific bundle and public key, and invokes its installer. A Windows clone with an existing first-logon deferral task is left to that task. Explicit SSH and bootstrap are separate from ongoing capability execution. + +## Payload resolution and boot + +The host SDK is pinned to `v0.2.7`. Guest payloads are resolved at operation time from GitHub stable releases: core from `/releases/latest`, and the highest stable semantic version of each selected module from module release tags. The resolver requires both amd64 and arm64 assets in the selected release even when installing only one architecture. It does not consume a promoted Weave release-channel manifest or freeze a coordinated core/module version set. + +| Input | Checks in this branch | +| --- | --- | +| Downloaded package | Require a GitHub release asset SHA-256, verify downloaded or cached bytes, and enforce a 256 MiB package limit. | +| Downloaded core | Verify the Sigstore-signed checksum document against the core release workflow identity, then bind the selected asset digest to that signed document. | +| Windows bundle | Validate ZIP paths and expansion limits; verify `.exe`, `install.ps1` and `uninstall.ps1` Authenticode signatures and the fixed publisher thumbprint. Guest PowerShell installers run under `AllSigned`. | +| Linux module | Verify the release API asset digest, then lay out the raw executable and manifest under root-owned `/usr/lib/weave/modules`. This fetch path does not verify a module checksum signature. | +| Local package override | Read configured local packages instead of the selected download. Linux overrides bypass download digest/core-signature checks; Windows bundles retain their Authenticode checks. Release discovery still runs. | + +These checks have distinct trust anchors. A release API digest is not a signed module checksum, and the Windows fixed publisher is not the core's Sigstore release identity. The source snapshot alone cannot certify the actual dynamically selected binaries or their compatibility with the linked SDK. + +Default native creation uses WSL to build a kernel and Ubuntu noble systemd rootfs. It installs the resolved core Debian package and raw Linux module layout, enables core/cloud-init services and boots the copied VHDX using `root=/dev/sda rw rootwait init=/sbin/init`. Host shares use a systemd/socat/9p path. The image writer emits a kernel and manifest; the main branch's initrd-only agent path is removed. Supplied native artifacts require both `--image` and `--rootfs`, but validation checks path existence rather than the manifest hashes or installed capability composition. + +Windows unattended media carries the verified core/module bundle. Ubuntu unattended media carries the Linux bundle. Fedora's inspected kickstart path does not carry that bundle, while the generic Linux bootstrap installer uses `.deb` and `dpkg`. The branch does not establish Fedora agent provisioning parity through those paths. + +## Updates, sealing and image transport + +After connection, the supervisor configures Windows static NICs and attempts automatic upgrades. It obtains the core version by guest exec and module versions from the registry, selects older installed packages, stages an installer through SDK exec and retries failed staging/verification up to three times. Missing modules are not selected for installation by this upgrade comparison. Version verification checks that selected packages report at least the requested versions; it does not require module `running` state or healthy probes. Staging resolves releases again, so its payload selection can change after the initial comparison. + +The Windows bundle's `install.cmd` records per-step failures but always exits zero so Windows Setup can continue. Accordingly, bootstrap's successful installer exit and written `agent-core-version` file do not prove package installation or guest readiness. Channel authentication and observed module behavior require separate evidence. + +Local clone requires a stopped source and checks host channel trust. Provisioned or unknown trust is refused unless `--share-key` explicitly copies the source identity; a normal allowed clone mints an independent key. Linux sealing stops core, removes the trust anchor, store identity and SSH host keys, cleans cloud-init and machine identity, then requests shutdown. Windows sealing checks SYSTEM execution, disk decryption and pending servicing, stages an interactive administrator task, stops core, removes store identity/trust/SSH keys and runs Sysprep. A first-logon task postpones core restart until clone OOBE completes. Recovery attempts restore channel access on a reported failure. + +The host marks a template sealed after its shutdown wait observes the VM stopped. Failure markers are polled while it remains running; there is no independent post-shutdown disk inspection or sealing attestation in this command. The host record therefore represents the observed command path, not a cryptographic proof of every disk property. + +OCI push still sends VM configuration plus a flattened VHDX using GuestWeave v2 media types. It has no local-clone trust gate, automatic sealing or trust scrubbing. It does not include the native kernel directory or `guest.vmgs`; source-host boot paths can remain in the exported configuration. Pull/registry clone creates a new host key, which does not remove prior trust from the guest disk. Neither native portability nor UEFI boot-state portability follows from a successful disk transfer, and the shared Weave raw-chunk OCI contract remains different. + +## Remaining integration limits + +The linked SDK `v0.2.7`, resolved to commit `0a61d653a0fd80e3befcb0b65d6b52171d36aad4`, has no `Core` field in [its `ModulesSnapshot` type](https://github.com/weaveplatform/weaveplatform-agent-modules/blob/0a61d653a0fd80e3befcb0b65d6b52171d36aad4/sdk/protocol/hvchannel/modules.go#L31-L35). The branch's `writeCoreCondition` serializes that snapshot before looking for the optional field, so it cannot recover a degraded-core condition the SDK has already discarded. Module state/health remain available; a complete current core condition does not. + +The HostWeave companion contract is also still incomplete. This branch registers no `weave capabilities` command, while the pinned HostWeave adapter requires it for Windows and marks failure unhealthy. Shared SDK adoption and binary stdin support do not resolve that admission failure. + +Compared with the inspected [macOS implementation](macos.mdx), Windows now shares the host SDK/guest-core/module composition. Its CLI, REST and MCP exec all use the SDK supervisor path, whereas the inspected macOS REST/MCP exec path uses SSH. Windows REST/MCP capture merged output as a JSON string and an exit code; CLI streams separate stdout/stderr, supports stdin and PTY resize, and propagates guest process exit codes. Base64 stdin does not make the API's output string a binary-safe stream. Windows served HTTP retains bearer-token and Host-header wrappers; macOS's inspected listener has a different authentication policy. Lifecycle defaults, transport, provisioning and publication behavior must each be compared separately. + +The next acceptance evidence must bind the exact SDK, downloaded core/modules, VM artifact and host platform to observed authentication, exec/PTY/stdin/exit behavior, session-dependent capabilities, restart/reconnect, update failures, seal/clone identity and producer-to-consumer boot. This documentation review has not executed those Windows VM paths. diff --git a/docs/products/guestweave/windows.mdx b/docs/products/guestweave/windows.mdx index 6559510..257a283 100644 --- a/docs/products/guestweave/windows.mdx +++ b/docs/products/guestweave/windows.mdx @@ -1,6 +1,6 @@ --- title: 'GuestWeave on Windows: HLD' -description: Windows VM lifecycle, HTTP protection and the current image contract. +description: Host-side HCS VM management, bundled guest agents, transport boundaries and persistent image state. product: guestweave audience: - consumer @@ -10,35 +10,128 @@ owner: ShocOne status: code-inspected verified_at: '2026-10-11' evidence: +- guestweave-windows:go.mod +- guestweave-windows:internal/cli/root.go +- hostweave:agent/runtime/guestweave/capabilities.go +- hostweave:agent/runtime/guestweave/guestweave.go +- guestweave-windows:internal/command/command.go +- guestweave-windows:internal/command/commands_create.go +- guestweave-windows:internal/command/commands_run.go +- guestweave-windows:internal/command/commands_exec.go +- guestweave-windows:internal/command/commands_agent.go +- guestweave-windows:internal/command/commands_oci.go +- guestweave-windows:internal/command/run_clipboard.go +- guestweave-windows:internal/hcs/engine.go +- guestweave-windows:internal/hcs/native.go +- guestweave-windows:internal/hcs/bridge.go +- guestweave-windows:internal/hcs/netmodes.go +- guestweave-windows:internal/hcs/snapshot.go +- guestweave-windows:internal/vm/config/config.go +- guestweave-windows:internal/vm/layout/layout.go +- guestweave-windows:internal/guestweaveagent/client/client.go +- guestweave-windows:internal/guestweaveagent/client/install.go +- guestweave-windows:internal/guestweaveagent/proto/proto.go +- guestweave-windows:internal/guestweaveagent/agent/agent.go +- guestweave-windows:cmd/weaveagent/main.go +- guestweave-windows:cmd/weaveagent/modulebus.go +- guestweave-windows:cmd/weaveguestd/main.go +- guestweave-windows:cmd/weave-gui/main.go +- guestweave-windows:internal/nativebuild/nativebuild.go +- guestweave-windows:internal/nativebuild/assets/weave-agent +- guestweave-windows:internal/guestimage/manifest.go +- guestweave-windows:internal/cli/serve.go - guestweave-windows:internal/serve/http.go +- guestweave-windows:internal/serve/auth.go +- guestweave-windows:internal/serve/mcp.go +- guestweave-windows:internal/serve/vmcreate.go - guestweave-windows:internal/serve/openapi_test.go - guestweave-windows:internal/oci/oci.go --- -GuestWeave on Windows manages local Windows and Linux guests through Windows host-compute facilities. The HTTP handlers adapt the command layer; the runtime implementation and local networking remain host responsibilities. +GuestWeave on Windows is the Windows host implementation of local VM management. The host executable owns VM configuration, disks, image transfers, network setup and lifecycle supervision. It also carries its own guest-agent implementation for selected guest capabilities. Those host and guest responsibilities cross an operating-system boundary even though their source is in the same repository. + +This HLD describes commit `48fe4242d4d8b884e1bc464287f9a6c8f72ca275`. The Windows implementation currently uses repository-local guest agents and a framed JSON module bus. Its guest bus and local Go feature modules have a different implementation and wire contract from the separately versioned [agent-core](../agent-core/hld.mdx) and [agent-modules](../agent-modules/hld.mdx) projects. + +The [development-branch HLD](windows-development.mdx) separately describes `feat/weave-agent-handover-168` at `651ec7d5d0459ece11526c5ee34e0774bc6d2f52`. That branch replaces the legacy guest agents with the SDK/core/module architecture. The design below remains the current `main` baseline; its legacy agent and embedded-payload findings do not describe the migrated branch. -The command layer invokes HCS-backed VM lifecycle operations. HCN provides host networking. Guest execution is a separate boundary from the HTTP entrypoint. +GuestWeave commands drive HCS compute systems and maintain local state. Native Linux execution reaches the bundled guest agent through a host bus client over HvSocket. The guest boundary contains the local agent implementation, while the OCI registry and initiating clients sit outside the host implementation. -## HTTP interface and trust boundary +## Responsibility and process boundaries + +| Component | Responsibility | Implementation boundary | +| --- | --- | --- | +| CLI, WinUI desktop application, HTTP server and MCP stdio server | Adapt user input to command structs and return results. Their exposed operation sets differ. | Host processes; `internal/cli`, `cmd/weave-gui`, `internal/serve`. | +| Command/session layer | Open the per-user VM store, validate VM state, select HCS, and coordinate lifecycle, images and guest tools. | `internal/command`; `openSession` constructs the HCS engine. | +| HCS engine | Create/start compute systems, supervise exits, stop/suspend/resume and construct compute-system documents. | `internal/hcs`; Windows host APIs. | +| Networking | Build HCN/HNS networks and endpoints; use virtual-switch WMI where a mode requires it. Address native private NICs through the guest bus. | Host network state, separate from the VM's guest configuration. | +| Native Linux `weave-agent` | Act as init when PID 1, mount essential filesystems and host shares, configure networking, and serve exec/PTY, shutdown and clipboard modules. | `cmd/weaveagent`, inside the Linux guest. | +| Windows `weave-guestd` | Serve the clipboard module in the logged-in user's interactive session. | `cmd/weaveguestd`, inside the Windows guest. | +| VM store and OCI cache | Persist configuration, VHDX chains, firmware/memory state and cached registry images. | Host filesystem; cache-backed VMs may depend on a cached parent disk. | + +The command layer selects HCS exclusively. A stored VM that names the retired `hyperv` backend is reported as `unmanaged` and refused for lifecycle control; a missing backend field identifies that legacy configuration. References to two engines in older interface comments do not establish a second executable backend at this commit. The `HyperV` field name also persists in the configuration schema: for an HCS VM its `VMID` carries the GuestWeave VM name, from which the compute-system ID `weave-` is derived. + +## Guest creation and boot paths + +Guest creation supports installer-backed Windows/Linux disks and weave-native Linux guests. The create command normalizes a plain `--linux` request with no installation media into a native guest. Windows and unattended Linux installation paths prepare installation media and answer-file volumes; the run supervisor watches an installation through shutdown and finalization. + +The native path uses HCS `LinuxKernelDirect`. Its image directory contains `kernel`, `initrd.img` and `manifest.json`. An initrd-only utility guest runs the bundled agent as `/init`; a rootfs guest attaches a VHDX and boots with `root=/dev/sda` and `init=/sbin/weave-agent`. Without a supplied image, `internal/nativebuild` uses an embedded agent and builder scripts to build/cache the kernel, base distro rootfs and image through a WSL Linux builder. This is a concrete image-construction path in the Windows repository; an automatic call to Imageweave is not present in it. + +Ordinary disk guests follow the UEFI compute-system document path. Windows requires persistent `guest.vmgs` firmware state so the installed boot entry survives. If the file cannot be created, start returns an error that identifies the elevation prerequisite. The native Linux path constructs its own document and guest-agent bridge instead of depending on a guest IP address for execution. + +## Lifecycle and durable state + +Creation writes the VM directory and configuration; an HCS compute system is created when the VM starts. Provisioning does not create a persistent VMMS-managed VM. A supervised run holds the VM mutation lock and compute-system handle. A detached run allows the compute system to outlive the initiating process, but operations requiring a live host supervisor have different behavior: standing port forwards, for example, are refused with detached start and require a separate forwarding process. + +| State artifact | Meaning | +| --- | --- | +| `config.json` | GuestWeave VM shape, backend identity, NICs, disks, shares and guest installation/native-boot metadata. | +| `disk.vhdx` and snapshot `.avhdx` layers | Boot disk and differencing chain; the active leaf determines clone/export contents. | +| `guest.vmgs` | UEFI firmware state and, where configured, guest vTPM state. | +| `suspend.vmrs` | Saved memory image. Its presence marks an inactive compute system as suspended; start recreates the system with that image. | +| `snapshots/index.json` | GuestWeave-owned snapshot names, parent relationships and current snapshot. | +| `oci-source.json` | Source image identity and whether the VM disk depends on a cached image. | +| `.lock`, `serial.log`, `clipboard-control.sock` | Mutation coordination, diagnostic serial capture and live clipboard-policy control. | + +HCS snapshots are implemented by GuestWeave with differencing disks and optional saved memory. Creating a snapshot of a running VM saves its memory, terminates the compute system and leaves the VM suspended; it does not leave that VM running. A stopped VM produces a disk-only snapshot. Resume recreates the compute system and consumes its pending saved-state file. + +## Guest RPC and the separate SSH tool path + +The resident guest bus uses four-byte big-endian length prefixes, JSON request/response envelopes and module-specific data frames. Host `client.Dial` addresses the HCS runtime GUID and HvSocket service corresponding to AF_VSOCK port `2010`. It sends the agent hello and requires the exact `weave-guestd/1` protocol version; a mismatch closes the channel and reports that the agent needs redeployment. Local module registration is a Go registry, rather than a separate module process or SDK gRPC server. + +Native execution follows this path: + +1. `ExecCommand` resolves the VM and requires a running native Linux guest. An ordinary disk guest is directed to SSH instead. +2. The HCS engine opens the guest bus and dispatches an `exec` module request. Execution stdio travels on additional vsock streams; PTY execution adds resize handling. +3. The local `weave-agent` starts the process inside Linux. The host returns guest output and exit status separately from a transport failure. Native graceful stop also requests shutdown through this agent before escalating to termination. + +The Windows resident agent registers only the clipboard module at this baseline. Clipboard supervision can deploy the embedded `weave-guestd.exe` over SSH/SFTP, register its HvSocket service with administrator rights, and create a per-user logon task. A Windows service in session 0 would see a different clipboard, so the resident agent runs in the interactive user's session. A guest waiting at logon and a build missing its embedded agent are distinct reported states; clipboard failure does not itself prevent the VM from running. + +The CLI's `agent install`, `agent call` and `agent tools`, also exposed under `/weave/vms/{name}/agent`, are another path. They reach a running guest over SSH, upload a caller-supplied binary as `guestweave-agent`, and invoke its `version`, `call` or `tools` commands. This path does not dial the resident framed bus and does not resolve an agent-core release. The provenance and compatibility of that supplied binary require separate verification. + +No import, module dependency or bootstrap invocation of `weaveplatform-agent-core` or `weaveplatform-agent-modules` was found in this Windows baseline. A platform diagram must therefore represent these repository-local guest agents explicitly; the presence of the names `weave-agent`, `module` or `proto` does not prove integration with the separate projects. + +## Networking and trust boundaries + +NIC modes are `nat`, `bridged:`, `internet:`, `host-only`, `isolated`, `vm-lab` and `none`. They have different reachability. Bridged guests lease from the LAN; private segments lack a host port and a DHCP server, so native NIC configuration is pushed over the guest bus. Host network setup and guest addressing are separate steps. Some network-setup failures are reported while allowing a VM to start without a NIC; private-address push is also best effort. A successful start alone therefore establishes neither network reachability nor guest-agent readiness. -VM routes are mounted under `/weave/vms`. The inspected server includes start, stop, suspend, guest execution, disk and snapshot operations. Liveness is at `/healthz`; the contract is available at `/openapi.json`. +HTTP defaults to `127.0.0.1:8080` and adapts a subset of command operations, including VM lifecycle, snapshots/disks, native exec and the SSH tool path. `ListenAndServe` wraps the router with a persistent bearer token and an address-literal/localhost Host-header guard. `/healthz` and `/openapi.json` bypass the token check but remain behind the Host guard. `NewHTTPHandler` itself has neither wrapper. The token check authenticates API requests; the Host check limits browser DNS-rebinding exposure. MCP is a separate stdio JSON-RPC entrypoint with the privileges of its host process. -`ListenAndServe` loads or creates an API token and wraps the handler with bearer-token and host checks. The bare handler constructor does not apply that protection itself. Integrations mounting the handler must preserve the wrappers. +## Image transfer and external compatibility -This distinction belongs in infrastructure and embedding guides because inspecting only the router would miss the server’s authentication boundary. +The OCI path publishes a flattened VHDX and GuestWeave VM configuration. Push requires the VM to be stopped and clears the exported host VM identity. Pull normally caches images by manifest digest and creates a differencing child; `--flatten` and `--no-cache` select independent-disk paths. Moving or reclaiming a cached parent must account for dependent VMs. -## Images and state +The media types are `application/vnd.guestweave.vm.config.v2+json` and `application/vnd.guestweave.vm.disk.v2.vhdx+zstd`, with chunk offsets addressing VHDX file bytes. The native kernel/initrd image directory is a separate boot input. The inspected OCI push code does not package that directory, so transferring a VHDX artifact does not establish portability of all native boot prerequisites. -The current Windows OCI implementation names GuestWeave VHDX v2 configuration and compressed disk-layer media types. VM identities are handled as part of the implementation’s image/lifecycle behaviour; do not infer interchangeability with the newer shared Weave raw-chunk contract. +HostWeave's GuestWeave runtime adapter invokes a CLI process. That integration requires the selected Windows command vocabulary and output to match the adapter's expectations; it does not replace the bundled guest protocol with agent-core RPC. Shared raw-chunk images, Imageweave-produced native artifacts and the resident guest-agent protocol need explicit compatibility checks before a cross-product execution guide can claim they work together. The [compatibility register](../../platform/compatibility.md) records these boundaries. -Disk and snapshot operations change persistent host state. Future task guides must identify the VM, expected state, recovery behaviour and cleanup procedure for the selected release. +There is already a concrete companion mismatch at these pins: HostWeave requires `weave capabilities`, but this Windows CLI does not register that command. HostWeave therefore marks its Windows GuestWeave driver unhealthy before workload execution. That gap requires an implementation change or a different verified companion version, in addition to runtime acceptance. -## Orchestration integration +## Embedded artifact provenance -HostWeave invokes the Windows GuestWeave CLI through its VM runtime adapter. CLI version and output compatibility are separate from the HTTP schema. +The native builder embeds a checked-in Linux binary from `internal/nativebuild/assets/weave-agent`. Its Go build metadata identifies `github.com/deploymenttheory/guestweave-windows/cmd/weaveagent`, revision `3c7d4dfe09303bceea9d30801af5730d164929dd`, and `vcs.modified=true`. The inspected product source is at a different revision. This metadata does not prove that the embedded payload was built from the current source or from a clean, reproducible checkout. The source-level guest behavior above must be checked against the exact embedded payload before a release guide treats it as runtime verified. -## Verification and limitations +## Verification scope -The repository includes a router/spec parity test. This HLD follows the inspected code baseline; it does not claim a Windows-host acceptance run. The [compatibility register](../../platform/compatibility.md) records the shared-image-format gap. +This review traced command dispatch, HCS documents, both resident guest implementations, their host client/framing, the SSH tool path, persistent state and OCI transfer code. Router/spec parity tests are available in the product repository. No Windows-host VM acceptance run was performed for this HLD. Boot, suspend/resume, live snapshot, network modes, clipboard deployment and each claimed cross-product combination still require runtime acceptance against the selected artifacts. diff --git a/docs/products/hostweave/hld.mdx b/docs/products/hostweave/hld.mdx index e7db49f..cd7fbcf 100644 --- a/docs/products/hostweave/hld.mdx +++ b/docs/products/hostweave/hld.mdx @@ -1,6 +1,6 @@ --- title: 'HostWeave: HLD' -description: Control-plane interfaces, host agents and the GuestWeave execution boundary. +description: Control-plane scheduling, capacity providers, host enrollment and workload execution backends. product: hostweave audience: - developer @@ -9,40 +9,126 @@ owner: ShocOne status: code-inspected verified_at: '2026-10-11' evidence: +- hostweave:internal/app/app.go +- hostweave:internal/cli/server/build.go - hostweave:internal/server/server.go +- hostweave:internal/config/config.go +- hostweave:pkg/broker/broker.go +- hostweave:pkg/broker/place.go +- hostweave:pkg/broker/lifecycle.go +- hostweave:pkg/broker/sweep.go +- hostweave:pkg/provider/provider.go +- hostweave:pkg/types/state.go +- hostweave:api/proto/agent/v1/agent.proto +- hostweave:internal/agentgw/gateway.go +- hostweave:agent/agent.go +- hostweave:agent/identity.go +- hostweave:agent/supervisor/supervisor.go +- hostweave:pkg/runtime/runtime.go +- hostweave:internal/cli/agentcmd/vms.go +- hostweave:internal/cli/agentcmd/containers.go - hostweave:agent/runtime/guestweave/guestweave.go +- hostweave:agent/runtime/guestweave/capabilities.go +- hostweave:agent/runtime/guestweave/registry.go +- hostweave:agent/runtime/vmguest/transfer.go - hostweave:pkg/images/vm.go +- hostweave:go.mod - hostweave:Dockerfile +- guestweave-windows:internal/cli/root.go --- -HostWeave coordinates work across hosts. Its control plane handles product requests and placement; a host agent executes the assigned work through a runtime adapter. GuestWeave owns VM lifecycle on the local machine. +HostWeave schedules jobs onto enrolled devices or provisioned cloud capacity. It owns placement, attempt state, capacity reservations and recovery. A separate `hostweave-agent` process runs on each execution host and supervises the selected runtime. GuestWeave is one VM backend available to that host agent. - -An operator calls the control plane. The host agent initiates its connection and receives work leases; the GuestWeave adapter executes subprocess commands against the local VM manager. + +Clients submit work to the control plane, which persists job state and queues provider actions. The host agent initiates a bidirectional gRPC session to the gateway. Its supervisor calls either the GuestWeave CLI or another configured execution backend. -## Control-plane interface +## Processes and ownership -The server mounts generated OpenAPI handlers on a Chi router. API requests pass through authentication, request limits, observation and operation middleware; authorisation is enforced through the server’s policy layer. Streaming job events, logs and platform changes are registered alongside the generated routes. +| Component | Runs where | Responsibility | +| --- | --- | --- | +| `hostweave` | Control-plane machine or container | HTTP API, agent gateway, broker, SQL queue workers, provider actions, autoscaling and retention tasks. | +| `hwctl` and web UI | Operator client | Call the HTTP API; they do not perform placement or execute workloads locally. | +| `hostweave-agent` | Execution host | Enrollment, host/runtime fingerprints, connection recovery, workload supervision, logs and artifact upload. | +| Runtime drivers | Inside the host agent | Implement `Prepare`, `Start`, `Wait`, `Stop`, `CollectArtifacts` and `Destroy` for their backend. | +| GuestWeave `weave` CLI | macOS or Windows execution host | Manages local VMs and their guest execution channel when the GuestWeave driver is selected. | -Signed-URL blob transfers use a distinct authorised transfer boundary. They bypass JSON request limits and API middleware by design. Deployment and integration guides must distinguish API credentials from signed artifact URLs. +The composition root is `internal/app/app.go`. It wires the API, broker, SQL-backed queue, provider manager, provisioner, durable provider actions, agent gateway and autoscaler. These are components of the server process, rather than separately deployed services implied by their package names. -## Host execution +Capacity providers and workload runtimes have different jobs. The built-in devices provider exposes enrolled machines; AWS, Azure and GCP implementations acquire or manage cloud capacity. A provider supplies a host or provider-native image reference. A runtime executes the workload on that host. Installing GuestWeave alone does not enroll the host in HostWeave. -The GuestWeave adapter supports macOS and Windows hosts. It invokes `weave` as a subprocess and interprets VM information and execution results. It supports an optional configured binary checksum; checksum verification is not unconditional in the constructor. +## Submission, placement and recovery -Readiness and guest execution capabilities are checked independently of successful process creation. A working CLI invocation alone does not prove that an assigned guest workload is ready. +The API passes job submission and cancellation to the broker. The broker persists each job transition together with the queue task that acts on it. The successful job path is: -## Storage and deployment +1. A job enters `queued`, then `evaluating`. The candidate source combines existing capacity, suspended hosts and provider offers. +2. The scheduler evaluates candidates using the job's placement profile, resource requirements, allowed locations, estimated duration and remaining budget. +3. The broker rechecks the proposed placement inside a transaction. It verifies job and host state, remaining host capacity and pool limits, reserves capacity with a versioned update, records an attempt and queues provisioning. A stale plan is retried against fresh state. +4. The provisioner resumes or acquires capacity when necessary and dispatches the assignment through the agent gateway. Job state moves through `placed` and `provisioning` to `running`. +5. Heartbeats extend the current attempt's lease. Workload exit starts `collecting`; the final result becomes `succeeded` or a terminal failure. -The control plane receives configured store, blob and secret implementations. The repository’s Docker build packages the service, CLI and web UI into a non-root runtime image. Its non-loopback API defaults refer to mounted TLS certificate and key files. +`failed`, `cancelled` and `timed_out` are separate terminal states. Recovery is conditional: the failure class, `max_attempts` and `retry_on_exit` determine whether another attempt is allowed. Capacity, quota, bootstrap, preemption and host-loss failures can trigger failover; ordinary workload exits require the job's retry setting. A sweeper recovers expired attempt leases. This is a persisted attempt lifecycle, not a claim that arbitrary jobs execute exactly once. -Those files are evidence for a deployment guide, not a complete production installation procedure. Certificate lifecycle, durable storage, migrations, backup and recovery require a tested environment-specific guide. +## Host connection and security boundaries -## Image compatibility +HostWeave has two network contracts. The operator contract is an HTTP API generated from its OpenAPI definition. The host contract is the repository's own `AgentService` gRPC protocol. -The inspected parser recognises GuestWeave VHDX v2, Tart and Lume artifact formats. Do not describe all shared Weave OCI images as admitted by this version. See the [compatibility register](../../platform/compatibility.md). +Enrollment exchanges a join token and CSR over server-authenticated TLS with a pinned gateway CA. After enrollment, `Renew` and the bidirectional `Connect` stream use mutual TLS. The host agent initiates the connection; assignments, cancellation and upload grants travel down that stream, while fingerprints, status, heartbeats and log chunks travel back. A host agent is therefore not an inbound VM API listener. -## Evidence baseline +The HTTP router applies request limits, observation and bearer API-key authentication before generated handlers. Cedar authorization and strict operation middleware enforce policy checks; public health/bootstrap operations and authentication-only operations are explicit exceptions. Mutations and denials feed the audit trail. Host enrollment identity, operator API credentials and registry credentials are separate credentials with separate consumers. -This HLD is code-inspected at the recorded `main` commit. Private server, runtime adapter, image-parser and Docker evidence is retained in the internal knowledge base. A cross-product acceptance run remains part of the next content milestone. +Artifact upload and download use signed URLs. The filesystem blob route bypasses the JSON API body limit and API authentication middleware because its handler verifies the transfer signature. Remote object stores supply their own signed transfer URLs. This boundary allows artifact-sized transfers without treating an artifact URL as an operator API key. + +## Host runtime selection + +The agent offers drivers according to host configuration and platform. The supervisor selects the first healthy configured driver that supports the workload kind; driver names do not describe separate agent processes. + +| Driver | Backend and availability | +| --- | --- | +| `guestweave` | Local `weave` subprocesses on macOS and Windows. `HOSTWEAVE_GUESTWEAVE_BIN` selects the binary; `HOSTWEAVE_GUESTWEAVE_SHA256` optionally verifies it. | +| `qemu` | QEMU VM execution when `HOSTWEAVE_QEMU_IMAGES` supplies configured base-image mappings. | +| `moby` | Docker/Moby container API; its fingerprint reports backend health. | +| `exec` | Host processes, enabled only through `HOSTWEAVE_AGENT_EXEC`; these run directly on the execution host. | + +The supervisor owns per-attempt scratch space, preparation, timeout and cancellation handling, workload probes, output forwarding, collection and signed-URL upload. Once preparation succeeds it defers `Destroy`, including after later failure. Preparation failures remain the driver's responsibility to clean up. A failed runtime, preparation, start or artifact transfer produces a failure report; VM existence is not sufficient for a successful job result. + +## GuestWeave VM execution + +The HostWeave GuestWeave driver depends on the CLI contract, rather than importing either GuestWeave implementation. A workload receives an attempt-specific VM name and follows this lifecycle: + +| Phase | macOS host | Windows host | +| --- | --- | --- | +| Prepare | `weave pull IMAGE`, then `weave clone IMAGE NAME --regenerate-random-mac`. | `weave pull IMAGE NAME`. | +| Configure | `weave set NAME` applies requested CPU and memory. | The same CLI operation applies requested CPU and memory. | +| Boot | Supervised `weave run --no-graphics NAME` child process. | `weave run --detach --no-graphics NAME`. | +| Ready and execute | Probe the owned guest execution channel, then run workload argv with `weave exec`. | Require the companion binary execution capabilities, then probe and execute through `weave exec`. | +| Inputs and outputs | Binary tar streams over non-TTY exec, with SHA-256 checks and hardened extraction. | The same transfer model requires the companion's stdin/binary-stream contract. | +| Stop and destroy | Stop the VM with a grace period, then delete the attempt VM. | Stop, then delete the attempt VM. | + +Capability checks bind supported guest platforms, image formats and binary stdin execution to the companion CLI. An OCI pull also requires isolated registry-auth capability and an environment-aware runner. The driver supplies credentials only to the pull subprocess and clears the stored workload credential afterwards. When a workload carries a resolved image, an unsupported guest platform or format is refused before preparation. + +These checks differ by path. macOS fingerprinting can remain healthy when the capability query fails, while resolved-image admission and resolved OCI pulls require compatible reporting. Windows fingerprinting and binary execution require it. The pinned Windows GuestWeave CLI does not register a `capabilities` command; consequently, the inspected HostWeave/Windows CLI combination is marked unhealthy. The implemented adapter is not evidence that this combination is currently usable. + +The driver checks VM state before guest exec and rechecks it after exit code `2`, because that value may represent either a guest command exit or a CLI state error. Readiness probes require a successful guest command; successful CLI startup does not prove guest readiness. + +`hostweave-agent` is a HostWeave host agent with its own enrollment protocol. It is distinct from the guest `weave-agent` in `weaveplatform-agent-core` and from the Windows GuestWeave native `weaveagent`. The GuestWeave CLI implements the guest-side bridge for its platform. HostWeave's adapter neither imports agent-core/modules nor launches their binaries directly; those relationships are described in the [GuestWeave HLDs](../guestweave/index.md). + +## State, artifacts and deployment + +The control plane supports SQLite, PostgreSQL and MySQL. Its SQL store retains jobs, attempts, hosts, capacity, provider configuration/actions and policy state; queue tasks use the same database. Logs are forwarded through an in-memory server buffer and are also collected as job artifacts. Result retention and queue purging are separate periodic tasks. Artifact blob storage supports filesystem, S3-compatible storage, Azure Blob and GCS. + +Configuration contains secret references, resolved when components are wired. A configured keyring seals credentials at rest. SQL durability, blob durability, encryption keys and the agent CA are separate operational assets and need coordinated backup and recovery procedures. + +The Dockerfile builds the service, `hwctl` and static web UI into a non-root control-plane image. It does not package the host agent or the GuestWeave CLI. Its API listener is non-loopback and expects mounted TLS files; it exposes the agent gateway port, but gateway enablement still depends on configuration. The process default API listener is loopback, and configuration validation requires TLS for non-loopback API listeners. + +## Image and product integration limits + +HostWeave's OCI VM metadata inspector recognises GuestWeave VHDX v2, Tart, Lume chunked and Lume sharded formats. It checks a bounded configuration blob and descriptors; VM disks remain in the registry during this metadata inspection. It does not recognise the shared Weave raw-chunk VM artifact contract at this source baseline. + +Imageweave can export raw or VHDX disk containers for named GuestWeave targets. Such an export is not an OCI conversion to every HostWeave-admitted media type, does not enroll a host and does not prove HostWeave workload execution. See the [compatibility register](../../platform/compatibility.md) before treating producer and consumer formats as interchangeable. + +## Evidence and remaining validation + +This HLD is an implementation inspection of `5080abf8d1a51232246b178d4be34dc9db21e4a0`, not a deployment or cross-product acceptance result. Its private evidence is registered by path and hash in the source lock. The core tracing points are `internal/app/app.go` for composition, `pkg/broker` for transaction and attempt handling, `internal/agentgw` and `agent/agent.go` for the host session, and `agent/supervisor` plus `agent/runtime` for execution. + +A tested deployment guide must still verify enrollment, policy enforcement, workload execution, artifact transfer, timeout recovery and restoration of control-plane state in the chosen environment. Format admission, companion capability reporting and guest boot acceptance require separate checks. diff --git a/docs/products/imageweave/hld.mdx b/docs/products/imageweave/hld.mdx index 46119d5..d06fd58 100644 --- a/docs/products/imageweave/hld.mdx +++ b/docs/products/imageweave/hld.mdx @@ -1,6 +1,6 @@ --- title: 'Imageweave: HLD' -description: Reviewed image inputs, Packer construction and qualification boundaries. +description: Image planning, native and QEMU construction, agent payloads, runtime acceptance and publication boundaries. product: imageweave audience: - developer @@ -9,36 +9,136 @@ owner: ShocOne status: code-inspected verified_at: '2026-10-11' evidence: +- imageweave:internal/cli/root.go +- imageweave:internal/cli/image.go +- imageweave:internal/cli/delivery.go +- imageweave:pkg/catalog/catalog.go - imageweave:pkg/plan/plan.go +- imageweave:pkg/plan/prepared.go - imageweave:internal/packerplugin/builder.go +- imageweave:internal/packerplugin/prepared.go +- imageweave:pkg/nativebuild/build.go - imageweave:pkg/delivery/native.go - imageweave:pkg/delivery/build.go +- imageweave:pkg/macosbuild/build.go +- imageweave:pkg/macosbuild/tools.go +- imageweave:internal/imagebuild/catalogue.go +- imageweave:internal/imagebuild/packages.go +- imageweave:images/catalogue.json +- imageweave:images/packages.lock.json +- imageweave:internal/imagebuild/macos/prepared.go +- imageweave:internal/imagebuild/macos/acceptance.go +- imageweave:internal/imagebuild/macos/recipe.go +- imageweave:internal/imagebuild/macos/machine_darwin.go +- imageweave:internal/imagebuild/linux/linux.go +- imageweave:internal/imagebuild/linux/linux_agent_check.go +- imageweave:internal/imagebuild/windows/windows_agent.go +- imageweave:internal/imagebuild/windows/recipe.go +- imageweave:internal/imagebuild/export.go +- imageweave:.github/workflows/image-native-candidate.yml +- imageweave:.github/workflows/macos-candidate.yml +- imageweave:.github/actions/publish-macos-candidate/action.yml +- imageweave:delivery/macos-candidate-policy.json +- imageweave:go.mod --- -Imageweave resolves reviewed image scenarios and constructs VM artifacts through Packer. It owns construction and its qualification paths; OCI tooling owns artifact packaging and checks at the handoff. +Imageweave produces and checks VM images. It contains reviewed scenario planning, Packer builders, native and QEMU VM adapters, offline agent-package preparation, derived image builders, runtime acceptance and target disk export. Its macOS candidate workflow can also publish an accepted base image. These are separate entry points with different outputs and acceptance requirements. - -Pinned inputs resolve to a plan and Packer template. The native path imports manifest-listed outputs, packs the artifact and performs deep inspection. Runtime qualification is a separate requirement. + +A pinned scenario selects construction. Declared disks and platform state become a checked OCI layout; the selected validator supplies separate runtime evidence. Target export and candidate publication have independent entry points, described below. Construction alone does not grant channel promotion. -## Inputs and planning +## Component and process boundaries -The request names an image family, release, architecture and purpose. Planning validates the relevant input shape, selects the scenario and freezes its template variables. Input hashes and reviewed recipe identity matter independently of filenames. +| Component | Responsibility | +| --- | --- | +| `imageweave` CLI | Select scenarios and invoke planning, delivery, image preparation, validation and export operations. | +| Packer templates and `packer-plugin-imageweave` | Orchestrate native base construction and macOS prepared-image construction; the QEMU template uses the QEMU Packer plugin for Linux bases. | +| Native platform adapters | Restore macOS through Apple Virtualization.framework; install Windows through Host Compute Service on a matching native host. | +| `weaveplatform-oci` library and `weaveoci` executable | Bundle import, disk/state contract, packaging, conformance checks, clone validation and publication/admission mechanisms. | +| Agent package lock and payload preparation | Authenticate and stage selected agent-core and module release installers for a derived image. | +| Runtime acceptance adapters | Exercise the exact candidate using native macOS or QEMU, recording boot, identity, reboot and profile-specific outcomes. | -The resolver branches for native macOS/Windows construction and other scenarios. A resolved plan is a candidate, with qualification explicitly unverified where applicable. Source authentication remains the caller’s responsibility; checking a byte hash alone does not establish who supplied those bytes. +Imageweave links OCI libraries and the agent-modules SDK at versions recorded in its own `go.mod`. Delivery also invokes `weaveoci` as a subprocess. The documentation's independent OCI source baseline is not a substitute for the dependency version that a particular Imageweave binary was built with. -## Native construction +Imageweave's native builders call platform libraries directly. They do not invoke the GuestWeave CLI to create or boot the build VM. Shared native library choices and compatible disk formats establish an implementation relationship, rather than a requirement that GuestWeave be installed to run these builders. -The native Packer plugin implements the builder boundary. Its output manifest identifies the artifacts eligible for import. The construction path invokes Packer initialisation, validation and build, then calls `weaveoci bundle import-imageweave`, packing and strict deep inspection. +## Planning and input integrity -The native `Construction` result contains layout, bundle, manifest and recipe information. It deliberately has no acceptance field. OS install or restore completion is different from first-boot, identity and reboot evidence. +`plan` decodes a strict single YAML request, rejects unknown fields and resolves a family/release/architecture through the compiled catalog. Release aliases are frozen into a concrete catalog selection. `Selection.Template` admits only implemented purpose/target combinations: Linux guest bases use QEMU, Windows guest bases use HCS, and macOS ARM64 bases or prepared images use Apple VZ. A listed matrix entry is not automatically an implemented or qualified build scenario. -## Qualification and publication +Base requests bind the media to a source SHA-256 and build identity. Linux requests also verify local firmware files and require separate SSH key files. Native requests verify local media bytes before building and avoid the Linux firmware/SSH inputs. Prepared macOS requests instead require a pinned parent OCI platform manifest; they reject source media and external SSH keys. -Construction, runtime qualification, authenticated admission and channel promotion are distinct stages. The native construction operation does not publish an image or grant promotion permission. Other delivery paths carry their own acceptance outputs; document each path independently. +A plan returns `qualification: unverified`. Byte verification establishes integrity against the supplied pin. The caller must establish vendor media authenticity and the reviewed recipe before supplying those inputs. A well-formed source URI or recipe commit string does not, by itself, prove that the running tools came from that source. -## Operational constraints +## Native base construction -Build hosts need the chosen Packer plugin, media and runtime prerequisites. Temporary workspace, disk space, firmware inputs and signing/publishing credentials belong in scenario-specific guides. A guide must record the exact build recipe and producer/consumer compatibility. +`delivery-native` resolves the request, creates a new workspace, runs Packer init/validate/build, imports only the manifest-listed outputs with `weaveoci bundle import-imageweave`, packs an OCI layout and performs strict deep inspection. Its `Construction` result carries layout, bundle, manifest and recipe identity with `qualification: unverified`; it has no runtime acceptance field. -Evidence: [plan resolver](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/pkg/plan/plan.go), [native Packer builder](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/internal/packerplugin/builder.go), [native construction](https://github.com/weaveplatform/imageweave/blob/3f81345846d7c64bc1b9c5f8f38949a02ca462c5/pkg/delivery/native.go). This source baseline does not imply an available tagged Imageweave binary release. +The base builder delegates to `pkg/nativebuild`. macOS requires an Apple Silicon macOS host and verifies the IPSW manifest's version/build. It restores a disk and auxiliary storage, records the Apple hardware model and minimum resources, and leaves the base at Setup Assistant. Windows requires a Windows host matching the requested architecture. It uses a private ISO copy and unattended seed, verifies the installed release/build/edition and generalization receipt, and converts the fixed VHD disk into raw guest sectors for the shared OCI bundle. + +Native bases remain sealed. The base Packer communicator rejects guest upload, download and command execution; host-side hooks still run. Normal guest provisioners require a derived-image builder. Native installation or restore completion is therefore distinct from the independent first-boot and reboot evidence required for an accepted image. + +## macOS prepared images and the build runner + +The macOS prepared builder is a separate Packer builder with a live guest communicator. It deeply verifies a pinned pristine base parent, unpacks a private copy, creates fresh VM identity, completes setup, keeps the VM running through Packer provisioning hooks, configures the intended account and automatic login, reboots, inspects the resulting session, then seals and shuts down the image. + +The built-in prepared profile uses the `weave` account, remote login and automatic login. It removes temporary build keys and state during sealing. Prepared images are deliberately different from pristine bases and agent images: the macOS acceptance adapter rejects a candidate containing an agent and checks that the intended profile has no baked agent or build trust key. + +`build-macos`, also exposed through `cmd/build-macos`, coordinates this broader workflow. It locks the workspace, resolves and caches selected Apple media, builds tools from a clean recipe checkout, installs the native Packer plugin, constructs the requested base/prepared tiers, checks the OCI outputs and invokes `image validate-macos` for each candidate. It retains a pinned base parent for the prepared tier. Storage planning and preflight checks happen before large downloads or VM disk creation. + +This runner can produce local runtime acceptance. It does not itself publish to a registry. Its tool setup derives the default `weaveoci` executable from the Imageweave-pinned Go module; an explicitly supplied executable has a different caller-managed provenance boundary. + +## Linux delivery and runtime acceptance + +`delivery` is the Linux path for Ubuntu/Fedora base scenarios. It runs the Packer QEMU template, imports its output bundle and invokes `image validate-linux`. That validator packs the exact candidate, performs deep content checks and boots two fresh clones per requested architecture, comparing guest machine identities. Successful delivery returns an acceptance report and `qualification: local-acceptance`, unlike native construction. + +The macOS validator also exercises two independent unpacked copies of the exact OCI image. It checks OS/build identity, the validation marker, guest startup, shutdown and persistent identity across reboot. Base setup automation is confined to disposable acceptance copies. The prepared profile additionally checks login, administrator membership, SSH, automatic login, desktop session and completed setup. This native validator uses Imageweave's own Apple VZ adapter. + +Agent image validation has additional requirements. `image validate-agent-linux` uses QEMU and the agent SDK/OCI `agentcheck` helpers to check module operations, owned-host trust, foreign-host rejection and reboot persistence. The private test keys remain in the host process. A successful base boot cannot substitute for agent-module or trust evidence, and the presence of validation code is not evidence that any particular image has passed it. + +The compiled release catalog and the transitional `images/catalogue.json` serve different command groups. The latter enumerates base, agent and desktop image definitions. Its matrix describes requested outputs; it does not prove that every definition has an executable builder, a complete package lock or qualifying acceptance. + +## Agent-core and module inputs + +Imageweave's image catalog and package lock pin release assets from `weaveplatform-agent-core` and `weaveplatform-agent-modules`, including platform-specific installers, module binaries, manifests and publisher evidence. `image prepare` verifies that evidence before creating an offline installation payload. Linux and Windows package preparation verify signed checksum bundles; macOS core preparation verifies the Sigstore bundle for its package. Module manifest identity, version, platform digest and size must agree with the lock. + +Linux agent construction starts from a pristine base, boots a disposable QEMU copy, installs the staged packages, stops the agent and removes host-specific trust/store and machine identity before producing a derived bundle. Windows agent construction stages the native installers and uses a native HCS preparation path, with Authenticode and installed version/hash checks in its recipe. Both record pinned parent and package lineage. Their construction inventory explicitly leaves module/channel or two-clone native acceptance pending. + +A macOS native installer recipe is available through payload preparation, including package signature and Gatekeeper checks. That does not imply an equivalent macOS agent-image construction command: the inspected `image` command tree registers Linux and Windows agent builders, while the macOS Packer prepared tier remains agent-free. + +These are agent-core/module release dependencies of Imageweave's derived-image and validation paths. They do not mean that every GuestWeave VM uses the shared agent-core, or that Windows GuestWeave's native `weaveagent` and agent-core's Windows `weave-agent.exe` are the same component. + +The checked-in default `images/packages.lock.json` is incomplete for these installation paths at this baseline. None of its module entries contains the signed checksum evidence required by `RequirePackages`; Linux entries also have no module installer. Consequently, the default `image prepare` and agent construction paths reject that lock before downloading or installing packages. A reviewed, complete replacement lock is a prerequisite. This finding concerns the checked-in inputs; it does not assert that the referenced release repositories lack those assets. + +## GuestWeave and other export targets + +`image export` requires an exact source index digest, guest platform, target and new output directory. It deeply checks the layout, selects the platform and unpacks verified guest sectors before converting storage containers. + +| Export target | Disk container | +| --- | --- | +| `guestweave-macos` | raw | +| `guestweave-windows` | VHDX | +| `aws` | raw | +| `azure` | fixed VHD | +| `gcp` | tar.gz | +| `openstack` | qcow2 | +| `vsphere` | VMDK | + +macOS export is restricted to the Apple host target. Only carried platform state is exported; regenerated VM identity, TPM or firmware state is not carried forward as clone identity. The result records source index/manifest digests and target file hashes with `acceptance: pending`. + +These outputs are target disk files, not a registered GuestWeave VM or a universal OCI image admitted by HostWeave. Export does not call a consumer VM CLI, launch a guest, install cloud guest adaptation or establish provider boot acceptance. A consumer-specific import/launch procedure and a producer/consumer format check remain necessary; see the [compatibility register](../../platform/compatibility.md). + +## Publication and release-channel boundaries + +The general native-construction workflow has read-only repository permissions and preserves diagnostics. It neither publishes nor signs candidates. + +The separate `macos-candidate.yml` workflow has an optional publication path. Its selection job is restricted to `main`; publication also requires a public repository and checks for environment reviewers. A dedicated ARM64 macOS runner builds and accepts a base candidate, then the publication job authenticates to GHCR. The composite publication action verifies the schema-3 acceptance report's tag and exact index digest, calls `weaveoci publish`, attaches build provenance and acceptance attestations, and reads back/verifies the published candidate against the reviewed policy. + +This workflow publishes macOS **base candidates**. Its reviewed policy contains producer and acceptance identities but no release-channel or trust-anchor configuration. Candidate publication is therefore not release-channel admission or promotion. Agent/module release assets and image candidate artifacts also have different release lifecycles. This HLD describes the workflow implementation; it does not assert that the required runner, environment approval, credentials or a successful published artifact currently exist. + +## Failure handling and evidence baseline + +Construction entry points require new output directories, hash input bytes and fail on invalid scenario, host mismatch, failed subprocess, missing artifact or conformance errors. The macOS runner can reuse an existing candidate only after content inspection, completed acceptance and recipe/artifact matching; incomplete or corrupt prior output is rejected. Native and derived VM adapters own disposable VM cleanup, and the macOS runner's workspace lock prevents concurrent reuse. An interrupted workspace needs inspection before removing its lock. Runtime validation records unsuccessful outcomes separately from the construction result. + +This HLD is based on `3f81345846d7c64bc1b9c5f8f38949a02ca462c5`. No VM construction, two-clone runtime acceptance, registry publication or cross-product workload run was performed for this documentation review. The source lock registers the implementation paths inspected for each mechanism. Runnable guides must bind the exact recipe, media/package pins, tool provenance, host prerequisites, acceptance profile and consumer format before claiming an end-to-end working path. diff --git a/docs/reference/index.md b/docs/reference/index.md index 8b0726b..f03b681 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,7 +1,7 @@ --- title: Reference index -description: Find the authoritative product APIs, agent protocols and configuration - contracts. +description: Find the authoritative product APIs, agent protocols and + configuration contracts. product: platform audience: - consumer @@ -16,6 +16,16 @@ evidence: - hostweave:api/openapi.yaml - agent-core:proto/weave/agent/v1/module.proto - agent-modules:sdk/modulesdk/module.go +- guestweave-macos:internal/httpapi/vms.go +- guestweave-macos:internal/httpapi/guest.go +- guestweave-macos:internal/mcp/server_mcp.go +- guestweave-windows:internal/serve/auth.go +- guestweave-windows:internal/guestweaveagent/proto/proto.go +- guestweave-windows:internal/command/commands_agent.go +- agent-modules:sdk/protocol/hvchannel/hvchannel.go +- agent-modules:sdk/weaveclient/weaveclient.go +- guestweave-windows-development:internal/serve/vmcreate.go +- guestweave-windows-development:internal/serve/mcp.go slug: /reference --- @@ -23,12 +33,16 @@ The platform has several distinct interfaces. Generate reference from the owning | Interface | Authority | Validation boundary | | --- | --- | --- | -| GuestWeave macOS HTTP | Product OpenAPI document served at `/weave/openapi.yaml` | Router/spec parity plus request/response conformance | -| GuestWeave Windows HTTP | Product OpenAPI document served at `/openapi.json` | Router/spec parity and bearer-token wrappers | -| HostWeave HTTP | Product `api/openapi.yaml` and generated server | Generated handlers, middleware and semantic tests | -| HostWeave host-agent protocol | Product agent protobuf | Gateway/runtime protocol tests | -| Agent lifecycle and host services | Agent-core protobuf | Buf lint, breaking-change and generation checks | -| Module implementation API | Agent-modules SDK | SDK interfaces and lifecycle/testkit behaviour | +| GuestWeave macOS HTTP | Product OpenAPI document served at `/weave/openapi.yaml` and actual handlers | Router/spec parity is only a starting point; verify SSH execution, stop policy, credentials and completion semantics | +| GuestWeave Windows HTTP | Product OpenAPI document served at `/openapi.json` and served-handler wrappers | Verify bearer token, Host guard, exemptions and operation-specific state rules | +| HostWeave HTTP | Product `api/openapi.yaml`, generated server and middleware | Verify authentication, policy, placement and asynchronous attempt outcomes | +| HostWeave host-agent protocol | Product agent protobuf and gateway/client | Separate enrollment and outbound host session; not agent-core RPC | +| Agent lifecycle and host services | Agent-core protobuf and runtime handlers | Local gRPC; verify generated versions, launch token and lifecycle semantics | +| macOS guest capability wire/client | Core/SDK frame implementations and agent-modules weaveclient/weavewire | External framed JSON; lifecycle Protocol `1` does not negotiate this wire | +| Windows main resident guest bus | GuestWeave Windows `internal/guestweaveagent/proto`, client and registered handlers | Own `weave-guestd/1` hello and module/op frames; separate from core/SDK wire | +| Windows development guest integration | Migration branch's guestchannel, guestctl and CLI/REST/MCP adapters | Shared SDK/core channel replaces the legacy bus; verify surface semantics and branch-specific prerequisites | +| Windows main supplied SSH agent | Product command implementation plus caller-supplied binary | Upload/invoke over SSH; resident-bus or agent-core compatibility cannot be assumed | +| Module implementation API | Agent-modules SDK | Verify implementation hooks, placement, host-service use and capability operations | | Module/channel manifests | Agent-core JSON schemas and parsers | Schema validity, parser rules and verifier checks | ## Available contract snapshots @@ -37,6 +51,14 @@ The [contract catalogue](contracts.md) exposes public-source schemas and protobu Private-product OpenAPI documents remain in the private companion until a publication review approves an export. Their public interface entrypoints are documented above without uploading raw private source files. +## Interface semantics + +macOS CLI exec uses the authenticated SDK/core guest channel; HTTP/MCP exec uses SSH. CLI stop defaults to graceful, while the inspected HTTP/MCP handlers construct a hard stop. The macOS HTTP listener defaults to loopback and has no API authentication in the inspected server. Windows's served HTTP router adds bearer-token and Host-header checks; constructing `NewHTTPHandler` alone does not add those wrappers. See the [macOS](../products/guestweave/macos.mdx) and [Windows](../products/guestweave/windows.mdx) HLDs before using a CLI example as an API equivalent. + +The [Windows development branch](../products/guestweave/windows-development.mdx) routes CLI, REST and MCP exec through its SDK-backed run supervisor. That differs from both Windows main's native bus and macOS's SSH-based API exec; sharing core/modules does not establish complete API parity. + +An endpoint in a schema, a registered route and a successful parity test do not establish its side effects, permission checks, streaming behaviour or readiness condition. This review inspected those implementation paths; it did not execute product API acceptance tests. + ## Next reference work The next milestone adds generated operation pages, examples and configuration tables after semantic cross-checking. CLI reference will use actual command definitions or help output. Request bodies, authentication, error responses, streaming and operation completion must be verified in addition to method/path parity. diff --git a/docs/start-here/index.md b/docs/start-here/index.md index e187f06..ea13415 100644 --- a/docs/start-here/index.md +++ b/docs/start-here/index.md @@ -13,10 +13,11 @@ verified_at: '2026-10-11' evidence: - hostweave:agent/runtime/guestweave/guestweave.go - agent-core:internal/core/core.go +- guestweave-windows-development:go.mod slug: /start-here --- -Weave brings together virtual machine management, workload orchestration, image construction and a capability-based agent runtime. This knowledge base explains the responsibilities of those parts and the interfaces between them. +Weave brings together virtual machine management and guest integration, workload orchestration, image construction and a capability-based agent runtime. macOS GuestWeave composes the host SDK with guest core/modules; Windows main has its own guest agent bus, while its reviewed development branch migrates to the shared architecture. This knowledge base explains their process ownership, interfaces and integration limits. ## Choose your starting point @@ -32,7 +33,7 @@ Weave brings together virtual machine management, workload orchestration, image ## Read the evidence correctly -The initial release focuses on high-level designs. These pages describe inspected implementation at recorded commits on `main`; they do not certify a deployed environment or every host/guest combination. A release tag and an implementation baseline are separate facts. +The initial release focuses on high-level designs. These pages describe inspected implementation at recorded main commits and an explicitly labelled Windows development baseline; they do not certify a deployed environment or every host/guest combination. A release tag and an implementation baseline are separate facts. The [compatibility register](../platform/compatibility.md) names integration limits. Runnable tutorials and infrastructure guides are added when their exact versions and procedures have been exercised. [The roadmap](../development/roadmap.md) records that work. diff --git a/scripts/check-browser.mjs b/scripts/check-browser.mjs index 00a4239..c9dbd2b 100644 --- a/scripts/check-browser.mjs +++ b/scripts/check-browser.mjs @@ -28,7 +28,7 @@ try { const errors = []; page.on('pageerror', error => errors.push(error.message)); const internal = process.env.DOCS_AUDIENCE === 'internal'; - const routes = internal ? ['/','/docs/start-here/','/docs/platform/architecture/','/docs/reference/'] : ['/', '/docs/platform/architecture/', '/docs/products/guestweave/macos/', '/docs/products/guestweave/windows/', '/docs/products/hostweave/hld/', '/docs/products/imageweave/hld/', '/docs/products/agent-core/hld/', '/docs/products/agent-modules/hld/', '/docs/development/architecture/', '/docs/operations/publishing/']; + const routes = internal ? ['/','/docs/start-here/','/docs/platform/architecture/','/docs/reference/'] : ['/', '/docs/platform/architecture/', '/docs/products/guestweave/macos/', '/docs/products/guestweave/windows/', '/docs/products/guestweave/windows-development/', '/docs/products/hostweave/hld/', '/docs/products/imageweave/hld/', '/docs/products/agent-core/hld/', '/docs/products/agent-modules/hld/', '/docs/development/architecture/', '/docs/operations/publishing/']; const results = []; for (const viewport of [{width:1440,height:900},{width:390,height:844}]) { await page.setViewportSize(viewport); diff --git a/scripts/source-refresh.mjs b/scripts/source-refresh.mjs index d5d0779..d9328a7 100644 --- a/scripts/source-refresh.mjs +++ b/scripts/source-refresh.mjs @@ -12,7 +12,7 @@ function api(endpoint, {allowMissing = false} = {}) { const changed = []; for (const source of readYaml('sources/sources.lock.yaml').sources) { if (source.visibility === 'private' && process.env.DOCS_AUDIENCE !== 'internal') continue; - const commit = api(`repos/${source.repository}/commits/main`).sha; + const commit = api(`repos/${source.repository}/commits/${encodeURIComponent(source.baseline || 'main')}`).sha; if (commit === source.revision) continue; const changes = []; for (const f of source.files) { diff --git a/site/sidebars.ts b/site/sidebars.ts index dd791ea..dcd449c 100644 --- a/site/sidebars.ts +++ b/site/sidebars.ts @@ -3,7 +3,7 @@ export default { 'start-here/index', {type: 'category', label: 'Platform', items: ['platform/architecture', 'platform/compatibility', 'platform/glossary', 'platform/repositories']}, {type: 'category', label: 'Products', items: [ - {type: 'category', label: 'GuestWeave', items: ['products/guestweave/index', 'products/guestweave/macos', 'products/guestweave/windows']}, + {type: 'category', label: 'GuestWeave', items: ['products/guestweave/index', 'products/guestweave/macos', 'products/guestweave/windows', 'products/guestweave/windows-development']}, 'products/hostweave/hld', 'products/imageweave/hld', 'products/agent-core/hld', 'products/agent-modules/hld', ]}, {type: 'category', label: 'Reference', items: ['reference/index', 'reference/contracts']}, diff --git a/sources/reviews/pr11-code-audit.md b/sources/reviews/pr11-code-audit.md new file mode 100644 index 0000000..c6f29a5 --- /dev/null +++ b/sources/reviews/pr11-code-audit.md @@ -0,0 +1,55 @@ +# PR11 implementation audit + +Reviewed on 2026-10-11 against eight exact main commits, the Windows development commit and its selected SDK release in [the source lock](../sources.lock.yaml). Three independent reviewers traced macOS GuestWeave, Windows GuestWeave, and HostWeave/Imageweave; the coordinating review traced agent-core/modules and reconciled shared pages and diagrams. The Windows migration received a second review against both implementations. Complete pinned trees were used to trace dispatch, transport, handlers, install inputs and publication workflows. Repository documentation, comments and upstream acceptance narratives were not treated as implementation proof. + +## Corrected relationships and design claims + +| Finding | Corrected documentation | Key implementation evidence | +| --- | --- | --- | +| GuestWeave was reduced to host VM management, obscuring guest integration | macOS host SDK and guest core/module payloads are explicit parts of the stack; their locations and independent release pins are documented | macOS `go.mod`, `internal/vm/run/guestchannel.go`, `internal/vm/guestagent/{packages,windows}.go` | +| Windows main was treated as another consumer of the shared core/modules | Main uses repository-owned guest binaries, a local Go module registry and its own framed bus; a supplied SSH tool is another path | Windows main `cmd/weaveagent/modulebus.go`, `cmd/weaveguestd/main.go`, `internal/guestweaveagent/proto/proto.go`, `internal/command/commands_agent.go` | +| Main alone obscured the Windows migration | The separately pinned development branch replaces those agents with a host SDK client, guest core and separate modules; CLI/REST/MCP exec use that channel | Windows development `go.mod`, `internal/guestchannel/channel.go`, `internal/command/{run_channel,commands_exec}.go`, `internal/serve/vmcreate.go` | +| macOS backend descriptions followed stale names/comments | macOS/Linux use Virtualization.framework; Windows-on-mac uses an in-process Hypervisor.framework VMM | macOS `internal/vm/run/{session,windows}.go`, `internal/hypervisor/hypervisor.go` | +| CLI/API operations were described as equivalent | macOS CLI exec uses the SDK/core channel, HTTP/MCP exec uses SSH; stop policy also differs | macOS `internal/command/{commands_exec,commands_stop}.go`, `internal/httpapi/{guest,vms}.go`, `internal/mcp/server_mcp.go` | +| Core's host services lacked process placement and trust semantics | Services run beside core inside the guest/device; local launch-token gRPC and external authenticated frames have different purposes | core `internal/hostserv/hostserv.go`, `internal/supervise/launch.go`, `internal/transport/hypervisor_auth.go` | +| Lifecycle, server liveness and capability health were conflated | Listener/launch/Init/Start order, session waiting, readiness-marker limits, rich/default health and operation acceptance are distinguished | core `internal/supervise/{launch,supervisor}.go`, `internal/lifecycle/lifecycle.go`; SDK `sdk/modulesdk/lifecycle.go`, `sdk/weavemodule/module.go` | +| HostWeave looked like a single GuestWeave execution path | Control-plane persistence/queue/providers and separate enrolled host agent are described; configured QEMU, Moby and opt-in host exec are included | HostWeave `internal/app/app.go`, `agent/agent.go`, `internal/cli/agentcmd/{vms,containers}.go` | +| Companion CLI integrity was unconditional | HostWeave checks the CLI SHA-256 only when configured | HostWeave `agent/runtime/guestweave/guestweave.go` | +| HostWeave/Windows compatibility was merely pending acceptance | Both Windows baselines lack HostWeave's required `capabilities` command; the Windows driver is marked unhealthy | HostWeave `agent/runtime/guestweave/{capabilities,guestweave}.go`; both Windows `internal/cli/root.go` | +| Imageweave construction was shown as GuestWeave orchestration | Native builders call platform libraries directly; SDK/release payloads, target disk exports and consumer admission are separate relationships | Imageweave `pkg/nativebuild/build.go`, `internal/imagebuild/{packages,export}.go` | +| All Imageweave outputs were treated as agent-bearing or construction-only | Base/prepared profiles are agent-free; derived agent inputs, runtime validators and guarded macOS base candidate publication have separate entrypoints | Imageweave `internal/imagebuild/macos/{prepared,acceptance}.go`, `.github/workflows/macos-candidate.yml` | +| Artifact transport implied cross-product compatibility | Shared raw-sector OCI, Windows VHDX v2 and HostWeave metadata formats remain distinct; Windows native boot assets are separate from its pushed disk | OCI `pkg/spec/mediatypes.go`; Windows `internal/oci/oci.go`; HostWeave `pkg/images/vm.go` | +| Clone/publication trust checks were generalised | macOS local template clone checks do not establish that OCI push seals or scrubs the guest disk | macOS `internal/command/{commands_clone,commands_push}.go`, `internal/imagesource/oci/{push,profile}.go` | +| Promotion implied a signed, consumable update feed | Signing is conditional; current module entries contain OCI references, while stock core requires a signed HTTP bundle and HTTP artifact URLs | channels `scripts/promote.sh`, `.github/workflows/promote.yml`, `channels/stable.json`; core `internal/lifecycle/lifecycle.go` | + +The six existing product HLDs and new Windows development HLD explain component ownership, actual request paths, lifecycle/readiness, state/artifacts, trust, failures and evidence scope. Seven existing product/platform diagrams were replaced or corrected, and the Windows development diagram was added. The platform overview, GuestWeave overview, compatibility register, glossary and reference index use the same relationships. + +## Windows development baseline + +The active agent handover is [Windows PR170](https://github.com/weaveplatform/guestweave-cli-windows/pull/170), branch `feat/weave-agent-handover-168`, pinned at `651ec7d5d0459ece11526c5ee34e0774bc6d2f52`. Its host SDK `v0.2.7` resolves independently to `0a61d653a0fd80e3befcb0b65d6b52171d36aad4`. This is implemented architectural convergence on an unmerged branch, not a released parity claim. + +The [development HLD](../../docs/products/guestweave/windows-development.mdx) traces the owner-only supervisor pipe, authenticated HvSocket channel, reconnect/reauthentication, package resolution and verification, native systemd boot, operation defaults, upgrades, seal/clone and OCI transport. Static review found remaining limits: + +- HostWeave's companion `capabilities` command remains absent. +- SDK `v0.2.7` omits core condition from its registry snapshot; serializing it cannot recover the discarded degraded-core field. +- Upgrade success checks versions rather than module health; missing modules are not selected. Windows installer success can mask per-package failure. +- Fedora's unattended path lacks the agent bundle, and generic Linux bootstrap uses Debian packages. +- OCI push retains a disk/config contract without automatic sealing, guest trust scrubbing, native kernel packaging or UEFI boot-state portability. +- Windows REST/MCP exec uses the SDK, whereas inspected macOS REST/MCP uses SSH. Transport convergence does not establish identical authentication, lifecycle defaults or binary streaming. + +Source refresh now resolves each entry's declared branch or tag, preserving these separate baselines rather than comparing all entries to main. + +## Source and artifact gaps that remain + +- **Imageweave default agent inputs:** the checked-in package lock lacks required signed module checksums; Linux module installers are also absent. `RequirePackages` rejects those inputs before default preparation/agent construction. This does not establish whether upstream releases have the required assets. +- **Windows main embedded native agent:** Go build metadata reports revision `3c7d4dfe09303bceea9d30801af5730d164929dd` and `vcs.modified=true`, rather than the inspected product revision. The checked-in binary's SHA-256 is registered separately; current source is not proof of that payload's behaviour. The development migration removes this payload. +- **Installed release baselines:** macOS host SDK, guest installers, generated protocol source and channel verification tooling select different versions. No platform-wide version or release-binary equivalence is asserted. +- **Consumer acceptance:** no real-machine product build, VM boot, module operation, API acceptance, registry publication or cross-product workload was performed for this audit. Runnable guides still require exact artifact and environment acceptance. Format admission and native boot prerequisites must be checked independently. + +## Reviewable evidence + +The source lock contains 232 inspected, checksummed files across ten immutable baselines in eight repositories. Their hashes were checked against the complete pinned trees. All 48 pinned product-source links in the site documentation resolve to registered paths at the declared commits, and their line anchors fall within the source files. Those checks establish reference integrity; reviewers still assess whether each claim follows from the implementation. + +Private source is represented by paths/checksums and reviewed public explanations. Raw private contracts and payloads were not added to this public repository. Public publication review remains part of PR review. + +Diagram delivery and browser receipts, actual screenshots and site verification results are retained as build artifacts. Automated diagram checks establish artifact/containment behaviour; image review separately assesses legibility and composition. See the PR validation results for the final head commit. diff --git a/sources/sources.lock.yaml b/sources/sources.lock.yaml index f706dd9..00cb539 100644 --- a/sources/sources.lock.yaml +++ b/sources/sources.lock.yaml @@ -30,6 +30,30 @@ sources: snapshot: sources/contracts/agent-core/schema/channel-manifest.schema.json - path: internal/transport/transport.go sha256: 45038435a23d032a6e4459c33bcad775e49b6029c7f149844501c8e78688b408 + - path: internal/weaveboot/weaveboot.go + sha256: c8f98db2fca0639976b7e3590e6fea59da3d9e2f908051ca2719300a25271ef8 + - path: internal/supervise/launch.go + sha256: cfe9fc48d6480af71229aff83ac56d7580056b13b1f7f9a89ad5ea600be3fddd + - path: internal/supervise/supervisor.go + sha256: a960b18e26fa7bdb2185d615ba2ebeba3a0d69981e27c3507d1441fce42b66f5 + - path: internal/hostserv/hostserv.go + sha256: 15e8feda5c30f1840cd81079b3b55cab973ef0b5b6a56cfc011620d9dcfc45a3 + - path: internal/transport/hypervisor.go + sha256: a1f96ce3b1e8a4d0fd61acf9e579188d9e309cc6df27f3558bb8c71d102e8d2a + - path: internal/transport/hypervisor_auth.go + sha256: 6bb3f15dedb788f47922312993e38d1a54f1e01ccdcc91a6a22aaaa0f4771a06 + - path: internal/protocol/hvchannel/hvchannel.go + sha256: 7eb9484159500aa1abbafe75a37436b2612963512f3b76af52e6eecf2c6dc8f9 + - path: internal/lifecycle/lifecycle.go + sha256: 9c0fb535fae17f36bcb359bd7305c7f7197e769533cb04cac60b16f588c43143 + - path: internal/layout/layout.go + sha256: 7ef43b462bae0ac8066fb6db6e95bb965c5116f069b7d8aa8442db6d27398194 + - path: internal/capability/channel.go + sha256: 451c76e04b5bf48abfdc76123656cff387350ad26586103d52a9e3998a1e8105 + - path: internal/verify/release_linux.go + sha256: f43b18bbe522d507a0e1731c15365ebcfea754a8c99a9fcb4fdfd0c917b6daaf + - path: internal/controlsock/authorize_unix.go + sha256: e97dca7371e96b7c3633288fb56260f75f82a595ee4438a3688760c5e2a7574b - id: agent-modules repository: weaveplatform/weaveplatform-agent-modules revision: 733ce935024428cadafc68957d2d49419c28d4fb @@ -47,6 +71,26 @@ sources: sha256: 11ef3020fd8f26c94d86e77b8f7affb9cfc742fa665e86ce006d2b5dc6294cd6 - path: go.work sha256: e4c4afa9fb7d58da0fcec6ff00d39c2e3ab6bd89327924305db76ba852e0497f + - path: sdk/weaveclient/weaveclient.go + sha256: 9a73b55a75e4182396f187dc9f4de4e9e1f57297ed82e3d5b96aa374dd583365 + - path: sdk/modulesdk/lifecycle.go + sha256: bd3858365e8e3727a710d401e9db96fd1709adb83a2557ae1726ce781cba0ebb + - path: sdk/modulesdk/hostclient.go + sha256: 7ebd8d67b5e0e9d07ce8ea2b9199ae573cf0e85f20d87ba7231262f06be6b6ea + - path: sdk/weavemodule/module.go + sha256: b1fcaa1c3185ab4a167cc4b136a1ba1d9562acc61b1d958372c72a10b96902c0 + - path: sdk/weaveclient/auth.go + sha256: 430ca5642c87f97881a116ae6e73bc79dde3b591fc3bfca30cd29a72d63dea1c + - path: sdk/weavewire/capability.go + sha256: f692b9cfe75e652da522f97300baf98401cf940e56b9ef7f8c2be861f91e4010 + - path: modules/weave-linux-exec/main.go + sha256: 00dac17a32f0c811de478a86ae195c9f9cc7af4bf0bc5ff6dbd07c4a63d527ab + - path: modules/weave-linux-clipboard/module.manifest.json + sha256: 8c04263046661ea86c73a62d99d17ca302083c6f198d62926fc5a41483695212 + - path: sdk/protocol/hvchannel/hvchannel.go + sha256: 0cee3b59c32c345a9b0c1c865ee17b66897c17758bd25fbbda50f45fbb089126 + - path: sdk/weaveclient/transport.go + sha256: 7c99b1d3f15918c2cc4e62a1987ba70b8285f83434584932da2cc63bcdbcca2b - id: hostweave repository: weaveplatform/hostweave revision: 5080abf8d1a51232246b178d4be34dc9db21e4a0 @@ -68,6 +112,46 @@ sources: sha256: 155ed8f348e4b2f47db5401f003198f5a587f753cc580f28d23c725f898d6a15 - path: api/proto/agent/v1/agent.proto sha256: d2717ee169736cd3c23f576f22f5b5ac22499bd0f84231eaafd8d5f4d9979493 + - path: internal/app/app.go + sha256: 78d5d374a759760dfc3cf560479019348e27c0a0f348a361eafed927b7e50a20 + - path: internal/cli/server/build.go + sha256: 9991a2a9e7085f64bff29acf3781b00a51cb99bfb5172f32378905b93f77dbc6 + - path: internal/config/config.go + sha256: f2a370107723b1deaad7dadc33d633905a39cc5d561b7c03f2655f6971361560 + - path: pkg/broker/broker.go + sha256: 6bbbd0277ec6c7ced27a8de2c550fb8f373ff6976e8e1b028af13c9453ac9ba7 + - path: pkg/broker/place.go + sha256: 20ead320de065515e8b3f6def6ce0cf9f96c92bf5c622097d245258c88f0a1cb + - path: pkg/broker/lifecycle.go + sha256: 54e714bb3aa190c6236378e020127243eae9e546fead5caf8cac561a850cbabb + - path: pkg/broker/sweep.go + sha256: 4fa3469a0154b1768403e1a56824628d398af92c8e9c7c8544f04b1cae356629 + - path: pkg/provider/provider.go + sha256: 9f700366cf518d1de1dec6d1f8d991af8bf64927d601ea01540ea352530fe8cd + - path: pkg/types/state.go + sha256: 63f02451f3dfb1d07c5a712d4ca8b72a0ffe6fd73568b90e147d0e5985d17f78 + - path: internal/agentgw/gateway.go + sha256: 9d2bb6251c502aa5bc07c7b04f779f4af895d19edf9e8c73fc035d8de7949421 + - path: agent/agent.go + sha256: e32faa42eeea3756ca9386c059ba1e2c4705c5272adae45586ae155255984f02 + - path: agent/identity.go + sha256: f67e9b244211fa220bc98091f58ea5377e3aa3a0cc09202910dd0ad224dd43ee + - path: agent/supervisor/supervisor.go + sha256: 9ccd6f0976b50d9080c247119b3f0be72370de2e10cfdc848edff7e4dd2151e9 + - path: pkg/runtime/runtime.go + sha256: 16f41481ca9028d0b89d58b54e42a0b85e5586a7601ec58ff326ef06da046d87 + - path: internal/cli/agentcmd/vms.go + sha256: 933b684f95f9a180d8558b37250c91e542ca30f5fce38bc80d41cbca0c27ee29 + - path: internal/cli/agentcmd/containers.go + sha256: 6ef0246539766b84478bc3ae204a5ca44bcba0227fbd8f2398ff92d8d744feb0 + - path: agent/runtime/guestweave/capabilities.go + sha256: 3d1473e79c7fa537efb250d814ee31549113f5d4caac850e4b124914e52c6862 + - path: agent/runtime/guestweave/registry.go + sha256: 55b61d059be89bbe51e7f709306aead7bd3dcd2efd902bafefd5eda29ee7d7ea + - path: agent/runtime/vmguest/transfer.go + sha256: 6f595b6955275b0bcfbcc6a334dab9a419197a21ba43fed5597e54a5c2c911ae + - path: go.mod + sha256: cd6e6ef19641e677861fba54a52320a8c804f1487d92df1318d6c3b2ecaed504 - id: guestweave-macos repository: weaveplatform/guestweave-cli-macos revision: f77fdaafd218cfa55b12dc1d4e814d4e2d874e46 @@ -83,6 +167,84 @@ sources: sha256: 2ac5f20c2acf317b8da426f83c3c75decbbeb96504805ee2d90b1d677325d867 - path: go.mod sha256: 40fbfb0b50feab9bd800a224aa0dd3a0f9ffe1a0b62f73f8361af49bffe500e4 + - path: main.go + sha256: ca08ebb110dbaee3145fdcc30e99eec2c4acfffdb80e41b06f9a4650268a9a39 + - path: internal/cli/root.go + sha256: 18bf935040c9c83738dc3b05445674fc5f22cad1334b7ef44d9ef4ae40098c52 + - path: internal/cli/serve.go + sha256: ea4a955bf27e419bb126996f1e330e400025e9fd02f8c2452b16c35a1387f469 + - path: internal/cli/stop.go + sha256: 1371fd77dde6bb8f911309941ba747f9bf7714957184d41b36f079d317a310c9 + - path: internal/serve/serve.go + sha256: 74f5788c39305f1caf3e3852c57f683f5f0766b446de0fcd87d8e4e0bcfa7096 + - path: internal/httpapi/vms.go + sha256: f85678d3d0d6d1a5131567175626ef46ec36d62277671fa324bcf90bcd1fba7c + - path: internal/httpapi/guest.go + sha256: 4e0c148456161783ba3cde92c5724c99a10cec36269780bdd9db308cbb5a8c64 + - path: internal/httpapi/ws.go + sha256: 154f0e1537b7c9761780028687a692bf5caeb3107dd137570c60cffdb1758a36 + - path: internal/mcp/server_mcp.go + sha256: c69e9281169a2eff4d69f649a637deac09de6676648ec1dde516ab07d0ff78dc + - path: internal/vm/service/service.go + sha256: 97a8a4724976654fa1b9c153ab7a9ed33d0671eca979dff03f4ee360ca8c96e6 + - path: internal/vm/run/session.go + sha256: 4c09cb590a9e8f764ee83dddce646317b22c7bd94dc6fdea8bcae2398335bc6a + - path: internal/vm/run/windows.go + sha256: dc0028244cbbabe26018662556a7ba3029d1a14b6a5bac1b9c9960a370835235 + - path: internal/vm/run/guestchannel.go + sha256: 6b759e3ff07f75281c26eb8de384dc54fbee9a596d0ab5017b2a166d229c1ab5 + - path: internal/vm/run/clipboard.go + sha256: b88ec5f8ee37bd07381983f31373cf3f1ab7f80de1ccc23f75ba8be2e6768775 + - path: internal/vm/run/firstboot.go + sha256: 6d977ee335cec655ba9cee73ec035251742beabe6ab985d01daab1d2b62b3a87 + - path: internal/vm/guestctl/socket.go + sha256: e3c675c39f1b2254a50fb2bc896a93c4155f94830011e4ea657519149880a07e + - path: internal/vm/guestagent/packages.go + sha256: ad77d7fea53c840a60e8826bb3394dcbb1cc259f0e00212ebcc5cfe255a42582 + - path: internal/vm/guestagent/windows.go + sha256: e2e8ff4561e04908a65bdebd84869913e6220ebec84e994eb88b59345ce48e10 + - path: internal/vm/guestagent/install.go + sha256: d4d67cae0e84c1ebe8193eaba3407403c18b287164e3bb3d33050cabffcc6468 + - path: internal/vm/guestagent/resident.go + sha256: 2868c7cb7055f4ae08eec31c1c1c45bc0c652d9c47cdb49671ee22dcb1389b05 + - path: internal/vm/channelkey/channelkey.go + sha256: a301072394771d9d24fa56d5f40085e0083b4c078d92e997de4d1f59fde3052c + - path: internal/vm/layout/layout.go + sha256: 7b7c2bd9bd2cfec9bf78f27bd4ecfc23562aee8613327570e50e51c7e5453a96 + - path: internal/hypervisor/hypervisor.go + sha256: ada7a8dd2d9d5e2be77591c49c9a4562e2d33448ccafd4a91fd93beef7c8f2da + - path: internal/hypervisor/bootfirmware.go + sha256: 02e7521e0053debd9b6f35ecbfc23b39837a97a6e4f8f93b8a0046a598014721 + - path: internal/hypervisor/netbackend.go + sha256: 6672eda9419c87dab4fb8c85300f4bc5a01cad3bf5a72723b719f3612cb3df34 + - path: internal/hypervisor/hardware/net/softnet.go + sha256: aa4e69f7e3ed9071e777ce28ff7c763bff0ed685c52383929e397c631a86cb17 + - path: internal/network/network_shared.go + sha256: 9c0bbc3f4e2ce913a3c93f6187da2bff72cbace71d9c249483597a0b55d064fc + - path: internal/config/config.go + sha256: 926147af40c4a97f51c436aa1a6bb7aa3472f53da200a258509da10826a3d263 + - path: internal/command/commands_exec.go + sha256: da2b99a668903e20717d36b027a84f9d15a66b1d4778acb11534a69cdfc5c5da + - path: internal/command/commands_stop.go + sha256: fccd19d3879181ef3857fe07286af5ab1c6b323af240a44fed4a07b58282b1e1 + - path: internal/command/commands_shutdown.go + sha256: 50b3af519e11a88fe82146a659ac1dc5a2cdafb4a3582a2ceba6d8d9a7e239e2 + - path: internal/command/commands_restart.go + sha256: cde3456cbe41afa165fdcbc75fccff42184ab600aa5181c765381da0bdbf58b2 + - path: internal/command/commands_clone.go + sha256: e8fa3ad74be10d8d9849fca3d55817d8d3488082a047f6a06625645089a592a0 + - path: internal/command/commands_push.go + sha256: 01794e9ac34ace49d6f66f6e96122ad2207f3f4024060c6486f2e957cf588ba8 + - path: internal/imagesource/oci/registry.go + sha256: f6f0676a690cad5581a6623219724f743473d988105e0f6003a7dc5a68ff29fe + - path: internal/imagesource/oci/base.go + sha256: bed00c4814d0875914c50a60b6a536c8d2ede1eff7f4fec6146f4b6732f94750 + - path: internal/imagesource/oci/push.go + sha256: c79064920e528d4003b9e924cafcebf40d6c3027d3df89f8df149cc9b2c7f389 + - path: internal/imagesource/oci/profile.go + sha256: 1d59935e5294c705115e0c32e7b3a90bc658c70f95111c62406a1b62070cf442 + - path: internal/ssh/sshclient.go + sha256: ee7179377ebdc8295b272a03d3de9b0007970a261f8bb25da43ab55573a32f7a - id: guestweave-windows repository: weaveplatform/guestweave-cli-windows revision: 48fe4242d4d8b884e1bc464287f9a6c8f72ca275 @@ -100,6 +262,66 @@ sources: sha256: c79db35d029653ab9c855506341df4ffa41b7be68436b8a3b1c761e7e6576101 - path: go.mod sha256: 01fb749536faf3fee2062b2157c64c437ed559e0bf9bc22df6cf8b8136dd9007 + - path: internal/command/command.go + sha256: 9380c6fc16e9979eda495ae64a5f9e8bf2b2ec5d93c101ee285c1dd5377618b4 + - path: internal/command/commands_create.go + sha256: 1a973463c2f61743cb52a6f0d116d641cdff89aa013b1406839afb808f296bf9 + - path: internal/command/commands_run.go + sha256: 2935fcb46baf63f4a43e1d4cfed2db522b5da25a63488d1b25dba56003e0a600 + - path: internal/command/commands_exec.go + sha256: 6cd756ca92922c2bc2ec0048b1d84e7b93e8ac97b97898c99a3d858c300fbd2e + - path: internal/command/commands_agent.go + sha256: d1df19858bc764578f1cff142f10e316946ed07401e2afdad14e1ba10a776e4a + - path: internal/command/commands_oci.go + sha256: 3250a4b4d3262fe9ed3101dc34d2e0dfc40351975af4b2b988c2531aab750886 + - path: internal/command/run_clipboard.go + sha256: a3b02d4f598c2db7d22bbe55ac6b2a024f06abcd90502f53266d8a6c8f872ada + - path: internal/hcs/engine.go + sha256: 4d4ef579cec742f51414c40633b3290ac01a9313084014ed9014f4136a3cac6d + - path: internal/hcs/native.go + sha256: acaecbc72f5473a8239ea87d1cdc93bcb053684878ccee33585622c1de8f440c + - path: internal/hcs/bridge.go + sha256: 980faf0423a373ff4f107d0b2d1eb7e507f91fbaa96bc070da421fc9041fae16 + - path: internal/hcs/netmodes.go + sha256: d858498cddc35717e5f22cdc5cf8d44c5dabf84da8bbb2a58970d3af1f5bd2cf + - path: internal/hcs/snapshot.go + sha256: 247eaa9ec4fccd583a0aab672141d40c2a0a92995221757484152ea8dbabb558 + - path: internal/vm/config/config.go + sha256: 70f948f5bb1213dbe5ec974634af0c1457832b641e205dc43958df6f66bc2f80 + - path: internal/vm/layout/layout.go + sha256: 5eb209a904ad75769304b70258c65603c22a57b2d71f2eb75082633bf5891457 + - path: internal/guestweaveagent/client/client.go + sha256: 1fd552c2e13e280b5e0f394911ec3fe3d02c1cdcdd0426cf54558dde2ca2a29c + - path: internal/guestweaveagent/client/install.go + sha256: 815ad270ed597c432e2de2bb501525c322cdbad9805f00762bfa7c267ddafdfd + - path: internal/guestweaveagent/proto/proto.go + sha256: f56ebcca0372bea6fd68ed38348809002b19654689535cc3047419d8657a5bdb + - path: internal/guestweaveagent/agent/agent.go + sha256: dbb6e0c22d6deb8303861cdaa4c37620f468f0be4109ad99a777408a4875a83b + - path: cmd/weaveagent/main.go + sha256: 522389ebae5d20ed2621dc2ec92978886d15047af09e5c85917edd0b54239fc7 + - path: cmd/weaveagent/modulebus.go + sha256: d2b1409111e5f1020503960050382cd03ce1d162bd6b2952817fdd19979230c0 + - path: cmd/weaveguestd/main.go + sha256: ce74994e218cda0f6815d652fc92819b383e55efd4fbe04ef9d53f97a127b59b + - path: cmd/weave-gui/main.go + sha256: be3bb52ae9a165f3e36a3cb1f223acbdeb8d5fab014538e118be4febfec142ff + - path: internal/nativebuild/nativebuild.go + sha256: f8a13d8dcdd102d945b4e401101b9fcc600f42b6e50958fe9025e6fee5b1d9d3 + - path: internal/nativebuild/assets/weave-agent + sha256: b9fde96a97c2099635264e9efc0daa846bc0c57f40255b25956ba476f94a1ff5 + - path: internal/guestimage/manifest.go + sha256: 6aaab5dfddaee4fbd6c99a37253361dc6123f0a66696091dabe16a2c6e6ab1c6 + - path: internal/cli/serve.go + sha256: 6497233f59396a8268a3e3cafa8a90e092fb4a3d9c52239b0d8422a7c31c8cf4 + - path: internal/serve/auth.go + sha256: 63789f3928fb3cd8512d94981d1f5a2aed6ac04496129b0bef6ce49cc1aa19d3 + - path: internal/serve/mcp.go + sha256: 98e60253180eff8442e1fdd8d0f323ffd083f599991a8edaf4ea77b2ccbf88f3 + - path: internal/serve/vmcreate.go + sha256: a1e4bdbb7415c2a32615e855eb9934aa44f6fd8a5831dc5619346335fb1907f3 + - path: internal/cli/root.go + sha256: 596a4cc16ff13078c29849ddaf95d2fc0d5547e653515bd610b25c54100f8a92 - id: imageweave repository: weaveplatform/imageweave revision: 3f81345846d7c64bc1b9c5f8f38949a02ca462c5 @@ -119,6 +341,56 @@ sources: sha256: 72faae6e93da153a31f4b1e4782aa1076383d1a12e36db11dc9f4d18a3f2e0f4 - path: go.mod sha256: 234011f24a7d60fad0e4a74b7776f7649c3a778803db208d0dcc119850d39b24 + - path: internal/cli/image.go + sha256: 162e3b3390bdb266a0b087b2e868caad81c7d4cf66f8fdc7d750456e912c6865 + - path: internal/cli/delivery.go + sha256: ada22d105a912c9e3130b72f88be4426f83f43beec6dd5b055e879fc465a451c + - path: pkg/catalog/catalog.go + sha256: bb7ed57c1cfc3681ed1e13671d32258fd97937e890408b93cc4922f3ad2caefe + - path: pkg/plan/prepared.go + sha256: f093142519f404ae30bc64b7a188dbf13a8ec980a38bc0923951f30bad6a4584 + - path: internal/packerplugin/prepared.go + sha256: 4ddcc27f5ddfa01fdeb4c448390a60896e4d14bfc281070aec569a0b4927b703 + - path: pkg/nativebuild/build.go + sha256: 95d8596539446ebc8cf9bdd98b05068266c870d2937078781e1bede64a284037 + - path: pkg/macosbuild/build.go + sha256: 6bcc8f472227ecdbe13e97236ebcf75921616aab13cbc7fbe63d09ba04b30a60 + - path: pkg/macosbuild/tools.go + sha256: 1777b5e111bdd77fc5c0426042d1187046cb2e25d3eb2957020b51fa7b288962 + - path: internal/imagebuild/catalogue.go + sha256: 6c4e2c565040babd51ee580f4ae0e263671660e8c9d1623b4400c48d0bd88d75 + - path: internal/imagebuild/packages.go + sha256: 8975c1281f4f08c33ecf377d759c2bccd1c4067506a5f9b431f2c8f68a6b9dae + - path: images/catalogue.json + sha256: b25b4f8b893b78354bce2eb2b5182363aa498343642525d9e9c16e36935feee8 + - path: images/packages.lock.json + sha256: 4e85e11b4dd4efd5392505fd5cca8bd61408f321f21ea2c80ddf6f9895f08395 + - path: internal/imagebuild/macos/prepared.go + sha256: 731fe08167c969d92da45b472ac6e0410dbdb377e7166f27d73c3bfb1a2cdf1b + - path: internal/imagebuild/macos/acceptance.go + sha256: a3cf9f9c04dd8652fa136b69bc32f573df9c8fd1a590b8fc99e0296d4262ea8c + - path: internal/imagebuild/macos/recipe.go + sha256: 6337a114ab19232f6a23e336eb12e215c80486a94557760a28b065f043bb9396 + - path: internal/imagebuild/macos/machine_darwin.go + sha256: 845b9593df7961cb7d770fdbfdded0af782ba0e8fb50df3d3317d5d17c380ad1 + - path: internal/imagebuild/linux/linux.go + sha256: 2e2fbac930d592c71bf308f44d22a427068ec026bd926a67e60b6993d88331ee + - path: internal/imagebuild/linux/linux_agent_check.go + sha256: e31b1c0ab8b83080e1678110f71fb3f1089bc77805264764b1cb702d81f84a75 + - path: internal/imagebuild/windows/windows_agent.go + sha256: f3c62d3eb40a580022dd4b3fe805ca3d3464ea1b06c08faacf13a2644bb1350c + - path: internal/imagebuild/windows/recipe.go + sha256: efecb99673a93136ff446e87d293b904e6499f185e271d21d60f817f941622f7 + - path: internal/imagebuild/export.go + sha256: 6cb3648df15ff9db6974247e4a0ad2f5b7a614291b5c217c7872a11b8c17cdc7 + - path: .github/workflows/image-native-candidate.yml + sha256: 0589caae91afb535e0a3ba54fddc96969cc9a3956e31edee99514a44c84057c1 + - path: .github/workflows/macos-candidate.yml + sha256: c64a39c1a9a48f3958ba12ba94dec2dfb98ea937e8cfda4043ea7a4133c08a4e + - path: .github/actions/publish-macos-candidate/action.yml + sha256: 428162d2271cb6964d8570742a8df45d8efc3c58af90b4ddbd563dcd450302e5 + - path: delivery/macos-candidate-policy.json + sha256: 1743b676720cef6d4f037b80fc4748e5f02cccf163bc23406baf2e7b69c005e8 - id: oci repository: weaveplatform/weaveplatform-oci revision: 2f088490822cf14a5e9b6824bffe07e932753715 @@ -142,3 +414,129 @@ sources: sha256: 4a2ab469c4bbaf7cbc98e6f8601e0e1e3c3832e74d2eac6038863c8a337814a3 - path: .github/workflows/quality-gate.yml sha256: 5709155d9806a8fe18f1944f22b5b524848c4531e3021a3639bacf072d4345c6 + - path: scripts/promote.sh + sha256: c48cef42906e0242814e5c237277a4e55f4222f736573ed7817a938cf95fd22c + - path: channels/stable.json + sha256: ae05c046a36f79df4ae8b812b9649c1a844ad412dac3004ec43f10f2aeb2c500 + - path: .github/workflows/promote.yml + sha256: 800d6453a46769d5c7f74960f6666f69d5342db47842ef5465cc3d760102d30d +- id: guestweave-windows-development + repository: weaveplatform/guestweave-cli-windows + revision: 651ec7d5d0459ece11526c5ee34e0774bc6d2f52 + baseline: feat/weave-agent-handover-168 + visibility: private + verified_at: 2026-10-11 + files: + - path: go.mod + sha256: 732d043fa11fbc9b82af0adddf66c825ddee5672ca18f299025b7875bc1c5db7 + - path: internal/command/run_channel.go + sha256: ec81063cd5852c695127309944084e500865750602d4230fbbd8b72060fb32be + - path: internal/command/commands_exec.go + sha256: ecdd64c0549cafabb9bf88fe699f431e8f308ebcf7fe3eff5ca6109846ee9e33 + - path: internal/cli/root.go + sha256: 5e8471c6331d7390d23e27c35941df6b570f08b93a077300d0b4837e728015c0 + - path: internal/cli/exec.go + sha256: ff8e872881e572682a97f810cedc59137ad806b028f072c9c711e8a7996815d4 + - path: internal/cli/stop.go + sha256: a5d2cccc1cad659170d581e092af2fff8ddb5704d77fe02d751e0cef38ed7cab + - path: internal/command/commands_create.go + sha256: 4109edef21165d31718c5962a6c874a51b967d1874f0f1764a5c4045612eac39 + - path: internal/command/commands_run.go + sha256: 02bb426255a222c6793ecb7c790d22ae2ffb601ee267bf0c38375848d010d4bc + - path: internal/command/run_detached.go + sha256: 1a8407e55363b61536483484d3a62758440db1edae11c8c168c12a5607766baf + - path: internal/command/run_guest.go + sha256: 085cc3acdd7c4fef01ca87e2da2ebad048701fedb9faab74ccb14258c66d9e98 + - path: internal/command/run_bootstrap.go + sha256: 1348f9b05abc7268fce64527b7e8bda0fc10c010b39a4465381913c6d4725829 + - path: internal/command/run_upgrade.go + sha256: e11584aa8a7ae00be6b7e5178f1319c8ae715d1520bb96679e9c34e09b8f0b9f + - path: internal/command/run_network.go + sha256: aa2dceaab5803861370f5807970abef7d24bf03aca037661ea9a27a1ec6fedb3 + - path: internal/command/run_clipboard.go + sha256: 4109fd938a001ec94afbb25ae8bfc43a511d1607cccb703e1b73939fbe008e90 + - path: internal/command/run_clipboard_guest.go + sha256: 0cd7ce5ffab3baea833f4c7997f91effd0596ade2ddf7a219981c939e87f35b0 + - path: internal/command/exec_result.go + sha256: 61e46df559e215676bd17f6f81b3923ef0db9f45fa5cc7a4d2364d02376a7565 + - path: internal/command/commands_modules.go + sha256: 15cf28f8d905357cc4ec0971cdb62ca09160c9b2c790d16aac6f6ba6b6bf2106 + - path: internal/command/commands_stop.go + sha256: 5bb8b077fb270fc0a8081365510cf7d3ff14ecf9a252f12178b5f39a757a9eeb + - path: internal/command/commands_shutdown.go + sha256: b4d6f4d60b1ea0f28dfd6c5050f6eede126653dca7af2a833ac65fd31d485821 + - path: internal/command/commands_restart.go + sha256: e0134b771f40c83d68aee5ad874821869fc762226dc67b5ec9b6e034c630ce9b + - path: internal/command/commands_clone.go + sha256: 5a9c1546b979ef07e37ffe3c9d693975e95b39d0bb017e2eaec35524fcb34524 + - path: internal/command/commands_seal.go + sha256: 927980d7dd98220b8d876072f7aca6c59aa12a33bd1f13db37e3f4f654e9d0f3 + - path: internal/command/seal_linux_script.go + sha256: 06788a126aa46fd7c0852b03a88de546649130c453d80e585e639b03b78e94e6 + - path: internal/command/seal_windows_script.go + sha256: fb65e0597910e87a0043c73048bb785d682dc06642a9df63e0433b6ab21dd9b9 + - path: internal/command/commands_oci.go + sha256: 3250a4b4d3262fe9ed3101dc34d2e0dfc40351975af4b2b988c2531aab750886 + - path: internal/command/commands_import.go + sha256: 0ac96be54085b93befca0abf87f161585e7dbfabb610f92bc78d2e1f493068d8 + - path: internal/guestchannel/channel.go + sha256: 6c7fdd91b2bd0e690d9bbcb0623a3879bfeb79bbd7a6568c922d7fa5c0c5c6b0 + - path: internal/guestchannel/dial_windows.go + sha256: 339699090bc47f8f418c17431d5155206bf205376035b12c0c2d60e53ef1c1ce + - path: internal/vm/guestctl/socket.go + sha256: fd46b1545d8151b74163df4e5cd0738e33ee3f0c89e03742b219c5e22e82ea67 + - path: internal/vm/guestctl/exec.go + sha256: df9b08398a80c5fe8849f3237a8c44df9df92341f9fafbd3977988fe0c931601 + - path: internal/vm/channelkey/channelkey.go + sha256: a3d122924c7e02a30ff1bdfe6a2852258ebf5e6036bdbde1538a8c6c6d628a57 + - path: internal/vm/channelkey/private_windows.go + sha256: dc38fe865349b45a89bbda37461fdd47fafda9b589bf55da57ceec2d682bae9d + - path: internal/vm/channelkey/trust.go + sha256: 38d27e5d927f2c8e7cfd2392a81c2507b0b53b84e6d2659397a54aad02677f0f + - path: internal/vm/guestagent/releases.go + sha256: cb6bce4cad09d1a5823c19cd6de620ef5827ac1cd7a52a0d34945ca7ddad5081 + - path: internal/vm/guestagent/packages.go + sha256: f74815289289670d18c6774d5f02fbd783b8087051d1739de4ac00e9d97cd079 + - path: internal/vm/guestagent/download.go + sha256: 5fd45cc3fbef9db95363da5cb15b9495bd64f535e8655a5947965a543250736e + - path: internal/vm/guestagent/signature.go + sha256: 73d3e9b9f0a359583df3d2db4da1b8bcd6898bf3779f48f756cf9da39c4d4fa7 + - path: internal/vm/guestagent/windows.go + sha256: 7b74e45a255e983849011eeaee713856dd6a366bd497ab80adc1c151ccb3f537 + - path: internal/vm/guestagent/authenticode_windows.go + sha256: ae38854965ee32ec5e7e79e3e2606661ee42cd7a8498e34da234f6e8bdc4b12d + - path: internal/provmedia/seed_windows.go + sha256: 403c8f146d33858fd0f1729ec47bf883757d9f33bc347db6e9d9571e91107cb9 + - path: internal/nativebuild/nativebuild.go + sha256: 563276c17cec0c85ec03b8221a12f2d208ec956ce17c2baa65755e0aa7ec8113 + - path: internal/nativebuild/scripts/build-weave-rootfs.sh + sha256: b2d5b14c5c125a167a13b0a21379b7a4db4cd752c0043e17a0f69cf32a99210c + - path: internal/hcs/engine.go + sha256: f3fc0eb226408a0f92290e0bcb30d039cfabf7e0d40146d092c3ff37923376d6 + - path: internal/hcs/native.go + sha256: 00e045116d4e42c729b29ecf1ade8150ada7d51d0e1cf80bfea7869e502b80fb + - path: internal/linuxmedia/ubuntu-desktop.yaml + sha256: faa0e2d6e0370659b417000b3e17e9d237afc1bd3421f5e57111e88264791e81 + - path: internal/winmedia/sidecar.go + sha256: 850ee42a6cac5d495c32cb3713ee58011a6b2c28018e0228009e8f12780bea18 + - path: internal/serve/http.go + sha256: e5ab6ba2fdfe9e866b345ccfba371217a5574ea56fbd871d94c28eafbba260d8 + - path: internal/serve/auth.go + sha256: 63789f3928fb3cd8512d94981d1f5a2aed6ac04496129b0bef6ce49cc1aa19d3 + - path: internal/serve/vmcreate.go + sha256: f100e471458bbdc9fee193a989a87c0fa8965ec40d1ef8da7685dd781cac66f4 + - path: internal/serve/mcp.go + sha256: 23b611cc0c0a50aefcdd23b8039643e09c9bf37c4f70a89958584be937ec26a8 + - path: internal/oci/oci.go + sha256: c79db35d029653ab9c855506341df4ffa41b7be68436b8a3b1c761e7e6576101 +- id: agent-modules-sdk-027 + repository: weaveplatform/weaveplatform-agent-modules + revision: 0a61d653a0fd80e3befcb0b65d6b52171d36aad4 + baseline: sdk/v0.2.7 + visibility: public + verified_at: 2026-10-11 + files: + - path: sdk/protocol/hvchannel/modules.go + sha256: 69dbbae0721379aa06de45c0bacac72b6a89c33df47f909d758dba5748a9c0d9 + - path: sdk/weaveclient/auth.go + sha256: 430ca5642c87f97881a116ae6e73bc79dde3b591fc3bfca30cd29a72d63dea1c