/** Build a public classroom roster from an actual successful account audit, never from passwords. */ import fs from 'node:fs/promises'; import crypto from 'node:crypto'; const auditFile = process.argv[2] || 'docs/board-login-20261111/account-verification.json'; const audit = JSON.parse(await fs.readFile(auditFile, 'utf8')); if (audit.appId !== 'ncloudmaster' || audit.expectedCompanyObjectId !== 'FKieRbIoGt' || !Array.isArray(audit.results) || !audit.results.length || audit.total !== audit.results.length || audit.platformPassed !== audit.total || !Number.isFinite(Date.parse(audit.checkedAt))) throw Error('ROSTER_AUDIT_INVALID'); const ids = new Set(), usernames = new Set(); const members = audit.results.map(row => { if (!row.loginOk || !row.companyMatched || !row.profileNameMatched || !row.probeSessionClosed || typeof row.name !== 'string' || !row.name.trim() || typeof row.username !== 'string' || !row.username.trim() || typeof row.userId !== 'string' || !row.userId || ids.has(row.userId) || usernames.has(row.username)) throw Error('ROSTER_ACCOUNT_NOT_VERIFIED'); ids.add(row.userId);usernames.add(row.username); return { id: 'platform-account:' + crypto.createHash('sha256').update(JSON.stringify(['ncloudmaster','FKieRbIoGt',row.userId])).digest('hex'), name: row.name.trim(), username: row.username.trim() }; }); const roster = { schema: 'ksf.workshop-members.v1', companyId: 'FKieRbIoGt', platformAppId: audit.appId, verifiedAt: audit.checkedAt, members }; const body = '/** Verified public classroom roster. Regenerate with scripts/sync-workshop-member-roster.mjs after an account audit. */\n' + 'export const WORKSHOP_ROSTER = Object.freeze(' + JSON.stringify(roster, null, 2) + ');\n'; await fs.writeFile('src/core/workshop-roster.js', body); console.log(JSON.stringify({ members: members.length, verifiedAt: roster.verifiedAt, file: 'src/core/workshop-roster.js', credentialValuesIncluded: false }));