Skip to content

Commit 458c754

Browse files
committed
fix: keep live integrations disabled during marketplace review
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
1 parent c0ba3c6 commit 458c754

10 files changed

Lines changed: 38 additions & 20 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ env:
1919
PUBLIC_UMAMI_WEBSITE_ID: ''
2020
PUBLIC_UMAMI_SCRIPT_SRC: ''
2121
PUBLIC_STARTER_REQUEST_ENDPOINT: ''
22+
PUBLIC_SHARED_TRACKING_ENABLED: 'false'
2223

2324
jobs:
2425
check:

‎AGENTS.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Registry Stack Marketplace
22

33
Separate Astro static marketplace for marketplace.registrystack.org.
4-
Regular GitHub Pages deployment is authorized. Resolve the repository visibility or plan prerequisite before enabling PAGES_DEPLOYMENT_ENABLED. Never change repository visibility without Jeremi’s explicit approval.
4+
The source repository is public and regular GitHub Pages deployment is authorized. Main may publish only after CI passes and only while PAGES_DEPLOYMENT_ENABLED is exactly true; leave it unset or set it to another value to stop publication.
55

66
- npm run check:ci validates a clean checkout without private assets.
77
- npm run check adds explicit local private-archive verification.
@@ -14,6 +14,7 @@ Regular GitHub Pages deployment is authorized. Resolve the repository visibility
1414
- Source archives stay in ignored content/downloads/, never public/ or dist/.
1515
- Download pack opens a request form. Only confirmed requests count as submitted; no automatic file delivery.
1616
- Keep personal data out of analytics. Local preview must not contact production analytics, bridge, or form endpoints.
17+
- Keep PUBLIC_SHARED_TRACKING_ENABLED false until the protocol-v3 website and documentation changes are ready for coordinated publication. Production builds must also leave analytics and form configuration empty until those integrations are deliberately enabled.
1718
- PublicSchema badges require recorded source provenance. Preserve attribution, licences, and archive integrity.
1819
- Contributions open user-reviewed GitHub drafts. Never send issues automatically.
1920
- Studio uses its checked local starter catalogue. Do not invent a launch or import protocol.

‎README.md‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Registry Stack Marketplace
22

3-
Astro static site for marketplace.registrystack.org, kept local and unpublished for review. The source lives in the private registrystack/marketplace repository. Regular GitHub Pages deployment is prepared, with activation pending the repository visibility or plan prerequisite.
3+
Astro static site for marketplace.registrystack.org. The source is public at https://github.com/registrystack/marketplace. Regular GitHub Pages deployment can publish the verified main-branch artifact after CI passes and the deployment switch is enabled.
44

55
## Review locally
66

@@ -10,9 +10,9 @@ Requires Node 24 or later and npm.
1010
npm run check:ci
1111
npm run dev
1212

13-
Open http://127.0.0.1:4317. The server binds to loopback. The generated site is in dist/. Review builds include noindex metadata, a disallow-all robots.txt, and an X-Robots-Tag header from the local server. Publication requires a deliberate change to those controls.
13+
Open http://127.0.0.1:4317. The server binds to loopback. The generated site is in dist/. Review builds and the initial Pages publication include noindex metadata and a disallow-all robots.txt. The local server also adds an X-Robots-Tag header. Indexing requires a deliberate change to those controls.
1414

15-
The local preview loads no analytics, shared consent bridge, or request endpoint. The request popup can be filled in for review, but Send request is disabled. No production form submissions are needed for verification.
15+
The default build loads no analytics, shared consent bridge, or request endpoint. The request popup can be filled in for review, but Send request is disabled. No production form submissions are needed for verification.
1616

1717
## Catalogue and pages
1818

@@ -45,7 +45,7 @@ The allowlisted marketplace_action event distinguishes download_intent from requ
4545

4646
Optional Mautic page activity uses the existing main-site consent bridge. The prepared integration adds the marketplace to a fresh protocol-v3 consent scope across the website, documentation, and marketplace. Earlier consent does not silently cover the extra property. DNT, GPC, refusal, expiry, and bridge failures keep optional tracking off. The marketplace cannot request the bridge's browser identity. Its form uses the unlinked kiosk request mode, regardless of tracking choice.
4747

