mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-16 23:43:12 +00:00
docs(semantic-model): document unified single-source-of-truth invariant (I9)
Add Contract Invariant I9 to the ScopeResolver contract and write the
single-source-of-truth + write/read phase contract into both the
SemanticModel file-head and ARCHITECTURE.md.
Three landing points so the rule is reachable from every entry:
* contract/scope-resolver.ts — new I9 entry in the Contract
Invariants list: scope-resolution passes consult SemanticModel
exclusively for symbol-keyed lookups; WorkspaceResolutionIndex is
reserved for Scope-valued maps. Documents the two-phase write
(legacy parse + reconcileOwnership) and the narrowed-handle read
posture. Calls out the reconciliation shim as transitional.
* model/semantic-model.ts — new "Single-source-of-truth invariant"
and "Write / read phase contract" sections in the file-head.
Three ordered write phases (parse → reconcile → attachScopeIndexes),
then frozen for readers.
* ARCHITECTURE.md § "Semantic-model source of truth" — expanded
subsection covering both invariants (ParsedFile = AST truth,
SemanticModel = symbol truth), the write/read phase diagram, and
the reconciliation-shim rationale.
No code change.
This commit is contained in:
parent
e3f4d8b1eb
commit
ba94fdca91
3 changed files with 106 additions and 1 deletions
|
|
@ -231,7 +231,35 @@ The CI parity workflow (`.github/workflows/ci-scope-parity.yml`) runs both paths
|
|||
|
||||
#### Semantic-model source of truth
|
||||
|
||||
`ParsedFile` (`gitnexus-shared/src/scope-resolution/parsed-file.ts`) is the single semantic model both paths consume. Scope-resolution passes MUST NOT build a parallel parse representation. If a per-language hook needs AST-level facts that `ParsedFile` doesn't expose, it should reuse the orchestrator's `treeCache` (`RunScopeResolutionInput.treeCache`) rather than re-invoking `parser.parse(...)` on its own — the C# `populateNamespaceSiblings` hook is the reference implementation of this pattern.
|
||||
Two independent invariants.
|
||||
|
||||
**ParsedFile = the AST-level truth.** `ParsedFile` (`gitnexus-shared/src/scope-resolution/parsed-file.ts`) is the single per-file artifact both resolution paths consume. Scope-resolution passes MUST NOT build a parallel parse representation. If a per-language hook needs AST-level facts that `ParsedFile` doesn't expose, it should reuse the orchestrator's `treeCache` (`RunScopeResolutionInput.treeCache`) rather than re-invoking `parser.parse(...)` on its own — the C# `populateNamespaceSiblings` hook is the reference implementation of this pattern.
|
||||
|
||||
**SemanticModel = the symbol-level truth.** `SemanticModel` (`gitnexus/src/core/ingestion/model/semantic-model.ts`) is the authoritative store for every symbol-indexed lookup (by `nodeId`, `simpleName`, `qualifiedName`, or `filePath`). Both paths read from here:
|
||||
|
||||
- Legacy Call-Resolution DAG → `call-processor` Tier 1/2/3 via `model.symbols.lookupExactAll`, `model.methods.lookupMethodByName`, `model.types.lookupClassByName`, `lookupMethodByOwnerWithMRO`.
|
||||
- Scope-resolution pipeline → `findOwnedMember`, `pickOverload`, `findExportedDefByName` all consult `model.methods` / `model.fields` / `model.symbols`.
|
||||
|
||||
The scope-resolution pipeline additionally carries `WorkspaceResolutionIndex` for `Scope`-valued lookups (`classScopeByDefId`, `moduleScopeByFile`) that `SemanticModel` structurally cannot hold. No symbol-indexed duplicates exist outside `SemanticModel`.
|
||||
|
||||
**Write / read phase contract.** The model is mutable during three ordered phases and read-only afterward:
|
||||
|
||||
```
|
||||
Phase 1: legacy parse ──► symbolTable.add fans into types/methods/fields
|
||||
Phase 2: scope-resolution ──► reconcileOwnership() registers corrected ownerIds
|
||||
Phase 3: finalize ──► model.attachScopeIndexes(bundle) — one-shot freeze
|
||||
─────────────────────────── phase boundary ───────────────────────────
|
||||
Read phase: all resolution passes + MCP + HTTP + embeddings see
|
||||
SemanticModel (read-only handle); writes are type-errors.
|
||||
```
|
||||
|
||||
`runScopeResolution` narrows `MutableSemanticModel` → `SemanticModel` at the phase boundary so downstream passes physically cannot mutate the model even accidentally.
|
||||
|
||||
**Transitional: reconciliation pass.** `reconcileOwnership` (`scope-resolution/pipeline/reconcile-ownership.ts`) is a shim for languages whose legacy extractor doesn't resolve `enclosingClassId` at parse time (Python class-body methods are the canonical case). It walks `parsed.localDefs[i].ownerId` after `populateOwners` and registers any missed methods/fields into the model. Idempotent — safe to re-run, safe alongside languages whose legacy extractor already carries `ownerId` (C#).
|
||||
|
||||
The architectural end state is for every language's parse-time extractor to emit the correct `ownerId` directly, making reconciliation a no-op (tracked as a follow-up refactor). The dev-mode validator `validateOwnershipParity` surfaces any drift via `onWarn` under `NODE_ENV !== 'production' && VALIDATE_SEMANTIC_MODEL !== '0'`.
|
||||
|
||||
References: `semantic-model.ts` file-head (full write/read contract); `contract/scope-resolver.ts` Contract Invariant I9 (scope-resolution-side rule).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,45 @@
|
|||
* direct `createSymbolTable()` caller (e.g. an isolated unit test) gets
|
||||
* the pure, registry-free behavior — no surprises, no hidden side
|
||||
* effects.
|
||||
*
|
||||
* ## Single-source-of-truth invariant
|
||||
*
|
||||
* `SemanticModel` is the authoritative symbol store for the whole
|
||||
* ingestion pipeline. Both the legacy Call-Resolution DAG and the
|
||||
* new scope-resolution pipeline read symbol-keyed lookups from here
|
||||
* exclusively — no parallel owner-keyed, name-keyed, or file-keyed
|
||||
* symbol indexes exist outside this module. The scope-resolution
|
||||
* pipeline does carry a small `WorkspaceResolutionIndex` for
|
||||
* `Scope`-valued maps (`classScopeByDefId`, `moduleScopeByFile`) that
|
||||
* `SemanticModel` structurally cannot hold, but nothing else.
|
||||
*
|
||||
* ## Write / read phase contract
|
||||
*
|
||||
* Writes to the model happen in three clearly-ordered phases during a
|
||||
* single ingestion run:
|
||||
*
|
||||
* 1. **Legacy parse phase** (`parsing-processor`) calls
|
||||
* `symbols.add(...)` per extracted symbol, which fans out via
|
||||
* the dispatch table into `types` / `methods` / `fields`.
|
||||
* 2. **Scope-resolution reconciliation** (`reconcileOwnership` in
|
||||
* `scope-resolution/pipeline/reconcile-ownership.ts`) registers
|
||||
* any `parsed.localDefs[i]` with a scope-resolution-corrected
|
||||
* `ownerId` that the legacy pass missed (Python class-body
|
||||
* methods are the canonical case). Idempotent.
|
||||
* 3. **Finalize-orchestrator** calls `attachScopeIndexes(...)` to
|
||||
* stamp the materialized `ScopeResolutionIndexes` bundle onto
|
||||
* `model.scopes`. One-shot; throws on a second call.
|
||||
*
|
||||
* After these three phases, the model is effectively frozen:
|
||||
* - `attachScopeIndexes` applied `Object.freeze` to its bundle.
|
||||
* - Downstream passes receive the narrowed `SemanticModel` reader
|
||||
* handle (not `MutableSemanticModel`), so `.register()` /
|
||||
* `.clear()` / `attachScopeIndexes()` are structurally absent.
|
||||
*
|
||||
* See `scope-resolution/contract/scope-resolver.ts` Contract
|
||||
* Invariant I9 for the scope-resolution-side rule and
|
||||
* `ARCHITECTURE.md` § "Semantic-model source of truth" for the
|
||||
* overall architecture.
|
||||
*/
|
||||
|
||||
import type { NodeLabel } from 'gitnexus-shared';
|
||||
|
|
|
|||
|
|
@ -138,6 +138,44 @@
|
|||
* `ScopeResolutionIndexes` is a read-guidance surface for
|
||||
* consumers, NOT an immutability promise during the resolve phase.
|
||||
*
|
||||
* - **I9 — `SemanticModel` is the single authoritative symbol store.**
|
||||
* Every symbol-indexed lookup (key = `nodeId | simpleName |
|
||||
* qualifiedName | filePath`) resolves through
|
||||
* `SemanticModel.{symbols,types,methods,fields}`. Scope-resolution
|
||||
* passes MUST NOT maintain parallel owner-keyed or name-keyed
|
||||
* symbol indexes — `WorkspaceResolutionIndex` is reserved for
|
||||
* `Scope`-valued lookups that `SemanticModel` structurally cannot
|
||||
* carry.
|
||||
*
|
||||
* The `runScopeResolution` orchestrator guarantees this invariant
|
||||
* in two steps:
|
||||
* 1. The legacy `parse` phase populates `SemanticModel` via
|
||||
* `symbolTable.add(...)`. For languages whose extractor
|
||||
* resolves `enclosingClassId` at parse time, class-body defs
|
||||
* are correctly owner-keyed there.
|
||||
* 2. The `reconcileOwnership` pass runs after
|
||||
* `provider.populateOwners(parsed)` and registers any def in
|
||||
* `parsed.localDefs[i]` with a corrected `ownerId` that the
|
||||
* legacy pass missed (primarily Python class-body methods).
|
||||
* Idempotent — duplicates are skipped by `nodeId`.
|
||||
*
|
||||
* Contract for consumers: `model` is `MutableSemanticModel` only
|
||||
* during those two write phases. Downstream passes receive a
|
||||
* narrowed `SemanticModel` (read-only) handle. This is enforced by
|
||||
* `runScopeResolution`'s type-level narrowing at the phase
|
||||
* boundary.
|
||||
*
|
||||
* The dev-mode runtime validator (`validateOwnershipParity`)
|
||||
* surfaces any drift between `parsed.localDefs` ownership and the
|
||||
* registries via `onWarn` when
|
||||
* `NODE_ENV !== 'production' && VALIDATE_SEMANTIC_MODEL !== '0'`.
|
||||
*
|
||||
* This invariant is a **transitional shim**: the architectural
|
||||
* end state is for every language's parse-time extractor to emit
|
||||
* the correct `ownerId` directly, removing the need for
|
||||
* reconciliation. Tracked as a follow-up; see ARCHITECTURE.md §
|
||||
* "Semantic-model source of truth".
|
||||
*
|
||||
* ## Semantic-model source of truth
|
||||
*
|
||||
* `ParsedFile` (from `gitnexus-shared/src/scope-resolution/parsed-file.ts`)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue