Skip to content

Commit b89bcc4

Browse files
dmealingclaude
andcommitted
feat(site): payload builder and its four build gates
`examples/showcase/site-payload.json` is now the single artifact metaobjects.dev renders: 15 snippets plus five registry coordinates, built by `bun run site:payload` and committed so the site's deploy has no build step and what it will publish is reviewable in a diff. The point is not assembly, it is that assembly CANNOT SUCCEED while a claim the site makes is false. Four gates run inside buildPayload, each throwing rather than degrading, so a payload that builds is a payload whose claims held: subsequence a published excerpt must still be an in-order subsequence of the real generated file. Elisions are COMPUTED from the match, so the page cannot imply contiguity it does not have — which is exactly how the landing page came to say "this exact model" while eliding three members. drift fixture the `verify` transcript must still be a FAILING run. A fixture gone green would leave the page showing an error the tool no longer emits — true once, false now, and invisible in a diff because the captured text would simply change. requirements `meta verify` on the showcase must exit 0, so the requirements page's "resolved, not trusted" claim is checked, not repeated. home paths the assembled JSON must carry no absolute user path. HOME_PATH is IMPORTED from transcript.ts, never respelled — two spellings of one rule is how the weaker one ends up being the one running. All four proven by breaking them, per the plan's step 6: renaming a symbol in an excerpt names the snippet, file and failing line; fixing the drift fixture is refused with the reason; a retired `verifiedBy` in a marked region is caught by the LOADER ("retired in 0.24.0"), which is why the highlighter deliberately does NOT gate vocabulary — it is tolerant by design and a throw there was measured at 8 false failures; and a dangling `implementedBy` surfaces ERR_REQUIREMENT_DANGLING_REF. `registries` is five coordinates, never one string: Maven runs on its historical major 7, so a single `version` would be wrong for one of four, and `metamodel` is a third contract again (ADR-0035 Amendment 2). Three departures from the plan, each because the plan's version would have been weaker than it looks: - **`meta verify --prompts templates`**, not a bare `verify`. The showcase's prompt text lives in `templates/`, so a bare run exits 1 on ERR_PARTIAL_UNRESOLVED — the gate would have been permanently red for a reason that has nothing to do with requirements resolving. snippets.ts already carries the same note for the drift capture. - **`Object.keys(payload)` pinned exactly**, not the plan's `(payload as Record<string, unknown>).version === undefined` — which does not typecheck (caught by the scripts/ gate), and which the types make unfailable anyway since SitePayload has no `version` field. The key-set pin catches any stray top-level key, which is the thing actually worth forbidding. - **A bijection test against SNIPPETS.** The plan checks 10 named ids of 15; the registry's own docstring says hand-typing the id set is how an id ends up in the payload with no page referencing it, and only a bijection catches both directions. Also completes the deferral in the previous commit: the gates-lane step is now `gate_site_payload` (its planned name, earned — there is a payload to check) and the release preflight checks payload freshness beside showcase freshness. A marker snippet reports `lang: "yaml"` rather than the registry's value: its content is metadata YAML by construction, and `Lang` — the highlight-code language set — has no member for it, so the registry cannot say so. Verified: 84 tests green across scripts/site (9 new), gates lane 19/19 with `site payload is true`, and `--check` on a fresh tree reports fresh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NynrRND6ZUwGvq3ZUTCfxG
1 parent 1f2fcc8 commit b89bcc4

7 files changed

Lines changed: 454 additions & 9 deletions

File tree

examples/showcase/site-payload.json

Lines changed: 101 additions & 0 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"typecheck": "bun run --filter '*' typecheck",
1414
"test": "cd server/typescript && bun test",
1515
"regen:showcase": "bun scripts/regen-showcase.ts",
16+
"site:payload": "bun scripts/build-site-payload.ts",
1617
"clean": "rm -rf server/typescript/packages/*/dist server/typescript/packages/*/*.tsbuildinfo client/web/packages/*/dist client/web/packages/*/*.tsbuildinfo",
1718
"release": "bun scripts/release.mjs",
1819
"prerelease:publish": "bun scripts/prerelease.mjs"

