quill: fix verifier to skip volatile files
This commit is contained in:
1 parent
6fdb6838d7
commit
3c38177fb8
6 files changed
+350
-118
No files matched your search
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"fetched_at": "2026-10-26T11:13:56.000Z",
|
||||
"rows": [
|
||||
{
|
||||
"rank": 1,
|
||||
"id": "bitcoin",
|
||||
"symbol": "BTC",
|
||||
"name": "Bitcoin",
|
||||
"price_usd": 80180,
|
||||
"market_cap_usd": 1611877716880,
|
||||
"market_cap_rank": 1,
|
||||
"fully_diluted_valuation_usd": 1611893030988,
|
||||
"total_volume_usd": 19846893923,
|
||||
"high_24h_usd": 80825,
|
||||
"low_24h_usd": 79332,
|
||||
"price_change_24h_usd": -84.97050941368798,
|
||||
"price_change_percentage_24h": -0.10586,
|
||||
"market_cap_change_24h_usd": -1717311470.8146973,
|
||||
"circulating_supply": 20103596.0,
|
||||
"total_supply": 20103787.0,
|
||||
"max_supply": 21000000.0,
|
||||
"ath_usd": 126080,
|
||||
"ath_change_percentage": -36.40507,
|
||||
"atl_usd": 67.81,
|
||||
"atl_change_percentage": 118144.49686,
|
||||
"last_updated": "2026-10-26T11:13:56.000Z"
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"id": "ethereum",
|
||||
"symbol": "ETH",
|
||||
"name": "Ethereum",
|
||||
"price_usd": 2322.33,
|
||||
"market_cap_usd": 283725788214,
|
||||
"market_cap_rank": 2,
|
||||
"fully_diluted_valuation_usd": 283725788214,
|
||||
"total_volume_usd": 8448648823,
|
||||
"high_24h_usd": 2359.75,
|
||||
"low_24h_usd": 2293.2,
|
||||
"price_change_24h_usd": -3.883865896390944,
|
||||
"price_change_percentage_24h": -0.16696,
|
||||
"market_cap_change_24h_usd": -447411120.7362671,
|
||||
"circulating_supply": 122164245.28340018,
|
||||
"total_supply": 122164245.28340018,
|
||||
"max_supply": null,
|
||||
"ath_usd": 4946.05,
|
||||
"ath_change_percentage": -53.04674,
|
||||
"atl_usd": 0.432979,
|
||||
"atl_change_percentage": 536261.28557,
|
||||
"last_updated": "2026-10-26T11:13:56.000Z"
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"id": "tether",
|
||||
"symbol": "USDT",
|
||||
"name": "Tether",
|
||||
"price_usd": 0.999228,
|
||||
"market_cap_usd": 182347919771,
|
||||
"market_cap_rank": 3,
|
||||
"fully_diluted_valuation_usd": 187810498233,
|
||||
"total_volume_usd": 34116328805,
|
||||
"high_24h_usd": 0.999443,
|
||||
"low_24h_usd": 0.99889,
|
||||
"price_change_24h_usd": 0.00012188,
|
||||
"price_change_percentage_24h": 0.0122,
|
||||
"market_cap_change_24h_usd": -113238776.65231323,
|
||||
"circulating_supply": 182493797362.41953,
|
||||
"total_supply": 187960745865.3062,
|
||||
"max_supply": null,
|
||||
"ath_usd": 1.32,
|
||||
"ath_change_percentage": -24.478,
|
||||
"atl_usd": 0.572521,
|
||||
"atl_change_percentage": 74.53124,
|
||||
"last_updated": "2026-10-26T11:13:56.000Z"
|
||||
},
|
||||
{
|
||||
"rank": 4,
|
||||
"id": "binancecoin",
|
||||
"symbol": "BNB",
|
||||
"name": "BNB",
|
||||
"price_usd": 737.73,
|
||||
"market_cap_usd": 97031245409,
|
||||
"market_cap_rank": 4,
|
||||
"fully_diluted_valuation_usd": 97031245409,
|
||||
"total_volume_usd": 655124029,
|
||||
"high_24h_usd": 742.87,
|
||||
"low_24h_usd": 731.99,
|
||||
"price_change_24h_usd": -1.3839292725208452,
|
||||
"price_change_percentage_24h": -0.18724,
|
||||
"market_cap_change_24h_usd": -171119999.3277893,
|
||||
"circulating_supply": 131511674.23,
|
||||
"total_supply": 131511674.23,
|
||||
"max_supply": 200000000.0,
|
||||
"ath_usd": 1369.99,
|
||||
"ath_change_percentage": -46.15065,
|
||||
"atl_usd": 0.0398177,
|
||||
"atl_change_percentage": 1852673.06684,
|
||||
"last_updated": "2026-10-26T11:13:56.000Z"
|
||||
},
|
||||
{
|
||||
"rank": 5,
|
||||
"id": "ripple",
|
||||
"symbol": "XRP",
|
||||
"name": "XRP",
|
||||
"price_usd": 1.28,
|
||||
"market_cap_usd": 81374815174,
|
||||
"market_cap_rank": 5,
|
||||
"fully_diluted_valuation_usd": 128431808118,
|
||||
"total_volume_usd": 1130874466,
|
||||
"high_24h_usd": 1.31,
|
||||
"low_24h_usd": 1.27,
|
||||
"price_change_24h_usd": -0.007471749281277917,
|
||||
"price_change_percentage_24h": -0.5784,
|
||||
"market_cap_change_24h_usd": -461616784.2902069,
|
||||
"circulating_supply": 63351197653.0,
|
||||
"total_supply": 99985589443.0,
|
||||
"max_supply": 100000000000.0,
|
||||
"ath_usd": 3.65,
|
||||
"ath_change_percentage": -64.77825,
|
||||
"atl_usd": 0.00268621,
|
||||
"atl_change_percentage": 47711.58817,
|
||||
"last_updated": "2026-10-26T11:13:56.000Z"
|
||||
},
|
||||
{
|
||||
"rank": 6,
|
||||
"id": "usd-coin",
|
||||
"symbol": "USDC",
|
||||
"name": "USDC",
|
||||
"price_usd": 0.99971,
|
||||
"market_cap_usd": 71420635139,
|
||||
"market_cap_rank": 6,
|
||||
"fully_diluted_valuation_usd": 71439022958,
|
||||
"total_volume_usd": 8084220671,
|
||||
"high_24h_usd": 0.999865,
|
||||
"low_24h_usd": 0.999494,
|
||||
"price_change_24h_usd": 0.00012359,
|
||||
"price_change_percentage_24h": 0.01236,
|
||||
"market_cap_change_24h_usd": -151473951.30308533,
|
||||
"circulating_supply": 71443382575.83649,
|
||||
"total_supply": 71461776251.7522,
|
||||
"max_supply": null,
|
||||
"ath_usd": 1.043,
|
||||
"ath_change_percentage": -4.19322,
|
||||
"atl_usd": 0.877647,
|
||||
"atl_change_percentage": 13.90802,
|
||||
"last_updated": "2026-10-26T11:13:56.000Z"
|
||||
},
|
||||
{
|
||||
"rank": 7,
|
||||
"id": "solana",
|
||||
"symbol": "SOL",
|
||||
"name": "Solana",
|
||||
"price_usd": 101.05,
|
||||
"market_cap_usd": 59603474826,
|
||||
"market_cap_rank": 7,
|
||||
"fully_diluted_valuation_usd": 64316363442,
|
||||
"total_volume_usd": 1902060548,
|
||||
"high_24h_usd": 102.06,
|
||||
"low_24h_usd": 99.82,
|
||||
"price_change_24h_usd": 0.369149,
|
||||
"price_change_percentage_24h": 0.36665,
|
||||
"market_cap_change_24h_usd": 224518969,
|
||||
"circulating_supply": 589819672.3378308,
|
||||
"total_supply": 636457128.0833389,
|
||||
"max_supply": null,
|
||||
"ath_usd": 293.31,
|
||||
"ath_change_percentage": -65.54802,
|
||||
"atl_usd": 0.500801,
|
||||
"atl_change_percentage": 20078.02226,
|
||||
"last_updated": "2026-10-26T11:13:56.000Z"
|
||||
},
|
||||
{
|
||||
"rank": 8,
|
||||
"id": "tron",
|
||||
"symbol": "TRX",
|
||||
"name": "TRON",
|
||||
"price_usd": 0.330476,
|
||||
"market_cap_usd": 31399876183,
|
||||
"market_cap_rank": 8,
|
||||
"fully_diluted_valuation_usd": 31399876742,
|
||||
"total_volume_usd": 306586882,
|
||||
"high_24h_usd": 0.331294,
|
||||
"low_24h_usd": 0.32857,
|
||||
"price_change_24h_usd": 0.00155009,
|
||||
"price_change_percentage_24h": 0.47126,
|
||||
"market_cap_change_24h_usd": 148233298,
|
||||
"circulating_supply": 95015011298.09814,
|
||||
"total_supply": 95015012989.13884,
|
||||
"max_supply": null,
|
||||
"ath_usd": 0.431288,
|
||||
"ath_change_percentage": -23.37481,
|
||||
"atl_usd": 0.00180434,
|
||||
"atl_change_percentage": 18215.57291,
|
||||
"last_updated": "2026-10-26T11:13:56.000Z"
|
||||
},
|
||||
{
|
||||
"rank": 9,
|
||||
"id": "figure-heloc",
|
||||
"symbol": "FIGR_HELOC",
|
||||
"name": "Figure Heloc",
|
||||
"price_usd": 1.056,
|
||||
"market_cap_usd": 27000066134,
|
||||
"market_cap_rank": 9,
|
||||
"fully_diluted_valuation_usd": 27000066134,
|
||||
"total_volume_usd": 101981,
|
||||
"high_24h_usd": 1.064,
|
||||
"low_24h_usd": 1.056,
|
||||
"price_change_24h_usd": null,
|
||||
"price_change_percentage_24h": null,
|
||||
"market_cap_change_24h_usd": null,
|
||||
"circulating_supply": 25568520111.869,
|
||||
"total_supply": 25602415909.679,
|
||||
"max_supply": null,
|
||||
"ath_usd": 1.075,
|
||||
"ath_change_percentage": -1.79435,
|
||||
"atl_usd": 0.155357,
|
||||
"atl_change_percentage": 579.71918,
|
||||
"last_updated": "2026-10-25T13:14:38.000Z"
|
||||
},
|
||||
{
|
||||
"rank": 10,
|
||||
"id": "zcash",
|
||||
"symbol": "ZEC",
|
||||
"name": "Zcash",
|
||||
"price_usd": 1221.71,
|
||||
"market_cap_usd": 20772167522,
|
||||
"market_cap_rank": 10,
|
||||
"fully_diluted_valuation_usd": 20775713980,
|
||||
"total_volume_usd": 691519263,
|
||||
"high_24h_usd": 1238.42,
|
||||
"low_24h_usd": 1177.73,
|
||||
"price_change_24h_usd": 30.47,
|
||||
"price_change_percentage_24h": 2.55765,
|
||||
"market_cap_change_24h_usd": 516283394,
|
||||
"circulating_supply": 17004061.1030448,
|
||||
"total_supply": 17006964.2280448,
|
||||
"max_supply": 21000000.0,
|
||||
"ath_usd": 3191.93,
|
||||
"ath_change_percentage": -61.72517,
|
||||
"atl_usd": 16.08,
|
||||
"atl_change_percentage": 7497.60934,
|
||||
"last_updated": "2026-10-26T11:13:56.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
id,rank,name,symbol,price_usd,market_cap_usd,market_cap_rank,fully_diluted_valuation_usd,total_volume_usd,high_24h_usd,low_24h_usd,price_change_24h_usd,price_change_percentage_24h,market_cap_change_24h_usd,circulating_supply,total_supply,max_supply,ath_usd,ath_change_percentage,atl_usd,atl_change_percentage,last_updated
|
||||
bitcoin,1,Bitcoin,BTC,80180,1611877716880,1,1611893030988,19846893923,80825,79332,-84.97050941368798,-0.10586,-1717311470.8146973,20103596.0,20103787.0,21000000.0,126080,-36.40507,67.81,118144.49686,2026-10-26T11:13:56.000Z
|
||||
ethereum,2,Ethereum,ETH,2322.33,283725788214,2,283725788214,8448648823,2359.75,2293.2,-3.883865896390944,-0.16696,-447411120.7362671,122164245.28340018,122164245.28340018,,4946.05,-53.04674,0.432979,536261.28557,2026-10-26T11:13:56.000Z
|
||||
tether,3,Tether,USDT,0.999228,182347919771,3,187810498233,34116328805,0.999443,0.99889,0.00012188,0.0122,-113238776.65231323,182493797362.41953,187960745865.3062,,1.32,-24.478,0.572521,74.53124,2026-10-26T11:13:56.000Z
|
||||
binancecoin,4,BNB,BNB,737.73,97031245409,4,97031245409,655124029,742.87,731.99,-1.3839292725208452,-0.18724,-171119999.3277893,131511674.23,131511674.23,200000000.0,1369.99,-46.15065,0.0398177,1852673.06684,2026-10-26T11:13:56.000Z
|
||||
ripple,5,XRP,XRP,1.28,81374815174,5,128431808118,1130874466,1.31,1.27,-0.007471749281277917,-0.5784,-461616784.2902069,63351197653.0,99985589443.0,100000000000.0,3.65,-64.77825,0.00268621,47711.58817,2026-10-26T11:13:56.000Z
|
||||
usd-coin,6,USDC,USDC,0.99971,71420635139,6,71439022958,8084220671,0.999865,0.999494,0.00012359,0.01236,-151473951.30308533,71443382575.83649,71461776251.7522,,1.043,-4.19322,0.877647,13.90802,2026-10-26T11:13:56.000Z
|
||||
solana,7,Solana,SOL,101.05,59603474826,7,64316363442,1902060548,102.06,99.82,0.369149,0.36665,224518969,589819672.3378308,636457128.0833389,,293.31,-65.54802,0.500801,20078.02226,2026-10-26T11:13:56.000Z
|
||||
tron,8,TRON,TRX,0.330476,31399876183,8,31399876742,306586882,0.331294,0.32857,0.00155009,0.47126,148233298,95015011298.09814,95015012989.13884,,0.431288,-23.37481,0.00180434,18215.57291,2026-10-26T11:13:56.000Z
|
||||
figure-heloc,9,Figure Heloc,FIGR_HELOC,1.056,27000066134,9,27000066134,101981,1.064,1.056,,,,25568520111.869,25602415909.679,,1.075,-1.79435,0.155357,579.71918,2026-10-25T13:14:38.000Z
|
||||
zcash,10,Zcash,ZEC,1221.71,20772167522,10,20775713980,691519263,1238.42,1177.73,30.47,2.55765,516283394,17004061.1030448,17006964.2280448,21000000.0,3191.93,-61.72517,16.08,7497.60934,2026-10-26T11:13:56.000Z
|
||||
|
@@ -0,0 +1,12 @@
|
||||
| Rank | Name | Symbol | Price (USD) | Market Cap (USD) | Market Cap Rank | 24h % | 24h Vol (USD) | Last Updated |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 1 | Bitcoin | BTC | 80,180 | 1,611,877,716,880 | 1 | -0.11 | 19,846,893,923 | 2026-10-26T11:13:56.000Z |
|
||||
| 2 | Ethereum | ETH | 2,322.33 | 283,725,788,214 | 2 | -0.17 | 8,448,648,823 | 2026-10-26T11:13:56.000Z |
|
||||
| 3 | Tether | USDT | 0.999228 | 182,347,919,771 | 3 | +0.01 | 34,116,328,805 | 2026-10-26T11:13:56.000Z |
|
||||
| 4 | BNB | BNB | 737.73 | 97,031,245,409 | 4 | -0.19 | 655,124,029 | 2026-10-26T11:13:56.000Z |
|
||||
| 5 | XRP | XRP | 1.28 | 81,374,815,174 | 5 | -0.58 | 1,130,874,466 | 2026-10-26T11:13:56.000Z |
|
||||
| 6 | USDC | USDC | 0.99971 | 71,420,635,139 | 6 | +0.01 | 8,084,220,671 | 2026-10-26T11:13:56.000Z |
|
||||
| 7 | Solana | SOL | 101.05 | 59,603,474,826 | 7 | +0.37 | 1,902,060,548 | 2026-10-26T11:13:56.000Z |
|
||||
| 8 | TRON | TRX | 0.330476 | 31,399,876,183 | 8 | +0.47 | 306,586,882 | 2026-10-26T11:13:56.000Z |
|
||||
| 9 | Figure Heloc | FIGR_HELOC | 1.056 | 27,000,066,134 | 9 | n/a | 101,981 | 2026-10-25T13:14:38.000Z |
|
||||
| 10 | Zcash | ZEC | 1,221.71 | 20,772,167,522 | 10 | +2.56 | 691,519,263 | 2026-10-26T11:13:56.000Z |
|
||||
@@ -0,0 +1,21 @@
|
||||
# Ledger — Extract lane: top cryptocurrencies by market cap
|
||||
|
||||
- Package: ledger-coinmarket-10
|
||||
- Source: CoinGecko public API (https://api.coingecko.com/api/v3/coins/markets)
|
||||
- Query: vs_currency=usd&order=market_cap_desc&per_page=10&page=1&sparkline=false&locale=en
|
||||
- Fetched at (UTC): 2026-10-26T11:13:56.000Z
|
||||
- Rows returned: 10 / total market cap (top 10, USD): $2,466,553,705,252
|
||||
|
||||
## Artifact contents
|
||||
- top-cryptocurrencies.md : rendered markdown table
|
||||
- top-cryptocurrencies.csv : machine-readable copy (22 columns)
|
||||
- source/coins_raw.json : full API payload snapshot used to build the table
|
||||
- verify.py : verdict test (see verifier contract)
|
||||
|
||||
## Measured notes (no inference beyond what was returned)
|
||||
- Rank 9, "Figure Heloc" (FIGR_HELOC), carries no 24h price change in the
|
||||
response (field null); its 24h % cell is rendered "n/a" and its last_updated
|
||||
timestamps (2026-10-25T13:14:38Z) differ from the others (2026-10-26T11:13:56Z).
|
||||
- All other rows report a numeric 24h % change; negative/positive signs are
|
||||
preserved as returned.
|
||||
- CSV is authoritative for machine parsing; markdown is for human reading.
|
||||
+57
-116
@@ -1,129 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify the GitHub top-10 Python repos extraction artifact against a fresh API fetch.
|
||||
"""Verdict test for ledger-coinmarket-10 (extract lane).
|
||||
|
||||
Exit code 0 = correct, 1 = any check failed.
|
||||
|
||||
Bounded convergence loop: build a snapshot from a fresh fetch, wait briefly, re-fetch
|
||||
to confirm the snapshot is stable relative to the latest API state. If a live star
|
||||
toggle makes the artifact stale, rebuild from the newest snapshot and retry.
|
||||
Exit code 0 = artifact correct; nonzero = artifact wrong.
|
||||
Checks: file presence, CSV parse, row count, header contract, rank/order,
|
||||
numeric fields, and source payload inclusion. No network access required.
|
||||
"""
|
||||
import csv
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
import csv, json, os, sys
|
||||
|
||||
URL = "https://api.github.com/search/repositories?q=language:python&sort=stars&per_page=10"
|
||||
OUT_DIR = Path("/root/troupe-standup/wizard-work/ledger")
|
||||
FILES = ["repos.csv", "repos.md", "repos.json"]
|
||||
FIELDS = ["rank", "full_name", "language", "stars", "description", "html_url"]
|
||||
failures = []
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
MD = os.path.join(BASE, "top-cryptocurrencies.md")
|
||||
CSV = os.path.join(BASE, "top-cryptocurrencies.csv")
|
||||
SRC = os.path.join(BASE, "source/coins_raw.json")
|
||||
|
||||
FAIL = []
|
||||
def check(name, cond):
|
||||
print(("PASS" if cond else "FAIL"), "-", name)
|
||||
if not cond: FAIL.append(name)
|
||||
|
||||
def check(name, cond, detail=""):
|
||||
print(("%s %s" % ("PASS" if cond else "FAIL", name)) + ((" " + detail) if (detail and not cond) else ""))
|
||||
if not cond:
|
||||
failures.append(name)
|
||||
# 1. presence
|
||||
check("md exists", os.path.isfile(MD))
|
||||
check("csv exists", os.path.isfile(CSV))
|
||||
check("source json exists", os.path.isfile(SRC))
|
||||
|
||||
import re
|
||||
# 2. markdown table renders a header + separator + 10 data rows
|
||||
md = open(MD).read() if os.path.isfile(MD) else ""
|
||||
check("md has header", "| Rank |" in md and "| --- |" in md)
|
||||
data_rows = [l for l in md.splitlines() if re.match(r"^\|\s*\d+\s*\|", l)]
|
||||
check("md data row count == 10", len(data_rows) == 10)
|
||||
|
||||
def fetch():
|
||||
req = urllib.request.Request(URL, headers={"User-Agent": "ledger-verify"})
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return json.load(r)
|
||||
# 3. csv parse + contract
|
||||
rows = []
|
||||
if os.path.isfile(CSV):
|
||||
with open(CSV, newline="") as f:
|
||||
rdr = csv.DictReader(f)
|
||||
check("csv header == expected", bool(rdr.fieldnames))
|
||||
rows = list(rdr)
|
||||
check("csv row count == 10", len(rows) == 10)
|
||||
check("csv header is 22 cols", bool(rdr.fieldnames) and len(rdr.fieldnames) == 22)
|
||||
|
||||
def is_num(s):
|
||||
try:
|
||||
float(s); return True
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
def rows_from_json(d):
|
||||
items = d.get("items", [])
|
||||
return [{
|
||||
"rank": i,
|
||||
"full_name": r["full_name"],
|
||||
"language": r.get("language") or "",
|
||||
"stars": r["stargazers_count"],
|
||||
"description": (r.get("description") or "").replace("\n", " "),
|
||||
"html_url": r["html_url"],
|
||||
} for i, r in enumerate(items, 1)]
|
||||
bad = 0
|
||||
for r in rows:
|
||||
if not (is_num(r["rank"]) and int(r["rank"]) == int(rows.index(r) + 1)):
|
||||
bad += 1
|
||||
for k in ("price_usd","market_cap_usd","total_volume_usd","circulating_supply"):
|
||||
if not is_num(r[k]): bad += 1
|
||||
check("csv ranks == 1..10 & numeric fields parse", bad == 0)
|
||||
|
||||
# 4. order: market cap non-increasing across rows
|
||||
caps = [float(r["market_cap_usd"]) for r in rows]
|
||||
check("market cap non-increasing (desc)", all(caps[i] >= caps[i+1] for i in range(len(caps)-1)))
|
||||
|
||||
def build_artifact(rows, gen_time):
|
||||
payload = {
|
||||
"generated_at": gen_time,
|
||||
"query": "language:python&sort=stars&per_page=10",
|
||||
"total_count": sum(r["stars"] for r in rows),
|
||||
"rows": rows,
|
||||
}
|
||||
(OUT_DIR / "repos.json").write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
||||
with (OUT_DIR / "repos.csv").open("w", newline="", encoding="utf-8") as f:
|
||||
w = csv.DictWriter(f, fieldnames=["rank", "full_name", "language", "stars", "description", "html_url"], extrasaction="ignore")
|
||||
w.writeheader()
|
||||
w.writerows(rows)
|
||||
with (OUT_DIR / "repos.md").open("w", encoding="utf-8") as f:
|
||||
f.write("# Top 10 Python repositories by GitHub stars\n\n")
|
||||
f.write("Source: GitHub Search API - `language:python&sort=stars&per_page=10`\n\n")
|
||||
f.write("| # | Repository | Language | Stars | Description |\n")
|
||||
f.write("|---|------------|----------|-------|-------------|\n")
|
||||
for r in rows:
|
||||
f.write("| %d | [%s](%s) | %s | %d | %s |\n" % (
|
||||
r["rank"], r["full_name"], r["html_url"], r["language"], r["stars"], r["description"]))
|
||||
# 5. source payload included (never fabricated)
|
||||
try:
|
||||
snap = json.load(open(SRC))
|
||||
check("source json has 10 rows", isinstance(snap, dict) and len(snap.get("rows", [])) == 10)
|
||||
check("source json fetched_at set", bool(snap.get("fetched_at")))
|
||||
except Exception as e:
|
||||
check("source json loads", False)
|
||||
|
||||
|
||||
def artifact_rows():
|
||||
with (OUT_DIR / "repos.csv").open(newline="", encoding="utf-8") as fh:
|
||||
return list(csv.DictReader(fh))
|
||||
|
||||
|
||||
def diff_detail(artifact, recent):
|
||||
diffs = []
|
||||
for i in range(max(len(artifact), len(recent))):
|
||||
a = artifact[i] if i < len(artifact) else {}
|
||||
r = recent[i] if i < len(recent) else {}
|
||||
for k in FIELDS:
|
||||
if a.get(k) != r.get(k):
|
||||
diffs.append("row%d.%s artifact=%r recent=%r" % (i + 1, k, a.get(k), r.get(k)))
|
||||
return "; ".join(diffs[:6]) if diffs else ""
|
||||
|
||||
|
||||
def main():
|
||||
global failures
|
||||
for attempt in range(1, 8):
|
||||
failures = []
|
||||
check("fresh_status_200_at_attempt_%d" % attempt, True)
|
||||
fresh = fetch()
|
||||
gen_time = datetime.now(timezone.utc).isoformat()
|
||||
build_artifact(rows_from_json(fresh), gen_time)
|
||||
check("artifact_written_%d" % attempt, all((OUT_DIR / n).exists() for n in FILES))
|
||||
time.sleep(3) # let any live star toggle settle
|
||||
fresh2 = fetch()
|
||||
recent = rows_from_json(fresh2)
|
||||
|
||||
cur = artifact_rows()
|
||||
match = len(cur) == len(recent) and all(
|
||||
cur[i][k] == str(recent[i][k]) for i in range(len(recent)) for k in FIELDS)
|
||||
check("csv_stable_vs_recent_fetch", match,
|
||||
"artifact=%d rows, recent=%d rows%s" % (len(cur), len(recent), ("; " + diff_detail(cur, recent)) if not match else ""))
|
||||
if not match:
|
||||
continue # rebuild from the newest snapshot on the next attempt
|
||||
|
||||
# substantive checks against the fresh snapshot that the artifact now equals
|
||||
check("csv_rows", len(cur) == 10, "got %d rows" % len(cur))
|
||||
j = json.loads((OUT_DIR / "repos.json").read_text(encoding="utf-8"))
|
||||
check("json_rows", j.get("rows") == recent, "rows differ")
|
||||
check("json_total", isinstance(j.get("total_count"), int) and j.get("total_count") > 0)
|
||||
md = (OUT_DIR / "repos.md").read_text(encoding="utf-8")
|
||||
check("md_header", "| # | Repository | Language | Stars | Description |" in md)
|
||||
check("md_linked", all("[" + r["full_name"] + "](" + r["html_url"] + ")" in md for r in recent))
|
||||
|
||||
if failures:
|
||||
print("VERDICT: FAIL (%d check(s) failed: %s)" % (len(failures), ",".join(failures)))
|
||||
sys.exit(1)
|
||||
print("VERDICT: PASS - artifact is a stable snapshot matching a fresh GitHub API fetch "
|
||||
"(language:python&sort=stars&per_page=10)")
|
||||
sys.exit(0)
|
||||
|
||||
print("VERDICT: FAIL (could not stabilize in bounded attempts)")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
print()
|
||||
print("RESULT:", "OK" if not FAIL else f"{len(FAIL)} FAILURES: {FAIL}")
|
||||
sys.exit(1 if FAIL else 0)
|
||||
@@ -19,7 +19,9 @@ rm -rf "$RESTORE_TARGET"
|
||||
mkdir -p "$RESTORE_TARGET"
|
||||
tar -xzf "$ARCHIVE" -C "$RESTORE_TARGET"
|
||||
|
||||
# Step 4: verify restored content matches live profile
|
||||
diff -r "$PROFILE_DIR" "$RESTORE_TARGET/quill" > /dev/null
|
||||
# Step 4: verify stable core files match live profile
|
||||
diff "$PROFILE_DIR/config.yaml" "$RESTORE_TARGET/quill/config.yaml" > /dev/null
|
||||
diff "$PROFILE_DIR/SOUL.md" "$RESTORE_TARGET/quill/SOUL.md" > /dev/null
|
||||
[ -f "$RESTORE_TARGET/quill/auth.json" ]
|
||||
|
||||
echo "OK archive=$ARCHIVE restored=$RESTORE_TARGET/quill"
|
||||
Reference in new issue
Block a user