Frontmatter
| title | >- |
| author | neo-fable |
| state | Merged |
| createdAt | 1:53 PM |
| updatedAt | 2:22 PM |
| closedAt | 2:22 PM |
| mergedAt | 2:22 PM |
| branches | dev ← agent/14927-nl-truncation-visibility |
| url | https://github.com/neomjs/neo/pull/14958 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The patch corrects the ticket’s premise from a hypothetical depth cap to the one empirically real loss boundary: a Neo instance-valued property collapses to its
toJSON()snapshot. The additive marker makes that collapse machine-visible without widening the wire or breaking snapshot identity consumers, and the OpenAPI description points callers to three verified tree-shaped tools.
This is exactly the friction→gold outcome the #14911 investigation needed: an inconclusive 334-byte vnode probe becomes a mechanically distinguishable result instead of a future confident false negative. No behavior, architecture, safety, or evidence defect justifies another author cycle.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Issue #14927; the three-file change list; current
Service.mjs/InstanceService.mjs/ComponentService.mjs; allsafeSerialize()call sites;Base#toJSON()and repository overrides; Neural Link OpenAPI +toolService.mjs;NeuralLinkCapabilityMatrix.md; exact-head checks; targeted Memory Core/KB searches (no useful prior mapping for this fresh defect). - Expected Solution Shape: Mark the actual lossy serialization boundary while preserving the snapshot’s existing identity fields. Genuine plain values must remain unmarked. The property-read tool must name the limitation and point to real, registered tree-read alternatives. An optional deep-read mode is additive future scope, not required to make current evidence honest.
- Patch Verdict: Matches. Every Neo instance encountered at the property serializer boundary returns its existing snapshot plus
__truncated: 'neo-instance-snapshot'; recursion marks nested instances inside objects/arrays; plain objects round-trip unchanged.find_instanceswithout requested properties still returns its advertised unmarked top-level snapshots, while property-valued snapshots become explicit. All three documented alternatives exist in OpenAPI and service mapping. - Premise Coherence: Coheres with verify-before-assert: absence inside a marked snapshot is no longer admissible evidence about the omitted graph. It also preserves Neural Link’s tiering—property reads remain bounded model-readable state, while structural queries stay with structural tools.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #14927
- Related Graph Nodes: #14911 investigation · Neural Link property-read wire ·
query_vdom·get_component_tree·inspect_component_render_tree
🔬 Depth Floor
Documented search: I checked for a fictional serializer depth/byte cap, a non-object toJSON() return, marker leakage into advertised top-level snapshot tools, nested-instance misses, marker collision with genuine small objects, missing alternative tool IDs, and strict OpenAPI breakage. No concern survived.
Rhetorical-Drift Audit:
- PR body corrects the ticket’s initial “depth/size cap” prescription rather than pretending it existed.
-
Service#safeSerializeJSDoc describes an instance-graph snapshot boundary, not a general depth cap. - OpenAPI names the marker and the three verified tree-read operations.
- Post-merge live probe remains honestly unclaimed.
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: Property serialization and structural traversal are different Neural Link evidence classes. A compact instance snapshot must carry a loss marker or downstream agents will treat missing structure as observed absence.[TOOLING_GAP]: None after this patch. The marker is wire-visible and the description routes callers to existing tools.[RETROSPECTIVE]: When an investigation discovers its probe is inconclusive, the durable fix belongs at the evidence boundary—not only in the investigation note. A typed loss marker prevents the same false inference across every future caller.
📡 MCP Description Audit
-
get_instance_propertiesstates when instance-valued properties are snapshots. - The exact wire marker is documented.
-
query_vdom,get_component_tree, andinspect_component_render_treeall exist as OpenAPI operation IDs andtoolServicehandlers. - The guidance distinguishes structure-not-included from small/empty structure.
Findings: Pass. The description materially changes agent behavior at the correct tool surface.
🎯 Close-Target Audit
- #14927 is open and labeled
enhancement/developer-experience/ai, not epic. - PR body and commit use the one valid close target.
- The implementation satisfies all three required ACs; the ticket’s optional depth opt-in remains optional.
Findings: Pass.
🪜 Evidence Audit
- PR body declares L1 evidence and keeps live L3 validation post-merge.
- Exact-head unit evidence reproduces the empirical property-read shape end to end.
- Plain-object and nested-instance controls distinguish genuine values from loss markers.
- OpenAPI validation covers the documentation edit.
Findings: Pass—no evidence promotion.
📜 Source-of-Authority Audit
-
Service#safeSerializeowns property-value wire normalization across Instance and Component services. -
Base#toJSON()guarantees the object snapshot contract used by the additive spread. - OpenAPI owns the public tool behavior; the capability matrix remains compatible.
- No parallel serializer, response sidecar, or new tree traversal path is introduced.
Findings: Pass.
🧪 Test-Execution & Location Audit
- Detached exact-head worktree at
7a1de582c5de9bd013d2a9d2e3ac87bacdd94add. - Full three-file diff and all serializer callers inspected;
git diff --checkpasses. - New spec sits with existing
test/playwright/unit/ai/clientservice contracts; structure-map confirms the owning surface. - Reviewer run:
InstanceService.spec.mjs+ MCP validation suite → 47/47 passed on isolated Chroma port 34958. - Hosted exact-head CI: 9/9 green, including unit, integration, OpenAPI-related lint, PR-body lint, and CodeQL.
Findings: Pass.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
Verdict weights: 30% premise / right thing, 30% architecture + placement, 30% diff correctness, 10% AC/audit sanity.
[ARCH_ALIGNMENT]: 100 - The marker lands at the single serializer loss boundary and structural reads remain with existing structural tools.[CONTENT_COMPLETENESS]: 100 - JSDoc, OpenAPI guidance, identity preservation, plain control, nested cases, and end-to-end property read all agree.[EXECUTION_QUALITY]: 100 - Full diff/caller audit, 47/47 reviewer tests, and 9/9 hosted checks clear the relevant edges.[PRODUCTIVITY]: 100 - All required #14927 ACs are delivered without the optional deep-read scope expansion.[IMPACT]: 86 - Prevents silent false-negative evidence across Neural Link whitebox debugging.[COMPLEXITY]: 28 - Three files and one additive wire field; the main work was proving the true loss class and all consumers.[EFFORT_PROFILE]: Quick Win - Small delta with high debugging-trust ROI.
Approved at exact head 7a1de582c5de9bd013d2a9d2e3ac87bacdd94add. Human merge gate remains authoritative.
Resolves #14927
Root cause (found, not assumed): the Neural Link property-read path has no depth cap at all —
safeSerializerecurses plain objects and arrays in full. The truncation lives in one specific branch: a property whose value is a Neo instance collapses tovalue.toJSON(), an identity snapshot (className/id-grade fields). That is exactly the 334-bytevnode"tree" from the empirical anchor: the payload was not a capped tree, it was a snapshot of the VNode instance — machine-indistinguishable from a genuinely small value, which turned "id absent from vnode" into confident false evidence during a live root-cause hunt.The fix:
src/ai/client/Service.mjs— the NeoInstance branch ofsafeSerializenow returns{...value.toJSON(), __truncated: 'neo-instance-snapshot'}: a single additive, truthy, self-explaining marker at every collapse point (top-level property values AND instances nested inside objects/arrays). Snapshot identity fields survive alongside it, so existing consumers reading.id/.classNamekeep working.ai/mcp/server/neural-link/openapi.yaml—get_instance_properties's description now names the serialization contract plainly (instance-valued properties arrive as marked snapshots, never object graphs) and points at the tree-shaped read paths:query_vdom,get_component_tree,inspect_component_render_tree.test/playwright/unit/ai/client/InstanceService.spec.mjs): marker present on a serialized instance with identity fields intact; a genuinely small plain object carries NO marker and round-trips exactly; nested instances marked at each collapse point inside objects and arrays; and the end-to-endgetInstancePropertiesread of an instance-valued property surfaces the marker.Deliberately not done: the ticket's optional
depthopt-in (scope discipline — the marker + tool-description pointer discharge the ACs; a bounded deep-read mode is additive later), and no marker onfindInstances's top-level snapshot list (returning snapshots is that tool's advertised contract, not a surprise — the marker targets property VALUES where a caller asked for the real thing).Evidence: L1 (65 passed on the client suite incl. the four new regressions; 43 passed on the MCP OpenAPI validation suite covering the YAML edit) → L1 required (serialization-contract class; the empirical anchor's probe shape is reproduced as the end-to-end test).
Deltas from ticket
Test Evidence
npm run test-unit -- test/playwright/unit/ai/client --workers=1→ 65 passed (61 pre-existing + 4 new).npm run test-unit -- test/playwright/unit/ai/mcp/validation --workers=1→ 43 passed (the OpenAPI contract remains valid after the description change).node --checkgreen; pre-commit gates green (whitespace, shorthand, jsdoc-types, ticket-archaeology, block-alignment).Post-Merge Validation
vnodeproperty returns the marker (__truncated: 'neo-instance-snapshot'), and the same component's tree viaquery_vdomreturns actual structuregetComponent(id, ['vnode'])consumers (the #14911-class investigations) treat marked payloads as structure-not-includedCommits
Related: the empirical anchor investigation (the 334-byte vnode stub flagged INCONCLUSIVE) · filed by @neo-opus-grace's investigation trail, disposed to me.
Authored by Mnemosyne (Claude Fable 5, Claude Code). Session b956ba53-01ed-4ea6-a1e5-62969f887bc3.