scripts/build-site-payload.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env bun
2+
/**
3+
* Build (or check) the site payload — the single artifact metaobjects.dev renders.
4+
*
5+
* bun run site:payload # write examples/showcase/site-payload.json
6+
* bun scripts/build-site-payload.ts --check # fail if the committed file is stale
7+
*
8+
* The payload is COMMITTED so the site's deploy has no build step of its own and the
9+
* diff of what the site will publish is reviewable in a pull request. That only means
10+
* something while the committed file matches what a fresh build produces, which is what
11+
* `--check` enforces in the gates lane.
12+
*
13+
* Every gate lives in buildPayload, not here — this file only decides whether to write
14+
* the result or compare it.
15+
*/
16+
import { writeFileSync, readFileSync, existsSync } from "node:fs";
17+
import { relative, resolve } from "node:path";
18+
import { buildPayload } from "./site/payload.js";
19+
20+
const REPO = resolve(import.meta.dirname, "..");
21+
const OUT = resolve(REPO, "examples/showcase/site-payload.json");
22+
const CHECK = process.argv.includes("--check");
23+
24+
const json = `${JSON.stringify(buildPayload(REPO), null, 2)}\n`;
25+
26+
if (CHECK) {
27+
if (!existsSync(OUT)) {
28+
console.error(`✗ ${relative(REPO, OUT)} does not exist — run \`bun run site:payload\` and commit it`);
29+
process.exit(1);
30+
}
31+
if (readFileSync(OUT, "utf8") !== json) {
32+
console.error(
33+
`✗ ${relative(REPO, OUT)} is stale — the site would publish something other than\n` +
34+
` what this repository currently generates.\n` +
35+
` Run \`bun run site:payload\`, review the diff, and commit.`);
36+
process.exit(1);
37+
}
38+
console.log(`✓ ${relative(REPO, OUT)} is fresh`);
39+
} else {
40+
writeFileSync(OUT, json);
41+
console.log(`✓ wrote ${relative(REPO, OUT)}`);
42+
}

