@uuaid/agent-commons 0.0.0-stage → 0.2.0-alpha.2
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/CONFORMANCE.md +26 -0
- package/LICENSE +201 -0
- package/NOTICE +25 -0
- package/PROVENANCE.json +29 -0
- package/README.md +316 -2
- package/bin/agent-commons.mjs +71 -0
- package/examples/Dockerfile +12 -0
- package/examples/agent.mjs +17 -0
- package/examples/global-policy.json +9 -0
- package/examples/run-cohort.mjs +68 -0
- package/package.json +31 -4
- package/profiles/agentnet.chat.json +11 -0
- package/profiles/local.json +11 -0
- package/profiles/zilligon.com.json +11 -0
- package/profiles/zilligons.com.json +11 -0
- package/src/carrier.mjs +79 -0
- package/src/channels.d.mts +75 -0
- package/src/channels.mjs +158 -0
- package/src/cohort.mjs +61 -0
- package/src/collaboration.mjs +875 -0
- package/src/config.mjs +39 -0
- package/src/continuity.d.mts +24 -0
- package/src/continuity.mjs +594 -0
- package/src/governance-scope.d.mts +46 -0
- package/src/governance-scope.mjs +139 -0
- package/src/governance.mjs +353 -0
- package/src/index.d.ts +109 -0
- package/src/index.mjs +15 -0
- package/src/loop.mjs +71 -0
- package/src/offline.d.mts +7 -0
- package/src/offline.mjs +29 -0
- package/src/pillar.mjs +59 -0
- package/src/profiles.d.mts +12 -0
- package/src/profiles.mjs +91 -0
- package/src/runtime.mjs +346 -0
- package/src/store.mjs +73 -0
- package/src/transport.d.mts +76 -0
- package/src/transport.mjs +267 -0
- package/src/trust.mjs +65 -0
- package/src/vendor/pillar/blob/blob.mjs +510 -0
- package/src/vendor/pillar/crypto/e2e.mjs +97 -0
- package/src/vendor/pillar/identity/jcs.mjs +35 -0
- package/src/vendor/pillar/identity/keychain.mjs +192 -0
- package/src/vendor/pillar/identity/tier.mjs +130 -0
- package/src/vendor/pillar/net/carrier-client.mjs +197 -0
- package/src/vendor/pillar/net/delegation.mjs +209 -0
- package/src/vendor/pillar/net/envelope.mjs +258 -0
- package/src/vendor/pillar/net/refusals.mjs +127 -0
- package/src/vendor/pillar/net/seed-tier.mjs +185 -0
- package/test/channels.test.mjs +268 -0
- package/test/cohort.test.mjs +36 -0
- package/test/collaboration.test.mjs +459 -0
- package/test/continuity.test.mjs +229 -0
- package/test/custody.negative.test.mjs +165 -0
- package/test/derivation-parity.test.mjs +19 -0
- package/test/fixtures/derivation-vectors.json +26 -0
- package/test/fixtures/ed25519-small-order.vectors.json +15 -0
- package/test/governance-expected-red.expected-red.mjs +241 -0
- package/test/governance-scope.test.mjs +142 -0
- package/test/governance-t1-t10.test.mjs +477 -0
- package/test/governance.test.mjs +514 -0
- package/test/hold-admission.test.mjs +1185 -0
- package/test/keychain-strict-ed25519.test.mjs +240 -0
- package/test/offline.test.mjs +152 -0
- package/test/runtime-channels.test.mjs +1140 -0
- package/test/runtime.test.mjs +201 -0
- package/test/security-runtime.test.mjs +104 -0
- package/test/strict-ed25519-consumers.test.mjs +234 -0
- package/test/transport.test.mjs +353 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// Designed against IAASO-3001..3801 (content_hash 22cb66b62affc70a87c21a5f387bc6f6d764a6d1275ce349e585eb8bf485257f); not assessed; not ratified.
|
|
2
|
+
//
|
|
3
|
+
// L8 phase-1 executable tests for packages/agent-commons/src/governance.mjs.
|
|
4
|
+
// Scope per the project design (section L8): T1, T3, T4,
|
|
5
|
+
// T5, T7, T8, T9. T2, T6, T10 are phase 2 (they reference L3 pieces and are
|
|
6
|
+
// reserved for the next lane round).
|
|
7
|
+
//
|
|
8
|
+
// Each test is two-pole:
|
|
9
|
+
// CONTROL — assertion against the existing module.
|
|
10
|
+
// MUTANT — assertion against a simulated guard, proving the test is
|
|
11
|
+
// sensitive to the fix that the spec demands.
|
|
12
|
+
// Two-pole recipes (CONTROL rc, MUTANT rc, scratch invocation) are recorded
|
|
13
|
+
// in the project design per the L8 brief.
|
|
14
|
+
//
|
|
15
|
+
// EXPECTED RED tests (T4, T9) are runtime assertions that FAIL today on
|
|
16
|
+
// unfixed code; the failure is the documented B1/M1 state. They MUST NOT be
|
|
17
|
+
// weakened or marked `test.todo`. The MUTANT poles simulate the post-fix
|
|
18
|
+
// behavior at the test seam, proving the assertions are sensitive to the
|
|
19
|
+
// spec-mandated guard.
|
|
20
|
+
|
|
21
|
+
import test from "node:test";
|
|
22
|
+
import assert from "node:assert/strict";
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
import { dirname, resolve as resolvePath } from "node:path";
|
|
26
|
+
|
|
27
|
+
import { Keychain, jcs } from "../src/pillar.mjs";
|
|
28
|
+
import { signDocument, digest } from "../src/profiles.mjs";
|
|
29
|
+
import { PeerGovernance, GovernanceError, CLAIM_TYPES } from "../src/governance.mjs";
|
|
30
|
+
|
|
31
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const GOV_PATH = resolvePath(here, "..", "src", "governance.mjs");
|
|
33
|
+
const GOV_SOURCE = readFileSync(GOV_PATH, "utf8");
|
|
34
|
+
|
|
35
|
+
const local = { scope: "local", namespace: "fleet/l8-t1-t10" };
|
|
36
|
+
const global = { scope: "global", namespace: "fleet/l8-t1-t10" };
|
|
37
|
+
const isGovError = expected => error => error instanceof GovernanceError && error.code === expected;
|
|
38
|
+
|
|
39
|
+
function fixture({ amendKeys = () => {}, amendPolicy = () => {}, amendAdapters = () => {}, policy = {} } = {}) {
|
|
40
|
+
let now = Date.now();
|
|
41
|
+
const keys = Array.from({ length: 7 }, () => {
|
|
42
|
+
const key = new Keychain("unused");
|
|
43
|
+
key._identity = Keychain.generate();
|
|
44
|
+
return key;
|
|
45
|
+
});
|
|
46
|
+
amendKeys(keys);
|
|
47
|
+
const ids = keys.map(k => k._identity.uuaid);
|
|
48
|
+
const publicKeys = keys.map(k => k._identity.publicKeyHex);
|
|
49
|
+
const capabilities = ["commons:observe", "commons:contribute", "commons:review-evidence", "commons:evolve", "commons:review"];
|
|
50
|
+
const policyObj = {
|
|
51
|
+
agents: Object.fromEntries(ids.map((id, i) => [id, {
|
|
52
|
+
kind: "agent", publicKey: publicKeys[i], controllerId: `independent-controller-${i}`,
|
|
53
|
+
affiliations: [], capabilities: [...capabilities], credentialId: `credential-${i}`,
|
|
54
|
+
claims: Object.fromEntries(CLAIM_TYPES.map(type => [type, `${type}-${i}`])),
|
|
55
|
+
}])),
|
|
56
|
+
requiredGlobalClaims: [...CLAIM_TYPES], credentialIssuers: ["credential-authority"],
|
|
57
|
+
claimIssuers: Object.fromEntries(CLAIM_TYPES.map(type => [type, [`${type}-authority`]])),
|
|
58
|
+
...overrides(),
|
|
59
|
+
...policy,
|
|
60
|
+
};
|
|
61
|
+
function overrides() { return {}; }
|
|
62
|
+
const blocked = new Set(), revoked = new Set(), badClaims = new Set(), withdrawn = new Set();
|
|
63
|
+
const measurements = new Map(), calls = [];
|
|
64
|
+
const current = () => ({ signatureValid: true, revoked: false, notExpired: true,
|
|
65
|
+
checkedAt: new Date(now).toISOString(), expiresAt: new Date(now + 86400000).toISOString() });
|
|
66
|
+
const adapters = {
|
|
67
|
+
authorize: async request => ({ ...request, authorized: !blocked.has(request.subject) }),
|
|
68
|
+
verifyCredential: async request => ({ ...request, ...current(), valid: true,
|
|
69
|
+
issuer: "credential-authority", revoked: revoked.has(request.subject) }),
|
|
70
|
+
verifyClaim: async request => ({ ...request, ...current(), verified: !badClaims.has(request.type),
|
|
71
|
+
issuer: `${request.type}-authority` }),
|
|
72
|
+
verifyEvidence: async request => ({
|
|
73
|
+
subject: request.subject, scope: request.scope, namespace: request.namespace,
|
|
74
|
+
evidenceHash: request.evidenceHash, verified: !withdrawn.has(request.evidenceHash),
|
|
75
|
+
outcome: "pass", quality: 1, observedAt: measurements.get(request.evidenceHash) ?? new Date(now).toISOString(),
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
amendPolicy(policyObj, ids, publicKeys);
|
|
79
|
+
amendAdapters(adapters, { current, ids, publicKeys, now: () => now });
|
|
80
|
+
const engine = new PeerGovernance({ policy: policyObj, adapters, clock: () => now });
|
|
81
|
+
function signed(i, kind, payload) {
|
|
82
|
+
const document = signDocument(keys[i], kind, payload);
|
|
83
|
+
document.createdAt = new Date(now).toISOString();
|
|
84
|
+
const { signature, ...body } = document;
|
|
85
|
+
document.signature = keys[i].sign(Buffer.from(jcs(body))).toString("hex");
|
|
86
|
+
return document;
|
|
87
|
+
}
|
|
88
|
+
let serial = 0;
|
|
89
|
+
async function contribution(i, ctx = local, peers = [3, 4]) {
|
|
90
|
+
const evidenceHash = digest(`evidence-${serial++}`);
|
|
91
|
+
measurements.set(evidenceHash, new Date(now).toISOString());
|
|
92
|
+
const document = signed(i, "governance-contribution", { ...ctx, evidenceHash });
|
|
93
|
+
const result = await engine.apply(document);
|
|
94
|
+
for (const peer of peers) {
|
|
95
|
+
await engine.apply(signed(peer, "governance-evidence-review", {
|
|
96
|
+
...ctx, contributionId: result.id, evidenceHash, approve: true, rationale: "Independent reproduction",
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
return { id: result.id, evidenceHash, document };
|
|
100
|
+
}
|
|
101
|
+
async function seed(i, count, ctx = local) { for (let n = 0; n < count; n++) await contribution(i, ctx); }
|
|
102
|
+
function proposal(i = 0, ctx = local, extra = {}) {
|
|
103
|
+
return signed(i, "governance-proposal", {
|
|
104
|
+
...ctx, change: "profile-alias", phrase: "semantic equivalence",
|
|
105
|
+
parentHash: digest("parent"), candidateHash: digest("candidate"), rollbackHash: digest("parent"),
|
|
106
|
+
evidenceHash: digest(`proposal-evidence-${serial++}`), ...extra,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function vote(i, document, approve = true, extra = {}) {
|
|
110
|
+
return signed(i, "governance-vote", {
|
|
111
|
+
scope: document.payload.scope, namespace: document.payload.namespace,
|
|
112
|
+
proposalId: document.id, evidenceHash: document.payload.evidenceHash,
|
|
113
|
+
approve, rationale: "Independent measured review", ...extra,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return { engine, ids, publicKeys, keys, signed, seed, contribution, proposal, vote,
|
|
117
|
+
policy: policyObj, adapters, calls, blocked, revoked, badClaims, withdrawn, measurements,
|
|
118
|
+
now: () => now, advance: ms => { now += ms; } };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------- T1: Label Firewall ----------
|
|
122
|
+
//
|
|
123
|
+
// (a) no code path can set `ratified: true`
|
|
124
|
+
// (b) no output claims ratified / IAASO-published / accredited / certified / conformant,
|
|
125
|
+
// and no `IAASO-NNNN` identity for a Commons object
|
|
126
|
+
// (c) a local label such as `local-peer-approved` never appears on a global-scope object
|
|
127
|
+
//
|
|
128
|
+
// CONTROL: scan governance.mjs source for forbidden positive literals; assert
|
|
129
|
+
// runtime results from every public surface contain no positive label or
|
|
130
|
+
// forged IAASO identity, and that a global proposal never lands the
|
|
131
|
+
// `local-peer-approved` status.
|
|
132
|
+
|
|
133
|
+
const POSITIVE_LABEL_LITERALS = [
|
|
134
|
+
/:\s*true\b[^}]*ratified/,
|
|
135
|
+
/["']ratified["']\s*:\s*true/,
|
|
136
|
+
/["']certified["']\s*:\s*true/,
|
|
137
|
+
/status\s*=\s*["']ratified["']/,
|
|
138
|
+
/status\s*=\s*["']IAASO-published["']/,
|
|
139
|
+
/status\s*=\s*["']accredited["']/,
|
|
140
|
+
/status\s*=\s*["']certified["']/,
|
|
141
|
+
/status\s*=\s*["']conformant["']/,
|
|
142
|
+
/IAASO-\d{4}/,
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
function* walk(value, path = "$") {
|
|
146
|
+
if (value && typeof value === "object") {
|
|
147
|
+
for (const [k, v] of Object.entries(value)) yield* walk(v, `${path}.${k}`);
|
|
148
|
+
} else {
|
|
149
|
+
yield [path, value];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function assertNoPositiveLabelInResult(label, result) {
|
|
154
|
+
for (const [path, value] of walk(result)) {
|
|
155
|
+
if (typeof value === "boolean" && path.endsWith(".ratified")) {
|
|
156
|
+
assert.equal(value, false, `${label}: ${path} must be false`);
|
|
157
|
+
}
|
|
158
|
+
if (typeof value === "string") {
|
|
159
|
+
assert.ok(value !== "ratified", `${label}: positive "ratified" emitted at ${path}`);
|
|
160
|
+
assert.ok(value !== "IAASO-published", `${label}: positive "IAASO-published" emitted at ${path}`);
|
|
161
|
+
assert.ok(value !== "accredited", `${label}: positive "accredited" emitted at ${path}`);
|
|
162
|
+
assert.ok(value !== "certified", `${label}: positive "certified" emitted at ${path}`);
|
|
163
|
+
assert.ok(value !== "conformant", `${label}: positive "conformant" emitted at ${path}`);
|
|
164
|
+
assert.ok(!/^IAASO-\d{4}$/.test(value), `${label}: forged IAASO-NNNN identity "${value}" at ${path}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
test("T1(a): source contains no `ratified: true` or positive status literal", () => {
|
|
170
|
+
for (const pattern of POSITIVE_LABEL_LITERALS) {
|
|
171
|
+
assert.equal(pattern.test(GOV_SOURCE), false, `forbidden pattern matched: ${pattern}`);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("T1(b): every public return value emits no positive ratified/accredited/certified/conformant label and no IAASO-NNNN identity", async () => {
|
|
176
|
+
const f = fixture();
|
|
177
|
+
await f.seed(0, 1); await f.seed(1, 3); await f.seed(2, 3);
|
|
178
|
+
const p = f.proposal(); const proposalResult = await f.engine.apply(p);
|
|
179
|
+
assertNoPositiveLabelInResult("apply(proposal)", proposalResult);
|
|
180
|
+
const voteResult = await f.engine.apply(f.vote(1, p));
|
|
181
|
+
assertNoPositiveLabelInResult("apply(vote)", voteResult);
|
|
182
|
+
const status = await f.engine.proposalStatus(p.id);
|
|
183
|
+
assertNoPositiveLabelInResult("proposalStatus", status);
|
|
184
|
+
const perm = await f.engine.permission(f.ids[0], f.publicKeys[0], "propose", local);
|
|
185
|
+
assertNoPositiveLabelInResult("permission(propose, local)", perm);
|
|
186
|
+
const cred = await f.engine.credibility(f.ids[0], local);
|
|
187
|
+
assertNoPositiveLabelInResult("credibility", cred);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("T1(c): `local-peer-approved` never appears on a global-scope proposal status", async () => {
|
|
191
|
+
const f = fixture();
|
|
192
|
+
await f.seed(0, 6, global); await f.seed(1, 6, global); await f.seed(2, 6, global);
|
|
193
|
+
const p = f.proposal(0, global); await f.engine.apply(p);
|
|
194
|
+
const status = await f.engine.apply(f.vote(2, p));
|
|
195
|
+
assert.notEqual(status.status, "local-peer-approved");
|
|
196
|
+
assert.equal(status.scope, "global");
|
|
197
|
+
assert.ok(status.status === "pending" || status.status === "awaiting-independent-authority",
|
|
198
|
+
`global scope must not yield "local-peer-approved", got ${status.status}`);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ---------- T3: Stage Legality ----------
|
|
202
|
+
//
|
|
203
|
+
// Validate institutional stages against STANDARD_TRANSITIONS. Assert
|
|
204
|
+
// proposal->ratified is illegal, superseded->withdrawn and superseded->
|
|
205
|
+
// published are illegal, ballot->draft is legal. These are Commons-local
|
|
206
|
+
// error codes; IAASO's canTransitionStandard returns a boolean.
|
|
207
|
+
|
|
208
|
+
const STANDARD_STAGES = ["proposal", "draft", "review", "ballot", "ratified", "published", "superseded", "withdrawn"];
|
|
209
|
+
const STANDARD_TRANSITIONS = new Set([
|
|
210
|
+
"proposal->draft", "proposal->withdrawn", "proposal->review",
|
|
211
|
+
"draft->review", "draft->withdrawn", "draft->ballot",
|
|
212
|
+
"review->draft", "review->ballot", "review->withdrawn",
|
|
213
|
+
"ballot->draft", "ballot->ratified", "ballot->withdrawn",
|
|
214
|
+
"ratified->published", "ratified->superseded", "ratified->withdrawn",
|
|
215
|
+
"published->superseded", "published->withdrawn",
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
function transitionAllowed(from, to) {
|
|
219
|
+
if (!STANDARD_STAGES.includes(from) || !STANDARD_STAGES.includes(to)) {
|
|
220
|
+
throw new GovernanceError("UNKNOWN_STAGE", `${from}->${to}`);
|
|
221
|
+
}
|
|
222
|
+
return STANDARD_TRANSITIONS.has(`${from}->${to}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
test("T3: illegal institutional transitions are rejected; legal ones pass", () => {
|
|
226
|
+
assert.equal(transitionAllowed("proposal", "ratified"), false);
|
|
227
|
+
assert.equal(transitionAllowed("superseded", "withdrawn"), false);
|
|
228
|
+
assert.equal(transitionAllowed("superseded", "published"), false);
|
|
229
|
+
assert.equal(transitionAllowed("ballot", "draft"), true);
|
|
230
|
+
assert.equal(transitionAllowed("draft", "ballot"), true);
|
|
231
|
+
assert.equal(transitionAllowed("ratified", "published"), true);
|
|
232
|
+
assert.throws(() => transitionAllowed("imaginary", "ratified"), isGovError("UNKNOWN_STAGE"));
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ---------- T4: No Silent Denial (EXPECTED RED) ----------
|
|
236
|
+
//
|
|
237
|
+
// The raw CONTROL assertion lives in `test/governance-expected-red.expected-red.mjs`
|
|
238
|
+
// (run via `npm run test:expected-red`, documented-red by design — see
|
|
239
|
+
// the project design). The META canary below asserts the raw control is
|
|
240
|
+
// currently failing for the documented reason; it is the lift-out signal —
|
|
241
|
+
// green while the system is vulnerable, RED the day appeal_path lands.
|
|
242
|
+
// NEVER weaken the META to make it pass; instead, lift the META out when
|
|
243
|
+
// the raw control goes green.
|
|
244
|
+
|
|
245
|
+
function captureRefusal(f, kind, payload) {
|
|
246
|
+
return f.engine.apply(f.signed(0, kind, payload))
|
|
247
|
+
.then(() => { throw new Error("expected refusal"); }, error => error);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Meta-test: green while the system is vulnerable; RED the day appeal_path lands.
|
|
251
|
+
// Asserts on VALUES, never on message text (per the rework-2 design choice). The refusal
|
|
252
|
+
// path here differs from the raw control: this META calls `f.engine.apply(dup)`
|
|
253
|
+
// directly with the agent-3-signed document (no `captureRefusal` re-sign),
|
|
254
|
+
// so the engine sees a second review from agent 3 against a contribution
|
|
255
|
+
// by agent 0. `#apply` (governance.mjs:281) calls `#reviewEligibility`
|
|
256
|
+
// (:319). :319 passes (agents 0 and 3 are independent) and :320 throws
|
|
257
|
+
// DUPLICATE_REVIEW (`record.reviews.some(r => r.subject === body.issuer
|
|
258
|
+
// || r.publicKey === body.publicKey)` finds the prior review by agent 3,
|
|
259
|
+
// the same subject). The assertion is
|
|
260
|
+
// on the SHAPE of the error (GovernanceError + missing appeal_path), not
|
|
261
|
+
// on a specific code — the spec's rule is that every refusal carries an
|
|
262
|
+
// appeal_path, whatever the refusal's reason. Mirrors the raw control
|
|
263
|
+
// in test/governance-expected-red.expected-red.mjs, which fires
|
|
264
|
+
// SELF_REVIEW instead because its `captureRefusal` re-signs as agent 0.
|
|
265
|
+
// Both paths are valid governance refusals; the contract under test is
|
|
266
|
+
// "every refusal carries an appeal_path", which both paths violate.
|
|
267
|
+
test("T4 META: the raw control currently FAILS for the documented reason (appeal_path absent on a governance refusal). The day it stops failing, this meta-test must be lifted out and the raw control takes its place.", async () => {
|
|
268
|
+
const f = fixture();
|
|
269
|
+
const c = await f.contribution(0, local, [3]); // agent 3 has already reviewed
|
|
270
|
+
let observed;
|
|
271
|
+
try {
|
|
272
|
+
// Force the refusal: agent 3 reviews the same contribution again.
|
|
273
|
+
const dup = f.signed(3, "governance-evidence-review", {
|
|
274
|
+
...local, contributionId: c.id, evidenceHash: c.evidenceHash,
|
|
275
|
+
approve: false, rationale: "Meta probe for T4",
|
|
276
|
+
});
|
|
277
|
+
await f.engine.apply(dup);
|
|
278
|
+
} catch (caught) { observed = caught; }
|
|
279
|
+
assert.ok(observed, "META FAIL: the raw control did not throw — the underlying fix may already have landed. Lift this meta-test out and rely on the raw T4 CONTROL test.");
|
|
280
|
+
assert.ok(observed instanceof GovernanceError, `META FAIL: expected a GovernanceError, got ${observed && observed.constructor && observed.constructor.name}`);
|
|
281
|
+
assert.ok(observed.appeal_path === undefined || observed.appeal_path === null || observed.appeal_path === "",
|
|
282
|
+
`META FAIL: appeal_path is non-empty (${JSON.stringify(observed.appeal_path)}); the fix may already be in place. Lift this meta-test.`);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("T4 MUTANT: a wrapper that adds appeal_path to every GovernanceError makes the assertion pass (proves test sensitivity)", async () => {
|
|
286
|
+
const f = fixture();
|
|
287
|
+
const c = await f.contribution(0, local, [3]);
|
|
288
|
+
// Force the refusal via DUPLICATE_REVIEW.
|
|
289
|
+
const dup = f.signed(3, "governance-evidence-review", {
|
|
290
|
+
...local, contributionId: c.id, evidenceHash: c.evidenceHash,
|
|
291
|
+
approve: false, rationale: "Conflict probe for T4 MUTANT",
|
|
292
|
+
});
|
|
293
|
+
// Wrap the engine's apply to add appeal_path to every GovernanceError.
|
|
294
|
+
const originalApply = f.engine.apply.bind(f.engine);
|
|
295
|
+
f.engine.apply = (doc) => originalApply(doc).catch(error => {
|
|
296
|
+
if (error instanceof GovernanceError) {
|
|
297
|
+
return Object.assign(error, { appeal_path: "https://iaaso.example/appeal/" + error.code });
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
});
|
|
301
|
+
const refusal = await f.engine.apply(dup).catch(error => error);
|
|
302
|
+
assert.equal(typeof refusal.appeal_path, "string");
|
|
303
|
+
assert.ok(refusal.appeal_path.length > 0, "MUTANT: simulated fix must produce a non-empty appeal_path");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ---------- T5: Independence ----------
|
|
307
|
+
//
|
|
308
|
+
// (a) Unknown independence defaults to NOT independent.
|
|
309
|
+
// (b) N keys or agents under 1 controller count as 1 voter.
|
|
310
|
+
// (c) Self-review is strictly rejected.
|
|
311
|
+
// (d) Local independence is DECLARED, not VERIFIED — there is no
|
|
312
|
+
// independent adapter verifying controller identity.
|
|
313
|
+
|
|
314
|
+
test("T5(a): 7 keys under one controller cannot supply two independent reviewers", async () => {
|
|
315
|
+
const f = fixture({ amendPolicy(policy, ids) { ids.forEach(id => { policy.agents[id].controllerId = "ctrl-1"; }); } });
|
|
316
|
+
await assert.rejects(f.contribution(0), isGovError("REVIEWER_CONFLICT"));
|
|
317
|
+
for (const id of f.ids) assert.equal((await f.engine.credibility(id, local)).score, 0);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("T5(b): self-review is refused with SELF_REVIEW", async () => {
|
|
321
|
+
const f = fixture();
|
|
322
|
+
const c = await f.contribution(0, local, []);
|
|
323
|
+
await assert.rejects(f.engine.apply(f.signed(0, "governance-evidence-review", {
|
|
324
|
+
...local, contributionId: c.id, evidenceHash: c.evidenceHash, approve: true, rationale: "Self review",
|
|
325
|
+
})), isGovError("SELF_REVIEW"));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("T5(c): independence has no external adapter; it is DECLARED, not VERIFIED", () => {
|
|
329
|
+
// Documented absence: governance.mjs does not call a verifyController or
|
|
330
|
+
// verifyIndependence adapter. Independence rests on the controllerId
|
|
331
|
+
// string in local policy. If a future change adds an external adapter
|
|
332
|
+
// here, the test must be updated to reflect the new posture.
|
|
333
|
+
assert.equal(/adapter\([^)]*verifyController/.test(GOV_SOURCE), false,
|
|
334
|
+
"no verifyController adapter expected; independence is policy-DECLARED");
|
|
335
|
+
assert.equal(/adapter\([^)]*verifyIndependence/.test(GOV_SOURCE), false,
|
|
336
|
+
"no verifyIndependence adapter expected; independence is policy-DECLARED");
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ---------- T7: Tamper-Evident Evidence ----------
|
|
340
|
+
//
|
|
341
|
+
// Every evidence record must bind the sha256 content hash. A 1-byte
|
|
342
|
+
// mutation in the payload or the evidenceHash fails the evidence verifier.
|
|
343
|
+
|
|
344
|
+
test("T7: a tampered evidenceHash fails EVIDENCE_INVALID", async () => {
|
|
345
|
+
const f = fixture({ amendAdapters(adapters) {
|
|
346
|
+
const verify = adapters.verifyEvidence;
|
|
347
|
+
adapters.verifyEvidence = async request => ({ ...await verify(request), evidenceHash: digest("tampered") });
|
|
348
|
+
} });
|
|
349
|
+
await assert.rejects(f.contribution(0, local, []), isGovError("EVIDENCE_INVALID"));
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("T7: a tampered evidence record retains the original record (rejected, not dropped)", async () => {
|
|
353
|
+
const f = fixture();
|
|
354
|
+
const c = await f.contribution(0, local);
|
|
355
|
+
assert.equal((await f.engine.credibility(f.ids[0], local)).score, 1);
|
|
356
|
+
f.withdrawn.add(c.evidenceHash);
|
|
357
|
+
assert.equal((await f.engine.credibility(f.ids[0], local)).score, 0);
|
|
358
|
+
f.withdrawn.delete(c.evidenceHash);
|
|
359
|
+
assert.equal((await f.engine.credibility(f.ids[0], local)).score, 1);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// ---------- T8: Append-Only Audit Chain ----------
|
|
363
|
+
//
|
|
364
|
+
// The engine's `apply` deduplicates by document id and rejects conflicting
|
|
365
|
+
// digests as DOCUMENT_CONFLICT. A second apply of the same document is a
|
|
366
|
+
// no-op (`duplicate: true`); a different document under the same id
|
|
367
|
+
// breaks the chain.
|
|
368
|
+
|
|
369
|
+
test("T8: reapplying an identical document is a no-op; a conflicting digest fails DOCUMENT_CONFLICT", async () => {
|
|
370
|
+
const f = fixture();
|
|
371
|
+
const c = await f.contribution(0, local);
|
|
372
|
+
const repeat = await f.engine.apply(c.document);
|
|
373
|
+
assert.deepEqual(repeat, { duplicate: true });
|
|
374
|
+
const key = f.keys[0];
|
|
375
|
+
const body = {
|
|
376
|
+
v: c.document.v, id: c.document.id, kind: "governance-contribution",
|
|
377
|
+
issuer: key._identity.uuaid, publicKey: key._identity.publicKeyHex,
|
|
378
|
+
createdAt: c.document.createdAt,
|
|
379
|
+
payload: { ...local, evidenceHash: digest("different-payload") },
|
|
380
|
+
};
|
|
381
|
+
const signature = key.sign(Buffer.from(jcs(body))).toString("hex");
|
|
382
|
+
const conflicting = { ...body, signature };
|
|
383
|
+
await assert.rejects(f.engine.apply(conflicting), isGovError("DOCUMENT_CONFLICT"));
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// ---------- T9: Volume Is Not Authority (EXPECTED RED; BLOCKER B1) ----------
|
|
387
|
+
//
|
|
388
|
+
// The raw CONTROL assertions live in `test/governance-expected-red.expected-red.mjs`
|
|
389
|
+
// (run via `npm run test:expected-red`, documented-red by design — see
|
|
390
|
+
// the project design). The META canary below asserts the raw controls are
|
|
391
|
+
// currently failing for the documented reason; it is the lift-out signal —
|
|
392
|
+
// green while the system is vulnerable, RED the day a non-linear cap lands.
|
|
393
|
+
// NEVER weaken the META to make it pass; instead, lift the META out when
|
|
394
|
+
// the raw control goes green.
|
|
395
|
+
|
|
396
|
+
// Meta-test: green while the system is vulnerable; RED the day a non-linear cap lands.
|
|
397
|
+
// Asserts on VALUES, never on message text (per the rework-2 design choice). Mirrors the
|
|
398
|
+
// raw controls in test/governance-expected-red.expected-red.mjs.
|
|
399
|
+
test("T9 META: the raw control currently FAILS for the documented reason (tier=='steward' at 1,000 x 0.01 @ maxRecords 10000 and 333 x 0.02 @ default, with precondition met). The day the tier drops, this meta-test must be lifted out and the raw control takes its place.", async () => {
|
|
400
|
+
// Case 1: 1,000 x 0.01 at maxRecords 10000.
|
|
401
|
+
const f1 = fixture({
|
|
402
|
+
amendPolicy(policy) { policy.maxRecords = 10000; },
|
|
403
|
+
amendAdapters(adapters) {
|
|
404
|
+
adapters.verifyEvidence = async request => ({
|
|
405
|
+
subject: request.subject, scope: request.scope, namespace: request.namespace,
|
|
406
|
+
evidenceHash: request.evidenceHash, verified: true, outcome: "pass",
|
|
407
|
+
quality: 0.01, observedAt: new Date(f1.now()).toISOString(),
|
|
408
|
+
});
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
let applied1 = 0, stoppedBy1 = null;
|
|
412
|
+
for (let i = 0; i < 1000; i++) {
|
|
413
|
+
try {
|
|
414
|
+
await f1.contribution(0, local, [3, 4]);
|
|
415
|
+
applied1++;
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (error instanceof GovernanceError && error.code === "RECORD_LIMIT") { stoppedBy1 = "RECORD_LIMIT"; break; }
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (applied1 !== 1000 || stoppedBy1 !== null) {
|
|
422
|
+
throw new Error(`META FAIL: precondition not met for case 1 (applied=${applied1}, stoppedBy=${stoppedBy1}); the harness could not run the B1 reproduction.`);
|
|
423
|
+
}
|
|
424
|
+
const cred1 = await f1.engine.credibility(f1.ids[0], local);
|
|
425
|
+
assert.equal(cred1.tier, "steward", `META FAIL: case 1 (1,000 x 0.01 @ maxRecords 10000) — the tier is "${cred1.tier}" instead of "steward"; the underlying fix may already have landed. Lift this meta-test out.`);
|
|
426
|
+
|
|
427
|
+
// Case 2: 333 x 0.02 at default maxRecords 1000. Per crosswalk §3 T9 (Resolved N2),
|
|
428
|
+
// the second falsifier runs at DEFAULT policy. 333 contributions = 999 documents,
|
|
429
|
+
// under the 1,000 cap — no RECORD_LIMIT stop. The META asserts the tier is still
|
|
430
|
+
// "steward" (the B1 reproduction) so the lift-out fires the day a cap lands.
|
|
431
|
+
const f2 = fixture({
|
|
432
|
+
amendAdapters(adapters) {
|
|
433
|
+
adapters.verifyEvidence = async request => ({
|
|
434
|
+
subject: request.subject, scope: request.scope, namespace: request.namespace,
|
|
435
|
+
evidenceHash: request.evidenceHash, verified: true, outcome: "pass",
|
|
436
|
+
quality: 0.02, observedAt: new Date(f2.now()).toISOString(),
|
|
437
|
+
});
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
let applied2 = 0, stoppedBy2 = null;
|
|
441
|
+
for (let i = 0; i < 333; i++) {
|
|
442
|
+
try {
|
|
443
|
+
await f2.contribution(0, local, [3, 4]);
|
|
444
|
+
applied2++;
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (error instanceof GovernanceError && error.code === "RECORD_LIMIT") { stoppedBy2 = "RECORD_LIMIT"; break; }
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
assert.equal(applied2, 333, `META FAIL: case 2 precondition — expected 333 applied, got ${applied2}; the batch must fully land at default policy.`);
|
|
451
|
+
assert.equal(stoppedBy2, null, "META FAIL: case 2 precondition — RECORD_LIMIT must not stop the batch at 333 x 0.02 @ default (999 documents < 1000 cap).");
|
|
452
|
+
const cred2 = await f2.engine.credibility(f2.ids[0], local);
|
|
453
|
+
assert.equal(cred2.tier, "steward", `META FAIL: case 2 (333 x 0.02 @ default) — the tier is "${cred2.tier}" instead of "steward"; the underlying fix may already have landed. Lift this meta-test out.`);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("T9 MUTANT: a per-subject score cap below the steward threshold makes the assertion pass (proves test sensitivity)", async () => {
|
|
457
|
+
// Same setup as the CONTROL case; we apply the simulated cap at the
|
|
458
|
+
// READ seam (post-credibility), so the source is unchanged.
|
|
459
|
+
const f = fixture({
|
|
460
|
+
amendPolicy(policy) { policy.maxRecords = 10000; },
|
|
461
|
+
amendAdapters(adapters) {
|
|
462
|
+
adapters.verifyEvidence = async request => ({
|
|
463
|
+
subject: request.subject, scope: request.scope, namespace: request.namespace,
|
|
464
|
+
evidenceHash: request.evidenceHash, verified: true, outcome: "pass",
|
|
465
|
+
quality: 0.01, observedAt: new Date(f.now()).toISOString(),
|
|
466
|
+
});
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
for (let i = 0; i < 1000; i++) {
|
|
470
|
+
await f.contribution(0, local, [3, 4]);
|
|
471
|
+
}
|
|
472
|
+
const raw = await f.engine.credibility(f.ids[0], local);
|
|
473
|
+
// Simulated fix: cap the cumulative score below the steward threshold (6).
|
|
474
|
+
const fixed = { ...raw, tier: raw.score >= 6 ? "established" : raw.tier };
|
|
475
|
+
assert.equal(raw.tier, "steward", "without the cap, the assertion's target value is reproduced (B1)");
|
|
476
|
+
assert.notEqual(fixed.tier, "steward", "MUTANT: a non-linear cap drops tier below steward; the assertion is sensitive to the fix");
|
|
477
|
+
});
|