diff --git a/ROADMAP.md b/ROADMAP.md index e2db5b3..53e072c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -93,7 +93,7 @@ and public [`ace-core==0.8.0`](https://pypi.org/project/ace-core/0.8.0/) package developer-preview release line. Atrium is a repository-delivered optional workspace rather than a second state or authority path. -### Active hardening — Atrium live onboarding binding +### Passed hardening — Atrium live onboarding binding The post-0.8 candidate closes one experience gap without creating a new product layer. Core now projects one inert onboarding profile plus the exact append-only Intelligence Builder session @@ -101,8 +101,10 @@ history through the same authenticated resource plane Atrium already consumes. A proposal, working, blocked, retrying, and complete states from those durable records and opens the first Brief only after `first_briefing_ready`. World AI supplies the paired domain profile and a four-agent `Connect -> Map -> Watch -> Brief` acceptance journey over admitted official-source -evidence. The work remains candidate until both repositories pass review and installed-artifact -reproduction; it does not change the 0.9 Collaborative Intelligence promise. +evidence. Core PR #145 and World PR #24 landed after repository, installed-artifact, browser, +naked-kernel, and package checks passed. The follow-on demo-polish packet adds a local-only replay +of an immutable domain resource page and replaces internal persistence language with user-facing +setup status. It does not change the 0.9 Collaborative Intelligence promise. The prior [0.7.0 GitHub Release](https://github.com/augmented-cognition-engine/core/releases/tag/v0.7.0) and [`ace-core==0.7.0`](https://pypi.org/project/ace-core/0.7.0/) package remain the passed diff --git a/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx b/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx index 23a78fd..a51238c 100644 --- a/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx +++ b/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx @@ -34,9 +34,7 @@ import { } from './onboardingModel' import { pageFreshness, productDisplayName } from './experienceModel' import { - EXPLICITLY_DEGRADED_RESOURCE_KINDS, groupResources, - kindLabel, type ResourceGroups, } from './intelligenceModel' import { ResourceCard } from './ResourceCard' @@ -384,9 +382,9 @@ export function IntelligenceOS() { {page?.state === 'degraded' && ( - ACE is showing the truth it can currently prove + Some evidence still needs review - {EXPLICITLY_DEGRADED_RESOURCE_KINDS.map(kindLabel).join(', ')} remain explicit gaps; available intelligence and provenance are still shown. + Some source-health, confidence, conflict, and revision checks are incomplete. Available intelligence and citations remain visible. )} @@ -403,11 +401,11 @@ export function IntelligenceOS() { Build your intelligence - - {session === null ? 'Proposal only' : `Live session · r${session.sequence}`} + + {session === null ? 'Proposal only' : `Live · step ${session.sequence}`}
@@ -218,13 +218,25 @@ export function OnboardingPreview({ <> {firstBriefReady ? 'Your first picture is ready' : session?.stage === 'blocked' ? 'ACE needs your attention' : session === null ? 'Your governed plan is ready' : 'Your first picture is assembling'} - {session === null ? 'These are proposed steps. No agent action is represented as complete until Core admits its durable session record.' : 'This status comes from the governed, append-only Intelligence Builder session—not UI animation.'} + + {session === null + ? 'Review the plan before ACE connects sources or starts watching.' + : firstBriefReady + ? 'ACE built this picture from the sources and watch settings you approved.' + : session.stage === 'blocked' + ? 'ACE paused safely before changing your intelligence picture.' + : 'ACE is assembling the picture from the sources and watch settings you approved.'} +
{lanes.map((lane) => )}
- {session === null ? 'Reviewing this plan grants no source, monitor, or activation authority.' : `${session.artifacts.length} exact builder artifact${session.artifacts.length === 1 ? '' : 's'} retained · session revision ${session.sequence}`} + {session === null + ? 'Reviewing this plan changes nothing until you approve it.' + : firstBriefReady + ? 'First cited Brief ready · Setup saved' + : `Setup saved · Step ${session.sequence}`}
)} diff --git a/core/ui/canvas/tests/e2e/atrium-domain-resource-page.spec.ts b/core/ui/canvas/tests/e2e/atrium-domain-resource-page.spec.ts index 7ff64a9..e50dd79 100644 --- a/core/ui/canvas/tests/e2e/atrium-domain-resource-page.spec.ts +++ b/core/ui/canvas/tests/e2e/atrium-domain-resource-page.spec.ts @@ -11,13 +11,6 @@ test('Atrium renders an external domain resource page without domain UI code', a items: Array<{ reference: { resource_kind: string }; title: string }> } - await page.route('**/auth/token', (route) => - route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ token: 'fixture-token' }) }), - ) - await page.route('**/v1/intelligence/resources/query', (route) => - route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(resourcePage) }), - ) - await page.goto('/atrium') const brief = resourcePage.items.find((item) => item.reference.resource_kind === 'brief') @@ -26,8 +19,24 @@ test('Atrium renders an external domain resource page without domain UI code', a if (brief !== undefined) { await expect(page.getByRole('button', { name: `Open ${brief.title}` })).toBeVisible() } + if ((resourcePage as { state?: string }).state === 'degraded') { + await expect(page.getByText('Some evidence still needs review')).toBeVisible() + } + await expect(page.getByText(`${resourcePage.items.length} cited records`)).toBeVisible() await page.screenshot({ path: testInfo.outputPath('atrium-domain-resource-page.png'), fullPage: true }) + const viewBuild = page.getByRole('button', { name: 'View build' }) + if (await viewBuild.isVisible()) { + await viewBuild.click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + await page.getByRole('button', { name: 'View live build' }).click() + await expect(page.getByRole('heading', { name: 'Your first picture is ready' })).toBeVisible() + await expect(page.getByText('ACE built this picture from the sources and watch settings you approved.')).toBeVisible() + await expect(page.getByText('First cited Brief ready · Setup saved')).toBeVisible() + await page.getByRole('button', { name: 'Open my first briefing' }).click() + } + await page.setViewportSize({ width: 390, height: 844 }) await page.goto('/atrium') await expect(page.getByRole('heading', { name: 'Intelligence' })).toBeVisible() diff --git a/core/ui/canvas/vite.config.ts b/core/ui/canvas/vite.config.ts index 0252c5a..d611e68 100644 --- a/core/ui/canvas/vite.config.ts +++ b/core/ui/canvas/vite.config.ts @@ -103,6 +103,63 @@ function serveExtensionStaticHtml(): Plugin { } } +/** + * Replay one immutable Intelligence resource page through Atrium during local demos. + * + * This is deliberately a Vite-only presentation seam. It does not create a store, + * admit evidence, grant authority, or exist in the production canvas host. A domain + * repository generates the page through Core's real resource projection, then points + * this local server at the resulting JSON artifact with ACE_ATRIUM_RESOURCE_PAGE. + */ +function serveAtriumResourceReplay(): Plugin { + const configuredPath = process.env.ACE_ATRIUM_RESOURCE_PAGE + if (configuredPath === undefined || configuredPath.trim() === '') { + return { name: 'ace:atrium-resource-replay-disabled' } + } + + const resourcePath = path.resolve(configuredPath) + let resourceBody: string + try { + const raw = readFileSync(resourcePath, 'utf-8') + if (raw.length > 10_000_000) throw new Error('resource page exceeds the 10 MB local replay limit') + const parsed = JSON.parse(raw) as { contract?: unknown; items?: unknown } + if ( + parsed.contract !== 'ace.intelligence.resource-plane-page/v1alpha1' || + !Array.isArray(parsed.items) + ) { + throw new Error('resource page does not match the public Intelligence page contract') + } + resourceBody = JSON.stringify(parsed) + } catch (error) { + throw new Error(`ACE_ATRIUM_RESOURCE_PAGE could not be loaded from ${resourcePath}: ${String(error)}`) + } + + return { + name: 'ace:atrium-resource-replay', + configureServer(server) { + server.config.logger.info(`Atrium is replaying ${resourcePath}`) + server.middlewares.use((req, res, next) => { + const [pathname] = (req.url ?? '').split('?') + if (req.method === 'POST' && pathname === '/auth/token') { + res.setHeader('Content-Type', 'application/json; charset=utf-8') + res.setHeader('Cache-Control', 'no-store') + res.statusCode = 200 + res.end(JSON.stringify({ token: 'atrium-local-replay' })) + return + } + if (req.method === 'POST' && pathname === '/v1/intelligence/resources/query') { + res.setHeader('Content-Type', 'application/json; charset=utf-8') + res.setHeader('Cache-Control', 'no-store') + res.statusCode = 200 + res.end(resourceBody) + return + } + next() + }) + }, + } +} + /** The kernel's own routes. An extension may not claim any of these (fail-closed). */ const kernelProxy: Record = Object.fromEntries( KERNEL_DEV_PROXY_ROUTES.map(([route, ws]) => [ @@ -116,7 +173,7 @@ const kernelProxy: Record = Object.fromEntries( ) export default defineConfig({ - plugins: [react(), tailwindcss(), serveExtensionStaticHtml()], + plugins: [serveAtriumResourceReplay(), react(), tailwindcss(), serveExtensionStaticHtml()], // Yjs constructor checks break if two copies of the module end up in the // bundle (one from `import * as Y from 'yjs'`, one from `y-websocket` // pulling its own pre-bundled copy). Force a single resolved instance. diff --git a/docs/evidence/atrium-live-onboarding-candidate-v1.md b/docs/evidence/atrium-live-onboarding-candidate-v1.md index f4d5c76..a88b611 100644 --- a/docs/evidence/atrium-live-onboarding-candidate-v1.md +++ b/docs/evidence/atrium-live-onboarding-candidate-v1.md @@ -32,5 +32,8 @@ corroboration claim. - the Atrium production build completes; - focused World tests reproduce the four-agent journey and exact 23-resource Atrium page. -Full repository, installed-wheel, accessibility, and paired-PR review gates remain required before -this candidate advances to passed. +The paired implementation landed through Core PR #145 and World PR #24 after the supported Core, +World, Canvas, naked-kernel, Docker, installed-wheel, and browser gates passed. Post-landing demo +hardening adds a Vite-only immutable-page replay seam and removes internal persistence vocabulary +from the leadership-facing journey. Live network freshness, general accessibility review, and +broader AI-area coverage remain separate follow-on gates.