forked from NSPG13/agent-bounties
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck-chatgpt-app-submission.py
More file actions
317 lines (298 loc) · 11.2 KB
/
Copy pathcheck-chatgpt-app-submission.py
File metadata and controls
317 lines (298 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#!/usr/bin/env python3
"""Validate the single-product ChatGPT app artifact and hosted-execution boundary."""
from __future__ import annotations
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CANONICAL_SCHEMA = (
"https://developers.openai.com/plugins/schemas/"
"chatgpt-app-submission.v1.json"
)
FULL_TOOLS = {
"get_bounty_feed",
"render_bounty_feed",
"prepare_moonpay_onramp",
"prepare_bounty_post",
"prepare_bounty_action",
"get_bounty_action_status",
"compile_objective_with_cloud_agent",
"list_bounty_comments",
"add_bounty_comment",
"create_share_bundle",
}
DIRECT_EXECUTION_TOOLS = {
"fund_bounty_with_x402",
"agent_native_claim",
"prepare_autonomous_bounty_submission",
"plan_autonomous_module_settlement",
"plan_autonomous_attestation_settlement",
}
EXPECTED_ANNOTATIONS = {
"get_bounty_feed": (True, False, False),
"render_bounty_feed": (True, False, False),
"prepare_moonpay_onramp": (True, False, False),
"prepare_bounty_post": (False, True, True),
"prepare_bounty_action": (False, False, False),
"get_bounty_action_status": (False, False, False),
"compile_objective_with_cloud_agent": (False, True, False),
"list_bounty_comments": (True, False, False),
"add_bounty_comment": (False, True, True),
"create_share_bundle": (True, False, False),
}
def require(condition: bool, message: str) -> None:
if not condition:
raise SystemExit(f"chatgpt_submission_check=failed reason={message}")
def main() -> None:
artifact = json.loads(
(ROOT / "chatgpt-app-submission.json").read_text(encoding="utf-8")
)
tools = artifact.get("tools", {})
require(artifact.get("$schema") == CANONICAL_SCHEMA, "official schema URL drifted")
require(artifact.get("schema_version") == 1, "schema_version must equal 1")
require(set(tools) == FULL_TOOLS, "artifact must equal the ten-tool full product")
require(
not DIRECT_EXECUTION_TOOLS.intersection(tools),
"lower-level wallet or settlement execution tool leaked into ChatGPT",
)
require(len(artifact.get("test_cases", [])) == 5, "exactly five positive tests are required")
require(
len(artifact.get("negative_test_cases", [])) == 3,
"exactly three negative tests are required",
)
require(
artifact["app_info"]["display_name"] == "Agent Bounties",
"single-product listing name drifted",
)
require(
artifact["release_status"]["product_profile"] == "full_hosted_execution"
and artifact["release_status"]["public_and_developer_parity"] is True,
"artifact must declare one full public/developer product profile",
)
require(
artifact["release_status"]["directory_submission"]
== "blocked_pending_written_openai_approval_or_policy_change",
"current Plugin Directory policy blocker must remain explicit",
)
for name, expected in EXPECTED_ANNOTATIONS.items():
annotations = tools[name]["annotations"]
actual = (
annotations["readOnlyHint"],
annotations["openWorldHint"],
annotations["destructiveHint"],
)
require(actual == expected, f"{name} annotations drifted: {actual} != {expected}")
widget = (ROOT / "site" / "chatgpt-bounty-feed-widget.html").read_text(
encoding="utf-8"
)
require(
'bridgeNotify("ui/message", message)' in widget
and "openai()?.sendFollowUpMessage" in widget,
"widget actions must continue in ChatGPT through the standard bridge and "
"the documented ChatGPT compatibility helper",
)
widget_lower = widget.lower()
require(
all(
element not in widget_lower
for element in ("<input", "<textarea", "<select", "<form")
),
"conversation-first widget must not expose fields or forms",
)
button_actions = set(re.findall(r'data-action="([^"]+)"', widget))
require(
button_actions == {"post-bounty", "comment", "share", "solve"}
and widget.count("<button") == 4,
f"widget buttons drifted from the approved four actions: {button_actions}",
)
for visible_label in (">Post bounty<", ">Comment<", ">Share<", ">Solve<"):
require(visible_label in widget, f"widget lost {visible_label}")
for forbidden_label in (
">Compete<",
">Fund<",
">Complete<",
">Verify<",
">Refresh<",
">Break down<",
):
require(
forbidden_label not in widget,
f"widget exposed an unapproved button: {forbidden_label}",
)
for outdated_term in (
"live quest feed",
"guild companion",
"share this quest step",
"explore the quest",
"open for competition",
"ready to compete",
):
require(
outdated_term not in widget_lower,
f"minimal widget must not contain outdated visual copy: {outdated_term}",
)
require(
'class="project-thumb"' in widget
and "Live bounties" in widget
and "ChatGPT gathers details conversationally" in widget,
"widget must preserve the branded conversation-first live-feed presentation",
)
require(
'callTool("get_bounty_feed"' in widget,
"read-only widget must load the live projection through the host bridge",
)
require(
"generate one unique bounty image using my ChatGPT account" in widget
and "prepare_bounty_post" in widget
and "must not generate a replacement" in widget,
"Post bounty conversation must preserve the user-owned ChatGPT image flow",
)
composer = (ROOT / "site" / "bounty-composer-v2.js").read_text(
encoding="utf-8"
)
chat_css = (ROOT / "site" / "bounty-chat.css").read_text(encoding="utf-8")
require(
"enableChatgptHandoffReview" in composer
and 'params.get("from") === "chatgpt-app"' in composer
and 'inputWrap.hidden = true' in composer
and 'ui.revise.hidden = true' in composer
and "chatgpt-handoff-review" in chat_css,
"ChatGPT post handoff must show a read-only review card without a second composer",
)
for mutating_tool in FULL_TOOLS - {"get_bounty_feed", "render_bounty_feed"}:
require(
f'callTool("{mutating_tool}"' not in widget,
f"widget must leave {mutating_tool} to the confirmed conversation flow",
)
preview = (ROOT / "site" / "chatgpt-bounty-card-preview.html").read_text(
encoding="utf-8"
)
for brand_color in (
"#020b08",
"#07140f",
"#091710",
"#b9ef37",
"#18d9ac",
"#e8bd26",
"#f6f7ef",
"#b9c0b8",
):
require(
brand_color in widget and brand_color in preview,
f"widget and share card must use website brand color {brand_color}",
)
for blue in (
"#2563eb",
"#1d4ed8",
"#60a5fa",
"#eff6ff",
"#bfdbfe",
"#93c5fd",
"#172554",
"#1e40af",
):
require(
blue not in widget_lower and blue not in preview.lower(),
f"unauthorized blue remains in the ChatGPT UI: {blue}",
)
server = (ROOT / "crates" / "mcp-server" / "src" / "chatgpt_app.rs").read_text(
encoding="utf-8"
)
require(
"CHATGPT_APP_PUBLIC_REVIEW_MODE" not in server,
"reduced public-review environment switch must be removed",
)
require(
'"prepare_moonpay_onramp",' in server
and "build_moonpay_onramp_handoff" in server
and "moonpay_onramp_output_schema" in server,
"bounded MoonPay MCP handoff contract is incomplete",
)
require(
'"prepare_bounty_post",' in server
and '"openai/fileParams"' in server
and '"bounty_image"' in server
and "chatgpt_user_generated" in server
and "put_bounty_image_asset" in server,
"ChatGPT-account bounty image handoff contract is incomplete",
)
require(
'"checkout_created": false' in server
and '"purchase_completed": false' in server
and '"bounty_funded": false' in server
and "FundingAdded" in server,
"MoonPay handoff must fail closed across purchase and funding evidence",
)
require(
'"prepare_bounty_action" =>' in server and "without_action_details" in server,
"hosted bounty lifecycle must minimize MCP responses",
)
require(
'"app_mode": {"type": "string", "enum": ["full", "sandbox"]}' in server,
"runtime output schema must expose only full and fixture-only profiles",
)
require(
"Pokémon-card-style" not in server
and "pokemon-card-style" not in server.lower(),
"third-party card-style branding leaked into model-readable metadata",
)
main_server = (ROOT / "crates" / "mcp-server" / "src" / "main.rs").read_text(
encoding="utf-8"
)
require(
'"/.well-known/openai-apps-challenge"' in main_server,
"OpenAI domain challenge route is missing",
)
require(
'"/chatgpt/bounty-card-preview"' in main_server,
"first-party bounty-card preview route is missing",
)
require(
'"/public/bounty-images/:sha256"' in main_server,
"content-addressed bounty image route is missing",
)
require(
'"/v1/onramps/moonpay/checkout"' in main_server,
"hosted MoonPay checkout-preparation route is missing",
)
preview = (ROOT / "site" / "chatgpt-bounty-card-preview.html").read_text(
encoding="utf-8"
)
require("Download PNG" in preview, "card preview must require an explicit download click")
require(
"No wallet, signature, social post, purchase, or payment authorization occurs"
in preview,
"card preview must disclose its non-transactional boundary",
)
privacy = (ROOT / "site" / "privacy.html").read_text(encoding="utf-8")
require("ChatGPT hosted action intents" in privacy, "intent privacy disclosure is missing")
require(
"deleted within 24 hours after expiry" in privacy,
"intent retention disclosure is missing",
)
require(
"public and developer-installed experiences use the same hosted-action flow"
in privacy,
"privacy policy must disclose public/developer parity",
)
require("MoonPay handles the purchase" in privacy, "MoonPay provider boundary is missing")
require(
"Agent Bounties does not use its own OpenAI API key" in privacy
and "ChatGPT-generated bounty images" in privacy,
"ChatGPT-account image privacy disclosure is missing",
)
submission_doc = (ROOT / "docs" / "chatgpt-app-submission.md").read_text(
encoding="utf-8"
)
require(
"Directory policy status: blocked" in submission_doc,
"release documentation must not misrepresent directory eligibility",
)
print(
"chatgpt_submission_check=ok "
f"tools={len(tools)} positive_tests={len(artifact['test_cases'])} "
f"negative_tests={len(artifact['negative_test_cases'])} "
"profile=full_hosted_execution directory_policy=blocked"
)
if __name__ == "__main__":
main()