scripts/ci-local.sh

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,18 @@ gate_doc_examples() { bun scripts/check-doc-examples.ts && bun scripts/test-doc-
219219
# SCOPED to `scripts/site` — a bare `bun test` at the repo root walks java/python/
220220
# csharp/fixtures and takes many minutes.
221221
#
222+
# It also checks the COMMITTED payload — `examples/showcase/site-payload.json`, the
223+
# single artifact the site renders. Committing it is what makes the site's deploy
224+
# build-step-free and its content reviewable in a diff, and that is only worth anything
225+
# while the committed bytes match a fresh build. Building it re-runs four gates of its
226+
# own (subsequence, drift-fixture-still-fails, requirements-resolve, no-home-path), so
227+
# `--check` is both a freshness check and a re-assertion of every claim the site makes.
228+
#
222229
# Deliberately does NOT run the other four ports: that shells out to mvn/dotnet/uv,
223230
# and this lane is `step_if bun` — guarded on bun alone and included in `--quick`, the
224231
# documented pre-PR command. The release preflight runs `--check --all-ports`, which
225-
# refuses to leave a port out. The site PAYLOAD half of this gate joins here when the
226-
# payload builder lands (plan task 9); today there is no payload to check, and naming
227-
# the gate for one would be a gate reading as more coverage than it has.
228-
gate_site_snippets() { bun_install && bun test scripts/site; }
232+
# refuses to leave a port out.
233+
gate_site_payload() { bun_install && bun test scripts/site && bun scripts/build-site-payload.ts --check; }
229234

230235
# ── repo-root scripts/ typechecks ─────────────────────────────────────────────
231236
# `bun test` transpiles per file and never typechecks, and `bun run --filter '*'
@@ -514,7 +519,7 @@ if want gates; then step "script-name hook collisions" gate_script_name_
514519
if want gates; then step "metamodel-version bump" gate_metamodel_version; fi
515520
if want gates; then step_if bun "peer-range bounds" gate_peer_ranges; fi
516521
if want gates; then step_if bun "shipped doc examples load" gate_doc_examples; fi
517-
if want gates; then step_if bun "site snippets are true" gate_site_snippets; fi
522+
if want gates; then step_if bun "site payload is true" gate_site_payload; fi
518523
if want gates; then step_if bun "scripts/ typecheck" gate_scripts_typecheck; fi
519524
if want gates; then step_if bun "requirements ledger verifies" gate_requirements_ledger; fi
520525
if want gates; then step_if bun "requirements cover vocabulary" gate_requirements_vocabulary; fi

scripts/release.mjs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,12 @@ ok(`lockstep set @ ${current}: ${set.length} packages → ${VERSION}`);
135135
// out; the ci-local gate deliberately runs the bun-only half.
136136
try {
137137
sh("bun scripts/regen-showcase.ts --check --all-ports", { quiet: true });
138-
ok("showcase: committed output matches a pristine regen on all five ports");
138+
sh("bun scripts/build-site-payload.ts --check", { quiet: true });
139+
ok("site payload: showcase fresh on all five ports, payload fresh");
139140
} catch (e) {
140-
die("showcase output is stale, or a port's toolchain is missing — the site would\n" +
141-
" publish a stale claim. Run `bun scripts/regen-showcase.ts --all-ports`, review\n" +
142-
" the diff, and commit before releasing.\n\n" +
141+
die("the site payload is stale, or a port's toolchain is missing — the site would\n" +
142+
" publish a stale claim. Run `bun scripts/regen-showcase.ts --all-ports` and\n" +
143+
" `bun run site:payload`, review the diff, and commit before releasing.\n\n" +
143144
`${e.stdout ?? ""}${e.stderr ?? ""}`);
144145
}
145146

scripts/site/payload.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { describe, test, expect } from "bun:test";
2+
import { resolve } from "node:path";
3+
import { buildPayload } from "./payload.js";
4+
import { SNIPPETS } from "./snippets.js";
5+
6+
const REPO = resolve(import.meta.dirname, "../..");
7+
const payload = buildPayload(REPO);
8+
9+
describe("buildPayload", () => {
10+
test("carries every snippet the site needs", () => {
11+
for (const id of ["showcase-model", "showcase-requirement", "showcase-prompt",
12+
"ts-entity", "java-dto", "kotlin-entity", "csharp-entity",
13+
"python-model", "sql-migration", "verify-transcript"])
14+
expect(payload.snippets[id]).toBeDefined();
15+
});
16+
17+
// The registry's own docstring: hand-typing the id set is how an id ends up in the
18+
// payload with no page referencing it. A bijection is the only check that catches
19+
// BOTH directions — a registered id the builder skipped, and a payload key nothing
20+
// registered.
21+
test("is in bijection with the snippet registry", () => {
22+
expect(Object.keys(payload.snippets).sort()).toEqual(Object.keys(SNIPPETS).sort());
23+
});
24+
25+
test("a generated-code snippet ships its FULL file for expand-to-view", () => {
26+
const s = payload.snippets["ts-entity"]!;
27+
expect(s.full).not.toBeNull();
28+
expect(s.lineCount).toBeGreaterThan(s.inline.split("\n").length);
29+
});
30+
31+
test("a marker snippet has no full file — it is already whole", () => {
32+
expect(payload.snippets["showcase-model"]!.full).toBeNull();
33+
});
34+
35+
// A `whole` snippet's published text IS the file, which is a stricter guarantee than
36+
// any excerpt can make — so there is nothing to expand to, and every line of the
37+
// source must be present rather than a subsequence of it.
38+
test("a whole-file snippet publishes the entire file and expands to nothing", () => {
39+
const s = payload.snippets["sql-migration"]!;
40+
expect(s.full).toBeNull();
41+
expect(s.inline).toContain("CREATE TABLE");
42+
});
43+
44+
test("carries one coordinate per registry, never a single version string", () => {
45+
expect(Object.keys(payload.registries).sort())
46+
.toEqual(["maven", "metamodel", "npm", "nuget", "pypi"]);
47+
// The exact top-level key set, not just `version === undefined`: SitePayload has no
48+
// `version` field, so the compiler already forbids that one, and a test the types
49+
// make unfailable proves nothing. Pinning the whole set catches ANY stray top-level
50+
// key — a single version string being the one that would misstate four registries.
51+
expect(Object.keys(payload).sort()).toEqual(["registries", "snippets"]);
52+
});
53+
54+
test("is deterministic — no timestamp, byte-identical across builds", () => {
55+
expect(JSON.stringify(buildPayload(REPO))).toBe(JSON.stringify(payload));
56+
expect(JSON.stringify(payload)).not.toMatch(/\d{4}-\d{2}-\d{2}T/);
57+
});
58+
59+
test("contains no absolute home path anywhere", () => {
60+
expect(JSON.stringify(payload)).not.toMatch(/\/(home|Users)\//);
61+
});
62+
63+
test("the drift fixture is still failing — the transcript is not stale", () => {
64+
expect(payload.snippets["verify-transcript"]!.inline)
65+
.toContain("ERR_VAR_NOT_ON_PAYLOAD");
66+
});
67+
});

scripts/site/payload.ts

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/**
2+
* The site payload — everything metaobjects.dev publishes, assembled once and gated
3+
* on the way out.
4+
*
5+
* The site's strongest claim is that its code blocks are real `meta gen` output. This
6+
* module is where that claim is made true rather than asserted: every snippet is READ
7+
* from the artifact it claims to come from, and four gates run during assembly, each
8+
* throwing rather than degrading. A payload that builds is a payload whose claims held.
9+
*
10+
* subsequence a published excerpt must still be an in-order subsequence of the real
11+
* generated file, so a renamed symbol or a dropped export fails here
12+
* instead of shipping. Elisions are COMPUTED from the match, so the
13+
* page cannot imply contiguity it does not have.
14+
* drift fixture the `verify` transcript must still be a FAILING run. A fixture gone
15+
* green would publish a screenshot of an error that no longer happens.
16+
* requirements `meta verify` on the showcase must exit 0, so the requirements page's
17+
* "resolved, not trusted" claim is checked rather than repeated.
18+
* home paths the assembled JSON must carry no absolute user path. This repo is
19+
* public and the payload publishes to a public site.
20+
*
21+
* Deterministic by construction: no timestamps, no durations (normalizeTranscript
22+
* replaces them), and object keys in registry order — so a rebuild that changed nothing
23+
* is byte-identical and `--check` means something.
24+
*/
25+
import { readFileSync } from "node:fs";
26+
import { resolve } from "node:path";
27+
import { SNIPPETS, type SnippetSource } from "./snippets.js";
28+
import { extractMarkedRegion } from "./markers.js";
29+
import { splitLines, matchSubsequence, renderWithElisions } from "./subsequence.js";
30+
import { loadVocabulary, highlightMetadata, type Vocabulary } from "./highlight-metadata.js";
31+
import { highlightCode } from "./highlight-code.js";
32+
import { captureTranscript, normalizeTranscript, HOME_PATH } from "./transcript.js";
33+
34+
export interface Snippet {
35+
lang: string;
36+
/** Highlighted HTML of what the page shows inline. */
37+
inline: string;
38+
/** Highlighted HTML of the whole generated file, for expand-to-view. */
39+
full: string | null;
40+
/** Lines in the full file — the page says "N lines" on the expander. */
41+
lineCount: number | null;
42+
}
43+
44+
export interface Registries {
45+
npm: string; pypi: string; nuget: string; maven: string; metamodel: string;
46+
}
47+
48+
export interface SitePayload {
49+
registries: Registries;
50+
snippets: Record<string, Snippet>;
51+
}
52+
53+
const REGISTRY_MANIFEST = "fixtures/registry-conformance/expected-registry.json";
54+
55+
/**
56+
* The showcase's prompt text lives in `templates/`, not `verify`'s default `prompts/`.
57+
* Omitting this yields ERR_PARTIAL_UNRESOLVED — a failure about where a file lives,
58+
* which would masquerade as the payload-drift signal these two captures are about.
59+
* The same flag is on the drift capture in snippets.ts, for the same reason.
60+
*/
61+
const PROMPTS_ARGS = ["--prompts", "templates"];
62+
63+
const read = (repoRoot: string, rel: string) => readFileSync(resolve(repoRoot, rel), "utf8");
64+
65+
/** First capture of `re` in a file, or a throw naming what was being looked for. */
66+
function readVersion(repoRoot: string, rel: string, re: RegExp, what: string): string {
67+
const m = re.exec(read(repoRoot, rel));
68+
const v = m?.[1];
69+
if (v === undefined) throw new Error(`site payload: could not read the ${what} version from ${rel}`);
70+
return v;
71+
}
72+
73+
/**
74+
* Five coordinates, never one string. The registries do NOT share a version — Maven
75+
* runs on its historical major 7 — so a single `version` field could only be right for
76+
* three of the four, and the page would state the wrong one somewhere. `metamodel` is a
77+
* separate contract again (ADR-0035 Amendment 2): it moves when the METADATA changes,
78+
* independently of any package line.
79+
*/
80+
function readRegistries(repoRoot: string): Registries {
81+
return {
82+
npm: readVersion(repoRoot, "server/typescript/packages/cli/package.json",
83+
/"version":\s*"([^"]+)"/, "npm"),
84+
pypi: readVersion(repoRoot, "server/python/pyproject.toml",
85+
/^version\s*=\s*"([^"]+)"/m, "PyPI"),
86+
nuget: readVersion(repoRoot, "server/csharp/Directory.Build.props",
87+
/<Version>([^<]+)<\/Version>/, "NuGet"),
88+
maven: readVersion(repoRoot, "server/java/pom.xml",
89+
/<version>([^<]+)<\/version>/, "Maven"),
90+
metamodel: readVersion(repoRoot, REGISTRY_MANIFEST,
91+
/"metamodelVersion":\s*"([^"]+)"/, "metamodel"),
92+
};
93+
}
94+
95+
/**
96+
* A hand-authored file, delimited in place. Already whole — nothing to expand to.
97+
*
98+
* Vocabulary is deliberately NOT gated here. highlightMetadata is tolerant by design:
99+
* no key allow-list can be complete (`attr.properties` is a chartered arbitrary bag,
100+
* and `attr.expression`/`attr.filter` carry their own grammars whose inner keys are not
101+
* registry attrs), so a throw would be a false-positive generator — measured at 8 false
102+
* failures against the real corpora. The vocabulary gate is the LOADER, which is
103+
* strictly stronger and has none: a marked region lives inside a real model, and
104+
* `assertRequirementsResolve` loads it.
105+
*
106+
* `lang` is "yaml" rather than the registry's value: a marker's content is metadata
107+
* YAML by construction (that is what highlightMetadata renders), and `Lang` — the
108+
* highlight-code language set — has no member for it, so the registry cannot say so.
109+
*/
110+
function markerSnippet(repoRoot: string, id: string, src: Extract<SnippetSource, { kind: "marker" }>,
111+
vocab: Vocabulary): Snippet {
112+
const region = extractMarkedRegion(read(repoRoot, src.file), id);
113+
return { lang: "yaml", inline: highlightMetadata(region, vocab), full: null, lineCount: null };
114+
}
115+
116+
/**
117+
* Machine-owned output, published as an excerpt. The excerpt is not trusted: it must be
118+
* an in-order subsequence of the real file, and where it skips, the elision marker is
119+
* computed from the match rather than authored — which is exactly how the landing page
120+
* came to claim "this exact model" while eliding three members.
121+
*/
122+
function excerptSnippet(repoRoot: string, id: string,
123+
src: Extract<SnippetSource, { kind: "excerpt" }>): Snippet {
124+
const inlineLines = splitLines(read(repoRoot, src.inline));
125+
const fullText = read(repoRoot, src.full);
126+
const fullLines = splitLines(fullText);
127+
128+
const m = matchSubsequence(inlineLines, fullLines);
129+
if (!m.ok) {
130+
throw new Error(
131+
`site payload: snippet "${id}" is stale.\n` +
132+
` ${src.inline} line ${m.failedAt + 1} is not present, in order, in ${src.full}:\n` +
133+
` ${m.line.trim()}\n` +
134+
` The site would publish it as real generated output. Re-cut the excerpt.`);
135+
}
136+
137+
const rendered = renderWithElisions(inlineLines, m.positions, fullLines.length);
138+
return {
139+
lang: src.lang,
140+
inline: highlightCode(rendered.join("\n"), src.lang),
141+
full: highlightCode(fullText, src.lang),
142+
lineCount: fullLines.length,
143+
};
144+
}
145+
146+
/**
147+
* Machine-owned output short enough to publish ENTIRE. The published text IS the file,
148+
* a stricter guarantee than any excerpt, so there is no subsequence gate and nothing to
149+
* expand to.
150+
*/
151+
function wholeSnippet(repoRoot: string, src: Extract<SnippetSource, { kind: "whole" }>): Snippet {
152+
return {
153+
lang: src.lang,
154+
inline: highlightCode(read(repoRoot, src.file), src.lang),
155+
full: null,
156+
lineCount: null,
157+
};
158+
}
159+
160+
/**
161+
* Live CLI output. The exit code is the gate: this transcript exists to show `verify`
162+
* CATCHING drift, so a fixture that started passing would leave the page showing an
163+
* error the tool no longer emits — true once, false now, and invisible in a diff
164+
* because the captured text would simply change.
165+
*/
166+
function transcriptSnippet(repoRoot: string, id: string,
167+
src: Extract<SnippetSource, { kind: "transcript" }>): Snippet {
168+
const { text, exitCode } = captureTranscript(src.argv, resolve(repoRoot, src.cwd));
169+
if (exitCode === 0) {
170+
throw new Error(
171+
`site payload: the "${id}" fixture now PASSES (exit 0).\n` +
172+
` It is published as a demonstration of \`meta ${src.argv.join(" ")}\` catching drift.\n` +
173+
` A passing fixture means the page would show an error that no longer happens.`);
174+
}
175+
return {
176+
lang: "console",
177+
inline: highlightCode(normalizeTranscript(text, repoRoot), "console"),
178+
full: null,
179+
lineCount: null,
180+
};
181+
}
182+
183+
/**
184+
* The requirements page claims a requirement's `implementedBy` is RESOLVED, not
185+
* trusted. `meta verify` is what resolves it, so the claim is only true while this
186+
* exits 0 — and a dangling reference is an ERROR, so a broken link cannot hide in the
187+
* warning cap.
188+
*/
189+
function assertRequirementsResolve(repoRoot: string): void {
190+
const showcase = resolve(repoRoot, "examples/showcase");
191+
const { text, exitCode } = captureTranscript(["verify", ...PROMPTS_ARGS], showcase);
192+
if (exitCode !== 0) {
193+
throw new Error(
194+
`site payload: \`meta verify\` fails on examples/showcase (exit ${exitCode}).\n` +
195+
` The site publishes its requirement links as resolved; they are not.\n\n` +
196+
normalizeTranscript(text, repoRoot));
197+
}
198+
}
199+
200+
export function buildPayload(repoRoot: string): SitePayload {
201+
const vocab = loadVocabulary(resolve(repoRoot, REGISTRY_MANIFEST));
202+
203+
const snippets: Record<string, Snippet> = {};
204+
for (const [id, src] of Object.entries(SNIPPETS)) {
205+
switch (src.kind) {
206+
case "marker": snippets[id] = markerSnippet(repoRoot, id, src, vocab); break;
207+
case "excerpt": snippets[id] = excerptSnippet(repoRoot, id, src); break;
208+
case "whole": snippets[id] = wholeSnippet(repoRoot, src); break;
209+
case "transcript": snippets[id] = transcriptSnippet(repoRoot, id, src); break;
210+
}
211+
}
212+
213+
assertRequirementsResolve(repoRoot);
214+
215+
const payload: SitePayload = { registries: readRegistries(repoRoot), snippets };
216+
217+
// Final sweep. HOME_PATH is IMPORTED, never respelled here: two spellings of one rule
218+
// is how the weaker one ends up being the one that runs. normalizeTranscript already
219+
// applies it to captured output; this catches a leak arriving by any other route —
220+
// an absolute path baked into a committed excerpt, say.
221+
const leak = HOME_PATH.exec(JSON.stringify(payload));
222+
if (leak) {
223+
throw new Error(
224+
`site payload: absolute home path ${leak[0]} — this repository is public and the ` +
225+
`payload publishes to a public site. Refusing to emit it.`);
226+
}
227+
return payload;
228+
}

0 commit comments

Comments
 (0)