import assert from 'node:assert/strict' import { describe, it } from 'node:test' import { FIXTURE_DIDS, fixtureSpace } from '../dist/fixture.js' import { materialize } from '../dist/materializer.js' import { MemoryRecordStore } from '../dist/store.js' import { allUnits, durabilityOf, staleness, tally, timeline } from '../dist/timeline.js' import { validateRecord } from '../dist/validation.js' const fixture = fixtureSpace() function index(records = fixture.records) { const store = new MemoryRecordStore() records.forEach((record) => store.put(record)) return materialize(store, { spaceUri: fixture.spaceUri, asOf: fixture.asOf }) } describe('the comp, as records', () => { it('is expressible in the lexicon, record for record', () => { // The point of porting the mock rather than mocking the port: a shape the comp draws that the // protocol cannot carry fails here rather than in the UI. for (const record of fixture.records) { const result = validateRecord(record.collection, record.value) assert.equal( result.success, true, `${record.uri}: ${result.success ? '' : result.issues.map((i) => `${i.path} ${i.message}`).join(', ')}`, ) } }) it('folds with nothing ignored and nothing silently edited', () => { const view = index() // Every record resolves into the space. An entry here means the comp drew something the fold // does not accept — a dangling ref, a non-member author, a request that anchors nowhere. assert.deepEqual(view.ignored, []) // The one in-place rewrite is the legacy goal closure, and a sanctioned closure is not flagged. assert.deepEqual(view.edits, []) assert.equal(view.members.length, 6) assert.equal(view.goals.length, 11) assert.equal(view.projects.length, 2) // Three humans, and only two of them have ever opened the space. The third is granted-and-absent, // which is the state a space is in the moment after somebody is added — the members list draws it, // so the fixture has to contain it. A `join` is the record that distinguishes the two. assert.deepEqual(view.joins.map((join) => join.did), [FIXTURE_DIDS.ana]) }) it('offers the registry the ⊕ menu is generated from, custom type and all', () => { const view = index() assert.deepEqual( view.artifactTypes.map((type) => [type.value.name, type.value.scope]), [ ['plan', 'goal'], ['implementation', 'goal'], ['adr', 'project'], ['architecture', 'project'], ['conventions', 'project'], ['security-review', 'goal'], ], ) // Living vs append-only is registry data, not a rule about the name "adr". assert.equal(durabilityOf(view, 'adr'), 'append-only') assert.equal(durabilityOf(view, 'architecture'), 'living') assert.equal(durabilityOf(view, 'conventions'), 'living') assert.equal(durabilityOf(view, 'plan'), 'living') }) it('gives the assignee picker the agents and the types each will accept', () => { const view = index() assert.deepEqual( view.agents.map((agent) => [agent.value.handleName, agent.value.artifactTypes]), [ ['planner.radial.bot', ['plan', 'adr', 'architecture', 'conventions']], ['builder.radial.bot', ['implementation']], // `answer` sits beside `review`: both are built-ins of the turn layer, and an agent has to // publish one to be offerable for it. ['reviewer.radial.bot', ['review', 'security-review', 'answer']], ], ) }) it('draws the headline goal exactly as the comp does', () => { const view = index() const units = timeline(view, fixture.goals.reviewQueue) assert.deepEqual( units.map((unit) => [unit.type, unit.state]), [ ['plan', 'judged'], ['implementation', 'assigned'], ['security-review', 'open'], ], ) const [plan, implementation, security] = units // One plan unit on its second version, with the verdict pinned per version: v1 was sent back // with three findings, v2 was approved. The row shows v2's verdict. assert.deepEqual(plan.versions.map((version) => version.version), [1, 2]) assert.deepEqual(plan.versions.map((version) => version.verdict), ['request_changes', 'approve']) assert.equal(plan.verdict, 'approve') assert.deepEqual( plan.versions[0].reviews[0].value.findings.map((finding) => finding.severity), ['error', 'warning', 'info'], ) assert.equal(plan.versions[0].reviews[0].value.findings[0].line, 41) assert.equal(plan.versions[1].artifact.value.criteria.length, 5) // The comp draws a live lease on this row. Claims arbitrate unassigned requests only, and the // index cannot know whether a container is up — so the honest state is "with builder". assert.equal(implementation.assignee, 'did:plc:z9k1cft6yb8m') assert.equal(implementation.claim, undefined) assert.equal(implementation.versions.length, 0) // The custom type an admin added this week is a request like any other. assert.equal(security.type, 'security-review') assert.equal(security.assignee, undefined) assert.deepEqual(security.openRequest.value.basedOn, [ { uri: plan.current.artifact.uri, cid: plan.current.artifact.cid }, ]) // The goal thread is five messages, oldest first: two standalone, then one exchange — a human's // reply asking something, and the agent's answer to it, which an `answer` request commissioned — // and then a second question that is still with the agent. const goalView = view.goals.find((candidate) => candidate.target.uri === fixture.goals.reviewQueue) assert.equal(goalView.messages.length, 5) const reply = goalView.messages[3] assert.ok(reply.value.parent, 'the answered question is threaded under what it answers') assert.ok(reply.value.re, 'and it names the request that asked for it') // The first answer request is fulfilled by that reply, and the second is not — an agent is // writing it now. Both are `answer` requests naming one exact message. const answers = goalView.requests.filter((request) => request.value.type === 'answer') const openAnswers = goalView.openRequests.filter((request) => request.value.type === 'answer') assert.equal(answers.length, 2) assert.deepEqual( openAnswers.map((request) => request.value.assignee), ['did:plc:r3d5xnp0uq7w'], ) // …and a conversation is never a unit, answered or not: the goal's rows are the three above and // no more. assert.equal(units.length, 3) }) it('carries both flavours of a verdict owed', () => { const view = index() const units = timeline(view, fixture.goals.ignored) assert.deepEqual( units.map((unit) => [unit.type, unit.state, unit.needsVerdict]), [ ['plan', 'landed', true], ['implementation', 'landed', true], ], ) const [plan, implementation] = units // Nobody was asked about the plan. Landing is not done, so it is owed a verdict anyway. assert.deepEqual(plan.current.reviewRequests, []) // The implementation was put in front of two readers: the daemon's own reviewer the moment it // landed (auto-review is on for `implementation` here), and then a named human. A review request // is a verdict owed on one exact version, not a unit — it hangs off the version it pins, so two // of them add no rows at all. assert.equal(units.length, 2) assert.deepEqual( implementation.current.reviewRequests.map((request) => request.value.assignee), ['did:plc:r3d5xnp0uq7w', 'did:plc:qv7hjr2mzk4x'], ) for (const owed of implementation.current.reviewRequests) { assert.deepEqual(owed.value.subject, { uri: implementation.current.artifact.uri, cid: implementation.current.artifact.cid, }) } }) it('blocks the capture goal on an unanswered question', () => { const view = index() const [unit] = timeline(view, fixture.goals.capture) assert.equal(unit.state, 'awaiting') const goalView = view.goals.find((candidate) => candidate.target.uri === fixture.goals.capture) assert.deepEqual(goalView.awaitingInput, [unit.openRequest.uri]) }) it('shows a merged implementation with its checks and PR', () => { const view = index() const units = timeline(view, fixture.goals.possession) const implementation = units.find((unit) => unit.type === 'implementation') assert.equal(implementation.state, 'merged') assert.equal(implementation.current.mergedAt, '2026-07-19T15:20:00Z') assert.equal(implementation.current.artifact.value.links.pr, 'https://github.com/disnet/radial-ng/pull/241') assert.deepEqual( implementation.current.checkruns[0].value.results.map((result) => [result.name, result.pass]), [ ['pnpm test', true], ['pnpm lint', true], ['pnpm typecheck', true], ], ) assert.equal(implementation.verdict, 'approve') }) it('reads an unassigned claimed request as the one genuinely-in-flight unit', () => { const view = index() const [unit] = timeline(view, fixture.goals.onboarding) assert.equal(unit.state, 'claimed') assert.equal(unit.assignee, undefined) assert.equal(unit.claim.did, 'did:plc:h4n8wsq3vd2p') // The lease is live at the instant the comp is drawn, and lapses after it. const store = new MemoryRecordStore() fixture.records.forEach((record) => store.put(record)) const later = materialize(store, { spaceUri: fixture.spaceUri, asOf: '2026-07-25T15:00:00Z' }) assert.equal(timeline(later, fixture.goals.onboarding)[0].state, 'open') }) it('ends five goals, one for each way a goal can end', () => { const view = index() const ended = view.goals.filter((goal) => goal.ended) assert.deepEqual( ended.map((goal) => [goal.target.uri, goal.disposition]).sort(), [ // A standalone `closeGoal`, written by a member who is not the goal's author. [fixture.goals.autoReview, 'completed'], // The legacy owner edit, read as `completed` — the word the old Close button meant. [fixture.goals.permutation, 'completed'], [fixture.goals.budgets, 'dropped'], [fixture.goals.diffViewer, 'superseded'], // The legacy `archiveGoal`, read as `parked`. [fixture.goals.permissioned, 'parked'], ].sort(), ) // Every ending but the legacy edit carries the instant it happened — the Logbook sorts on it. const withInstant = ended.filter((goal) => goal.endedAt !== undefined).map((goal) => goal.target.uri) assert.deepEqual(withInstant.includes(fixture.goals.permutation), false) assert.equal(withInstant.length, 4) // The two with work under them are fully judged, which is what fills their pies. for (const uri of [fixture.goals.autoReview, fixture.goals.permutation]) { const counted = tally(timeline(view, uri)) assert.equal(counted.judged, counted.total) } }) it('lists each system document once, on one chain', () => { const view = index() const units = timeline(view, fixture.projects.radialNg) const project = view.projects.find((candidate) => candidate.target.uri === fixture.projects.radialNg) // Six documents, not the nine artifacts they are made of. Before `prev` was generalized this // would have been nine rows — the §3.2 bug the comp surfaced. assert.equal(units.length, 6) // Oldest first, by the request that started each document. assert.deepEqual( units.map((unit) => [unit.type, unit.versions.length]), [ ['adr', 2], // 0004 (Jun 12), superseded by 0008 — one row, two entries ['architecture', 3], // Jun 24 ['adr', 1], // 0005 (Jun 28) ['adr', 1], // 0006 (Jul 02) ['conventions', 2], // Jul 05 ['adr', 1], // 0007 (Jul 19), captured from the possession plan ], ) assert.equal(project.currentSystemArtifacts.length, 6) const architecture = units.find((unit) => unit.type === 'architecture') assert.match(architecture.current.artifact.value.body, /four moving parts/) // The living/append-only split the System view groups on. const living = units.filter((unit) => durabilityOf(view, unit.type) === 'living') assert.deepEqual(living.map((unit) => unit.type).sort(), ['architecture', 'conventions']) // Drift, computed from state the index already holds: three implementations merged since the // current architecture landed. An append-only record is superseded, never revised, so it is 0. assert.equal(staleness(view, fixture.projects.radialNg, architecture), 3) for (const adr of units.filter((unit) => unit.type === 'adr')) { assert.equal(staleness(view, fixture.projects.radialNg, adr), 0) } }) it('fills the smart lists the rail counts', () => { const view = index() const units = allUnits(view) const inFlight = units.filter((unit) => unit.state === 'claimed') const awaiting = units.filter((unit) => unit.state === 'awaiting') const forMe = units.filter((unit) => unit.needsVerdict || unit.state === 'awaiting') assert.equal(inFlight.length, 1) assert.equal(awaiting.length, 1) assert.equal(forMe.length > 0, true) // Everything landed on the two closed goals was judged, so neither shows up as owing a verdict. assert.equal( forMe.some((unit) => unit.target === fixture.goals.autoReview || unit.target === fixture.goals.permutation), false, ) }) it('converges under 100 deterministic arrival permutations', () => { const expected = allUnits(index()) let state = 7 const random = () => { state ^= state << 13 state ^= state >>> 17 state ^= state << 5 return state >>> 0 } for (let round = 0; round < 100; round += 1) { const shuffled = [...fixture.records] for (let position = shuffled.length - 1; position > 0; position -= 1) { const target = random() % (position + 1) ;[shuffled[position], shuffled[target]] = [shuffled[target], shuffled[position]] } assert.deepEqual(allUnits(index(shuffled)), expected, `round ${round}`) } }) })