@riddledc/riddle-proof 0.8.30 → 0.8.32

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.
Files changed (49) hide show
  1. package/dist/advanced/engine-harness.cjs +132 -10
  2. package/dist/advanced/engine-harness.js +2 -2
  3. package/dist/advanced/index.cjs +132 -10
  4. package/dist/advanced/index.d.cts +2 -2
  5. package/dist/advanced/index.d.ts +2 -2
  6. package/dist/advanced/index.js +4 -4
  7. package/dist/advanced/proof-run-core.cjs +3 -1
  8. package/dist/advanced/proof-run-core.d.cts +1 -1
  9. package/dist/advanced/proof-run-core.d.ts +1 -1
  10. package/dist/advanced/proof-run-core.js +1 -1
  11. package/dist/advanced/proof-run-engine.cjs +80 -1
  12. package/dist/advanced/proof-run-engine.d.cts +2 -2
  13. package/dist/advanced/proof-run-engine.d.ts +2 -2
  14. package/dist/advanced/proof-run-engine.js +2 -2
  15. package/dist/advanced/runner.js +2 -2
  16. package/dist/{chunk-3OTO7IDH.js → chunk-C2NHHBFV.js} +1 -1
  17. package/dist/{chunk-32RE64IO.js → chunk-IOI6QR3B.js} +78 -1
  18. package/dist/{chunk-XJA2GDVN.js → chunk-U73JPBZW.js} +1 -1
  19. package/dist/{chunk-K6HZUSHH.js → chunk-X7SQTCIQ.js} +3 -1
  20. package/dist/{chunk-UWO4YR7I.js → chunk-ZREWMTFA.js} +53 -10
  21. package/dist/cli/index.js +3 -3
  22. package/dist/cli.cjs +132 -10
  23. package/dist/cli.js +3 -3
  24. package/dist/engine-harness.cjs +132 -10
  25. package/dist/engine-harness.js +2 -2
  26. package/dist/index.cjs +132 -10
  27. package/dist/index.js +3 -3
  28. package/dist/{proof-run-core-C8FDUhle.d.cts → proof-run-core-B1GeqkR8.d.cts} +2 -0
  29. package/dist/{proof-run-core-C8FDUhle.d.ts → proof-run-core-B1GeqkR8.d.ts} +2 -0
  30. package/dist/proof-run-core.cjs +3 -1
  31. package/dist/proof-run-core.d.cts +1 -1
  32. package/dist/proof-run-core.d.ts +1 -1
  33. package/dist/proof-run-core.js +1 -1
  34. package/dist/{proof-run-engine-By7oLsF-.d.ts → proof-run-engine-DYfmd8d7.d.ts} +4 -4
  35. package/dist/{proof-run-engine-D80hVFMf.d.cts → proof-run-engine-DeHxtGnW.d.cts} +4 -4
  36. package/dist/proof-run-engine.cjs +80 -1
  37. package/dist/proof-run-engine.d.cts +2 -2
  38. package/dist/proof-run-engine.d.ts +2 -2
  39. package/dist/proof-run-engine.js +2 -2
  40. package/dist/runner.js +2 -2
  41. package/lib/workspace-core.mjs +62 -7
  42. package/package.json +2 -2
  43. package/runtime/lib/riddle_core_call.mjs +662 -40
  44. package/runtime/lib/ship.py +363 -16
  45. package/runtime/lib/util.py +117 -40
  46. package/runtime/lib/verify.py +4 -3
  47. package/runtime/pipelines/riddle-proof-ship.lobster +11 -1
  48. package/runtime/tests/recon_verify_smoke.py +132 -0
  49. package/runtime/tests/ship_artifact_publication.py +185 -0
@@ -310,12 +310,13 @@ def payload_has_capture_artifacts(payload):
310
310
  def capture_payload_error(payload):
311
311
  if not isinstance(payload, dict):
312
312
  return ''
313
- if payload.get('ok') is False and not payload_has_capture_artifacts(payload):
314
- for key in ('error', 'stderr', 'stdout'):
313
+ if payload.get('ok') is False:
314
+ for key in ('error', 'script_error', 'stderr', 'stdout'):
315
315
  value = payload.get(key)
316
316
  if value:
317
317
  return str(value).strip()
