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); });