Архитектура: централизовать navigation focus authority в continuity policy и сделать phase15 replay time-stable

This commit is contained in:
dctouch 2026-04-19 17:44:37 +03:00
parent 0e098cde38
commit 82cc5cfd1a
10 changed files with 199 additions and 92 deletions

View File

@ -23,7 +23,7 @@ This snapshot is based on:
- live replay comparison between:
- `address_truth_harness_phase12_wider_saved_session_pool_live_20260419_rerun16`
- `address_truth_harness_phase14_counterparty_tail_resume_live_20260418_rerun2`
- `address_truth_harness_phase15_answer_inspection_followup_live_20260418_rerun8`
- `address_truth_harness_phase15_answer_inspection_followup_live_20260419_rerun11`
- `address_truth_harness_phase16_multicompany_late_pivot_live_20260419_rerun10`
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail_live_20260419_rerun5`
- [10 - regression_breakpoint_analysis_2026-04-17.md](./10%20-%20regression_breakpoint_analysis_2026-04-17.md)
@ -33,8 +33,8 @@ This snapshot is based on:
Latest graph rebuild:
- `5371 nodes`
- `11523 edges`
- `5372 nodes`
- `11525 edges`
- `135 communities`
Most relevant current god nodes for turnaround `11`:

View File

@ -520,6 +520,12 @@ Latest phase17 clarification-resume evidence after the current replay hardening:
- `decomposeStage` no longer hydrates inventory follow-up filters with the selected organization alias as a fake counterparty anchor, so hidden carryover state stays truthful;
- targeted `assistantRoutePolicy`, `assistantAddressFollowupContext`, `addressImplicitOrganizationScope`, and `addressFollowupTemporalRegression` suites are green after the fix, and backend build stays green;
- live replay `address_truth_harness_phase17_clarification_resume_and_counterparty_tail_live_20260419_rerun5` is accepted `10/10`, which is the current proof that clarification-resume, historical inventory continuation, and short counterparty-tail retarget are now semantically clean on a non-flagship saved-session path.
- the next continuity-authority pass now centralizes navigation focus-state parsing for the hot selected-object path:
- `assistantContinuityPolicy` now owns `resolveNavigationSessionContextState(...)`, which extracts `date scope`, `organization`, `active focus object`, and `active result set id` from navigation state through one shared helper;
- `assistantTransitionPolicy` no longer reconstructs `session_context.active_focus_object` in multiple local branches for purchase-date VAT bridge detection and selected-item carryover setup;
- this matters because selected-item continuity, purchase-date VAT bridge, and navigation-driven focus-object hints now read one shared navigation-state interpretation instead of three local parsers inside the transition hot path;
- targeted `assistantContinuityPolicy` and `assistantTransitionPolicy` suites are green after the move (`37/37`), backend build is green, and graphify was rebuilt on the updated codebase;
- phase15 replay has also been made time-stable for the current snapshot step, and live replay `address_truth_harness_phase15_answer_inspection_followup_live_20260419_rerun11` is accepted `9/9`, which is the semantic proof that the selected-item / answer-inspection / VAT bridge contour still survives after the focus-authority convergence pass.
## Next Execution Slice (2026-04-19)
@ -558,6 +564,34 @@ Current remaining heavy fronts before low-risk domain expansion:
- complete the missing contour for counterparty shipped-goods / service extraction instead of relying on honest-but-limited document-list fallback;
- keep answer shaping as secondary debt only where it materially affects acceptance, not as the primary architecture frontier.
Execution framing for the next level:
- the working target is now `90%+ pre-multidomain readiness` before controlled domain expansion begins;
- the current honest level is `~78%`, so the remaining gap should be treated as four large iterations, not as a few cosmetic follow-ups.
Current four-iteration plan:
1. `Iteration 1 / Continuity authority completion`
Goal:
finish convergence toward one owner for `organization / date / root frame / focus object / clarification state` across the hot runtime path.
Expected gain:
`~+4%`.
2. `Iteration 2 / Wider saved-session replay pool`
Goal:
widen replay breadth beyond the current flagship + phase14 + phase15 + phase16 + phase17 family set.
Expected gain:
`~+4%`.
3. `Iteration 3 / Coordinator pressure reduction`
Goal:
reduce control-plane overload in `assistantService.ts`, `addressQueryService.ts`, and adjacent top-level orchestration seams.
Expected gain:
`~+2-3%`.
4. `Iteration 4 / Critical domain-enablement gaps + final replay proof`
Goal:
close the remaining high-value business gaps such as `counterparty shipped goods/service` and confirm the result under replay until the system crosses the `90%` threshold honestly.
Expected gain:
`~+1-2%`.
## Ready Signal
The project can leave the current breakpoint when:

View File

@ -26,7 +26,7 @@ Current confidence snapshot:
- turnaround implementation progress: `~96%`
- exit-from-danger-zone readiness: `~91%`
- pre-multidomain readiness: `~78%`
- latest graph snapshot: `5371 nodes`, `11523 edges`, `135 communities`
- latest graph snapshot: `5372 nodes`, `11525 edges`, `135 communities`
## What Is Already True
@ -139,7 +139,13 @@ The system should not be considered ready for the next level until all of the fo
## Recommended Next Execution Sequence
### Pass 18. Continuity authority completion
The current planning assumption is:
- `90%+ pre-multidomain readiness` is the practical threshold for starting controlled domain expansion;
- the project is currently around `78%`;
- therefore the remaining gap should be treated as roughly four large iterations rather than as a handful of cosmetic fixes.
### Iteration 1 / Pass 18. Continuity authority completion
Goal:
@ -149,7 +155,11 @@ Target:
- transition / route / clarification should consume one continuity snapshot before making divergent decisions.
### Pass 19. Wider saved-session acceptance pool
Expected readiness gain:
- `~+4%` toward pre-multidomain readiness.
### Iteration 2 / Pass 19. Wider saved-session acceptance pool
Goal:
@ -159,17 +169,11 @@ Target:
- several saved sessions covering inventory, VAT, counterparty, payables/receivables, meta interrupts, and cross-domain pivots beyond phase12 / phase16 / phase17.
### Pass 20. Human answer shaping cleanup
Expected readiness gain:
Goal:
- `~+4%` toward pre-multidomain readiness.
- remove the remaining mechanical, template-heavy feel from long exact answers.
Target:
- product-quality business answers on already-correct truth paths.
### Pass 21. Coordinator pressure reduction
### Iteration 3 / Pass 20. Coordinator pressure reduction
Goal:
@ -177,7 +181,30 @@ Goal:
Target:
- less policy/service glue concentrated in `assistantService.ts` and adjacent god-modules.
- less policy/service glue concentrated in `assistantService.ts`, `addressQueryService.ts`, and adjacent coordinator-heavy seams.
Expected readiness gain:
- `~+2-3%` toward pre-multidomain readiness.
### Iteration 4 / Pass 21. Critical domain-enablement gaps and final acceptance proof
Goal:
- close the remaining high-value domain gaps that still block truthful expansion claims and then prove the result on replay.
Target:
- missing business contours such as `counterparty shipped goods/service`, plus the final replay confirmation that the system is over the `90%` threshold.
Expected readiness gain:
- `~+1-2%` toward pre-multidomain readiness.
Secondary cleanup after those four iterations:
- human answer shaping cleanup where it materially affects acceptance;
- further quality polish that should not be confused with the core readiness gate.
## Final Statement

View File

@ -48,7 +48,7 @@ Current honest status:
- turnaround implementation progress: `~96%`
- exit-from-danger-zone readiness: `~91%`
- pre-multidomain readiness: `~78%`
- graph snapshot after latest rebuild: `5371 nodes`, `11523 edges`, `135 communities`
- graph snapshot after latest rebuild: `5372 nodes`, `11525 edges`, `135 communities`
- current breakpoint:
- the validated hot paths are no longer structurally broken;
- flagship continuity collapse is no longer the primary risk;
@ -65,7 +65,7 @@ Latest live proof now includes:
- `address_truth_harness_phase12_wider_saved_session_pool_live_20260419_rerun16` accepted `20/20`
- `address_truth_harness_phase14_counterparty_tail_resume_live_20260418_rerun2` accepted `10/10`
- `address_truth_harness_phase15_answer_inspection_followup_live_20260418_rerun8` accepted `9/9`
- `address_truth_harness_phase15_answer_inspection_followup_live_20260419_rerun11` accepted `9/9`
- `address_truth_harness_phase16_multicompany_late_pivot_live_20260419_rerun10` accepted
- `address_truth_harness_phase17_clarification_resume_and_counterparty_tail_live_20260419_rerun5` accepted `10/10`
@ -74,6 +74,7 @@ Current architectural reading:
- the system is already materially past the dangerous regression breakpoint;
- it is now safe for continued architecture hardening and controlled domain-by-domain enablement under replay gates;
- it is now materially closer to pre-multidomain stability, but still not safe to declare broad low-risk multi-domain expansion.
- the practical next target is now `90%+ pre-multidomain readiness`, and the remaining gap should be treated as four large architecture iterations rather than as cosmetic cleanup.
For the detailed audit, current percentages, and remaining debt, read:

View File

@ -50,12 +50,12 @@
],
"expected_recipe": "address_inventory_on_hand_as_of_date_v1",
"required_filters": {
"as_of_date": "2026-04-18",
"as_of_date": "{{runtime.today_iso}}",
"organization": "ООО Альтернатива Плюс"
},
"required_direct_answer_patterns_any": [
"(?i)на складе|остат",
"18\\.04\\.2026"
"{{runtime.today_dot_regex}}"
],
"criticality": "critical",
"semantic_tags": [

View File

@ -7,6 +7,7 @@ exports.readAddressDebugOrganization = readAddressDebugOrganization;
exports.readAddressDebugScopedDate = readAddressDebugScopedDate;
exports.readAddressDebugTemporalScope = readAddressDebugTemporalScope;
exports.resolveAddressDebugAnchorContext = resolveAddressDebugAnchorContext;
exports.resolveNavigationSessionContextState = resolveNavigationSessionContextState;
exports.resolveAddressDebugContextFacts = resolveAddressDebugContextFacts;
exports.resolveAddressDebugCarryoverFilters = resolveAddressDebugCarryoverFilters;
exports.hydrateInventoryRootFrameState = hydrateInventoryRootFrameState;
@ -127,6 +128,23 @@ function resolveAddressDebugAnchorContext(debug, toNonEmptyString = fallbackToNo
anchorValue: null
};
}
function resolveNavigationSessionContextState(addressNavigationState, toNonEmptyString = fallbackToNonEmptyString, normalizeOrganizationScopeValue = normalizeOrganizationScopeDefault) {
const sessionContext = toRecordObject(toRecordObject(addressNavigationState)?.session_context);
const rawFocusObject = toRecordObject(sessionContext?.active_focus_object);
const focusObject = rawFocusObject
? {
objectType: toNonEmptyString(rawFocusObject.object_type),
label: toNonEmptyString(rawFocusObject.label),
provenanceResultSetId: toNonEmptyString(rawFocusObject.provenance_result_set_id)
}
: null;
return {
dateScope: toRecordObject(sessionContext?.date_scope),
organization: normalizeOrganizationScopeValue(sessionContext?.organization_scope),
focusObject,
activeResultSetId: toNonEmptyString(sessionContext?.active_result_set_id)
};
}
function resolveAddressDebugContextFacts(debug, toNonEmptyString = fallbackToNonEmptyString) {
return {
item: readAddressDebugItem(debug, toNonEmptyString),

View File

@ -104,17 +104,9 @@ function createAssistantTransitionPolicy(deps) {
return computeMonthWindowFromIso(explicitFirstDateIso);
}
}
const sessionContext = addressNavigationState &&
typeof addressNavigationState === "object" &&
addressNavigationState.session_context &&
typeof addressNavigationState.session_context === "object"
? addressNavigationState.session_context
: null;
const focusObject = sessionContext && typeof sessionContext.active_focus_object === "object"
? sessionContext.active_focus_object
: null;
const preferredResultSetId = deps.toNonEmptyString(focusObject?.provenance_result_set_id) ??
deps.toNonEmptyString(sessionContext?.active_result_set_id);
const navigationSessionState = (0, assistantContinuityPolicy_1.resolveNavigationSessionContextState)(addressNavigationState, deps.toNonEmptyString, deps.normalizeOrganizationScopeValue);
const focusObject = navigationSessionState.focusObject;
const preferredResultSetId = deps.toNonEmptyString(focusObject?.provenanceResultSetId) ?? navigationSessionState.activeResultSetId;
const resultSets = Array.isArray(addressNavigationState?.result_sets) ? addressNavigationState.result_sets : [];
const preferredResultSet = (preferredResultSetId
? resultSets.find((item) => deps.toNonEmptyString(item?.result_set_id) === preferredResultSetId) ?? null
@ -331,16 +323,10 @@ function createAssistantTransitionPolicy(deps) {
? deps.isImplicitAddressContinuationByLlm(alternateMessage, llmPreDecomposeMeta)
: false));
const sourceIntentHint = deps.toNonEmptyString(carryoverSourceDebug?.detected_intent);
const navigationFocusObjectHint = addressNavigationState &&
typeof addressNavigationState === "object" &&
addressNavigationState.session_context &&
typeof addressNavigationState.session_context === "object" &&
addressNavigationState.session_context.active_focus_object &&
typeof addressNavigationState.session_context.active_focus_object === "object"
? addressNavigationState.session_context.active_focus_object
: null;
const navigationSessionState = (0, assistantContinuityPolicy_1.resolveNavigationSessionContextState)(addressNavigationState, deps.toNonEmptyString, deps.normalizeOrganizationScopeValue);
const navigationFocusObjectHint = navigationSessionState.focusObject;
const hasNavigationInventoryItemFocusHint = Boolean(deps.toNonEmptyString(navigationFocusObjectHint?.label) &&
deps.toNonEmptyString(navigationFocusObjectHint?.object_type) === "item" &&
deps.toNonEmptyString(navigationFocusObjectHint?.objectType) === "item" &&
(sourceIntentHint === "inventory_on_hand_as_of_date" ||
sourceIntentHint === "inventory_supplier_stock_overlap_as_of_date" ||
deps.isInventorySelectedObjectIntent(sourceIntentHint)));
@ -499,19 +485,10 @@ function createAssistantTransitionPolicy(deps) {
const previousAnchorContext = (0, assistantContinuityPolicy_1.resolveAddressDebugAnchorContext)(carryoverSourceDebug, deps.toNonEmptyString);
let previousAnchorType = previousAnchorContext.anchorType;
let previousAnchor = previousAnchorContext.anchorValue;
const navigationSessionContext = addressNavigationState && typeof addressNavigationState === "object"
? addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
? addressNavigationState.session_context
: null
: null;
const navigationDateScope = navigationSessionContext && typeof navigationSessionContext.date_scope === "object"
? navigationSessionContext.date_scope
: null;
const navigationOrganization = deps.normalizeOrganizationScopeValue(navigationSessionContext?.organization_scope);
const navigationFocusObject = navigationSessionContext && typeof navigationSessionContext.active_focus_object === "object"
? navigationSessionContext.active_focus_object
: null;
const navigationFocusObjectType = deps.toNonEmptyString(navigationFocusObject?.object_type);
const navigationDateScope = navigationSessionState.dateScope;
const navigationOrganization = navigationSessionState.organization;
const navigationFocusObject = navigationSessionState.focusObject;
const navigationFocusObjectType = deps.toNonEmptyString(navigationFocusObject?.objectType);
const navigationFocusObjectLabel = deps.toNonEmptyString(navigationFocusObject?.label);
const hasInventoryItemFocusCarryover = navigationFocusObjectType === "item" &&
navigationFocusObjectLabel &&

View File

@ -45,6 +45,19 @@ export interface AssistantNavigationDateScope {
period_to?: unknown;
}
export interface AssistantNavigationFocusObject {
objectType: string | null;
label: string | null;
provenanceResultSetId: string | null;
}
export interface AssistantNavigationSessionContextState {
dateScope: AssistantNavigationDateScope | null;
organization: string | null;
focusObject: AssistantNavigationFocusObject | null;
activeResultSetId: string | null;
}
export interface AssistantAddressDebugAnchorContext {
anchorType: string | null;
anchorValue: string | null;
@ -203,6 +216,28 @@ export function resolveAddressDebugAnchorContext(
};
}
export function resolveNavigationSessionContextState(
addressNavigationState: unknown,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString,
normalizeOrganizationScopeValue: (value: unknown) => string | null = normalizeOrganizationScopeDefault
): AssistantNavigationSessionContextState {
const sessionContext = toRecordObject(toRecordObject(addressNavigationState)?.session_context);
const rawFocusObject = toRecordObject(sessionContext?.active_focus_object);
const focusObject = rawFocusObject
? {
objectType: toNonEmptyString(rawFocusObject.object_type),
label: toNonEmptyString(rawFocusObject.label),
provenanceResultSetId: toNonEmptyString(rawFocusObject.provenance_result_set_id)
}
: null;
return {
dateScope: toRecordObject(sessionContext?.date_scope) as AssistantNavigationDateScope | null,
organization: normalizeOrganizationScopeValue(sessionContext?.organization_scope),
focusObject,
activeResultSetId: toNonEmptyString(sessionContext?.active_result_set_id)
};
}
export function resolveAddressDebugContextFacts(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString

View File

@ -10,6 +10,7 @@ import {
readAddressDebugFilters,
readAddressDebugItem,
readAddressDebugTemporalScope,
resolveNavigationSessionContextState,
resolveAddressDebugCarryoverFilters,
resolveAddressDebugAnchorContext,
resolveDisplayedEntityFollowupRetarget,
@ -145,20 +146,14 @@ export function createAssistantTransitionPolicy(deps) {
}
}
const sessionContext =
addressNavigationState &&
typeof addressNavigationState === "object" &&
addressNavigationState.session_context &&
typeof addressNavigationState.session_context === "object"
? addressNavigationState.session_context
: null;
const focusObject =
sessionContext && typeof sessionContext.active_focus_object === "object"
? sessionContext.active_focus_object
: null;
const navigationSessionState = resolveNavigationSessionContextState(
addressNavigationState,
deps.toNonEmptyString,
deps.normalizeOrganizationScopeValue
);
const focusObject = navigationSessionState.focusObject;
const preferredResultSetId =
deps.toNonEmptyString(focusObject?.provenance_result_set_id) ??
deps.toNonEmptyString(sessionContext?.active_result_set_id);
deps.toNonEmptyString(focusObject?.provenanceResultSetId) ?? navigationSessionState.activeResultSetId;
const resultSets = Array.isArray(addressNavigationState?.result_sets) ? addressNavigationState.result_sets : [];
const preferredResultSet =
(preferredResultSetId
@ -431,18 +426,15 @@ export function createAssistantTransitionPolicy(deps) {
? deps.isImplicitAddressContinuationByLlm(alternateMessage, llmPreDecomposeMeta)
: false));
const sourceIntentHint = deps.toNonEmptyString(carryoverSourceDebug?.detected_intent);
const navigationFocusObjectHint =
addressNavigationState &&
typeof addressNavigationState === "object" &&
addressNavigationState.session_context &&
typeof addressNavigationState.session_context === "object" &&
addressNavigationState.session_context.active_focus_object &&
typeof addressNavigationState.session_context.active_focus_object === "object"
? addressNavigationState.session_context.active_focus_object
: null;
const navigationSessionState = resolveNavigationSessionContextState(
addressNavigationState,
deps.toNonEmptyString,
deps.normalizeOrganizationScopeValue
);
const navigationFocusObjectHint = navigationSessionState.focusObject;
const hasNavigationInventoryItemFocusHint = Boolean(
deps.toNonEmptyString(navigationFocusObjectHint?.label) &&
deps.toNonEmptyString(navigationFocusObjectHint?.object_type) === "item" &&
deps.toNonEmptyString(navigationFocusObjectHint?.objectType) === "item" &&
(sourceIntentHint === "inventory_on_hand_as_of_date" ||
sourceIntentHint === "inventory_supplier_stock_overlap_as_of_date" ||
deps.isInventorySelectedObjectIntent(sourceIntentHint))
@ -643,22 +635,10 @@ export function createAssistantTransitionPolicy(deps) {
const previousAnchorContext = resolveAddressDebugAnchorContext(carryoverSourceDebug, deps.toNonEmptyString);
let previousAnchorType = previousAnchorContext.anchorType;
let previousAnchor = previousAnchorContext.anchorValue;
const navigationSessionContext =
addressNavigationState && typeof addressNavigationState === "object"
? addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
? addressNavigationState.session_context
: null
: null;
const navigationDateScope =
navigationSessionContext && typeof navigationSessionContext.date_scope === "object"
? navigationSessionContext.date_scope
: null;
const navigationOrganization = deps.normalizeOrganizationScopeValue(navigationSessionContext?.organization_scope);
const navigationFocusObject =
navigationSessionContext && typeof navigationSessionContext.active_focus_object === "object"
? navigationSessionContext.active_focus_object
: null;
const navigationFocusObjectType = deps.toNonEmptyString(navigationFocusObject?.object_type);
const navigationDateScope = navigationSessionState.dateScope;
const navigationOrganization = navigationSessionState.organization;
const navigationFocusObject = navigationSessionState.focusObject;
const navigationFocusObjectType = deps.toNonEmptyString(navigationFocusObject?.objectType);
const navigationFocusObjectLabel = deps.toNonEmptyString(navigationFocusObject?.label);
const hasInventoryItemFocusCarryover =
navigationFocusObjectType === "item" &&

View File

@ -8,6 +8,7 @@ import {
buildRootScopedCarryoverFilters,
hydrateInventoryRootFrameState,
readAddressDebugTemporalScope,
resolveNavigationSessionContextState,
resolveAddressDebugCarryoverFilters,
resolveAddressDebugContextFacts,
resolveAddressDebugAnchorContext,
@ -111,6 +112,40 @@ describe("assistantContinuityPolicy organization authority", () => {
});
});
it("resolves navigation session context through one shared helper", () => {
const state = resolveNavigationSessionContextState({
session_context: {
organization_scope: "Org Alt",
active_result_set_id: "rs-42",
date_scope: {
as_of_date: "2020-03-31",
period_from: "2020-03-01",
period_to: "2020-03-31"
},
active_focus_object: {
object_type: "item",
label: "Workstation",
provenance_result_set_id: "rs-focus"
}
}
});
expect(state).toEqual({
organization: "Org Alt",
activeResultSetId: "rs-42",
dateScope: {
as_of_date: "2020-03-31",
period_from: "2020-03-01",
period_to: "2020-03-31"
},
focusObject: {
objectType: "item",
label: "Workstation",
provenanceResultSetId: "rs-focus"
}
});
});
it("resolves carryover temporal scope and inferred anchor from debug filters when explicit anchor fields are absent", () => {
const debug = {
extracted_filters: {