// P2 RED: model is implicit, durable/private, and status never exposes current/retiring values. import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { randomBytes, createHash } from 'node:crypto'; import { Credentials } from '../src/credentials.mjs'; function fixture(t) { const root = fs.mkdtempSync(join(tmpdir(), 'p2-store-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const storeRoot = join(root, 'config', 'secrets', 'agents'); fs.mkdirSync(storeRoot, { recursive: true, mode: 0o700 }); const open = () => new Credentials({ storeRoot, dataRoot: join(root, 'data'), modelAgents: ['demo/coder'] }); const file = join(storeRoot, 'demo', 'coder', 'model'); return { root, storeRoot, open, file }; } const key = () => 'msk-fixture-' + randomBytes(32).toString('base64url'); const id = value => createHash('sha256').update(value).digest('hex').slice(0, 12); const record = value => ({ key: value, keyId: id(value), issuedAt: '2026-10-24T00:00:00.000Z', rotateBy: '2026-11-23', state: 'valid', retiring: [] }); test('implicit model kind starts not-issued without a business or role reference', t => { const f = fixture(t), c = f.open(); t.after(() => c.close()); const status = c.status().find(s => s.instance === 'demo/coder' && s.service === 'model'); assert.equal(status?.state, 'not-issued'); assert.equal(fs.existsSync(f.file), false); }); test('model record persists at 0600 in 0700 dirs and reloads ids/issue time/state only in status', t => { const f = fixture(t), c = f.open(), value = key(); t.after(() => c.close()); assert.equal(typeof c.writeModel, 'function'); c.writeModel('demo/coder', record(value)); assert.equal(fs.statSync(f.file).mode & 0o777, 0o600); for (const dir of [f.storeRoot, join(f.storeRoot, 'demo'), join(f.storeRoot, 'demo', 'coder')]) assert.equal(fs.statSync(dir).mode & 0o777, 0o700); c.close(); const again = f.open(); t.after(() => again.close()); const status = again.status().find(s => s.service === 'model'); assert.equal(status.keyId === id(value), true); assert.equal(status.issuedAt, '2026-10-24T00:00:00.000Z'); assert.equal(status.state, 'valid'); assert.equal(JSON.stringify(again.status()).includes(value), false); assert.equal(again.modelRecord('demo/coder').key === value, true); }); test('revoking model metadata survives restart without losing the private record', t => { const f = fixture(t), c = f.open(), value = key(); t.after(() => c.close()); assert.equal(typeof c.writeModel, 'function'); c.writeModel('demo/coder', { ...record(value), state: 'revoking', revokingAt: '2026-10-24T01:00:00.000Z' }); c.close(); const again = f.open(); t.after(() => again.close()); assert.equal(again.status().find(s => s.service === 'model').state, 'revoking'); assert.equal(again.modelRecord('demo/coder').key === value, true); assert.equal(JSON.stringify(again.status()).includes(value), false); }); test('model record wrong file mode refuses startup rather than inventing a replacement', t => { const f = fixture(t), c = f.open(); t.after(() => c.close()); assert.equal(typeof c.writeModel, 'function'); c.writeModel('demo/coder', record(key())); c.close(); fs.chmodSync(f.file, 0o640); assert.throws(() => f.open(), e => e.code === 'credential-file'); }); test('non-proxy Credentials has no implicit model kind', t => { const f = fixture(t), c = new Credentials({ storeRoot: f.storeRoot, dataRoot: join(f.root, 'data') }); t.after(() => c.close()); assert.deepEqual(c.status(), []); }); test('credential-revoke-failed: generic remove cannot bypass remote model confirmation', t => { const f = fixture(t), c = f.open(); t.after(() => c.close()); c.writeModel('demo/coder', record(key())); assert.throws(() => c.remove('demo/coder'), e => e.code === 'credential-revoke-failed'); assert.equal(c.modelRecord('demo/coder').state, 'valid'); assert.equal(fs.existsSync(f.file), true); }); test('retiring keys reload privately and remain covered by the credential leak guard', t => { const f = fixture(t), c = f.open(), value = key(), old = key(); t.after(() => c.close()); c.writeModel('demo/coder', { ...record(value), retiring: [{ key: old, keyId: id(old), issuedAt: '2026-10-23T00:00:00.000Z', retireAt: '2026-10-24T02:00:00.000Z' }] }); c.close(); const again = f.open(); t.after(() => again.close()); assert.equal(again.modelRecord('demo/coder').retiring[0].key === old, true); const status = JSON.stringify(again.status()); assert.equal(status.includes(old), false); assert.equal(status.includes(value), false); assert.throws(() => again.assertClean({ value: old }), e => e.code === 'credential-leak'); });