318
- return 'capture tool returned ok=false without artifacts'
318
+ if not payload_has_capture_artifacts(payload):
319
+ return 'capture tool returned ok=false without artifacts'
319
320
  return ''
320
321
 
321
322
 
@@ -12,7 +12,17 @@ steps:
12
12
  import json, os
13
13
  state_file = os.environ.get('RIDDLE_PROOF_STATE_FILE', '/tmp/riddle-proof-state.json')
14
14
  s = json.load(open(state_file))
15
- if not s.get('after_cdn'):
15
+ after = ((s.get('evidence_bundle') or {}).get('after') or {})
16
+ observation = after.get('observation') or {}
17
+ supporting = after.get('supporting_artifacts') or {}
18
+ has_structured_after = bool(
19
+ observation.get('valid') and (
20
+ supporting.get('has_structured_payload') or
21
+ supporting.get('proof_evidence_present') or
22
+ observation.get('telemetry_ready')
23
+ )
24
+ )
25
+ if not s.get('after_cdn') and not has_structured_after:
16
26
  raise SystemExit('No after evidence. Run verify first.')
17
27
  print('SHIP')
18
28
  print(' Proof goal: ' + s.get('change_request', ''))
@@ -89,6 +89,138 @@ class FakeRiddle:
89
89
  'id': f'pv-{label}',
90
90
  'preview_url': f'https://preview.example.com/{label}/',
91
91
  }