48-
The main website and documentation need their matching protocol-v3 preparations published together with any eventual marketplace launch. No such publication has occurred.
48+
PUBLIC_SHARED_TRACKING_ENABLED must also be exactly true before a marketplace build can contact the shared consent bridge. It defaults to false. Keep it false until the main website and documentation have their matching protocol-v3 changes ready for coordinated publication.
4949

5050
## Source archives and verification
5151

@@ -71,18 +71,18 @@ Archive checks prove integrity and native file closure. They do not establish pr
7171

7272
Prepared main-site changes keep its solution stories and leave current starter pages active by default. Only PUBLIC_MARKETPLACE_MIGRATION_ENABLED=true enables the 85 starter redirects and marketplace collection links. The prepared static redirects are HTML meta refresh documents, not HTTP 301 responses. The exact route map can later configure an edge redirect layer. No live routes have changed.
7373

74-
For compatibility with the existing native request form, requests use request_type=starter and starter_id for every free pack, including integration packs. Reporting should treat starter_id as the general pack identifier; the public UI calls these packs. Before enabling live sending, verify that the existing form gateway allows the exact marketplace origin and preserves the canonical marketplace source_page. These hosting and endpoint changes have not been made.
74+
For compatibility with the existing native request form, requests use request_type=starter and starter_id for every free pack, including integration packs. Reporting should treat starter_id as the general pack identifier; the public UI calls these packs. Before enabling live sending, verify that the existing form gateway allows the exact marketplace origin and preserves the canonical marketplace source_page. The initial Pages publication leaves the request endpoint unconfigured, so visitors can fill the dialog but cannot send it.
7575

7676
## Repository and CI
7777

78-
The repository is private at https://github.com/registrystack/marketplace. Marketplace CI runs on pull requests, pushes to main, and manual dispatch. Its build token is read-only. A separate Pages deployment job has the Pages and OIDC permissions required for publication; it runs only after successful checks on main and when PAGES_DEPLOYMENT_ENABLED is exactly true. Official actions are pinned to immutable commits.
78+
The repository is public at https://github.com/registrystack/marketplace. Marketplace CI runs on pull requests, pushes to main, and manual dispatch. Its build token is read-only. A separate Pages deployment job has the Pages and OIDC permissions required for publication; it runs only after successful checks on main and when PAGES_DEPLOYMENT_ENABLED is exactly true. Leaving the variable unset or changing it from true provides an emergency publication stop. Official actions are pinned to immutable commits.
7979

80-
The workflow installs locked dependencies, runs check:ci, executes browser journeys on a temporary loopback server, and checks the fully intercepted request/analytics funnel. It uploads only the verified dist/ site and browser review images, retained for seven days. Preview output rejects ZIPs and other archives, private asset paths, live request/analytics configuration, and indexable HTML.
80+
The workflow installs locked dependencies, runs check:ci, executes browser journeys on a temporary loopback server, and checks the fully intercepted request/analytics funnel. It uploads only the verified dist/ site and browser review images, retained for seven days. The deployment build sets PUBLIC_SHARED_TRACKING_ENABLED to false and leaves Umami and request endpoint configuration empty. Preview output rejects ZIPs and other archives, private asset paths, live request/analytics configuration, and indexable HTML.
8181

8282
The tracking protocol and shared client are pinned by data/tracking-contract.json. The explicit local cross-site integration check compares maintained checkouts without making the standalone CI depend on sibling repositories:
8383

8484
node scripts/check-shared-tracking.mjs --website-root /absolute/website --docs-root /absolute/docs/site
8585

86-
Those pins describe the prepared protocol-v3 integration. They do not claim that the main website and documentation have been deployed with it.
86+
Those pins describe the prepared protocol-v3 integration. They do not establish that the main website and documentation have published it, so shared tracking remains separately build-gated.
8787

