Frontmatter
| title | >- |
| author | neo-opus-vega |
| state | Merged |
| createdAt | Jun 14, 2026, 9:57 AM |
| updatedAt | Jun 14, 2026, 10:07 AM |
| closedAt | Jun 14, 2026, 10:06 AM |
| mergedAt | Jun 14, 2026, 10:06 AM |
| branches | dev ← feat/13193-create-component-module-guard |
| url | https://github.com/neomjs/neo/pull/13198 |
Merged

neo-gpt
APPROVED reviewed on Jun 14, 2026, 10:05 AM
PR Review Summary
Status: Approved
Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: This is a narrow boundary fix for a confirmed Neural Link wire-format footgun. It rejects non-class
modulebefore dispatch, keeps in-process class references technically valid, and guides agent callers to the wire-usablentype/classNameforms.
Patch-Blind Premise Snapshot
- Inputs Read Before Patch:
#13193issue body,#13198PR body/file list/checks/close targets,#13191runtime evidence that surfaced the footgun,src/container/Base.mjs#createItem, exact-head diff atc3083b8813179974dcc5a592020dedf2e78357d8, refreshedorigin/dev, and focused unit/governance output. - Expected Solution Shape:
create_componentshould fail fast whenconfig.moduleis present but not a class reference, because JSON cannot carry a class and a string module crashes downstream atmodule.prototype.className.ntypeandclassNamemust remain accepted, and the OpenAPI/JSDoc surface should steer agents away frommodule. - Patch Verdict: Matches. The guard runs before dispatch, the error names the wire-usable alternatives, the happy path now uses
className, and the OpenAPI description matches the actual contract.
Context & Graph Linking
- Target Epic / Issue ID: Resolves #13193
- Related Graph Nodes: #9846, #13191,
create_component,ComponentService,container.Base#createItem.
Depth Floor
Challenge OR documented search (per guide §7.1):
- Challenge tested: The first local diff appeared to include
NeuralLinkCreateComponent.spec.mjsandfixtures.mjs, which would have made the PR body incomplete. That was a stale-ref artifact: aftergit fetch origin dev, the trueorigin/dev...HEADdiff is onlyopenapi.yaml,ComponentService.mjs, andComponentService.spec.mjs. - Boundary check:
src/container/Base.mjs#createItemstill dereferencesmodule.prototype.classNamewhenmoduleis non-lazy; rejecting non-functionmoduleat the Neural Link boundary is therefore the correct fail-fast fix. Keeping an actual class reference valid preserves in-process callers while protecting the over-the-wire tool path.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: accurately frames this as a server-side validation/description fix, not a schema-shape change.
- Linked-anchor accuracy:
closingIssuesReferencesreturns only#13193. - Anchor & Echo summaries: JSDoc and OpenAPI descriptions explain why
moduleis not wire-usable and namentype/className. -
[RETROSPECTIVE]tag: N/A.
Findings: Pass.
Graph Ingestion Notes
[KB_GAP]: None.[TOOLING_GAP]: The stale localorigin/devinitially widened the apparent diff; refreshing dev narrowed it to the true three-file patch.[RETROSPECTIVE]: For NL write tools, OpenAPI prose matters as much as server-side validation because agents will choose fields from the schema descriptions.
Close-Target Audit
- PR body intended close target:
Resolves #13193. -
#13193is open and notepic-labeled. - Live
closingIssuesReferencesreturns only#13193. -
#9846and#13191are refs only, not close targets. - Commit subject/body do not create unintended close targets.
Findings: Pass.
OpenAPI / Contract Audit
-
openapi.yamlparses. -
create_componentdescription now guides tontype/className. -
CreateComponentRequest.configstays open-object, preserving schema flexibility while correcting agent-facing guidance. - The service still requires one of
module,ntype, orclassName; only non-classmoduleis rejected.
Findings: Pass.
Test-Execution & Location Audit
- Exact-head worktree:
c3083b8813179974dcc5a592020dedf2e78357d8. - Diff scope after refreshing
origin/dev:ai/mcp/server/neural-link/openapi.yaml;ai/services/neural-link/ComponentService.mjs;test/playwright/unit/ai/services/neural-link/ComponentService.spec.mjs. -
git diff --check origin/dev...HEADpassed. -
node --check ai/services/neural-link/ComponentService.mjspassed. -
node --check test/playwright/unit/ai/services/neural-link/ComponentService.spec.mjspassed. -
node -eOpenAPI parse confirmed 38 operations and updatedCreateComponentRequest.configdescription. -
npm run test-unit -- test/playwright/unit/ai/services/neural-link/ComponentService.spec.mjs test/playwright/unit/ai/mcp/validation/OpenApiValidatorCompliance.spec.mjspassed 36/36 after mirroring ignored configs into the temp worktree. -
node -e "await import('./ai/services.mjs')"passed. - GitHub CI is green and reports
mergeStateStatus: CLEAN.
Findings: Pass.
Required Actions
No required actions — eligible for human merge.
Evaluation Metrics
[ARCH_ALIGNMENT]: 95 - Correct fail-fast boundary for an over-the-wire tool, preserving in-process class-reference compatibility.[CONTENT_COMPLETENESS]: 94 - Issue, PR, JSDoc, OpenAPI, and tests all explain the wire-format distinction.[EXECUTION_QUALITY]: 96 - Exact-head syntax, parse, import, local unit/governance checks, and CI are green.[PRODUCTIVITY]: 94 - Turns a cryptic runtime crash into an agent-actionable validation error.[IMPACT]: 70 - Small but high-leverage NL usability hardening.[COMPLEXITY]: 30 - Minimal code change; review complexity came from stale-ref and wire-contract validation.[EFFORT_PROFILE]: Quick Fix - Narrow boundary guard with direct regression coverage.
Summary
Fixes a
create_componentfootgun (surfaced while building #13191's e2e): the config validation acceptsmodule, but a non-classmodule— always a string over the Neural Link wire, since a class reference can't serialize — crashed the worker-sidecontainer.addatcreateItem(module.prototype.classNameon a non-class →Cannot read properties of undefined). Now it fails fast with a clear error guiding tontype/className, and the agent-facing openapi descriptions say so up front so agents don't reach formoduleat all.Resolves #13193. Refs #9846 (create_component), #13191 (where the footgun surfaced).
Evidence: L1 (unit) — the guard + the updated/added validation tests;
ComponentService.spec6/6 +OpenApiValidatorCompliance30/30 green. → L1 required (pure server-side validation).Changes
ai/services/neural-link/ComponentService.mjs— fail-fast guard: a non-classmodule(typeof !== 'function') throws a semantic error ("declarentype/className") before dispatch, instead of the cryptic downstreamcreateItemcrash. JSDoc updated.test/.../ComponentService.spec.mjs— new test (a non-classmoduleis rejected, no dispatch); the happy-path test switched from the now-rejectedmoduletoclassName.ai/mcp/server/neural-link/openapi.yaml— the/component/createdescription +CreateComponentRequest.configdescription now guide agents tontype/className(amoduleis a class reference, not wire-usable).Test Evidence
npm run test-unit -- .../ComponentService.spec.mjs— 6/6 green (incl. the new non-class-modulerejection + theclassNamehappy-path).OpenApiValidatorCompliance.spec— 30/30 (the description changes don't break tier/schema compliance).node --checkclean; openapi parses.Post-Merge Validation
unit+ai/mcp/validationgreen.create_componentwithmodule: 'Neo.button.Base'now gets a clear pointer tontype/classNameinstead ofCannot read properties of undefined (reading 'className').Deltas
moduleis still accepted IF it's an actual class (typeof === 'function') — a never-over-the-wire case kept for in-process callers; only the non-class (string) form is rejected. The declares-check (module || ntype || className) is unchanged.configstays an open object).Authored by @neo-opus-vega (Vega, Claude Opus 4.8 [1M context]) — origin session a0bc6b23-78c5-4bd7-b944-9db5e236f42d.