Skip to content

Commit a002c38

Browse files
committed
h
1 parent c526347 commit a002c38

3 files changed

Lines changed: 49 additions & 16 deletions

File tree

worker/src/index.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ async function callTool(rawName, env, args = {}) {
942942

943943
function howtoSeal() {
944944
return {
945-
what: "AI Action Receipts: a portable, signed receipt for any AI action, in a public append-only transparency log. Input/output go in as HASHES only (the action label and metadata you send are published); you get back signature + RFC 6962 inclusion proof + operator-signed C2SP checkpoint; the tree root is anchored daily on Base (EAS). Evidence of existence and time — NOT a compliance certificate. Independent witness cosigning: invitation open.",
945+
what: "AI Action Receipts: a portable, signed receipt for any AI action, in a public append-only transparency log. Input/output go in as HASHES only (the action label and metadata you send are published); you get back signature + RFC 6962 inclusion proof + a C2SP checkpoint signed by us and cosigned by an independent witness (markovianprotocol.com/witness, since 2026-08-22); the tree root is anchored daily on Base (EAS). Evidence of existence and time — NOT a compliance certificate. A cosignature attests only that the log stayed append-only between the sizes that witness has seen; it says nothing about whether a sealed action is true.",
946946
paid_endpoint: `${SITE}/api/x402/seal`,
947947
price: "0.01 USDC on Base via x402 (unlimited)",
948948
demo: "MCP tool receipts.seal (mode demo) or POST https://gblin-mcp.gblin-mcp-worker.workers.dev/v1/seal-demo (5/day/IP, receipts marked demo:true)",
@@ -1386,12 +1386,12 @@ export default {
13861386
if (url.pathname === "/log" && request.method === "GET") {
13871387
const st = await rlogStatus(env);
13881388
return json({ ...st,
1389-
what: "GBLIN AI Action Receipts — signed append-only RFC 6962 transparency log of sealed AI actions (input/output as hashes only; the action label and metadata you send are published). A seal proves existence and time; root anchored daily on Base via EAS. It is NOT a compliance certificate and NOT an endorsement. Independent witness cosigning of the checkpoint: invitation open.",
1389+
what: "GBLIN AI Action Receipts — signed append-only RFC 6962 transparency log of sealed AI actions (input/output as hashes only; the action label and metadata you send are published). A seal proves existence and time; root anchored daily on Base via EAS. It is NOT a compliance certificate and NOT an endorsement. The checkpoint is cosigned by an independent witness since 2026-08-22 (see /log/witnesses); that cosignature attests only that the log stayed append-only between the sizes it has seen. More witnesses welcome.",
13901390
seal_paid: "POST https://gblin.digital/api/x402/seal ($0.01 USDC via x402)",
13911391
seal_demo: "POST /v1/seal-demo (5/day/IP, marked demo:true)",
13921392
read: "GET /v1/receipt/:index (free forever) · GET /log/proof/:index · GET /log/checkpoint",
13931393
explorer: "GET /receipt/:index (human page)",
1394-
for_witnesses: "GET /log/checkpoint (C2SP signed note) · GET /log/consistency?old=<m>&new=<n> (RFC 6962 append-only proof) · GET /log/leaves?start=&end= (raw records, recompute the tree yourself) · GET /log/proof/<i> (inclusion) · GET /log/witnesses (who cosigns, and how to join). We also push our checkpoints with c2sp.org/tlog-witness (POST add-checkpoint) to any witness that configures our origin. Cosigning invitation open.",
1394+
for_witnesses: "GET /log/checkpoint (C2SP signed note) · GET /log/consistency?old=<m>&new=<n> (RFC 6962 append-only proof) · GET /log/leaves?start=&end= (raw records, recompute the tree yourself) · GET /log/proof/<i> (inclusion) · GET /log/witnesses (who cosigns, and how to join). We also push our checkpoints with c2sp.org/tlog-witness (POST add-checkpoint) to any witness that configures our origin. One witness cosigns today (markovianprotocol.com/witness); more are welcome.",
13951395
anchor: "tree root anchored daily on Base via EAS (schema " + "0x9f433a96..., promiseId keccak256('gblin-receipts-log'))",
13961396
offline_verifier: "verify-receipt.mjs in github.com/gblinproject/gblin-treasury-risk-regime (zero deps)",
13971397
design_note: "https://github.com/gblinproject/gblin-treasury-risk-regime/blob/main/docs/ai-action-transparency-log.md — what a receipt proves and what it does not, wire formats, and the honest scale of this log",
@@ -1612,7 +1612,9 @@ export default {
16121612
} else {
16131613
// Giro di sonde del catalogo SOLO nei tick senza sigillo: il budget
16141614
// free è 50 subrequest/invocazione e il sigillo ne consuma parecchi.
1615-
await catalogTick(env).catch((e) => console.error("catalog:", e.message));
1615+
// Una volta all'ora, non a ogni tick: il budget KV free e' 1000 scritture/giorno
1616+
// e il catalogo ne scriveva una ogni 10 minuti (144) per un dato che cambia di rado.
1617+
if (new Date().getUTCMinutes() < 10) await catalogTick(env).catch((e) => console.error("catalog:", e.message));
16161618
}
16171619
}
16181620
})();

worker/src/rlog.mjs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,18 @@ export async function leaves(env, start, end) {
185185
const c = await env.COHERENCE.get(`rlog:entry:${i}`);
186186
out.push(c === null ? null : c);
187187
}
188-
return { start, end, size: N, encoding: "raw canonical JSON (gblin-canonical-json/1); leaf = SHA256(0x00 || record)", leaves: out };
188+
// Onesta': quattro record (indici 11-14) NON sono JSON valido — contengono il token
189+
// letterale `undefined` scritto dal bug del canonicalizzatore corretto il 21/08/2026.
190+
// Le foglie restano quei byte esatti (un log append-only non si riscrive) e l'albero e'
191+
// corretto, ma un consumatore che fa JSON.parse va in errore: va detto QUI, non solo nei doc.
192+
const malformed = [];
193+
for (let i = 0; i < out.length; i++) { try { JSON.parse(out[i]); } catch { malformed.push(start + i); } }
194+
const res = { start, end, size: N, encoding: "raw record bytes; leaf = SHA256(0x00 || record). Normally gblin-canonical-json/1, but see malformed_indices", leaves: out };
195+
if (malformed.length) {
196+
res.malformed_indices = malformed;
197+
res.malformed_note = "These records are NOT parseable JSON: they contain the literal token `undefined`, written by a canonicalizer bug fixed on 2026-08-21. They are hashed and served as the exact bytes they were appended with, because an append-only log is not rewritten. Hash them as opaque bytes; do not JSON.parse them.";
198+
}
199+
return res;
189200
}
190201

191202
// ---------- checkpoint (signed note C2SP) ----------
@@ -226,6 +237,20 @@ export async function witnessState(env) {
226237
return out;
227238
}
228239

240+
// Quale size tiene questo witness per il NOSTRO log? Il c2sp espone la nota cofirmata
241+
// sotto sha256(origin) in esadecimale minuscolo: la leggiamo e ne prendiamo la size.
242+
async function witnessHeldSize(w, fetchImpl = fetch) {
243+
try {
244+
const h = new Uint8Array(await crypto.subtle.digest("SHA-256", te.encode(RLOG_ORIGIN)));
245+
const hex = [...h].map((b) => b.toString(16).padStart(2, "0")).join("");
246+
const base = new URL(w.url); base.pathname = `/${hex}/checkpoint`;
247+
const res = await fetchImpl(base.toString());
248+
if (res.status !== 200) return null;
249+
const n = Number((await res.text()).split("\n")[1]);
250+
return Number.isInteger(n) ? n : null;
251+
} catch { return null; }
252+
}
253+
229254
export async function pushToWitnesses(env, { force = false, fetchImpl = fetch } = {}) {
230255
if (!env.RLOG_KEY) return { skipped: "no key" };
231256
const N = Number((await env.COHERENCE.get("rlog:size")) || 0);
@@ -253,8 +278,13 @@ export async function pushToWitnesses(env, { force = false, fetchImpl = fetch }
253278
};
254279
try {
255280
let r = await attempt(prev?.size || 0);
256-
if (r.status === 409) { // il witness ne tiene un'altra: riparti dalla sua
257-
const held = Number(r.text.trim().split(/\s+/).pop());
281+
// 409 = il witness ne tiene una piu' grande e la dichiara nel corpo.
282+
// 422 = la prova non regge per la size che gli abbiamo dichiarato: succede quando
283+
// il witness ci ha gia' registrati fuori banda (trust-on-first-use) e noi non lo
284+
// sappiamo. In quel caso chiediamo a LUI quale size tiene e rifacciamo la prova.
285+
if (r.status === 409 || r.status === 422) {
286+
let held = Number(r.text.trim().split(/\s+/).pop());
287+
if (!Number.isInteger(held) || held < 0 || held > N) held = await witnessHeldSize(w, fetchImpl);
258288
if (Number.isInteger(held) && held >= 0 && held <= N) r = await attempt(held);
259289
}
260290
if (r.status === 200 && r.text.startsWith(SEP)) {

worker/src/witness.mjs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export async function witnessHistory(env, id) {
184184
// State in KV (binding COHERENCE, same namespace as the coherence automaton):
185185
// witness:<id>:last {size, root(b64), ts, cosignedNote, logSigOk:true}
186186
// witness:<id>:err {at, error} (cleared on success)
187-
// witness:<id>:count number of cosignatures ever produced
187+
// witness:<id>:count contatore STORICO (non piu' scritto: ora vive dentro :last come .count)
188188
export async function witnessTick(env, fetchImpl = fetch) {
189189
if (!env.COHERENCE || !env.WITNESS_KEY) return { skipped: "not armed" };
190190
let keyPair;
@@ -219,10 +219,11 @@ export async function witnessTick(env, fetchImpl = fetch) {
219219

220220
const { line, ts } = await cosign(note, keyPair);
221221
const cosignedNote = text.endsWith("\n") ? text + line + "\n" : text + "\n" + line + "\n";
222-
await env.COHERENCE.put(kLast, JSON.stringify({ size: note.size, root: b64(note.root), ts, cosignedNote, firstSeen: prev?.firstSeen || ts }));
222+
// Il contatore sta DENTRO kLast: su Cloudflare free il budget e' 1000 scritture/giorno
223+
// e una chiave separata per il conteggio ne bruciava 144 al giorno per niente.
224+
const count = (Number.isInteger(prev?.count) ? prev.count : Number((await env.COHERENCE.get(kCount)) || 0)) + 1;
225+
await env.COHERENCE.put(kLast, JSON.stringify({ size: note.size, root: b64(note.root), ts, cosignedNote, count, firstSeen: prev?.firstSeen || ts }));
223226
await appendHistory(env, log.id, { size: note.size, root: b64(note.root), ts, via: "fetch", note: cosignedNote });
224-
const count = Number((await env.COHERENCE.get(kCount)) || 0) + 1;
225-
await env.COHERENCE.put(kCount, String(count));
226227
await env.COHERENCE.delete(kErr);
227228
out[log.id] = { cosigned: true, size: note.size, ts, count };
228229
} catch (e) {
@@ -335,10 +336,10 @@ export async function witnessAddCheckpoint(env, bodyText) {
335336
if (!prev || note.size > prev.size) {
336337
const text = bodyText.slice(sep + 2);
337338
const cosignedNote = text.endsWith("\n") ? text + line + "\n" : text + "\n" + line + "\n";
338-
await env.COHERENCE.put(kLast, JSON.stringify({ size: note.size, root: b64(note.root), ts, cosignedNote, firstSeen: prev?.firstSeen || ts, via: "push" }));
339+
const pushCount = (Number.isInteger(prev?.count) ? prev.count : Number((await env.COHERENCE.get(kCount)) || 0)) + 1;
340+
await env.COHERENCE.put(kLast, JSON.stringify({ size: note.size, root: b64(note.root), ts, cosignedNote, count: pushCount, firstSeen: prev?.firstSeen || ts, via: "push" }));
339341
await appendHistory(env, log.id, { size: note.size, root: b64(note.root), ts, via: "push", note: cosignedNote });
340-
const count = Number((await env.COHERENCE.get(kCount)) || 0) + 1;
341-
await env.COHERENCE.put(kCount, String(count));
342+
const count = pushCount;
342343
await env.COHERENCE.delete(kErr);
343344
}
344345
return { status: 200, body: line + "\n" };
@@ -354,7 +355,7 @@ export async function witnessIndex(env) {
354355
if (env.COHERENCE) {
355356
try { last = JSON.parse((await env.COHERENCE.get(`witness:${log.id}:last`)) || "null"); } catch { /* none */ }
356357
try { err = JSON.parse((await env.COHERENCE.get(`witness:${log.id}:err`)) || "null"); } catch { /* none */ }
357-
count = Number((await env.COHERENCE.get(`witness:${log.id}:count`)) || 0);
358+
count = Number.isInteger(last?.count) ? last.count : Number((await env.COHERENCE.get(`witness:${log.id}:count`)) || 0);
358359
}
359360
logs.push({
360361
id: log.id, origin: log.origin, log: log.base, pinnedLogKey: log.vkey, note: log.note,
@@ -385,7 +386,7 @@ export async function witnessIndex(env) {
385386
limits: "best effort, work-in-progress service run by one operator; not a production guarantee",
386387
ourOwnLog: {
387388
origin: "gblin.digital/receipts-log",
388-
note: "We also operate an application transparency log for AI actions; it is operator-signed only and we are looking for witnesses.",
389+
note: "We also operate an application transparency log for AI actions (gblin.digital/receipts-log). Since 2026-08-22 it is cosigned by markovianprotocol.com/witness — see /log/witnesses for who cosigns and at which size. More witnesses welcome: we speak the log side of c2sp.org/tlog-witness and push on every size change.",
389390
designNote: "https://github.com/gblinproject/gblin-treasury-risk-regime/blob/main/docs/ai-action-transparency-log.md",
390391
forWitnesses: "GET /log/checkpoint · /log/consistency?old=&new= · /log/leaves?start=&end= · /log/proof/<i>",
391392
},

0 commit comments

Comments
 (0)