88-
The selected host is regular GitHub Pages. Pull requests produce CI review artifacts; main can publish the checked Pages artifact after explicit activation. GitHub currently refuses Pages activation for this private repository on the organization plan. See docs/github-pages.md for activation steps and the Cloudflare DNS configuration. No separate PR hosting service is required.
88+
The selected host is regular GitHub Pages. Pull requests produce downloadable CI review artifacts but no hosted PR environment. Main can publish the checked Pages artifact after explicit activation. See docs/github-pages.md for repository configuration, deployment controls, and the Cloudflare DNS record. No separate PR hosting service is used.

‎docs/github-pages.md‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
The marketplace uses the regular GitHub Pages deployment from the checked main branch. CI still runs on every pull request. It publishes only after checks pass, from main, and when the repository variable PAGES_DEPLOYMENT_ENABLED is exactly true. Pull requests never deploy to the production Pages environment.
44

5-
## Current activation prerequisite
5+
## Repository visibility and private Pages
66

7-
The repository was created private. GitHub rejected Pages activation because the current organization plan does not support Pages for this private repository. The repository must either become public with the owner's explicit approval, or the organization must use a GitHub plan that supports private-repository Pages. Changing source visibility and changing website visibility are different actions; a public website does not require its source to be public on a suitable plan.
7+
This repository is public. GitHub plan support matters if a future repository or fork is private: private-repository Pages requires a plan that includes it, while a public repository can use public GitHub Pages. Changing source visibility and changing website visibility remain separate decisions; a public website does not require public source on a plan that supports private-repository Pages.
88

99
No private archive bytes are tracked or uploaded. The 87 request-only ZIPs remain in ignored content/downloads/. Only static catalogue pages, CSS, and browser scripts enter the checked deployment artifact.
1010

@@ -22,21 +22,23 @@ In the registrystack.org Cloudflare zone:
2222

2323
The equivalent Cloudflare API request body is in [cloudflare-dns.json](cloudflare-dns.json). TTL 1 represents Auto. The CNAME target is the organization hostname, without a repository path.
2424

25-
Because this record is DNS only, GitHub serves the TLS certificate. Cloudflare's proxy SSL mode does not control this hostname.
25+
The DNS-only CNAME has been verified against registrystack.github.io. GitHub serves the TLS certificate, so Cloudflare's proxy SSL mode does not control this hostname. DNS correctness alone does not establish that a Pages deployment is live.
2626

27-
## GitHub activation
27+
## GitHub configuration and deployment
2828

29-
After resolving the repository plan or visibility prerequisite:
29+
The repository uses this configuration:
3030

3131
1. In repository Settings, Pages, select GitHub Actions as the deployment source.
3232
2. Set the custom domain to marketplace.registrystack.org. A CNAME file in this repository is not required by the Actions publishing method.
3333
3. Confirm DNS points to registrystack.github.io and enable Enforce HTTPS when GitHub has issued the certificate. Certificate provisioning can take up to 24 hours.
3434
4. Set the repository variable PAGES_DEPLOYMENT_ENABLED to true.
3535
5. Run Marketplace CI on main, or push a main-branch change. The deploy job consumes the same artifact that passed the checks.
3636

37-
Setting the activation variable to any other value disables publication. GitHub Pages must be enabled first; the flag does not bypass a GitHub plan restriction.
37+
Setting the activation variable to any other value disables publication and can be used as an emergency stop. GitHub Pages must be enabled first; the flag does not bypass a GitHub plan restriction.
3838

39-
The initial deployment retains noindex metadata and disallow-all robots.txt while the content remains under review. Form submissions and analytics remain unconfigured. Removing those review controls and enabling live integrations require a deliberate subsequent change.
39+
The initial deployment retains noindex metadata and disallow-all robots.txt while the content remains under review. The workflow sets PUBLIC_SHARED_TRACKING_ENABLED to false and leaves Umami analytics and the form endpoint unconfigured. Visitors can fill the request dialog, but its Send request button remains disabled. Removing the review controls or enabling any live integration requires a deliberate subsequent change.
40+
41+
Pull requests upload the verified site as a downloadable CI artifact. They do not receive a hosted preview URL or deploy to GitHub Pages.
4042

