Make uds-auth optional: standalone local mode for cron.

Without udsAuth, trusted HTTP uses a synthetic __local__ viewer that sees all jobs; with uds-auth, keep multi-user empNo isolation and admin folders. Document that IM channels are not default UDS accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-08 17:39:54 +08:00
parent 1c504eb1fd
commit 933bad9201
8 changed files with 132 additions and 27 deletions

View file

@ -916,6 +916,47 @@ test('GET /jobs claims unassigned jobs that match viewer cwd', async (t) => {
assert.equal(listed.body.jobs[0].ownerEmpNo, 'tester')
})
test('HTTP works standalone without uds-auth (local mode)', async (t) => {
const dir = await mkdtemp(join(tmpdir(), 'dsh-ops-cron-local-'))
t.after(() => rm(dir, { recursive: true, force: true }))
const service = createHostService({
filePath: join(dir, 'store.json'),
now: () => Date.parse('2026-08-24T01:00:00.000Z'),
sessionPort: {
async createAndPrompt() {
return { sessionId: 'local-sess', status: 'succeeded', summary: 'ok' }
},
async archiveSession() {},
},
getUdsAuth: () => undefined,
})
const http = await listen(service)
t.after(() => http.close())
const created = await jsonRequest(http.url, '/dsh-ops-cron/jobs', {
method: 'POST',
body: JSON.stringify({
name: 'local job',
prompt: 'ping',
schedule: { kind: 'cron', expr: '0 9 * * *', timezone: 'UTC' },
}),
})
assert.equal(created.status, 200)
assert.equal(created.body.job.name, 'local job')
assert.ok(!created.body.job.ownerEmpNo || created.body.job.ownerEmpNo === '__unassigned__')
const listed = await jsonRequest(http.url, '/dsh-ops-cron/jobs')
assert.equal(listed.status, 200)
assert.equal(listed.body.viewer.mode, 'local')
assert.equal(listed.body.viewer.canViewAll, true)
assert.equal(listed.body.jobs.length, 1)
const ran = await jsonRequest(http.url, `/dsh-ops-cron/jobs/${created.body.job.id}/run`, { method: 'POST' })
assert.equal(ran.status, 200)
assert.equal(ran.body.run.sessionId, 'local-sess')
})
test('migrateJobOwners assigns unassigned for legacy jobs', async () => {
const { migrateJobOwners, UNASSIGNED_OWNER } = await import('../lib/ownership.js')
const state = {

View file

@ -7,7 +7,10 @@ import {
filterJobsForIdentity,
filterRunsForJobs,
inferOwnerEmpNo,
isMultiUserIdentity,
jobVisibleToIdentity,
LOCAL_EMP_NO,
localIdentity,
migrateJobOwners,
UNASSIGNED_OWNER,
viewerPayload,
@ -73,6 +76,23 @@ test('viewerPayload', () => {
assert.equal(v.empNo, 'u1')
assert.equal(v.canViewAll, false)
assert.equal(v.workspacePath, '/w/u1')
assert.equal(v.mode, 'multi')
})
test('standalone local identity sees all jobs including unassigned', () => {
const local = localIdentity()
assert.equal(isMultiUserIdentity(local), false)
assert.equal(local.empNo, LOCAL_EMP_NO)
assert.equal(canViewAllJobs(local), true)
assert.equal(jobVisibleToIdentity({ ownerEmpNo: UNASSIGNED_OWNER }, local), true)
assert.equal(jobVisibleToIdentity({ ownerEmpNo: 'u2' }, local), true)
const payload = viewerPayload(local)
assert.equal(payload.mode, 'local')
assert.equal(payload.canViewAll, true)
assert.deepEqual(
filterJobsForIdentity([{ id: '1', ownerEmpNo: 'u1' }, { id: '2', ownerEmpNo: UNASSIGNED_OWNER }], local).map((j) => j.id),
['1', '2'],
)
})
test('claimUnassignedForViewer claims by session owner or user-workspaces cwd', () => {