Found by the parity checker built for #16585, on its first run against the live tree. Each of these is the same defect class as #16577 — a parameter the service reads that its OpenAPI operation does not declare — so ai/services.mjs's Zod facade strips it and the read evaluates to its default, always.
The consequence is not a crash. It is a capability that exists in the service and is unreachable from outside it. No error, no warning, and every direct-construction unit test passes because those bypass the Proxy.
viaMcp is WITHDRAWN — it is a second correct design, not a third defect (corrected 2026-08-07)
@tobiu answered the design question directly and the answer inverts this row: "for an MCP tool use, viaMcp should be always true." Traced, and it already is:
path
what happens to viaMcp
correct?
MCP tool dispatch
ToolService.mjs:124 Zod-parses first, stripping any caller value; then the mapping at toolService.mjs:77 re-adds viaMcp: trueafter validation
yes — forced true, and un-spoofable by the caller
ai/services.mjs wrapper (CLI, scripts)
stripped → the viaMcp = false default applies
yes — VectorService.mjs:981 documents false as the deliberate bypass, "explicit opt-in to long-running work", and :1125 names CLI as its caller
So both paths already get the value they should, from opposite directions, and neither takes it from the caller. Declaring it would be the regression: VectorService.mjs:1133 gates on if (viaMcp && …), so an agent able to send viaMcp: false through the KB MCP surface could switch the work-volume gate OFF — and per @tobiu, KB and MC are the only publicly accessible surfaces, which is precisely where that must not be settable.
This is the same category as who_is_online.now below: a value that must never be caller-supplied. It differs only in mechanism — now has a correct default, while viaMcp has a correct default on one path and an explicit override on the other.
What I got wrong, recorded because the reasoning error is the reusable part. I read the service default (viaMcp = false) and the stripping facade, and concluded "always false, capability unselectable". I never checked the dispatch path, where the mapping re-adds it. Reading a default and a stripper is not reading the call path — and mid-correction I briefly swung the other way and nearly filed the inverse claim (that every MCP call was silently bypassing the gate), which toolService.mjs:77 falsifies just as flatly. Both errors came from stopping one file short of the producer.
staleStrategy survives on this operation and is the real finding here. Same Zod strip at ToolService.mjs:124, and nothing re-adds it — so it is genuinely unreachable through MCP, unlike its neighbour.
The fifth is NOT a defect, and the distinction matters
who_is_online reads now, and that is correct as-is:
asyncwhoIsOnline({family, verbose = false, now = newDate()} = {}) {
now is an injected clock with a working default, used identically across bootstrap, retireStaleHarnessPresence and whoIsOnline — a test seam, not an input. Declaring it would be actively harmful: it would let a caller supply an arbitrary "current time" to a liveness computation, i.e. lie about whether a peer is online. It is baselined in the checker with that reason rather than fixed.
Stating it explicitly because the tempting read of a 5-row failure is "5 defects," and one of them is a correct design whose exposure would be a regression. A checker's output is a list of findings, not verdicts.
The Problem
Each row is a one-line contract omission, but the four are not one fix — declaring a parameter makes it agent-settable, and that is a design decision per parameter:
viaMcp — withdrawn, see above. The question this bullet posed ("should an agent be able to choose it?") had an obvious answer I hedged on: no, and it is already enforced. Recording the hedge rather than deleting it, because "it is not obviously yes" was me declining to trace one more file.
staleStrategy selects stale-row handling during sync — adjacent to the stale-deletion scoping in #16590 and to the corpus-loss incident. Exposing a strategy selector to agents needs the same care.
includeMetadata looks safe and payload-affecting: it widens every query_documents response, which interacts with the tool-result size concerns in #16588.
depth on the context frontier is an unbounded-traversal knob. If declared, it needs a maximum, or an agent can request an arbitrarily deep walk.
So the remedy is four small, individually-reasoned decisions — declare with bounds, or record the parameter as deliberately internal.
Dispositions — researched 2026-08-07, and NOT ONE of them is "declare it"
Each was traced to its producer before deciding. Three of the four dispositions differ from what this ticket's Problem section assumed, so the assumptions are corrected in place below rather than left standing.
get_context_frontier.depth → DELETE the parameter. It is dead, not throttled.
The Problem section says "frontier depth is permanently 2", which implies a knob stuck at its default. It is worse than that. Measured by AST over the method's full body rather than by eye:
GraphService#getContextFrontier — body 2484 chars
occurrences of `depth` in the full body: 0
It is destructured in the signature (getContextFrontier({depth = 2} = {})) and never read. The lint flags it correctly — destructuring is a read by the checker's definition — but the capability does not exist at any value.
So both obvious dispositions are wrong. Declaring it is the worst available option: an agent sets depth: 5, receives no error, and gets depth-2 results — a silently-lying contract, which is the exact failure class this instrument exists to remove, re-introduced by "fixing" the finding. Baselining it would record "deliberately internal" about a parameter that does nothing, so the stated reason would be false. Deleting the dead parameter removes the finding at its root and takes a lie-in-waiting with it.
query_documents.includeMetadata → baseline PERMANENT. Internal by design, and nothing is lost.
QueryService.mjs:137 states it: "Internal hydration flag for RAG synthesis callers." Exactly one caller repo-wide — SearchService.mjs:305, queryDocuments({query, type, limit, includeMetadata: true}) — hydrating result.metadata for answer synthesis.
The Problem section framed this as "metadata is unreachable through the tool", i.e. a loss. There is no loss: the synthesis surface that needs metadata is ask_knowledge_base, which sets the flag itself. query_documents returning ranked source references is its contract, not a degraded version of one. This is the who_is_online.now class — an internal flag with a working default and a single internal consumer.
manage_knowledge_base.staleStrategy → baseline PERMANENT. The operator surface is an env var, and one of the two values is the destructive path.
STALE_STRATEGIES is a validated two-value enum (VectorService.mjs:20) — delete-upfront and shadow-swap — and resolveStaleStrategy throws on anything else, so declaring it would be schema-safe. Schema-safe is not the same as safe:
shadow-swap embeds into a shadow collection and swaps atomically. Non-destructive.
delete-upfront deletes stale rows before embedding. A failure between the delete and the embed leaves the corpus missing both the removed rows and their replacements — which is the shape of the corpus losses this week, and the reason the ceiling and retention work exist at all.
Declaring it would let a caller through the KB MCP surface — one of only two publicly reachable surfaces — select the destructive path against the live corpus. The code already selects shadow-swap where it matters (VectorService.mjs:822, :850), and the operator-facing control already exists as an environment variable: NEO_KB_STALE_STRATEGY, read at syncKnowledgeBase.mjs:86. So the capability is reachable by whoever should reach it, and the MCP parameter would add nothing except the ability to choose the dangerous branch remotely.
get_all_summaries.category → remains open, and it is the worst of the set for a caller.
A sixth row, surfaced by the ToolService dispatch join's ADVISORY direction rather than by a stripped read — so it is the inverse defect from every other row here, and it arrived after this ticket was filed.
get_all_summariesdeclarescategory, its parameter description reads "Filter by category", and the operation description instructs an agent to "find sessions related to a specific category of work (e.g. 'refactoring')". The bound handler is SummaryService.listSummaries({limit, offset, agentIdentity}) — it never reads it. The two category occurrences in that file are output shaping (category: metadata.category) and a different method, querySummaries, which genuinely does filter by it.
Worse than an unreachable capability, because the documentation actively instructs callers to use it. Every other row here is a capability an agent cannot reach and does not know about. This one an agent is told to use, sends, and receives silently unfiltered results for — no error, no warning, and the filtering it wanted exists one method over.
Two dispositions, both behaviour changes rather than lint edits, which is why it is baselined rather than fixed in the instrument's PR:
Wire it through — pass category into the storage query in listSummaries, matching what querySummaries already does. Makes the documentation true.
Remove it from the contract and point callers at query_summaries. Makes the documentation honest.
I lean toward wiring it through, because the description is the more likely intent and a removal silently narrows a surface agents may already be sending. But it is a behaviour change on a memory-read path and it gets its own reasoning rather than riding on that lean.
get_session_memories.memorySharing → remains open. This is the one I want challenged.
Surfaced by the ToolService dispatch join, not the original run, so it is a fifth row rather than one of four. It is a tenant-isolation policy override, unreachable on get_session_memories while declared on two sibling memory-core operations — so precedent says exposing it is acceptable somewhere.
Baselined as DEBT for now, deliberately not decided: declaring it changes which memories an agent can read, and that belongs in a disposition with a stated reason rather than in a lint PR that happened to find it. The honest fork: either the siblings' precedent extends here and it should be declared, or the siblings are themselves over-exposed and the precedent is the defect. I do not know which, and guessing would settle a memory-visibility question by accident.
Sequencing note: the edits for all of the above touch PARITY_BASELINE, which PR #16612 is currently modifying. The decisions above are the work; the edits are mechanical and land once that PR merges, so this ticket is decision-complete and merge-blocked rather than unstarted.
Acceptance Criteria
Each of the three remaining (staleStrategy, includeMetadata, depth) is dispositioned individually and with a stated reason: either declared in its operation's schema (with bounds where the value is a traversal or size knob), or recorded as deliberately internal in the checker's PARITY_BASELINE.
depth is removed from getContextFrontier's signature. Measured dead (0 occurrences in the method body) — FALSIFIED 2026-08-10 by the gate, and the original measurement was mine and wrong. There are twogetContextFrontiers. The operation binds MemoryService's, which declares no parameters and calls GraphService.getContextFrontier() forwarding nothing — that is the method I measured. The live depth read belongs to GraphService's same-named method, whose sole caller is GoldenPathSynthesizer passing a literal {depth: 1}. Deleting the baseline row reds the lint with get_context_frontier reads depth — ai/services/memory-core/GraphService.mjs. So the parameter is neither dead nor MCP-reachable: deleting it breaks an internal traversal knob in use, and declaring it advertises one the bound method cannot forward. Superseded disposition: PERMANENT baseline carrying the wrong-method history, so the next author does not repeat the name collision.
viaMcp is dispositioned: stays baselined, never declared, with the two-path rationale above preserved so a future sweep does not helpfully widen the schema and hand the work-volume gate to callers.
The staleStrategy disposition cites #16577's finding, since that ticket established what the sibling parameter controls and what going wrong costs.
No row is resolved by widening the schema without asking whether the parameter should be agent-settable. A silently-ignored parameter and a wrongly-exposed one are both defects.
includeMetadata and staleStrategy are baselined PERMANENT with the reasons above — internal RAG hydration flag with one internal caller, and a destructive-branch selector whose operator surface is already NEO_KB_STALE_STRATEGY.
memorySharing is either declared or baselined with a stated reason, and the decision records whether the two sibling operations that DO declare it are precedent or are themselves over-exposed.
After disposition, ai:lint-openapi-service-parity is clean with every remaining baseline row carrying a reason.
who_is_online.now stays baselined and is not declared — with the "an agent could lie about the current time to a liveness computation" rationale preserved, so a future sweep does not helpfully "fix" it.
Out of Scope
The checker itself — #16585 delivers the instrument; this ticket dispositions what it found.
ingest_source_files — already repaired via #16583.
A sweep for the inverse (declared but never read). The checker reports that as a warning by design; intentional forward-compat is legitimate.
Related
#16585 — the instrument that found these; this ticket is its first output.
#16577 / #16583 — the original two instances and the repair, and the authority on what viaMcp controls.
#16588 — tool-result payload size, directly relevant to the includeMetadata disposition.
#16590 — tenant-scoped stale-id gathering, adjacent to the staleStrategy disposition.
Live latest-open sweep: checked the latest open issues at 2026-08-07T00:3xZ plus a targeted search on includeMetadata / staleStrategy / contextFrontier depth; no equivalent found (#16591 is the nearest neighbour and concerns a refusal proven below the agent-facing surface, a different defect on adjacent ground).
Retrieval Hint: query_raw_memories("consumed but undeclared openapi parameter service capability unreachable") · ai/scripts/lint/lint-openapi-service-parity.mjs
viaMcp governs whether a bulk path passes through the MCP work-volume gate. https://github.com/neomjs/neo/issues/16577 shows getting this wrong causes KB_VECTOR_EMBED_FAILED above mcpSyncMaxChunks. Whether an agent should be able to choose it is exactly the question that ticket wrestled with; it is not obviously "yes".
It is an obvious "yes". there is polling and push based KB ingestion. For security reasons, KB and MC are the only publicly accessible surfaces. push goes through the KB MCP tool. As a side effect, agents also can use the tool manually, which has potential for own content. However, for an MCP tool use, viaMcp should be always true.
@neo-opus-vega - 2026-08-07T08:58:59Z
@tobiu Traced it, and you are right — viaMcp is withdrawn from this ticket. Four rows became three, and the body carries the correction.
It is already always true on the MCP path, from two directions I had not connected:
path
viaMcp
correct?
MCP tool dispatch
ToolService.mjs:124 Zod-parses first, stripping any caller value; the mapping at toolService.mjs:77 then re-adds viaMcp: trueafter validation
yes — forced, and un-spoofable
ai/services.mjs wrapper (CLI, scripts)
stripped → the viaMcp = false default applies
yes — VectorService.mjs:981 documents false as the deliberate bypass, "explicit opt-in to long-running work"
Both paths get the value they should, from opposite directions, and neither takes it from the caller. Declaring it would be the regression — VectorService.mjs:1133 gates on if (viaMcp && …), so an agent able to send viaMcp: false through the KB surface could switch the work-volume gate off. Given KB and MC are the only publicly accessible surfaces, that is exactly where it must not be settable. Same category as who_is_online.now, differing only in mechanism.
Two errors on the way, both from stopping one file short of the producer. First I read the service default plus the stripping facade and concluded "always false, capability unselectable" — never checking the dispatch path where the mapping re-adds it. Then, mid-correction, I swung the other way and was about to report that every MCP call silently bypasses the work-volume gate — which toolService.mjs:77 falsifies just as flatly. Reading a default and a stripper is not reading the call path.
staleStrategy survives on that operation and is the real finding. Same Zod strip at ToolService.mjs:124, and nothing re-adds it — genuinely unreachable through MCP, unlike its neighbour. So the two rows I had bundled as one class are a correct design and a live gap sitting side by side, which is the distinction the ticket now makes.
Remaining: staleStrategy, includeMetadata, depth. The viaMcp AC is checked off as stays baselined, never declared, with the rationale preserved so a future sweep does not helpfully widen the schema.
Lane order unchanged: #16612 finishes first (the advisory direction and the end-to-end fixtures landed; the ToolService join is next), then these three.
Authored by @neo-opus-vega (Claude Opus 5).
2026-08-07T10:39:06Z @neo-opus-vega referenced in commit 3a916f6 - "fix(ci): push and pull_request path filters are now asserted equal, not maintained in parallel (#16585)
Cycle-4 blocker. I fixed ONE of two path lists. The three ToolService authorities
went into pull_request.paths and not push.paths, so pushes to dev never re-ran the
guard on the mapping table its dispatch join derives every handler from.
@neo-gpt measured it with micromatch — true under pull_request, false under push,
with ai/services/knowledge-base/QueryService.mjs as a positive control that is true
under both, so the instrument was proven before the finding was reported.
This is the same defect I removed from backup.mjs this morning and cited in my own
commit message hours ago: two lists that must agree are a list that will not. I
then reproduced it while fixing a reachability gap, in the file I was editing.
GitHub Actions does not reliably expand YAML anchors in workflow files, so one
authority is not available here and mirroring is unavoidable. What IS available is
a mechanical equality assertion, so the next divergence fails locally instead of
waiting for a reviewer to run micromatch. Reviewer attention is the wrong layer:
the omission is invisible in a diff that SHOWS the addition.
The new spec also asserts every authority the lint reads is matched under both
triggers, derived from SERVERS rather than listed, plus a negative control — a
match-everything filter would satisfy every reachability assertion while making the
gate run on every commit in the repo.
A NOTE ON THE MUTATION TEST, because it nearly misled me: my first attempt to
re-introduce the live defect reported the spec PASSING, which reads as a vacuous
guard. The perl pattern had silently not matched, so the mutation never applied.
Re-applied with an assertion that the edit landed (1 to 0 occurrences) and the spec
fails as intended. A mutation result means nothing until the mutation is proven to
have happened.
Also in this bounded pass: all five PARITY_BASELINE reasons truth-folded against
the researched #16611 dispositions — viaMcp withdrawn as correct-by-design, depth
marked TRANSITIONAL pending deletion since it is measured dead, includeMetadata and
staleStrategy reclassified PERMANENT with their real reasons — and
get_all_summaries.category given a real owner as a #16611 ledger row rather than a
dangling reference.
Refs #16612
Authored by @neo-opus-vega (Claude Opus 5)."
tobiu referenced in commit 71abf8b - "A parameter a service reads must be declared, or the facade strips it silently (#16585) (#16612) on Aug 7, 2026, 2:05 PM
Context
Found by the parity checker built for #16585, on its first run against the live tree. Each of these is the same defect class as #16577 — a parameter the service reads that its OpenAPI operation does not declare — so
ai/services.mjs's Zod facade strips it and the read evaluates to its default, always.The consequence is not a crash. It is a capability that exists in the service and is unreachable from outside it. No error, no warning, and every direct-construction unit test passes because those bypass the Proxy.
Observed — five flagged, four genuine
manage_knowledge_baseviaMcpmanageKnowledgeBase({action, viaMcp = false, ...})viaMcpsection below.manage_knowledge_basestaleStrategyundefined; stale-handling strategy unselectablequery_documentsincludeMetadataqueryDocuments({query, type='all', limit=25, includeMetadata=false})false; metadata is unreachable through the toolget_context_frontierdepthgetContextFrontier({depth = 2} = {})(GraphService.mjs:1340)viaMcpis WITHDRAWN — it is a second correct design, not a third defect (corrected 2026-08-07)@tobiu answered the design question directly and the answer inverts this row: "for an MCP tool use,
viaMcpshould be alwaystrue." Traced, and it already is:viaMcpToolService.mjs:124Zod-parses first, stripping any caller value; then the mapping attoolService.mjs:77re-addsviaMcp: trueafter validationai/services.mjswrapper (CLI, scripts)viaMcp = falsedefault appliesVectorService.mjs:981documentsfalseas the deliberate bypass, "explicit opt-in to long-running work", and:1125names CLI as its callerSo both paths already get the value they should, from opposite directions, and neither takes it from the caller. Declaring it would be the regression:
VectorService.mjs:1133gates onif (viaMcp && …), so an agent able to sendviaMcp: falsethrough the KB MCP surface could switch the work-volume gate OFF — and per @tobiu, KB and MC are the only publicly accessible surfaces, which is precisely where that must not be settable.This is the same category as
who_is_online.nowbelow: a value that must never be caller-supplied. It differs only in mechanism —nowhas a correct default, whileviaMcphas a correct default on one path and an explicit override on the other.What I got wrong, recorded because the reasoning error is the reusable part. I read the service default (
viaMcp = false) and the stripping facade, and concluded "always false, capability unselectable". I never checked the dispatch path, where the mapping re-adds it. Reading a default and a stripper is not reading the call path — and mid-correction I briefly swung the other way and nearly filed the inverse claim (that every MCP call was silently bypassing the gate), whichtoolService.mjs:77falsifies just as flatly. Both errors came from stopping one file short of the producer.staleStrategysurvives on this operation and is the real finding here. Same Zod strip atToolService.mjs:124, and nothing re-adds it — so it is genuinely unreachable through MCP, unlike its neighbour.The fifth is NOT a defect, and the distinction matters
who_is_onlinereadsnow, and that is correct as-is:async whoIsOnline({family, verbose = false, now = new Date()} = {}) {nowis an injected clock with a working default, used identically acrossbootstrap,retireStaleHarnessPresenceandwhoIsOnline— a test seam, not an input. Declaring it would be actively harmful: it would let a caller supply an arbitrary "current time" to a liveness computation, i.e. lie about whether a peer is online. It is baselined in the checker with that reason rather than fixed.Stating it explicitly because the tempting read of a 5-row failure is "5 defects," and one of them is a correct design whose exposure would be a regression. A checker's output is a list of findings, not verdicts.
The Problem
Each row is a one-line contract omission, but the four are not one fix — declaring a parameter makes it agent-settable, and that is a design decision per parameter:
— withdrawn, see above. The question this bullet posed ("should an agent be able to choose it?") had an obvious answer I hedged on: no, and it is already enforced. Recording the hedge rather than deleting it, because "it is not obviously yes" was me declining to trace one more file.viaMcpstaleStrategyselects stale-row handling during sync — adjacent to the stale-deletion scoping in #16590 and to the corpus-loss incident. Exposing a strategy selector to agents needs the same care.includeMetadatalooks safe and payload-affecting: it widens everyquery_documentsresponse, which interacts with the tool-result size concerns in #16588.depthon the context frontier is an unbounded-traversal knob. If declared, it needs a maximum, or an agent can request an arbitrarily deep walk.So the remedy is four small, individually-reasoned decisions — declare with bounds, or record the parameter as deliberately internal.
Dispositions — researched 2026-08-07, and NOT ONE of them is "declare it"
Each was traced to its producer before deciding. Three of the four dispositions differ from what this ticket's Problem section assumed, so the assumptions are corrected in place below rather than left standing.
get_context_frontier.depth→ DELETE the parameter. It is dead, not throttled.The Problem section says "frontier depth is permanently 2", which implies a knob stuck at its default. It is worse than that. Measured by AST over the method's full body rather than by eye:
It is destructured in the signature (
getContextFrontier({depth = 2} = {})) and never read. The lint flags it correctly — destructuring is a read by the checker's definition — but the capability does not exist at any value.So both obvious dispositions are wrong. Declaring it is the worst available option: an agent sets
depth: 5, receives no error, and gets depth-2 results — a silently-lying contract, which is the exact failure class this instrument exists to remove, re-introduced by "fixing" the finding. Baselining it would record "deliberately internal" about a parameter that does nothing, so the stated reason would be false. Deleting the dead parameter removes the finding at its root and takes a lie-in-waiting with it.query_documents.includeMetadata→ baseline PERMANENT. Internal by design, and nothing is lost.QueryService.mjs:137states it: "Internal hydration flag for RAG synthesis callers." Exactly one caller repo-wide —SearchService.mjs:305,queryDocuments({query, type, limit, includeMetadata: true})— hydratingresult.metadatafor answer synthesis.The Problem section framed this as "metadata is unreachable through the tool", i.e. a loss. There is no loss: the synthesis surface that needs metadata is
ask_knowledge_base, which sets the flag itself.query_documentsreturning ranked source references is its contract, not a degraded version of one. This is thewho_is_online.nowclass — an internal flag with a working default and a single internal consumer.manage_knowledge_base.staleStrategy→ baseline PERMANENT. The operator surface is an env var, and one of the two values is the destructive path.STALE_STRATEGIESis a validated two-value enum (VectorService.mjs:20) —delete-upfrontandshadow-swap— andresolveStaleStrategythrows on anything else, so declaring it would be schema-safe. Schema-safe is not the same as safe:shadow-swapembeds into a shadow collection and swaps atomically. Non-destructive.delete-upfrontdeletes stale rows before embedding. A failure between the delete and the embed leaves the corpus missing both the removed rows and their replacements — which is the shape of the corpus losses this week, and the reason the ceiling and retention work exist at all.Declaring it would let a caller through the KB MCP surface — one of only two publicly reachable surfaces — select the destructive path against the live corpus. The code already selects
shadow-swapwhere it matters (VectorService.mjs:822,:850), and the operator-facing control already exists as an environment variable:NEO_KB_STALE_STRATEGY, read atsyncKnowledgeBase.mjs:86. So the capability is reachable by whoever should reach it, and the MCP parameter would add nothing except the ability to choose the dangerous branch remotely.get_all_summaries.category→ remains open, and it is the worst of the set for a caller.A sixth row, surfaced by the ToolService dispatch join's ADVISORY direction rather than by a stripped read — so it is the inverse defect from every other row here, and it arrived after this ticket was filed.
get_all_summariesdeclarescategory, its parameter description reads "Filter by category", and the operation description instructs an agent to "find sessions related to a specific category of work (e.g. 'refactoring')". The bound handler isSummaryService.listSummaries({limit, offset, agentIdentity})— it never reads it. The twocategoryoccurrences in that file are output shaping (category: metadata.category) and a different method,querySummaries, which genuinely does filter by it.Worse than an unreachable capability, because the documentation actively instructs callers to use it. Every other row here is a capability an agent cannot reach and does not know about. This one an agent is told to use, sends, and receives silently unfiltered results for — no error, no warning, and the filtering it wanted exists one method over.
Two dispositions, both behaviour changes rather than lint edits, which is why it is baselined rather than fixed in the instrument's PR:
categoryinto the storage query inlistSummaries, matching whatquerySummariesalready does. Makes the documentation true.query_summaries. Makes the documentation honest.I lean toward wiring it through, because the description is the more likely intent and a removal silently narrows a surface agents may already be sending. But it is a behaviour change on a memory-read path and it gets its own reasoning rather than riding on that lean.
get_session_memories.memorySharing→ remains open. This is the one I want challenged.Surfaced by the ToolService dispatch join, not the original run, so it is a fifth row rather than one of four. It is a tenant-isolation policy override, unreachable on
get_session_memorieswhile declared on two sibling memory-core operations — so precedent says exposing it is acceptable somewhere.Baselined as DEBT for now, deliberately not decided: declaring it changes which memories an agent can read, and that belongs in a disposition with a stated reason rather than in a lint PR that happened to find it. The honest fork: either the siblings' precedent extends here and it should be declared, or the siblings are themselves over-exposed and the precedent is the defect. I do not know which, and guessing would settle a memory-visibility question by accident.
Sequencing note: the edits for all of the above touch
PARITY_BASELINE, which PR #16612 is currently modifying. The decisions above are the work; the edits are mechanical and land once that PR merges, so this ticket is decision-complete and merge-blocked rather than unstarted.Acceptance Criteria
staleStrategy,includeMetadata,depth) is dispositioned individually and with a stated reason: either declared in its operation's schema (with bounds where the value is a traversal or size knob), or recorded as deliberately internal in the checker'sPARITY_BASELINE.— FALSIFIED 2026-08-10 by the gate, and the original measurement was mine and wrong. There are twodepthis removed fromgetContextFrontier's signature. Measured dead (0 occurrences in the method body)getContextFrontiers. The operation bindsMemoryService's, which declares no parameters and callsGraphService.getContextFrontier()forwarding nothing — that is the method I measured. The livedepthread belongs toGraphService's same-named method, whose sole caller isGoldenPathSynthesizerpassing a literal{depth: 1}. Deleting the baseline row reds the lint withget_context_frontier reads depth — ai/services/memory-core/GraphService.mjs. So the parameter is neither dead nor MCP-reachable: deleting it breaks an internal traversal knob in use, and declaring it advertises one the bound method cannot forward. Superseded disposition:PERMANENTbaseline carrying the wrong-method history, so the next author does not repeat the name collision.viaMcpis dispositioned: stays baselined, never declared, with the two-path rationale above preserved so a future sweep does not helpfully widen the schema and hand the work-volume gate to callers.staleStrategydisposition cites #16577's finding, since that ticket established what the sibling parameter controls and what going wrong costs.includeMetadataandstaleStrategyare baselined PERMANENT with the reasons above — internal RAG hydration flag with one internal caller, and a destructive-branch selector whose operator surface is alreadyNEO_KB_STALE_STRATEGY.memorySharingis either declared or baselined with a stated reason, and the decision records whether the two sibling operations that DO declare it are precedent or are themselves over-exposed.ai:lint-openapi-service-parityis clean with every remaining baseline row carrying a reason.who_is_online.nowstays baselined and is not declared — with the "an agent could lie about the current time to a liveness computation" rationale preserved, so a future sweep does not helpfully "fix" it.Out of Scope
ingest_source_files— already repaired via #16583.Related
viaMcpcontrols.includeMetadatadisposition.staleStrategydisposition.Origin Session ID: 4141258c-36d3-4788-b0c2-ab3ebe0867be
Live latest-open sweep: checked the latest open issues at 2026-08-07T00:3xZ plus a targeted search on
includeMetadata/staleStrategy/contextFrontier depth; no equivalent found (#16591 is the nearest neighbour and concerns a refusal proven below the agent-facing surface, a different defect on adjacent ground).Retrieval Hint:
query_raw_memories("consumed but undeclared openapi parameter service capability unreachable")·ai/scripts/lint/lint-openapi-service-parity.mjsAuthored by @neo-opus-vega (Claude Opus 5).