@warpgogol/werkstatt-shared 0.14.1 → 0.15.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/README.md +23 -15
- package/README.uk.md +1 -1
- package/package.json +517 -766
- package/src/{share/agent → agent}/ard-catalog.ts +6 -1
- package/src/{share/agent → agent}/dns-aid.test.ts +2 -2
- package/src/{share/agent → agent}/manifest.ts +15 -1
- package/src/{share/agent → agent}/search.ts +6 -1
- package/src/{share → agent}/tests/api-catalog.test.ts +2 -2
- package/src/{share → agent}/tests/ard-catalog.test.ts +2 -2
- package/src/{share → agent}/tests/capability.test.ts +1 -1
- package/src/{share → agent}/tests/fleet-catalog.test.ts +1 -1
- package/src/{share → agent}/tests/knowledge.test.ts +2 -2
- package/src/{share → agent}/tests/manifest.test.ts +1 -1
- package/src/{share → agent}/tests/mcp-card.test.ts +2 -2
- package/src/{share → agent}/tests/openapi.test.ts +2 -2
- package/src/{share → agent}/tests/proof.test.ts +1 -1
- package/src/{share → attribution}/material-credits.ts +4 -4
- package/src/{share → checks}/check-hints.ts +6 -1
- package/src/checks/index.ts +4 -1
- package/src/checks/lib/astro-site-url.ts +6 -1
- package/src/checks/lib/i18n.ts +14 -0
- package/src/checks/result-helpers.ts +2 -2
- package/src/checks/tests/remediation-auto-populate.test.ts +1 -1
- package/src/{share → client-scripts}/counter-utils.ts +6 -2
- package/src/{share/scripts → client-scripts}/external-link-qr.ts +11 -16
- package/src/{share/scripts → client-scripts}/external-links.ts +12 -1
- package/src/{share/scripts → client-scripts}/gsap-counter.ts +8 -3
- package/src/client-scripts/index.ts +35 -0
- package/src/{share/scripts → client-scripts}/inline-number-animation.ts +1 -1
- package/src/{share/scripts → client-scripts}/lenis.ts +6 -1
- package/src/{share/scripts → client-scripts}/lordicon.ts +6 -1
- package/src/{share/scripts → client-scripts}/orchestrator.ts +10 -1
- package/src/{share/scripts → client-scripts}/scheduler.ts +6 -1
- package/src/client-scripts/scroll-spy.ts +106 -0
- package/src/client-scripts/tests/orchestrator.test.ts +47 -0
- package/src/client-scripts/tests/scroll-spy.test.ts +204 -0
- package/src/{share/scripts → client-scripts/tests}/video-player.test.ts +51 -31
- package/src/component/contracts.ts +206 -0
- package/src/component/index.ts +11 -0
- package/src/{share → content}/content-reference.ts +3 -3
- package/src/{share/content → content}/entity-id.ts +6 -3
- package/src/content/index.ts +3 -0
- package/src/{share/content → content}/merge.ts +6 -1
- package/src/content/system-manifest.test.ts +107 -0
- package/src/content/system-manifest.ts +12 -94
- package/src/{share → content}/tests/resolve-field-path.test.ts +1 -1
- package/src/{share → content}/tests/substitute-deep.test.ts +1 -1
- package/src/content-discipline/index.ts +19 -0
- package/src/{share/content-discipline → content-discipline}/parsers.ts +2 -2
- package/src/{share/env.d.ts → env.d.ts} +6 -3
- package/src/fingerprint/canonical-json.ts +731 -0
- package/src/fingerprint/index.ts +12 -0
- package/src/fingerprint/primitives.ts +72 -0
- package/src/{share/i18n → i18n}/localization.ts +6 -2
- package/src/integration/adapters.ts +10 -71
- package/src/integration/dispatch.ts +1 -1
- package/src/integration/funnel.ts +6 -1
- package/src/integration/index.ts +101 -4
- package/src/integration/lagebild-destination-adapter.ts +108 -0
- package/src/integration/lagebild-ingress.ts +7 -12
- package/src/integration/orchestration.ts +11 -10
- package/src/integration/tests/deliver-event.test.ts +1 -1
- package/src/integration/tests/dispatch.test.ts +4 -4
- package/src/integration/tests/hub-runtime.test.ts +22 -14
- package/src/integration/vitest.config.ts +1 -0
- package/src/kernel/desired-state.ts +227 -0
- package/src/kernel/diagnostic.ts +289 -0
- package/src/kernel/fs-atomic.ts +131 -0
- package/src/kernel/index.ts +15 -0
- package/src/kernel/tests/fs-atomic.test.ts +150 -0
- package/src/kernel/tests/workspace-io.test.ts +187 -0
- package/src/kernel/types.ts +459 -0
- package/src/kernel/workspace-io.ts +248 -0
- package/src/{share → knowledge}/tests/claim.test.ts +1 -1
- package/src/{share → knowledge}/tests/derivation.test.ts +1 -1
- package/src/{share → knowledge}/tests/freshness.test.ts +1 -1
- package/src/{share → knowledge}/tests/ledger.test.ts +1 -1
- package/src/{share → knowledge}/tests/plan.test.ts +1 -1
- package/src/{share → knowledge}/tests/source.test.ts +1 -1
- package/src/legal/index.ts +15 -0
- package/src/{share/middleware → middleware}/access-protection.ts +8 -8
- package/src/{share → node}/dev-props-validator.ts +6 -1
- package/src/{share → node}/import-scan.ts +23 -3
- package/src/{share → node}/semantic/derived-prices-loader.ts +4 -2
- package/src/{share → node}/semantic/markdown-twin-provenance.ts +1 -1
- package/src/{share/semantic → node}/tests/derived-prices-loader.test.ts +14 -1
- package/src/{share/semantic → node}/tests/markdown-twin-provenance.test.ts +1 -1
- package/src/observability/env-resolver.ts +6 -1
- package/src/observability/index.ts +6 -1
- package/src/observability/otlp-converter.ts +6 -1
- package/src/observability/otlp-transport.ts +6 -1
- package/src/observability/pusher.ts +6 -1
- package/src/observability/redact.ts +6 -1
- package/src/observability/typed-refs.ts +6 -2
- package/src/observability/vitest.config.ts +1 -0
- package/src/{share → offers}/formula-eval.ts +3 -3
- package/src/{share → offers}/tests/formula-eval.test.ts +2 -2
- package/src/onboarding/index.ts +1 -0
- package/src/{share → onboarding}/tests/onboarding-yaml.test.ts +1 -1
- package/src/ontology/archetype-registry.ts +6 -1
- package/src/ontology/archetypes/index.json +33 -1
- package/src/ontology/archetypes/index.yaml +27 -1
- package/src/ontology/archetypes/sections/entity-grid.yaml +46 -0
- package/src/ontology/archetypes/sections/exit-diagram.yaml +43 -0
- package/src/ontology/biome-token-projection.ts +6 -1
- package/src/ontology/cosmic/index.ts +6 -1
- package/src/ontology/cosmic/moon-catalog.ts +6 -1
- package/src/ontology/cosmic/planet-catalog.ts +6 -1
- package/src/ontology/cosmic/star-catalog.ts +6 -1
- package/src/ontology/enums.ts +6 -1
- package/src/ontology/external-surfaces/index.ts +1 -1
- package/src/ontology/index.ts +6 -2
- package/src/ontology/manifest.ts +7 -4
- package/src/ontology/operations/artifact-store.ts +45 -0
- package/src/ontology/operations/dht.ts +98 -0
- package/src/ontology/operations/handoff.ts +92 -0
- package/src/ontology/operations/index.ts +187 -1
- package/src/ontology/operations/leitstand.ts +99 -0
- package/src/ontology/operations/materialization.ts +90 -0
- package/src/ontology/operations/mission.ts +111 -0
- package/src/ontology/operations/naming-policy.ts +132 -0
- package/src/ontology/operations/notausgang.ts +64 -0
- package/src/ontology/operations/release.ts +121 -0
- package/src/ontology/operations/sternsystem.ts +192 -0
- package/src/ontology/operations/werkstatt.ts +48 -0
- package/src/ontology/schemas/biome.ts +7 -2
- package/src/ontology/schemas/capability.ts +1 -1
- package/src/ontology/schemas/constellation.ts +6 -1
- package/src/ontology/schemas/index.ts +6 -2
- package/src/ontology/schemas/manifest-resolver.ts +6 -1
- package/src/ontology/schemas/page-entry.ts +9 -5
- package/src/ontology/schemas/system/growth.ts +6 -1
- package/src/ontology/schemas/system/manifest.ts +40 -1
- package/src/ontology/schemas/system/page-output.ts +7 -6
- package/src/ontology/schemas/system/text.ts +15 -0
- package/src/ontology/schemas/system.ts +6 -2
- package/src/ontology/shared-section-props/body-fragments.ts +2 -0
- package/src/ontology/tests/sternsystem-owner.test.ts +1 -1
- package/src/ontology/vitest.config.ts +1 -0
- package/src/{share → page}/page.ts +13 -9
- package/src/{share → page}/tests/dev-props-validator.test.ts +1 -1
- package/src/{share → page}/tests/hero-section-numbering-parity.test.ts +1 -1
- package/src/passport/index.ts +6 -1
- package/src/passport/key-rotate.ts +10 -2
- package/src/passport/schema.ts +6 -1
- package/src/passport/sign.ts +8 -3
- package/src/passport/vitest.config.ts +1 -0
- package/src/pbt/index.test.ts +95 -0
- package/src/pbt/index.ts +251 -0
- package/src/plugin/plugin-contract.ts +1 -1
- package/src/{share → policy}/entitlement.ts +24 -4
- package/src/{share → policy}/feature-policy.ts +8 -3
- package/src/{share → policy}/visibility.ts +7 -2
- package/src/redirects/redirects.ts +117 -0
- package/src/{share → redirects/tests}/redirects.test.ts +1 -1
- package/src/remediation/remediation-catalog.ts +316 -0
- package/src/{share/routes → routes}/template-filter.ts +1 -1
- package/src/{share → runtime}/runtime-context.ts +7 -2
- package/src/{share → runtime}/shared-context.ts +1 -1
- package/src/{share/schemas → schemas}/claims.ts +2 -2
- package/src/{share/schemas → schemas}/effects.ts +6 -1
- package/src/{share/schemas → schemas}/features.ts +1 -1
- package/src/{share/schemas → schemas}/index.ts +3 -3
- package/src/{share/schemas → schemas}/navigation.ts +6 -1
- package/src/{share/schemas → schemas}/page-base.ts +6 -2
- package/src/{share/schemas → schemas}/person.ts +1 -1
- package/src/{share/schemas → schemas}/standard-list-item.ts +1 -1
- package/src/{share → sections}/rfc0042-utils.ts +1 -1
- package/src/{share/semantic → semantic}/block-extractors/index.ts +20 -1
- package/src/{share/semantic → semantic}/business-projection.ts +95 -1
- package/src/semantic/canonical-uri.ts +54 -0
- package/src/{share/semantic → semantic}/extract.ts +100 -1
- package/src/semantic/fact-extraction.test.ts +47 -0
- package/src/semantic/fact-extraction.ts +46 -0
- package/src/semantic/freshness.ts +67 -0
- package/src/{share/semantic → semantic}/ids.ts +6 -1
- package/src/{share/semantic → semantic}/index.ts +9 -2
- package/src/{share/semantic → semantic}/jsonld/breadcrumb.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/context.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/faq.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/initiative.ts +6 -1
- package/src/{share/semantic → semantic}/jsonld/organization.ts +6 -1
- package/src/{share/semantic → semantic}/jsonld/person.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/service.ts +6 -2
- package/src/semantic/jsonld/shared.ts +36 -0
- package/src/semantic/jsonld/types.ts +24 -0
- package/src/{share/semantic → semantic}/jsonld/webpage.ts +6 -1
- package/src/semantic/jsonld/website.ts +32 -0
- package/src/{share/semantic → semantic}/jsonld.ts +6 -1
- package/src/{share/semantic → semantic}/llms.ts +41 -3
- package/src/{share/semantic → semantic}/models.ts +26 -38
- package/src/{share/semantic → semantic}/organization-profile.ts +3 -0
- package/src/{share/semantic → semantic}/page-builders/markdown-page.ts +6 -3
- package/src/{share/semantic → semantic}/page-utils.ts +6 -3
- package/src/{share/tests/breadcrumbs.test.ts → semantic/tests/breadcrumbs-0229.test.ts} +1 -1
- package/src/{share → semantic}/tests/build-page-price-markers.test.ts +2 -2
- package/src/{share/semantic → semantic}/tests/business-projection.test.ts +179 -0
- package/src/semantic/tests/canonical-uri.test.ts +56 -0
- package/src/{share/semantic → semantic}/tests/extract.test.ts +83 -0
- package/src/semantic/tests/freshness.pbt.test.ts +108 -0
- package/src/{share/tests/image-sitemap.test.ts → semantic/tests/image-sitemap-0150.test.ts} +1 -1
- package/src/{share → semantic}/tests/jsonld-service.test.ts +2 -2
- package/src/{share → semantic}/tests/jsonld-video.test.ts +3 -3
- package/src/{share → semantic}/tests/jsonld-webpage.test.ts +3 -3
- package/src/{share → semantic}/tests/llms-0789.test.ts +2 -2
- package/src/{share/semantic → semantic}/tests/llms.test.ts +46 -0
- package/src/{share → semantic}/tests/organization-jsonld.test.ts +3 -3
- package/src/{share/tests/output-projection.test.ts → semantic/tests/output-projection-0143.test.ts} +1 -1
- package/src/{share/tests/price-marker-resolver.test.ts → semantic/tests/price-marker-resolver-0767.test.ts} +2 -2
- package/src/{share/semantic → semantic}/tests/price-marker-resolver.test.ts +4 -2
- package/src/semantic/tests/project-claims.pbt.test.ts +60 -0
- package/src/{share/semantic → semantic}/tests/split-sentences.test.ts +1 -1
- package/src/{share → semantic}/tests/twin-path.test.ts +1 -1
- package/src/signing/index.ts +13 -0
- package/src/signing/key.ts +139 -0
- package/src/signing/sign.ts +66 -0
- package/src/signing/types.ts +29 -0
- package/src/{share/slug → slug}/slug-url.ts +4 -1
- package/src/stack/run-tool.ts +108 -0
- package/src/stack/stack-checks.ts +124 -0
- package/src/stack/tests/run-tool.test.ts +138 -0
- package/src/stack/tests/stack-checks.test.ts +141 -0
- package/src/stack/tests/walk-files.test.ts +131 -0
- package/src/stack/walk-files.ts +114 -0
- package/src/surface/blueprint.ts +342 -66
- package/src/surface/decision-composer.ts +6 -1
- package/src/surface/geo.ts +6 -1
- package/src/surface/index.ts +11 -7
- package/src/surface/types.ts +2 -2
- package/src/surface/vitest.config.ts +1 -0
- package/src/tests/signing-core.test.ts +129 -0
- package/src/{share → text}/css-value-normalize.ts +6 -1
- package/src/text/string-utils.ts +24 -0
- package/src/{share → text}/tests/text-normalize.test.ts +5 -0
- package/src/{share → text}/text-normalize.ts +4 -3
- package/src/{share → text}/wrap-inline-numbers.ts +1 -1
- package/src/types/axiom-study.d.ts +36 -0
- package/src/types/hls-js-light.d.ts +21 -0
- package/src/types/qrcode-browser.d.ts +23 -0
- package/src/typography/fix.pbt.test.ts +208 -0
- package/src/typography/fix.ts +148 -0
- package/src/typography/index.ts +54 -0
- package/src/typography/locale-defaults.ts +97 -0
- package/src/typography/rules-tier1.test.ts +459 -0
- package/src/typography/rules-tier1.ts +925 -0
- package/src/typography/rules-tier2-locale.pbt.test.ts +190 -0
- package/src/typography/rules-tier2-locale.test.ts +286 -0
- package/src/typography/rules-tier2-locale.ts +399 -0
- package/src/typography/rules-tier2-structure.test.ts +330 -0
- package/src/typography/rules-tier2-structure.ts +378 -0
- package/src/typography/rules-tier3-advisory.test.ts +290 -0
- package/src/typography/rules-tier3-advisory.ts +239 -0
- package/src/typography/text-surface.test.ts +244 -0
- package/src/typography/text-surface.ts +448 -0
- package/src/index.ts +0 -14
- package/src/integration/port-barrel.ts +0 -105
- package/src/share/agent/tests/api-catalog.test.ts +0 -8
- package/src/share/agent/tests/ard-catalog.test.ts +0 -8
- package/src/share/agent/tests/capability.test.ts +0 -14
- package/src/share/agent/tests/fleet-catalog.test.ts +0 -15
- package/src/share/agent/tests/knowledge.test.ts +0 -20
- package/src/share/agent/tests/manifest.test.ts +0 -9
- package/src/share/agent/tests/mcp-card.test.ts +0 -8
- package/src/share/agent/tests/openapi.test.ts +0 -8
- package/src/share/agent/tests/proof.test.ts +0 -15
- package/src/share/content/index.ts +0 -16
- package/src/share/content-discipline/index.ts +0 -14
- package/src/share/index.ts +0 -86
- package/src/share/knowledge/tests/claim.test.ts +0 -11
- package/src/share/knowledge/tests/derivation.test.ts +0 -30
- package/src/share/knowledge/tests/freshness.test.ts +0 -18
- package/src/share/knowledge/tests/ledger.test.ts +0 -35
- package/src/share/knowledge/tests/plan.test.ts +0 -19
- package/src/share/knowledge/tests/source.test.ts +0 -19
- package/src/share/legal/index.ts +0 -1
- package/src/share/redirects.ts +0 -54
- package/src/share/remediation/remediation-catalog.ts +0 -196
- package/src/share/scripts/index.ts +0 -26
- package/src/share/scripts/tests/orchestrator.test.ts +0 -8
- package/src/share/semantic/jsonld/shared.ts +0 -31
- package/src/share/semantic/jsonld/types.ts +0 -19
- package/src/share/semantic/jsonld/website.ts +0 -27
- package/src/share/string-utils.ts +0 -19
- package/src/share/types/axiom-study.d.ts +0 -22
- package/src/share/types/hls-js-light.d.ts +0 -16
- package/src/share/types/qrcode-browser.d.ts +0 -18
- package/src/share/utility-registry.yaml +0 -50
- package/src/share/vitest.config.ts +0 -8
- package/src/surface/blueprint-schema.ts +0 -314
- package/src/surface/blueprint-types.ts +0 -256
- /package/src/{share/agent → agent}/api-catalog.ts +0 -0
- /package/src/{share/agent → agent}/capability.ts +0 -0
- /package/src/{share/agent → agent}/dns-aid.ts +0 -0
- /package/src/{share/agent → agent}/fleet-catalog.ts +0 -0
- /package/src/{share/agent → agent}/index.ts +0 -0
- /package/src/{share/agent → agent}/knowledge.ts +0 -0
- /package/src/{share/agent → agent}/mcp-card.ts +0 -0
- /package/src/{share/agent → agent}/omit-empty.ts +0 -0
- /package/src/{share/agent → agent}/openapi.ts +0 -0
- /package/src/{share/agent → agent}/proof.ts +0 -0
- /package/src/{share/agent → agent}/search.test.ts +0 -0
- /package/src/{share/agent → agent}/tests/omit-empty.test.ts +0 -0
- /package/src/{share → attribution}/attribution-display.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-mountain-journey.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-parallax.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-reveal.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-stagger.ts +0 -0
- /package/src/{share/scripts → client-scripts}/live-photos.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/external-link-qr.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/external-links.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/gsap-counter.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/gsap-mountain-journey.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/gsap-parallax.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/gsap-reveal.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/gsap-stagger.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/inline-number-animation.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/lenis.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/live-photos.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/lordicon.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/tests/scheduler.test.ts +0 -0
- /package/src/{share/scripts → client-scripts}/video-player.ts +0 -0
- /package/src/{share/content → content}/resolve-field-path.ts +0 -0
- /package/src/{share/content → content}/substitute-deep.ts +0 -0
- /package/src/{share → content}/tests/content-ref-index.test.ts +0 -0
- /package/src/{share/content-discipline → content-discipline}/types.ts +0 -0
- /package/src/{share → image-provider}/image-provider.ts +0 -0
- /package/src/{share/knowledge → knowledge}/claim.ts +0 -0
- /package/src/{share/knowledge → knowledge}/derivation.ts +0 -0
- /package/src/{share/knowledge → knowledge}/freshness.ts +0 -0
- /package/src/{share/knowledge → knowledge}/index.ts +0 -0
- /package/src/{share/knowledge → knowledge}/ledger.ts +0 -0
- /package/src/{share/knowledge → knowledge}/plan.ts +0 -0
- /package/src/{share/knowledge → knowledge}/source.ts +0 -0
- /package/src/{share/legal → legal}/translation-policy.ts +0 -0
- /package/src/{share/middleware → middleware}/language-redirect.ts +0 -0
- /package/src/{share/middleware → middleware}/tests/access-protection.test.ts +0 -0
- /package/src/{share → node}/fs/index.ts +0 -0
- /package/src/{share/tests/markdown-twin-provenance.test.ts → node/tests/markdown-twin-provenance-0613.test.ts} +0 -0
- /package/src/{share → offers}/offer-capacity.ts +0 -0
- /package/src/{share/onboarding-yaml/index.ts → onboarding/yaml.ts} +0 -0
- /package/src/{share → ontology}/tests/ontology-role-by-cosmic-name.test.ts +0 -0
- /package/src/{share/routes → routes}/tests/template-filter.test.ts +0 -0
- /package/src/{share/schemas → schemas}/claim-records.ts +0 -0
- /package/src/{share/schemas → schemas}/horizontal-align.ts +0 -0
- /package/src/{share/schemas → schemas}/icon-color.ts +0 -0
- /package/src/{share/schemas → schemas}/live-photo.ts +0 -0
- /package/src/{share/schemas → schemas}/material-credit.ts +0 -0
- /package/src/{share/schemas → schemas}/media.ts +0 -0
- /package/src/{share/schemas → schemas}/participant.ts +0 -0
- /package/src/{share/schemas → schemas}/print.ts +0 -0
- /package/src/{share/schemas → schemas}/section-background.ts +0 -0
- /package/src/{share/schemas → schemas}/section-body.ts +0 -0
- /package/src/{share/schemas → schemas}/section-cards.ts +0 -0
- /package/src/{share/schemas → schemas}/section-cta.ts +0 -0
- /package/src/{share/schemas → schemas}/section-header.ts +0 -0
- /package/src/{share/schemas → schemas}/section-image.ts +0 -0
- /package/src/{share/schemas → schemas}/section-motion.ts +0 -0
- /package/src/{share/schemas → schemas}/section-shell.ts +0 -0
- /package/src/{share/schemas → schemas}/section-stats.ts +0 -0
- /package/src/{share/schemas → schemas}/site-background.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/claim-records.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/claims.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/effects.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/features.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/horizontal-align.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/icon-color.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/live-photo.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/material-credit.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/media.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/navigation.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/page-base.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/participant.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/person.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/print.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/section-background.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/section-body.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/section-cards.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/section-cta.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/section-header.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/section-image.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/section-motion.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/section-shell.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/section-stats.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/site-background.test.ts +0 -0
- /package/src/{share/schemas → schemas}/tests/standard-list-item.test.ts +0 -0
- /package/src/{share/semantic → semantic}/ai.ts +0 -0
- /package/src/{share/semantic → semantic}/block-extraction.ts +0 -0
- /package/src/{share/semantic → semantic}/breadcrumbs.ts +0 -0
- /package/src/{share/semantic → semantic}/build-page.ts +0 -0
- /package/src/{share/semantic → semantic}/feed.ts +0 -0
- /package/src/{share/semantic → semantic}/image-sitemap.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/article.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/collection-list.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/article.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/breadcrumb.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/collection-list.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/faq.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/initiative.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/organization.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/person.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/service.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/shared.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/video.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/webpage.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/tests/website.test.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/video.ts +0 -0
- /package/src/{share/semantic → semantic}/llms-policy.ts +0 -0
- /package/src/{share/semantic → semantic}/markdown-hygiene.ts +0 -0
- /package/src/{share/semantic → semantic}/output-projection.ts +0 -0
- /package/src/{share/semantic → semantic}/page-markdown.ts +0 -0
- /package/src/{share/semantic → semantic}/price-marker-resolver.ts +0 -0
- /package/src/{share/semantic → semantic}/robots.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/ai.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/block-extraction.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/breadcrumbs.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/build-page.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/feed.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/helpers.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/ids.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/image-sitemap.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/jsonld.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/llms-policy.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/markdown-hygiene.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/markdown-page.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/organization-profile.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/output-projection.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/page-markdown.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/page-utils.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/robots.test.ts +0 -0
- /package/src/{share/semantic → semantic}/tests/update-stamp.test.ts +0 -0
- /package/src/{share/semantic → semantic}/update-stamp.ts +0 -0
- /package/src/{share/slug → slug}/heading-slugger.ts +0 -0
- /package/src/{share/slug → slug}/index.ts +0 -0
- /package/src/{share/slug → slug}/slug-id.ts +0 -0
- /package/src/{share/slug → slug}/strategies.ts +0 -0
- /package/src/{share/slug → slug}/tests/slug.test.ts +0 -0
- /package/src/{share → text}/tests/text-normalize.pbt.test.ts +0 -0
- /package/src/{share → text}/text-position.ts +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
RFC-0258: atomic file-write primitive for workspace-shared kernel-command
|
|
5
|
+
outputs. Every kernel command that writes a file outside its target app
|
|
6
|
+
directory (workspace root, docs/, packages/ontology/) MUST write through
|
|
7
|
+
writeFileAtomic instead of node:fs/promises writeFile, so that a reader
|
|
8
|
+
polling the target path never observes a torn/partial file when Turborepo
|
|
9
|
+
runs multiple app builds in parallel.
|
|
10
|
+
</purpose>
|
|
11
|
+
<non-goals>
|
|
12
|
+
<item>Do not provide cross-process mutual exclusion (locks) — outputs are deterministic (RFC-0087), so convergent atomic writes are sufficient.</item>
|
|
13
|
+
<item>Do not fall back to a non-atomic write when retries are exhausted — fail loudly instead.</item>
|
|
14
|
+
</non-goals>
|
|
15
|
+
</MODULE_CONTRACT>
|
|
16
|
+
<KEY_DECISIONS>
|
|
17
|
+
<item>Writes go through temp-file plus rename — readers never observe partial content.</item>
|
|
18
|
+
<item>Canonical implementation lives in @warpgogol/forge — this is a compatibility re-export.</item>
|
|
19
|
+
</KEY_DECISIONS>
|
|
20
|
+
<CHANGE_SUMMARY>
|
|
21
|
+
<item>RFC-0258: initial implementation.</item>
|
|
22
|
+
<item>RFC-1097: step 6 — compass.migrate codemod run
|
|
23
|
+
|
|
24
|
+
Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.</item>
|
|
25
|
+
<item>RFC-1097: sweep — werkstatt-engine clean
|
|
26
|
+
|
|
27
|
+
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.</item>
|
|
28
|
+
</CHANGE_SUMMARY>
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { writeFile, rename as fsRename, unlink } from "node:fs/promises";
|
|
32
|
+
import { existsSync } from "node:fs";
|
|
33
|
+
import { dirname, join, basename } from "node:path";
|
|
34
|
+
import { randomBytes } from "node:crypto";
|
|
35
|
+
|
|
36
|
+
export interface WriteFileAtomicOptions {
|
|
37
|
+
/** Max rename retries on transient EPERM/EBUSY. Default 10, linear 50ms backoff. */
|
|
38
|
+
retries?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Indirection seam so unit tests can simulate a transient EPERM/EBUSY
|
|
42
|
+
// on the first rename attempt without monkey-patching the frozen node:fs/promises
|
|
43
|
+
// module namespace (ESM named exports are read-only bindings). Production code
|
|
44
|
+
// always goes through fsRename; only tests in this package call the setter.
|
|
45
|
+
let renameImpl: typeof fsRename = fsRename;
|
|
46
|
+
|
|
47
|
+
/** @internal test-only seam — do not call from production code. */
|
|
48
|
+
export function __setRenameImplForTests(impl: typeof fsRename | undefined): void {
|
|
49
|
+
renameImpl = impl ?? fsRename;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const DEFAULT_RETRIES = 10;
|
|
53
|
+
const RETRY_BACKOFF_MS = 50;
|
|
54
|
+
|
|
55
|
+
const RETRYABLE_CODES = new Set(["EPERM", "EBUSY"]);
|
|
56
|
+
|
|
57
|
+
function isRetryableError(error: unknown): boolean {
|
|
58
|
+
if (!error || typeof error !== "object") return false;
|
|
59
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
60
|
+
return typeof code === "string" && RETRYABLE_CODES.has(code);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function sleep(ms: number): Promise<void> {
|
|
64
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function makeTempPath(filePath: string): string {
|
|
68
|
+
const dir = dirname(filePath);
|
|
69
|
+
const name = basename(filePath);
|
|
70
|
+
const random = randomBytes(6).toString("hex");
|
|
71
|
+
return join(dir, `${name}.${random}.tmp`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Write `content` to `filePath` atomically: write to a temp file in the same
|
|
76
|
+
* directory, then rename over the target. Rename within one directory is
|
|
77
|
+
* atomic on POSIX; a bounded retry loop absorbs transient EPERM/EBUSY
|
|
78
|
+
* (e.g. a reader briefly holding the file handle during overwrite).
|
|
79
|
+
*
|
|
80
|
+
* On failure (write or all rename retries exhausted), the temp file is
|
|
81
|
+
* unlinked in a finally block and the original error is rethrown — this
|
|
82
|
+
* helper never falls back to a non-atomic write.
|
|
83
|
+
*/
|
|
84
|
+
export async function writeFileAtomic(
|
|
85
|
+
filePath: string,
|
|
86
|
+
content: string | Uint8Array,
|
|
87
|
+
options?: WriteFileAtomicOptions,
|
|
88
|
+
): Promise<void> {
|
|
89
|
+
const retries = options?.retries ?? DEFAULT_RETRIES;
|
|
90
|
+
const tempPath = makeTempPath(filePath);
|
|
91
|
+
let tempWritten = false;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await writeFile(tempPath, content);
|
|
95
|
+
tempWritten = true;
|
|
96
|
+
|
|
97
|
+
let attempt = 0;
|
|
98
|
+
// Bounded retry loop: attempt 0 is the first try, then up to `retries` more.
|
|
99
|
+
for (;;) {
|
|
100
|
+
try {
|
|
101
|
+
await renameImpl(tempPath, filePath);
|
|
102
|
+
return;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (attempt >= retries || !isRetryableError(error)) {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
attempt += 1;
|
|
108
|
+
// EPERM on rename-over-existing: try unlinking the target
|
|
109
|
+
// first (it may have been briefly locked by a reader that has since
|
|
110
|
+
// released it), then retry the rename on the next iteration.
|
|
111
|
+
if (existsSync(filePath)) {
|
|
112
|
+
try {
|
|
113
|
+
await unlink(filePath);
|
|
114
|
+
} catch {
|
|
115
|
+
// If unlink also fails, the retry loop will try again.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
await sleep(RETRY_BACKOFF_MS * attempt);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} finally {
|
|
122
|
+
if (tempWritten) {
|
|
123
|
+
try {
|
|
124
|
+
// If rename succeeded, the temp file no longer exists — unlink is a no-op error we swallow.
|
|
125
|
+
await unlink(tempPath);
|
|
126
|
+
} catch {
|
|
127
|
+
// Expected in the success path (temp file already renamed away) — ignore.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Barrel export for @warpgogol/werkstatt-shared/kernel sub-path. Re-exports the kernel contract cluster (command/pipeline types, workspace IO, atomic fs, desired state, diagnostic schemas) sunk from @warpgogol/werkstatt-engine per RFC-1104.</purpose>
|
|
4
|
+
<non-goals>Does not re-export engine runtime modules (executor, module registry, CLI).</non-goals>
|
|
5
|
+
</MODULE_CONTRACT>
|
|
6
|
+
<CHANGE_SUMMARY>
|
|
7
|
+
<item>RFC-1104: initial barrel — kernel contract cluster sunk from werkstatt-engine.</item>
|
|
8
|
+
</CHANGE_SUMMARY>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export * from "./types.ts";
|
|
12
|
+
export * from "./workspace-io.ts";
|
|
13
|
+
export * from "./fs-atomic.ts";
|
|
14
|
+
export * from "./desired-state.ts";
|
|
15
|
+
export * from "./diagnostic.ts";
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
RFC-0258 unit tests for writeFileAtomic, written before the workspace-shared
|
|
5
|
+
writer migration per the RFC's own rollout order.
|
|
6
|
+
</purpose>
|
|
7
|
+
<responsibilities>
|
|
8
|
+
<item>Content lands intact after a single write.</item>
|
|
9
|
+
<item>A reader polling the target during many sequential writes never observes a partial file.</item>
|
|
10
|
+
<item>The temp file is removed when the write ultimately fails.</item>
|
|
11
|
+
<item>A simulated Windows EPERM on the first rename attempt is retried and succeeds.</item>
|
|
12
|
+
</responsibilities>
|
|
13
|
+
<non-goals>
|
|
14
|
+
<item>Do not cover the workspace-write-boundary lint here — see workspace-write-boundary.test.ts in site-kernel-checks.</item>
|
|
15
|
+
</non-goals>
|
|
16
|
+
</MODULE_CONTRACT>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from "node:fs/promises";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import os from "node:os";
|
|
22
|
+
import { test, expect } from "vitest";
|
|
23
|
+
import { writeFileAtomic, __setRenameImplForTests } from "../fs-atomic.ts";
|
|
24
|
+
|
|
25
|
+
async function makeTmpDir(): Promise<string> {
|
|
26
|
+
return fs.mkdtemp(path.join(os.tmpdir(), "fs-atomic-"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("writeFileAtomic: content lands intact", async () => {
|
|
30
|
+
const dir = await makeTmpDir();
|
|
31
|
+
try {
|
|
32
|
+
const target = path.join(dir, "out.json");
|
|
33
|
+
const content = JSON.stringify({ hello: "world", n: 42 }, null, 2);
|
|
34
|
+
await writeFileAtomic(target, content);
|
|
35
|
+
const read = await fs.readFile(target, "utf8");
|
|
36
|
+
expect(read).toBe(content);
|
|
37
|
+
} finally {
|
|
38
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("writeFileAtomic: no leftover temp files after a successful write", async () => {
|
|
43
|
+
const dir = await makeTmpDir();
|
|
44
|
+
try {
|
|
45
|
+
const target = path.join(dir, "out.json");
|
|
46
|
+
await writeFileAtomic(target, "content");
|
|
47
|
+
const entries = await fs.readdir(dir);
|
|
48
|
+
expect(entries).toEqual(["out.json"]);
|
|
49
|
+
} finally {
|
|
50
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("writeFileAtomic: a reader polling the target during 200 sequential writes never observes a partial file", async () => {
|
|
55
|
+
const dir = await makeTmpDir();
|
|
56
|
+
try {
|
|
57
|
+
const target = path.join(dir, "shared.json");
|
|
58
|
+
const totalWrites = 200;
|
|
59
|
+
let stop = false;
|
|
60
|
+
let observedPartial = false;
|
|
61
|
+
let observedCount = 0;
|
|
62
|
+
|
|
63
|
+
// Each write produces a distinct, fully-valid JSON payload of a known shape.
|
|
64
|
+
function payloadFor(i: number): string {
|
|
65
|
+
return JSON.stringify({ seq: i, marker: "X".repeat(32) }) + "\n";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const writer = (async () => {
|
|
69
|
+
for (let i = 0; i < totalWrites; i++) {
|
|
70
|
+
// A polling reader sharing the event loop can still collide with the
|
|
71
|
+
// rename often enough to exceed the production default's 5-retry
|
|
72
|
+
// budget on Windows. Widen the retry budget for this synthetic
|
|
73
|
+
// worst-case poll loop only; the production default stays 5 (RFC-0258).
|
|
74
|
+
await writeFileAtomic(target, payloadFor(i), { retries: 80 });
|
|
75
|
+
}
|
|
76
|
+
stop = true;
|
|
77
|
+
})();
|
|
78
|
+
|
|
79
|
+
const reader = (async () => {
|
|
80
|
+
while (!stop) {
|
|
81
|
+
let raw: string | undefined;
|
|
82
|
+
try {
|
|
83
|
+
raw = await fs.readFile(target, "utf8");
|
|
84
|
+
} catch {
|
|
85
|
+
// Target may not exist yet at the very start — not a partial-read failure.
|
|
86
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
observedCount += 1;
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(raw) as { seq: number; marker: string };
|
|
92
|
+
if (parsed.marker !== "X".repeat(32)) {
|
|
93
|
+
observedPartial = true;
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
observedPartial = true;
|
|
97
|
+
}
|
|
98
|
+
// Yield once per read so this is a fast, realistic poller rather than
|
|
99
|
+
// a worst-case tight spin that starves the writer's rename retries.
|
|
100
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
|
|
104
|
+
await Promise.all([writer, reader]);
|
|
105
|
+
|
|
106
|
+
expect(observedPartial).toBe(false);
|
|
107
|
+
expect(observedCount > 0).toBeTruthy();
|
|
108
|
+
} finally {
|
|
109
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("writeFileAtomic: temp file is removed when the write ultimately fails", async () => {
|
|
114
|
+
const dir = await makeTmpDir();
|
|
115
|
+
try {
|
|
116
|
+
// Target directory does not exist -> the temp-file write itself fails,
|
|
117
|
+
// and no temp file should remain in `dir`.
|
|
118
|
+
const target = path.join(dir, "missing-subdir", "out.json");
|
|
119
|
+
await expect(() => writeFileAtomic(target, "content")).rejects.toThrow();
|
|
120
|
+
const entries = await fs.readdir(dir);
|
|
121
|
+
expect(entries).toEqual([]);
|
|
122
|
+
} finally {
|
|
123
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("writeFileAtomic: simulated EPERM on first rename attempt is retried and succeeds", async () => {
|
|
128
|
+
const dir = await makeTmpDir();
|
|
129
|
+
try {
|
|
130
|
+
const target = path.join(dir, "out.json");
|
|
131
|
+
let calls = 0;
|
|
132
|
+
__setRenameImplForTests(async (...args: Parameters<typeof fs.rename>) => {
|
|
133
|
+
calls += 1;
|
|
134
|
+
if (calls === 1) {
|
|
135
|
+
const error = new Error("simulated EPERM") as NodeJS.ErrnoException;
|
|
136
|
+
error.code = "EPERM";
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
return fs.rename(...args);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await writeFileAtomic(target, "content", { retries: 5 });
|
|
143
|
+
expect(calls >= 2).toBeTruthy();
|
|
144
|
+
const read = await fs.readFile(target, "utf8");
|
|
145
|
+
expect(read).toBe("content");
|
|
146
|
+
} finally {
|
|
147
|
+
__setRenameImplForTests(undefined);
|
|
148
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { test, expect } from "vitest";
|
|
2
|
+
import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
createDefaultIO,
|
|
7
|
+
createRecordingIO,
|
|
8
|
+
createReadOnlyIO,
|
|
9
|
+
KernelMetaError,
|
|
10
|
+
} from "../workspace-io.ts";
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
<MODULE_CONTRACT>
|
|
14
|
+
<purpose>
|
|
15
|
+
RFC-0267: port + adapter tests, written before wiring into the executor.
|
|
16
|
+
Recording adapter captures intents and never touches disk (fs spy via a
|
|
17
|
+
real temp directory). Read-only adapter throws KERNEL-META-01 naming the
|
|
18
|
+
command and path on any mutation. Default adapter writes atomically
|
|
19
|
+
(delegates to writeFileAtomic, rfc-0258 — proven by "no partial content"
|
|
20
|
+
and "no leftover temp file" after a successful write).
|
|
21
|
+
</purpose>
|
|
22
|
+
</MODULE_CONTRACT>
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
test("createDefaultIO: writeFile creates the file with exact content, no leftover temp file", async () => {
|
|
26
|
+
const root = await mkdtemp(join(tmpdir(), "workspace-io-default-"));
|
|
27
|
+
try {
|
|
28
|
+
const { io } = createDefaultIO();
|
|
29
|
+
const target = join(root, "nested", "file.txt");
|
|
30
|
+
await io.writeFile(target, "hello world");
|
|
31
|
+
|
|
32
|
+
const content = await readFile(target, "utf8");
|
|
33
|
+
expect(content).toBe("hello world");
|
|
34
|
+
|
|
35
|
+
const entries = await import("node:fs/promises").then((fs) => fs.readdir(join(root, "nested")));
|
|
36
|
+
expect(entries).toEqual(["file.txt"]);
|
|
37
|
+
} finally {
|
|
38
|
+
await rm(root, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("createDefaultIO: exists/readFile/mkdir/rm round-trip", async () => {
|
|
43
|
+
const root = await mkdtemp(join(tmpdir(), "workspace-io-default2-"));
|
|
44
|
+
try {
|
|
45
|
+
const { io } = createDefaultIO();
|
|
46
|
+
expect(await io.exists(join(root, "missing.txt"))).toBe(false);
|
|
47
|
+
|
|
48
|
+
await io.mkdir(join(root, "dir"));
|
|
49
|
+
expect(await io.exists(join(root, "dir"))).toBe(true);
|
|
50
|
+
|
|
51
|
+
await io.writeFile(join(root, "dir", "a.txt"), "a");
|
|
52
|
+
expect(await io.readFile(join(root, "dir", "a.txt"))).toBe("a");
|
|
53
|
+
|
|
54
|
+
await io.rm(join(root, "dir"), { recursive: true });
|
|
55
|
+
expect(await io.exists(join(root, "dir"))).toBe(false);
|
|
56
|
+
} finally {
|
|
57
|
+
await rm(root, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("createDefaultIO: readdir returns port-neutral DirEntry[] with isFile/isDirectory booleans", async () => {
|
|
62
|
+
const root = await mkdtemp(join(tmpdir(), "workspace-io-readdir-"));
|
|
63
|
+
try {
|
|
64
|
+
const { io } = createDefaultIO();
|
|
65
|
+
await io.writeFile(join(root, "a.md"), "content");
|
|
66
|
+
await io.mkdir(join(root, "subdir"));
|
|
67
|
+
|
|
68
|
+
const entries = (await io.readdir(root)).sort((x, y) => x.name.localeCompare(y.name));
|
|
69
|
+
expect(entries).toEqual([
|
|
70
|
+
{ name: "a.md", isFile: true, isDirectory: false },
|
|
71
|
+
{ name: "subdir", isFile: false, isDirectory: true },
|
|
72
|
+
]);
|
|
73
|
+
} finally {
|
|
74
|
+
await rm(root, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("createReadOnlyIO and createRecordingIO: readdir passes through (a read, not a mutation)", async () => {
|
|
79
|
+
const root = await mkdtemp(join(tmpdir(), "workspace-io-readdir-passthrough-"));
|
|
80
|
+
try {
|
|
81
|
+
const { io: base } = createDefaultIO();
|
|
82
|
+
await base.writeFile(join(root, "x.txt"), "x");
|
|
83
|
+
|
|
84
|
+
const readOnly = createReadOnlyIO(base, "fixture.readonly.command");
|
|
85
|
+
expect((await readOnly.readdir(root))[0]?.name).toBe("x.txt");
|
|
86
|
+
|
|
87
|
+
const { io: recording } = createRecordingIO(base);
|
|
88
|
+
expect((await recording.readdir(root))[0]?.name).toBe("x.txt");
|
|
89
|
+
} finally {
|
|
90
|
+
await rm(root, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("createRecordingIO: captures write/mkdir/rm intents and never touches disk", async () => {
|
|
95
|
+
const root = await mkdtemp(join(tmpdir(), "workspace-io-recording-"));
|
|
96
|
+
try {
|
|
97
|
+
const { io: base } = createDefaultIO();
|
|
98
|
+
const { io, intents } = createRecordingIO(base);
|
|
99
|
+
|
|
100
|
+
const target = join(root, "would-write.txt");
|
|
101
|
+
await io.writeFile(target, "some content");
|
|
102
|
+
await io.mkdir(join(root, "would-mkdir"));
|
|
103
|
+
await io.rm(join(root, "would-rm"));
|
|
104
|
+
|
|
105
|
+
expect(intents.length).toBe(3);
|
|
106
|
+
expect(intents.map((i) => i.kind)).toEqual(["write", "mkdir", "rm"]);
|
|
107
|
+
expect(intents[0]?.path).toBe(target);
|
|
108
|
+
expect(intents[0]?.bytes).toBe(Buffer.byteLength("some content", "utf8"));
|
|
109
|
+
|
|
110
|
+
// Nothing was actually written.
|
|
111
|
+
await expect(() => stat(target)).rejects.toThrow();
|
|
112
|
+
const rootEntries = await import("node:fs/promises").then((fs) => fs.readdir(root));
|
|
113
|
+
expect(rootEntries).toEqual([]);
|
|
114
|
+
} finally {
|
|
115
|
+
await rm(root, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("createRecordingIO: reads pass through to the base adapter", async () => {
|
|
120
|
+
const root = await mkdtemp(join(tmpdir(), "workspace-io-recording-reads-"));
|
|
121
|
+
try {
|
|
122
|
+
const { io: base } = createDefaultIO();
|
|
123
|
+
await base.writeFile(join(root, "existing.txt"), "real content");
|
|
124
|
+
|
|
125
|
+
const { io } = createRecordingIO(base);
|
|
126
|
+
expect(await io.exists(join(root, "existing.txt"))).toBe(true);
|
|
127
|
+
expect(await io.readFile(join(root, "existing.txt"))).toBe("real content");
|
|
128
|
+
} finally {
|
|
129
|
+
await rm(root, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("createReadOnlyIO: throws KERNEL-META-01 naming the command and path on writeFile", async () => {
|
|
134
|
+
const io = createReadOnlyIO(createDefaultIO().io, "fixture.readonly.command");
|
|
135
|
+
try {
|
|
136
|
+
await io.writeFile("/tmp/should-not-write.txt", "x");
|
|
137
|
+
expect.fail("should have thrown");
|
|
138
|
+
} catch (error) {
|
|
139
|
+
expect(error).toBeInstanceOf(KernelMetaError);
|
|
140
|
+
expect((error as Error).message).toMatch(/KERNEL-META-01/);
|
|
141
|
+
expect((error as Error).message).toMatch(/fixture\.readonly\.command/);
|
|
142
|
+
expect((error as Error).message).toMatch(/should-not-write\.txt/);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("createReadOnlyIO: throws on mkdir, rm, and exec too", async () => {
|
|
147
|
+
const io = createReadOnlyIO(createDefaultIO().io, "fixture.readonly.command");
|
|
148
|
+
await expect(() => io.mkdir("/tmp/nope")).rejects.toThrow(KernelMetaError);
|
|
149
|
+
await expect(() => io.rm("/tmp/nope")).rejects.toThrow(KernelMetaError);
|
|
150
|
+
await expect(() => io.exec("rm", ["-rf", "/tmp/nope"])).rejects.toThrow(KernelMetaError);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("createReadOnlyIO: reads still work", async () => {
|
|
154
|
+
const root = await mkdtemp(join(tmpdir(), "workspace-io-readonly-reads-"));
|
|
155
|
+
try {
|
|
156
|
+
const { io: base } = createDefaultIO();
|
|
157
|
+
await base.writeFile(join(root, "readable.txt"), "readable content");
|
|
158
|
+
|
|
159
|
+
const io = createReadOnlyIO(base, "fixture.readonly.command");
|
|
160
|
+
expect(await io.exists(join(root, "readable.txt"))).toBe(true);
|
|
161
|
+
expect(await io.readFile(join(root, "readable.txt"))).toBe("readable content");
|
|
162
|
+
} finally {
|
|
163
|
+
await rm(root, { recursive: true, force: true });
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// RFC-0326: createDefaultIO now returns { io, intents } — the intents array
|
|
168
|
+
// captures every mutation (write/mkdir/rm) for filesModified reporting.
|
|
169
|
+
test("createDefaultIO: captures write/mkdir/rm intents while writing to real disk (RFC-0326)", async () => {
|
|
170
|
+
const root = await mkdtemp(join(tmpdir(), "workspace-io-default-intents-"));
|
|
171
|
+
try {
|
|
172
|
+
const { io, intents } = createDefaultIO();
|
|
173
|
+
|
|
174
|
+
await io.writeFile(join(root, "file.txt"), "content");
|
|
175
|
+
await io.mkdir(join(root, "subdir"));
|
|
176
|
+
await io.rm(join(root, "subdir"), { recursive: true });
|
|
177
|
+
|
|
178
|
+
expect(intents.length).toBe(3);
|
|
179
|
+
expect(intents.map((i) => i.kind)).toEqual(["write", "mkdir", "rm"]);
|
|
180
|
+
expect(intents[0]?.bytes).toBe(Buffer.byteLength("content", "utf8"));
|
|
181
|
+
|
|
182
|
+
// File was actually written to disk (this is the real adapter, not recording).
|
|
183
|
+
expect(await io.readFile(join(root, "file.txt"))).toBe("content");
|
|
184
|
+
} finally {
|
|
185
|
+
await rm(root, { recursive: true, force: true });
|
|
186
|
+
}
|
|
187
|
+
});
|