@warpgogol/werkstatt-shared 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +352 -0
- package/src/checks/index.ts +24 -0
- package/src/checks/lib/astro-site-url.ts +30 -0
- package/src/checks/lib/i18n.ts +30 -0
- package/src/checks/result-helpers.ts +102 -0
- package/src/checks/suppressions-config.ts +195 -0
- package/src/index.ts +14 -0
- package/src/integration/README.md +38 -0
- package/src/integration/adapters.ts +143 -0
- package/src/integration/crm-buffer.ts +491 -0
- package/src/integration/dispatch.ts +106 -0
- package/src/integration/funnel.ts +379 -0
- package/src/integration/index.ts +56 -0
- package/src/integration/lifecycle.ts +155 -0
- package/src/integration/orchestration.ts +439 -0
- package/src/integration/port-barrel.ts +124 -0
- package/src/integration/port.ts +143 -0
- package/src/integration/qstash.ts +134 -0
- package/src/integration/sharding.ts +133 -0
- package/src/integration/tests/deliver-event.test.ts +52 -0
- package/src/integration/tests/dispatch.test.ts +88 -0
- package/src/integration/tests/funnel-bridge.test.ts +46 -0
- package/src/integration/tests/hub-runtime.test.ts +128 -0
- package/src/integration/tests/lifecycle.test.ts +69 -0
- package/src/integration/tests/qstash.test.ts +83 -0
- package/src/integration/tests/sharding.test.ts +64 -0
- package/src/integration/vitest.config.ts +8 -0
- package/src/integration-adapter-supabase-crm/adapter.ts +437 -0
- package/src/integration-adapter-supabase-crm/client.ts +605 -0
- package/src/integration-adapter-supabase-crm/index.ts +28 -0
- package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +482 -0
- package/src/integration-adapter-supabase-crm/supabase-rest.ts +89 -0
- package/src/integration-adapter-supabase-crm/tenant-registry.ts +334 -0
- package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +83 -0
- package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +409 -0
- package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +288 -0
- package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +80 -0
- package/src/integration-adapter-supabase-crm/vitest.config.ts +8 -0
- package/src/integration-adapter-supabase-crm/worker.ts +223 -0
- package/src/observability/conventions.ts +93 -0
- package/src/observability/env-resolver.ts +57 -0
- package/src/observability/index.ts +57 -0
- package/src/observability/metric-registry.ts +184 -0
- package/src/observability/otlp-converter.ts +125 -0
- package/src/observability/otlp-json.ts +135 -0
- package/src/observability/otlp-transport.ts +68 -0
- package/src/observability/pusher.ts +142 -0
- package/src/observability/redact.ts +27 -0
- package/src/observability/tests/conventions.test.ts +100 -0
- package/src/observability/tests/metric-registry.test.ts +117 -0
- package/src/observability/tests/otlp-converter.pbt.test.ts +77 -0
- package/src/observability/tests/otlp-converter.test.ts +157 -0
- package/src/observability/tests/otlp-json.test.ts +120 -0
- package/src/observability/tests/pusher.test.ts +75 -0
- package/src/observability/tests/redact.pbt.test.ts +71 -0
- package/src/observability/tests/redact.test.ts +37 -0
- package/src/observability/typed-refs.ts +137 -0
- package/src/observability/vitest.config.ts +8 -0
- package/src/ontology/analytics/matomo-binding.yaml +172 -0
- package/src/ontology/analytics/matomo-fleet.registry.yaml +26 -0
- package/src/ontology/analytics/messkanon.yaml +151 -0
- package/src/ontology/archetype-registry.ts +61 -0
- package/src/ontology/archetypes/components/brand-label.yaml +19 -0
- package/src/ontology/archetypes/components/copyright.yaml +19 -0
- package/src/ontology/archetypes/components/currency-aware-price-display.yaml +19 -0
- package/src/ontology/archetypes/components/currency-selector.yaml +19 -0
- package/src/ontology/archetypes/components/footer-promo.yaml +19 -0
- package/src/ontology/archetypes/components/lang-switcher.yaml +19 -0
- package/src/ontology/archetypes/components/layout.yaml +19 -0
- package/src/ontology/archetypes/components/live-photo.yaml +20 -0
- package/src/ontology/archetypes/components/material-credit.yaml +20 -0
- package/src/ontology/archetypes/components/media.yaml +21 -0
- package/src/ontology/archetypes/components/nachweis-card.yaml +34 -0
- package/src/ontology/archetypes/components/nachweis-detail.yaml +43 -0
- package/src/ontology/archetypes/components/nachweis-list.yaml +37 -0
- package/src/ontology/archetypes/components/nachweis-verify.yaml +31 -0
- package/src/ontology/archetypes/components/not-found.yaml +22 -0
- package/src/ontology/archetypes/components/offer-capacity.yaml +21 -0
- package/src/ontology/archetypes/components/passport-header.yaml +19 -0
- package/src/ontology/archetypes/components/passport-provenance.yaml +20 -0
- package/src/ontology/archetypes/components/passport-score-grid.yaml +20 -0
- package/src/ontology/archetypes/components/passport-star-map.yaml +20 -0
- package/src/ontology/archetypes/components/person-profile.yaml +20 -0
- package/src/ontology/archetypes/components/pulsar.yaml +20 -0
- package/src/ontology/archetypes/components/responsive-image.yaml +19 -0
- package/src/ontology/archetypes/components/scroll-to-top.yaml +22 -0
- package/src/ontology/archetypes/components/social-meta.yaml +20 -0
- package/src/ontology/archetypes/components/structured-data.yaml +19 -0
- package/src/ontology/archetypes/index.json +1003 -0
- package/src/ontology/archetypes/index.yaml +821 -0
- package/src/ontology/archetypes/sections/approach.yaml +23 -0
- package/src/ontology/archetypes/sections/article-list.yaml +30 -0
- package/src/ontology/archetypes/sections/audience-cards.yaml +30 -0
- package/src/ontology/archetypes/sections/breadcrumbs.yaml +21 -0
- package/src/ontology/archetypes/sections/chat-widget.yaml +37 -0
- package/src/ontology/archetypes/sections/comparison-cards.yaml +31 -0
- package/src/ontology/archetypes/sections/controlled-responsibility-block.yaml +34 -0
- package/src/ontology/archetypes/sections/credits.yaml +29 -0
- package/src/ontology/archetypes/sections/donation-card.yaml +53 -0
- package/src/ontology/archetypes/sections/donation-use.yaml +23 -0
- package/src/ontology/archetypes/sections/dynamic-status-block.yaml +35 -0
- package/src/ontology/archetypes/sections/faq-list.yaml +26 -0
- package/src/ontology/archetypes/sections/final-cta.yaml +53 -0
- package/src/ontology/archetypes/sections/hero-decision-card.yaml +65 -0
- package/src/ontology/archetypes/sections/hero.yaml +50 -0
- package/src/ontology/archetypes/sections/impact.yaml +23 -0
- package/src/ontology/archetypes/sections/markdown.yaml +43 -0
- package/src/ontology/archetypes/sections/mountain-journey.yaml +43 -0
- package/src/ontology/archetypes/sections/navigation.yaml +20 -0
- package/src/ontology/archetypes/sections/notausgang-block.yaml +41 -0
- package/src/ontology/archetypes/sections/open-source-registry.yaml +26 -0
- package/src/ontology/archetypes/sections/ownership-block.yaml +57 -0
- package/src/ontology/archetypes/sections/people.yaml +48 -0
- package/src/ontology/archetypes/sections/price-card.yaml +50 -0
- package/src/ontology/archetypes/sections/problem.yaml +23 -0
- package/src/ontology/archetypes/sections/send-message.yaml +63 -0
- package/src/ontology/archetypes/sections/service-metadata-block.yaml +40 -0
- package/src/ontology/archetypes/sections/social-proof.yaml +27 -0
- package/src/ontology/archetypes/sections/transparency.yaml +32 -0
- package/src/ontology/archetypes/sections/trust-strip.yaml +42 -0
- package/src/ontology/archetypes/sections/video-section.yaml +27 -0
- package/src/ontology/archetypes/sections/women.yaml +29 -0
- package/src/ontology/biome-token-projection.ts +229 -0
- package/src/ontology/biomes/check-concrete-blueprint.yaml +118 -0
- package/src/ontology/biomes/handwerk-material-warm.yaml +135 -0
- package/src/ontology/biomes/nonprofit-trust.yaml +123 -0
- package/src/ontology/blueprints/offer.yaml +39 -0
- package/src/ontology/blueprints/prompts/local-market-signal.md +13 -0
- package/src/ontology/blueprints/prompts/website-local-narrative.md +24 -0
- package/src/ontology/blueprints/ratgeber.yaml +62 -0
- package/src/ontology/blueprints/website-local.yaml +309 -0
- package/src/ontology/blueprints/website-service.yaml +84 -0
- package/src/ontology/capabilities/lead.submit.yaml +44 -0
- package/src/ontology/cosmic/index.ts +48 -0
- package/src/ontology/cosmic/moon-catalog.ts +173 -0
- package/src/ontology/cosmic/planet-catalog.ts +82 -0
- package/src/ontology/cosmic/star-catalog.ts +337 -0
- package/src/ontology/enums.ts +159 -0
- package/src/ontology/external-surfaces/index.ts +158 -0
- package/src/ontology/external-surfaces/jsonld-types.yaml +135 -0
- package/src/ontology/external-surfaces/sitemap-shape.yaml +6 -0
- package/src/ontology/external-surfaces/url-schema.yaml +95 -0
- package/src/ontology/growth/events/contact-email-click.yaml +21 -0
- package/src/ontology/growth/events/contact-form-submit.yaml +25 -0
- package/src/ontology/growth/events/contact-phone-click.yaml +21 -0
- package/src/ontology/growth/events/contact-route-click.yaml +21 -0
- package/src/ontology/growth/events/contact-submit.yaml +28 -0
- package/src/ontology/growth/events/contact-whatsapp-click.yaml +21 -0
- package/src/ontology/growth/events/cta-click.yaml +30 -0
- package/src/ontology/growth/events/donation-confirmed.yaml +32 -0
- package/src/ontology/growth/events/donation-form-start.yaml +21 -0
- package/src/ontology/growth/events/donation-form-submit.yaml +29 -0
- package/src/ontology/growth/events/donation-intent.yaml +29 -0
- package/src/ontology/growth/events/form-error.yaml +27 -0
- package/src/ontology/growth/events/form-start.yaml +21 -0
- package/src/ontology/growth/events/form-submit.yaml +21 -0
- package/src/ontology/growth/events/outbound-click.yaml +25 -0
- package/src/ontology/growth/events/page-view.yaml +24 -0
- package/src/ontology/growth/events/passport-view.yaml +28 -0
- package/src/ontology/growth/events/star-map-navigate.yaml +30 -0
- package/src/ontology/index.ts +165 -0
- package/src/ontology/manifest.ts +344 -0
- package/src/ontology/operations/index.ts +1 -0
- package/src/ontology/schemas/biome.ts +303 -0
- package/src/ontology/schemas/capability.ts +104 -0
- package/src/ontology/schemas/constellation.ts +114 -0
- package/src/ontology/schemas/dns-records.ts +53 -0
- package/src/ontology/schemas/index.ts +95 -0
- package/src/ontology/schemas/manifest-resolver.ts +123 -0
- package/src/ontology/schemas/page-entry.ts +213 -0
- package/src/ontology/schemas/section-archetype.ts +109 -0
- package/src/ontology/schemas/site-family.ts +98 -0
- package/src/ontology/schemas/system/growth.ts +80 -0
- package/src/ontology/schemas/system/integrations.ts +143 -0
- package/src/ontology/schemas/system/manifest.ts +492 -0
- package/src/ontology/schemas/system/page-output.ts +99 -0
- package/src/ontology/schemas/system/release.ts +58 -0
- package/src/ontology/schemas/system/text.ts +58 -0
- package/src/ontology/schemas/system.ts +53 -0
- package/src/ontology/shared-section-props/body-fragments.ts +182 -0
- package/src/ontology/shared-section-props/catalog.ts +195 -0
- package/src/ontology/shared-section-props/common.ts +178 -0
- package/src/ontology/shared-section-props/index.ts +34 -0
- package/src/ontology/shared-section-props/visual-header.ts +163 -0
- package/src/ontology/site-families/charity-donation-trust/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/charity-donation-trust/family.yaml +44 -0
- package/src/ontology/site-families/charity-donation-trust/linguistic-rules.yaml +6 -0
- package/src/ontology/site-families/check-quality-operator/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/check-quality-operator/family.yaml +40 -0
- package/src/ontology/site-families/check-quality-operator/linguistic-rules.yaml +6 -0
- package/src/ontology/site-families/handwerk-trust-engineering/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/handwerk-trust-engineering/family.yaml +53 -0
- package/src/ontology/site-families/handwerk-trust-engineering/linguistic-rules.yaml +6 -0
- package/src/ontology/tests/catalogs.pbt.test.ts +89 -0
- package/src/ontology/tests/enums.test.ts +203 -0
- package/src/ontology/tests/sternsystem-owner.test.ts +63 -0
- package/src/ontology/vitest.config.ts +8 -0
- package/src/passport/dht-sign.test.ts +99 -0
- package/src/passport/dht-sign.ts +73 -0
- package/src/passport/identity-sign.test.ts +134 -0
- package/src/passport/identity-sign.ts +85 -0
- package/src/passport/index.ts +53 -0
- package/src/passport/key-rotate.ts +133 -0
- package/src/passport/schema.ts +198 -0
- package/src/passport/sign.ts +225 -0
- package/src/passport/tests/schema.pbt.test.ts +93 -0
- package/src/passport/tests/schema.test.ts +222 -0
- package/src/passport/vitest.config.ts +8 -0
- package/src/share/agent/api-catalog.ts +77 -0
- package/src/share/agent/capability.ts +68 -0
- package/src/share/agent/dns-aid.test.ts +74 -0
- package/src/share/agent/dns-aid.ts +53 -0
- package/src/share/agent/fleet-catalog.ts +149 -0
- package/src/share/agent/index.ts +29 -0
- package/src/share/agent/knowledge.ts +96 -0
- package/src/share/agent/manifest.ts +126 -0
- package/src/share/agent/mcp-card.ts +63 -0
- package/src/share/agent/omit-empty.ts +77 -0
- package/src/share/agent/openapi.ts +157 -0
- package/src/share/agent/proof.ts +61 -0
- package/src/share/attribution-display.ts +86 -0
- package/src/share/check-hints.ts +38 -0
- package/src/share/content/entity-id.ts +65 -0
- package/src/share/content/index.ts +16 -0
- package/src/share/content/merge.ts +86 -0
- package/src/share/content/resolve-field-path.ts +39 -0
- package/src/share/content/substitute-deep.ts +47 -0
- package/src/share/content-discipline/index.ts +14 -0
- package/src/share/content-discipline/parsers.ts +66 -0
- package/src/share/content-discipline/types.ts +118 -0
- package/src/share/content-reference.ts +333 -0
- package/src/share/counter-utils.ts +75 -0
- package/src/share/css-value-normalize.ts +48 -0
- package/src/share/dev-props-validator.ts +131 -0
- package/src/share/entitlement.ts +172 -0
- package/src/share/env.d.ts +73 -0
- package/src/share/feature-policy.ts +367 -0
- package/src/share/formula-eval.ts +364 -0
- package/src/share/fs/index.ts +96 -0
- package/src/share/i18n/localization.ts +57 -0
- package/src/share/image-provider.ts +314 -0
- package/src/share/index.ts +86 -0
- package/src/share/knowledge/claim.ts +129 -0
- package/src/share/knowledge/derivation.ts +55 -0
- package/src/share/knowledge/freshness.ts +127 -0
- package/src/share/knowledge/index.ts +23 -0
- package/src/share/knowledge/ledger.ts +222 -0
- package/src/share/knowledge/plan.ts +122 -0
- package/src/share/knowledge/source.ts +170 -0
- package/src/share/legal/translation-policy.ts +133 -0
- package/src/share/material-credits.ts +181 -0
- package/src/share/middleware/language-redirect.ts +49 -0
- package/src/share/offer-capacity.ts +149 -0
- package/src/share/onboarding-yaml/index.ts +162 -0
- package/src/share/page.ts +351 -0
- package/src/share/redirects.test.ts +62 -0
- package/src/share/redirects.ts +54 -0
- package/src/share/rfc0042-utils.ts +95 -0
- package/src/share/runtime-context.ts +70 -0
- package/src/share/schemas/claim-records.ts +69 -0
- package/src/share/schemas/claims.ts +150 -0
- package/src/share/schemas/effects.ts +212 -0
- package/src/share/schemas/features.ts +147 -0
- package/src/share/schemas/horizontal-align.ts +22 -0
- package/src/share/schemas/icon-color.ts +27 -0
- package/src/share/schemas/index.ts +43 -0
- package/src/share/schemas/live-photo.ts +53 -0
- package/src/share/schemas/material-credit.ts +292 -0
- package/src/share/schemas/media.ts +220 -0
- package/src/share/schemas/navigation.ts +74 -0
- package/src/share/schemas/page-base.ts +33 -0
- package/src/share/schemas/participant.ts +247 -0
- package/src/share/schemas/person.ts +24 -0
- package/src/share/schemas/print.ts +93 -0
- package/src/share/schemas/section-background.ts +106 -0
- package/src/share/schemas/section-body.ts +138 -0
- package/src/share/schemas/section-cards.ts +37 -0
- package/src/share/schemas/section-cta.ts +74 -0
- package/src/share/schemas/section-header.ts +77 -0
- package/src/share/schemas/section-image.ts +77 -0
- package/src/share/schemas/section-motion.ts +52 -0
- package/src/share/schemas/section-shell.ts +63 -0
- package/src/share/schemas/section-stats.ts +37 -0
- package/src/share/schemas/site-background.ts +78 -0
- package/src/share/schemas/standard-list-item.ts +38 -0
- package/src/share/scripts/external-links.ts +45 -0
- package/src/share/scripts/gsap-counter.ts +168 -0
- package/src/share/scripts/gsap-mountain-journey.ts +236 -0
- package/src/share/scripts/gsap-parallax.ts +50 -0
- package/src/share/scripts/gsap-reveal.ts +65 -0
- package/src/share/scripts/gsap-stagger.ts +54 -0
- package/src/share/scripts/index.ts +25 -0
- package/src/share/scripts/inline-number-animation.ts +120 -0
- package/src/share/scripts/lenis.ts +109 -0
- package/src/share/scripts/live-photos.ts +250 -0
- package/src/share/scripts/lordicon.ts +94 -0
- package/src/share/scripts/orchestrator.ts +138 -0
- package/src/share/scripts/scheduler.ts +53 -0
- package/src/share/scripts/video-player.ts +116 -0
- package/src/share/semantic/ai.ts +140 -0
- package/src/share/semantic/block-extraction.ts +52 -0
- package/src/share/semantic/block-extractors/index.ts +502 -0
- package/src/share/semantic/breadcrumbs.ts +120 -0
- package/src/share/semantic/build-page.ts +309 -0
- package/src/share/semantic/business-projection.ts +278 -0
- package/src/share/semantic/derived-prices-loader.ts +36 -0
- package/src/share/semantic/extract.ts +118 -0
- package/src/share/semantic/feed.ts +124 -0
- package/src/share/semantic/ids.ts +76 -0
- package/src/share/semantic/image-sitemap.ts +153 -0
- package/src/share/semantic/index.ts +36 -0
- package/src/share/semantic/jsonld/article.ts +47 -0
- package/src/share/semantic/jsonld/breadcrumb.ts +47 -0
- package/src/share/semantic/jsonld/collection-list.ts +30 -0
- package/src/share/semantic/jsonld/context.ts +46 -0
- package/src/share/semantic/jsonld/faq.ts +49 -0
- package/src/share/semantic/jsonld/initiative.ts +53 -0
- package/src/share/semantic/jsonld/organization.ts +101 -0
- package/src/share/semantic/jsonld/person.ts +71 -0
- package/src/share/semantic/jsonld/service.ts +80 -0
- package/src/share/semantic/jsonld/shared.ts +31 -0
- package/src/share/semantic/jsonld/types.ts +18 -0
- package/src/share/semantic/jsonld/webpage.ts +98 -0
- package/src/share/semantic/jsonld/website.ts +27 -0
- package/src/share/semantic/jsonld.ts +75 -0
- package/src/share/semantic/llms-policy.ts +74 -0
- package/src/share/semantic/llms.ts +394 -0
- package/src/share/semantic/markdown-hygiene.ts +68 -0
- package/src/share/semantic/markdown-twin-provenance.ts +242 -0
- package/src/share/semantic/models.ts +402 -0
- package/src/share/semantic/organization-profile.ts +160 -0
- package/src/share/semantic/output-projection.ts +131 -0
- package/src/share/semantic/page-builders/markdown-page.ts +84 -0
- package/src/share/semantic/page-markdown.ts +234 -0
- package/src/share/semantic/page-utils.ts +125 -0
- package/src/share/semantic/price-marker-resolver.ts +92 -0
- package/src/share/semantic/robots.ts +127 -0
- package/src/share/semantic/update-stamp.ts +136 -0
- package/src/share/shared-context.ts +177 -0
- package/src/share/string-utils.ts +19 -0
- package/src/share/tests/api-catalog.test.ts +97 -0
- package/src/share/tests/breadcrumbs.test.ts +126 -0
- package/src/share/tests/build-page-price-markers.test.ts +160 -0
- package/src/share/tests/capability.test.ts +107 -0
- package/src/share/tests/claim.test.ts +90 -0
- package/src/share/tests/content-ref-index.test.ts +328 -0
- package/src/share/tests/create-dev-props-validator.test.ts +38 -0
- package/src/share/tests/derivation.test.ts +39 -0
- package/src/share/tests/dev-props-validator.test.ts +88 -0
- package/src/share/tests/fleet-catalog.test.ts +134 -0
- package/src/share/tests/formula-eval.test.ts +619 -0
- package/src/share/tests/freshness.test.ts +60 -0
- package/src/share/tests/hero-section-numbering-parity.test.ts +51 -0
- package/src/share/tests/image-sitemap.test.ts +101 -0
- package/src/share/tests/jsonld-service.test.ts +282 -0
- package/src/share/tests/knowledge.test.ts +86 -0
- package/src/share/tests/ledger.test.ts +125 -0
- package/src/share/tests/llms-0789.test.ts +63 -0
- package/src/share/tests/manifest.test.ts +100 -0
- package/src/share/tests/markdown-twin-provenance.test.ts +108 -0
- package/src/share/tests/mcp-card.test.ts +57 -0
- package/src/share/tests/onboarding-yaml.test.ts +111 -0
- package/src/share/tests/ontology-role-by-cosmic-name.test.ts +28 -0
- package/src/share/tests/openapi.test.ts +116 -0
- package/src/share/tests/organization-jsonld.test.ts +74 -0
- package/src/share/tests/output-projection.test.ts +61 -0
- package/src/share/tests/plan.test.ts +97 -0
- package/src/share/tests/price-marker-resolver.test.ts +141 -0
- package/src/share/tests/proof.test.ts +72 -0
- package/src/share/tests/resolve-field-path.test.ts +52 -0
- package/src/share/tests/source.test.ts +81 -0
- package/src/share/tests/substitute-deep.test.ts +65 -0
- package/src/share/tests/text-normalize.pbt.test.ts +143 -0
- package/src/share/tests/text-normalize.test.ts +285 -0
- package/src/share/tests/twin-path.test.ts +43 -0
- package/src/share/text-normalize.ts +548 -0
- package/src/share/text-position.ts +19 -0
- package/src/share/types/hls-js-light.d.ts +16 -0
- package/src/share/visibility.ts +167 -0
- package/src/share/vitest.config.ts +8 -0
- package/src/share/wrap-inline-numbers.ts +139 -0
- package/src/surface/blueprint-schema.ts +314 -0
- package/src/surface/blueprint-types.ts +256 -0
- package/src/surface/blueprint.ts +300 -0
- package/src/surface/breaker.ts +57 -0
- package/src/surface/decision-composer.ts +156 -0
- package/src/surface/eligibility.ts +372 -0
- package/src/surface/evidence-records.ts +97 -0
- package/src/surface/fleet.ts +60 -0
- package/src/surface/geo.ts +170 -0
- package/src/surface/governance.ts +146 -0
- package/src/surface/index.ts +216 -0
- package/src/surface/io/index.ts +18 -0
- package/src/surface/io/surface-module-context-io.ts +60 -0
- package/src/surface/io/visibility-outcomes-io.ts +46 -0
- package/src/surface/module-context.ts +120 -0
- package/src/surface/substance.ts +161 -0
- package/src/surface/tests/eligibility.pbt.test.ts +137 -0
- package/src/surface/tests/eligibility.test.ts +302 -0
- package/src/surface/types.ts +289 -0
- package/src/surface/visibility.ts +57 -0
- package/src/surface/vitest.config.ts +8 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { test, expect, describe } from "vitest";
|
|
2
|
+
import { systemConfigSchema } from "@warpgogol/werkstatt/schemas";
|
|
3
|
+
|
|
4
|
+
const validBaseEntry = {
|
|
5
|
+
schemaVersion: "system-config/v1",
|
|
6
|
+
id: "test-site",
|
|
7
|
+
cosmicStar: "Vega",
|
|
8
|
+
mirrors: [{ path: "https://github.com/org/test-site", storageType: "non-bare" }],
|
|
9
|
+
pinnedPlatform: "1.0.0",
|
|
10
|
+
status: "active" as const,
|
|
11
|
+
registeredAt: "2026-07-27T00:00:00Z",
|
|
12
|
+
notes: "",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("systemConfigSchema owner field (RFC-0561)", () => {
|
|
16
|
+
test("accepts entry without owner (backwards compatible)", () => {
|
|
17
|
+
const result = systemConfigSchema.parse(validBaseEntry);
|
|
18
|
+
expect(result.owner).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("accepts entry with valid did:web owner", () => {
|
|
22
|
+
const result = systemConfigSchema.parse({
|
|
23
|
+
...validBaseEntry,
|
|
24
|
+
owner: "did:web:warpgogol.com#operator-v1",
|
|
25
|
+
});
|
|
26
|
+
expect(result.owner).toBe("did:web:warpgogol.com#operator-v1");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("rejects entry with non-did:web owner", () => {
|
|
30
|
+
expect(() =>
|
|
31
|
+
systemConfigSchema.parse({
|
|
32
|
+
...validBaseEntry,
|
|
33
|
+
owner: "not-a-did",
|
|
34
|
+
}),
|
|
35
|
+
).toThrow();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("rejects entry with empty string owner", () => {
|
|
39
|
+
expect(() =>
|
|
40
|
+
systemConfigSchema.parse({
|
|
41
|
+
...validBaseEntry,
|
|
42
|
+
owner: "",
|
|
43
|
+
}),
|
|
44
|
+
).toThrow();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("rejects entry with did:web but no key-version fragment", () => {
|
|
48
|
+
expect(() =>
|
|
49
|
+
systemConfigSchema.parse({
|
|
50
|
+
...validBaseEntry,
|
|
51
|
+
owner: "did:web:warpgogol.com",
|
|
52
|
+
}),
|
|
53
|
+
).toThrow();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("accepts entry with did:web owner with subdomain", () => {
|
|
57
|
+
const result = systemConfigSchema.parse({
|
|
58
|
+
...validBaseEntry,
|
|
59
|
+
owner: "did:web:sub.example.com#key-1",
|
|
60
|
+
});
|
|
61
|
+
expect(result.owner).toBe("did:web:sub.example.com#key-1");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { dhtEntryBytes, signDhtEntry, verifyDhtEntry, type DHTEntryData } from "./dht-sign.ts";
|
|
3
|
+
import { generateKeypair } from "./sign.ts";
|
|
4
|
+
import type { DHTSiteEntry } from "@warpgogol/werkstatt-shared/ontology/operations";
|
|
5
|
+
|
|
6
|
+
describe("dht-sign", () => {
|
|
7
|
+
it("dhtEntryBytes produces deterministic canonical bytes", () => {
|
|
8
|
+
const entry: DHTEntryData = {
|
|
9
|
+
siteId: "test-site",
|
|
10
|
+
owner: "did:web:example.com#v1",
|
|
11
|
+
workshopEndpoint: "10.0.0.1:7947",
|
|
12
|
+
mirrors: ["10.0.0.2:7947"],
|
|
13
|
+
registeredAt: "2025-01-01T00:00:00.000Z",
|
|
14
|
+
lastUpdated: "2025-01-01T00:00:00.000Z",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const bytes1 = dhtEntryBytes(entry);
|
|
18
|
+
const bytes2 = dhtEntryBytes(entry);
|
|
19
|
+
|
|
20
|
+
expect(bytes1).toEqual(bytes2);
|
|
21
|
+
expect(bytes1.length).toBeGreaterThan(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("dhtEntryBytes excludes signature field", () => {
|
|
25
|
+
const entryData: DHTEntryData = {
|
|
26
|
+
siteId: "test-site",
|
|
27
|
+
owner: "did:web:example.com#v1",
|
|
28
|
+
workshopEndpoint: "10.0.0.1:7947",
|
|
29
|
+
mirrors: [],
|
|
30
|
+
registeredAt: "2025-01-01T00:00:00.000Z",
|
|
31
|
+
lastUpdated: "2025-01-01T00:00:00.000Z",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const bytes = dhtEntryBytes(entryData);
|
|
35
|
+
const decoded = new TextDecoder().decode(bytes);
|
|
36
|
+
expect(decoded).not.toContain("signature");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("signDhtEntry and verifyDhtEntry round-trip", async () => {
|
|
40
|
+
const keypair = await generateKeypair();
|
|
41
|
+
const entryData: DHTEntryData = {
|
|
42
|
+
siteId: "test-site",
|
|
43
|
+
owner: "did:web:example.com#v1",
|
|
44
|
+
workshopEndpoint: "10.0.0.1:7947",
|
|
45
|
+
mirrors: ["10.0.0.2:7947"],
|
|
46
|
+
registeredAt: "2025-01-01T00:00:00.000Z",
|
|
47
|
+
lastUpdated: "2025-01-01T00:00:00.000Z",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const signature = await signDhtEntry(entryData, keypair.privateKeyHex);
|
|
51
|
+
expect(signature).toBeTruthy();
|
|
52
|
+
expect(signature.length).toBeGreaterThan(0);
|
|
53
|
+
|
|
54
|
+
const fullEntry: DHTSiteEntry = { ...entryData, signature };
|
|
55
|
+
const isValid = await verifyDhtEntry(fullEntry, keypair.publicKeyMultibase);
|
|
56
|
+
expect(isValid).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("verifyDhtEntry rejects tampered entries", async () => {
|
|
60
|
+
const keypair = await generateKeypair();
|
|
61
|
+
const entryData: DHTEntryData = {
|
|
62
|
+
siteId: "test-site",
|
|
63
|
+
owner: "did:web:example.com#v1",
|
|
64
|
+
workshopEndpoint: "10.0.0.1:7947",
|
|
65
|
+
mirrors: [],
|
|
66
|
+
registeredAt: "2025-01-01T00:00:00.000Z",
|
|
67
|
+
lastUpdated: "2025-01-01T00:00:00.000Z",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const signature = await signDhtEntry(entryData, keypair.privateKeyHex);
|
|
71
|
+
const tamperedEntry: DHTSiteEntry = {
|
|
72
|
+
...entryData,
|
|
73
|
+
workshopEndpoint: "10.0.0.99:7947",
|
|
74
|
+
signature,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const isValid = await verifyDhtEntry(tamperedEntry, keypair.publicKeyMultibase);
|
|
78
|
+
expect(isValid).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("verifyDhtEntry rejects wrong public key", async () => {
|
|
82
|
+
const keypair1 = await generateKeypair();
|
|
83
|
+
const keypair2 = await generateKeypair();
|
|
84
|
+
const entryData: DHTEntryData = {
|
|
85
|
+
siteId: "test-site",
|
|
86
|
+
owner: "did:web:example.com#v1",
|
|
87
|
+
workshopEndpoint: "10.0.0.1:7947",
|
|
88
|
+
mirrors: [],
|
|
89
|
+
registeredAt: "2025-01-01T00:00:00.000Z",
|
|
90
|
+
lastUpdated: "2025-01-01T00:00:00.000Z",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const signature = await signDhtEntry(entryData, keypair1.privateKeyHex);
|
|
94
|
+
const fullEntry: DHTSiteEntry = { ...entryData, signature };
|
|
95
|
+
|
|
96
|
+
const isValid = await verifyDhtEntry(fullEntry, keypair2.publicKeyMultibase);
|
|
97
|
+
expect(isValid).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
RFC-0565: DHT entry signing and verification — canonicalization and sign/verify
|
|
5
|
+
wrappers for DHTSiteEntry, built on the existing Ed25519 signBytes/verifyBytes
|
|
6
|
+
primitives from sign.ts. Analogous to identity-sign.ts but for the DHT site
|
|
7
|
+
entry shape.
|
|
8
|
+
</purpose>
|
|
9
|
+
<non-goals>
|
|
10
|
+
<item>Do not handle key storage or management — private keys arrive as env vars.</item>
|
|
11
|
+
<item>Do not assemble W3C VC envelopes — DHT entries use a simple signature string, not VCProof.</item>
|
|
12
|
+
<item>Do not modify sign.ts — this module wraps signBytes/verifyBytes.</item>
|
|
13
|
+
</non-goals>
|
|
14
|
+
</MODULE_CONTRACT>
|
|
15
|
+
<CHANGE_SUMMARY>
|
|
16
|
+
<item>RFC-0565: initial DHT entry signing module.</item>
|
|
17
|
+
</CHANGE_SUMMARY>
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @warpgogol/werkstatt-shared/passport/dht-sign — DHT entry signing (RFC-0565)
|
|
22
|
+
*
|
|
23
|
+
* Uses signBytes/verifyBytes from sign.ts with a dedicated canonicalization
|
|
24
|
+
* function for DHT site entries. The signature field is excluded from
|
|
25
|
+
* canonicalization (it is computed over the entry data without the signature).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { signBytes, verifyBytes } from "./sign.ts";
|
|
29
|
+
import type { DHTSiteEntry } from "@warpgogol/werkstatt-shared/ontology/operations";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* DHTSiteEntry without the signature field — the data that is actually signed.
|
|
33
|
+
*/
|
|
34
|
+
export type DHTEntryData = Omit<DHTSiteEntry, "signature">;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Produce canonical UTF-8 bytes from a DHT entry (excluding signature).
|
|
38
|
+
* DETERMINISM: identical inputs → identical bytes (sorted-key JSON, no whitespace).
|
|
39
|
+
*/
|
|
40
|
+
export function dhtEntryBytes(entry: DHTEntryData): Uint8Array {
|
|
41
|
+
const sorted: Record<string, unknown> = {};
|
|
42
|
+
for (const key of Object.keys(entry).sort()) {
|
|
43
|
+
sorted[key] = (entry as Record<string, unknown>)[key];
|
|
44
|
+
}
|
|
45
|
+
const canonical = JSON.stringify(sorted);
|
|
46
|
+
return new TextEncoder().encode(canonical);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sign a DHT entry and return a multibase Ed25519 signature string.
|
|
51
|
+
*
|
|
52
|
+
* @param entry — DHT entry data (without signature)
|
|
53
|
+
* @param privateKeyHex — 32-byte Ed25519 private key as hex (from env secret)
|
|
54
|
+
* @returns multibase base58btc signature string
|
|
55
|
+
*/
|
|
56
|
+
export async function signDhtEntry(entry: DHTEntryData, privateKeyHex: string): Promise<string> {
|
|
57
|
+
return signBytes(privateKeyHex, dhtEntryBytes(entry));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Verify a DHT entry signature against the published public key.
|
|
62
|
+
*
|
|
63
|
+
* @param entry — full DHT site entry (including signature)
|
|
64
|
+
* @param publicKeyMultibase — multibase public key from werkstatt.identity.json
|
|
65
|
+
* @returns true if signature is valid
|
|
66
|
+
*/
|
|
67
|
+
export async function verifyDhtEntry(
|
|
68
|
+
entry: DHTSiteEntry,
|
|
69
|
+
publicKeyMultibase: string,
|
|
70
|
+
): Promise<boolean> {
|
|
71
|
+
const { signature, ...entryData } = entry;
|
|
72
|
+
return verifyBytes(publicKeyMultibase, dhtEntryBytes(entryData), signature);
|
|
73
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>RFC-0558: unit tests for identity credential signing and verification.</purpose>
|
|
4
|
+
</MODULE_CONTRACT>
|
|
5
|
+
<CHANGE_SUMMARY>
|
|
6
|
+
<item>RFC-0558: initial identity-sign tests.</item>
|
|
7
|
+
</CHANGE_SUMMARY>
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from "vitest";
|
|
11
|
+
import { generateKeypair } from "./sign.ts";
|
|
12
|
+
import {
|
|
13
|
+
identityCredentialBytes,
|
|
14
|
+
signIdentityCredential,
|
|
15
|
+
verifyIdentityCredential,
|
|
16
|
+
} from "./identity-sign.ts";
|
|
17
|
+
import type { SiteOwnershipCredentialSubject, ActorDelegationCredentialSubject } from "./schema.ts";
|
|
18
|
+
|
|
19
|
+
describe("identityCredentialBytes", () => {
|
|
20
|
+
it("produces deterministic bytes for SiteOwnershipCredentialSubject", () => {
|
|
21
|
+
const subject: SiteOwnershipCredentialSubject = {
|
|
22
|
+
id: "did:web:warpgogol.com#operator-v1",
|
|
23
|
+
siteId: "warpgogol-com",
|
|
24
|
+
role: "owner",
|
|
25
|
+
};
|
|
26
|
+
const bytes1 = identityCredentialBytes(subject);
|
|
27
|
+
const bytes2 = identityCredentialBytes(subject);
|
|
28
|
+
expect(bytes1).toEqual(bytes2);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("produces deterministic bytes for ActorDelegationCredentialSubject", () => {
|
|
32
|
+
const subject: ActorDelegationCredentialSubject = {
|
|
33
|
+
id: "did:web:warpgogol.com#agent-v1",
|
|
34
|
+
siteId: "warpgogol-com",
|
|
35
|
+
delegatedBy: "did:web:warpgogol.com#operator-v1",
|
|
36
|
+
expiresAt: "2027-01-01T00:00:00.000Z",
|
|
37
|
+
scopes: ["*"],
|
|
38
|
+
};
|
|
39
|
+
const bytes1 = identityCredentialBytes(subject);
|
|
40
|
+
const bytes2 = identityCredentialBytes(subject);
|
|
41
|
+
expect(bytes1).toEqual(bytes2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("produces different bytes for different subjects", () => {
|
|
45
|
+
const subject1: SiteOwnershipCredentialSubject = {
|
|
46
|
+
id: "did:web:warpgogol.com#operator-v1",
|
|
47
|
+
siteId: "warpgogol-com",
|
|
48
|
+
role: "owner",
|
|
49
|
+
};
|
|
50
|
+
const subject2: SiteOwnershipCredentialSubject = {
|
|
51
|
+
id: "did:web:warpgogol.com#operator-v2",
|
|
52
|
+
siteId: "warpgogol-com",
|
|
53
|
+
role: "owner",
|
|
54
|
+
};
|
|
55
|
+
expect(identityCredentialBytes(subject1)).not.toEqual(identityCredentialBytes(subject2));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("signIdentityCredential / verifyIdentityCredential", () => {
|
|
60
|
+
it("round-trips for SiteOwnershipCredentialSubject", async () => {
|
|
61
|
+
const keypair = await generateKeypair();
|
|
62
|
+
const subject: SiteOwnershipCredentialSubject = {
|
|
63
|
+
id: "did:web:warpgogol.com#operator-v1",
|
|
64
|
+
siteId: "warpgogol-com",
|
|
65
|
+
role: "owner",
|
|
66
|
+
};
|
|
67
|
+
const proof = await signIdentityCredential(
|
|
68
|
+
subject,
|
|
69
|
+
keypair.privateKeyHex,
|
|
70
|
+
"did:web:warpgogol.com#v1",
|
|
71
|
+
"2026-07-27T00:00:00.000Z",
|
|
72
|
+
);
|
|
73
|
+
const valid = await verifyIdentityCredential(subject, proof, keypair.publicKeyMultibase);
|
|
74
|
+
expect(valid).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("round-trips for ActorDelegationCredentialSubject", async () => {
|
|
78
|
+
const keypair = await generateKeypair();
|
|
79
|
+
const subject: ActorDelegationCredentialSubject = {
|
|
80
|
+
id: "did:web:warpgogol.com#agent-v1",
|
|
81
|
+
siteId: "warpgogol-com",
|
|
82
|
+
delegatedBy: "did:web:warpgogol.com#operator-v1",
|
|
83
|
+
expiresAt: "2027-01-01T00:00:00.000Z",
|
|
84
|
+
scopes: ["mission.open", "workpiece.write"],
|
|
85
|
+
};
|
|
86
|
+
const proof = await signIdentityCredential(
|
|
87
|
+
subject,
|
|
88
|
+
keypair.privateKeyHex,
|
|
89
|
+
"did:web:warpgogol.com#v1",
|
|
90
|
+
"2026-07-27T00:00:00.000Z",
|
|
91
|
+
);
|
|
92
|
+
const valid = await verifyIdentityCredential(subject, proof, keypair.publicKeyMultibase);
|
|
93
|
+
expect(valid).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("fails verification for tampered subject", async () => {
|
|
97
|
+
const keypair = await generateKeypair();
|
|
98
|
+
const subject: SiteOwnershipCredentialSubject = {
|
|
99
|
+
id: "did:web:warpgogol.com#operator-v1",
|
|
100
|
+
siteId: "warpgogol-com",
|
|
101
|
+
role: "owner",
|
|
102
|
+
};
|
|
103
|
+
const proof = await signIdentityCredential(
|
|
104
|
+
subject,
|
|
105
|
+
keypair.privateKeyHex,
|
|
106
|
+
"did:web:warpgogol.com#v1",
|
|
107
|
+
"2026-07-27T00:00:00.000Z",
|
|
108
|
+
);
|
|
109
|
+
const tampered: SiteOwnershipCredentialSubject = {
|
|
110
|
+
...subject,
|
|
111
|
+
siteId: "other-site",
|
|
112
|
+
};
|
|
113
|
+
const valid = await verifyIdentityCredential(tampered, proof, keypair.publicKeyMultibase);
|
|
114
|
+
expect(valid).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("fails verification with wrong public key", async () => {
|
|
118
|
+
const keypair1 = await generateKeypair();
|
|
119
|
+
const keypair2 = await generateKeypair();
|
|
120
|
+
const subject: SiteOwnershipCredentialSubject = {
|
|
121
|
+
id: "did:web:warpgogol.com#operator-v1",
|
|
122
|
+
siteId: "warpgogol-com",
|
|
123
|
+
role: "owner",
|
|
124
|
+
};
|
|
125
|
+
const proof = await signIdentityCredential(
|
|
126
|
+
subject,
|
|
127
|
+
keypair1.privateKeyHex,
|
|
128
|
+
"did:web:warpgogol.com#v1",
|
|
129
|
+
"2026-07-27T00:00:00.000Z",
|
|
130
|
+
);
|
|
131
|
+
const valid = await verifyIdentityCredential(subject, proof, keypair2.publicKeyMultibase);
|
|
132
|
+
expect(valid).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
RFC-0558: Identity credential signing and verification — canonicalization and
|
|
5
|
+
sign/verify wrappers for SiteOwnershipCredential and ActorDelegationCredential
|
|
6
|
+
subjects, built on the existing Ed25519 signBytes/verifyBytes primitives.
|
|
7
|
+
</purpose>
|
|
8
|
+
<non-goals>
|
|
9
|
+
<item>Do not handle key storage or management — private keys arrive as env vars.</item>
|
|
10
|
+
<item>Do not assemble full W3C VC envelopes — callers attach the proof after signing.</item>
|
|
11
|
+
<item>Do not modify sign.ts — this module wraps signBytes/verifyBytes, not signCredential.</item>
|
|
12
|
+
</non-goals>
|
|
13
|
+
</MODULE_CONTRACT>
|
|
14
|
+
<CHANGE_SUMMARY>
|
|
15
|
+
<item>RFC-0558: initial identity credential signing module.</item>
|
|
16
|
+
</CHANGE_SUMMARY>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @warpgogol/werkstatt-shared/passport/identity-sign — Identity credential signing (RFC-0558)
|
|
21
|
+
*
|
|
22
|
+
* Uses signBytes/verifyBytes from sign.ts with a dedicated canonicalization
|
|
23
|
+
* function for identity credential subjects. signCredential is NOT reused —
|
|
24
|
+
* it is typed to CredentialSubjectDigest (build provenance fields) and cannot
|
|
25
|
+
* accept identity credential subjects.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { signBytes, verifyBytes } from "./sign.ts";
|
|
29
|
+
import type {
|
|
30
|
+
SiteOwnershipCredentialSubject,
|
|
31
|
+
ActorDelegationCredentialSubject,
|
|
32
|
+
VCProof,
|
|
33
|
+
} from "./schema.ts";
|
|
34
|
+
|
|
35
|
+
export type IdentityCredentialSubject =
|
|
36
|
+
SiteOwnershipCredentialSubject | ActorDelegationCredentialSubject;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Produce canonical UTF-8 bytes from an identity credential subject.
|
|
40
|
+
* DETERMINISM: identical inputs → identical bytes (sorted-key JSON, no whitespace).
|
|
41
|
+
*/
|
|
42
|
+
export function identityCredentialBytes(subject: IdentityCredentialSubject): Uint8Array {
|
|
43
|
+
const canonical = JSON.stringify(subject, Object.keys(subject).sort());
|
|
44
|
+
return new TextEncoder().encode(canonical);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sign an identity credential subject and produce a W3C VC proof.
|
|
49
|
+
*
|
|
50
|
+
* @param subject — identity credential subject fields
|
|
51
|
+
* @param privateKeyHex — 32-byte Ed25519 private key as hex (from env secret)
|
|
52
|
+
* @param verificationMethod — DID key reference (e.g. "did:web:warpgogol.com#v1")
|
|
53
|
+
* @param issuedAt — ISO-8601 timestamp for the proof creation date
|
|
54
|
+
*/
|
|
55
|
+
export async function signIdentityCredential(
|
|
56
|
+
subject: IdentityCredentialSubject,
|
|
57
|
+
privateKeyHex: string,
|
|
58
|
+
verificationMethod: string,
|
|
59
|
+
issuedAt: string,
|
|
60
|
+
): Promise<VCProof> {
|
|
61
|
+
const proofValue = await signBytes(privateKeyHex, identityCredentialBytes(subject));
|
|
62
|
+
return {
|
|
63
|
+
type: "Ed25519Signature2020",
|
|
64
|
+
created: issuedAt,
|
|
65
|
+
verificationMethod,
|
|
66
|
+
proofPurpose: "assertionMethod",
|
|
67
|
+
proofValue,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Verify an identity credential proof against the published public key.
|
|
73
|
+
*
|
|
74
|
+
* @param subject — identity credential subject fields
|
|
75
|
+
* @param proof — proof object from the credential
|
|
76
|
+
* @param publicKeyMultibase — multibase public key from werkstatt.identity.json
|
|
77
|
+
* @returns true if signature is valid
|
|
78
|
+
*/
|
|
79
|
+
export async function verifyIdentityCredential(
|
|
80
|
+
subject: IdentityCredentialSubject,
|
|
81
|
+
proof: VCProof,
|
|
82
|
+
publicKeyMultibase: string,
|
|
83
|
+
): Promise<boolean> {
|
|
84
|
+
return verifyBytes(publicKeyMultibase, identityCredentialBytes(subject), proof.proofValue);
|
|
85
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Facilitates the export of passport-related schemas, types, and utility functions for credential management.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement business logic for credential validation or user authentication.</item>
|
|
6
|
+
<item>Do not manage raw data parsing or transport orchestration.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>Tidied by compass.changesummary.tidy; see git history for prior entries.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @warpgogol/werkstatt-shared/passport — Cosmic Passport
|
|
16
|
+
* DNA-31, DNA-34 / RFC-0028
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
PassportSchema,
|
|
21
|
+
PassportPublicKeyFileSchema,
|
|
22
|
+
PassportPublicKeyEntrySchema,
|
|
23
|
+
SiteOwnershipCredentialSubjectSchema,
|
|
24
|
+
ActorDelegationCredentialSubjectSchema,
|
|
25
|
+
WerkstattCredentialSchema,
|
|
26
|
+
WerkstattIdentityConfigSchema,
|
|
27
|
+
} from "./schema.ts";
|
|
28
|
+
export type {
|
|
29
|
+
PassportJson,
|
|
30
|
+
PassportPublicKeyFile,
|
|
31
|
+
PassportPublicKeyEntry,
|
|
32
|
+
VCProof,
|
|
33
|
+
VerifiableCredential,
|
|
34
|
+
SiteOwnershipCredentialSubject,
|
|
35
|
+
ActorDelegationCredentialSubject,
|
|
36
|
+
WerkstattCredential,
|
|
37
|
+
WerkstattIdentityConfig,
|
|
38
|
+
} from "./schema.ts";
|
|
39
|
+
|
|
40
|
+
export { rotateKey } from "./key-rotate.ts";
|
|
41
|
+
export type { KeyRotateOptions, KeyRotateResult } from "./key-rotate.ts";
|
|
42
|
+
|
|
43
|
+
export { signBytes, verifyBytes, generateKeypair } from "./sign.ts";
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
identityCredentialBytes,
|
|
47
|
+
signIdentityCredential,
|
|
48
|
+
verifyIdentityCredential,
|
|
49
|
+
} from "./identity-sign.ts";
|
|
50
|
+
export type { IdentityCredentialSubject } from "./identity-sign.ts";
|
|
51
|
+
|
|
52
|
+
export { dhtEntryBytes, signDhtEntry, verifyDhtEntry } from "./dht-sign.ts";
|
|
53
|
+
export type { DHTEntryData } from "./dht-sign.ts";
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Facilitates the generation and rotation of Ed25519 keypairs for secure application authentication.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not handle raw key parsing or validation beyond schema checks.</item>
|
|
6
|
+
<item>Do not manage application configuration or orchestration.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>Tidied by compass.changesummary.tidy; see git history for prior entries.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @warpgogol/werkstatt-shared/passport — Key rotation
|
|
16
|
+
*
|
|
17
|
+
* DNA-34 / RFC-0028
|
|
18
|
+
*
|
|
19
|
+
* SECURITY CONTRACT:
|
|
20
|
+
* - Private key is NEVER written to disk. It is printed to stdout ONCE
|
|
21
|
+
* for the engineer to paste into GitHub Actions secret PASSPORT_SIGNING_KEY.
|
|
22
|
+
* - The public key file is written to apps/<app>/public/.well-known/cosmic-passport-key.json
|
|
23
|
+
* and committed to the repository.
|
|
24
|
+
* - Old public keys remain in the file (active: false) for historical verification.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
28
|
+
import { join, dirname } from "node:path";
|
|
29
|
+
import { generateKeypair } from "./sign.ts";
|
|
30
|
+
import { PassportPublicKeyFileSchema } from "./schema.ts";
|
|
31
|
+
import type { PassportPublicKeyFile, PassportPublicKeyEntry } from "./schema.ts";
|
|
32
|
+
|
|
33
|
+
export interface KeyRotateOptions {
|
|
34
|
+
/** Absolute path to the app directory */
|
|
35
|
+
appDirectory: string;
|
|
36
|
+
/** App identifier (from system.yaml) */
|
|
37
|
+
appId: string;
|
|
38
|
+
/** If true, this is the initial key generation (no previous key file exists) */
|
|
39
|
+
initial?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface KeyRotateResult {
|
|
43
|
+
/** New key version string (e.g. "v2") */
|
|
44
|
+
newVersion: string;
|
|
45
|
+
/** New public key multibase — written to key file */
|
|
46
|
+
publicKeyMultibase: string;
|
|
47
|
+
/**
|
|
48
|
+
* Private key as hex string — print to stdout and discard.
|
|
49
|
+
* Store as GitHub Actions secret PASSPORT_SIGNING_KEY.
|
|
50
|
+
* NEVER log, write, or return this beyond the immediate CLI call.
|
|
51
|
+
*/
|
|
52
|
+
privateKeyHex: string;
|
|
53
|
+
/** Path where the updated public key file was written */
|
|
54
|
+
publicKeyFilePath: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate a new Ed25519 keypair and update the public key file.
|
|
59
|
+
*
|
|
60
|
+
* Steps:
|
|
61
|
+
* 1. Generate new keypair
|
|
62
|
+
* 2. Load existing public key file (if any)
|
|
63
|
+
* 3. Mark all existing keys as active: false
|
|
64
|
+
* 4. Append new key as active: true
|
|
65
|
+
* 5. Write updated file to apps/<app>/public/.well-known/cosmic-passport-key.json
|
|
66
|
+
* 6. Return private key for secure storage (never written to disk)
|
|
67
|
+
*
|
|
68
|
+
* After calling this function:
|
|
69
|
+
* - Update system.yaml release.passport.keyVersion to the new version
|
|
70
|
+
* - Paste the returned privateKeyHex into GitHub Actions secret PASSPORT_SIGNING_KEY
|
|
71
|
+
* - Commit the updated public key file
|
|
72
|
+
*/
|
|
73
|
+
export async function rotateKey(options: KeyRotateOptions): Promise<KeyRotateResult> {
|
|
74
|
+
const { appDirectory, appId, initial = false } = options;
|
|
75
|
+
const publicKeyFilePath = join(appDirectory, "public", ".well-known", "cosmic-passport-key.json");
|
|
76
|
+
|
|
77
|
+
// Step 1: Generate new keypair
|
|
78
|
+
const { privateKeyHex, publicKeyMultibase } = await generateKeypair();
|
|
79
|
+
|
|
80
|
+
// Step 2: Load existing key file
|
|
81
|
+
let existingKeys: PassportPublicKeyEntry[] = [];
|
|
82
|
+
let nextVersionNumber = 1;
|
|
83
|
+
|
|
84
|
+
if (!initial) {
|
|
85
|
+
try {
|
|
86
|
+
const raw = await readFile(publicKeyFilePath, "utf8");
|
|
87
|
+
const existing = PassportPublicKeyFileSchema.parse(JSON.parse(raw));
|
|
88
|
+
existingKeys = existing.keys;
|
|
89
|
+
|
|
90
|
+
// Determine next version number
|
|
91
|
+
const versionNumbers = existingKeys
|
|
92
|
+
.map((k) => parseInt(k.version.replace("v", ""), 10))
|
|
93
|
+
.filter((n) => !isNaN(n));
|
|
94
|
+
nextVersionNumber = Math.max(0, ...versionNumbers) + 1;
|
|
95
|
+
} catch {
|
|
96
|
+
// No existing file — treat as initial
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const newVersion = `v${nextVersionNumber}`;
|
|
101
|
+
|
|
102
|
+
// Step 3: Mark existing keys inactive
|
|
103
|
+
const updatedKeys: PassportPublicKeyEntry[] = existingKeys.map((k) => ({
|
|
104
|
+
...k,
|
|
105
|
+
active: false,
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
// Step 4: Append new key
|
|
109
|
+
updatedKeys.push({
|
|
110
|
+
version: newVersion,
|
|
111
|
+
active: true,
|
|
112
|
+
type: "Ed25519VerificationKey2020",
|
|
113
|
+
publicKeyMultibase,
|
|
114
|
+
createdAt: new Date().toISOString(),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Step 5: Write updated file
|
|
118
|
+
const updatedFile: PassportPublicKeyFile = {
|
|
119
|
+
schemaVersion: "1.0",
|
|
120
|
+
appId,
|
|
121
|
+
keys: updatedKeys,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
await mkdir(dirname(publicKeyFilePath), { recursive: true });
|
|
125
|
+
await writeFile(publicKeyFilePath, JSON.stringify(updatedFile, null, 2), "utf8");
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
newVersion,
|
|
129
|
+
publicKeyMultibase,
|
|
130
|
+
privateKeyHex,
|
|
131
|
+
publicKeyFilePath,
|
|
132
|
+
};
|
|
133
|
+
}
|