mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-10-31 10:31:00 +00:00
feat: adapter contract, commerce proof-of-fit, and reusability docs
Proves the engine did not stay video-specific during extraction. - application adapter contract in core (createApplicationAdapter, evaluateObject, collectTargets): an object names the targets that govern it and the strictest verdict reaching any of them wins, so denying a seller takes their whole catalogue down with no cascade logic in the application - examples/commerce: a marketplace on the public API covering all ten proof-of-fit behaviors the plan requires, including that no core change was needed. One product, one snapshot, four surfaces, four different verdicts. - README, integration guide, per-example README, CHANGELOG, four ADRs, and GPL-3.0-or-later LICENSE (the license was listed in Phase 1 but missing) - checkpoints rewritten to reflect actual state and to say plainly which migration-plan phases belong to the consumer repos rather than here Fixes a real gap found by the proof-of-fit: composeDecisions kept only the first decision's evidence, so an object governed by several targets lost the evidence that drove its verdict. Evidence now merges — counts summed, contributor lists unioned, flags or-ed. 556 tests; lint, typecheck, and per-package type build clean.
This commit is contained in:
1 parent
4d460c12fe
commit
2beb7a7bb5
24 files changed
+2732
-48
No files matched your search
@@ -0,0 +1,508 @@
|
||||
// Commerce proof-of-fit.
|
||||
//
|
||||
// The plan requires a commerce consumer to demonstrate ten behaviors before the
|
||||
// package reaches 1.0.0, the tenth being that none of it needs a change to the
|
||||
// generic core. Each numbered case below is one of those requirements.
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createCommands, createGovernanceRuntime, createMemoryTransport } from "@nostr-governance/runtime";
|
||||
|
||||
import { COMMERCE_POLICY, COMMERCE_ROLE_CAPABILITIES } from "../src/policy.js";
|
||||
import { productAdapter, reviewAdapter, sellerAdapter } from "../src/adapters.js";
|
||||
import {
|
||||
buildMarketplaceGrid,
|
||||
checkoutVerdict,
|
||||
explainToSeller,
|
||||
governProductFeed,
|
||||
} from "../src/marketplace.js";
|
||||
|
||||
const ROOT = "a1".repeat(32);
|
||||
const LISTING_MOD = "b2".repeat(32);
|
||||
const SELLER_MOD = "c3".repeat(32);
|
||||
const CURATOR = "e5".repeat(32);
|
||||
const SELLER = "d4".repeat(32);
|
||||
const OTHER_SELLER = "f6".repeat(32);
|
||||
const BUYER = "ab".repeat(32);
|
||||
const NOW = 1_750_000_000;
|
||||
|
||||
const trusted = (index) => index.toString(16).padStart(2, "0").repeat(32);
|
||||
const TRUSTED = Array.from({ length: 8 }, (_, index) => trusted(index + 1));
|
||||
|
||||
/** @type {import('../src/adapters.js').Product} */
|
||||
const PRODUCT = {
|
||||
id: "1b".repeat(32),
|
||||
sellerPubkey: SELLER,
|
||||
identifier: "sku-001",
|
||||
title: "Widget",
|
||||
priceSats: 1000,
|
||||
};
|
||||
|
||||
/** @type {import('../src/adapters.js').Product} */
|
||||
const OTHER_PRODUCT = {
|
||||
id: "2c".repeat(32),
|
||||
sellerPubkey: SELLER,
|
||||
identifier: "sku-002",
|
||||
title: "Gadget",
|
||||
priceSats: 2000,
|
||||
};
|
||||
|
||||
/** @type {import('../src/adapters.js').Product} */
|
||||
const RIVAL_PRODUCT = {
|
||||
id: "3d".repeat(32),
|
||||
sellerPubkey: OTHER_SELLER,
|
||||
identifier: "sku-100",
|
||||
title: "Rival widget",
|
||||
priceSats: 900,
|
||||
};
|
||||
|
||||
function setup({ allowlisted = [SELLER, OTHER_SELLER] } = {}) {
|
||||
const runtime = createGovernanceRuntime({
|
||||
applicationId: "commerce-example",
|
||||
namespace: "commerce",
|
||||
transport: createMemoryTransport(),
|
||||
policy: COMMERCE_POLICY,
|
||||
now: () => NOW,
|
||||
});
|
||||
|
||||
runtime.admin.setRoles({
|
||||
root: ROOT,
|
||||
roles: COMMERCE_ROLE_CAPABILITIES,
|
||||
actors: {
|
||||
[ROOT]: ["super_admin"],
|
||||
[LISTING_MOD]: ["listing_moderator"],
|
||||
[SELLER_MOD]: ["seller_moderator"],
|
||||
[CURATOR]: ["community_curator"],
|
||||
},
|
||||
});
|
||||
|
||||
// Roles here are application-defined, so super_admin needs its default bundle
|
||||
// restored alongside them.
|
||||
runtime.admin.setRoles({
|
||||
root: ROOT,
|
||||
roles: {
|
||||
...COMMERCE_ROLE_CAPABILITIES,
|
||||
super_admin: [
|
||||
"manage-roles",
|
||||
"manage-policy",
|
||||
"manage-community-sources",
|
||||
"contribute-user-allow",
|
||||
"contribute-user-deny",
|
||||
"contribute-event-deny",
|
||||
"contribute-address-deny",
|
||||
"contribute-trust-seed",
|
||||
"review-evidence",
|
||||
],
|
||||
},
|
||||
actors: {
|
||||
[ROOT]: ["super_admin"],
|
||||
[LISTING_MOD]: ["listing_moderator"],
|
||||
[SELLER_MOD]: ["seller_moderator"],
|
||||
[CURATOR]: ["community_curator"],
|
||||
},
|
||||
});
|
||||
|
||||
if (allowlisted.length) {
|
||||
runtime.admin.upsertContribution({
|
||||
actor: ROOT,
|
||||
kind: "user-allow",
|
||||
targets: allowlisted.map((pubkey) => ({ type: "user", pubkey })),
|
||||
createdAt: NOW,
|
||||
});
|
||||
}
|
||||
|
||||
// Set the viewer first: switching viewers clears viewer-scoped trust state,
|
||||
// so seeding contacts beforehand would be wiped.
|
||||
runtime.setViewer(BUYER);
|
||||
runtime.trust.setContacts(TRUSTED);
|
||||
|
||||
return { runtime, commands: createCommands(runtime) };
|
||||
}
|
||||
|
||||
/** Report a target from `count` distinct trusted accounts. */
|
||||
function reportFrom(runtime, targetKey, category, count) {
|
||||
for (const reporter of TRUSTED.slice(0, count)) {
|
||||
runtime.reports.ingest({ reporter, category, createdAt: NOW - 100 }, targetKey);
|
||||
}
|
||||
}
|
||||
|
||||
const productAddressKey = (product) =>
|
||||
`address:30078:${product.sellerPubkey}:${product.identifier}`;
|
||||
|
||||
describe("1. seller allowlist mode", () => {
|
||||
it("hides a seller who is not allowlisted", () => {
|
||||
const { runtime } = setup({ allowlisted: [] });
|
||||
const [governed] = governProductFeed([PRODUCT], runtime, "public-marketplace");
|
||||
expect(governed.moderation.visible).toBe(false);
|
||||
expect(governed.moderation.reasons).toContain("allowlist-miss");
|
||||
});
|
||||
|
||||
it("shows an allowlisted seller", () => {
|
||||
const { runtime } = setup();
|
||||
const [governed] = governProductFeed([PRODUCT], runtime, "public-marketplace");
|
||||
expect(governed.moderation.visible).toBe(true);
|
||||
});
|
||||
|
||||
it("does not enforce the allowlist on checkout", () => {
|
||||
const { runtime } = setup({ allowlisted: [] });
|
||||
expect(checkoutVerdict(PRODUCT, runtime).allowed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("2. administrative seller denial", () => {
|
||||
it("removes every product from a denied seller", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: SELLER_MOD,
|
||||
kind: "user-deny",
|
||||
targets: [{ type: "user", pubkey: SELLER }],
|
||||
createdAt: NOW,
|
||||
});
|
||||
|
||||
const grid = buildMarketplaceGrid([PRODUCT, OTHER_PRODUCT, RIVAL_PRODUCT], runtime);
|
||||
expect(grid.map((product) => product.identifier)).toEqual(["sku-100"]);
|
||||
});
|
||||
|
||||
it("blocks checkout for the denied seller", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: SELLER_MOD,
|
||||
kind: "user-deny",
|
||||
targets: [{ type: "user", pubkey: SELLER }],
|
||||
createdAt: NOW,
|
||||
});
|
||||
expect(checkoutVerdict(PRODUCT, runtime).allowed).toBe(false);
|
||||
});
|
||||
|
||||
it("leaves the seller their own dashboard view", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: SELLER_MOD,
|
||||
kind: "user-deny",
|
||||
targets: [{ type: "user", pubkey: SELLER }],
|
||||
createdAt: NOW,
|
||||
});
|
||||
|
||||
const explanation = explainToSeller(PRODUCT, runtime);
|
||||
expect(explanation.visible).toBe(true);
|
||||
expect(explanation.sellable).toBe(false);
|
||||
expect(explanation.reasons).toContain("admin-user-deny");
|
||||
});
|
||||
});
|
||||
|
||||
describe("3. product-address denial", () => {
|
||||
it("denies one listing without touching the seller's others", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: LISTING_MOD,
|
||||
kind: "address-deny",
|
||||
targets: [
|
||||
{ type: "address", kind: "30078", pubkey: SELLER, identifier: PRODUCT.identifier },
|
||||
],
|
||||
createdAt: NOW,
|
||||
});
|
||||
|
||||
const grid = buildMarketplaceGrid([PRODUCT, OTHER_PRODUCT], runtime);
|
||||
expect(grid.map((product) => product.identifier)).toEqual(["sku-002"]);
|
||||
});
|
||||
|
||||
it("persists across a revision that changes the event id", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: LISTING_MOD,
|
||||
kind: "address-deny",
|
||||
targets: [
|
||||
{ type: "address", kind: "30078", pubkey: SELLER, identifier: PRODUCT.identifier },
|
||||
],
|
||||
createdAt: NOW,
|
||||
});
|
||||
|
||||
const republished = { ...PRODUCT, id: "99".repeat(32), title: "Widget v2" };
|
||||
const [governed] = governProductFeed([republished], runtime, "public-marketplace");
|
||||
expect(governed.moderation.visible).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("4. exact-event denial", () => {
|
||||
it("affects only the named revision", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: LISTING_MOD,
|
||||
kind: "event-deny",
|
||||
targets: [{ type: "event", id: PRODUCT.id }],
|
||||
createdAt: NOW,
|
||||
});
|
||||
|
||||
const [denied] = governProductFeed([PRODUCT], runtime, "public-marketplace");
|
||||
expect(denied.moderation.visible).toBe(false);
|
||||
|
||||
const republished = { ...PRODUCT, id: "99".repeat(32) };
|
||||
const [fresh] = governProductFeed([republished], runtime, "public-marketplace");
|
||||
expect(fresh.moderation.visible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("5. trusted scam-report downranking", () => {
|
||||
it("downranks on a single trusted scam report without hiding", () => {
|
||||
const { runtime } = setup();
|
||||
reportFrom(runtime, productAddressKey(PRODUCT), "scam", 1);
|
||||
|
||||
const [governed] = governProductFeed([PRODUCT], runtime, "public-marketplace");
|
||||
expect(governed.moderation.downranked).toBe(true);
|
||||
expect(governed.moderation.visible).toBe(true);
|
||||
});
|
||||
|
||||
it("orders downranked listings after clean ones", () => {
|
||||
const { runtime } = setup();
|
||||
reportFrom(runtime, productAddressKey(PRODUCT), "scam", 1);
|
||||
|
||||
const grid = buildMarketplaceGrid([PRODUCT, RIVAL_PRODUCT], runtime);
|
||||
expect(grid.map((product) => product.identifier)).toEqual(["sku-100", "sku-001"]);
|
||||
});
|
||||
|
||||
it("escalates to review then denial as reports accumulate", () => {
|
||||
const { runtime } = setup();
|
||||
reportFrom(runtime, productAddressKey(PRODUCT), "scam", 2);
|
||||
expect(checkoutVerdict(PRODUCT, runtime).needsReview).toBe(true);
|
||||
|
||||
reportFrom(runtime, productAddressKey(PRODUCT), "scam", 3);
|
||||
expect(checkoutVerdict(PRODUCT, runtime).allowed).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("6. malware-based checkout denial", () => {
|
||||
it("blocks checkout on a single trusted malware report", () => {
|
||||
const { runtime } = setup();
|
||||
reportFrom(runtime, productAddressKey(PRODUCT), "malware", 1);
|
||||
expect(checkoutVerdict(PRODUCT, runtime).allowed).toBe(false);
|
||||
});
|
||||
|
||||
it("treats malware far more harshly than misleading copy", () => {
|
||||
const { runtime } = setup();
|
||||
reportFrom(runtime, productAddressKey(PRODUCT), "misleading", 3);
|
||||
expect(checkoutVerdict(PRODUCT, runtime).allowed).toBe(true);
|
||||
|
||||
reportFrom(runtime, productAddressKey(RIVAL_PRODUCT), "malware", 1);
|
||||
expect(checkoutVerdict(RIVAL_PRODUCT, runtime).allowed).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("7. moderator capability checks", () => {
|
||||
const signerFor = (pubkey) => ({
|
||||
async getPublicKey() {
|
||||
return pubkey;
|
||||
},
|
||||
async signEvent(template) {
|
||||
return { ...template, id: "ff".repeat(32), pubkey, sig: "00".repeat(64) };
|
||||
},
|
||||
});
|
||||
|
||||
function commandsAs(pubkey) {
|
||||
const runtime = createGovernanceRuntime({
|
||||
applicationId: "commerce-example",
|
||||
namespace: "commerce",
|
||||
transport: createMemoryTransport(),
|
||||
signer: signerFor(pubkey),
|
||||
policy: COMMERCE_POLICY,
|
||||
now: () => NOW,
|
||||
});
|
||||
runtime.admin.setRoles({
|
||||
root: ROOT,
|
||||
roles: COMMERCE_ROLE_CAPABILITIES,
|
||||
actors: {
|
||||
[LISTING_MOD]: ["listing_moderator"],
|
||||
[SELLER_MOD]: ["seller_moderator"],
|
||||
[CURATOR]: ["community_curator"],
|
||||
},
|
||||
});
|
||||
return createCommands(runtime);
|
||||
}
|
||||
|
||||
it("lets a listing moderator deny an address", async () => {
|
||||
expect((await commandsAs(LISTING_MOD).denyAddress("30078", SELLER, "sku-001")).ok).toBe(true);
|
||||
});
|
||||
|
||||
it("stops a listing moderator denying a seller", async () => {
|
||||
expect((await commandsAs(LISTING_MOD).denyUser(SELLER)).code).toBe("not-authorized");
|
||||
});
|
||||
|
||||
it("lets a seller moderator deny a seller", async () => {
|
||||
expect((await commandsAs(SELLER_MOD).denyUser(SELLER)).ok).toBe(true);
|
||||
});
|
||||
|
||||
it("stops a seller moderator denying a listing address", async () => {
|
||||
expect((await commandsAs(SELLER_MOD).denyAddress("30078", SELLER, "sku-001")).code).toBe(
|
||||
"not-authorized",
|
||||
);
|
||||
});
|
||||
|
||||
it("stops a curator denying an exact event", async () => {
|
||||
expect((await commandsAs(CURATOR).denyEvent("1b".repeat(32))).code).toBe("not-authorized");
|
||||
});
|
||||
|
||||
it("stops any moderator changing roles", async () => {
|
||||
expect((await commandsAs(SELLER_MOD).setRoles({ actors: {} })).code).toBe("not-authorized");
|
||||
});
|
||||
});
|
||||
|
||||
describe("8. community curator ingestion", () => {
|
||||
it("honors a curator's seller denial", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: CURATOR,
|
||||
kind: "user-deny",
|
||||
source: "curated-scam-list",
|
||||
targets: [{ type: "user", pubkey: SELLER }],
|
||||
createdAt: NOW,
|
||||
});
|
||||
|
||||
const [governed] = governProductFeed([PRODUCT], runtime, "public-marketplace");
|
||||
expect(governed.moderation.visible).toBe(false);
|
||||
expect(governed.moderation.reasons).toContain("community-user-deny");
|
||||
});
|
||||
|
||||
it("ignores a curator contribution beyond their capability", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: CURATOR,
|
||||
kind: "address-deny",
|
||||
source: "curated-scam-list",
|
||||
targets: [
|
||||
{ type: "address", kind: "30078", pubkey: SELLER, identifier: PRODUCT.identifier },
|
||||
],
|
||||
createdAt: NOW,
|
||||
});
|
||||
|
||||
const [governed] = governProductFeed([PRODUCT], runtime, "public-marketplace");
|
||||
expect(governed.moderation.visible).toBe(true);
|
||||
});
|
||||
|
||||
it("cannot deny the root through a curated list", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: CURATOR,
|
||||
kind: "user-deny",
|
||||
source: "hostile-list",
|
||||
targets: [{ type: "user", pubkey: ROOT }],
|
||||
createdAt: NOW,
|
||||
});
|
||||
expect(runtime.admin.state.userDeny.has(`user:${ROOT}`)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("9. seller-dashboard explanation", () => {
|
||||
it("exposes evidence counts to the seller", () => {
|
||||
const { runtime } = setup();
|
||||
reportFrom(runtime, productAddressKey(PRODUCT), "scam", 3);
|
||||
|
||||
const explanation = explainToSeller(PRODUCT, runtime);
|
||||
expect(explanation.evidence.trustedReportTotal).toBe(3);
|
||||
expect(explanation.evidence.trustedReportsByCategory.scam).toBe(3);
|
||||
expect(explanation.evidence.trustedReporterPubkeys).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("withholds reporter identities from public surfaces", () => {
|
||||
const { runtime } = setup();
|
||||
reportFrom(runtime, productAddressKey(PRODUCT), "scam", 3);
|
||||
|
||||
const decision = runtime.evaluate(
|
||||
{ type: "address", kind: "30078", pubkey: SELLER, identifier: PRODUCT.identifier },
|
||||
{ profile: "public-marketplace" },
|
||||
);
|
||||
expect(decision.evidence?.trustedReportTotal).toBe(3);
|
||||
expect(decision.evidence?.trustedReporterPubkeys).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("10. no core changes required", () => {
|
||||
it("expresses four different verdicts for one product from one snapshot", () => {
|
||||
const { runtime } = setup();
|
||||
reportFrom(runtime, productAddressKey(PRODUCT), "scam", 3);
|
||||
|
||||
const target = {
|
||||
type: "address",
|
||||
kind: "30078",
|
||||
pubkey: SELLER,
|
||||
identifier: PRODUCT.identifier,
|
||||
};
|
||||
|
||||
expect(runtime.evaluate(target, { profile: "public-marketplace" }).visibility.effect).toBe(
|
||||
"restrict",
|
||||
);
|
||||
expect(runtime.evaluate(target, { profile: "seller-dashboard" }).visibility.effect).toBe(
|
||||
"restrict",
|
||||
);
|
||||
expect(runtime.evaluate(target, { profile: "checkout" }).transaction?.effect).toBe("deny");
|
||||
expect(runtime.evaluate(target, { profile: "product-detail" }).visibility.overridable).toBe(true);
|
||||
});
|
||||
|
||||
it("governs reviews without letting a bad product silence them", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: SELLER_MOD,
|
||||
kind: "user-deny",
|
||||
targets: [{ type: "user", pubkey: SELLER }],
|
||||
createdAt: NOW,
|
||||
});
|
||||
|
||||
const review = {
|
||||
id: "7f".repeat(32),
|
||||
reviewerPubkey: BUYER,
|
||||
productIdentifier: PRODUCT.identifier,
|
||||
rating: 1,
|
||||
};
|
||||
|
||||
const decision = runtime.evaluate(reviewAdapter.toTargets(review)[0], {
|
||||
profile: "product-detail",
|
||||
});
|
||||
expect(decision.visibility.effect).not.toBe("hide");
|
||||
});
|
||||
|
||||
it("uses only the published core API", async () => {
|
||||
// Every import in this example resolves through a package entry point; if
|
||||
// one reached into a package's internals, the reference guard and this
|
||||
// assertion are what would catch it.
|
||||
const core = await import("@nostr-governance/core");
|
||||
for (const name of [
|
||||
"createApplicationAdapter",
|
||||
"evaluateObject",
|
||||
"collectTargets",
|
||||
"createPolicyDefinition",
|
||||
"createRoleDefinition",
|
||||
]) {
|
||||
expect(typeof core[name]).toBe("function");
|
||||
}
|
||||
});
|
||||
|
||||
it("governs a seller through the same engine as a product", () => {
|
||||
const { runtime } = setup();
|
||||
runtime.admin.upsertContribution({
|
||||
actor: SELLER_MOD,
|
||||
kind: "user-deny",
|
||||
targets: [{ type: "user", pubkey: SELLER }],
|
||||
createdAt: NOW,
|
||||
});
|
||||
|
||||
const decision = runtime.evaluate(sellerAdapter.toTargets({ pubkey: SELLER })[0], {
|
||||
profile: "public-marketplace",
|
||||
});
|
||||
expect(decision.visibility.effect).toBe("hide");
|
||||
});
|
||||
});
|
||||
|
||||
describe("adapter composition", () => {
|
||||
it("reports decisions against the product's own identity", () => {
|
||||
const { runtime } = setup();
|
||||
const decision = runtime.evaluate(productAdapter.toTargets(PRODUCT)[2], {
|
||||
profile: "public-marketplace",
|
||||
});
|
||||
expect(decision.key).toBe(productAddressKey(PRODUCT));
|
||||
});
|
||||
|
||||
it("collects deduplicated targets across a feed", () => {
|
||||
const { runtime } = setup();
|
||||
governProductFeed([PRODUCT, OTHER_PRODUCT], runtime, "public-marketplace");
|
||||
// Two products from one seller: 2 events + 2 addresses + 1 shared seller.
|
||||
expect(runtime.activeTargets.size).toBe(5);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user