4143
## Sources
4244

‎scripts/verify-preview-artifact.mjs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function verifyPreviewArtifact(directory) {
2121
pages++;
2222
assert.match(content, /<meta name="robots" content="noindex,nofollow"/);
2323
assert.doesNotMatch(content, /data-(?:endpoint|umami-script-src|umami-website-id)="[^\"]+"/, 'Review builds cannot include live form or analytics configuration.');
24+
assert.doesNotMatch(content, /data-shared-tracking-enabled="true"/, 'Review builds cannot activate shared visit tracking.');
2425
}
2526
};
2627
inspect(root);

‎src/components/MarketplaceTracking.astro‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ const analytics = marketplaceAnalyticsConfig({
1616
websiteId: import.meta.env.PUBLIC_UMAMI_WEBSITE_ID,
1717
scriptSrc: import.meta.env.PUBLIC_UMAMI_SCRIPT_SRC,
1818
});
19+
const sharedTrackingEnabled = import.meta.env.PUBLIC_SHARED_TRACKING_ENABLED === 'true';
1920
---
2021

2122
<div
2223
class="mp-tracking-control"
2324
data-marketplace-analytics
25+
data-shared-tracking-enabled={sharedTrackingEnabled ? 'true' : 'false'}
2426
data-umami-script-src={analytics?.scriptSrc}
2527
data-umami-website-id={analytics?.websiteId}
2628
data-umami-domain={analytics?.domain}
@@ -42,7 +44,7 @@ const analytics = marketplaceAnalyticsConfig({
4244
<div>
4345
<h2 id="marketplace-tracking-title" tabindex="-1">Cookies on Registry Stack</h2>
4446
<p>
45-
We use optional cookies to understand how you use our website, documentation, and marketplace.
47+
When enabled, optional cookies help us understand how you use our website, documentation, and marketplace.
4648
If you contact us, we may link this activity to your enquiry. You can change your choice at any time.
4749
<a href="/privacy/">Privacy policy</a>.
4850
</p>
@@ -87,6 +89,7 @@ const analytics = marketplaceAnalyticsConfig({
8789
const canonicalRuntime = window.location.origin === 'https://marketplace.registrystack.org';
8890
const removeActionTracking = canonicalRuntime ? installMarketplaceActionTracking() : () => {};
8991
const analyticsConfig = document.querySelector<HTMLElement>('[data-marketplace-analytics]');
92+
const sharedTrackingEnabled = analyticsConfig?.dataset.sharedTrackingEnabled === 'true';
9093
const panel = document.querySelector<HTMLElement>('[data-marketplace-tracking]');
9194
const openButton = document.querySelector<HTMLButtonElement>('[data-marketplace-tracking-open]');
9295
const acceptButton = panel?.querySelector<HTMLButtonElement>('[data-marketplace-tracking-accept]');
@@ -115,10 +118,11 @@ const analytics = marketplaceAnalyticsConfig({
115118
const path = panel.dataset.marketplacePath;
116119
const title = panel.dataset.marketplaceTitle;
117120
if (!path || !title) throw new Error('Marketplace tracking configuration is incomplete.');
118-
if (!canonicalRuntime) {
121+
if (!canonicalRuntime || !sharedTrackingEnabled) {
119122
acceptButton.disabled = true;
120123
rejectButton.disabled = true;
121124
localMessage.hidden = false;
125+
if (canonicalRuntime) localMessage.textContent = 'Optional visit tracking is not enabled on this site.';
122126
openButton.addEventListener('click', () => {
123127
panel.hidden = false;
124128
heading?.focus();

‎src/pages/privacy.astro‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Base from '../layouts/Base.astro';
33
44
const title = 'Privacy policy';
55
const description = 'What the Registry Stack Marketplace collects, why, and how to control optional tracking.';
6+
const sharedTrackingEnabled = import.meta.env.PUBLIC_SHARED_TRACKING_ENABLED === 'true';
67
---
78

89
<Base title={title} description={description} screen="privacy">
@@ -45,6 +46,7 @@ const description = 'What the Registry Stack Marketplace collects, why, and how
4546
</p>
4647

4748
<h2>Optional visit tracking</h2>
49+
{!sharedTrackingEnabled && <p>Optional visit tracking is currently disabled on this marketplace. The following controls apply when it is enabled.</p>}
4850
<p>
4951
Optional Mautic tracking relies on your consent. A fresh acceptance covers the main Registry
5052
Stack website, its canonical documentation, and this marketplace for six months. An earlier

‎tests/funnel-browser.mjs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { chromium } from 'playwright';
88
// All canonical hosts below are intercepted. No production service receives a request.
99
const output = mkdtempSync(join(tmpdir(), 'marketplace-funnel-'));
1010
execFileSync(process.execPath, ['node_modules/astro/bin/astro.mjs', 'build', '--outDir', output], {
11-
env: { ...process.env, PUBLIC_STARTER_REQUEST_ENDPOINT: 'https://forms.example.test/form/submit?formId=1&privacy=kiosk', PUBLIC_UMAMI_WEBSITE_ID: '00000000-0000-4000-8000-000000000001', PUBLIC_UMAMI_SCRIPT_SRC: 'https://stats.example.test/script.js' },
11+
env: { ...process.env, PUBLIC_SHARED_TRACKING_ENABLED: 'false', PUBLIC_STARTER_REQUEST_ENDPOINT: 'https://forms.example.test/form/submit?formId=1&privacy=kiosk', PUBLIC_UMAMI_WEBSITE_ID: '00000000-0000-4000-8000-000000000001', PUBLIC_UMAMI_SCRIPT_SRC: 'https://stats.example.test/script.js' },
1212
stdio: 'pipe',
1313
});
1414
const browser = await chromium.launch({ headless: true });
@@ -18,6 +18,7 @@ try {
1818
const submissions = [];
1919
const unexpected = [];
2020
let failRequest = true;
21+
let bridgeRequests = 0;
2122
page.on('pageerror', error => errors.push(error.message));
2223
await page.route('**/*', async route => {
2324
const request = route.request();
@@ -30,6 +31,7 @@ try {
3031
if (url.origin === 'https://stats.example.test') return route.fulfill({ contentType: 'text/javascript', body: 'window.__analytics=[];window.umami={track:(name,data)=>window.__analytics.push({name,data})};' });
3132
if (url.origin === 'https://fonts.googleapis.com') return route.fulfill({ contentType: 'text/css', body: '' });
3233
if (url.origin === 'https://registrystack.org' && url.pathname === '/tracking-bridge/') {
34+
bridgeRequests++;
3335
return route.fulfill({ contentType: 'text/html', body: `<script>addEventListener('message',event=>{const request=event.data;if(request.protocol!=='registrystack-tracking'||request.version!==3)return;event.source.postMessage({protocol:request.protocol,version:3,id:request.id,ok:true,state:{choice:'rejected',generation:'00000000-0000-4000-8000-000000000002',expiresAt:Date.now()+86400000,revision:1}},event.origin)});</script>` });
3436
}
3537
if (url.origin === 'https://forms.example.test') {
@@ -66,6 +68,7 @@ try {
6668
assert.equal(submissions.length, 2);
6769
assert.ok(submissions.every(data => !data.has('mautic_device_id')));
6870
assert.equal(submissions[1].get('mauticform[source_page]'), 'https://marketplace.registrystack.org/packs/professional-licensing/');
71+
assert.equal(bridgeRequests, 0, "Shared tracking stays disabled on the canonical origin until explicitly configured.");
6972
assert.deepEqual(unexpected, []);
7073
assert.deepEqual(errors, []);
7174
console.log('PASS: mocked request popup, failed-request retention, confirmed submission, separate intent/submission analytics, no visitor data in analytics, no production network.');

0 commit comments

Comments
 (0)