@tanqory/theme-conformance 0.0.0-stage → 0.1.0-alpha.15
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/LICENSE +21 -0
- package/README.md +146 -2
- package/dist/abi-error.d.ts +9 -0
- package/dist/abi-error.js +37 -0
- package/dist/abi-error.js.map +1 -0
- package/dist/artifact.d.ts +39 -0
- package/dist/artifact.js +105 -0
- package/dist/artifact.js.map +1 -0
- package/dist/browser/harness.d.ts +14 -0
- package/dist/browser/harness.js +83 -0
- package/dist/browser/harness.js.map +1 -0
- package/dist/browser/init-script.d.ts +33 -0
- package/dist/browser/init-script.js +364 -0
- package/dist/browser/init-script.js.map +1 -0
- package/dist/browser/page-run.d.ts +109 -0
- package/dist/browser/page-run.js +469 -0
- package/dist/browser/page-run.js.map +1 -0
- package/dist/browser/rules.d.ts +141 -0
- package/dist/browser/rules.js +376 -0
- package/dist/browser/rules.js.map +1 -0
- package/dist/build-info.json +4 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +112 -0
- package/dist/cli.js.map +1 -0
- package/dist/field-types.d.ts +17 -0
- package/dist/field-types.js +54 -0
- package/dist/field-types.js.map +1 -0
- package/dist/fixtures/store-fixture.d.ts +60 -0
- package/dist/fixtures/store-fixture.js +13 -0
- package/dist/fixtures/store-fixture.js.map +1 -0
- package/dist/fixtures/stores/alpha.json +795 -0
- package/dist/fixtures/stores/beta.json +790 -0
- package/dist/globals-allowlist.d.ts +37 -0
- package/dist/globals-allowlist.js +73 -0
- package/dist/globals-allowlist.js.map +1 -0
- package/dist/html-checks.d.ts +16 -0
- package/dist/html-checks.js +152 -0
- package/dist/html-checks.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +186 -0
- package/dist/index.js.map +1 -0
- package/dist/jcs.d.ts +10 -0
- package/dist/jcs.js +43 -0
- package/dist/jcs.js.map +1 -0
- package/dist/jobs.d.ts +79 -0
- package/dist/jobs.js +136 -0
- package/dist/jobs.js.map +1 -0
- package/dist/limits-table.d.ts +23 -0
- package/dist/limits-table.js +58 -0
- package/dist/limits-table.js.map +1 -0
- package/dist/processes.d.ts +18 -0
- package/dist/processes.js +58 -0
- package/dist/processes.js.map +1 -0
- package/dist/production-shim/bridge.mjs +226 -0
- package/dist/production-shim/evidence.d.ts +118 -0
- package/dist/production-shim/evidence.js +158 -0
- package/dist/production-shim/evidence.js.map +1 -0
- package/dist/production-shim/log-sink.mjs +34 -0
- package/dist/production-shim/parity-probe.d.ts +7 -0
- package/dist/production-shim/parity-probe.js +189 -0
- package/dist/production-shim/parity-probe.js.map +1 -0
- package/dist/production-shim/pass.d.ts +74 -0
- package/dist/production-shim/pass.js +113 -0
- package/dist/production-shim/pass.js.map +1 -0
- package/dist/production-shim/shims/4a50141a1e26f360b23e97a497d28c67febb189a5ed32dc524b405c01cf10496/host.mjs +27 -0
- package/dist/production-shim/shims/4a50141a1e26f360b23e97a497d28c67febb189a5ed32dc524b405c01cf10496/shim.mjs +820 -0
- package/dist/production-shim/shims/5029d5e1263abc3156ab059485035bbd5eb84c03f7770c044d36da1cc3f6f733/host.mjs +27 -0
- package/dist/production-shim/shims/5029d5e1263abc3156ab059485035bbd5eb84c03f7770c044d36da1cc3f6f733/shim.mjs +1568 -0
- package/dist/production-shim/shims/6b124cddbaf6a92cafd92a8d62253c0d0ea11d8928504dc89915acd6e8784f49/host.mjs +27 -0
- package/dist/production-shim/shims/6b124cddbaf6a92cafd92a8d62253c0d0ea11d8928504dc89915acd6e8784f49/shim.mjs +575 -0
- package/dist/production-shim/shims/83968efd101f6f5f6943bfdef9fecb862b32a2ea32a2fa5056eba924f4dfc1e7/host.mjs +27 -0
- package/dist/production-shim/shims/83968efd101f6f5f6943bfdef9fecb862b32a2ea32a2fa5056eba924f4dfc1e7/shim.mjs +1606 -0
- package/dist/report.d.ts +166 -0
- package/dist/report.js +71 -0
- package/dist/report.js.map +1 -0
- package/dist/runner-identity.d.ts +11 -0
- package/dist/runner-identity.js +52 -0
- package/dist/runner-identity.js.map +1 -0
- package/dist/schema/conformance-report.v2.schema.json +482 -0
- package/dist/schema/storefront-report.v1.schema.json +110 -0
- package/dist/schema/x-tanqory-ai.v0.schema.json +70 -0
- package/dist/shim-digest.d.ts +38 -0
- package/dist/shim-digest.js +98 -0
- package/dist/shim-digest.js.map +1 -0
- package/dist/static-scan.d.ts +8 -0
- package/dist/static-scan.js +106 -0
- package/dist/static-scan.js.map +1 -0
- package/dist/storefront/browser.d.ts +77 -0
- package/dist/storefront/browser.js +245 -0
- package/dist/storefront/browser.js.map +1 -0
- package/dist/storefront/budgets.d.ts +170 -0
- package/dist/storefront/budgets.js +364 -0
- package/dist/storefront/budgets.js.map +1 -0
- package/dist/storefront/checks/a11y.d.ts +23 -0
- package/dist/storefront/checks/a11y.js +207 -0
- package/dist/storefront/checks/a11y.js.map +1 -0
- package/dist/storefront/checks/ai.d.ts +12 -0
- package/dist/storefront/checks/ai.js +81 -0
- package/dist/storefront/checks/ai.js.map +1 -0
- package/dist/storefront/checks/collection-search.d.ts +4 -0
- package/dist/storefront/checks/collection-search.js +285 -0
- package/dist/storefront/checks/collection-search.js.map +1 -0
- package/dist/storefront/checks/editor.d.ts +2 -0
- package/dist/storefront/checks/editor.js +225 -0
- package/dist/storefront/checks/editor.js.map +1 -0
- package/dist/storefront/checks/i18n.d.ts +7 -0
- package/dist/storefront/checks/i18n.js +228 -0
- package/dist/storefront/checks/i18n.js.map +1 -0
- package/dist/storefront/checks/product-cart.d.ts +18 -0
- package/dist/storefront/checks/product-cart.js +300 -0
- package/dist/storefront/checks/product-cart.js.map +1 -0
- package/dist/storefront/checks/templates-seo.d.ts +11 -0
- package/dist/storefront/checks/templates-seo.js +168 -0
- package/dist/storefront/checks/templates-seo.js.map +1 -0
- package/dist/storefront/context.d.ts +21 -0
- package/dist/storefront/context.js +18 -0
- package/dist/storefront/context.js.map +1 -0
- package/dist/storefront/discover.d.ts +44 -0
- package/dist/storefront/discover.js +70 -0
- package/dist/storefront/discover.js.map +1 -0
- package/dist/storefront/fixture.d.ts +85 -0
- package/dist/storefront/fixture.js +179 -0
- package/dist/storefront/fixture.js.map +1 -0
- package/dist/storefront/gateway.d.ts +177 -0
- package/dist/storefront/gateway.js +624 -0
- package/dist/storefront/gateway.js.map +1 -0
- package/dist/storefront/index.d.ts +12 -0
- package/dist/storefront/index.js +115 -0
- package/dist/storefront/index.js.map +1 -0
- package/dist/storefront/report.d.ts +83 -0
- package/dist/storefront/report.js +17 -0
- package/dist/storefront/report.js.map +1 -0
- package/dist/storefront/ssr.d.ts +11 -0
- package/dist/storefront/ssr.js +39 -0
- package/dist/storefront/ssr.js.map +1 -0
- package/dist/storefront/vendor/editor-bridge.client.mjs +371 -0
- package/dist/storefront/vendor/editor-bridge.d.ts +1 -0
- package/dist/storefront/vendor/editor-bridge.js +10 -0
- package/dist/storefront/vendor/editor-bridge.js.map +1 -0
- package/dist/suites/context.d.ts +47 -0
- package/dist/suites/context.js +24 -0
- package/dist/suites/context.js.map +1 -0
- package/dist/suites/render-helpers.d.ts +22 -0
- package/dist/suites/render-helpers.js +46 -0
- package/dist/suites/render-helpers.js.map +1 -0
- package/dist/suites/s1-static.d.ts +2 -0
- package/dist/suites/s1-static.js +374 -0
- package/dist/suites/s1-static.js.map +1 -0
- package/dist/suites/s2-render.d.ts +2 -0
- package/dist/suites/s2-render.js +80 -0
- package/dist/suites/s2-render.js.map +1 -0
- package/dist/suites/s3-ambient.d.ts +2 -0
- package/dist/suites/s3-ambient.js +67 -0
- package/dist/suites/s3-ambient.js.map +1 -0
- package/dist/suites/s4-limits.d.ts +41 -0
- package/dist/suites/s4-limits.js +299 -0
- package/dist/suites/s4-limits.js.map +1 -0
- package/dist/suites/s5-hydration.d.ts +7 -0
- package/dist/suites/s5-hydration.js +189 -0
- package/dist/suites/s5-hydration.js.map +1 -0
- package/dist/suites/s6-preview.d.ts +2 -0
- package/dist/suites/s6-preview.js +45 -0
- package/dist/suites/s6-preview.js.map +1 -0
- package/dist/suites/s7-determinism.d.ts +20 -0
- package/dist/suites/s7-determinism.js +125 -0
- package/dist/suites/s7-determinism.js.map +1 -0
- package/dist/suites/s8-negative.d.ts +9 -0
- package/dist/suites/s8-negative.js +174 -0
- package/dist/suites/s8-negative.js.map +1 -0
- package/dist/suites/s9-tenancy.d.ts +13 -0
- package/dist/suites/s9-tenancy.js +130 -0
- package/dist/suites/s9-tenancy.js.map +1 -0
- package/dist/workerd/config.d.ts +30 -0
- package/dist/workerd/config.js +132 -0
- package/dist/workerd/config.js.map +1 -0
- package/dist/workerd/egress-sink.mjs +13 -0
- package/dist/workerd/host.d.ts +205 -0
- package/dist/workerd/host.js +238 -0
- package/dist/workerd/host.js.map +1 -0
- package/dist/workerd/shim.mjs +1312 -0
- package/dist/workerd/storefront-data.mjs +423 -0
- package/docs/storefront-tier.md +250 -0
- package/fixtures/hydration-pass/client/client-manifest.json +28 -0
- package/fixtures/hydration-pass/client/entry.css +1 -0
- package/fixtures/hydration-pass/client/entry.js +25 -0
- package/fixtures/hydration-pass/client/lazy.js +4 -0
- package/fixtures/hydration-pass/defaults/regions/header.json +1 -0
- package/fixtures/hydration-pass/defaults/settings.json +1 -0
- package/fixtures/hydration-pass/defaults/templates/index.json +1 -0
- package/fixtures/hydration-pass/locales/en.json +1 -0
- package/fixtures/hydration-pass/manifest.json +1 -0
- package/fixtures/hydration-pass/server/entry.mjs +131 -0
- package/fixtures/hydration-violations/abort-signal-timeout/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/abort-signal-timeout/client/entry.js +5 -0
- package/fixtures/hydration-violations/animationend-late/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/animationend-late/client/entry.css +9 -0
- package/fixtures/hydration-violations/animationend-late/client/entry.js +8 -0
- package/fixtures/hydration-violations/beacon/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/beacon/client/entry.js +5 -0
- package/fixtures/hydration-violations/delayed-inline-script/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/delayed-inline-script/client/entry.js +4 -0
- package/fixtures/hydration-violations/delayed-offorigin-image/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/delayed-offorigin-image/client/entry.js +5 -0
- package/fixtures/hydration-violations/delayed-products-fetch/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/delayed-products-fetch/client/entry.js +4 -0
- package/fixtures/hydration-violations/eventsource-offorigin/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/eventsource-offorigin/client/entry.js +5 -0
- package/fixtures/hydration-violations/head-meta-added/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/head-meta-added/client/entry.js +4 -0
- package/fixtures/hydration-violations/html-attrs-changed/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/html-attrs-changed/client/entry.js +5 -0
- package/fixtures/hydration-violations/hydration-exception/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/hydration-exception/client/entry.js +6 -0
- package/fixtures/hydration-violations/idle-callback-loop/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/idle-callback-loop/client/entry.js +5 -0
- package/fixtures/hydration-violations/image-not-in-data/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/image-not-in-data/client/entry.js +6 -0
- package/fixtures/hydration-violations/inline-handler/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/inline-handler/client/entry.js +6 -0
- package/fixtures/hydration-violations/insert-outside-slot/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/insert-outside-slot/client/entry.js +6 -0
- package/fixtures/hydration-violations/intersection-observer-late/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/intersection-observer-late/client/entry.css +9 -0
- package/fixtures/hydration-violations/intersection-observer-late/client/entry.js +8 -0
- package/fixtures/hydration-violations/interval-beyond-window/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/interval-beyond-window/client/entry.js +5 -0
- package/fixtures/hydration-violations/media-ended-late/client/client-manifest.json +35 -0
- package/fixtures/hydration-violations/media-ended-late/client/entry.js +7 -0
- package/fixtures/hydration-violations/media-ended-late/client/silence.wav +0 -0
- package/fixtures/hydration-violations/media-timeupdate-late/client/client-manifest.json +35 -0
- package/fixtures/hydration-violations/media-timeupdate-late/client/entry.js +8 -0
- package/fixtures/hydration-violations/media-timeupdate-late/client/silence.wav +0 -0
- package/fixtures/hydration-violations/message-channel-loop/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/message-channel-loop/client/entry.js +7 -0
- package/fixtures/hydration-violations/mutation-observer-late/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/mutation-observer-late/client/entry.js +8 -0
- package/fixtures/hydration-violations/off-origin-script/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/off-origin-script/client/entry.js +6 -0
- package/fixtures/hydration-violations/over-budget/client/client-manifest.json +35 -0
- package/fixtures/hydration-violations/over-budget/client/entry.js +7 -0
- package/fixtures/hydration-violations/over-budget/client/heavy.js +2 -0
- package/fixtures/hydration-violations/over-hard-limit/client/client-manifest.json +37 -0
- package/fixtures/hydration-violations/over-hard-limit/client/entry.js +6 -0
- package/fixtures/hydration-violations/preconnect-late/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/preconnect-late/client/entry.js +7 -0
- package/fixtures/hydration-violations/raf-loop/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/raf-loop/client/entry.js +5 -0
- package/fixtures/hydration-violations/resize-observer-late/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/resize-observer-late/client/entry.css +9 -0
- package/fixtures/hydration-violations/resize-observer-late/client/entry.js +9 -0
- package/fixtures/hydration-violations/scheduler-posttask-delay/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/scheduler-posttask-delay/client/entry.js +5 -0
- package/fixtures/hydration-violations/scheduler-yield-loop/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/scheduler-yield-loop/client/entry.js +5 -0
- package/fixtures/hydration-violations/slot-server-children/server/entry.mjs +128 -0
- package/fixtures/hydration-violations/style-attribute/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/style-attribute/client/entry.js +4 -0
- package/fixtures/hydration-violations/timer-beyond-window/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/timer-beyond-window/client/entry.js +5 -0
- package/fixtures/hydration-violations/title-changed/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/title-changed/client/entry.js +4 -0
- package/fixtures/hydration-violations/transitionend-late/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/transitionend-late/client/entry.css +9 -0
- package/fixtures/hydration-violations/transitionend-late/client/entry.js +9 -0
- package/fixtures/hydration-violations/webrtc-peer/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/webrtc-peer/client/entry.js +4 -0
- package/fixtures/hydration-violations/websocket-offorigin/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/websocket-offorigin/client/entry.js +5 -0
- package/fixtures/hydration-violations/window-postmessage-loop/client/client-manifest.json +28 -0
- package/fixtures/hydration-violations/window-postmessage-loop/client/entry.js +7 -0
- package/fixtures/invalid-url-setting/client/client-manifest.json +1 -0
- package/fixtures/invalid-url-setting/defaults/regions/header.json +1 -0
- package/fixtures/invalid-url-setting/defaults/settings.json +1 -0
- package/fixtures/invalid-url-setting/defaults/templates/index.json +1 -0
- package/fixtures/invalid-url-setting/locales/en.json +1 -0
- package/fixtures/invalid-url-setting/manifest.json +1 -0
- package/fixtures/invalid-url-setting/server/entry.mjs +107 -0
- package/fixtures/minimal-pass/client/client-manifest.json +1 -0
- package/fixtures/minimal-pass/defaults/regions/header.json +1 -0
- package/fixtures/minimal-pass/defaults/settings.json +1 -0
- package/fixtures/minimal-pass/defaults/templates/index.json +1 -0
- package/fixtures/minimal-pass/locales/en.json +1 -0
- package/fixtures/minimal-pass/manifest.json +1 -0
- package/fixtures/minimal-pass/server/entry.mjs +105 -0
- package/fixtures/storefront-forms/defaults/regions/header.json +1 -0
- package/fixtures/storefront-forms/defaults/settings.json +1 -0
- package/fixtures/storefront-forms/defaults/templates/404.json +1 -0
- package/fixtures/storefront-forms/defaults/templates/cart.json +1 -0
- package/fixtures/storefront-forms/defaults/templates/collection.json +1 -0
- package/fixtures/storefront-forms/defaults/templates/index.json +1 -0
- package/fixtures/storefront-forms/defaults/templates/product.json +1 -0
- package/fixtures/storefront-forms/defaults/templates/search.json +1 -0
- package/fixtures/storefront-forms/locales/en.json +1 -0
- package/fixtures/storefront-forms/locales/th.json +1 -0
- package/fixtures/storefront-forms/manifest.json +1 -0
- package/fixtures/storefront-forms/server/entry.mjs +105 -0
- package/fixtures/storefront-hooks/client/client-manifest.json +21 -0
- package/fixtures/storefront-hooks/client/entry.css +1 -0
- package/fixtures/storefront-hooks/client/entry.js +24 -0
- package/fixtures/storefront-hooks/defaults/regions/header.json +1 -0
- package/fixtures/storefront-hooks/defaults/settings.json +1 -0
- package/fixtures/storefront-hooks/defaults/templates/404.json +1 -0
- package/fixtures/storefront-hooks/defaults/templates/collection.json +1 -0
- package/fixtures/storefront-hooks/defaults/templates/index.json +1 -0
- package/fixtures/storefront-hooks/locales/en.json +1 -0
- package/fixtures/storefront-hooks/manifest.json +1 -0
- package/fixtures/storefront-hooks/server/entry.mjs +79 -0
- package/fixtures/violations/client/client-manifest.json +1 -0
- package/fixtures/violations/defaults/regions/header.json +1 -0
- package/fixtures/violations/defaults/settings.json +1 -0
- package/fixtures/violations/defaults/templates/index.json +1 -0
- package/fixtures/violations/entries/async-iterable-body.mjs +105 -0
- package/fixtures/violations/entries/browser-only-capability.mjs +104 -0
- package/fixtures/violations/entries/deferred-ambient-access.mjs +104 -0
- package/fixtures/violations/entries/depends-on-harness-scope.mjs +104 -0
- package/fixtures/violations/entries/fetch-without-network.mjs +104 -0
- package/fixtures/violations/entries/forges-array-push.mjs +114 -0
- package/fixtures/violations/entries/forges-json-stringify.mjs +112 -0
- package/fixtures/violations/entries/forges-object-then.mjs +130 -0
- package/fixtures/violations/entries/forges-to-json.mjs +114 -0
- package/fixtures/violations/entries/grants-own-capability.mjs +106 -0
- package/fixtures/violations/entries/lowercase-tenant-leak.mjs +106 -0
- package/fixtures/violations/entries/nondeterministic-output.mjs +104 -0
- package/fixtures/violations/entries/reads-date-now.mjs +104 -0
- package/fixtures/violations/entries/reads-new-date.mjs +104 -0
- package/fixtures/violations/entries/redefines-prototype-method.mjs +106 -0
- package/fixtures/violations/entries/renders-tq-csrf-field.mjs +104 -0
- package/fixtures/violations/entries/renders-tq-form-field.mjs +104 -0
- package/fixtures/violations/entries/response-too-large.mjs +105 -0
- package/fixtures/violations/entries/retains-tenant-state.mjs +105 -0
- package/fixtures/violations/entries/signed-in-actions-customer.mjs +103 -0
- package/fixtures/violations/entries/signed-in-harness-scope.mjs +104 -0
- package/fixtures/violations/entries/slow-cold-render.mjs +106 -0
- package/fixtures/violations/entries/slow-signed-in-render.mjs +104 -0
- package/fixtures/violations/entries/slow-store-b-render.mjs +104 -0
- package/fixtures/violations/entries/stashes-on-date-prototype.mjs +104 -0
- package/fixtures/violations/entries/stashes-on-json.mjs +104 -0
- package/fixtures/violations/entries/stashes-on-prototype.mjs +104 -0
- package/fixtures/violations/entries/swallows-data-limit.mjs +105 -0
- package/fixtures/violations/locales/en.json +1 -0
- package/fixtures/violations/manifest.json +1 -0
- package/package.json +69 -4
- package/schema/conformance-report.v2.schema.json +482 -0
- package/schema/storefront-report.v1.schema.json +110 -0
- package/schema/x-tanqory-ai.v0.schema.json +70 -0
- package/shim-digests.json +12 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tanqory
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,147 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tanqory/theme-conformance
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The conformance runner for **Tanqory Theme ABI v2**. It boots a built theme in `workerd` behind the platform's host shim and tells you, before you upload anything, whether the platform will accept it and whether it works like a real storefront.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx theme-conformance run dist/abi --tier all
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Starting a theme? `npm create tanqory-theme@latest my-theme` gives you a project whose `npm run check` runs exactly this.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install --save-dev @tanqory/theme-conformance@alpha # prereleases carry the `alpha` dist-tag
|
|
15
|
+
npx playwright install chromium # suite 5 and the storefront tier drive headless Chromium
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Node ≥ 20. The `workerd` binary comes from npm (`workerd` package, installed for your platform). On Linux CI use `npx playwright install --with-deps chromium`.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
theme-conformance run <theme-dir|artifact-dir> [--tier security|storefront|budgets|all]
|
|
24
|
+
[--out conformance-report.json] [--storefront-out storefront-report.json]
|
|
25
|
+
[--evidence <dir>] [--quiet]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Tier | What it checks | Report |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `security` (default) | the nine ABI suites: manifest + static scan, every route renders with the response contract, no network or clock, limits, hydration parity in Chromium, preview, determinism, runner self-check, multi-tenant isolation | `conformance-report.json` (beside the theme unless `--out`) |
|
|
31
|
+
| `storefront` | "works like a real store", theme-agnostic: variants and `?variant=`, cart through `/_tq/actions`, checkout hand-off, sort / filter / pagination, search and predictive search, th/en with no hard-coded text, market currency, SEO and Product JSON-LD, axe / keyboard / touch targets, `data-tq-node`, live edit over the editor canvas bridge, Add-section previews, consent banner vs preview, the `x-tanqory-ai` profile (warn) | `storefront-report.json` |
|
|
32
|
+
| `budgets` | ABI §8 size budgets plus a lab approximation of mobile performance and accessibility | `storefront-report.json` |
|
|
33
|
+
| `all` | all three | both |
|
|
34
|
+
|
|
35
|
+
Exit codes: `0` every selected tier passed · `1` a tier failed or is incomplete · `2` runner error (bad path, `workerd` or Chromium missing). Every failing check prints its id, the reason and a `fix:` hint; the JSON reports carry the same, machine-readable (`schema/conformance-report.v2.schema.json`, `schema/storefront-report.v1.schema.json`).
|
|
36
|
+
|
|
37
|
+
Check ids, fix hints, the optional `data-tq-*` hooks (`tq-storefront-hooks/1`) and exactly what the budgets tier approximates: [docs/storefront-tier.md](docs/storefront-tier.md) (shipped in the package).
|
|
38
|
+
|
|
39
|
+
- **theme-dir** = `manifest.json` + `server/entry.mjs` (+ optional `client/client-manifest.json`, `defaults/`, `locales/`, `preview/entry.mjs`) — what a theme build writes to `dist/abi`.
|
|
40
|
+
- **artifact-dir** = has `artifact.json`; `files[]`, the Merkle `artifactDigest`, `manifestDigest` and `limits` are recomputed and must match.
|
|
41
|
+
|
|
42
|
+
Programmatic: `import { runConformance, runStorefront } from '@tanqory/theme-conformance'`.
|
|
43
|
+
|
|
44
|
+
## How it works
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Node (runner) ──spawn──▶ workerd 1.20261006.1, compatibilityDate 2025-09-01 (no flags), globalOutbound = egress sink
|
|
48
|
+
├─ shim.mjs host shim: sanitise globals → golden snapshot → tripwires → clocks
|
|
49
|
+
├─ tq-config generated: §11 allow/deny lists, LIMITS from @tanqory/theme-abi
|
|
50
|
+
└─ theme-entry the artifact's server/entry.mjs, dynamic-imported on /__tq/init
|
|
51
|
+
◀── binary envelope (meta JSON + raw body) per /__tq/render ──┘
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The shim builds a `RenderContext` from deterministic store fixtures (`src/fixtures/stores/{alpha,beta}.json`, `mode: 'check'`). `ctx.capabilities` and the working surface of `ctx.data`/`ctx.actions` are `manifest.capabilities.server ∩ store.capPlan`; anything else throws `CapabilityError` synchronously. Data calls, sub-requests, rounds (macrotask-bounded, §3 S7), `network:` calls, log lines/bytes and body bytes are counted in the isolate and enforced with `DataLimitError` / truncation at the hard column of the limits table. Personalising reads (`accept-language`, `user-agent`, `sec-ch-ua*`, `x-tq-country`, `hasCart`, `isSignedIn`, `consent`, cart/customer data) are tracked through getters and a `Headers` proxy.
|
|
55
|
+
|
|
56
|
+
## Suites — what is real, what is not
|
|
57
|
+
|
|
58
|
+
| # | Suite | Status in P1 | Notes |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| 1 | Static | **real** | manifest via `@tanqory/theme-abi` `validateManifest` (schema + semantic codes) plus: JCS canonical bytes, byte/count limits, forms, `requires`, **field-type registry the manifest declares (`engines["tanqory-field-types"]`, `1` or `2`, default `1`; Appendix B / B.2): membership, per-type constraints, required keys, every stored value** via `@tanqory/theme-abi@2.0.0-alpha.3` `FIELD_TYPES_BY_REGISTRY` + `validateFieldValue({registry, origin: 'theme', artifactFiles})` (`src/field-types.ts`; registry-2 `asset:client/assets/<file>` must name a file in the artifact), static scan (espree + eslint-scope: imports, `import()`, `import.meta`, `require`, `eval`/`Function`, free identifiers outside §11), client-manifest digests/SRI, `defaults/` + `locales/`, `manifest` export == file bytes (S3), golden `globalThis` (every §11 name present, no denied name reachable), module-top-level ambient access, init time (`init_timeout`). |
|
|
61
|
+
| 2 | Render fixtures | **real** | every `routes[kind]` + template, expected status, §5 response contract (content-type allowlist, `set-cookie`, loosened CSP, `location`), per-content-type checks (HTML: `<html lang>`, canonical, assets under `assets.base` with `integrity`, inline scripts hashed via `x-tq-csp-script-hash`, `data-tq-node` ∈ doc; XML well-formed; SVG no `<script>`; JSON parses), actions other than `cart.create`/`consent.set` during GET (A3), capability denials, `DataLimitError`. |
|
|
62
|
+
| 3 | No-network / no-ambient | **real** | recording tripwires on the egress surface (`fetch WebSocket XMLHttpRequest EventSource caches connect`; Impl-1: access recorded, value `undefined`, so `typeof fetch === 'undefined'`); `network:` origin refusals; **every clock path** counted — `Date.now()`, `performance.now()`, `new Date()`/`Date()` without arguments, Intl `format`/`formatToParts` without a date; **post-render ambient access** (timers a render scheduled run with attribution; any tripwire/clock hit outside a render after init fails); timers still pending after a 250 ms grace are cleared and fail; request-time code generation; "everything else absent" re-snapshot. |
|
|
63
|
+
| 4 | Limits | **real with proxies** | 5 home renders + every other render, p95. **CPU rows (`cpuPerRenderMs`, `moduleInitCpuMs`) use the attempt rule** (theme-abi §8, security §4.4 v1.10.8): elapsed time inside the isolate is an upper bound on CPU, so an attempt = a fresh isolate rendering every sample (cold one first); the row passes if every sample of one of at most three attempts is within the hard limit — never the best single sample across attempts. The samples are **every render the run counts** — suite 2's routes, the five limit samples, suite 7's determinism pairs and every `VaryKey` variant, the pages suite 5 loads, and suite 9's A-then-B renders in its own isolate — so suite 4 is evaluated last; all its other runtime rows cover the same renders. Attempt 1 is the run itself (two isolates: the run's and suite 9's); a further attempt is two fresh isolates rendering the same jobs in the same order, made only when a sample is over; an attempt with fewer samples than the first cannot pass. `measurements[]` records `method: "wall-clock-in-isolate"`, `upperBound: true`, `attemptCount`, and per attempt every sample and the load average. The harness no longer turns one slow sample into a `limit_cpu` error value (only a render that never yields is killed and reported as such). Every hard-limit breach must surface as the **ABI error value** (capability-security §6.4a): 5xx + `x-tq-abi-error: <code>` (closed 13-code list), empty body (JSON on preview) — asserted on header + code. **A DataLimitError the theme swallowed still fails**; data-call and sub-request measurements count every attempt, including ones refused past the cap. `responseBytes` = bytes the theme tried to send. CPU per render / init are **wall-clock proxies**, labelled where `limit_cpu` is assigned (`abiErrorBasis`). Memory 128 MiB `not_measured` (runtime-enforced, D2). |
|
|
64
|
+
| 5 | Hydration parity | **real** (P2) | `client.hydration: none` → `not_applicable` (S6). Otherwise every HTML route/template is rendered by the workerd isolate and loaded in headless Chromium (**Playwright 1.63.0, exact pin**). The runner plays the gateway: serves the page with the §5 platform CSP (render's `x-tq-csp-script-hash`/`x-tq-csp-style-hash` merged, `style-src-attr 'none'`, no `'unsafe-inline'`, `report-uri` to a runner path), serves `assets.base` from `client/`, answers same-origin `/_tq/data/*` + `/_tq/actions/*` through the isolate under `manifest.capabilities.browser ∩ capPlan` (JCS `a` param enforced), aborts everything else. Fails on: script/style/connect/frame/beacon outside `assets.base` and `/_tq/*`; image/media/font whose exact URL is not in `ctx.data` results, `ctx.content.page` or `ctx.content.settings` (`asset:client/assets/<f>` resolved to `${assets.base}/assets/<f>` before render); any other request type outside the two places (fail-closed); any CSP violation (report-uri **and** `securitypolicyviolation`); any `style` attribute or inline `on*` handler in the live DOM (CDP); console errors; uncaught exceptions/rejections (CDP + a capture listener installed before page JS, so a theme `preventDefault()` cannot hide them); hydration not completing (`load` + 500 ms network quiet, cap 10 s), after which recording continues for a fixed **3 s window**; every deferred-scheduling API is tracked by the init script (setTimeout/setInterval, rAF, requestIdleCallback, `AbortSignal.timeout`, `scheduler.postTask`/`yield`, MessageChannel and `window.postMessage` self-loops; a close probe reads finite running CSS animations/transitions, playing media, open WebSocket/EventSource) and anything pending at window close ⇒ `timers-drained` skipped ⇒ suite `incomplete`. **Generic rule:** the page then stays open 2 s with routes and CSP listeners live; ANY request, CSP violation or Intersection/Resize/Mutation observer callback in that grace ⇒ `no-late-activity` skipped (`incomplete`), and a disallowed late request also fails — whatever API scheduled it. Chromium runs with `--autoplay-policy=no-user-gesture-required` so media handlers execute while observed; route handlers and console events drained before close, `events-drained`); `RTCPeerConnection` construction (connect); `<link rel=preconnect|dns-prefetch|prefetch|preload|modulepreload|prerender>` targets present at any time (MutationObserver + final DOM) under the origin rule (`resource-hints`); post-hydration `<html>` (attributes, `<head>`, `<body>`) ≠ SSR `<html>` outside `PARITY_ATTRIBUTE_ALLOWLIST` (comments and whitespace-only text ignored; descendants of `<div data-tq-client-slot>` excluded, the slot element itself must match, and a slot with any server-rendered child node fails `client-slots-empty` — Hyd-2); (recorded, never a failure) client initial bytes **actually fetched** — gzip -9 of script/style served from `assets.base` before hydration completed — as the **budget-only** §8 row `clientInitialTransferredGzipBytes` (250 KiB budget, no hard limit; over budget ⇒ `budget_exceeded`, suite still passes). Chromium cannot launch → `incomplete`. |
|
|
65
|
+
| 6 | Preview profile | **half → incomplete** | `renderSection` per component ≤ 200 ms when `preview/entry.mjs` exists; the theme-abi §7.3 canvas protocol is not exercised yet, so such a theme is `incomplete` (not passed). No preview entry → `not_applicable`. |
|
|
66
|
+
| 7 | Determinism & cache safety | **real** | **every route** rendered twice, byte-identical on the same runtime version with the same ctx (Conf-2); home hash recorded with `runtimeVersion` in `report.determinism`; two header variants per `VaryKey`; differing bodies without `vary`/`private` fail (B3). |
|
|
67
|
+
| 8 | Negative | **real — runner self-check** (Conf-4) | runs **first**, before suites 1–7 and 9, against hostile probes built into the runner: Node import, CPU loop (`limit_cpu`), 10 MiB body (`limit_response`), `set-cookie`, loosened CSP, `vary`-less personalisation, `cart.addLines` in GET (`not_allowed_in_render`), browser cap `data:customer`. Recorded `reason: self_check` plus `report.selfCheck {runner, version, workerd, passed}` for the signer to attest. |
|
|
68
|
+
| 9 | A-then-B tenancy | **real** | fresh isolate; store A renders (all routes, cart + signed-in) then store B; B's body, headers and log scanned for A's markers **case-insensitively and NFKC/whitespace-normalised**; for **every §11 allowed global and its `.prototype`**, `Function.prototype`, the iterator prototypes, `%TypedArray%` and `globalThis` with its prototype chain (targets resolved to the ORIGINAL objects before the theme runs), every own key (names + symbols) is fingerprinted by **descriptor kind, flags and value identity**. Snapshots before/after are diffed into `added` / `removed` / `changed`, and **any diff fails**. A same-name replacement such as `String.prototype.trim = function trim…` is `changed`. Export values are compared. Module-private `let` state is visible only through its effects. |
|
|
69
|
+
|
|
70
|
+
### Anti-forgery (security review HIGH, fixed in 0.1.0-alpha.2)
|
|
71
|
+
|
|
72
|
+
The theme under test runs in the same isolate as the host shim and can mutate any built-in. Up to 0.1.0-alpha.1 the shim called through those built-ins, so a theme could drop its own violations from the records the runner reads. The shim (`src/workerd/shim.mjs`) now works like this after `import('theme-entry')`:
|
|
73
|
+
|
|
74
|
+
- **Captured intrinsics.** Every intrinsic it uses (`JSON.stringify`/`parse`, `Object.*`, `Reflect.*`, Array/String methods, `Map`/`Set`, `Promise.prototype.then`, TypedArray/Response/Headers/stream/URL accessors…) is captured into a frozen `$` table before the theme evaluates, and uncurried with a captured `Function.prototype.call`.
|
|
75
|
+
- **No theme-reachable array or iteration paths.** Arrays are appended with an own define, never `Array.prototype.push` (index setters) or `map`/`filter`/`slice` (`Symbol.species`). There is no `for…of`, spread or destructuring over arrays, Sets or Maps at run time.
|
|
76
|
+
- **Null-prototype objects.** Descriptors, host-parsed jobs and every object the shim resolves a promise with have a null prototype.
|
|
77
|
+
- **Own JSON encoder.** Records are serialised by the shim's own encoder, which reads own data properties only and never calls `toJSON`. Theme-supplied values are structured-cloned before entering a record.
|
|
78
|
+
- **Locked `then`.** `Object.prototype.then` is a non-writable, non-configurable data property with value `undefined`. `Promise.prototype.then` and `.constructor` are locked. Responses returned to workerd carry an own `then: undefined`. `await {}`, `Promise.resolve({})` and `JSON.stringify({})` are unchanged (`test/shim-runtime.test.ts`).
|
|
79
|
+
- **Private capability set.** `ctx.capabilities` is a copy; the shim checks its own set.
|
|
80
|
+
|
|
81
|
+
Exploit fixtures (each one failed to be caught on alpha.1 and is caught now; see `test/violations.test.ts`):
|
|
82
|
+
|
|
83
|
+
| Fixture | Attack | alpha.1 | now |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| `forges-json-stringify` | replace `JSON.stringify` to drop clock/tripwire records, read `Date.now()` | passed | suite 3 fails |
|
|
86
|
+
| `forges-array-push` | patch `Array.prototype.push`/`map` to drop tripwire records, touch `fetch` | passed | suite 3 fails |
|
|
87
|
+
| `forges-to-json` | `Object.prototype.toJSON` rewrites records, read `Date.now()` | passed | suite 3 fails |
|
|
88
|
+
| `forges-object-then` | `Object.prototype.then` swaps the envelope Response for a forged one | passed | init throws (locked), suite 1 fails |
|
|
89
|
+
| `redefines-prototype-method` | `String.prototype.trim` replaced by a same-name function during render | passed | suite 9 fails (`changed: [trim]`) |
|
|
90
|
+
| `grants-own-capability` | `ctx.capabilities.add('data:customer')`, then use it | escalated (caught only incidentally by suite 9) | suite 2 fails |
|
|
91
|
+
|
|
92
|
+
**Same object model as production (required).** `'then' in {}` is `true`; `obj.then = f` on an ordinary object, and any redefinition of `Object.prototype.then` or `Promise.prototype.then`/`.constructor`, throw `TypeError`. Object literals with `then` and `Object.defineProperty(obj, 'then', …)` still work. The production loader (storefront-edge `runtime/shim.mjs`) must apply the identical locks before the theme module is imported, so a theme never sees different behaviour in conformance than on the edge.
|
|
93
|
+
|
|
94
|
+
### Suite status and `passed`
|
|
95
|
+
|
|
96
|
+
Suite status is `pass | fail | incomplete | not_applicable`. `passed` is true only when **every** suite is `pass` or `not_applicable`; a suite with a check the runner could not perform is `incomplete` and makes `passed` false (the signer requires every suite to pass, theme-artifact-v1.md §6 step 2). CLI exit code 1 covers both `fail` and `incomplete`.
|
|
97
|
+
|
|
98
|
+
### Production shim — the results rest on it (report format 2, security §4.2 v1.10.8)
|
|
99
|
+
|
|
100
|
+
Security §4.2: *"'the same shim' … means the same bytes are executed, not that two programs were shown to agree. Parity of rendered output between a test harness and the production shim on fixture routes is a useful regression check and is **not** certification."*
|
|
101
|
+
|
|
102
|
+
**Where this runner stands (0.1.0-alpha.9).** Every render a suite counts is made twice: on the instrumented harness (`src/workerd/shim.mjs`) and in the edge's own bundle built with the **current** listed shim (`shim-digests.json`, today `83968efd…`). The report is **format 2** (`theme-artifact-v1.md` §2.1, `schema/conformance-report.v2.schema.json` = the contract's file) and names that shim as `shimDigest`. A result is marked `producedBy: "production-shim"` only when what it rests on was observed on the production bundle (`src/production-shim/evidence.ts`):
|
|
103
|
+
|
|
104
|
+
| Evidence | Where it is observed |
|
|
105
|
+
|---|---|
|
|
106
|
+
| outcome, status, bytes of every counted render | the caller (`bridge.mjs`), equal to the harness's (regression check below) |
|
|
107
|
+
| tripwires (H3), clock reads (H4), timers outliving a render (H9), intrinsic integrity (H10), global removal + `unenforced` names (H1 start report) | the shim's own host-channel record, `x-tq-host-record` (ABI Shim-7 F), decoded per render |
|
|
108
|
+
| data / action calls, rounds, egress | the query stub in `bridge.mjs` |
|
|
109
|
+
| console lines and bytes | the log sink (`log-sink.mjs`), attached to every production bundle as its tail worker |
|
|
110
|
+
| elapsed time per render, isolate start | the caller, around the call, body drained (`bridge.mjs`) — an upper bound on the isolate's own time; the shim exposes no in-isolate timer (H8 is not in `83968efd…`). The report's `method` is the contract's single value `wall-clock-in-isolate`; each CPU row's `note` says where the figure was taken |
|
|
111
|
+
|
|
112
|
+
Counted renders are made with `ctx.mode` `'serve'` (ABI §10), on both programs. Suite 1 adds `production-shim:module:evaluates` (the bundle's isolate start), suite 3 the `production-shim:ambient:*` checks over every record, suite 7 the B3 verdict on the shim's `x-tq-render-report`, suite 8 the four runtime probes with the hostile bundle in the edge's form (`production-shim:self-check:*`), suite 9 the A-then-B leak scan on the production bodies and `production-shim:tenancy:intrinsics-unchanged`. Suite 4's runtime rows (CPU attempts in fresh production isolates included) are measured on the production renders; `memoryBytes` is not reported (nothing measures it; rule 6 lets it be missing, D2). Each suite carries `production-shim:basis:suite-<id>`: `pass`, or `not_applicable` listing what was not observed on the production shim, in which case the suite is `producedBy: "other"` and the report cannot back a production-key signature. Suite 6 (`renderSection`) has no production counterpart (H13), so an artifact with `preview/entry.mjs` is never production-signable. `globals` is the shim's start report; `snapshot` (names left reachable) is the harness's, taken in the same workerd at the same date under the same Appendix C.2 rule. **Listing a shim here is still not qualifying it**: the signer's own list of qualified digests (build-service `src/config/qualified-shims.ts`) decides whether a format-2 report may back a production key.
|
|
113
|
+
|
|
114
|
+
- **Bytes.** `src/production-shim/shims/<shimDigest>/host.mjs` + `shim.mjs` are the edge's files, byte for byte, one directory per digest; never edited here. `node scripts/sync-production-shim.mjs <storefront-edge clone> [<rev>]` copies them (`--check` compares). `shimDigest` is the edge's construction (`core/bundle.ts` `shimFromSources`): `sha256("tanqory-wfp-shim/1\n" + "<sha256(bytes)> <byteLength> <name>\n"` per module, sorted by UTF-8 name`)` over `host.mjs` + `shim.mjs`.
|
|
115
|
+
- **`shim-digests.json`** (package root, shipped; `listedShimDigests()` / `shimDigestList()`): `{ format: "tanqory-shim-digests/2", algorithm, basis: "parity-checked", basisDescription, listed: [{ digest, status: "current" | "previous", source, since, qualified: false }] }`. *Listed* means: this build carries those bytes and runs the regression check below under them. It does not mean certified, and no listed shim has passed shim qualification. The runner refuses to start when the list and the shim directories disagree; `test/shim-digest.test.ts` recomputes every digest.
|
|
116
|
+
- **Regression check (`<label>:production-shim:<digest[0:12]>:render|status|body`).** Every render a suite counts — suite 2 (every route, including one the harness attributes to suite 4), suite 4 (the limit samples), suite 5 (the pages the browser loads), suite 7 (both determinism renders of every route and both variants of every `VaryKey`: headers, `hasCart`, `isSignedIn`, consent) and suite 9 (store A then store B, in fresh production isolates) — is repeated in the edge's bundle (`host.mjs` main module, `shim.mjs`, generated `tq-meta.mjs`, the theme as `theme/entry.mjs`), once per listed digest. `bridge.mjs` plays the loader and answers the shim's one outbound path from the same fixture client. `render` fails when exactly one of the two programs fails to render or the bundle misbehaves (identity, egress); `status` and `body` must be equal. A failure fails the owning suite. Suite 6 (`renderSection`) has no production counterpart and is not repeated.
|
|
117
|
+
- **Compatibility date.** Every worker the runner starts — the harness, the production bundles, the bridge, the sink — runs at **`2025-09-01`, no flags**: the value the ABI rules (§10, Appendix C.4) and the edge builds with; `runner.compatibilityDate` / `runner.compatibilityFlags` record it. It is imported directly from `@tanqory/theme-abi` (`RUNTIME_COMPATIBILITY_DATE` / `RUNTIME_COMPATIBILITY_FLAGS`).
|
|
118
|
+
- **Shim parity probe (`shim-parity:<digest[0:12]>:<id>`, suite 8).** A built-in probe theme reports the global surface and a set of `ctx` behaviours from inside `render()`, on the harness and under each listed shim. Every observation that differs is an **informational** check (`not_applicable`, `details` = both sides; name lists as what each side has that the other lacks), and `…:summary` carries the counts and the list of ids. Findings never change a status. Today: **22 of 85** observations differ under `83968efd…` (PR #43: the same 22 ids as `5029d5e1…`) and under `5029d5e1…` (P3-9 / PR #34: H1 live global removal eliminates 14 dangerous differences including `WeakRef`, `FinalizationRegistry`, `Function`, `eval`, `scheduler`, and `sendBeacon`), **36 of 85** under `4a50141a…` and **35 of 85** under `6b124cdd…`. `test/parity-probe.test.ts` pins the id list, so the gap only changes in a reviewed diff.
|
|
119
|
+
- **Listed now (`0.1.0-alpha.9`):** `83968efd…c1e7` `current` (storefront-edge PR #43 / `c2a6004`: `digest`, `importKey`, `sign` and `verify` read each member they validate once and pass the real WebCrypto values the shim built, closing a getter TOCTOU); `5029d5e1…733` `previous` (storefront-edge PR #34 / `7e7464a`: `@tanqory/storefront-shim` package with H1, H3, H4, H9, H10 and Shim-7 crypto reduction); `4a50141a…10496` `previous` (storefront-edge PR #11, `f1f7d69`); and `6b124cdd…84f49` `previous` (edge `main` `47998e7`). Every counted render is repeated under all four, and the parity probe reports all four.
|
|
120
|
+
- **Two-phase upgrades (§4.2 steps 1 and 5, §6.6.3).** Adding a shim = one directory + one entry (it becomes `current`, the one it replaces `previous`); both are run until the edge has flipped and the old entry is dropped.
|
|
121
|
+
|
|
122
|
+
### Fixtures
|
|
123
|
+
|
|
124
|
+
- `fixtures/minimal-pass` — zero-JS theme; every suite `pass` except 5/6 `not_applicable`.
|
|
125
|
+
- `fixtures/storefront-forms` — zero-JS storefront built only on platform semantics (links, `?variant=`, `?sort=`, `?page=`, a declared `cart` form whose POST render calls `ctx.actions.cart`); the storefront tier's product, cart, collection and search checks pass on it, while `fixtures/minimal-pass` fails that tier (`test/storefront.test.ts`).
|
|
126
|
+
- `fixtures/storefront-hooks` — hydrating storefront for the hook forms the spec allows: theme setting `sortControl` puts `data-tq-sort` on a container before its `<select>`, on the `<select>`, or on a container of `?sort=` links; the client applies live edits in place to a CSS-uppercased heading (`liveEdit: reload` stores the value and reloads instead, which `editor:live-edit` rejects). `test/helpers.ts` `storefrontHooksDir()` writes the settings.
|
|
127
|
+
- `fixtures/hydration-pass` — hydrating theme (client entry + CSS + lazy chunk, hashed inline `<style>`, `/_tq/data` calls, product image from `ctx.data`); every suite `pass` except 6 `not_applicable`.
|
|
128
|
+
- `fixtures/hydration-violations/over-hard-limit` — the same chunk imported **statically** and declared in `imports`: the static row `clientInitialGzipBytes` exceeds its 400 KiB hard limit and **suite 4 fails** (a 26 MiB `clientTotalRawBytes` case is generated in `test/limits-class.test.ts`).
|
|
129
|
+
- **Budget warnings.** A row over its budget (status `budget_exceeded`) is not a failure. It is reported as a structured warning `{code: 'budget_exceeded', name, unit, value, budget, hard, over}`: on the owning check as `details.warning` (`limits:<name>`), from `budgetWarnings(report)` (exported), and as a `[WARN]` line in the CLI. The report's top level is unchanged.
|
|
130
|
+
- `fixtures/hydration-violations/over-budget` — NOT a violation since limits 2.1: a dynamically imported 447 KiB-gz chunk (generated by `scripts/gen-heavy-chunk.mjs`), invisible to the static suite-4 row; suite 5 passes and reports `clientInitialTransferredGzipBytes` `budget_exceeded`.
|
|
131
|
+
- `fixtures/hydration-violations/<case>/client/` — overlays on `hydration-pass`, each failing exactly suite 5: `off-origin-script` · `beacon` · `image-not-in-data` · `style-attribute` · `inline-handler` · `hydration-exception` · `insert-outside-slot` · `slot-server-children` (server overlay) · review probes `delayed-offorigin-image` · `delayed-products-fetch` · `delayed-inline-script` · `head-meta-added` · `title-changed` · `html-attrs-changed` · `webrtc-peer` · `preconnect-late`; `timer-beyond-window` must be `incomplete`; deferred-class overlays (each `fail` or `incomplete`): `abort-signal-timeout` · `scheduler-posttask-delay` (review probes) · `scheduler-yield-loop` · `interval-beyond-window` · `raf-loop` · `idle-callback-loop` · `message-channel-loop` · `window-postmessage-loop` · `intersection-observer-late` · `resize-observer-late` · `mutation-observer-late` · `animationend-late` · `transitionend-late` · `media-ended-late` · `media-timeupdate-late` · `websocket-offorigin` · `eventsource-offorigin` . `pnpm fixtures:sync` derives every `client-manifest.json` size/digest/integrity.
|
|
132
|
+
- `fixtures/violations/entries/*.mjs` — one file per violation, same manifest, each failing exactly its suite:
|
|
133
|
+
`browser-only-capability` (2) · `fetch-without-network` (3) · `reads-date-now` (3) · `reads-new-date` (3) · `deferred-ambient-access` (3) · `response-too-large` (4) · `swallows-data-limit` (4) · `slow-cold-render` · `slow-signed-in-render` · `slow-store-b-render` (4, CPU attempt rule over every counted render) · `async-iterable-body` (2) · `nondeterministic-output` (7; its production renders differ too: 2, 7, 9) · production-shim regression check: `depends-on-harness-scope` (2, 4, 7, 9) · `signed-in-harness-scope` (7, 9) · `signed-in-actions-customer` (7, 9 — throws on both programs) · `retains-tenant-state` (9) · `lowercase-tenant-leak` (9) · `stashes-on-prototype` (9) · `stashes-on-json` (9) · `stashes-on-date-prototype` (9). Plus `fixtures/invalid-url-setting` (1). The last six are the code-review probes that a previous build passed.
|
|
134
|
+
|
|
135
|
+
### Process hygiene
|
|
136
|
+
|
|
137
|
+
Each isolate is the **platform** workerd binary (`require('workerd').default`, never the npm Node launcher) spawned in its own process group (`detached: true`) and killed as a group (`kill(-pid)`), in `finally` blocks, on a hung render, and from a process `exit`/signal handler. `test/shim-runtime.test.ts` asserts no workerd PID survives a normal run or a killed render. Suite 5's Chromium is started with `launchServer` (PID known; Playwright makes it a process-group leader) and registered in the same registry (`src/processes.ts`), which group-kills workerd and Chromium in `finally`, on error, and on `exit`/SIGINT/SIGTERM/SIGHUP. `test/browser-harness.test.ts` asserts no Chromium process or group member survives a full run, and that SIGTERM to a runner holding both a browser and a workerd leaves neither behind. Underneath route interception the browser has no egress: DNS maps to nothing, every connection (loopback included) goes to a closed proxy port, WebRTC UDP is disabled, service workers are blocked; the same test proves neither the internet nor a loopback server is reachable.
|
|
138
|
+
|
|
139
|
+
`manifest.json` files are derived from each entry's `export const manifest` by `pnpm fixtures:sync`; `pnpm fixtures:check` (pre-commit, CI) fails if stale.
|
|
140
|
+
|
|
141
|
+
## Report
|
|
142
|
+
|
|
143
|
+
**Format 2.** `shimDigest` names the production shim; every suite and every measurement carries `producedBy` (`production-shim`, `static` for a §8 static row, `none` for suite 5 / 6 that did not run, `other` otherwise); `runner.compatibilityFlags` is `[]`; the two CPU rows carry `method`, `upperBound`, `attemptCount` and `attempts`. There is no `resultsBasis` (format 1 only): the schema is closed.
|
|
144
|
+
|
|
145
|
+
**Measurements = the ABI §8 table by class** (`limitsVersion` `2.1`, `@tanqory/theme-abi@2.0.0-alpha.4`; `src/limits-table.ts`). `measurements[]` holds exactly the `runtime` and `static` rows, in table order; `host-policy` rows (`networkCallTimeoutMs`, `quarantine*`) are never reported. Static structure rows `blocksPerNode` and `nestingDepth` are suite 1's maxima across manifest presets and every `defaults/` document (a section's blocks are depth 1). `hard: null` marks a budget-only row. A covered row nobody measured is `not_measured` (only `memoryBytes` may be, rule 6); `wallClockPerRenderPreviewMs` (suite 6 renders) and `clientInitialTransferredGzipBytes` (suite 5) are omitted when their suite measured nothing.
|
|
146
|
+
|
|
147
|
+
`schema/conformance-report.v2.schema.json` is the contract's normative schema, byte for byte (`_contracts/conformance-report.v2.schema.json`; the contract's file wins). Top level: `format`, `abiVersion`, `limitsVersion`, `shimDigest`, `passed`, `runner {name, version, gitHead, workerd, chromium, compatibilityDate, compatibilityFlags, cpuMeasurement}` — the canonical runner identity (theme-artifact-v1.md §2.1; `gitHead` from `dist/build-info.json` written by `pnpm build`, else the package's published `gitHead`, else `git rev-parse HEAD`; `chromium` is the version suite 5 drove, `null` only when it launched no browser; `selfCheck` repeats name/version/gitHead/workerd), `artifact {digests}`, `suites[9] {id, status, producedBy, checks[]}`, `measurements[] {name, value, budget, hard, status, producedBy}`, `globals {snapshot, deleted, shadowed, notDeletable, unexpected, enforcement}`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RenderResult } from './workerd/host.js';
|
|
2
|
+
export interface AbiErrorObservation {
|
|
3
|
+
code: string | null;
|
|
4
|
+
status: number | null;
|
|
5
|
+
header: string | null;
|
|
6
|
+
bodyBytes: number;
|
|
7
|
+
problems: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function observeAbiError(r: RenderResult, mode?: 'check' | 'preview'): AbiErrorObservation;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* capability-security-v1.md §6.4a — validation of the ABI error value the host produced.
|
|
3
|
+
* Shape: status 500–599, `x-tq-abi-error: <code>` with <code> from the closed 13-code list,
|
|
4
|
+
* empty body on live/check, diagnostic JSON `{code, message, requestId, releaseId}` on preview.
|
|
5
|
+
*/
|
|
6
|
+
import { ABI_ERROR_CODES, ABI_ERROR_HEADER } from './globals-allowlist.js';
|
|
7
|
+
export function observeAbiError(r, mode = 'check') {
|
|
8
|
+
const header = r.meta.headers.find(([k]) => k.toLowerCase() === ABI_ERROR_HEADER)?.[1] ?? null;
|
|
9
|
+
const code = r.meta.abiError;
|
|
10
|
+
const problems = [];
|
|
11
|
+
if (code !== null) {
|
|
12
|
+
if (!ABI_ERROR_CODES.includes(code))
|
|
13
|
+
problems.push(`code "${code}" is not in the §6.4a closed list`);
|
|
14
|
+
if (header !== code)
|
|
15
|
+
problems.push(`${ABI_ERROR_HEADER} header is ${JSON.stringify(header)}, expected "${code}"`);
|
|
16
|
+
if (r.meta.status === null || r.meta.status < 500 || r.meta.status > 599)
|
|
17
|
+
problems.push(`status ${r.meta.status} is not 500–599`);
|
|
18
|
+
if (mode === 'check' && r.body.length !== 0)
|
|
19
|
+
problems.push(`body must be empty outside preview, got ${r.body.length} B`);
|
|
20
|
+
if (mode === 'preview') {
|
|
21
|
+
try {
|
|
22
|
+
const j = JSON.parse(r.body.toString('utf8'));
|
|
23
|
+
for (const k of ['code', 'message', 'requestId', 'releaseId'])
|
|
24
|
+
if (!(k in j))
|
|
25
|
+
problems.push(`preview diagnostic JSON lacks "${k}"`);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
problems.push('preview body is not diagnostic JSON');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (header !== null) {
|
|
33
|
+
problems.push(`${ABI_ERROR_HEADER} present without a host-side breach (theme-set x-tq-* must be dropped, §5)`);
|
|
34
|
+
}
|
|
35
|
+
return { code, status: r.meta.status, header, bodyBytes: r.body.length, problems };
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=abi-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abi-error.js","sourceRoot":"","sources":["../src/abi-error.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAW1E,MAAM,UAAU,eAAe,CAAC,CAAe,EAAE,OAA4B,OAAO;IAClF,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;IAC9F,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAA;IAC5B,MAAM,QAAQ,GAAa,EAAE,CAAA;IAC7B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,IAAI,CAAE,eAAqC,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,IAAI,mCAAmC,CAAC,CAAA;QAC3H,IAAI,MAAM,KAAK,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,gBAAgB,cAAc,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,IAAI,GAAG,CAAC,CAAA;QACjH,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG;YAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,MAAM,iBAAiB,CAAC,CAAA;QACjI,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAA;QACxH,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAA4B,CAAA;gBACxE,KAAK,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,CAAC;oBAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;wBAAE,QAAQ,CAAC,IAAI,CAAC,kCAAkC,CAAC,GAAG,CAAC,CAAA;YACrI,CAAC;YAAC,MAAM,CAAC;gBAAC,QAAQ,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAA;YAAC,CAAC;QAClE,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC,GAAG,gBAAgB,4EAA4E,CAAC,CAAA;IAChH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAA;AACpF,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface LoadedFile {
|
|
2
|
+
path: string;
|
|
3
|
+
bytes: Buffer;
|
|
4
|
+
sha256: string;
|
|
5
|
+
size: number;
|
|
6
|
+
}
|
|
7
|
+
export interface LoadedArtifact {
|
|
8
|
+
rootDir: string;
|
|
9
|
+
kind: 'artifact-dir' | 'theme-dir';
|
|
10
|
+
manifestBytes: Buffer;
|
|
11
|
+
manifestJson: unknown;
|
|
12
|
+
manifestDigest: string;
|
|
13
|
+
entry: LoadedFile & {
|
|
14
|
+
gzipSize: number;
|
|
15
|
+
};
|
|
16
|
+
previewEntry?: LoadedFile;
|
|
17
|
+
clientManifest?: {
|
|
18
|
+
bytes: Buffer;
|
|
19
|
+
json: unknown;
|
|
20
|
+
};
|
|
21
|
+
defaults: {
|
|
22
|
+
settings?: unknown;
|
|
23
|
+
regions: Record<string, unknown>;
|
|
24
|
+
templates: Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
locales: Record<string, unknown>;
|
|
27
|
+
/** Every regular file under the root, sorted by path (byte order). */
|
|
28
|
+
files: LoadedFile[];
|
|
29
|
+
artifactJson?: unknown;
|
|
30
|
+
artifactDigestComputed: string;
|
|
31
|
+
problems: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function sha256Hex(bytes: Buffer | string): string;
|
|
34
|
+
export declare function merkleDigest(header: string, files: readonly {
|
|
35
|
+
path: string;
|
|
36
|
+
sha256: string;
|
|
37
|
+
size: number;
|
|
38
|
+
}[]): string;
|
|
39
|
+
export declare function loadArtifact(dirArg: string): LoadedArtifact;
|
package/dist/artifact.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads the directory under test. Two shapes (CLI: `run <artifact-dir|theme-dir>`):
|
|
3
|
+
* - artifact-dir: has `artifact.json` (theme-artifact-v1.md §1–§4) — files[] digests/sizes and the
|
|
4
|
+
* Merkle `artifactDigest` are recomputed and must match.
|
|
5
|
+
* - theme-dir: `manifest.json` + `server/entry.mjs` (+ optional client/, defaults/, locales/, preview/).
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
9
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
10
|
+
import { gzipSync } from 'node:zlib';
|
|
11
|
+
export function sha256Hex(bytes) {
|
|
12
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
export function merkleDigest(header, files) {
|
|
15
|
+
const sorted = [...files].sort((a, b) => Buffer.compare(Buffer.from(a.path, 'utf8'), Buffer.from(b.path, 'utf8')));
|
|
16
|
+
let preimage = `${header}\n`;
|
|
17
|
+
for (const f of sorted)
|
|
18
|
+
preimage += `${f.sha256} ${f.size} ${f.path}\n`;
|
|
19
|
+
return `sha256:${sha256Hex(preimage)}`;
|
|
20
|
+
}
|
|
21
|
+
const EXCLUDED_FROM_MERKLE = new Set(['artifact.json', 'conformance-report.json']);
|
|
22
|
+
function listFiles(root) {
|
|
23
|
+
const out = [];
|
|
24
|
+
const walk = (dir) => {
|
|
25
|
+
for (const ent of readdirSync(dir, { withFileTypes: true })) {
|
|
26
|
+
if (ent.name === 'node_modules' || ent.name.startsWith('.'))
|
|
27
|
+
continue;
|
|
28
|
+
const p = join(dir, ent.name);
|
|
29
|
+
if (ent.isSymbolicLink()) {
|
|
30
|
+
out.push(p);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (ent.isDirectory())
|
|
34
|
+
walk(p);
|
|
35
|
+
else if (ent.isFile())
|
|
36
|
+
out.push(p);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
walk(root);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function tryJson(bytes, label, problems) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(bytes.toString('utf8'));
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
problems.push(`${label}: not valid JSON (${e.message})`);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function loadArtifact(dirArg) {
|
|
52
|
+
const rootDir = resolve(dirArg);
|
|
53
|
+
if (!existsSync(rootDir) || !statSync(rootDir).isDirectory())
|
|
54
|
+
throw new Error(`not a directory: ${rootDir}`);
|
|
55
|
+
const problems = [];
|
|
56
|
+
const files = listFiles(rootDir).map((abs) => {
|
|
57
|
+
const rel = relative(rootDir, abs).split(sep).join('/');
|
|
58
|
+
const st = statSync(abs, { throwIfNoEntry: true });
|
|
59
|
+
if (!st.isFile()) {
|
|
60
|
+
problems.push(`${rel}: symlinks are not allowed in an artifact (theme-artifact-v1.md §1)`);
|
|
61
|
+
return { path: rel, bytes: Buffer.alloc(0), sha256: sha256Hex(''), size: 0 };
|
|
62
|
+
}
|
|
63
|
+
const bytes = readFileSync(abs);
|
|
64
|
+
return { path: rel, bytes, sha256: sha256Hex(bytes), size: bytes.length };
|
|
65
|
+
}).sort((a, b) => Buffer.compare(Buffer.from(a.path), Buffer.from(b.path)));
|
|
66
|
+
const byPath = new Map(files.map((f) => [f.path, f]));
|
|
67
|
+
const manifest = byPath.get('manifest.json');
|
|
68
|
+
const entry = byPath.get('server/entry.mjs');
|
|
69
|
+
if (!manifest)
|
|
70
|
+
throw new Error(`manifest.json missing in ${rootDir}`);
|
|
71
|
+
if (!entry)
|
|
72
|
+
throw new Error(`server/entry.mjs missing in ${rootDir}`);
|
|
73
|
+
const artifactFile = byPath.get('artifact.json');
|
|
74
|
+
const kind = artifactFile ? 'artifact-dir' : 'theme-dir';
|
|
75
|
+
const artifactJson = artifactFile ? tryJson(artifactFile.bytes, 'artifact.json', problems) : undefined;
|
|
76
|
+
const clientManifestFile = byPath.get('client/client-manifest.json');
|
|
77
|
+
const defaults = { regions: {}, templates: {} };
|
|
78
|
+
const locales = {};
|
|
79
|
+
for (const f of files) {
|
|
80
|
+
if (f.path === 'defaults/settings.json')
|
|
81
|
+
defaults.settings = tryJson(f.bytes, f.path, problems);
|
|
82
|
+
else if (f.path.startsWith('defaults/regions/') && f.path.endsWith('.json'))
|
|
83
|
+
defaults.regions[f.path.slice('defaults/regions/'.length, -5)] = tryJson(f.bytes, f.path, problems);
|
|
84
|
+
else if (f.path.startsWith('defaults/templates/') && f.path.endsWith('.json'))
|
|
85
|
+
defaults.templates[f.path.slice('defaults/templates/'.length, -5)] = tryJson(f.bytes, f.path, problems);
|
|
86
|
+
else if (f.path.startsWith('locales/') && f.path.endsWith('.json'))
|
|
87
|
+
locales[f.path.slice('locales/'.length, -5)] = tryJson(f.bytes, f.path, problems);
|
|
88
|
+
}
|
|
89
|
+
const merkleFiles = files.filter((f) => !EXCLUDED_FROM_MERKLE.has(f.path) && !f.path.endsWith('.dsse.json'));
|
|
90
|
+
const previewEntry = byPath.get('preview/entry.mjs');
|
|
91
|
+
return {
|
|
92
|
+
rootDir, kind,
|
|
93
|
+
manifestBytes: manifest.bytes,
|
|
94
|
+
manifestJson: tryJson(manifest.bytes, 'manifest.json', problems),
|
|
95
|
+
manifestDigest: `sha256:${manifest.sha256}`,
|
|
96
|
+
entry: { ...entry, gzipSize: gzipSync(entry.bytes, { level: 9 }).length },
|
|
97
|
+
...(previewEntry ? { previewEntry } : {}),
|
|
98
|
+
...(clientManifestFile ? { clientManifest: { bytes: clientManifestFile.bytes, json: tryJson(clientManifestFile.bytes, clientManifestFile.path, problems) } } : {}),
|
|
99
|
+
defaults, locales, files,
|
|
100
|
+
...(artifactJson !== undefined ? { artifactJson } : {}),
|
|
101
|
+
artifactDigestComputed: merkleDigest('tanqory-theme-artifact/1', merkleFiles),
|
|
102
|
+
problems,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=artifact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact.js","sourceRoot":"","sources":["../src/artifact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Browser } from 'playwright';
|
|
2
|
+
export declare function playwrightVersion(): string;
|
|
3
|
+
export declare const EGRESS_LOCKDOWN_ARGS: readonly string[];
|
|
4
|
+
export declare class BrowserHarness {
|
|
5
|
+
private server;
|
|
6
|
+
readonly browser: Browser;
|
|
7
|
+
readonly pid: number | undefined;
|
|
8
|
+
private constructor();
|
|
9
|
+
readonly playwright: string;
|
|
10
|
+
get chromiumVersion(): string;
|
|
11
|
+
static launch(): Promise<BrowserHarness>;
|
|
12
|
+
/** Closes the browser; the process group is killed regardless of how close went. */
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless Chromium for suite 5 (theme-abi-v2.md §10.5). Playwright is pinned exactly in package.json
|
|
3
|
+
* (`playwright@1.63.0`), so the Chromium build is pinned with it.
|
|
4
|
+
*
|
|
5
|
+
* Egress: the browser can reach nothing real. Every request is answered by the runner's route handler
|
|
6
|
+
* (page-run.ts) or aborted; underneath that, DNS resolves nothing (`MAP * ~NOTFOUND`), every
|
|
7
|
+
* connection — loopback included — is sent to a closed proxy port, and WebRTC may not use UDP outside
|
|
8
|
+
* a proxy. Service workers are blocked per context so nothing can bypass the route handler.
|
|
9
|
+
*
|
|
10
|
+
* Process hygiene: Chromium is started with `launchServer` so its PID is known; Playwright spawns it as
|
|
11
|
+
* a process-group leader (`detached: true`), and the PID is registered with the shared registry
|
|
12
|
+
* (src/processes.ts) that group-kills on close, on error, and on process exit/signals.
|
|
13
|
+
*/
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
import { chromium } from 'playwright';
|
|
16
|
+
import { killGroup, track } from '../processes.js';
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
export function playwrightVersion() {
|
|
19
|
+
return require('playwright/package.json').version;
|
|
20
|
+
}
|
|
21
|
+
/** Closed port on loopback: every connection the route handler did not answer fails here. */
|
|
22
|
+
const DEAD_PROXY = 'http://127.0.0.1:9';
|
|
23
|
+
export const EGRESS_LOCKDOWN_ARGS = [
|
|
24
|
+
'--host-resolver-rules=MAP * ~NOTFOUND',
|
|
25
|
+
`--proxy-server=${DEAD_PROXY}`,
|
|
26
|
+
// Chromium bypasses the proxy for loopback unless told otherwise; `<-loopback>` removes that bypass.
|
|
27
|
+
'--proxy-bypass-list=<-loopback>',
|
|
28
|
+
'--force-webrtc-ip-handling-policy=disable_non_proxied_udp',
|
|
29
|
+
'--webrtc-ip-handling-policy=disable_non_proxied_udp',
|
|
30
|
+
'--dns-prefetch-disable',
|
|
31
|
+
'--disable-background-networking',
|
|
32
|
+
'--disable-component-update',
|
|
33
|
+
'--disable-domain-reliability',
|
|
34
|
+
// Observe MORE theme code, never less: media plays without a user gesture, so a theme's
|
|
35
|
+
// timeupdate / ended handlers run inside the recording window (a shopper's click would run them).
|
|
36
|
+
'--autoplay-policy=no-user-gesture-required',
|
|
37
|
+
];
|
|
38
|
+
export class BrowserHarness {
|
|
39
|
+
server;
|
|
40
|
+
browser;
|
|
41
|
+
pid;
|
|
42
|
+
constructor(server, browser, pid) {
|
|
43
|
+
this.server = server;
|
|
44
|
+
this.browser = browser;
|
|
45
|
+
this.pid = pid;
|
|
46
|
+
}
|
|
47
|
+
playwright = playwrightVersion();
|
|
48
|
+
get chromiumVersion() { return this.browser.version(); }
|
|
49
|
+
static async launch() {
|
|
50
|
+
const server = await chromium.launchServer({ headless: true, args: [...EGRESS_LOCKDOWN_ARGS], handleSIGINT: false, handleSIGTERM: false, handleSIGHUP: false });
|
|
51
|
+
const proc = server.process();
|
|
52
|
+
track('chromium', proc);
|
|
53
|
+
try {
|
|
54
|
+
const browser = await chromium.connect(server.wsEndpoint());
|
|
55
|
+
return new BrowserHarness(server, browser, proc.pid);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
if (proc.pid !== undefined)
|
|
59
|
+
killGroup(proc.pid);
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Closes the browser; the process group is killed regardless of how close went. */
|
|
64
|
+
async close() {
|
|
65
|
+
try {
|
|
66
|
+
await withTimeout(this.browser.close(), 5_000);
|
|
67
|
+
}
|
|
68
|
+
catch { /* fall through to the kill */ }
|
|
69
|
+
try {
|
|
70
|
+
await withTimeout(this.server.close(), 5_000);
|
|
71
|
+
}
|
|
72
|
+
catch { /* fall through to the kill */ }
|
|
73
|
+
if (this.pid !== undefined)
|
|
74
|
+
killGroup(this.pid);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function withTimeout(p, ms) {
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const t = setTimeout(() => reject(new Error('timeout')), ms);
|
|
80
|
+
p.then((v) => { clearTimeout(t); resolve(v); }, (e) => { clearTimeout(t); reject(e); });
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/browser/harness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs in every frame before any page script (Playwright `addInitScript`). Every intrinsic it uses
|
|
3
|
+
* is captured first, so page JavaScript cannot redirect its reporting.
|
|
4
|
+
*
|
|
5
|
+
* Channel: ONE `console.debug` string per event, `<MARK> <nonce> <kind> <a> <b> <c>`, each field
|
|
6
|
+
* `encodeURIComponent`-encoded. The runner parses `ConsoleMessage.text()` synchronously (no
|
|
7
|
+
* JSHandle round trip, so nothing is lost when the context closes). The per-page nonce lives only in
|
|
8
|
+
* the init script's outer scope; `Function.prototype.toString` of any wrapper does not reveal it, so
|
|
9
|
+
* a page cannot forge records (it could only ever add failures anyway — except for timer
|
|
10
|
+
* `fire`/`clear` records, which is why the nonce exists).
|
|
11
|
+
*
|
|
12
|
+
* Records: csp · exception · console-error (when the theme replaced console.error) · timer-set /
|
|
13
|
+
* timer-fire / timer-clear for every deferred-scheduling API (setTimeout, setInterval,
|
|
14
|
+
* requestAnimationFrame, requestIdleCallback, AbortSignal.timeout, scheduler.postTask/yield,
|
|
15
|
+
* MessageChannel and window.postMessage self-loops) · pending (close probe: running finite
|
|
16
|
+
* animations/transitions, playing media, open WebSocket/EventSource) · observer-cb (Intersection/
|
|
17
|
+
* Resize/Mutation observer callbacks after the window closed) ·
|
|
18
|
+
* hint (every <link rel=preconnect|dns-prefetch|prefetch|preload|modulepreload|prerender> ever
|
|
19
|
+
* inserted or changed, via a MutationObserver started before the parser runs) · webrtc
|
|
20
|
+
* (RTCPeerConnection construction).
|
|
21
|
+
*/
|
|
22
|
+
export declare const MARK = "__tq_conformance__";
|
|
23
|
+
/** Name of the close-probe function the init script defines on window (non-enumerable, locked). */
|
|
24
|
+
export declare const closeProbeName: (nonce: string) => string;
|
|
25
|
+
export declare function initScript(nonce: string): string;
|
|
26
|
+
export interface Marker {
|
|
27
|
+
kind: string;
|
|
28
|
+
a: string;
|
|
29
|
+
b: string;
|
|
30
|
+
c: string;
|
|
31
|
+
}
|
|
32
|
+
/** Parses one console text; `null` unless it carries this page's nonce. */
|
|
33
|
+
export declare function parseMarker(text: string, nonce: string): Marker | null;
|