spike(modules): carry /public/atlas/* behind the proposed module surface

THROWAWAY BRANCH — evidence for the Phase 1 contract, never merged. See
modules/uo/SPIKE.md and docs/website/MODULE_API.md Part 7.

The six public spawn-atlas routes now live in modules/uo/, reached only through
the ctx/register surface, with the client half loading as a prebuilt ESM chunk.
All three exit criteria met:

  • zero internal-file imports from the module into core; the built chunk has
    zero bare import specifiers and bundles no React
  • routes.manifest.json AND routes.guards.json are byte-identical
  • /uo/atlas renders from /modules/uo/entry.js under script-src 'self' with
    zero CSP violation reports

729 core tests and 81 module tests pass. Verified end to end against the real
database: the schema fragment replays after core's, onBoot runs the atlas
refresh, and the six API URLs answer unchanged.

Two things the spike changed in the contract:

  • ctx.express / ctx.validator. A module lives outside server/, so Node never
    reaches server/node_modules and require('express') fails outright — the
    server-side twin of the one-React rule, which §2.6 had only for the client.
  • window.__rg.jsxRuntime, so a module can build with the automatic JSX
    runtime its tooling already assumes rather than being forced to classic.

And it confirmed §6.1 empirically: regenerating the OpenAPI spec silently
deleted all 361 lines of the atlas paths with "Swagger-autogen: Success", while
the route manifest kept all six in the same run. That is exactly the
static-analysis-vs-runtime split the fragment merge exists to prevent.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-10 05:29:35 -05:00
parent f1dda8fe66
commit bf470c7658
55 changed files with 4638 additions and 601 deletions

View File

@@ -0,0 +1,256 @@
process.env.DB_HOST = '127.0.0.1'
process.env.DB_PORT = '59999'
const fs = require('fs')
const os = require('os')
const path = require('path')
const { test, beforeEach, after } = require('node:test')
const assert = require('node:assert/strict')
const db = require('../src/utils/db')
after(() => db.close())
// ── The loader's failure guarantees ────────────────────────────────────────
//
// docs/website/MODULE_API.md §4.4 promises that a module which fails ANYWHERE in
// its lifecycle fails alone: the site comes up, other modules are unaffected, and
// the failure is recorded rather than thrown. That is the property most worth a
// test, because the failure paths are the ones nobody exercises by hand — every
// manual check runs the happy path.
//
// Each test builds a throwaway modules directory, points MODULES_DIR at it and
// re-requires the loader with a clean cache, so the scan is genuinely redone.
let tmpRoot
function freshLoader(dir) {
process.env.MODULES_DIR = dir
delete require.cache[require.resolve('../src/modules/loader')]
// eslint-disable-next-line global-require
return require('../src/modules/loader')
}
function writeModule(id, { manifest = {}, server, schema } = {}) {
const dir = path.join(tmpRoot, id)
fs.mkdirSync(dir, { recursive: true })
const full = {
id,
name: id,
version: '1.0.0',
coreApi: '^1.0.0',
...(server === undefined ? {} : { server: 'index.js' }),
...(schema === undefined ? {} : { schema: 'schema.sql', purge: 'purge.sql' }),
...manifest,
}
fs.writeFileSync(path.join(dir, 'module.json'), JSON.stringify(full))
if (server !== undefined) fs.writeFileSync(path.join(dir, 'index.js'), server)
if (schema !== undefined) {
fs.writeFileSync(path.join(dir, 'schema.sql'), schema)
fs.writeFileSync(path.join(dir, 'purge.sql'), '')
}
return dir
}
const stateOf = (loader, id) => loader.list().find((m) => m.id === id)
beforeEach(() => {
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'rg-modules-'))
})
test('a missing modules directory is the normal case, not an error', () => {
const loader = freshLoader(path.join(tmpRoot, 'does-not-exist'))
assert.deepEqual(loader.list(), [])
})
test('a module whose entry point throws does not stop the others loading', () => {
writeModule('aaa', { server: 'module.exports = () => {}' })
writeModule('bbb', { server: 'throw new Error("boom")' })
writeModule('ccc', { server: 'module.exports = () => {}' })
const loader = freshLoader(tmpRoot)
assert.equal(stateOf(loader, 'aaa').state, 'registered')
assert.equal(stateOf(loader, 'ccc').state, 'registered')
const bad = stateOf(loader, 'bbb')
assert.equal(bad.state, 'startup_failed')
assert.match(bad.reason, /boom/)
})
test('a coreApi mismatch is refused before the module is required at all', () => {
// The entry point would throw if it ran; the version gate must run first.
writeModule('old', {
manifest: { coreApi: '^99.0.0' },
server: 'throw new Error("should never be required")',
})
const loader = freshLoader(tmpRoot)
const mod = stateOf(loader, 'old')
assert.equal(mod.state, 'startup_failed')
assert.match(mod.reason, /needs core API \^99\.0\.0/)
})
test('an unknown manifest key is rejected, not ignored', () => {
// A typo'd key must be loud: an operator who believes they configured
// something and silently did not is worse off than one who sees a failure.
writeModule('typo', { manifest: { mount: { public: ['/x'] } } })
const loader = freshLoader(tmpRoot)
assert.match(stateOf(loader, 'typo').reason, /unknown key "mount"/)
})
test('a module id that does not match its directory is rejected', () => {
writeModule('onedir', { manifest: { id: 'another' } })
const loader = freshLoader(tmpRoot)
// Recorded under the DIRECTORY name — the id it claimed is exactly what is
// not trusted here.
assert.match(stateOf(loader, 'onedir').reason, /does not match directory/)
})
test('two modules cannot claim the same prefix; the first one wins', () => {
writeModule('aaa', {
manifest: { mounts: { public: ['/thing'] } },
server: "module.exports = (ctx, api) => api.registerRoutes({ public: { '/thing': ctx.express.Router() } })",
})
writeModule('bbb', {
manifest: { mounts: { public: ['/thing'] } },
server: "module.exports = (ctx, api) => api.registerRoutes({ public: { '/thing': ctx.express.Router() } })",
})
const loader = freshLoader(tmpRoot)
assert.equal(stateOf(loader, 'aaa').state, 'registered')
assert.match(stateOf(loader, 'bbb').reason, /already registered by module "aaa"/)
})
test('a module cannot take a prefix core owns', () => {
writeModule('greedy', { manifest: { mounts: { admin: ['/users'] } } })
const loader = freshLoader(tmpRoot)
assert.match(stateOf(loader, 'greedy').reason, /owned by core/)
})
test('registering a prefix that was never declared is rejected', () => {
// module.json is what the admin panel, the collision check and the reviewer
// all read, so it has to be the truth rather than a hint.
writeModule('sneaky', {
manifest: { mounts: { public: ['/declared'] } },
server: `module.exports = (ctx, api) => api.registerRoutes({
public: { '/declared': ctx.express.Router(), '/undeclared': ctx.express.Router() },
})`,
})
const loader = freshLoader(tmpRoot)
assert.match(stateOf(loader, 'sneaky').reason, /registered public\/undeclared without declaring it/)
})
test('declaring a prefix and never registering it is rejected too', () => {
writeModule('forgetful', {
manifest: { mounts: { public: ['/a', '/b'] } },
server: "module.exports = (ctx, api) => api.registerRoutes({ public: { '/a': ctx.express.Router() } })",
})
const loader = freshLoader(tmpRoot)
assert.match(stateOf(loader, 'forgetful').reason, /declared public\/b but never registered it/)
})
test('a schema fragment declaring a core table is rejected', () => {
writeModule('thief', { schema: 'CREATE TABLE IF NOT EXISTS users (id INT);' })
const loader = freshLoader(tmpRoot)
assert.match(stateOf(loader, 'thief').reason, /declares core table "users"/)
})
test('a schema fragment table must carry the module id as a prefix', () => {
writeModule('mine', { schema: 'CREATE TABLE IF NOT EXISTS widgets (id INT);' })
const loader = freshLoader(tmpRoot)
assert.match(stateOf(loader, 'mine').reason, /not prefixed "mine_"/)
const ok = fs.mkdtempSync(path.join(os.tmpdir(), 'rg-modules-'))
tmpRoot = ok
writeModule('mine', { schema: 'CREATE TABLE IF NOT EXISTS mine_widgets (id INT);' })
assert.equal(stateOf(freshLoader(ok), 'mine').state, 'registered')
})
test('declaring a schema without a purge is rejected', () => {
const dir = path.join(tmpRoot, 'noway')
fs.mkdirSync(dir, { recursive: true })
fs.writeFileSync(
path.join(dir, 'module.json'),
JSON.stringify({ id: 'noway', name: 'x', version: '1.0.0', coreApi: '^1.0.0', schema: 'schema.sql' }),
)
const loader = freshLoader(tmpRoot)
assert.match(stateOf(loader, 'noway').reason, /declares schema but no purge/)
})
test('an onBoot that throws marks the module failed and never rejects', async () => {
writeModule('boomer', { server: 'module.exports = (ctx, api) => api.onBoot(async () => { throw new Error("late boom") })' })
writeModule('fine', { server: 'module.exports = (ctx, api) => api.onBoot(async () => {})' })
const loader = freshLoader(tmpRoot)
await loader.boot() // must resolve, not reject
assert.equal(stateOf(loader, 'fine').state, 'started')
const bad = stateOf(loader, 'boomer')
assert.equal(bad.state, 'startup_failed')
assert.match(bad.reason, /onBoot: late boom/)
})
test('onShutdown failures and hangs are absorbed', async () => {
writeModule('slow', {
server: 'module.exports = (ctx, api) => { api.onBoot(async () => {}); api.onShutdown(() => new Promise(() => {})) }',
})
writeModule('angry', {
server: 'module.exports = (ctx, api) => { api.onBoot(async () => {}); api.onShutdown(async () => { throw new Error("nope") }) }',
})
const loader = freshLoader(tmpRoot)
await loader.boot()
// `slow` never settles its promise; the loader's own budget has to end it, and
// `angry` throwing must not stop the loop either. Neither may reject.
await loader.shutdown()
})
test('registering the same thing twice is an error, not a silent overwrite', () => {
writeModule('twice', {
manifest: { mounts: { public: ['/x'] } },
server: `module.exports = (ctx, api) => {
api.registerRoutes({ public: { '/x': ctx.express.Router() } })
api.registerRoutes({ public: { '/x': ctx.express.Router() } })
}`,
})
const loader = freshLoader(tmpRoot)
assert.match(stateOf(loader, 'twice').reason, /registerRoutes\(\) called twice/)
})
test('ctx exposes exactly the documented surface, and is frozen', () => {
const seen = path.join(tmpRoot, 'probe-out.json')
writeModule('probe', {
server: `const fs = require('fs')
module.exports = (ctx) => {
let mutable = true
try { ctx.db.query = null; mutable = ctx.db.query === null } catch { mutable = false }
fs.writeFileSync(${JSON.stringify(seen)}, JSON.stringify({
keys: Object.keys(ctx).sort(),
middleware: Object.keys(ctx.middleware).sort(),
mutable,
}))
}`,
})
// list() is what triggers the lazy scan — requiring the loader alone does not
// run it, deliberately, so app.js controls when modules are discovered.
assert.equal(stateOf(freshLoader(tmpRoot), 'probe').state, 'registered')
const probe = JSON.parse(fs.readFileSync(seen, 'utf8'))
assert.deepEqual(probe.keys, [
'auth', 'db', 'express', 'log', 'middleware', 'moduleId', 'paths',
'posts', 'push', 'secretBox', 'settings', 'uploads', 'validator',
])
assert.deepEqual(probe.middleware, ['noindex', 'requireAuth', 'requireRole', 'siteMode', 'validate'])
assert.equal(probe.mutable, false, 'ctx members must be frozen')
})
test('the client and server halves agree on MODULE_API_VERSION', () => {
const { MODULE_API_VERSION } = require('../src/modules/version')
const clientSrc = fs.readFileSync(
path.join(__dirname, '..', '..', 'client', 'src', 'modules', 'version.js'),
'utf8',
)
// They version ONE contract; a module checks whichever half it is talking to,
// so a drift between them is a module that passes one gate and fails the other.
assert.match(clientSrc, new RegExp(`'${MODULE_API_VERSION.replace(/\./g, '\\.')}'`))
})