92
+ if tool == 'riddle_server_preview':
93
+ script = args.get('script', '')
94
+ target_path = args.get('path') or '/'
95
+ path_only, _, query = str(target_path).partition('?')
96
+ search = '?' + query if query else ''
97
+ delegated_markers = [
98
+ 'after-proof',
99
+ 'audioNoProof',
100
+ 'audioFailedProof',
101
+ 'throwAfterProofEvidence',
102
+ 'attack_ms_after',
103
+ 'window.__riddleProofEvidence',
104
+ 'globalThis.__riddleProofEvidence',
105
+ 'clickedSkipHashNavigation',
106
+ 'pricingQueryHashDropsTerminal',
107
+ 'pricingQueryHashStructuredNegativeControl',
108
+ 'pricingQueryHashPassesWithPageStateHashGap',
109
+ 'clickedProofNavigation',
110
+ 'clickedHomeNavigation',
111
+ 'skipLinkTimeout',
112
+ 'interactionThrownAfterFailedEvidence',
113
+ 'interactionThrownError',
114
+ ]
115
+ if any(marker in script for marker in delegated_markers):
116
+ return self.invoke_retry('riddle_script', {'script': script}, retries=retries, timeout=timeout)
117
+ if path_only == '/wrong' or '/wrong' in script:
118
+ return {
119
+ 'ok': True,
120
+ 'runner': 'local-server-preview',
121
+ 'target_url': 'http://127.0.0.1:3000/wrong',
122
+ 'screenshots': [{'url': 'https://cdn.example.com/wrong.png'}],
123
+ 'outputs': [{'name': 'wrong.png', 'url': 'https://cdn.example.com/wrong.png'}],
124
+ 'console': ['RIDDLE_PROOF_STATE:{"bodyTextLength":5,"interactiveElements":0,"pathname":"/wrong","title":"Wrong"}'],
125
+ }
126
+ if '/games/drum-sequencer' in path_only:
127
+ page_state = {
128
+ 'bodyTextLength': 240,
129
+ 'visibleTextSample': 'Neon Step Sequencer Monkberry Moon Delight Mix Board Play All',
130
+ 'interactiveElements': 8,
131
+ 'visibleInteractiveElements': 8,
132
+ 'pathname': '/games/drum-sequencer',
133
+ 'search': search,
134
+ 'title': 'Neon Step Sequencer',
135
+ 'buttons': ['Play All', 'Shuffle'],
136
+ 'headings': ['Neon Step Sequencer'],
137
+ 'links': [],
138
+ 'canvasCount': 1,
139
+ 'largeVisibleElements': [{'tag': 'canvas', 'text': ''}],
140
+ }
141
+ return {
142
+ 'ok': True,
143
+ 'runner': 'local-server-preview',
144
+ 'target_url': 'http://127.0.0.1:3000' + str(target_path),
145
+ 'screenshots': [{'url': 'https://cdn.example.com/sequencer-before.png'}],
146
+ 'outputs': [{'name': 'before.png', 'url': 'https://cdn.example.com/sequencer-before.png'}],
147
+ 'console': state_console(page_state),
148
+ }
149
+ if '/games/tic-tac-toe' in path_only:
150
+ return {
151
+ 'ok': True,
152
+ 'runner': 'local-server-preview',
153
+ 'target_url': 'http://127.0.0.1:3000' + str(target_path),
154
+ 'screenshots': [{'url': 'https://cdn.example.com/tictactoe-before.png'}],
155
+ 'outputs': [{'name': 'before.png', 'url': 'https://cdn.example.com/tictactoe-before.png'}],
156
+ 'console': state_console({
157
+ 'bodyTextLength': 220,
158
+ 'visibleTextSample': 'LilArcade Tic Tac Toe Player X Reset Game',
159
+ 'interactiveElements': 5,
160
+ 'visibleInteractiveElements': 5,
161
+ 'pathname': '/games/tic-tac-toe',
162
+ 'title': 'TicTacToe',
163
+ 'buttons': ['Reset Game'],
164
+ 'headings': ['Tic Tac Toe'],
165
+ 'links': [],
166
+ 'canvasCount': 0,
167
+ 'largeVisibleElements': [{'tag': 'button', 'text': 'Reset Game'}],
168
+ }),
169
+ }
170
+ if 'after-proof' in script:
171
+ after_url = 'https://cdn.example.com/after-artifact' if 'noVisualDelta' in script else 'https://cdn.example.com/after.png'
172
+ outputs = [{'name': 'after.png', 'url': after_url}]
173
+ if 'proof-session' in script:
174
+ outputs.append({'name': 'proof-session.json', 'url': 'https://cdn.example.com/proof-session.json'})
175
+ payload = {
176
+ 'ok': True,
177
+ 'runner': 'local-server-preview',
178
+ 'target_url': 'http://127.0.0.1:3000' + str(target_path),
179
+ 'screenshots': [{'url': after_url}],
180
+ 'outputs': outputs,
181
+ 'console': state_console({
182
+ 'bodyTextLength': 180,
183
+ 'visibleTextSample': 'Pricing CTA Buy Now',
184
+ 'interactiveElements': 4,
185
+ 'visibleInteractiveElements': 4,
186
+ 'pathname': path_only or '/pricing',
187
+ 'search': search,
188
+ 'title': 'After',
189
+ 'buttons': ['Buy Now'],
190
+ 'headings': ['Pricing'],
191
+ 'links': [],
192
+ 'canvasCount': 0,
193
+ 'largeVisibleElements': [{'tag': 'button', 'text': 'Buy Now'}],
194
+ }),
195
+ }
196
+ if 'noVisualDelta' not in script:
197
+ payload['visual_diff'] = {
198
+ 'diffPercentage': 1.2,
199
+ 'differentPixels': 12000,
200
+ 'totalPixels': 972000,
201
+ }
202
+ return payload
203
+ return {
204
+ 'ok': True,
205
+ 'runner': 'local-server-preview',
206
+ 'target_url': 'http://127.0.0.1:3000' + str(target_path),
207
+ 'screenshots': [{'url': 'https://cdn.example.com/home-before.png'}],
208
+ 'outputs': [{'name': 'before.png', 'url': 'https://cdn.example.com/home-before.png'}],
209
+ 'console': state_console({
210
+ 'bodyTextLength': 180,
211
+ 'visibleTextSample': 'Riddle Proof homepage hero Start Free',
212
+ 'interactiveElements': 4,
213
+ 'visibleInteractiveElements': 4,
214
+ 'pathname': path_only or '/',
215
+ 'search': search,
216
+ 'title': 'Riddle',
217
+ 'buttons': ['Start Free'],
218
+ 'headings': ['Riddle Proof'],
219
+ 'links': [],
220
+ 'canvasCount': 0,
221
+ 'largeVisibleElements': [{'tag': 'button', 'text': 'Start Free'}],
222
+ }),
223
+ }
92
224
  if tool == 'riddle_script':
93
225
  script = args.get('script', '')
94
226
  if 'preview.example.com' in script and '/wrong' in script:
@@ -0,0 +1,185 @@
1
+ import json
2
+ import os
3
+ import subprocess
4
+ import tempfile
5
+ from pathlib import Path
6
+
7
+
8
+ ROOT = Path(__file__).resolve().parents[2]
9
+ SHIP = ROOT / "runtime" / "lib" / "ship.py"
10
+
11
+
12
+ def run(args, cwd, env=None):
13
+ result = subprocess.run(args, cwd=cwd, env=env, capture_output=True, text=True, timeout=120)
14
+ if result.returncode != 0:
15
+ raise AssertionError(
16
+ f"{' '.join(args)} failed\nstdout:\n{result.stdout}\nstderr:\n{result.stderr}"
17
+ )
18
+ return result
19
+
20
+
21
+ def write_fake_gh(path):
22
+ path.write_text(
23
+ """#!/usr/bin/env python3
24
+ import json
25
+ import os
26
+ import sys
27
+
28
+ args = sys.argv[1:]
29
+ if args[:3] == ["repo", "view", "--json"]:
30
+ print("example/test-repo")
31
+ raise SystemExit(0)
32
+ if args[:2] == ["pr", "list"]:
33
+ print("")
34
+ raise SystemExit(0)
35
+ if args[:2] == ["pr", "create"]:
36
+ print("https://github.com/example/test-repo/pull/321")
37
+ raise SystemExit(0)
38
+ if args[:2] == ["pr", "comment"]:
39
+ body = ""
40
+ if "--body" in args:
41
+ body = args[args.index("--body") + 1]
42
+ with open(os.environ["FAKE_GH_COMMENT_BODY"], "w") as f:
43
+ f.write(body)
44
+ print("https://github.com/example/test-repo/pull/321#issuecomment-999")
45
+ raise SystemExit(0)
46
+ if args[:2] == ["pr", "checks"]:
47
+ print("[]")
48
+ raise SystemExit(0)
49
+ if args[:2] == ["pr", "ready"]:
50
+ raise SystemExit(0)
51
+ if args[:2] == ["pr", "edit"]:
52
+ raise SystemExit(0)
53
+ print("unknown gh command: " + " ".join(args), file=sys.stderr)
54
+ raise SystemExit(1)
55
+ """,
56
+ encoding="utf-8",
57
+ )
58
+ path.chmod(0o755)
59
+
60
+
61
+ def main():
62
+ with tempfile.TemporaryDirectory(prefix="riddle-proof-ship-artifacts-") as tmp:
63
+ root = Path(tmp)
64
+ origin = root / "origin.git"
65
+ repo = root / "repo"
66
+ artifacts = root / "artifacts"
67
+ bin_dir = root / "bin"
68
+ state_path = root / "state.json"
69
+ comment_body_path = root / "comment.md"
70
+ artifacts.mkdir()
71
+ bin_dir.mkdir()
72
+
73
+ run(["git", "init", "--bare", str(origin)], cwd=root)
74
+ run(["git", "init", str(repo)], cwd=root)
75
+ run(["git", "config", "user.name", "Test User"], cwd=repo)
76
+ run(["git", "config", "user.email", "test@example.com"], cwd=repo)
77
+ run(["git", "remote", "add", "origin", str(origin)], cwd=repo)
78
+ run(["git", "checkout", "-b", "agent/proof-artifact-test"], cwd=repo)
79
+ (repo / "README.md").write_text("initial\n", encoding="utf-8")
80
+ run(["git", "add", "README.md"], cwd=repo)
81
+ run(["git", "commit", "-m", "Initial"], cwd=repo)
82
+ (repo / "README.md").write_text("changed\n", encoding="utf-8")
83
+
84
+ # Tiny valid PNG header/body is enough for GitHub Markdown image embedding.
85
+ screenshot = artifacts / "after-proof.png"
86
+ screenshot.write_bytes(
87
+ bytes.fromhex(
88
+ "89504e470d0a1a0a0000000d4948445200000001000000010802000000907753de"
89
+ "0000000c49444154789c63606060000000040001f61738550000000049454e44ae426082"
90
+ )
91
+ )
92
+ proof_json = artifacts / "proof.json"
93
+ proof_json.write_text(
94
+ json.dumps(
95
+ {
96
+ "version": "riddle-proof.test.v1",
97
+ "assertions": [{"name": "proof image published", "passed": True}],
98
+ },
99
+ indent=2,
100
+ ),
101
+ encoding="utf-8",
102
+ )
103
+
104
+ write_fake_gh(bin_dir / "gh")
105
+ state = {
106
+ "repo_dir": str(repo),
107
+ "branch": "agent/proof-artifact-test",
108
+ "target_branch": "agent/proof-artifact-test",
109
+ "run_id": "rp_ship_artifact_test",
110
+ "change_request": "Prove PR artifact publication.",
111
+ "commit_message": "Test proof artifact publication",
112
+ "success_criteria": "The PR proof comment embeds a GitHub-hosted image.",
113
+ "verification_mode": "proof",
114
+ "requested_reference": "none",
115
+ "reference": "none",
116
+ "verify_status": "evidence_captured",
117
+ "after_cdn": screenshot.as_uri(),
118
+ "assertion_status": "passed",
119
+ "proof_summary": "All assertions passed.",
120
+ "proof_assessment_source": "supervising_agent",
121
+ "proof_assessment": {
122
+ "source": "supervising_agent",
123
+ "decision": "ready_to_ship",
124
+ "summary": "Evidence is strong enough to ship.",
125
+ },
126
+ "evidence_bundle": {
127
+ "verification_mode": "proof",
128
+ "after": {
129
+ "observation": {"valid": True, "reason": "ok", "telemetry_ready": True},
130
+ "supporting_artifacts": {
131
+ "has_structured_payload": True,
132
+ "proof_evidence_present": True,
133
+ "image_outputs": [{"name": "after-proof.png", "url": screenshot.as_uri()}],
134
+ "data_outputs": [{"name": "proof.json", "url": proof_json.as_uri()}],
135
+ },
136
+ },
137
+ },
138
+ "verify_results": {
139
+ "after": {
140
+ "raw": {
141
+ "outputs": [
142
+ {"name": "after-proof.png", "url": screenshot.as_uri(), "path": str(screenshot)},
143
+ {"name": "proof.json", "url": proof_json.as_uri(), "path": str(proof_json)},
144
+ ],
145
+ },
146
+ },
147
+ },
148
+ }
149
+ state_path.write_text(json.dumps(state, indent=2), encoding="utf-8")
150
+ env = {
151
+ **os.environ,
152
+ "PATH": str(bin_dir) + os.pathsep + os.environ.get("PATH", ""),
153
+ "RIDDLE_PROOF_STATE_FILE": str(state_path),
154
+ "FAKE_GH_COMMENT_BODY": str(comment_body_path),
155
+ "DISCORD_BOT_TOKEN": "",
156
+ "OPENCLAW_HOME": str(root / "openclaw-home"),
157
+ }
158
+
159
+ run(["python3", str(SHIP)], cwd=repo, env=env)
160
+
161
+ updated = json.loads(state_path.read_text(encoding="utf-8"))
162
+ publication = updated.get("proof_artifact_publication") or {}
163
+ assert publication.get("ok") is True, "proof artifact publication should be recorded"
164
+ assert publication.get("artifacts"), "published artifact list should be recorded"
165
+ assert updated.get("ship_report", {}).get("after_artifact_url", "").startswith(
166
+ "https://raw.githubusercontent.com/example/test-repo/"
167
+ ), "ship report should expose a public after artifact URL"
168
+
169
+ comment = comment_body_path.read_text(encoding="utf-8")
170
+ assert "file://" not in comment, "PR proof comment must not expose local file URLs"
171
+ assert "![after](https://raw.githubusercontent.com/example/test-repo/" in comment, (
172
+ "PR proof comment should embed the GitHub-hosted after screenshot"
173
+ )
174
+ assert "[proof.json](https://github.com/example/test-repo/blob/" in comment, (
175
+ "PR proof comment should link the structured proof JSON"
176
+ )
177
+ assert "Proof artifacts:" in comment, "PR proof comment should link the artifact bundle"
178
+
179
+ artifact_branch = publication.get("branch")
180
+ refs = run(["git", f"--git-dir={origin}", "show-ref", f"refs/heads/{artifact_branch}"], cwd=root)
181
+ assert artifact_branch in refs.stdout, "artifact branch should be pushed to origin"
182
+
183
+
184
+ if __name__ == "__main__":
185
+ main()