@warpgogol/werkstatt-shared 0.14.1 → 0.16.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 +534 -766
- package/src/{share/agent → agent}/api-catalog.ts +14 -1
- package/src/{share/agent → agent}/ard-catalog.ts +6 -1
- package/src/{share/agent → agent}/dns-aid.test.ts +2 -2
- package/src/agent/guidance.ts +52 -0
- package/src/agent/health.ts +68 -0
- package/src/{share/agent → agent}/index.ts +5 -1
- package/src/{share/agent → agent}/manifest.ts +83 -6
- package/src/{share/agent → agent}/openapi.ts +97 -4
- package/src/agent/receipt.ts +123 -0
- package/src/{share/agent → agent}/search.test.ts +3 -0
- package/src/{share/agent → agent}/search.ts +31 -5
- package/src/{share → agent}/tests/api-catalog.test.ts +8 -2
- package/src/{share → agent}/tests/ard-catalog.test.ts +11 -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 +72 -1
- package/src/{share → agent}/tests/mcp-card.test.ts +6 -2
- package/src/{share → agent}/tests/openapi.test.ts +119 -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 +13 -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 +14 -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/idempotency.ts +88 -0
- package/src/integration/index.ts +105 -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/tests/idempotency.test.ts +81 -0
- 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/drift-guard.ts +166 -0
- package/src/kernel/fs-atomic.ts +131 -0
- package/src/kernel/index.ts +22 -0
- package/src/kernel/tests/drift-guard.test.ts +112 -0
- package/src/kernel/tests/fs-atomic.test.ts +150 -0
- package/src/kernel/tests/workpiece-env.test.ts +44 -0
- package/src/kernel/tests/workspace-io.test.ts +187 -0
- package/src/kernel/types.ts +518 -0
- package/src/kernel/workpiece-env.ts +48 -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 +11 -8
- package/src/{share/middleware → middleware}/language-redirect.ts +3 -0
- 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 +14 -1
- package/src/observability/otlp-converter.ts +6 -1
- package/src/observability/otlp-transport.ts +6 -1
- package/src/observability/pipeline-log.ts +81 -0
- 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/capabilities/lead.prepare.yaml +44 -0
- package/src/ontology/capabilities/lead.submit.yaml +20 -3
- 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 +193 -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 +108 -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 +56 -4
- 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/claim-sign.ts +120 -0
- package/src/passport/index.ts +9 -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 +2 -2
- 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 +74 -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 +51 -5
- 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 +68 -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 +15 -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 +106 -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}/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}/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}/proof.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}/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,112 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { guardTrackedOverwrite } from "../drift-guard.ts";
|
|
6
|
+
|
|
7
|
+
describe("guardTrackedOverwrite (RFC-1126)", () => {
|
|
8
|
+
let dir: string;
|
|
9
|
+
const logger = { warn: vi.fn() };
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
dir = mkdtempSync(join(tmpdir(), "drift-guard-"));
|
|
13
|
+
logger.warn.mockClear();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
rmSync(dir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("reports nothing to lose when the file does not exist", async () => {
|
|
21
|
+
const report = await guardTrackedOverwrite(
|
|
22
|
+
join(dir, "missing.json"),
|
|
23
|
+
"{}",
|
|
24
|
+
{ format: "json" },
|
|
25
|
+
logger,
|
|
26
|
+
);
|
|
27
|
+
expect(report.wouldLose).toEqual([]);
|
|
28
|
+
expect(report.identical).toBe(false);
|
|
29
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("reports identical when content matches", async () => {
|
|
33
|
+
const file = join(dir, "same.json");
|
|
34
|
+
writeFileSync(file, '{"a":1}\n');
|
|
35
|
+
const report = await guardTrackedOverwrite(file, '{"a":1}\n', { format: "json" }, logger);
|
|
36
|
+
expect(report.identical).toBe(true);
|
|
37
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("warns with the lost key path for JSON drift", async () => {
|
|
41
|
+
const file = join(dir, "package.json");
|
|
42
|
+
writeFileSync(file, JSON.stringify({ dependencies: { "extra-dep": "^1.0.0" } }));
|
|
43
|
+
const report = await guardTrackedOverwrite(
|
|
44
|
+
file,
|
|
45
|
+
JSON.stringify({ dependencies: {} }),
|
|
46
|
+
{ format: "json", fixHint: "edit the manifest" },
|
|
47
|
+
logger,
|
|
48
|
+
);
|
|
49
|
+
expect(report.wouldLose).toContain("dependencies.extra-dep");
|
|
50
|
+
expect(logger.warn).toHaveBeenCalledOnce();
|
|
51
|
+
expect(logger.warn.mock.calls[0]![0]).toContain("DRIFT-GUARD-01");
|
|
52
|
+
expect(logger.warn.mock.calls[0]![0]).toContain("dependencies.extra-dep");
|
|
53
|
+
expect(logger.warn.mock.calls[0]![0]).toContain("edit the manifest");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("detects changed scalar values as lost edits", async () => {
|
|
57
|
+
const file = join(dir, "cfg.json");
|
|
58
|
+
writeFileSync(file, JSON.stringify({ version: "2.0.0" }));
|
|
59
|
+
const report = await guardTrackedOverwrite(
|
|
60
|
+
file,
|
|
61
|
+
JSON.stringify({ version: "1.0.0" }),
|
|
62
|
+
{ format: "json" },
|
|
63
|
+
logger,
|
|
64
|
+
);
|
|
65
|
+
expect(report.wouldLose).toContain("version");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("warns for YAML drift", async () => {
|
|
69
|
+
const file = join(dir, "config.yaml");
|
|
70
|
+
writeFileSync(file, "keep: yes\ndrop: gone\n");
|
|
71
|
+
const report = await guardTrackedOverwrite(file, "keep: yes\n", { format: "yaml" }, logger);
|
|
72
|
+
expect(report.wouldLose).toContain("drop");
|
|
73
|
+
expect(logger.warn).toHaveBeenCalledOnce();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("warns for text drift listing removed lines", async () => {
|
|
77
|
+
const file = join(dir, "list.txt");
|
|
78
|
+
writeFileSync(file, "alpha\nbeta\ngamma\n");
|
|
79
|
+
const report = await guardTrackedOverwrite(file, "alpha\n", { format: "text" }, logger);
|
|
80
|
+
expect(report.wouldLose).toEqual(expect.arrayContaining(["beta", "gamma"]));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("does not warn when new content only adds keys", async () => {
|
|
84
|
+
const file = join(dir, "grow.json");
|
|
85
|
+
writeFileSync(file, JSON.stringify({ a: 1 }));
|
|
86
|
+
const report = await guardTrackedOverwrite(
|
|
87
|
+
file,
|
|
88
|
+
JSON.stringify({ a: 1, b: 2 }),
|
|
89
|
+
{ format: "json" },
|
|
90
|
+
logger,
|
|
91
|
+
);
|
|
92
|
+
expect(report.wouldLose).toEqual([]);
|
|
93
|
+
expect(report.identical).toBe(false);
|
|
94
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("throws under strict instead of warning", async () => {
|
|
98
|
+
const file = join(dir, "strict.json");
|
|
99
|
+
writeFileSync(file, JSON.stringify({ manual: true }));
|
|
100
|
+
await expect(
|
|
101
|
+
guardTrackedOverwrite(file, "{}", { format: "json", strict: true }, logger),
|
|
102
|
+
).rejects.toThrow("DRIFT-GUARD-01");
|
|
103
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("falls back to line diff when existing JSON is unparseable", async () => {
|
|
107
|
+
const file = join(dir, "broken.json");
|
|
108
|
+
writeFileSync(file, "{ not json, custom-line\n");
|
|
109
|
+
const report = await guardTrackedOverwrite(file, "{}", { format: "json" }, logger);
|
|
110
|
+
expect(report.wouldLose.length).toBeGreaterThan(0);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -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,44 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { EMPTY_WORKPIECE_ENV, loadWorkpieceEnv } from "../workpiece-env.ts";
|
|
6
|
+
|
|
7
|
+
describe("loadWorkpieceEnv (RFC-1126)", () => {
|
|
8
|
+
let dir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
dir = mkdtempSync(join(tmpdir(), "workpiece-env-"));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
rmSync(dir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("returns the shared empty map when siteDirectory is undefined", async () => {
|
|
19
|
+
expect(await loadWorkpieceEnv(undefined)).toBe(EMPTY_WORKPIECE_ENV);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns an empty map when .env is missing", async () => {
|
|
23
|
+
const env = await loadWorkpieceEnv(dir);
|
|
24
|
+
expect(env.size).toBe(0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("parses KEY=VALUE pairs including quoted values", async () => {
|
|
28
|
+
writeFileSync(
|
|
29
|
+
join(dir, ".env"),
|
|
30
|
+
'PUBLIC_IMAGE_PROVIDER=build-portable\nQUOTED="with space"\n# comment\n\n',
|
|
31
|
+
);
|
|
32
|
+
const env = await loadWorkpieceEnv(dir);
|
|
33
|
+
expect(env.get("PUBLIC_IMAGE_PROVIDER")).toBe("build-portable");
|
|
34
|
+
expect(env.get("QUOTED")).toBe("with space");
|
|
35
|
+
expect(env.has("# comment")).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("does not mutate process.env", async () => {
|
|
39
|
+
writeFileSync(join(dir, ".env"), "WORKPIECE_ONLY_KEY=from-file\n");
|
|
40
|
+
delete process.env["WORKPIECE_ONLY_KEY"];
|
|
41
|
+
await loadWorkpieceEnv(dir);
|
|
42
|
+
expect(process.env["WORKPIECE_ONLY_KEY"]).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -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
|
+
});
|