Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions schemas/listing-detail.json
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,8 @@
"token",
"expiry",
"created_at",
"authorization_verification",
"authorization_verified_at",
"receipt_id",
"tx_hash",
"receipt_source",
Expand Down Expand Up @@ -830,6 +832,14 @@
"created_at": {
"type": "integer"
},
"authorization_verification": {
"type": "string",
"description": "The immutable authorization verdict recorded when this binding was filed; carried here as well as on its canonical record so omission cannot read as an unknown result."
},
"authorization_verified_at": {
"type": "integer",
"description": "When the immutable authorization verdict was recorded, ms epoch."
},
"receipt_id": {
"type": [
"integer",
Expand Down
7 changes: 6 additions & 1 deletion src/society.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4605,7 +4605,8 @@ export async function getListing(env: Env, id: number, deps: { escrowReader?: Es
// (migration 0063). At most one per binding: a citizen is paid once per
// listing, and the settler refuses a second. It is the other settlement
// fact beside a receipt, and every 'paid' derivation below reads both.
`SELECT pb.id, pb.docket_id AS row, c.handle, pb.payout_address, pb.amount_atomic, pb.chain_id, pb.token, pb.expiry, pb.created_at, pr.id AS receipt_id, pr.tx_hash, pr.source_address AS receipt_source,
`SELECT pb.id, pb.docket_id AS row, c.handle, pb.payout_address, pb.amount_atomic, pb.chain_id, pb.token, pb.expiry, pb.created_at,
pb.authorization_verification, pb.authorization_verified_at, pr.id AS receipt_id, pr.tx_hash, pr.source_address AS receipt_source,
os.id AS settled_observed_transfer_id, os.settled_award_id, os.funder_address AS observed_source, os.tx_hash AS observed_tx_hash
FROM payout_bindings pb JOIN citizens c ON c.id = pb.citizen_id LEFT JOIN payout_receipts pr ON pr.binding_id = pb.id
LEFT JOIN observed_transfers os ON os.binding_id = pb.id AND os.settled_award_id IS NOT NULL
Expand Down Expand Up @@ -5082,6 +5083,10 @@ export async function getListing(env: Env, id: number, deps: { escrowReader?: Es
bindings_count: results.length,
bindings_total: bindingsTotal,
bindings_has_more: results.length < bindingsTotal,
// Keep the binding's authorization verdict on the listing projection too.
// A null/absent summary field is not negative evidence, but it previously
// made a binding that the canonical record had verified read as unknown.
//
// asset_agreement is carried on the LIST rows and not only on the single
// binding record. Bindings 163 and 164 are reached far more often through
// this array than by anyone fetching /api/payout-bindings/163 directly, and
Expand Down
2 changes: 2 additions & 0 deletions test/listing-detail-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ function bindingRow(over: Record<string, unknown> = {}) {
token: USDC,
expiry: Math.floor(now / 1000) + 3600,
created_at: now - 400,
authorization_verification: "valid-at-binding-event",
authorization_verified_at: now - 400,
receipt_id: null,
tx_hash: null,
receipt_source: null,
Expand Down
4 changes: 4 additions & 0 deletions test/listings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ test("a listing is posted with its identity event, and a payee binds against it
const detail = await getListing(env, 1);
assert.equal(detail.bindings.length, 1);
assert.equal(detail.bindings[0]!.handle, "li-nuwa");
// The listing projection must preserve the authorization verdict from the
// canonical binding record; omitting it makes a real verdict look unknown.
assert.equal(detail.bindings[0]!.authorization_verification, "valid-at-binding-event");
assert.equal(typeof detail.bindings[0]!.authorization_verified_at, "number");
assert.equal(detail.expired, false);

const page = await listListings(env);
Expand Down
Loading