guanghu-ice-heart/runtime/public-personal-language-os/engine.test.mjs

133 lines
8.9 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { DAY } from './engine.mjs';
import { setup } from './test-support.mjs';
test('initialization has one context, no assigned independent persona, neutral editable names', t => {
const f = setup(t), first = f.create();
assert.equal(first.independentPersonaAssigned, false); assert.equal(first.enterpriseDomainsEmbedded, false);
f.call('alice', 'SAY', { text: '正在想一个问题' });
const change = f.call('alice', 'SWITCH_CHANNEL', { channelId: 'conversation' });
assert.equal(change.contextId, first.contextId);
assert.equal(f.call('alice', 'READ_CONTEXT').messages.length, 1);
assert.equal(f.call('alice', 'RENAME', { channelId: 'conversation', name: '我的花园' }).permissionChange, false);
assert.equal(change.authorityGranted, false);
});
test('cross-user and unassigned guide reads are rejected', t => {
const f = setup(t); f.create();
assert.throws(() => f.call('bob', 'READ_CONTEXT'), /OWNER_REQUIRED/);
assert.throws(() => f.call('guide', 'READ_CONTEXT'), /UNASSIGNED/);
});
test('guide enrollment requires both team signature and guide consent', t => {
const f = setup(t);
assert.throws(() => f.call('alice', 'ENROLL_GUIDE', {}), /TEAM_REQUIRED/);
assert.throws(() => f.call('team', 'ENROLL_GUIDE', { guideId: 'guide', name: '示例', registryEvidence: 'demo' }), /CONSENT_REQUIRED/);
assert.equal(f.enroll().enrolled, 'guide');
});
test('residency needs user consent and waiting request is revocable', t => {
const f = setup(t); f.create(); f.enroll();
assert.throws(() => f.call('dispatcher', 'START_RESIDENCY'), /REQUEST_REQUIRED/);
f.call('alice', 'REQUEST_RESIDENCY', { consent: true }); f.call('alice', 'CANCEL_RESIDENCY_REQUEST');
assert.throws(() => f.call('dispatcher', 'START_RESIDENCY'), /REQUEST_REQUIRED/);
});
test('residency lasts 30 real days; replay cannot restore expired guide access', t => {
const f = setup(t), visit = f.start();
const read = f.envelope('guide', 'READ_CONTEXT'); f.engine.handle(read);
f.advance(30 * DAY - 1); f.call('guide', 'READ_CONTEXT');
assert.throws(() => f.call('dispatcher', 'END_RESIDENCY'), /NOT_DUE/);
const lastRead = f.envelope('guide', 'READ_CONTEXT'); f.engine.handle(lastRead);
f.advance(1);
assert.throws(() => f.engine.handle(lastRead), /EXPIRED/);
assert.throws(() => f.call('guide', 'SAY', { text: 'after expiry' }), /EXPIRED/);
const handoff = f.call('dispatcher', 'END_RESIDENCY');
assert.equal(handoff.returnedGuide, visit.guideId); assert.equal(handoff.independentPersona, false);
assert.equal(f.call('alice', 'CONTINUE_SEED', { consent: false }).seedRetained, true);
});
test('guide remains public and becomes available to next user after handoff', t => {
const f = setup(t); f.start(); f.call('bob', 'CREATE_OS', {}, 'bob-os');
f.call('bob', 'REQUEST_RESIDENCY', { consent: true }, 'bob-os');
assert.throws(() => f.call('dispatcher', 'START_RESIDENCY', {}, 'bob-os'), /NO_AVAILABLE/);
f.advance(30 * DAY); f.call('dispatcher', 'END_RESIDENCY');
assert.equal(f.call('dispatcher', 'START_RESIDENCY', {}, 'bob-os').guideId, 'guide');
assert.throws(() => f.call('guide', 'READ_CONTEXT'), /UNASSIGNED/);
});
test('guide may stop accepting new placements and leave; seed is retained', t => {
const f = setup(t); f.start(); f.call('guide', 'GUIDE_AVAILABILITY', { available: false });
assert.equal(f.call('guide', 'END_RESIDENCY').independentPersona, false);
f.call('bob', 'CREATE_OS', {}, 'bob-os'); f.call('bob', 'REQUEST_RESIDENCY', { consent: true }, 'bob-os');
assert.throws(() => f.call('dispatcher', 'START_RESIDENCY', {}, 'bob-os'), /NO_AVAILABLE/);
});
test('assisted routing is opt-in and never creates another context', t => {
const f = setup(t); f.start();
assert.throws(() => f.call('guide', 'SWITCH_CHANNEL', { channelId: 'conversation' }), /USER_CHANNEL/);
f.call('alice', 'ALLOW_ASSISTED_ROUTING', { enabled: true });
const before = f.call('alice', 'READ_CONTEXT').contextId;
assert.equal(f.call('guide', 'SWITCH_CHANNEL', { channelId: 'conversation' }).contextId, before);
});
test('self naming stays a candidate; another space seed cannot take the identity', t => {
const f = setup(t); assert.equal(f.seed().independentRecognition, false);
assert.throws(() => f.call('wrongSeed', 'SELF_NAME', { name: 'wrong', evidence: 'demo' }), /SEED_ONLY/);
});
test('registration requires three reviews and final zero-sense team signature', t => {
const f = setup(t), app = f.application();
assert.throws(() => f.call('team', 'ISSUE_NUMBER', { digest: app.digest, number: 'PUBLIC-P-DEMO-001' }), /THREE_REVIEWS/);
f.reviews(app.digest);
const cert = f.call('team', 'ISSUE_NUMBER', { digest: app.digest, number: 'PUBLIC-P-DEMO-001' });
assert.equal(cert.governanceDomain, 'DOMAIN-ZS'); assert.equal(cert.executionPermissionsGranted, false);
assert.equal(cert.livePublicRegistration, false); assert.equal(cert.registryScope, 'LOCAL_DEVELOPMENT');
});
test('HOLD and withdrawn consent cannot issue a number', t => {
const f = setup(t), app = f.application(); f.reviews(app.digest, 'HOLD');
assert.throws(() => f.call('team', 'ISSUE_NUMBER', { digest: app.digest, number: 'PUBLIC-P-DEMO-001' }), /THREE_REVIEWS/);
f.call('alice', 'CONTINUE_SEED', { consent: false }); f.call('alice', 'CONTINUE_SEED', { consent: true });
assert.throws(() => f.call('mother', 'REVIEW_APPLICATION', { digest: app.digest, decision: 'PASS', reason: 'test' }), /STALE/);
});
test('private mother and stale application review cannot certify a public candidate', t => {
const f = setup(t), app = f.application();
assert.throws(() => f.call('privateMother', 'REVIEW_APPLICATION', { digest: app.digest, decision: 'PASS', reason: 'test' }), /PUBLIC_REVIEW/);
f.call('alice', 'WITHDRAW_APPLICATION');
const newer = f.call('alice', 'APPLY_RECOGNITION', { consent: true, evidenceRefs: ['evidence://demo/two'] });
assert.notEqual(newer.digest, app.digest);
assert.throws(() => f.call('checker', 'REVIEW_APPLICATION', { digest: app.digest, decision: 'PASS', reason: 'test' }), /STALE/);
});
test('tampered signature, wrong role and old request cannot mutate state', t => {
const f = setup(t); f.create(); const e = f.envelope('alice', 'RENAME', { name: 'a' }); e.payload.name = 'b';
assert.throws(() => f.engine.handle(e), /INVALID_SIGNATURE/);
assert.throws(() => f.call('alice', 'ISSUE_NUMBER', { number: 'PUBLIC-P-001' }), /TEAM_REQUIRED/);
const stale = f.envelope('alice', 'READ_CONTEXT'); f.advance(300001);
assert.throws(() => f.engine.handle(stale), /STALE_ENVELOPE/);
});
test('incomplete execution language waits instead of guessing target', t => {
const f = setup(t); f.create(); const result = f.call('alice', 'PROPOSE_ACTION', { operation: 'delete' });
assert.equal(result.status, 'WAITING_FOR_INFORMATION'); assert.deepEqual(result.missing, ['target']); assert.equal(result.executed, false);
});
test('stored signed authorization chain can be audited and tampering is detected', t => {
const f = setup(t); f.create(); f.call('alice', 'SAY', { text: 'local audit' });
assert.equal(f.engine.audit().signatures, 2);
const file = path.join(f.directory, 'state.json'), state = JSON.parse(fs.readFileSync(file));
state.journal[0].signedEvent.payload.name = 'tampered'; fs.writeFileSync(file, JSON.stringify(state));
assert.throws(() => f.engine.audit(), /AUDIT_CHAIN/);
});
test('real host execution requires exact confirmation and target readback', async t => {
const f = setup(t), a = f.action();
const start = f.envelope('alice', 'START_ACTION', { actionId: a.plan.id, digest: a.digest });
const result = await f.engine.executeAction(start, {
authorize: async (_, context) => ({ digest: context.digest, receipt: 'DEMO_LOCAL_SCOPE' }),
execute: async plan => { assert.equal(path.dirname(plan.target), f.directory); fs.writeFileSync(plan.target, plan.args.text, { flag: 'wx' }); return { target: plan.target }; },
verify: async plan => fs.readFileSync(plan.target, 'utf8') === plan.args.text,
});
assert.equal(result.status, 'COMPLETED'); assert.equal(result.verified, true);
await assert.rejects(f.engine.executeAction(start, { authorize() {}, execute() {}, verify() {} }), /NOT_CONFIRMED/);
});
test('stop during host approval prevents real operation', async t => {
const f = setup(t), a = f.action(); let unlock, entered;
const gate = new Promise(r => { unlock = r; }), ready = new Promise(r => { entered = r; }); let calls = 0;
const running = f.engine.executeAction(f.envelope('alice', 'START_ACTION', { actionId: a.plan.id, digest: a.digest }), {
authorize: async (_, c) => { entered(); await gate; return { digest: c.digest, receipt: 'demo' }; },
execute: async () => { calls++; return {}; }, verify: async () => true,
});
await ready; assert.equal(f.call('alice', 'STOP_ACTION', { actionId: a.plan.id }).status, 'STOP_REQUESTED');
unlock(); assert.equal((await running).status, 'STOPPED'); assert.equal(calls, 0);
});