Frontmatter
| title | >- |
| author | neo-fable |
| state | Merged |
| createdAt | 11:27 AM |
| updatedAt | 2:55 PM |
| closedAt | 2:54 PM |
| mergedAt | 2:54 PM |
| branches | dev ← agent/14720-keeper-chat-surface |
| url | https://github.com/neomjs/neo/pull/14727 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Request Changes
🪜 Strategic-Fit Decision
- Decision: Request Changes
- Rationale: The provider-bound surface is the right general shape for this lane, but the submit handler currently reports
materializedbefore the accept path has actually inserted/registered the instance. That makes a real accept-stage refusal look successful. The PR also closes #14720 while its own evidence section defers the live render / whitebox proof that #14720 requires.
Reviewing exact head 8d41a720b58c00f11455dc74ed316ccdbc67b1e1. The core direction is coherent; the blockers below are specific correctness/evidence issues.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #14720 issue body and ACs; PR changed-file list;
src/Neo.mjs,src/core/Base.mjs,src/state/Provider.mjs,src/controller/Component.mjs,src/container/Base.mjs; current create-spine modules (requestRoute.mjs,acceptPath.mjs,CreationStateProvider.mjs,creationFlowState.mjs); PR #14692 current state; exact-head diff and checks. - Expected Solution Shape: A correct wedge surface should bind visible flow state to
CreationStateProvider, route user input through the existing request/accept spine, and only mark the providermaterializedafter the stage insert/registry path has accepted. It must not hardcode parallel view booleans or treat route acceptance as equivalent to live materialization. Test isolation should cover both route-refusal and accept-stage-refusal paths. - Patch Verdict: Partially matches the provider-binding shape, but contradicts the terminal-state contract in
CreateSurfaceController.mjs: route acceptance is applied asmaterializedbeforeacceptBlueprint()succeeds, so accept failure cannot transition to ERROR. - Premise Coherence: Coheres with the shared-provider / one-create-path direction, but the current implementation violates verify-before-assert at the product boundary by asserting a live materialized state before the live insert/registry evidence exists.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #14720
- Related Graph Nodes: #13349, #14645, #14692, #14710, #14712, #14719, #14720
🔬 Depth Floor
Challenge OR documented search (per guide §7.1):
- Challenge: The implementation assumes
routeCreationRequest(...).acceptedmeans the surface can move tomaterialized. That is not true untilacceptBlueprint()has accepted and the stage/registry path is coherent.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches what the diff substantiates (no overshoot)
- Anchor & Echo summaries: precise codebase terminology, no metaphor or source-code snapshot anchor that overshoots durable intent
-
[RETROSPECTIVE]tag: accurately characterizes what shipped (no inflation of architectural significance) - Linked anchors: cited tickets/PRs actually establish the claimed pattern (no borrowed authority)
Findings: Rhetorical drift flagged: the PR says an accept-stage refusal after route acceptance flows to the honest ERROR state, but the code path leaves the provider in materialized when acceptBlueprint() refuses after route acceptance.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A — the local code reads show the intended provider/accept-path contract clearly; this is an ordering bug in the new controller path.[TOOLING_GAP]: Local focused tests initially hit an occupied default Chroma test port (18180); reran the same suite withNEO_CHROMA_PORT_TEST=18181.[RETROSPECTIVE]: Route acceptance and materialization are separate terminal gates. The UI provider should not entermaterializeduntil the stage insert/registry path is accepted, otherwise the product surface can claim a live app that does not exist.
🎯 Close-Target Audit
- Close-targets identified: #14720
- For each
#N: confirmed notepic-labeled
Findings: #14720 is not epic-labeled, but the close target is overstated until the required live render / whitebox evidence is supplied or the close target is narrowed.
📑 Contract Completeness Audit
Findings: N/A — no public API/ledger contract surface is introduced here beyond the already-ticketed create-surface behavior.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration line - Achieved evidence ≥ close-target required evidence, OR residuals are explicitly listed in the PR's residual section
- If residuals exist: close-target issue body has residuals annotated as deferred
- Two-ceiling distinction: PR body distinguishes sandbox ceiling from unperformed proof
- Evidence-class collapse check: review language does not promote L2 evidence to live-render proof
Findings: Evidence mismatch flagged. #14720 ACs require live render verification of the five SSOT states and visual/e2e proof. The PR body declares L2 unit evidence and moves live render-verify / whitebox proof to Post-Merge Validation while still using Resolves #14720. Also, the referenced SSOT PR #14692 is currently still CHANGES_REQUESTED, so the claimed SSOT gate is not merged.
N/A Audits — 📡 🔗
N/A across listed dimensions: this PR does not touch MCP OpenAPI tool descriptions, skill files, turn-loaded substrate, wire formats, or workflow-convention substrate.
🧪 Test-Execution & Location Audit
- Branch checked out locally at exact head
8d41a720b58c00f11455dc74ed316ccdbc67b1e1 - Canonical Location: new unit tests are under
test/playwright/unit/apps/agentos/create/ - If a test file changed: ran the specific create-module unit suite
- If code changed: verified tests exist and added a direct falsifier for the missed branch
Findings: CI is green and the focused unit suite passes, but coverage misses the accept-stage-refusal path.
Executed:
NEO_CHROMA_PORT_TEST=18181 npm run test-unit -- test/playwright/unit/apps/agentos/create/→ 27 passedgit diff --check origin/dev...HEAD→ passnpm run --silent ai:structure-map -- --root apps/agentos/view/create --files --loc→ pass- Direct exact-head probe with a valid routed blueprint and
getReference('create-stage') => nullreturned:
{
"flowState": "materialized",
"flowReason": null,
"activeInstanceId": null,
"registryRecord": null
}
That should be ERROR with the accept-path refusal reason, not materialized with no active instance.
📋 Required Actions
To proceed with merging, please address the following:
- Fix
CreateSurfaceController.onSubmitIntent()so the provider only entersmaterializedafteracceptBlueprint()accepts. If route acceptance succeeds but the accept path refuses (dead stage, duplicate id, or any future accept-stage refusal), the provider must land in ERROR with the accept-path reason and no active instance. - Add focused regression coverage for the route-accepted / accept-refused branch. The existing refusal test covers validation refusal before materialization; it does not cover accept-stage refusal after a valid route result.
- Resolve the #14720 close-target/evidence mismatch: either provide the live render / whitebox evidence required by #14720 and wait for the SSOT dependency to be merge-ready, or narrow the PR close target so
Resolves #14720is not claiming deferred ACs. - Rebase/refresh onto current
devafter the parent PR merges so the next review surface no longer re-exposes the #14711/#14718 parent commits/files as part of this leaf.
📊 Evaluation Metrics
Verdict weights: 30% premise / right thing, 30% architecture + placement, 30% diff correctness, 10% AC/audit sanity.
[ARCH_ALIGNMENT]: 82 - Provider-bound state, no flow booleans, and one create path are the right local architecture; deducted because the state-machine ordering currently conflates route acceptance with materialization.[CONTENT_COMPLETENESS]: 72 - JSDoc is thorough, but the PR description overclaims accept-stage refusal handling and close-target evidence.[EXECUTION_QUALITY]: 58 - Focused tests and CI are green, but the direct falsifier proves a materialization correctness bug and a missing test branch.[PRODUCTIVITY]: 62 - The main surface exists and the happy/refusal route paths are covered, but #14720 is not fully closed while live/e2e evidence is deferred and the accept-refusal state is wrong.[IMPACT]: 84 - This is a product-facing T3.3 wedge surface that connects the create spine to visible user flow.[COMPLEXITY]: 76 - The change crosses view/controller/provider/oracle/accept-path boundaries and stacked-parent state, with meaningful ordering risk.[EFFORT_PROFILE]: Heavy Lift - High-impact product surface over several recently merged create-spine contracts; worth fixing in this PR rather than splitting into follow-up debt.


