Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
87d9594
Split BONanza update metadata from userscript payload
maghuro Sep 25, 2026
0754bb6
Rebuild BONanza userscript metadata for split updates
maghuro Sep 25, 2026
9aef3ea
Publish BONanza .meta.js update manifest to Gist
maghuro Sep 25, 2026
86b6757
Test split BONanza update metadata publishing
maghuro Sep 25, 2026
35f2540
Validate BONanza Gist publishing workflow changes
maghuro Sep 25, 2026
79528ea
Fix BONanza validation workflow path YAML
maghuro Sep 25, 2026
69b7bd8
Fix BONanza metadata regression tests
maghuro Sep 25, 2026
7ef6c9a
Label rehearsal statement transactions as simulated
maghuro Sep 25, 2026
96f2537
Keep rehearsal settlement output explicitly simulated
maghuro Sep 25, 2026
cb8fa4c
Document rehearsal statement semantics
maghuro Sep 25, 2026
c90b23c
Test rehearsal statements remain explicitly simulated
maghuro Sep 25, 2026
ed596ff
Rebuild BONanza userscript after rehearsal status fixes
maghuro Sep 25, 2026
ec8348f
Preserve BONanza namespace as stable userscript identity
maghuro Sep 25, 2026
cfca403
Test legacy namespace remains stable while homepage moves
maghuro Sep 25, 2026
2796569
Rebuild BONanza after preserving legacy namespace
maghuro Sep 25, 2026
44879b3
Use DarkPeers profile as BONanza namespace
maghuro Sep 25, 2026
eb6caab
Test intentional fresh BONanza userscript identity
maghuro Sep 25, 2026
29c4a66
Rebuild BONanza with DarkPeers namespace
maghuro Sep 25, 2026
68247da
Preserve simulated BON Pool status during finalization
maghuro Sep 25, 2026
596b3fe
Infer and sanitize legacy rehearsal statements
maghuro Sep 25, 2026
3158ee3
Cover final dry-run status and legacy rehearsal migration
maghuro Sep 25, 2026
0a191ae
Document rehearsal finalization and legacy statement migration
maghuro Sep 25, 2026
7c1ff21
Rebuild BONanza after Codex rehearsal fixes
maghuro Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 72 additions & 17 deletions .github/workflows/publish-bonanza-gist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- userscripts/giveaway/DarkPeers_BONanza_Giveaway.user.js
- userscripts/giveaway/src/**
- scripts/build-bonanza.mjs
- .github/workflows/publish-bonanza-gist.yml
workflow_dispatch:

permissions:
Expand All @@ -27,12 +28,57 @@ jobs:
git diff --exit-code -- userscripts/giveaway/DarkPeers_BONanza_Giveaway.user.js
node --check userscripts/giveaway/DarkPeers_BONanza_Giveaway.user.js

- name: Publish userscript to Gist
- name: Generate userscript update metadata
env:
SOURCE_FILE: userscripts/giveaway/DarkPeers_BONanza_Giveaway.user.js
META_FILE: /tmp/DarkPeers_BONanza_Giveaway.meta.js
run: |
set -euo pipefail

python3 - <<'PY'
import os
import re
from pathlib import Path

source = Path(os.environ["SOURCE_FILE"]).read_text(encoding="utf-8")
metadata_keys = (
"@name",
"@namespace",
"@version",
"@updateURL",
"@downloadURL",
)

selected = []
for key in metadata_keys:
pattern = re.compile(rf"^//\s+{re.escape(key)}\s+.+$", re.MULTILINE)
matches = pattern.findall(source)
if len(matches) != 1:
raise SystemExit(
f"Expected exactly one {key} metadata line, found {len(matches)}"
)
selected.append(matches[0])

meta = "// ==UserScript==\n" + "\n".join(selected) + "\n// ==/UserScript==\n"
meta_path = Path(os.environ["META_FILE"])
meta_path.write_text(meta, encoding="utf-8")

if "DarkPeers_BONanza_Giveaway.meta.js" not in selected[3]:
raise SystemExit("@updateURL does not point to the .meta.js update manifest")
if "DarkPeers_BONanza_Giveaway.user.js" not in selected[4]:
raise SystemExit("@downloadURL does not point to the full .user.js payload")

print(meta, end="")
PY

- name: Publish userscript and metadata to Gist
env:
GIST_TOKEN: ${{ secrets.BONANZA_GIST_TOKEN }}
GIST_ID: ${{ vars.BONANZA_GIST_ID }}
SOURCE_FILE: userscripts/giveaway/DarkPeers_BONanza_Giveaway.user.js
GIST_FILE: DarkPeers_BONanza_Giveaway.user.js
META_FILE: /tmp/DarkPeers_BONanza_Giveaway.meta.js
GIST_USER_FILE: DarkPeers_BONanza_Giveaway.user.js
GIST_META_FILE: DarkPeers_BONanza_Giveaway.meta.js
run: |
set -euo pipefail

Expand All @@ -44,14 +90,15 @@ jobs:
import os
from pathlib import Path

source = Path(os.environ["SOURCE_FILE"])
content = source.read_text(encoding="utf-8")
files = {
os.environ["GIST_USER_FILE"]: Path(os.environ["SOURCE_FILE"]).read_text(encoding="utf-8"),
os.environ["GIST_META_FILE"]: Path(os.environ["META_FILE"]).read_text(encoding="utf-8"),
}

payload = {
"files": {
os.environ["GIST_FILE"]: {
"content": content,
}
name: {"content": content}
for name, content in files.items()
}
}

Expand All @@ -75,19 +122,27 @@ jobs:
import os
from pathlib import Path

source = Path(os.environ["SOURCE_FILE"]).read_text(encoding="utf-8")
expected = {
os.environ["GIST_USER_FILE"]: Path(os.environ["SOURCE_FILE"]).read_text(encoding="utf-8"),
os.environ["GIST_META_FILE"]: Path(os.environ["META_FILE"]).read_text(encoding="utf-8"),
}
response = json.loads(Path("/tmp/gist-response.json").read_text(encoding="utf-8"))
gist_file = os.environ["GIST_FILE"]
published = response.get("files", {}).get(gist_file)

if not published:
raise SystemExit(f"Gist update response does not contain {gist_file!r}")
for gist_file, wanted in expected.items():
published = response.get("files", {}).get(gist_file)

if not published:
raise SystemExit(f"Gist update response does not contain {gist_file!r}")

if published.get("truncated"):
raise SystemExit("Gist API returned truncated content; refusing an unverified publish")
if published.get("truncated"):
raise SystemExit(
f"Gist API returned truncated content for {gist_file}; refusing an unverified publish"
)

if published.get("content") != source:
raise SystemExit("Published Gist content does not match the repository userscript")
if published.get("content") != wanted:
raise SystemExit(
f"Published Gist content for {gist_file} does not match the generated content"
)

print(f"Published and verified {gist_file} in gist {os.environ['GIST_ID']}.")
print(f"Published and verified {gist_file} in gist {os.environ['GIST_ID']}.")
PY
2 changes: 2 additions & 0 deletions .github/workflows/validate-bonanza.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- "scripts/build-bonanza.mjs"
- "tests/bonanza-*.test.mjs"
- ".github/workflows/validate-bonanza.yml"
- ".github/workflows/publish-bonanza-gist.yml"
push:
branches:
- main
Expand All @@ -15,6 +16,7 @@ on:
- "scripts/build-bonanza.mjs"
- "tests/bonanza-*.test.mjs"
- ".github/workflows/validate-bonanza.yml"
- ".github/workflows/publish-bonanza-gist.yml"
workflow_dispatch:

permissions:
Expand Down
62 changes: 61 additions & 1 deletion tests/bonanza-review-hardening.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const sourcePath = new URL(
const source = readFileSync(sourcePath, "utf8");

test("review hardening invariants stay present", () => {
assert.match(source, /^\/\/ @version\s+1\.5\.9$/m);
assert.match(source, /^\/\/ @version\s+1\.5\.10$/m);
assert.doesNotMatch(source, /pollChatFallback/);
assert.doesNotMatch(source, /onlyguardians/i);
assert.match(source, /async function getLatestMainChatReplayBoundary\(\)/);
Expand All @@ -28,6 +28,39 @@ test("review hardening invariants stay present", () => {
);
});

test("public update metadata is split from the install payload", () => {
const header = source.match(/\/\/ ==UserScript==[\s\S]*?\/\/ ==\/UserScript==/)?.[0] || "";

assert.match(header, /^\/\/ @namespace\s+https:\/\/darkpeers\.org\/users\/maghuro$/m);
assert.match(header, /^\/\/ @homepageURL\s+https:\/\/darkpeers\.org\/users\/maghuro$/m);
assert.match(
header,
/^\/\/ @updateURL\s+https:\/\/gist\.githubusercontent\.com\/maghuro\/da2dbfec94951990cbc54e75a9aee318\/raw\/DarkPeers_BONanza_Giveaway\.meta\.js$/m
);
assert.match(
header,
/^\/\/ @downloadURL\s+https:\/\/gist\.githubusercontent\.com\/maghuro\/da2dbfec94951990cbc54e75a9aee318\/raw\/DarkPeers_BONanza_Giveaway\.user\.js$/m
);
assert.doesNotMatch(header, /github\.com\/maghuro\/unit3d-userscripts/);
assert.match(source, /v1\.5\.10 intentionally changes @namespace/);
});

test("Gist workflow generates and verifies a minimal .meta.js manifest", () => {
const workflow = readFileSync(
new URL("../.github/workflows/publish-bonanza-gist.yml", import.meta.url),
"utf8"
);

assert.match(workflow, /GIST_META_FILE: DarkPeers_BONanza_Giveaway\.meta\.js/);
assert.match(workflow, /META_FILE: \/tmp\/DarkPeers_BONanza_Giveaway\.meta\.js/);
assert.match(
workflow,
/metadata_keys = \([\s\S]*?"@name"[\s\S]*?"@namespace"[\s\S]*?"@version"[\s\S]*?"@updateURL"[\s\S]*?"@downloadURL"[\s\S]*?\)/
);
assert.match(workflow, /Gist update response does not contain/);
assert.match(workflow, /Published Gist content for \{gist_file\} does not match the generated content/);
});

test("winner-count commands are host-only", () => {
const winners = source.match(/winners\(ctx\) \{[\s\S]*?\n\s*\},\n\n\s*maxwinners\(ctx\)/)?.[0] || "";
const maxWinners = source.match(/maxwinners\(ctx\) \{[\s\S]*?\n\s*\},\n\n\s*scale\(ctx\)/)?.[0] || "";
Expand Down Expand Up @@ -180,6 +213,33 @@ test("rehearsal toggle handles forced overrides honestly", () => {
});


test("rehearsal statements keep simulated transfers distinct from live confirmations", () => {
assert.match(source, /rehearsalMode: REHEARSAL_MODE/);
assert.match(source, /"dry-run": "simulated \(no BON sent\)"/);
assert.match(source, /isRehearsalStatementRecord\(targetStatement\)/);
assert.match(source, /REHEARSAL \/ SIMULATION \(no BON-moving requests sent\)/);
assert.match(source, /const rehearsalPool = poolResult\.dryRun === true/);
assert.match(source, /simulated only \(no BON Pool contribution sent\)/);
assert.match(source, /REHEARSAL:[\s\S]*?No BON was sent\./);
assert.match(source, /noEntryPoolResult\.dryRun/);
assert.match(source, /simulated only \(zero entrants; no BON Pool contribution sent\)/);
assert.match(source, /const finalPoolStatus = donationActive[\s\S]*?poolResult\.dryRun[\s\S]*?simulated only \(no BON Pool contribution sent\)/);
assert.match(source, /currentStatement\.donationStatus = finalPoolStatus/);
assert.match(source, /currentStatement\.verification = poolResult\.dryRun[\s\S]*?rehearsal simulation complete; no BON-moving requests sent/);
});

test("legacy rehearsal statements inherit rehearsal context and lose false live confirmations", () => {
assert.match(source, /function normalizeStatementRecord\(record\)/);
assert.match(source, /const hasExplicitMode = typeof normalized\.rehearsalMode === "boolean"/);
assert.match(source, /if \(!hasExplicitMode\) \{[\s\S]*?normalized\.rehearsalMode = REHEARSAL_MODE/);
assert.match(source, /legacy rehearsal; no BON Pool contribution sent/);
assert.match(source, /normalized\.verification === "nothing to verify"/);
assert.match(source, /normalized\.verification === "all gifts confirmed in DarkPeers"/);
assert.match(source, /return Array\.isArray\(arr\) \? arr\.map\(normalizeStatementRecord\) : \[\]/);
assert.match(source, /function isRehearsalStatementRecord\(record\)/);
assert.match(source, /typeof record\.rehearsalMode === "boolean"[\s\S]*?\? record\.rehearsalMode[\s\S]*?: REHEARSAL_MODE/);
});

test("rehearsal persistent state is isolated from live giveaway state", () => {
assert.match(source, /const REHEARSAL_STORAGE_SUFFIX = REHEARSAL_MODE \? "::rehearsal" : ""/);
assert.match(source, /BONANZA_GIVEAWAY_STATS_v2::\$\{location\.hostname\}\$\{REHEARSAL_STORAGE_SUFFIX\}/);
Expand Down
Loading
Loading