@riddledc/riddle-proof 0.8.33 → 0.8.34
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.
package/package.json
CHANGED
package/runtime/lib/ship.py
CHANGED
|
@@ -320,7 +320,20 @@ def local_artifact_sources_fingerprint(local_sources):
|
|
|
320
320
|
def public_proof_artifacts(state):
|
|
321
321
|
published = state.get('proof_artifact_publication') or {}
|
|
322
322
|
artifacts = published.get('artifacts') if isinstance(published, dict) else []
|
|
323
|
-
|
|
323
|
+
attachments = state.get('proof_image_attachments') or {}
|
|
324
|
+
attachment_artifacts = attachments.get('artifacts') if isinstance(attachments, dict) else []
|
|
325
|
+
public = [
|
|
326
|
+
{
|
|
327
|
+
**artifact,
|
|
328
|
+
'raw_url': artifact.get('attachment_url'),
|
|
329
|
+
'html_url': artifact.get('attachment_url'),
|
|
330
|
+
'embeddable': True,
|
|
331
|
+
'attachment': True,
|
|
332
|
+
}
|
|
333
|
+
for artifact in attachment_artifacts or []
|
|
334
|
+
if isinstance(artifact, dict) and artifact.get('attachment_url')
|
|
335
|
+
]
|
|
336
|
+
public.extend([artifact for artifact in artifacts or [] if isinstance(artifact, dict) and (artifact.get('raw_url') or artifact.get('html_url'))])
|
|
324
337
|
for artifact in collect_proof_artifact_sources(state):
|
|
325
338
|
url = str(artifact.get('url') or '').strip()
|
|
326
339
|
if is_http_url(url):
|
|
@@ -328,6 +341,7 @@ def public_proof_artifacts(state):
|
|
|
328
341
|
**artifact,
|
|
329
342
|
'raw_url': url,
|
|
330
343
|
'html_url': url,
|
|
344
|
+
'embeddable': True,
|
|
331
345
|
'published': False,
|
|
332
346
|
})
|
|
333
347
|
deduped = []
|
|
@@ -346,6 +360,8 @@ def first_public_artifact_url(state, role, kind=None):
|
|
|
346
360
|
continue
|
|
347
361
|
if kind and artifact.get('kind') != kind:
|
|
348
362
|
continue
|
|
363
|
+
if artifact.get('kind') == 'image' and artifact.get('embeddable') is False:
|
|
364
|
+
continue
|
|
349
365
|
return artifact.get('raw_url') or artifact.get('html_url') or artifact.get('url') or ''
|
|
350
366
|
return ''
|
|
351
367
|
|
|
@@ -362,11 +378,92 @@ def resolve_github_repo_name(repo_dir):
|
|
|
362
378
|
return ''
|
|
363
379
|
|
|
364
380
|
|
|
381
|
+
def resolve_github_repo_private(repo_dir):
|
|
382
|
+
result = sp.run(['gh', 'repo', 'view', '--json', 'isPrivate', '--jq', '.isPrivate'],
|
|
383
|
+
cwd=repo_dir, capture_output=True, text=True, timeout=30)
|
|
384
|
+
if result.returncode == 0:
|
|
385
|
+
text = result.stdout.strip().lower()
|
|
386
|
+
if text in ('true', 'false'):
|
|
387
|
+
return text == 'true'
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
|
|
365
391
|
def github_file_url(repo_name, ref, path_value, mode='blob'):
|
|
366
392
|
safe_path = urllib.parse.quote(str(path_value or '').lstrip('/'), safe='/._-')
|
|
367
393
|
return 'https://github.com/' + repo_name + '/' + mode + '/' + ref + '/' + safe_path
|
|
368
394
|
|
|
369
395
|
|
|
396
|
+
def parse_github_image_markdown(line):
|
|
397
|
+
match = re.search(r'!\[[^\]]*\]\((https://github\.com/user-attachments/assets/[^)\s]+)\)', str(line or '').strip())
|
|
398
|
+
return match.group(1) if match else ''
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def upload_local_images_to_github_attachments(state, repo_dir):
|
|
402
|
+
if truthy(os.environ.get('RIDDLE_PROOF_DISABLE_GITHUB_IMAGE_ATTACHMENTS')):
|
|
403
|
+
state['proof_image_attachments'] = {'ok': False, 'skipped': True, 'reason': 'disabled'}
|
|
404
|
+
save_state(state)
|
|
405
|
+
return state['proof_image_attachments']
|
|
406
|
+
|
|
407
|
+
image_sources = [
|
|
408
|
+
artifact for artifact in local_proof_artifact_sources(state)
|
|
409
|
+
if artifact.get('kind') == 'image' and artifact.get('path')
|
|
410
|
+
]
|
|
411
|
+
if not image_sources:
|
|
412
|
+
state['proof_image_attachments'] = {'ok': True, 'skipped': True, 'reason': 'no local image artifacts'}
|
|
413
|
+
save_state(state)
|
|
414
|
+
return state['proof_image_attachments']
|
|
415
|
+
|
|
416
|
+
repo_name = resolve_github_repo_name(repo_dir)
|
|
417
|
+
if not repo_name:
|
|
418
|
+
state['proof_image_attachments'] = {'ok': False, 'skipped': True, 'reason': 'repo name unavailable'}
|
|
419
|
+
save_state(state)
|
|
420
|
+
return state['proof_image_attachments']
|
|
421
|
+
|
|
422
|
+
timeout_seconds = int(os.environ.get('RIDDLE_PROOF_GH_IMAGE_TIMEOUT_SECONDS') or '15')
|
|
423
|
+
command = ['gh', 'image'] + [artifact.get('path') for artifact in image_sources] + ['--repo', repo_name]
|
|
424
|
+
try:
|
|
425
|
+
result = sp.run(command, cwd=repo_dir, capture_output=True, text=True, timeout=timeout_seconds)
|
|
426
|
+
except sp.TimeoutExpired:
|
|
427
|
+
state['proof_image_attachments'] = {'ok': False, 'skipped': True, 'reason': 'gh image timed out'}
|
|
428
|
+
save_state(state)
|
|
429
|
+
return state['proof_image_attachments']
|
|
430
|
+
|
|
431
|
+
if result.returncode != 0:
|
|
432
|
+
state['proof_image_attachments'] = {
|
|
433
|
+
'ok': False,
|
|
434
|
+
'skipped': True,
|
|
435
|
+
'reason': 'gh image failed',
|
|
436
|
+
'stderr': (result.stderr or result.stdout or '')[:500],
|
|
437
|
+
}
|
|
438
|
+
save_state(state)
|
|
439
|
+
return state['proof_image_attachments']
|
|
440
|
+
|
|
441
|
+
urls = [parse_github_image_markdown(line) for line in result.stdout.splitlines()]
|
|
442
|
+
urls = [url for url in urls if url]
|
|
443
|
+
if not urls:
|
|
444
|
+
state['proof_image_attachments'] = {'ok': False, 'skipped': True, 'reason': 'gh image returned no attachment URLs'}
|
|
445
|
+
save_state(state)
|
|
446
|
+
return state['proof_image_attachments']
|
|
447
|
+
|
|
448
|
+
artifacts = []
|
|
449
|
+
for index, artifact in enumerate(image_sources):
|
|
450
|
+
if index >= len(urls):
|
|
451
|
+
break
|
|
452
|
+
artifacts.append({
|
|
453
|
+
**artifact,
|
|
454
|
+
'attachment_url': urls[index],
|
|
455
|
+
'attachment': True,
|
|
456
|
+
'embeddable': True,
|
|
457
|
+
})
|
|
458
|
+
state['proof_image_attachments'] = {
|
|
459
|
+
'ok': True,
|
|
460
|
+
'repo': repo_name,
|
|
461
|
+
'artifacts': artifacts,
|
|
462
|
+
}
|
|
463
|
+
save_state(state)
|
|
464
|
+
return state['proof_image_attachments']
|
|
465
|
+
|
|
466
|
+
|
|
370
467
|
def write_artifact_readme(path_value, state, artifacts):
|
|
371
468
|
lines = [
|
|
372
469
|
'# Riddle Proof Artifacts',
|
|
@@ -408,6 +505,7 @@ def publish_local_proof_artifacts_to_github(state, repo_dir, pr_num):
|
|
|
408
505
|
repo_name = resolve_github_repo_name(repo_dir)
|
|
409
506
|
if not repo_name:
|
|
410
507
|
raise SystemExit('Could not resolve GitHub repository name for proof artifact publication.')
|
|
508
|
+
repo_private = resolve_github_repo_private(repo_dir)
|
|
411
509
|
|
|
412
510
|
run_id = safe_slug(state.get('run_id') or str(int(time.time())), 'run')
|
|
413
511
|
pr_slug = safe_slug('pr-' + str(pr_num or 'unknown'), 'pr')
|
|
@@ -474,11 +572,13 @@ def publish_local_proof_artifacts_to_github(state, repo_dir, pr_num):
|
|
|
474
572
|
published_path = artifact.get('published_path')
|
|
475
573
|
artifact['raw_url'] = github_file_url(repo_name, commit, published_path, 'raw')
|
|
476
574
|
artifact['html_url'] = github_file_url(repo_name, commit, published_path, 'blob')
|
|
575
|
+
artifact['embeddable'] = False if repo_private is True and artifact.get('kind') == 'image' else True
|
|
477
576
|
publication = {
|
|
478
577
|
'ok': True,
|
|
479
578
|
'branch': artifact_branch,
|
|
480
579
|
'commit': commit,
|
|
481
580
|
'repo': repo_name,
|
|
581
|
+
'repo_private': repo_private,
|
|
482
582
|
'html_url': github_file_url(repo_name, commit, artifact_dir_name, 'tree'),
|
|
483
583
|
'manifest_url': github_file_url(repo_name, commit, artifact_dir_name + '/proof-artifacts.json', 'blob'),
|
|
484
584
|
'readme_url': github_file_url(repo_name, commit, artifact_dir_name + '/README.md', 'blob'),
|
|
@@ -1080,6 +1180,12 @@ if publication.get('ok') and not publication.get('skipped'):
|
|
|
1080
1180
|
elif publication.get('skipped'):
|
|
1081
1181
|
print('Proof artifact publication skipped: ' + publication.get('reason', 'unknown'))
|
|
1082
1182
|
|
|
1183
|
+
image_attachments = upload_local_images_to_github_attachments(s, repo_dir)
|
|
1184
|
+
if image_attachments.get('ok') and not image_attachments.get('skipped'):
|
|
1185
|
+
print('Proof images attached: ' + str(len(image_attachments.get('artifacts') or [])))
|
|
1186
|
+
elif image_attachments.get('skipped'):
|
|
1187
|
+
print('Proof image attachment skipped: ' + image_attachments.get('reason', 'unknown'))
|
|
1188
|
+
|
|
1083
1189
|
# Post proof comment on PR
|
|
1084
1190
|
body = '## Riddle Proof — Proof of Fix\n\n'
|
|
1085
1191
|
body += '**Goal:** ' + s.get('change_request', '') + '\n\n'
|
|
@@ -1099,15 +1205,34 @@ if publication.get('ok') and not publication.get('skipped'):
|
|
|
1099
1205
|
before_image = first_public_artifact_url(s, 'before', 'image')
|
|
1100
1206
|
prod_image = first_public_artifact_url(s, 'prod', 'image')
|
|
1101
1207
|
after_image = first_public_artifact_url(s, 'after', 'image')
|
|
1208
|
+
linked_image_artifacts = [
|
|
1209
|
+
artifact for artifact in public_artifacts
|
|
1210
|
+
if artifact.get('kind') == 'image'
|
|
1211
|
+
and artifact.get('embeddable') is False
|
|
1212
|
+
and (artifact.get('html_url') or artifact.get('raw_url'))
|
|
1213
|
+
]
|
|
1102
1214
|
if before_image:
|
|
1103
1215
|
body += '### Before\n\n\n'
|
|
1104
1216
|
if prod_image:
|
|
1105
1217
|
body += '### Prod\n\n\n'
|
|
1106
1218
|
if after_image:
|
|
1107
1219
|
body += '### After\n\n\n'
|
|
1220
|
+
elif linked_image_artifacts:
|
|
1221
|
+
body += '### After evidence\nA screenshot artifact was captured, but GitHub attachment upload was unavailable; linked image artifacts are listed below.\n\n'
|
|
1108
1222
|
else:
|
|
1109
1223
|
body += '### After evidence\nNo after screenshot was captured for this verification mode; structured evidence is summarized below.\n\n'
|
|
1110
1224
|
|
|
1225
|
+
if linked_image_artifacts:
|
|
1226
|
+
body += '### Image artifacts\n'
|
|
1227
|
+
for artifact in linked_image_artifacts[:12]:
|
|
1228
|
+
label = artifact.get('name') or artifact.get('filename') or 'image'
|
|
1229
|
+
url = artifact.get('html_url') or artifact.get('raw_url')
|
|
1230
|
+
body += '- [' + str(label) + '](' + str(url) + ')'
|
|
1231
|
+
if artifact.get('role'):
|
|
1232
|
+
body += ' (' + str(artifact.get('role')) + ')'
|
|
1233
|
+
body += '\n'
|
|
1234
|
+
body += '\n'
|
|
1235
|
+
|
|
1111
1236
|
data_artifacts = [
|
|
1112
1237
|
artifact for artifact in public_artifacts
|
|
1113
1238
|
if artifact.get('kind') != 'image' and (artifact.get('html_url') or artifact.get('raw_url'))
|
|
@@ -27,7 +27,15 @@ import sys
|
|
|
27
27
|
|
|
28
28
|
args = sys.argv[1:]
|
|
29
29
|
if args[:3] == ["repo", "view", "--json"]:
|
|
30
|
-
|
|
30
|
+
if "isPrivate" in args:
|
|
31
|
+
print("true")
|
|
32
|
+
else:
|
|
33
|
+
print("example/test-repo")
|
|
34
|
+
raise SystemExit(0)
|
|
35
|
+
if args and args[0] == "image":
|
|
36
|
+
image_paths = [arg for arg in args[1:] if not arg.startswith("--") and arg != "example/test-repo"]
|
|
37
|
+
for index, image_path in enumerate(image_paths):
|
|
38
|
+
print(f"")
|
|
31
39
|
raise SystemExit(0)
|
|
32
40
|
if args[:2] == ["pr", "list"]:
|
|
33
41
|
print("")
|
|
@@ -163,16 +171,16 @@ def main():
|
|
|
163
171
|
assert publication.get("ok") is True, "proof artifact publication should be recorded"
|
|
164
172
|
assert publication.get("artifacts"), "published artifact list should be recorded"
|
|
165
173
|
assert updated.get("ship_report", {}).get("after_artifact_url", "").startswith(
|
|
166
|
-
"https://github.com/
|
|
167
|
-
), "ship report should expose a GitHub-hosted after artifact
|
|
174
|
+
"https://github.com/user-attachments/assets/"
|
|
175
|
+
), "ship report should expose a GitHub-hosted attachment URL for the after artifact"
|
|
168
176
|
|
|
169
177
|
comment = comment_body_path.read_text(encoding="utf-8")
|
|
170
178
|
assert "file://" not in comment, "PR proof comment must not expose local file URLs"
|
|
171
179
|
assert "raw.githubusercontent.com" not in comment, (
|
|
172
180
|
"PR proof comment must not depend on unauthenticated raw GitHub URLs"
|
|
173
181
|
)
|
|
174
|
-
assert "
|
|
177
185
|
assert "[proof.json](https://github.com/example/test-repo/blob/" in comment, (
|
|
178
186
|
"PR proof comment should link the structured proof JSON"
|