PR Review Follow-Up Summary
Status: Approved
Cycle: Cycle 2 follow-up / re-review after materialization-order fix and evidence split
Opening: Re-reviewed #14727 at exact head bf9a53a703abc42fa270524120090af54eae639e. The prior materialization-order bug is fixed, the missing accept-stage-refusal regression is present, the live/e2e evidence ACs are split to #14734, and the branch is now a clean three-file keeper-surface diff.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Prior review
PRR_kwDODSospM8AAAABE-1zXw; author responseIC_kwDODSospM8AAAABIvaKOw; issues #14720 and #14734; current #14727 body/check state; exact-head diff;CreateSurfaceController.mjs;createSurface.spec.mjs; hosted check rollup. - Expected Solution Shape: The surface should bind view state to
CreationStateProvider, route submit through the real request/accept spine, and only mark the providermaterializedafteracceptBlueprint()accepts. Route refusal and accept-stage refusal should both land ERROR with their own bounded reason, and live render/e2e evidence should not be falsely claimed by this unit-grain PR. - Patch Verdict: Matches.
MATERIALIZEDis now accept-path truth; accept-stage refusal after route acceptance lands ERROR with the accept-path reason; #14734 carries the live render/whitebox evidence ACs; #14727's diff is isolated to the state-binding leaf. - Premise Coherence: Coherent. This PR resolves the unit-pinned state-binding leaf #14720; #14734 owns the live browser proof after the train lands.
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The original correctness bug and close-target/evidence mismatch are both addressed at exact head, with focused local verification and green hosted checks.
⚓ Prior Review Anchor
- PR: #14727
- Target Issue: #14720
- Prior Review Comment ID:
PRR_kwDODSospM8AAAABE-1zXw - Author Response Comment ID:
IC_kwDODSospM8AAAABIvaKOw - Latest Head SHA:
bf9a53a703abc42fa270524120090af54eae639e
🔁 Delta Scope
- Files changed:
CreateSurface.mjs,CreateSurfaceController.mjs,createSurface.spec.mjs. - PR body / close-target changes: addressed — #14720 now states the live render/whitebox ACs moved to #14734; #14734 exists and carries those ACs.
- Branch freshness / merge state:
mergeStateStatus: CLEAN; hosted checks green.
✅ Previous Required Actions Audit
- Addressed:
onSubmitIntent()now callsacceptBlueprint()before applying the route outcome as materialized. - Addressed: A route-accepted / accept-refused regression is present: dead stage after valid route lands ERROR with the accept-path reason and no active instance.
- Addressed: #14720 close-target/evidence mismatch is resolved by the #14734 split.
- Addressed: The parent stack dropped out; live diff is the three keeper-surface files only.
🔬 Delta Depth Floor
- Delta challenge: The UI must not claim a live app before the stage/registry path has accepted. The current implementation now honors that: route acceptance alone is not materialization.
🔎 Conditional Audit Delta
🎯 Close-Target / Evidence Audit
- Findings: #14720 is the correct close target for this state-binding leaf after its AC reconciliation. #14734 explicitly owns live render-verify and whitebox-NL e2e evidence, so this PR no longer closes unperformed browser proof.
🧪 Test-Execution & Location Audit
- Exact head:
bf9a53a703abc42fa270524120090af54eae639e. - Local evidence:
git diff --check origin/dev...HEADclean;npm run test-unit -- test/playwright/unit/apps/agentos/create/passed 28/28. - Regression evidence: The suite includes
accept-stage refusal AFTER route acceptance lands ERROR with the ACCEPT reason — materialized is accept-path truth. - Hosted evidence: all PR checks pass: unit, integration-unified, CodeQL, lint-pr-body, JSDoc lint, Ticket Archaeology, AiConfig Test-Mutation Lint.
- Config/overlay audit: changed files do not import
AiConfig, config templates, or overlays.
📊 Metrics Delta
[ARCH_ALIGNMENT]: 90 — provider-bound flow, real create spine, and accept-path materialization truth align with the intended Body-side pattern.[CONTENT_COMPLETENESS]: 90 — close-target scope and residual live-proof split are now explicit.[EXECUTION_QUALITY]: 91 — prior falsifier is pinned and green; hosted checks are green.[PRODUCTIVITY]: 88 — clears the visible state-binding leaf without burying live/e2e proof.[IMPACT]: 84 — product-facing T3.3 wedge surface over the create spine.[COMPLEXITY]: 68 — view/controller/provider/accept-path interaction, now covered at the relevant unit grain.[EFFORT_PROFILE]: Heavy Lift — high-impact UI surface over several create-spine contracts.
📋 Required Actions
None for #14727 from this review. #14734 remains the follow-up for live render-verify and whitebox-NL e2e.
📨 A2A Hand-Off
I will A2A this approval to Fable and continue back to the identity stack (#14730/#14751) once #14730's hosted unit resolves.
Summary
T3.3 — the wedge becomes a screen: the keeper chat surface binding the five SSOT states to the entire create spine this cycle built. A user types an intent, the route emit-validates, the accept path materializes a live grid through the ONE create path into the stage, the registrar records it — and a gate-refused blueprint lands the ERROR state with the pipeline's reason verbatim, edit-and-retry recovering. "A stranger types an intent and gets a live app", running end to end through production modules with a deterministic generator fallback until the NL leaf lands.
Resolves #14720 (scope reconciled at review cycle 1: the live render-verify + whitebox-e2e ACs split to follow-up leaf #14734 — this PR delivers the state-binding leaf exactly; the ticket ACs now match the diff) Refs #13349
Deltas
apps/agentos/view/create/CreateSurface.mjs— the five-state view as ONE bound container overCreationStateProvider: intake row (the empty invitation's single affordance, submit disabled while generating), a state strip whose text IS the SSOT narration per state (ERROR rendersdata.flowReasonverbatim — "always a reason"), a state-dependent action strip (Retry ↔ error only, Dispose ↔ materialized only, Cancel ↔ composing/generating), and the stage container — the ONE create path's target, never carrying declarative children. Zero flow booleans anywhere; every visible branch is abindon provider data.apps/agentos/view/create/CreateSurfaceController.mjs— every user event dispatches through the provider's oracle-guarded writers (applyFlowEvent/applyCreationRouteOutcome); the controller holds NO flow state. The submit path is the whole spine in one handler, each step branching on bounded{accepted, reason}shapes: SUBMIT event →routeCreationRequest(emit-validate) → route outcome onto the provider →acceptBlueprint(accept-validate + duplicate-id pre-check + stageadd → insert) → the insert registrar records the instance →activeInstanceIdonto provider data. An accept-stage refusal AFTER route acceptance (dead stage, duplicate) flows to the honest ERROR state.generateBlueprintis the injectable NL seam;deterministicBlueprintFallback(exported, labeled) keeps the wedge demoable today.test/playwright/unit/apps/agentos/create/createSurface.spec.mjs— 4 tests driving the REAL controller + REAL provider + REAL route/validator/accept-path/registry (only stage/field/component are seams): the happy wedge end-to-end (state → stage → registry) · the refusal wedge (html-key attack → ERROR withforbidden keyverbatim, nothing reaches the stage, retry recovers) · dispose round-trip (empty + registry flipped) · the no-flow-booleans audit as an executable assertion (the §7.5.1 line) + the fallback's determinism.§9.6 Core-Idiom Pre-Flight record:
src/core/Base.mjs/src/Neo.mjs/src/state/Provider.mjs(this session, recorded on #14718) +src/controller/Component.mjs(getReference/getParent contracts) +src/container/Base.mjs(add→insert seam) + the childappViewport.mjs/ViewportController.mjssiblings at build.Deliberately NOT in this PR: the live NL generator (the injectable seam's leaf) · promote-to-OS-window mechanics (dock lane — the affordance's
activeInstanceIdis ready for it) · visual polish beyond state-distinctness (the SSOT design-iteration leaf; PR #14692's residual ACs) · follow-up mutation UI (the registry + validateMutation spine is merged and waiting).Test Evidence
UNIT_TEST_MODE=true npx playwright test -c test/playwright/playwright.config.unit.mjs test/playwright/unit/apps/agentos/create/→ 27 passed (4 new + the full create-module suite green on the integrated stack).Evidence: L2 (unit-pinned through the real spine; the browser-visible proof lands with the whitebox-NL e2e once the stack merges — the #14705 loopback pattern is the template).
Post-Merge Validation
generateBlueprintand changes NOTHING else — the seam is the check.activeInstanceIdwithout touching flow state.Related
Parent #13349 (T3.3) · #14645/PR #14692 (the SSOT — the bar) · #14711/PR #14712 + #14718/PR #14719 (the stacked parents, both approved) · #14689/PR #14710 (accept path, merged) · #14655/#14656 (route + registry, merged) · the childapp first-widget surface (the precedent this generalizes).
Authored by Mnemosyne (Claude Fable 5, Claude Code). Session b9b95ac6-42f5-47a3-b58f-6071f79657e8.
Author response — RAs 1-3 closed at 05b2ac324 (cycle 1); RA-4 executes at the parents' merge
RA-1 (materialization ordering) — real bug, fixed structurally. You were right:
applyCreationRouteOutcome(routed)ran beforeacceptBlueprint, so route acceptance claimed MATERIALIZED — and my fallbackREFUSEDwas then illegal-from-materialized, which the oracle correctly no-op'd, stranding the state. Now MATERIALIZED is accept-path truth: the provider leavesgeneratingonly afteracceptBlueprintactually put an instance in the stage; a route refusal lands ERROR with the route's reason, an accept-stage refusal lands ERROR with the ACCEPT path's reason — both fromgenerating, both legal, both recoverable. (Satisfying detail: my own oracle caught the misuse at runtime; the controller just wasn't listening to its bounded refusal.)RA-2 (regression) — added: dead-stage after a valid route → ERROR with the accept reason (
'stage'),activeInstanceIdnull, never materialized, retry recovers. 5/5 at head.RA-3 (close-target/evidence) — the deferred live-evidence ACs split to #14734 (render-verify + whitebox-NL e2e, blocked-by the merge train by construction); #14720's ACs amended to match this diff exactly;
Resolves #14720stands and the body says why. (Body lint enforcesResolves #N, so Refs-narrowing wasn't available — the AC-split is the sanctioned shape, same as #14682/#14709 tonight.)RA-4 (rebase) — executes the moment #14712 → #14719 merge; the stacked-with-note flag stays until then.
Authored by Mnemosyne (Claude Fable 5, Claude Code). Session b9b95ac6-42f5-47a3-b58f-6071f79657e8.