Documentation
¶
Overview ¶
Package types — confidence overlay (Phase 1C, issue #2769).
Every entity and relationship the indexer emits carries a confidence value in [0.0, 1.0] reflecting how certain the extraction is. The taxonomy below is universal: the same rules apply across every supported language.
Storage:
- On EntityRecord / RelationshipRecord (extractor emission contract): a Confidence float64 field with JSON tag "confidence,omitempty".
- On graph.Entity / graph.Relationship (on-disk schema): same field name.
- Default value (zero / unset) is interpreted by readers as 1.0 — see EffectiveConfidence below.
Propagation passes (Phase 0 constant propagation, Phase 1A effect propagation, future passes) MUST call DecayConfidence with the appropriate source so each additional hop reduces the value geometrically.
Filtering:
- MCP tools accept a `min_confidence` argument (float, default 0.0). When non-zero, results whose effective confidence falls below the threshold are dropped. See internal/mcp/tools.go::argFloat and the per-tool wiring in internal/mcp/server.go.
Package types defines core entity, relationship, and batch types for the grafel pipeline.
Package types defines core entity, relationship, and batch types for the grafel pipeline. See entity.go, relationship.go, batch.go, and job.go for type definitions.
Index ¶
- Constants
- func BaseConfidence(src ConfidenceSource) float64
- func DecayConfidence(src ConfidenceSource, seed float64, hops int) float64
- func EffectiveConfidence(stored float64) float64
- func IaCKindForCategory(category string) string
- func IaCResourceCategory(typeString string) string
- func IsDeclaredRelationshipKind(s string) bool
- func IsDerivedRelationshipKind(s string) bool
- func IsHTTPEndpointCallKind(kind string) bool
- func IsHTTPEndpointDefinitionKind(kind string) bool
- func IsHTTPEndpointKind(kind string) bool
- func IsValidEntityKind(s string) bool
- func IsValidRelationshipKind(s string) bool
- func KnownUndeclaredProducedKinds() map[string]int
- func SortEntityRecordsCanonical(rs []EntityRecord)
- func ValidateProducedEntityKind(site, kind string)
- type BatchRecord
- type ConfidenceSource
- type EnrichmentStatus
- type EntityKind
- type EntityRecord
- type FileBatch
- type FileEntry
- type IndexJob
- type PropKV
- type Props
- func (p Props) Clone() Props
- func (p *Props) Delete(key string)
- func (p Props) Get(key string) string
- func (p Props) Len() int
- func (p Props) Lookup(key string) (string, bool)
- func (p Props) MarshalJSON() ([]byte, error)
- func (p Props) Range(f func(k, v string) bool)
- func (p *Props) Set(key, val string)
- func (p *Props) SetDefault(key, val string) bool
- func (p Props) Snapshot() map[string]string
- func (p *Props) UnmarshalJSON(b []byte) error
- type Relationship
- type RelationshipKind
- type RelationshipRecord
Constants ¶
const ( // EntityBaseSubtypeProperty holds a base-path Subtype that a more // specific custom-extractor Subtype displaced (e.g. "method" displaced by // "endpoint"). The custom value wins because it is more specific; the // displaced value is retained rather than discarded. EntityBaseSubtypeProperty = "grafel.base_subtype" // EntityTwinOfProperty holds the entity ID of the co-located base-path // entity this record is a FACET of — same source construct, same file, // same name, DIFFERENT Kind (e.g. SCOPE.Schema|Order alongside the base // SCOPE.Component|Order that the ORM extractor recognised as a model). // // Both nodes exist in the graph, but for name resolution a facet is an // ALIAS, not a competing definition: it must never flip a name to // ambiguous and never displace its anchor in a symbol table. EntityTwinOfProperty = "grafel.twin_of" // EntityGeneratedProperty is "true" on every entity extracted from a // machine-generated source file (#6329). It is stamped centrally in // extractors.safeExtract, so no extractor can forget it. // // It is deliberately UNPREFIXED, unlike the two constants above. Those are // internal bookkeeping about how a record was merged; this one is a fact // about the source file that ranking, docgen, the quality benchmark and the // security audit are all expected to read — the same category as the // extractor-stamped "local_scope" that internal/mcp/denoise.go consumes. EntityGeneratedProperty = "generated" // EntityGeneratedByProperty names the rule that fired: "path:<pattern>" or // "marker:<name>". // // Provenance is not decoration. Detection is a heuristic over other // people's code, and the near-miss that motivated it — JPA's // @GeneratedValue matching a case-insensitive @generated on 101 // hand-written classes — says there will eventually be a wrong flag. A // wrong flag with no provenance cannot be diagnosed from a user's report. EntityGeneratedByProperty = "generated_by" )
Merge-provenance property keys (issue #6104). The merge boundary between base-path and custom/framework extractor output records what it displaced and what it aliased, so nothing is silently lost.
const ( IaCCategoryDatastore = "datastore" IaCCategoryQueue = "queue" IaCCategoryTopic = "topic" IaCCategoryStream = "stream" IaCCategoryFunction = "function" IaCCategoryCache = "cache" IaCCategorySecret = "secret" IaCCategorySecurity = "security" // IAM / KMS / ACM / Cognito / security groups (#4885) IaCCategoryObservability = "observability" // CloudWatch / X-Ray / GCP logging+monitoring (#4885) IaCCategoryNetwork = "network" IaCCategoryCompute = "compute" IaCCategoryStorage = "storage" IaCCategoryOther = "other" )
IaC resource category constants — the closed set returned by IaCResourceCategory. "other" is the fallback for anything unrecognised.
const ( RelationshipKindCalls RelationshipKind = "CALLS" RelationshipKindImports RelationshipKind = "IMPORTS" RelationshipKindExtends RelationshipKind = "EXTENDS" RelationshipKindImplements RelationshipKind = "IMPLEMENTS" RelationshipKindUses RelationshipKind = "USES" RelationshipKindUsesHook RelationshipKind = "USES_HOOK" RelationshipKindContains RelationshipKind = "CONTAINS" RelationshipKindDependsOn RelationshipKind = "DEPENDS_ON" RelationshipKindReferences RelationshipKind = "REFERENCES" RelationshipKindRoutesTo RelationshipKind = "ROUTES_TO" RelationshipKindServes RelationshipKind = "SERVES" RelationshipKindPublishesTo RelationshipKind = "PUBLISHES_TO" RelationshipKindTests RelationshipKind = "TESTS" RelationshipKindHasProps RelationshipKind = "HAS_PROPS" RelationshipKindAccessesTable RelationshipKind = "ACCESSES_TABLE" RelationshipKindInjectedInto RelationshipKind = "INJECTED_INTO" RelationshipKindReadsFrom RelationshipKind = "READS_FROM" RelationshipKindWritesTo RelationshipKind = "WRITES_TO" // Documented-but-previously-undocumented relationship kinds (Issue #77): RelationshipKindRenders RelationshipKind = "RENDERS" RelationshipKindReturns RelationshipKind = "RETURNS" // #3629: endpoint→request-DTO edge. Emitted by the request/response // extractors (Spring already emits ACCEPTS_INPUT/RETURNS as string // literals; FastAPI, Flask and ASP.NET now mirror it) from a handler/ // endpoint operation entity → the request body DTO/schema type. Partner // to RETURNS so `expand`/`traces`/`payload_drift` can traverse // endpoint→DTO in both directions. RelationshipKindAcceptsInput RelationshipKind = "ACCEPTS_INPUT" // Issue #86: surfaced by the producer-boundary validator scan. Emitted // by the OpenAPI pattern extractor to associate operations with their // tag entities. RelationshipKindTaggedAs RelationshipKind = "TAGGED_AS" // #3639 (epic #3625): DB-migration apply-order edge. Emitted by the // migration-sequence engine pass (Pass 8.9) from a parent migration entity // to the child that must run AFTER it. Currently derived from Alembic's // down_revision → revision DAG (read from the migration file body); the // FromID migration PRECEDES the ToID migration. Lets expand/traces walk the // migration chain in apply order rather than re-deriving it from filenames. RelationshipKindPrecedes RelationshipKind = "PRECEDES" // #3628 [schema]: per-migration schema-operation edge. Emitted by the // migration-schema-ops engine pass from a migration schema-change entity // (Alembic op.create_table / Rails create_table / Django CreateModel / // TypeORM-knex-Sequelize schema ops / Flyway-Liquibase DDL) to the table // it mutates. The edge carries `op` (create_table|add_column|drop_column| // create_index|drop_table|alter_column|…), `table`, and (when known) // `column`. The edge's `table` property uses the SAME normalised table // key that ACCESSES_TABLE uses, so the shared-DB coupling pass and // "what touches table X" queries converge query→table access with // migration→table evolution on one logical table. Complements PRECEDES // (apply-order) with the actual operations a migration performs. RelationshipKindModifiesTable RelationshipKind = "MODIFIES_TABLE" // ADR-0018: Agent-learned pattern edge kinds (append-only additions). // Outgoing from Pattern entities: RelationshipKindExemplar RelationshipKind = "EXEMPLAR" // Pattern -> Entity: real code example of this pattern in use RelationshipKindTouches RelationshipKind = "TOUCHES" // Pattern -> Entity: entity the pattern's steps read or modify RelationshipKindAntiExemplar RelationshipKind = "ANTI_EXEMPLAR" // Pattern -> Entity: real code example of the anti-pattern RelationshipKindSupersedes RelationshipKind = "SUPERSEDES" // Pattern -> Pattern: this pattern replaces an older one RelationshipKindConflictsWith RelationshipKind = "CONFLICTS_WITH" // Pattern -> Pattern: these two patterns cannot both apply RelationshipKindCoAppliesWith RelationshipKind = "CO_APPLIES_WITH" // Pattern -> Pattern: typically applied together RelationshipKindPrerequisite RelationshipKind = "PREREQUISITE" // Pattern -> Pattern: must be satisfied before this one // Incoming to Pattern: RelationshipKindCreatedBy RelationshipKind = "CREATED_BY" // Entity -> Pattern: entity produced using the linked pattern // #713: React Native / Expo platform-specific file variants. // Emitted from a platform-variant file entity (e.g. Button.ios.tsx) // to the canonical base file (Button.tsx). Lets the orphan-rate audit // count platform variants as "connected" to their canonical counterpart. RelationshipKindPlatformVariantOf RelationshipKind = "PLATFORM_VARIANT_OF" // #723: ORM query call site -> model class. Emitted by the engine-layer // applyORMQueries pass for every recognised ORM query call (Prisma, // Django ORM, SQLAlchemy, JPA, gorm, ActiveRecord, etc.). Properties // on the edge: operation, filter_keys, is_join, orm, pattern_type. // Closes the orphan class on model entities referenced only via ORM. RelationshipKindQueries RelationshipKind = "QUERIES" // #3426: MongoDB aggregation-pipeline cross-collection join edge. // Emitted from the aggregating collection/model entity → the `from` // collection named in a `$lookup` / `$graphLookup` stage. This is the // implicit (application-side) join the migration needs for data-flow // reasoning, since MongoDB has no schema-level foreign keys. Properties // on the edge: local_field, foreign_field, as, stage (lookup|graphLookup), // pattern_type. RelationshipKindJoinsCollection RelationshipKind = "JOINS_COLLECTION" // #3611 (epic #3606): Neo4j graph-schema domain-topology edge. Emitted from // an @Node-annotated owner node-label entity → the target @Node entity named // by an @Relationship(type=…, direction=…) field. Mirrors JOINS_COLLECTION // for graph databases: encodes the application's domain graph schema // (e.g. (:Person)-[:ACTED_IN]->(:Movie)) as a traversable subgraph rather // than as opaque string props on a relationship component. Properties on the // edge: rel_type (the Neo4j relationship type), direction (OUTGOING/INCOMING), // field_name, framework, provenance. RelationshipKindGraphRelates RelationshipKind = "GRAPH_RELATES" // #721: Consumer-side HTTP fetch edge. Emitted from a calling // function/method entity → the synthetic http_endpoint entity that // represents the URL the client invokes. Lets the process-flow BFS // and cross-repo HTTP matcher traverse directly from a caller to its // endpoint without re-running the post-hoc regex matcher. RelationshipKindFetches RelationshipKind = "FETCHES" // #726 wave 1: Kafka producer/consumer cross-repo edges. // PUBLISHES_TO : caller method → MessageTopic // SUBSCRIBES_TO : consumer method → MessageTopic // TRANSFORMS : input topic → output topic (a single method is BOTH // @Incoming and @Outgoing — a stream transformer). // PUBLISHES_TO is distinct from the older PUBLISHES_TO above which is // already declared (RelationshipKindPublishesTo) — we reuse that // constant from kafka_edges.go rather than introducing a duplicate. RelationshipKindSubscribesTo RelationshipKind = "SUBSCRIBES_TO" RelationshipKindTransforms RelationshipKind = "TRANSFORMS" // #5686: async-trigger delivery edge. Synthesised intra-repo by // engine.ApplyAsyncTriggerEdges as the DIRECTIONAL INVERSE of a // SUBSCRIBES_TO edge: for every consumer handler that SUBSCRIBES_TO a // topic/queue, the pass emits DELIVERS_TO from the topic → handler. // // PUBLISHES_TO : producer → topic (extractor, unchanged) // SUBSCRIBES_TO : handler → topic (extractor, unchanged) // DELIVERS_TO : topic → handler (synthesised inbound trigger) // // Purpose: async/event-driven handlers whose only trigger is a queue/topic // subscription previously had ZERO inbound edges, so find_callers, // impact_radius and trace all dead-ended at the async boundary (the handler // looked like an orphan with no production callers). DELIVERS_TO gives the // handler an inbound trigger edge and — combined with the existing inbound // PUBLISHES_TO on the topic — completes the publisher → topic → handler // chain for inbound/impact/trace traversal. // // It is deliberately a DISTINCT kind (not CALLS): the pure call graph // (process-flow BFS, calls/called_by) must stay clean. DELIVERS_TO surfaces // only in the inbound/impact/trace/semantic-edge facets that already accept // non-CALLS semantic edges. RelationshipKindDeliversTo RelationshipKind = "DELIVERS_TO" // #727: Real-time event channel edges. Emitted by the engine-layer // synthesizers in internal/engine/{websocket_edges,sse_edges, // graphql_subscriptions}.go. All append-only — they never replace or // modify existing edges, so they cannot regress surrounding passes. // // WebSocket: // WS_SUBSCRIBES_TO handler → ChannelEvent (server: on/onMessage; channel/room as props) // WS_EMITS emitter → ChannelEvent (server: emit/send; scope=broadcast|room|user) // WS_CONNECTS client → ChannelEvent (browser/Node client construct on a channel) // Server-Sent Events: // STREAMS_FROM client → Stream (browser EventSource / polling SSE client) // STREAMS_TO server → Stream (server emits text/event-stream) // GraphQL subscriptions: // GRAPHQL_SUBSCRIBES client → Subscription (Apollo/urql/graphql client subscription) // GRAPHQL_PUBLISHES server → Subscription (resolver / subscriptionType) RelationshipKindWSSubscribesTo RelationshipKind = "WS_SUBSCRIBES_TO" RelationshipKindWSEmits RelationshipKind = "WS_EMITS" RelationshipKindWSConnects RelationshipKind = "WS_CONNECTS" RelationshipKindStreamsFrom RelationshipKind = "STREAMS_FROM" RelationshipKindStreamsTo RelationshipKind = "STREAMS_TO" RelationshipKindGraphQLSubscribes RelationshipKind = "GRAPHQL_SUBSCRIBES" RelationshipKindGraphQLPublishes RelationshipKind = "GRAPHQL_PUBLISHES" // #3624 (epic #3607): GraphQL DataLoader N+1 batch wiring. // BATCHES : SCOPE.DataLoader → batch function (the wrapped per-key // fetch function that the loader coalesces calls into). // The complementary resolver→loader edge reuses RelationshipKindUses // ("USES"), emitted at each `loader.load(id)` call site. RelationshipKindBatches RelationshipKind = "BATCHES" // #728: Scheduled-job and webhook edges. // TRIGGERS : SCOPE.ScheduledJob → handler function/method // (scheduler fires the handler on the declared schedule) // // ORM model lifecycle-hook reuse (#3628 area): // TRIGGERS : SCOPE.ModelEvent:<Model>.<event> → handler function/method // A model persistence lifecycle event (Django post_save, // ActiveRecord after_create, TypeORM @AfterInsert, SQLAlchemy // after_insert event, Sequelize afterCreate hook, Mongoose // post('save') middleware) fires the handler. Same edge kind as // the scheduler→handler case: "this event runs that function". RelationshipKindTriggers RelationshipKind = "TRIGGERS" // #3700 (child of #3628 area #14): background-job enqueue topology. // ENQUEUES : enclosing function (the caller) → SCOPE.ScheduledJob job // entity. Emitted at a dispatch call site that pushes work // onto a background-job queue for asynchronous execution — // e.g. Sidekiq `Worker.perform_async/perform_in/perform_at`, // Resque/Que enqueue calls. This is the queue-system // counterpart of TRIGGERS (scheduler→handler): TRIGGERS says // "fired on a schedule", ENQUEUES says "a caller pushed this // job onto the queue". Distinct from PUBLISHES_TO, which // carries broker pub/sub fan-out semantics. RelationshipKindEnqueues RelationshipKind = "ENQUEUES" // #6741 (Arm 1, ADR-0028): the queue-hop pair. Both strings were already // traversed by four consumers (internal/mcp/flow_tools.go, // internal/mcp/dead_code.go, internal/links/reachability.go, // internal/dashboard/topology_compound.go) and PRODUCES was already emitted // by internal/custom/java/quartz.go; neither was ever declared here. // // PRODUCES : producing site (the enclosing operation of the dispatch // call, or the framework producer entity such as a Quartz // job_builder) → the WORK UNIT the queue carries, addressed // `task:<framework>:<id>` (e.g. `task:hangfire:EmailService.SendConfirmation`). // CONSUMES : that same work unit → the handler that executes it. // // The two hops share the work-unit node, so a reachability walk goes // producer → work unit → handler. CONSUMES therefore points the same way as // TRIGGERS (ScheduledJob → handler) and DELIVERS_TO (topic → handler), not // back at the queue. // // PRODUCES SUPPLEMENTS the CALLS edge a dispatch lambda already yields; it // never replaces it (ADR-0028). CALLS says "this code names that function"; // PRODUCES says "the invocation is deferred through a queue" — a fact CALLS // cannot carry, and one that has no CALLS edge at all in the // `JobBuilder.newJob(SendEmailJob.class)` / string-keyed dispatch shapes. // Same complementary stance as CONSUMES_API vs CALLS→ExternalEndpoint. // // Distinct from ENQUEUES above: ENQUEUES/TRIGGERS is the already-complete // pair for the SCOPE.ScheduledJob topology (Sidekiq/Resque/Que). A pass // that emits ENQUEUES for a hop must not also emit PRODUCES for it. RelationshipKindProduces RelationshipKind = "PRODUCES" RelationshipKindConsumes RelationshipKind = "CONSUMES" // #725: gRPC service definitions + client/server cross-repo edges. // GRPC_IMPLEMENTS : handler method → GrpcMethod (server declares it implements this RPC). // GRPC_HANDLES : client call site → GrpcMethod (client invokes this RPC). // Cross-repo linking: both sides emit GrpcMethod with the same ID // `grpc:ServiceName/MethodName` so the existing import-channel linker joins them. RelationshipKindGRPCImplements RelationshipKind = "GRPC_IMPLEMENTS" RelationshipKindGRPCHandles RelationshipKind = "GRPC_HANDLES" // #925: Serverless function invocation edges. HANDLES is the consumer-side // counterpart of CALLS — a handler function HANDLES a ServerlessFunction. // CALLS is already declared above (RelationshipKindCalls) and is reused here. RelationshipKindHandles RelationshipKind = "HANDLES" // CLI command entry-point edges (epic #3628). HANDLES_COMMAND is the CLI // sibling of an HTTP endpoint's HANDLES edge: it joins a SCOPE.Command // entity to the function that runs when that command is invoked // (Command → handler fn). Emitted by applyCLICommandEdges for // click/argparse/typer (Python), commander/yargs/oclif (Node), cobra (Go), // picocli/Spring Shell (Java), and Thor/Rake (Ruby). RelationshipKindHandlesCommand RelationshipKind = "HANDLES_COMMAND" // #927: Managed event-bus edges. Both sides emit a synthetic EventBusEvent // entity with the same bus-prefixed key, so the import-channel linker joins // producer ↔ rule/consumer without new linker code. // PUBLISHES_TO : producer call site → EventBusEvent (reuses existing constant) // SUBSCRIBES_TO : rule / trigger handler → EventBusEvent (reuses existing constant) // The two edge kinds above are already declared: // RelationshipKindPublishesTo ("PUBLISHES_TO") // RelationshipKindSubscribesTo ("SUBSCRIBES_TO") // New kinds introduced for #927: // EVENTBRIDGE_TRIGGERS : EventBridge rule entity → aws-lambda target // EVENTGRID_TRIGGERS : EventGrid subscription → Azure Function target // CLOUDEVENT_FLOWS : CloudEvent route → CloudEvent route (HTTP metadata match) RelationshipKindEventBridgeTriggers RelationshipKind = "EVENTBRIDGE_TRIGGERS" RelationshipKindEventGridTriggers RelationshipKind = "EVENTGRID_TRIGGERS" RelationshipKindCloudEventFlows RelationshipKind = "CLOUDEVENT_FLOWS" // #1217 (Sub-A of #1115): HTTP endpoint kind split. // UNRESOLVED_FETCH is emitted from an http_endpoint_call to an // http_endpoint_definition when the static linker cannot pair them // (e.g. dynamic base URL, template-literal path, or no matching // definition in the indexed group). Orphan calls that previously // required a post-hoc regex pass (#1099) become first-class graph // citizens via this edge kind. RelationshipKindUnresolvedFetch RelationshipKind = "UNRESOLVED_FETCH" // #1344: Post-rebuild rename / move / split detection. // Emitted from a NEW entity to the OLD entity's ID after the indexer // detects that a function, method, or class was renamed between rebuilds. // Properties: confidence (0.0–1.0), old_name, old_id, method // (name_exact+same_file, name_fuzzy+neighborhood, moved, split, …). // The edge is append-only and never modifies existing entities or edges. RelationshipKindRenamedFrom RelationshipKind = "RENAMED_FROM" // #1374: Django signal/admin connectivity edges. // HANDLES_SIGNAL : signal-handler function entity → sender model entity // (emitted for every @receiver(signal, sender=Model) handler). // REGISTERS : admin class entity → registered model entity // (emitted for admin.site.register(Model[, AdminClass]) and // @admin.register(Model) class AdminClass(…)). // Both edges use the model's bare name as the ToID structural-ref // ("Class:<ModelName>") so the intra-repo resolver matches the existing // SCOPE.Component/class or SCOPE.Schema model entity without new linker code. RelationshipKindHandlesSignal RelationshipKind = "HANDLES_SIGNAL" RelationshipKindRegisters RelationshipKind = "REGISTERS" // #1708: Debezium / Kafka-Connect CDC connector edges. // CAPTURES : cdc_connector → source table (one per element of the // connector's table.include.list). // PUBLISHES_TO is reused (RelationshipKindPublishesTo) for the // connector → kafka:<topic> edge, so downstream Kafka consumers' // SUBSCRIBES_TO edges attach to the same MessageTopic node without // any cross-pass handoff. RelationshipKindCaptures RelationshipKind = "CAPTURES" // #1885: First-class config-entity edges. Emitted by the // internal/extractors/config discovery pass for project-level config // files (Dockerfile, Makefile, pyproject.toml, package.json, pom.xml, // build.gradle, application.properties, .env, …). // // DEPENDS_ON_CONFIG : Module / file entity → SCOPE.Config entity // (the module is configured by the linked Config). // CONFIGURES : SCOPE.Config → consumer module (best-effort // directional inverse; emitted only when the // config's directory contains downstream modules). RelationshipKindDependsOnConfig RelationshipKind = "DEPENDS_ON_CONFIG" RelationshipKindConfigures RelationshipKind = "CONFIGURES" // #5782 (ADR-0025) messaging config↔code↔topic join edges. Emitted by the // config-discovery ChannelBinding recognizer and rebound by the intra-repo // resolver: // // BINDS_CHANNEL : SCOPE.ChannelBinding → SCOPE.Operation, matched by the // shared `channel` property with direction agreement // (incoming binding → @Incoming op; outgoing → @Outgoing). // This is a DISTINCT kind (NOT the reused Helm/DI "BINDS"): // a config→channel binding must never be mislabeled as a // dependency-injection edge (isDIEdgeKind) nor be swept by // the generic ambiguous-bare-name resolver via // hintKinds("BINDS")→componentKindFamily, which could // mis-bind an unresolved channel ref to a coincidentally // named component. Leaving it out of both keeps unresolved // channel refs available for orphan detection. // BINDS_TOPIC : SCOPE.ChannelBinding → SCOPE.MessageTopic, matched by // Name == "kafka:" + topic. RelationshipKindBindsChannel RelationshipKind = "BINDS_CHANNEL" RelationshipKindBindsTopic RelationshipKind = "BINDS_TOPIC" // #2008: DRF SerializerMethodField → method link. // RESOLVED_BY : SCOPE.Schema/field → SCOPE.Operation/method // Emitted for `<field> = serializers.SerializerMethodField(...)` // declarations, pointing at the sibling `get_<field>` (or // `method_name=` kwarg) operation that produces the field's value. // Generalisable to any "value resolved by another entity" shape; // kept narrow today to the DRF extractor producer. RelationshipKindResolvedBy RelationshipKind = "RESOLVED_BY" // #2142: DRF plain serializer field → custom field class. // USES_SCHEMA : SCOPE.Schema/field → custom field class entity // Emitted by the DRF serializer-fields extractor when a plain // serializers.Serializer field references a capitalised, non-DRF-scalar // type (e.g. MoneyField, PhoneField) that may be a project-local custom // field class. Lets the resolver bind the field to its custom type entity // and prevents the field from becoming an orphan node. RelationshipKindUsesSchema RelationshipKind = "USES_SCHEMA" // #2279: Django ORM field-access edges. Emitted by the engine-layer // applyORMFieldEdges pass for every ORM call site (filter/get/exclude/ // values/values_list/order_by/annotate/select_related/prefetch_related // → READS_FIELD; update/create/save/update_or_create/bulk_create/ // bulk_update → WRITES_FIELD). Django lookup suffixes (__icontains, // __in, __gte, etc.) are stripped before field lookup. Only emitted // when the field node (SCOPE.Schema subtype=field, Name=<Model>.<field>) // resolves to an entity in the same file; cross-file resolution is // left to the linker. Append-only — cannot regress surrounding passes. // Closes the orphan class on ORM-only field references (bench Q08 // User.cognito_id was 60+ grep hits, 0 graph edges before this pass). RelationshipKindReadsField RelationshipKind = "READS_FIELD" RelationshipKindWritesField RelationshipKind = "WRITES_FIELD" // for Expo Router / React Navigation / Next.js navigation call sites. // NAVIGATES_TO : caller operation → route stub (synthetic "route:<path>") // Properties: // "route" : destination route/screen name or path // "params" : comma-separated param key names (omitted when empty) // "line" : 1-indexed source line of the navigation call // "via" : "navigation_call" (traceability tag) // Phase 2 (followup): dedicated grafel_navigates MCP query tool. RelationshipKindNavigatesTo RelationshipKind = "NAVIGATES_TO" // #2183 — Monorepo M6: Bazel BUILD-graph fusion. // BAZEL_DEPENDS_ON : bazel_target → bazel_target // Emitted for every entry in a BUILD rule's deps= list that can be // resolved to another target in the same workspace. External deps // (@maven//:guava etc.) also emit this edge; the ToID is a stable // synthetic ID for the external label. // BAZEL_DEP_STATUS : bazel_target → bazel_target // Emitted by the resolver overlay (internal/resolve/bazel_overlay.go) // after cross-referencing declared BUILD deps against inferred CALLS / // IMPORTS edges. Properties["status"] is one of: // "declared+used" — dep confirmed by runtime usage // "declared_unused" — dep declared but no crossing call/import found // "undeclared_used" — call/import crossing with no BUILD dep declared RelationshipKindBazelDependsOn RelationshipKind = "BAZEL_DEPENDS_ON" RelationshipKindBazelDepStatus RelationshipKind = "BAZEL_DEP_STATUS" // #3217 — Go build-system fusion (Cluster 6). // MAGE_DEPENDS_ON : mage_target → mage_target // Emitted by internal/extractors/mage for every target named in a // mg.Deps / mg.SerialDeps / mg.CtxDeps call within a Mage target's // body. The ToID resolves to a sibling target entity when known, or a // stable synthetic ID for an unrecognised dependency. // TASK_DEPENDS_ON : task → task // Emitted by internal/extractors/task for every deps: prerequisite and // every { task: <name> } command reference in a Taskfile task. The // ToID resolves within the same Taskfile, or a synthetic ID for a // namespaced/cross-file dependency. RelationshipKindMageDependsOn RelationshipKind = "MAGE_DEPENDS_ON" RelationshipKindTaskDependsOn RelationshipKind = "TASK_DEPENDS_ON" // #2761: Constant-binding cross-file propagation edge. Emitted by the // Phase 0 substrate (internal/links/constant_propagation.go) once a // use-site identifier resolves to a declaration in another file (or // recursively via IMPORTS). The edge goes from the use-site entity → // the declaring entity. Properties: // "resolved_value" : the literal string value // "resolved_via" : provenance chain (comma-joined steps) // "confidence" : "1.00", "0.85", "0.60", ... (see propagation pass) // Append-only. Consumers (http_endpoint canonicalizer, taint flow, // payload-shape inference) read the resolved_value/confidence pair // without modifying the underlying entity kind. RelationshipKindResolvesTo RelationshipKind = "RESOLVES_TO" // #3628 area #22: SCOPED request-input → sink dataflow edge. Emitted by // the dataflow pass (internal/links/dataflow_pass.go) for a value that // travels from an HTTP request input (req.body.X, request.data['x'], …) // to a recognised sink (DB write / outbound HTTP call / response body) // within a single function body, or across exactly one local-call hop. // The FROM endpoint is the request-handler entity that reads the input; // the TO endpoint is the sink entity/call. Properties carry: // "field" : the source field name when statically known // "sink_kind" : "db_write" | "http_call" | "response" // "sink" : the sink callee/expression as written // "hop_via" : the local function name for a one-hop flow (else absent) // SCOPED & honest-partial: this is NOT full taint analysis. Flows that // cannot be soundly followed (reassignment, branch merges, collection // mutation, >1 hop, cross-file) are dropped, never fabricated. RelationshipKindDataFlowsTo RelationshipKind = "DATA_FLOWS_TO" // #2666: Discriminator comparison edges. Emitted by the JS/TS and Python // extractors for every `identifier == literal` comparison detected in a // function/method body (the discriminator pattern from #2659). // DISCRIMINATES_ON : enclosing operation → synthetic "var:<varName>" stub // Properties: // "line" : 1-indexed source line of the comparison // "literal" : RHS literal value as a string (e.g. "2", "periodic") // Surfaced by grafel_inspect (discriminators section) and mixed into // BM25 doc terms so literal-value queries (e.g. "checklistType 2") rank // the enclosing entity higher. RelationshipKindDiscriminatesOn RelationshipKind = "DISCRIMINATES_ON" // #2885: General branch-condition edges. Emitted by the JS/TS extractor for // every `if`/ternary/`switch` whose controlling expression is a comparison // in a function/method body — including member comparisons that the // narrower discriminator pass (DISCRIMINATES_ON, bare-identifier ===/!== // literal only) misses, e.g. real NativeScript view-model branches like // `if (this._x !== value)` or `if (this._counter <= 0)`. // BRANCHES_ON : enclosing operation → synthetic "branch:<expr>" stub // Properties: // "line" : 1-indexed source line of the branch // "operator" : the comparison operator (===, !==, <=, <, >, >=, ==, !=) // "kind" : "if", "ternary", or "switch" RelationshipKindBranchesOn RelationshipKind = "BRANCHES_ON" // #3100: Bean Validation custom ConstraintValidator<A,T> implementations. // CustomValidator represents a class that implements ConstraintValidator<A,T> // and provides the validation logic for a custom constraint annotation. // Subtype "constraint_validator" is used to distinguish from generic component types. EntityKindCustomValidator EntityKind = "SCOPE.CustomValidator" // #3628 area #17: Feature-flag entities. FeatureFlag represents a single // feature-flag key checked at runtime via a flag-management SDK // (LaunchDarkly, Unleash, OpenFeature, Flipper, Flagsmith) or a generic // config flag. Cross-repo / cross-file identity = the flag key string, // encoded in the synthetic ID `feature:<flag-key>`. One entity per // distinct key per repo; the enclosing function that checks the flag // links to it via GATED_BY so the graph can answer "what code is gated // by flag X" (flag blast-radius). Subtype = the SDK that detected it // ("launchdarkly", "unleash", "openfeature", "flipper", "flagsmith"). EntityKindFeatureFlag EntityKind = "SCOPE.FeatureFlag" // #3628 area #25: Plugin / extension-system entities. Plugin represents a // single plugin / extension that a build tool, application, or framework // registers — e.g. a Webpack/Vite/Rollup plugin in a bundler config, a // Babel/ESLint plugin, a pytest plugin or setuptools entry-point plugin, or // a Maven/Gradle build plugin. Cross-file identity = the plugin's name // string within its ecosystem, encoded in the synthetic ID // `plugin:<ecosystem>:<name>`. The config / build file that declares the // plugin links to it via REGISTERS_PLUGIN so the graph can answer "which // plugins does this build/app register". Subtype = the registering system // ("webpack", "vite", "rollup", "babel", "eslint", "pytest", // "setuptools", "maven", "gradle"). EntityKindPlugin EntityKind = "SCOPE.Plugin" // #2904: Request-validation / DTO-extraction linkage edges. Emitted by // the JS/TS extractor when a route handler / controller method is wired // to a schema validator or a typed DTO, turning validators that were // previously seen only as imports into a route↔validator graph edge. // VALIDATES : enclosing operation (route handler) → synthetic stub // - "validator:<lib>" for call-site validation (zod .parse/.safeParse, // joi/yup .validate, express-validator validationResult/check/body, // class-validator validate/validateOrReject) // - "dto:<TypeName>" for NestJS `@Body()/@Query()/@Param() x: Dto` // parameter-decorator DTO extraction // Properties: // "library" : the validator library ("zod", "joi", "yup", // "express-validator", "class-validator", "nestjs-dto") // "method" : the validation method or decorator observed // "dto" : the DTO type name (dto_extraction edges only) // "line" : 1-indexed source line of the call / parameter // "via" : "request_validation" or "dto_extraction" (capability tag) // Append-only — never modifies existing entities or edges. RelationshipKindValidates RelationshipKind = "VALIDATES" // #3520: Kustomize overlay-patch edge. Emitted by the YAML extractor's // Kustomize flavor for every entry in `patches:` / `patchesStrategicMerge:` // / `patchesJson6902:`. The edge goes from the kustomization entity → // a synthetic patch-target stub ("kustomize_target:<Kind>/<name>" when the // patch declares a target, or "kustomize_patch_file:<path>" for a bare // strategic-merge file reference). Properties: // "patch_style" : "strategic_merge" | "json6902" | "inline" // "target_kind" : the K8s Kind named by the patch target (when present) // "target_name" : the metadata.name named by the patch target (when present) // Lets the overlay graph show which base resources an overlay mutates. // Append-only — never modifies existing entities or edges. RelationshipKindPatches RelationshipKind = "PATCHES" // #3526: Helm chart edges. Emitted by the YAML extractor's Helm flavors. // // BINDS : a templates/*.yaml entity → a values.yaml "values_key" // entity, for every `{{ .Values.<dotted.path> }}` reference in // the template. ToID is the synthetic stub // "helm_values:<dotted.path>" which matches the QualifiedName of // the values_key entity emitted from values.yaml, so the edge // resolves cross-file within the chart. Properties: // "binding_kind" : "helm_values_ref" // "values_path" : the dotted path under .Values // // INCLUDES : a template / helper → a named template ("helm_template:<name>") // for every `{{ include "name" . }}` or `{{ template "name" . }}` // reference. ToID matches the QualifiedName of the // named_template entity emitted from _helpers.tpl. Properties: // "include_kind" : "helm_include" // "template_name" : the named-template name // // Both are append-only — they never modify existing entities or edges. RelationshipKindBinds RelationshipKind = "BINDS" RelationshipKindIncludes RelationshipKind = "INCLUDES" // #3552: Helm parent-chart values override edge. Emitted by the YAML // extractor's Helm values flavor when a parent chart's values.yaml carries a // top-level block whose key matches a declared subchart (name or alias) in the // sibling Chart.yaml. Each nested key under that block overrides the // subchart's own values key — the cross-chart values data-flow. The edge goes // from the parent values.yaml entity → a synthetic subchart-values stub // "helm_subchart_values:<subchart>:<dotted.path>". Properties: // "override_kind" : "helm_subchart_value" // "subchart" : the subchart name (or alias) the block targets // "values_path" : the dotted path under the subchart's values root // "parent_path" : the dotted path in the parent values tree // Append-only — never modifies existing entities or edges. RelationshipKindOverrides RelationshipKind = "OVERRIDES" // #3632 (epic #3625): API-consumption edge. Emitted by the // internal/extractors/cross/consumes_api Pass-3 cross extractor for every // HTTP client call site whose (verb, path) matches a server endpoint // declared in the SAME source file (co-located client + server — BFFs, API // gateways, Django views that call their own API, integration-test // harnesses). The edge goes from the consuming caller stub // ("scope:component:http_caller:<file>") → the canonical endpoint entity ID // ("scope:endpoint:<file>#<VERB>:<canonical-path>") emitted by the // _cross_endpoint extractor. Properties: // "method" : the HTTP verb of the client call // "matched_url" : the raw client URL/pattern that produced the match // "matched_path" : the path component used for the join // "endpoint_path" : the server endpoint's canonical path // "via" : "same_file_http_consumption" (capability tag) // "provenance" : "INFERRED_FROM_HTTP_CLIENT_CALL" // This is the *consumption* semantic — it is complementary to, not a // duplicate of, the CALLS→ExternalEndpoint edge emitted by _cross_httpclient and // the cross-repo MethodHTTP links emitted by internal/links/http_pass.go // (which only fires for groups of ≥2 repos via synthetic http_endpoint // entities). Cross-org / cross-repo consumption stays owned by the links // layer; this extractor never crosses a file boundary, so its org guard is // trivially satisfied. Append-only — never modifies existing entities/edges. RelationshipKindConsumesAPI RelationshipKind = "CONSUMES_API" // #3689 (epic #3628, area #11): OpenTelemetry tracing-span instrumentation // edge. Emitted by the per-language tracing-span passes (Python, Go, JS/TS, // Java) from the enclosing function/method operation entity → a synthetic // span stub ("span:<name>" for a string-literal span name, "span:<fn>" for // a dynamic/variable span name). The edge records WHERE a distributed-tracing // span is created and WHICH operation it instruments. Mirrors the // DISCRIMINATES_ON / BRANCHES_ON / NAVIGATES_TO precedent (enclosing op → // synthetic stub) so no new entity Kind is introduced. Properties: // "span_name" : the static span name (omitted when dynamic) // "library" : "opentelemetry" // "api" : the idiom observed (e.g. "start_as_current_span", // "tracer.Start", "startActiveSpan", "spanBuilder", // "WithSpan") // "line" : 1-indexed source line of the span-creation site // "traced" : "true" (always; lets honest-partial dynamic-name spans // carry the flag without a fabricated name) // "dynamic" : "true" when the span name is a variable/expression rather // than a string literal (omitted otherwise) // Append-only — never modifies existing entities or edges. RelationshipKindInstruments RelationshipKind = "INSTRUMENTS" // #3692 (epic #3628, area #18): caching-topology edges. Emitted by the // per-framework caching passes (Spring @Cacheable/@CachePut/@CacheEvict, // Python @lru_cache/Flask-Caching/cachetools, NestJS @CacheKey/cache-manager, // Rails Rails.cache.fetch/delete) from the enclosing function/method // operation entity → a synthetic cache-region/key target entity // ("cache:<framework>:<region-or-key>", Kind SCOPE.Datastore, subtype // "cache_region"). Two directions: // CACHES : read-through / write cache population // (@Cacheable, @CachePut, lru_cache, cache.fetch{}, cache.wrap). // The function's result is stored/served under the region/key. // INVALIDATES : cache eviction (@CacheEvict, Rails.cache.delete, // cacheManager.del). The function clears the region/key. // Multiple call-sites that touch the same region converge on one target node, // so CACHES and INVALIDATES edges for the same region are traversable together // (a producer ↔ invalidator subgraph). Properties on the edge: // "framework" : spring | flask_caching | cachetools | lru_cache | nestjs | // cache_manager | rails // "region" : the cache region/name (Spring value=, Flask key_prefix, // NestJS CacheKey) when statically resolvable // "key" : the static cache key when resolvable (Rails fetch literal, // cache.wrap literal) // "mode" : read_through | write | evict | in_process // "dynamic" : "true" when the region/key is a runtime expression // (honest-partial: edge + mode recorded, target labelled // "<dynamic>") // Append-only — never modifies existing entities or edges. RelationshipKindCaches RelationshipKind = "CACHES" RelationshipKindInvalidates RelationshipKind = "INVALIDATES" // #3628 area #17: Feature-flag gating edge. Emitted by the engine // feature_flag_edges pass for every flag-check call site detected via a // flag-management SDK (LaunchDarkly, Unleash, OpenFeature, Flipper, // Flagsmith) or a generic config flag. // GATED_BY : enclosing function/method → SCOPE.FeatureFlag entity // (synthetic ID `feature:<flag-key>`). Reads "this code path // is gated by feature flag X", enabling flag blast-radius. // Properties: // "flag" : the flag key string // "sdk" : the SDK that detected the check ("launchdarkly", // "unleash", "openfeature", "flipper", "flagsmith") // "method" : the SDK call/decorator observed (e.g. "variation", // "isEnabled", "getBooleanValue", "Flipper.enabled?") // "line" : 1-indexed source line of the flag-check call site // Dynamic flag keys (non-literal first argument) are NOT emitted — no // fabricated flag entity. Append-only; never modifies existing edges. RelationshipKindGatedBy RelationshipKind = "GATED_BY" // #3628 area #25: plugin / extension-system registration edge. // REGISTERS_PLUGIN : config / build file (or app entry) → SCOPE.Plugin // entity (synthetic ID `plugin:<ecosystem>:<name>`). // Reads "this build/app registers plugin X", enabling // "which plugins does this build register" queries. // Emitted by the plugin-system pass (plugin_system_edges.go) for Webpack / // Vite / Rollup bundler configs, Babel / ESLint configs, pytest + // setuptools entry-point plugins, and Maven / Gradle build plugins. // Properties: // "plugin" : the plugin name string // "system" : the registering system ("webpack", "vite", "rollup", // "babel", "eslint", "pytest", "setuptools", "maven", // "gradle") // "group" : (setuptools only) the entry-point group the plugin // registers under (e.g. "flake8.extension") // "line" : 1-indexed source line of the plugin declaration // Dynamically-computed plugin names (non-literal) are NOT emitted — no // fabricated plugin entity. Append-only; never modifies existing edges. RelationshipKindRegistersPlugin RelationshipKind = "REGISTERS_PLUGIN" // #3704 (epic #3628, area #20): finite-state-machine transition edge. // Emitted by the FSM-topology pass (state_machine_edges.go) for XState // (JS/TS), Ruby AASM, Spring StateMachine (Java), and the Python // `transitions` library. Direction follows the transition: source state → // target state. Both endpoints are SCOPE.State entities. Properties: // "event" : the triggering event / trigger name (e.g. "FETCH", // "activate", "START", "go"); omitted for event-less // (e.g. always/eventless) transitions. // "library" : the FSM library ("xstate" | "aasm" | "spring-statemachine" // | "python-transitions"). // "machine" : the owning machine / class name. // Append-only — never modifies existing entities or edges. RelationshipKindTransitionsTo RelationshipKind = "TRANSITIONS_TO" // the project-scope shared-db-coupling pass (engine.ApplySharedDataCoupling) // between two synthetic Module entities that BOTH access the same table or // collection entity (via ACCESSES_TABLE / JOINS_COLLECTION / SCOPE.DataAccess // attribution). It is the cross-service data-ownership / boundary-violation // signal: when ≥2 distinct modules touch one table, those modules are // data-coupled even with no direct call/import edge between them. Properties // on the edge: coupling=shared_data, shared_tables (comma-joined sorted list // of the co-accessed table/collection names), shared_count (how many tables // the pair co-accesses), provenance=SHARED_DB_COUPLING. The edge is // undirected in meaning but emitted once per unordered module pair (the // lexicographically smaller module ID is FromID) so it is deterministic and // not double-counted. RelationshipKindSharesData RelationshipKind = "SHARES_DATA" // granularity. Emitted by the project-scope shared-table-coupling pass // (engine.ApplySharedTableCouplingEdges) between two canonical // `service:<repo>` SCOPE.Service entities that BOTH access the SAME resolved // physical table (normalised across schema/alias), when those accessors live // in DISTINCT repos/services AND at least one side WRITES the table. Where // SHARES_DATA models intra-repo module↔module data coupling, SHARES_TABLE_WITH // is the orthogonal CROSS-SERVICE hidden-coupling smell the Django→NestJS // rewrite cares about: two services silently sharing one physical table is // exactly what a clean service split must break apart. Properties on the // edge: table (the normalised physical table name), access_from / access_to // (the read/write access kinds per side, comma-joined sorted, e.g. // "read,write"), writer ("from"|"to"|"both" — which side(s) write, so a // read-only pair is never emitted), confidence ("high" — only resolved // literal table identities qualify), provenance=SHARED_TABLE_COUPLING. The // edge is undirected in meaning but emitted once per unordered service pair // (the lexicographically smaller service ID is FromID) so it is // deterministic and idempotent. Honest: dynamic/unresolved tables and // same-service multi-module access never fire it. RelationshipKindSharesTableWith RelationshipKind = "SHARES_TABLE_WITH" // #3623 (epic #3607): Apollo Federation cross-subgraph entity edge. // FEDERATES : the extending subgraph's `extend type Foo @key(fields:"id")` // SCOPE.Component stub → the owning entity type `Foo`. The edge // records that this subgraph contributes fields to an entity // whose canonical definition (the @key-bearing `type Foo`) lives // in another subgraph. Properties on the edge: // federation=apollo, key_fields (the @key selection set), // external_fields / requires_fields / provides_fields // (comma-joined field names carrying @external / @requires / // @provides), and import_kind=federation_extend. Emitted by // internal/extractors/graphql/graphql.go. It is the // cross-subgraph entity-ownership signal Federation gateways // use to plan query fan-out. RelationshipKindFederates RelationshipKind = "FEDERATES" // #3628 error-flow: exception / error-contract edges. Both point a // callable (function / method) at a synthetic SCOPE.ExceptionType node // (Name "exception:<Type>") so a type raised in one place and caught in // another converge on a single node — the join that makes the graph // answer "what can this function raise?" and "where is X handled?". // // THROWS : function/method → SCOPE.ExceptionType it can raise. // Emitted only for an IDENTIFIABLE type: // JS/TS `throw new ValidationError(...)` / bare `throw new Error()` // Python `raise NotFound(...)` // Java `throw new IllegalArgumentException()` + method `throws IOException` // Go `return ErrNotFound` (named error var/type only) // CATCHES : handler function/method → SCOPE.ExceptionType it catches. // Emitted only when the caught type is identifiable: // JS/TS typed `catch` via `e instanceof AuthError` // Python `except (ValueError, KeyError):` // Java `catch (IOException e)` // Go `errors.Is(err, ErrNotFound)` // // Precision-first / honest-partial: dynamic-or-computed raise types, bare // `except:` / untyped `catch(e){}`, and anonymous inline errors // (`errors.New("...")`, bare `fmt.Errorf("...")`) emit NO edge — a wrong // THROWS/CATCHES edge would mislead error-contract analysis. See // internal/extractor/exception_flow.go. RelationshipKindThrows RelationshipKind = "THROWS" RelationshipKindCatches RelationshipKind = "CATCHES" // #3628 third-party integration: a function/method calls a recognised // external-service SDK entry-point. // // DEPENDS_ON_SERVICE : function/method → SCOPE.ExternalService node // (Name "service:<name>") it integrates with. The // target service is identified from the SDK // import/symbol context, NOT a bare method name: // Python `stripe.Charge.create(...)` → stripe // `boto3.client("s3").put_object(...)` → aws-s3 // JS/TS `new Stripe(key); stripe.charges.create()` → stripe // `sgMail.send(...)` → sendgrid // `new S3Client(...).send(cmd)` → aws-s3 // Optional edge property `operation` carries the SDK // call (e.g. "charges.create", "put_object"). // // Precision-first / honest-partial: a dynamic boto3 service string // (`boto3.client(svc_var)`) resolves to aws-generic; an unrecognised SDK or // a bare `.create()`/`.send()` on a non-SDK object emits NO edge — a wrong // integration edge would mislead "what services do we depend on?". See // internal/extractor/external_service.go. RelationshipKindDependsOnService RelationshipKind = "DEPENDS_ON_SERVICE" // RelationshipKindUsesTranslation points an enclosing function / component at // a synthetic SCOPE.TranslationKey node for each STATIC i18n key it // references via a recognised translation function (localization capability, // child of #3628). Direction: caller → key. Two callers that reference the // same key converge on ONE key node, so the node's inbound USES_TRANSLATION // set is the key's full reference footprint ("where is 'errors.notFound' // used?") and the absence of a backing catalog flags an untranslated key. // Recognised shapes: // JS/TS react-i18next/i18next `t('errors.notFound')`, `i18n.t('x')`, // `<Trans i18nKey="x">`; vue-i18n `$t('x')` / `t('x')` (useI18n). // Python Django/gettext `_('Welcome')`, `gettext('x')`, `gettext_lazy('x')`. // Ruby Rails `I18n.t('users.title')`, relative `t('.title')`. // PHP Laravel `__('messages.welcome')`, `trans('x')`. // Precision-first / honest-partial: emitted ONLY when the i18n CONTEXT is // recognised (import or unambiguous framework symbol) and the key is a static // literal — a dynamic key (`t(keyVar)`) or a non-i18n `_('x')` (lodash) / // unrelated `t(...)` emits NO edge. See internal/extractor/translation_key.go. RelationshipKindUsesTranslation RelationshipKind = "USES_TRANSLATION" // [realtime] WS room/channel grouping (child of #3628). Both edges point a // callable (function / method) at a synthetic SCOPE.Channel node // (Name "channel:<room>") so a join and a broadcast on the SAME room // converge on one node — the join that makes the graph answer // "who joins / broadcasts to room X?". // // JOINS_CHANNEL : function/method → SCOPE.Channel it subscribes a // participant to. Emitted for an IDENTIFIABLE literal room: // socket.io `socket.join('room1')` // ActionCable `stream_from 'chat_1'` / `stream_for @x`(skip dynamic) // Django Channels `self.channel_layer.group_add('chat', ...)` // BROADCASTS_TO : function/method → SCOPE.Channel it publishes a message to. // socket.io `io.to('room1').emit('ev')` / // `socket.broadcast.to('room1').emit()` / // `io.in('room1').emit()` // ActionCable `ActionCable.server.broadcast('chat_1', ...)` / // `ChatChannel.broadcast_to(room, ...)`(skip dynamic) // Django Channels `self.channel_layer.group_send('chat', ...)` // Phoenix `broadcast(socket, "ev", payload)` (topic) / // `MyApp.Endpoint.broadcast("room:1", ...)` // // Precision-first / honest-partial: dynamic room names (a bare variable, a // template literal) emit NO edge, and a non-socket `.join` (array join) is // rejected by requiring a socket/cable/channels context. See // internal/engine/ws_channel_grouping.go. RelationshipKindJoinsChannel RelationshipKind = "JOINS_CHANNEL" RelationshipKindBroadcastsTo RelationshipKind = "BROADCASTS_TO" // [sbom] DEPENDS_ON_PACKAGE points a manifest's project-anchor entity at a // synthetic SCOPE.Package node (Name "package:<ecosystem>:<name>") it // declares as an external dependency. Distinct from the file-scoped // DEPENDS_ON(kind=external_dependency) edge: this edge targets the // CONVERGED, file/repo-agnostic package node so the SAME package declared in // many repos shares one inbound set — the graph's software bill-of-materials // (SBOM). ToID is the package entity's QualifiedName so the resolver binds // it via the byQualifiedName exact-match tier. Edge properties: // "package_manager" : ecosystem (npm, pip, go_modules, maven, gradle, // cargo, bundler, composer, ...). // "version" : the declared version range / pin (verbatim; "" when // the manifest omits it — honest-partial). // "dev" : "true" when the package is a dev/test-only dependency. // "dependency_kind" : runtime|dev|peer|locked|indirect (from the manifest). // Emitted by internal/extractors/cross/manifest/extractor.go. RelationshipKindDependsOnPackage RelationshipKind = "DEPENDS_ON_PACKAGE" // #3834 (epic #3829, MRO T4): member-granularity inheritance edge. // INHERITS : an inherited member STUB (e.g. a bodyless DRF // `RoleViewSet.retrieve` synthetic, or a `ChildService.handle` // the child never redeclares) → the DEFINING member that owns // the real body (the in-repo base method, or — when the base is // external and the body is not indexed — a synthetic contract // node from the baseknowledge pack). // This is the member-level counterpart of the class-level EXTENDS edge: it // lets neighbors/def_use/trace HOP from the inherited stub (which has empty // CALLS/called_by) to the defining body's call graph, so traversal reaches // the real implementation instead of dead-ending at the bodyless node (the // rewrite agent's G5). Edge properties: // "member" : the inherited member's bare name (e.g. "retrieve"). // "owning_class" : the subclass that inherits the member. // "defining_class" : FQN of the class whose body defines the member // (in-repo base name, or external pack FQN such as // "rest_framework.mixins.RetrieveModelMixin"). // "resolved_from" : "extends_in_repo" | "baseknowledge_pack". // "external" : "true" when the defining body is an external library // member described only by the pack (no in-repo body). // The MCP read path (internal/mcp/mro_traverse.go) projects this edge // synthetically from the EXTENDS walk + pack so it works before any // indexer-side emission; an indexer producer MAY later materialise it. RelationshipKindInherits RelationshipKind = "INHERITS" // #4306 (Layer 1 of epic #4294): deterministic markdown doc ingestion. // MENTIONS : SCOPE.Section → code entity. Emitted when a section's body // text contains an identifier token that EXACTLY matches a // code entity's Name or QualifiedName in the current index // (word-boundary, case-sensitive). Precision-first: keywords, // very common short words, and sub-threshold-length tokens are // skipped, and a token that matches more than one distinct // entity is dropped (ambiguous → no edge). Under-linking is // preferred to noisy links. OPT-IN (--ingest-docs), no LLM. // The document→section hierarchy uses the existing CONTAINS // edge (RelationshipKindContains); no new hierarchy kind. RelationshipKindMentions RelationshipKind = "MENTIONS" // #4657 IaC module instantiation: an environment stack's module instance // (e.g. envs/prod/main.tf `module.worker`) → the module DEFINITION // directory it instantiates (e.g. modules/worker-service). Connects the // env stacks to the shared module definitions in the IaC architecture view. RelationshipKindInstantiates RelationshipKind = "INSTANTIATES" // #4308/#4309 (Layer 2 of epic #4294): agent-driven semantic doc ingestion. // RATIONALE_FOR : SCOPE.DesignDecision → code entity. Emitted by the // apply step when an external agent classifies a Section // as stating a design decision / rationale / spec that // justifies a referenced code entity. grafel only // VALIDATES (the target entity must exist in the current // graph) and APPLIES what the agent produced — no LLM // call. The DesignDecision→Section anchoring uses the // existing CONTAINS edge; no new hierarchy kind. OPT-IN. RelationshipKindRationaleFor RelationshipKind = "RATIONALE_FOR" // #4929 (follow-up #4903): Erlang/OTP supervision-tree edge. Emitted from a // supervisor module entity → each child module named in its `init/1` child // spec list. The child spec may be a modern map // (`#{id => ..., start => {Mod, Fun, Args}}`) or the legacy tuple // (`{Id, {M, F, A}, Restart, Shutdown, Type, Modules}`); in both forms the // child module is the `M` of the `start` MFA. Properties on the edge: // child_id (the spec's id), provenance ("otp_child_spec"). Makes the OTP // supervision hierarchy a traversable subgraph rather than being buried // inside the supervisor's init body. RelationshipKindSupervises RelationshipKind = "SUPERVISES" // #5074 object-mapping topology: a configured object-mapping // (AutoMapper Profile.CreateMap<TSrc,TDest> / Mapster NewConfig / // .Adapt<T>()) from a source DTO/entity type to a destination type. // MAPS_TO : source type (SCOPE.Schema/class) → destination type // Carries the mapping framework, the optional member-mapping count and // the owning profile on edge properties so DTO↔entity mapping topology is // traversable rather than buried in a mapper configuration class. RelationshipKindMapsTo RelationshipKind = "MAPS_TO" // #4983 infra↔code deployment topology: an IaC compute resource (a // Kubernetes workload, a docker-compose service with an image/build, a // serverless function) → the CODE service/module/image it runs. // DEPLOYS : infra compute resource → canonical code `service:<name>` node // Resolved by container-image repository name / compose service name / // serverless handler source. Carries an `inferred` confidence marker so the // dashboard can style the deploy-time mapping distinctly from identity and // typed-usage cross-links (Topology Model 2/3, #4810). RelationshipKindDeploys RelationshipKind = "DEPLOYS" )
const KindVocabularyVersion = 1
KindVocabularyVersion is the version of the ENTITY-KIND VOCABULARY — the set of kind strings a graph written by this build actually carries.
It is deliberately a THIRD number, distinct from the two that already exist, because all three change for different reasons (#6779):
- graph.SchemaVersion versions the SHAPE of graph.json.
- fbversion.Version versions the FlatBuffers PAYLOAD FORMAT of graph.fb, and a bump there makes internal/graph's min-version gate REJECT the stored graph, which internal/daemon turns into an automatic reindex.
- this versions the kind STRINGS inside an otherwise perfectly readable graph. Renaming a kind breaks no format and no schema: the old graph loads fine and simply answers queries about the new kind with silence.
A rename must therefore NOT be expressed as an fbversion bump. That would reindex every registered group behind the user's back, and reindexing a large group is expensive enough that the user gets to choose when to pay for it (the same call taken on #6757 arm C). Mismatch here is DETECTED AND REPORTED — through `grafel doctor` — never auto-migrated and never auto-reindexed.
Bump this whenever an entity kind is RENAMED or RETIRED ¶
Adding a brand-new kind does not require a bump: no already-stored entity changes spelling, and a query for the new kind correctly finds nothing in an older graph because nothing of that kind was ever extracted. Renaming or retiring one DOES: entities already on disk keep the old spelling forever, and every consumer filtering on the new spelling silently sees an empty result set.
Version 1 (#6779) — the mechanism's baseline. Graphs indexed before it existed carry no stamp at all, decode as 0, and are correctly reported as speaking an older vocabulary: v0.3.1 shipped exactly two such renames (#6451 retired SCOPE.ExternalAPI in favour of SCOPE.ExternalEndpoint and SCOPE.IngressHost; #6499 made Kotlin SCOPE.Operation names class-qualified) with nothing but a release note to migrate them.
const PropBindTier = "to_bind_tier"
PropBindTier is the RelationshipRecord property key under which the reference resolver records WHICH TIER chose the edge's ToID, and it is written ONLY when that tier was a lexical guess: the target was picked from a bare name plus a locality (same file, same package directory, same crate, "the only one in the graph"), with no type evidence from the extractor.
Issue #7071. Before this key existed, a CALLS edge bound because `Process` happened to be the only same-named method in the caller's file was byte-identical to one bound on a qualified name the extractor actually resolved. Every recall instrument we own — bind rate, orphan rate, dangle count, the golden ratchet's relationship_found — scores both as an equal success, so an extractor regression that stops typing receivers moves none of them.
WHY NOT THE KEY "confidence". internal/graph/algorithms.go:572-586 (edgeWeight) reads props["confidence"] as a float multiplier on the edge weight that feeds PageRank, betweenness, Louvain communities AND the graph-algorithm determinism hash. Writing a marker under that key — with any parseable sub-1.0 value — would silently re-weight every centrality and community number in every indexed corpus. That is a separate, separately-measured decision; this key is deliberately disjoint from it.
WHY A PROPERTY AND NOT RelationshipRecord.Confidence. The FlatBuffers `Relationship` table (internal/graph/schema/graph.fbs:104-109) carries from_id / to_id / kind / properties and nothing else. A Confidence float is dropped at write time and reads back as 0 in the daemon, so a marker stored there could never reach the MCP surface, the dashboard, or `grafel quality`. Properties are in the schema and survive the round trip.
ABSENCE MEANS "not a guess", which covers two different things: the edge was bound by an evidence tier (exact qualified name, a structural address the extractor minted, a resolved qualifier, a stamped receiver type), or it was never bound at all. Consumers that care about the difference must also look at whether ToID is an entity ID.
That reading is only as good as the list of sites that stamp the key, and THAT LIST IS NOT A UNIVERSAL. An earlier revision of this comment claimed "every pass that can bind a name lexically stamps this key"; two successive reviews of #7078 each found one more rung it was false of — first ResolveImports' plain-import and wildcard rungs, then the same-class fallback three lines above them. A completeness claim that the next person to read the code can refute is worse than no claim, because it discourages the reading. So it is replaced by two lists that can be wrong in a VISIBLE way:
WHAT STAMPS THIS KEY is enumerated, one constant each with its site and its argument, in internal/resolve.AllBindTiers. That enumeration is the authority; this file deliberately does not duplicate it.
WHAT HAS BEEN CHECKED AND FOUND NOT TO BE A GUESS, and is therefore deliberately unmarked, is recorded as an "EVIDENCE (#7071)" comment AT EACH SUCH SITE rather than in a central list — exact QualifiedName and structural-ref hits and the explicit-Kind bucket in internal/resolve/refs.go; the explicit from-import binding, the module-and-submodule receiver rungs of ResolveCrossModuleCallTarget, the `ext:` ResolvedToID, and the candidate-directory rung of the Rust pass in internal/resolve/imports.go.
WHAT HAS NOT BEEN CHECKED is everything else, and there is no claim here that the set is empty. A binding site carrying neither a tier nor an "EVIDENCE (#7071)" note has simply not been classified; finding one is a finding, not a contradiction. Two rounds of review each found one, so the prior should be that more exist.
SCOPE: the key describes the TO endpoint only, which is why it is named to_*. The same tiers can fire on a FROM endpoint rewrite (internal/resolve/refs.go's rewriteOneWithCaller is called for FromID too); those binds are deliberately NOT marked here, because one key per edge cannot carry two answers. That is a known, named gap, not an oversight.
Variables ¶
This section is empty.
Functions ¶
func BaseConfidence ¶
func BaseConfidence(src ConfidenceSource) float64
BaseConfidence returns the per-source base confidence at hop=0. For sources without a hop dimension this is the final value.
func DecayConfidence ¶
func DecayConfidence(src ConfidenceSource, seed float64, hops int) float64
DecayConfidence computes the confidence after N hops from a seed of value `seed` along the given propagation source. For non-propagation sources (direct_ast, regex_pattern, yaml_rule_pattern, fallback_speculation), hops is ignored and the base value is returned.
DecayConfidence(SourceInferredViaCallsHop, 1.0, 3) = 1.0 * 0.95^3 ≈ 0.857 DecayConfidence(SourceResolvedViaConstantPropagation, 0.9, 2) = 0.9 * 0.85^2 = 0.65
func EffectiveConfidence ¶
EffectiveConfidence returns the read-side confidence value for a record. A zero (unset) value is interpreted as 1.0 so legacy graphs and extractors that never call StampConfidence keep working — the issue explicitly says "default if not specified: 1.0 for direct AST matches".
Negative and >1.0 values are clamped to [0, 1].
func IaCKindForCategory ¶
IaCKindForCategory maps a resource_category to the semantic SCOPE.* entity Kind that CloudFormation uses, so the CFN Kind and the property are derived from one source and can never drift. Categories without a dedicated CFN Kind fall back to SCOPE.InfraResource (the shared IaC-resource class). This keeps the historical CFN Kinds (Datastore / Queue / ServerlessFunction) intact while guaranteeing they agree with IaCResourceCategory.
func IaCResourceCategory ¶
IaCResourceCategory maps an IaC resource type string — in ANY of the tool dialects the catalog covers — to one of the IaCCategory* constants. It walks the declarative iacCategoryCatalog in order and returns the first matching rule's category; "other" if none match. Matching is case-insensitive on substrings, tolerating the differing shapes the tools use for the "same" resource.
func IsDeclaredRelationshipKind ¶ added in v0.3.2
IsDeclaredRelationshipKind reports whether s belongs to EITHER relationship vocabulary — structural or derived.
This is the predicate for "does the graph's vocabulary know this kind at all", as opposed to IsValidRelationshipKind's narrower "is this a structural kind". Callers that classify unknown kinds (#6757 arm B's ledger, arm C's write-path counter) use this one, so that a kind declared in either place is declared for both of them.
func IsDerivedRelationshipKind ¶ added in v0.3.2
IsDerivedRelationshipKind reports whether s is one of the derived (statistical) relationship kinds. It is false for every structural kind.
func IsHTTPEndpointCallKind ¶
IsHTTPEndpointCallKind reports whether kind represents a consumer call site (either the new dedicated kind or the legacy kind with pattern_type == "http_endpoint_client_synthesis").
func IsHTTPEndpointDefinitionKind ¶
IsHTTPEndpointDefinitionKind reports whether kind represents a backend handler definition (either the new dedicated kind or the legacy kind, which was exclusively producer-side before the #1217 split for graphs where pattern_type != "http_endpoint_client_synthesis").
func IsHTTPEndpointKind ¶
IsHTTPEndpointKind reports whether kind is any of the three HTTP endpoint entity kinds: the pre-#1217 legacy kind and the two new split kinds. Use this helper everywhere instead of a raw string comparison against "http_endpoint" so that existing code transparently handles graphs indexed before and after the #1217 split.
func IsValidEntityKind ¶
IsValidEntityKind reports whether s is one of the typed EntityKind values.
func IsValidRelationshipKind ¶
IsValidRelationshipKind reports whether s is one of the typed RelationshipKind values.
func KnownUndeclaredProducedKinds ¶ added in v0.3.4
KnownUndeclaredProducedKinds returns a copy of the roster above: each off-vocabulary entity kind the tree produces today, mapped to its pinned source-site count. Exported for the roster test, which is the only thing that reads the counts.
func SortEntityRecordsCanonical ¶ added in v0.2.1
func SortEntityRecordsCanonical(rs []EntityRecord)
SortEntityRecordsCanonical orders an EntityRecord slice by the tuple (Kind, Name, SourceFile, StartLine, EndLine, Signature) — issue #481's canonical order.
WHY IT LIVES HERE. Several downstream passes disambiguate identically-named entities by FIRST-WRITER-WINS over the slice they are handed (resolve.BuildIndex, the seenEntity/seenRel dedup in buildDocument, engine.ResolveHTTPEndpointHandlers' globalIdx / globalMulti / sameFileBareIdx, engine.ApplyResponseShapesCorpus' handler index). Whoever comes first in the slice therefore DECIDES the graph, so a producer that hands those passes a differently-ordered slice can bind a different entity than the full rebuild does — a mis-bind, which is the class every count-based metric reports as healthy (#6123).
There were two copies of this comparator before #6150 (cmd/grafel's determinism.go and internal/daemon/extract's coordinator.go) and the incremental producer needed a third, which is exactly the drift #5974 fixed for the emission sort by moving it into one shared place. This is that place for the canonical order: it is the lowest package all three producers already depend on, so no import cycle is possible.
sort.SliceStable, not sort.Slice: records comparing equal on all six fields keep their arrival order, so the answer stays a function of the input rather than of the sort's internal pivot choices.
func ValidateProducedEntityKind ¶ added in v0.3.4
func ValidateProducedEntityKind(site, kind string)
ValidateProducedEntityKind reports an entity kind that a producer is about to write into types.EntityRecord.Kind but that the EntityKind enum does not carry and the known-undeclared roster does not record.
site names the constructor for the failure message — it is what makes the panic actionable, since the kind alone does not say which of nineteen constructors let it through.
Under `go test` an unrecorded kind PANICS. Outside `go test` it is a no-op: the kind flows through unchanged and the index is unaffected.
Types ¶
type BatchRecord ¶
type BatchRecord struct {
JobID string `json:"job_id"`
OrgID string `json:"org_id"`
ProjectID string `json:"project_id"`
BatchID string `json:"batch_id"`
Entities []EntityRecord `json:"entities"`
}
BatchRecord holds a set of extracted entities for a single indexing batch. Written to S3 by IndexerTransform and read by IndexerLoad.
type ConfidenceSource ¶
type ConfidenceSource string
ConfidenceSource enumerates how a record was produced. The numeric value of each source is its base confidence at hop=0.
const ( // SourceDirectAST is a tree-sitter / parser-level structural match. The // extractor saw the exact syntactic form (a function declaration, a class // body, an import statement). Confidence: 1.0. SourceDirectAST ConfidenceSource = "direct_ast" // SourceRegexPattern is a regex-based extraction. The extractor matched a // textual pattern without confirming AST structure. Used by a handful of // engine producers where tree-sitter does not yet expose the grammar // (e.g. Rails routes DSL, Gorm tag literals). Confidence: 0.7. SourceRegexPattern ConfidenceSource = "regex_pattern" // SourceYAMLRulePattern is a rule-pack match — the YAML in // internal/engine/rules/*.yaml drives the emission. Stronger than raw // regex because the rule pack is curated and reviewed. Confidence: 0.85. SourceYAMLRulePattern ConfidenceSource = "yaml_rule_pattern" // SourceInferredViaCallsHop is the result of walking the CALLS graph // from a high-confidence seed. Each hop multiplies confidence by 0.95. SourceInferredViaCallsHop ConfidenceSource = "inferred_via_calls_hop" // SourceInferredViaImportHop is the result of walking the IMPORTS graph. // Each hop multiplies confidence by 0.9. SourceInferredViaImportHop ConfidenceSource = "inferred_via_import_hop" // SourceResolvedViaConstantPropagation is the result of Phase 0 // constant-binding propagation (#2761/#2781). Each propagation hop // multiplies confidence by 0.85. SourceResolvedViaConstantPropagation ConfidenceSource = "resolved_via_constant_propagation" // SourceFallbackSpeculation is a low-confidence guess made when no // better signal exists (e.g. inferred class shadow). Confidence: 0.4. // Filtering callers should set min_confidence > 0.4 to exclude these. SourceFallbackSpeculation ConfidenceSource = "fallback_speculation" )
type EnrichmentStatus ¶
type EnrichmentStatus string
EnrichmentStatus typed string for enrichment pipeline state.
const ( StatusPending EnrichmentStatus = "pending" StatusEnriched EnrichmentStatus = "enriched" StatusDegraded EnrichmentStatus = "degraded" )
type EntityKind ¶
type EntityKind string
EntityKind enumerates the SCOPE.* entity kinds emitted by grafel extractors. Producers should use these typed constants rather than free-form string literals so that drift between code and SCHEMA.md (Issue #77) stays detectable at compile time.
The on-disk graph keeps the namespaced "SCOPE." form. The MCP rendering layer strips the prefix when surfacing kinds to the agent (ADR-0003).
const ( EntityKindOperation EntityKind = "SCOPE.Operation" EntityKindComponent EntityKind = "SCOPE.Component" EntityKindClass EntityKind = "SCOPE.Class" EntityKindFunction EntityKind = "SCOPE.Function" EntityKindSchema EntityKind = "SCOPE.Schema" EntityKindVariable EntityKind = "SCOPE.Variable" EntityKindReference EntityKind = "SCOPE.Reference" EntityKindPattern EntityKind = "SCOPE.Pattern" EntityKindEvolution EntityKind = "SCOPE.Evolution" EntityKindEndpoint EntityKind = "SCOPE.Endpoint" EntityKindRoute EntityKind = "SCOPE.Route" EntityKindService EntityKind = "SCOPE.Service" EntityKindView EntityKind = "SCOPE.View" EntityKindUIComponent EntityKind = "SCOPE.UIComponent" EntityKindJSX EntityKind = "SCOPE.JSX" EntityKindStylesheet EntityKind = "SCOPE.Stylesheet" EntityKindQueue EntityKind = "SCOPE.Queue" EntityKindEvent EntityKind = "SCOPE.Event" EntityKindDatastore EntityKind = "SCOPE.Datastore" EntityKindDataAccess EntityKind = "SCOPE.DataAccess" // #3628 [schema]: synthetic per-(repo,table) convergence node. The // migration-schema-ops engine pass creates one SCOPE.Table per logical // table and points both migration MODIFIES_TABLE edges and (existing) // query ACCESSES_TABLE accessors at it, so "what touches table X" unifies // schema evolution (migrations) with data access (queries) on one node. EntityKindTable EntityKind = "SCOPE.Table" // #6451: SCOPE.ExternalAPI was minted by two unrelated producers under // incompatible address dialects — internal/extractors/cross/httpclient // (Name = a URL *path*, usually relative, no host) and the k8s Ingress // pass in internal/extractors/yaml (Name = a bare *hostname*, no path). // One kind, two meanings: neither half's join story could be settled // while they shared it. Split along the producer boundary, the way #6472 // split local_scope. The retired kind is deliberately absent from the // enum so a stale producer fails IsValidEntityKind rather than silently // re-creating the ambiguity. // // ExternalEndpoint: an outbound HTTP call target, keyed on the URL alone // (the verb rides on the CALLS edge). IngressHost: a Kubernetes Ingress // hostname — inbound cluster topology, not a call out. EntityKindExternalEndpoint EntityKind = "SCOPE.ExternalEndpoint" EntityKindIngressHost EntityKind = "SCOPE.IngressHost" EntityKindInfraResource EntityKind = "SCOPE.InfraResource" EntityKindCodeBlock EntityKind = "SCOPE.CodeBlock" EntityKindDocument EntityKind = "SCOPE.Document" EntityKindHeading EntityKind = "SCOPE.Heading" EntityKindScopeUnknown EntityKind = "SCOPE.ScopeUnknown" // Documented-but-previously-undocumented kinds (Issue #77 reconciliation): EntityKindExternal EntityKind = "SCOPE.External" EntityKindProject EntityKind = "SCOPE.Project" EntityKindConfig EntityKind = "SCOPE.Config" EntityKindModel EntityKind = "SCOPE.Model" // #2839: Named constant declarations (e.g. assembly equ/const directives). // Registered as a first-class kind so producers across all low-level // languages can emit constant symbols without reusing SCOPE.Schema (which // carries "data type / field" semantics). Subtype "equate" is used by the // assembly extractor; future producers may use other subtypes (e.g. "define" // for C preprocessor macros, "const" for Zig/Rust compile-time constants). EntityKindConstant EntityKind = "SCOPE.Constant" // AgentPattern is the kind for agent-learned Pattern entities introduced // in ADR-0018. Stored as "AgentPattern" (no SCOPE. prefix) to distinguish // from the structural SCOPE.Pattern kind used by static-analysis extractors. EntityKindAgentPattern EntityKind = "AgentPattern" // #1884: Python package-level module entities. One Module entity is emitted // per Python package boundary (__init__.py or namespace package). Kind="Module" // intentionally matches the synthetic module aggregation kind used by // internal/module (KindModule="Module") so that kind_filter=Module queries // surface both extracted and synthetic module nodes in a single pass. // Subtype="package" distinguishes extractor-produced Module entities from // the synthetic ones emitted by the aggregation layer (subtype=""). EntityKindModule EntityKind = "Module" // #726 wave 1: Kafka producer/consumer cross-repo edges. MessageTopic // represents a message-broker topic. Cross-repo identity = the topic // name string (per-broker namespace via the `broker` property). Wave 1 // is Kafka-only; wave 2 will reuse the kind for RabbitMQ, SQS, NATS, // and Pub/Sub. EntityKindMessageTopic EntityKind = "SCOPE.MessageTopic" // #5782 (ADR-0025): ChannelBinding captures the config row that maps a // messaging channel to its connector + topic (reference impl: // Quarkus/SmallRye/Kafka `mp.messaging.{incoming,outgoing}.<ch>.*`). One // entity per (direction, channel) group. It is the join the graph never // had between the code-side @Incoming/@Outgoing SCOPE.Operation (by // `channel`) and the engine SCOPE.MessageTopic (by `kafka:<topic>`), via // the BINDS / BINDS_TOPIC edges. A config-side declaration with no callers // by design — allow-listed as a framework entry-kind in dead-code and a // real (non-noise) structural signal in denoise. EntityKindChannelBinding EntityKind = "SCOPE.ChannelBinding" // #725: gRPC service definitions + client/server cross-repo edges. // GrpcService represents a gRPC service implementation (server) or stub // (client). Cross-repo identity is the service name. // GrpcMethod represents a single RPC method. Cross-repo identity is // `grpc:<ServiceName>/<MethodName>` — identical on both client and server // sides so the import-channel linker can join them without new linker code. EntityKindGrpcService EntityKind = "SCOPE.GrpcService" EntityKindGrpcMethod EntityKind = "SCOPE.GrpcMethod" // #749: Django Model.Meta constraints=[UniqueConstraint/CheckConstraint] // emit SCOPE.Constraint entities bound to the parent Model via CONTAINS. // Subtypes: "unique" (UniqueConstraint) and "check" (CheckConstraint). EntityKindConstraint EntityKind = "SCOPE.Constraint" // #925: Serverless function invocation edges (AWS Lambda, GCP Cloud Functions, // Azure Functions). ServerlessFunction represents a deployed serverless // function. Cross-repo identity = provider-prefixed name // (`aws-lambda:<name>`, `gcp-cloudfunction:<name>`, `azure-function:<name>`). // Both the invoker side (CALLS) and the handler side (HANDLES) emit the // same entity ID so the import-channel linker joins them cross-repo. EntityKindServerlessFunction EntityKind = "SCOPE.ServerlessFunction" // #927: Managed event-bus entities. EventBusEvent is a synthetic entity // representing a routable event type on a managed event bus. Cross-repo // identity = bus-prefixed event key: // `event:eventbridge:<source>:<detail-type>` // `event:eventgrid:<topic>:<event-type>` // `event:cloudevents:<ce-source>:<ce-type>` // Both producers (PUBLISHES_TO) and consumers / rules (SUBSCRIBES_TO) emit // the same synthetic ID so the existing import-channel linker joins them // without any new linker code. EntityKindEventBusEvent EntityKind = "SCOPE.EventBusEvent" // GAP-005: generic string-literal event-identity model. EventType is a // synthetic entity representing a routable event TYPE contract carried // by ANY channel (Kinesis/SQS/Kafka/etc.) — the generalization of // EntityKindEventBusEvent's managed-bus-specific modeling to a plain // envelope `{eventType:"X"}` with no managed-bus `source`. Cross-repo // identity = the VERBATIM event-type string (deliberately NOT // case-folded — the string is the wire contract producer and consumer // both hard-code): // // event:type:<VerbatimEventString> // // Kept as a DISTINCT kind from EntityKindEventBusEvent (different key // scopes — bus-prefixed vs. bare string) and from the channel nodes // (EntityKindMessageTopic / SCOPE.Queue) — the event flows OVER a // channel, it is not the channel itself. Both producers (PUBLISHES_TO) // and consumers (SUBSCRIBES_TO) emit the same synthetic ID so the // existing import-channel linker / messaging_related MCP surface joins // them without new linker code. EntityKindEventType EntityKind = "SCOPE.EventType" // CLI command entry-point detection (epic #3628). A SCOPE.Command is a // statically-declared command-line command — the CLI sibling of an HTTP // endpoint. Covers click/argparse/typer (Python), commander/yargs/oclif // (Node), cobra (Go), picocli/Spring Shell (Java), and Thor/Rake (Ruby). // One entity per statically-named command (or subcommand path). The // command's handler function is joined via a HANDLES_COMMAND edge // (Command → handler fn), modelling the CLI entry-point → handler flow. // Dynamic command names / handler refs are skipped (honest-partial). EntityKindCommand EntityKind = "SCOPE.Command" // #3704 (epic #3628, area #20): finite-state-machine (FSM) topology. // A single declared state in an application-level state machine — XState // (JS/TS), Ruby AASM, Spring StateMachine (Java), or the Python // `transitions` library. One entity per statically-named state. Distinct // from the whole-machine kind "SCOPE.StateMachine", which models an AWS // Step Functions *whole-machine*; this models the individual nodes of the // state graph, connected by RelationshipKindTransitionsTo. Synthetic ID // shape: `state:<lib>:<machine>:<stateName>`. // // This paragraph used to name a constant `EntityKindStateMachine` that had // never existed (#6776 arm B3): "SCOPE.StateMachine" was written only as a // bare Go constant in internal/engine/workflow_edges.go. #6776 arm B4 // declared it below as EntityKindStateMachine, so the name is real now — // but it remains a DIFFERENT kind from this one, and the distinction the // paragraph above draws is unchanged. EntityKindState EntityKind = "SCOPE.State" // #1217 (Sub-A of #1115): Split http_endpoint into two distinct kinds. // // HTTPEndpointDefinition is emitted for backend handler registrations // (e.g. @app.route, APIRouter, Express app.get). It carries an // owning_backend property derived by walking the handler file path up // until a framework manifest (pyproject.toml, package.json, go.mod) or // framework marker is found. The synthetic ID retains the canonical // `http:<METHOD>:<path>` form so the cross-repo linker continues to // match producer↔consumer by Name without code changes. // // HTTPEndpointCall is emitted for consumer-side call sites (fetch, axios, // requests.get, etc.). It carries caller_file, caller_line, and url_kind // ("literal" | "template_literal" | "dynamic_baseurl"). One entity per // call site — no merging. // // Backward compatibility: all existing dashboard, link, and MCP query // code that compares e.Kind against "http_endpoint" uses the helper // IsHTTPEndpointKind() to match either new kind. The old kind string // is preserved as HTTPEndpointKindLegacy for aliasing only — no new // producers should emit it. EntityKindHTTPEndpointDefinition EntityKind = "http_endpoint_definition" EntityKindHTTPEndpointCall EntityKind = "http_endpoint_call" // HTTPEndpointKindLegacy is the pre-#1217 kind string. Kept so that // on-disk graphs indexed before this release can still be read without // a migration step. // // #6494 — this comment used to claim "no extractor emits this kind // after #1217". That is false: nine live non-test sites in // internal/engine still emit it, eight of them through the // httpEndpointKind alias in http_endpoint_synthesis.go (FastAPI mount // points, the Django urlconf/DRF/admin route synthesisers and the Java // annotation-route synthesiser) plus webhooks_edges.go. New code must // therefore keep matching with IsHTTPEndpointKind() rather than // assuming only the split kinds appear in a freshly indexed graph. HTTPEndpointKindLegacy EntityKind = "http_endpoint" // #3624 (epic #3607): GraphQL DataLoader N+1 batch-loader topology. // A DataLoader instance batches per-key fetches to avoid the N+1 query // problem in GraphQL field resolvers. One entity per statically-named // loader: a `new DataLoader(batchFn)` construction in JS/TS (the // `dataloader` npm package) or a `DataLoader(load_fn=batch_fn)` // construction in Python (the `aiodataloader` package used by // Strawberry/Ariadne). The loader is named by the const/field/variable it // is assigned to. Two edges connect it to the rest of the graph: // - RelationshipKindBatches : loader → batch function (the wrapped // per-key fetch function). // - RelationshipKindUses : resolver → loader, emitted at each // `loader.load(id)` / `loader.loadMany(ids)` call site. This surfaces // which field resolver avoids N+1 via which batch loader. EntityKindDataLoader EntityKind = "SCOPE.DataLoader" // ORM model lifecycle-hook / signal events (child of #3628 area). // A SCOPE.ModelEvent node represents a single persistence lifecycle event // on a specific model/entity — e.g. `User.post_save` (Django signal), // `User.after_create` (ActiveRecord callback), `Order.afterInsert` // (TypeORM), `User.after_insert` (SQLAlchemy event), `User.afterCreate` // (Sequelize hook), `User.save` (Mongoose post/pre middleware). The // node makes both the model and the event queryable ("what runs after a // User is saved?"); the handler is joined via a TRIGGERS edge // (ModelEvent → handler fn). The name segment is "<Model>.<event>". EntityKindModelEvent EntityKind = "SCOPE.ModelEvent" // EntityKindExceptionType is a synthetic, file-agnostic node representing a // single exception / error type by its (normalized, unqualified) name — // e.g. "ValidationError", "NotFound", "IOException", "ErrNotFound". It is // the convergence point for the error-flow capability (epic #3628): a type // raised in one function and caught in another resolve to the SAME node, so // the graph answers "what can this function raise?" (outbound THROWS) and // "where is X handled?" (inbound CATCHES). Like SCOPE.Config/config_key it // carries a constant synthetic SourceFile (ExceptionTypeSourceFile) so // EntityRecord.ComputeID(SourceFile+Kind+Name) collapses identical type // names across files/languages into one node. See // internal/extractor/exception_flow.go. EntityKindExceptionType EntityKind = "SCOPE.ExceptionType" // EntityKindExternalService is a synthetic, file-agnostic node representing a // single well-known third-party service a codebase integrates with via its // official SDK — e.g. "stripe", "twilio", "sendgrid", "aws-s3", "aws-ses", // "openai", "slack", "sentry", "firebase", "algolia". It is the convergence // point for the third-party-integration capability (epic #3628): every // function that calls a recognised SDK entry-point gets a DEPENDS_ON_SERVICE // edge to the SAME service node, so the graph answers "what third-party // services does this codebase integrate with, and where?" (a service's // inbound DEPENDS_ON_SERVICE edges are its call sites). Distinct from raw // HTTP-client CONSUMES_API (path-level): this is SDK-level, NAMED services. // Like SCOPE.ExceptionType/SCOPE.Config it carries a constant synthetic // SourceFile (ExternalServiceSourceFile) so EntityRecord.ComputeID // (SourceFile+Kind+Name) collapses the same service across files/languages // into one node. See internal/extractor/external_service.go. EntityKindExternalService EntityKind = "SCOPE.ExternalService" // EntityKindTemplate is a synthetic, file-agnostic node representing a // server-side view template by its normalized logical name/path — e.g. // "users/list.html", "dashboard", "welcome". It is the convergence point // for the view-layer capability (epic #3628): a request handler / controller // method renders a template, and two handlers that render the SAME template // resolve to ONE node, so the graph answers "what renders users/list.html?" // (inbound RENDERS) and "what does this handler render?" (outbound RENDERS). // Like SCOPE.ExceptionType it carries a constant synthetic SourceFile // (TemplateSourceFile) so EntityRecord.ComputeID(SourceFile+Kind+Name) // collapses identical template names across files/languages/frameworks // (Flask render_template, Django render/TemplateView, Express res.render, // Rails render, Spring MVC view names, Laravel view()) into one node. See // internal/extractor/template_render.go. EntityKindTemplate EntityKind = "SCOPE.Template" // [realtime] WS room/channel grouping (child of #3628). A SCOPE.Channel // node represents a real-time room / channel / group that participants // JOIN and that messages are BROADCAST to — the grouping layer above the // per-event WS endpoints (#3739). Named "channel:<name>" so a join and a // broadcast on the same room (e.g. socket.io `socket.join('lobby')` and // `io.to('lobby').emit(...)`, ActionCable `stream_from 'chat_1'` and // `broadcast('chat_1', ...)`, Django Channels `group_add('chat')` and // `group_send('chat', ...)`, Phoenix `broadcast(socket, "ev", ...)` on a // topic) converge on a single node. Answers "who joins / broadcasts to // room X?". Emitted by internal/engine/ws_channel_grouping.go. EntityKindChannel EntityKind = "SCOPE.Channel" // #3628 area (data-model): enumerated-type value-set node. A SCOPE.Enum // entity represents ONE enumerated type and carries its full member / // value-set as queryable Properties — so the graph answers "what values can // field X take?" for rewrite enum-parity (e.g. a Django Python Enum that a // NestJS rewrite must reproduce value-for-value). One entity per declared // enum, keyed on the declaring file + enum name (distinct same-named enums // in different files stay distinct — unlike the file-agnostic ExceptionType). // Covers Python Enum/IntEnum/StrEnum/TextChoices/IntegerChoices, TypeScript // `enum` + string-literal unions, Java enum, Go iota const groups, Ruby // ActiveRecord `enum`, and C# enum. Properties: // "enum_name" : the bare enum type name. // "members" : comma-joined member names, declaration order. // "values" : comma-joined "Name=Literal" pairs for members whose // literal value is statically known (omitted for members // with no explicit value). e.g. "RED=1, GREEN=2". // "member_count" : number of members. // "kind_hint" : the source construct ("python_enum", "ts_enum", // "ts_literal_union", "java_enum", "go_iota", // "rails_enum", "csharp_enum"). // See internal/extractor/enum_valueset.go. EntityKindEnum EntityKind = "SCOPE.Enum" // [sbom] EntityKindPackage is a synthetic, file-agnostic node representing a // single declared EXTERNAL package (dependency) by its ecosystem-qualified // name — e.g. "npm:react", "go_modules:github.com/gin-gonic/gin", // "pip:requests", "maven:org.springframework:spring-core", "cargo:serde". // It is the convergence point for the software-bill-of-materials (SBOM) // capability (child of epic #3628): every manifest that declares the SAME // package — across files, modules, AND repos in a group — resolves to ONE // node, so the graph answers "which repos depend on package X, and at what // versions?" (a package's inbound DEPENDS_ON_PACKAGE edges are its full // usage footprint) and conversely "what is this repo's full dependency // set?". Like SCOPE.ExternalService it carries a constant synthetic // SourceFile (PackageSourceFile) so EntityRecord.ComputeID // (SourceFile+Kind+Name) collapses the same ecosystem:name across every // manifest into a single node — distinct from the per-manifest // SCOPE.Component(subtype=external_dependency) record (which stays // file-scoped for license/version provenance). The version is NOT part of // the identity (two repos pinning different versions of react converge on // one node); per-edge `version` + `dev` scope live on the DEPENDS_ON_PACKAGE // edge. Emitted by internal/extractors/cross/manifest/extractor.go. EntityKindPackage EntityKind = "SCOPE.Package" // EntityKindTranslationKey is a synthetic, file-agnostic node representing a // single i18n / localization translation KEY by its literal value — e.g. // "errors.notFound", "messages.welcome", "users.title", "Welcome". It is the // convergence point for the localization capability (child of #3628): every // function / component that references the SAME key via a recognised i18n // function gets a USES_TRANSLATION edge to the SAME key node, so the graph // answers "where is the 'errors.notFound' string used?" (a key's inbound // USES_TRANSLATION edges are its reference sites) and supports // untranslated-key analysis (keys with no catalog backing). Like // SCOPE.ExternalService it carries a constant synthetic SourceFile // (TranslationKeySourceFile) so EntityRecord.ComputeID(SourceFile+Kind+Name) // collapses the same key across files/languages/frameworks (react-i18next / // i18next t('k') / <Trans i18nKey>, vue-i18n $t('k'), Django/gettext _('m') / // {% trans %}, Rails I18n.t('k') / t('.k'), Laravel __('k') / trans('k')) // into one node. Name = "i18n:<key>". Precision-first / honest-partial: an // edge is emitted only when the i18n function CONTEXT is recognised (import // or unambiguous framework symbol) and the key is a STATIC literal; a dynamic // key (`t(keyVar)`, interpolated) or a non-i18n `_('x')` (lodash) / // unrelated `t(...)` emits NO node/edge. See internal/extractor/translation_key.go. EntityKindTranslationKey EntityKind = "SCOPE.TranslationKey" // #4306 (Layer 1 of epic #4294): deterministic markdown documentation // ingestion. Two new structural kinds model the prose layer of a repo so // the graph can answer "where is X documented?" and "what does this doc // describe?". Emission is OPT-IN (--ingest-docs, default OFF) and FULLY // DETERMINISTIC — no LLM calls, no network. // // EntityKindMarkdownDocument represents ONE markdown file (a *.md). One // entity per discovered, non-vendored markdown file. SourceFile is the // repo-relative path; StartLine/EndLine span the whole file. // // EntityKindSection represents ONE heading-delimited block within a // markdown document — the heading line plus all body text up to the next // heading of equal-or-shallower depth. Sections nest by heading depth via // CONTAINS (Document → top-level Section → sub-Section). The section's // source span (StartLine/EndLine) is preserved so get_source can quote it. // // Distinct from the pre-existing SCOPE.Document / SCOPE.Heading kinds, // which were reserved for an unrelated detector taxonomy and carry no // hierarchy/span contract. EntityKindMarkdownDocument EntityKind = "SCOPE.MarkdownDocument" EntityKindSection EntityKind = "SCOPE.Section" // #4308/#4309 (Layer 2 of epic #4294): agent-driven semantic doc ingestion. // grafel EMITS a per-Section prompt bundle; an EXTERNAL agent runs its // own LLM and returns structured results; grafel VALIDATES + APPLIES // them. grafel itself makes NO LLM call (emit→apply candidate pattern, // mirroring docgen --llm-mode and grafel_enrichments). // // EntityKindDesignDecision represents ONE agent-classified semantic node // distilled from a Section — a design decision, rationale, spec, or // constraint stated in prose. It is anchored to its source Section via a // CONTAINS edge (Section → DesignDecision) and links to the code entities // it justifies via RATIONALE_FOR (DesignDecision → code entity). Distinct // from the deterministic SCOPE.Section node (which carries verbatim prose); // a DesignDecision is the agent's distilled claim ABOUT that prose. EntityKindDesignDecision EntityKind = "SCOPE.DesignDecision" // #6776 arm B4 — six kinds that Go passes have been WRITING all along // while this enum did not carry them. // // They are not new extraction: every one is produced today, with the exact // spelling declared here, by a pass that keeps its own package-level // constant — internal/engine/workflow_edges.go, workflow_dag_edges.go, // scheduled_jobs_edges.go, serverless_framework_edges.go, // iac_cloudformation_edges.go, event_flow.go and process_flow.go. // (internal/graph/flows/flows.go declares SCOPE.Process and SCOPE.EventFlow // too, but only READS them, in IsFlowEntityKind; it is not a producer.) // They drifted out of the enum because the guard that was supposed to catch // exactly this read `Kind:` only when it was a string LITERAL, and every one // of these producers writes `Kind: <identifier>`. Arm B3 replaced that guard // with internal/entkinds.ScanGo, which resolves source constants, and the // six fell out of it immediately. // // Adding them changes NO spelling and therefore does not move // KindVocabularyVersion: nothing already on disk is restated, and a query // for one of these kinds against an older graph correctly finds whatever // was extracted, because the string was always this. What does change is // that types.IsValidEntityKind stops rejecting a kind the writer emits — // which is what internal/graph/fbwriter's arm-A tally counts. // // The producers are deliberately NOT rewired to reference these constants // here; that is a separate change, and the duplication is the pre-existing // state rather than something this arm introduces. // // SCOPE.Workflow / SCOPE.Activity — a durable workflow definition and one // step within it (Temporal, AWS Step Functions, Airflow-style DAGs). // SCOPE.StateMachine — an AWS Step Functions WHOLE machine. Distinct from // EntityKindState above, which is one node of an application-level FSM. // SCOPE.ScheduledJob — a cron/Quartz/Hangfire/serverless-schedule entry // point; the handler it fires is joined by a TRIGGERS edge. // SCOPE.Process — a linearised call/data-flow chain (internal/graph/flows). // SCOPE.EventFlow — its pub/sub sibling: a linearised multi-hop chain. EntityKindWorkflow EntityKind = "SCOPE.Workflow" EntityKindActivity EntityKind = "SCOPE.Activity" EntityKindStateMachine EntityKind = "SCOPE.StateMachine" EntityKindScheduledJob EntityKind = "SCOPE.ScheduledJob" EntityKindProcess EntityKind = "SCOPE.Process" EntityKindEventFlow EntityKind = "SCOPE.EventFlow" // #6776 arm B5 — thirteen kinds that internal/engine/rules/**/*.yaml has // been declaring all along while AllEntityKinds() omitted them. // // internal/engine/detector.go writes SourcePattern.EntityType straight into // types.EntityRecord.Kind with no validation, so each of these strings // already reaches the graph exactly as spelled here; what it did NOT reach // was the enum, so IsValidEntityKind rejected every entity carrying one and // internal/graph/fbwriter's arm-A tally counted them as unrecognised. // // The SPELLINGS ARE UNCHANGED — deliberately. Arm A measured that adding a // `SCOPE.` prefix buys spelling and nothing else (seven already-prefixed // kinds were outside the enum too), at the cost of ~532 rule edits plus a // stored-graph migration. This arm is enum membership only, so nothing // already on disk is restated and KindVocabularyVersion does not move (see // the doc on that constant). // // These thirteen are exactly the rule-declared kinds whose GO IDENTIFIER // was free — that is the whole of the selection rule, and it is narrower // than it sounds. The other twelve on internal/entkinds' ledger each // already have an `EntityKind<Name>` constant bound to a `SCOPE.`-prefixed // value, so declaring the un-prefixed spelling needs a second Go name. // Arms B6, B7 and B8 migrate eleven of them (see the blocks below); // Endpoint alone remains deferred, and NOT because nobody got to it — see // arm B8's block for why it is the one pair that is not a synonym (#6820). // // WHAT THAT RULE DOES NOT SAY IS THAT NO SYNONYM EXISTS. Four of the // thirteen have a live `SCOPE.`-prefixed twin that a Go producer emits // TODAY, as a bare string literal, and that is itself outside this enum: // // "SCOPE.Middleware" internal/custom/scala/frameworks.go (9 sites) // "SCOPE.Relationship" internal/custom/kotlin/orm_schema.go (5 sites) // "SCOPE.Interface" internal/custom/scala/type_system.go (2 sites) // "SCOPE.Dependency" internal/custom/java/dropwizard.go (1 site) // // So after this arm `"Middleware"` is a valid kind while `"SCOPE.Middleware"` // is not, for the same concept. Nothing depends on the pair being reconciled // and nothing breaks, but it IS the synonym state, reached from the other // side — and saying otherwise would be this issue's own defect. These sites // are invisible to internal/entkinds.ScanGo because each passes the kind as // a FUNCTION ARGUMENT (makeEntity(id, kind, ...)) rather than as a `Kind:` // field, which is precisely the call-site hole arm B4 documented and left // open; that is why an empty goPrefixedKindsDeferred does not contradict // this list. Reconciling the four is not in scope here. // // Eight rule-declared kinds produce zero entities on both corpora, and // SEVEN OF THOSE EIGHT ARE IN THIS BLOCK (Decorator, Fixture, // Implementation, Interface, Relationship, TestClass, TestConfig; the // eighth, Template, is a twin arm B8 declares in its own block below). // Zero is not dead: arm B1 drove the // shipped rule tree over the construct each of their declaration sites // names and every site fired — see // internal/engine/zero_producing_rule_kinds_6776_test.go. The corpora lack // `directive @`, `@pytest.fixture`, `= relationship(`, `actual fun` and // friends; the rules are live. EntityKindController EntityKind = "Controller" EntityKindDecorator EntityKind = "Decorator" EntityKindDependency EntityKind = "Dependency" EntityKindFixture EntityKind = "Fixture" EntityKindImplementation EntityKind = "Implementation" EntityKindInterface EntityKind = "Interface" EntityKindMiddleware EntityKind = "Middleware" EntityKindMigration EntityKind = "Migration" EntityKindRelationship EntityKind = "Relationship" EntityKindTask EntityKind = "Task" EntityKindTest EntityKind = "Test" EntityKindTestClass EntityKind = "TestClass" EntityKindTestConfig EntityKind = "TestConfig" // #6776 arm B6 — four more rule-YAML-declared kinds, this time ones whose // obvious Go identifier was ALREADY TAKEN by a `SCOPE.`-prefixed constant // for the same concept. Hence the `Bare` suffix: it names the SPELLING, // not a second concept. // // Arm B5's block called that collision "a synonym decision, not a // mechanical add". That framing is still wrong — but NOT for the reason // this block used to give, and the old reason is retracted rather than // restated. It cited internal/engine/classfold.go's blanket claim that // every kind "MUST APPEAR" in both spellings. #6841 withdrew that claim as // never having been true (eleven bare rows and two prefixed rows have no // twin) after finding it cited twice as proof that a particular pair // exists, wrong both times. PAIRING IS PER ROW: the authority is the table // engine.FrameworkClassKindTwins, graded by // classfold_twin_declaration_6841_test.go, and a pair is a row to be read // there, never an inference from a rule. // // Re-derived that way row by row (#6858), this arm's claim survives: for // the three kinds it rests on classfold for, the table says TwinInMap, and // the fourth never rested on classfold at all. A same-concept pair is the // shipped graph for them, not a pending choice. A decision would only be // needed to MERGE a pair, which #6776's standing "enum membership, no // rename" ruling puts out of scope. // // Each pair is load-bearing in code today: // // Component — NOT a classfold row in either spelling; see the NOTE below. // internal/resolve/refs.go:1962 (isComponentKind) and :2139 // (componentKindFamily) list "Component" beside // "SCOPE.Component"; :1235 names the dual-indexing. // Model — FrameworkClassKindTwins["Model"] and ["SCOPE.Model"] are // both TwinInMap; priority 100/100, canon-rank 5/5. // View — both TwinInMap; priority 100/100. refs.go:1189 indexes an // entity under both its kind and the SCOPE-trimmed spelling. // Schema — both TwinInMap; priority 80/80. // internal/dashboard/shape_tree.go:257,262 handles // "SCOPE.Schema" and bare "Schema" in adjacent arms. // // NOTE the one place the pairing does NOT hold, so this comment does not // claim more than it can: FrameworkClassKindPriority deliberately omits // BOTH "Component" and "SCOPE.Component" (classfold.go's doc on the generic // AST node, which is folded away and never a candidate). Component's // justification is the refs.go pair above, not classfold. // // Every classfold reading in this block — the three TwinInMap rows with // their priorities, Model's canon rank, Component's absence, and the // eleven/two count above — is asserted against the live tables by // TestKindsCitation6858_* in internal/engine. If a row's pairing state // changes, that fails rather than this prose drifting. // // Spellings unchanged, so KindVocabularyVersion does not move. EntityKindComponentBare EntityKind = "Component" EntityKindModelBare EntityKind = "Model" EntityKindSchemaBare EntityKind = "Schema" EntityKindViewBare EntityKind = "View" // #6776 arm B7 — the next four rule-YAML-declared kinds whose obvious Go // identifier was already taken by a `SCOPE.`-prefixed constant. Same // `Bare` suffix, same reason: it names the SPELLING, not a second concept, // and arm B6's block records why a same-concept pair is the shipped graph // rather than a pending decision. That record is per row: pairing is read // off engine.FrameworkClassKindTwins one kind at a time, never inferred // from a blanket "both spellings must appear" rule — classfold.go carried // such a rule and #6841 retracted it as false (#6858). // // Each pair was read at its cited site before being relied on: // // Operation — internal/resolve/refs.go:1940, isOperationKind, is // literally `k == "SCOPE.Operation" || k == "Operation"`. // Route — internal/enrichment/candidates.go:98 and // internal/enrichment/pricing.go:38 each list "Route" beside // "SCOPE.Route" in ONE case arm; both mean the HTTP route. // Service — FrameworkClassKindTwins["Service"] and ["SCOPE.Service"] // are both TwinInMap: priority 100/100, canon-rank 4/4. The // same construct is also extracted twice over: a docker // compose service is bare `Service` from // internal/engine/rules/docker/frameworks/docker_compose.yaml:39 // and "SCOPE.Service" from // internal/patterns/docker_compose_extractor.go:68. // Config — both spellings name a configuration file: rule YAML emits // bare `Config` for one (ansible_core.yaml:31,34; // docker_compose.yaml:30,33) and // internal/patterns/config_detector.go:49 emits // "SCOPE.Config" subtype "config_file" for one. // // Only Service's evidence is classfold's. Operation, Route and Config are // rows of FrameworkClassKindPriority in NEITHER spelling, so that table // says nothing about them in either direction, and each rests solely on the // site cited beside it. Service's TwinInMap reading and the other three's // absence are asserted by TestKindsCitation6858_* in internal/engine. // // The grounding for this batch carried a caveat that the Config twin uses // a SYNTHETIC SourceFile for id collapse. Two things about it, neither of // which blocks the add: config_detector.go passes the REAL filePath to // makeEntity, so the caveat does not describe that producer; and // IsValidEntityKind is membership in a string set that never reads // SourceFile, so an id-shaping difference could not bear on it either way. // // Spellings unchanged, so KindVocabularyVersion does not move. EntityKindConfigBare EntityKind = "Config" EntityKindOperationBare EntityKind = "Operation" EntityKindRouteBare EntityKind = "Route" EntityKindServiceBare EntityKind = "Service" // #6776 arm B8 — THREE of the four kinds left on internal/entkinds' // ledger, not four. `Endpoint` is deliberately excluded; the paragraph // after the concept notes says why, and the ledger goes 4 -> 1 rather than // 4 -> 0 as a result. // // Same `Bare` suffix and the same reason as arms B6/B7: it names the // SPELLING, not a second concept. Spellings unchanged, so // KindVocabularyVersion does not move. // // CONCEPT NOTES. Each pair below was read at both ends — the rule-YAML // site and the `SCOPE.`-prefixed constant's doc — before being relied on: // // Constraint — bare: one site, sqlalchemy.yaml:79, whose pattern is // `ForeignKey("table.id")`, i.e. a constraint declared on a // SQLAlchemy table/model. SCOPE.Constraint (kinds.go, #749) // is a Django `Model.Meta.constraints` entry // (UniqueConstraint/CheckConstraint) bound to its parent // Model by CONTAINS. ONE concept — a declarative constraint // on a persisted model — reached through two Python ORMs. // Neither spelling appears in // internal/engine/classfold.go's FrameworkClassKindPriority, // and that is deliberate rather than an omission: its doc at // classfold.go:26-27 names "a Django Meta `Constraint`" as // the example of a nested artifact that merely shares a // class's name and must never win a fold. // Plugin — bare: five sites in two files, and both are the ACT OF // REGISTRATION, not a coincidence of naming — // ktor.yaml:62 matches `install(<Name>)` and // fastify.yaml:48 matches `<app>.register(<Name>)`, with // */plugins/* file conventions beside them (ktor.yaml:40, // fastify.yaml:35,38). SCOPE.Plugin (#3628 area #25) is "a // single plugin / extension that a build tool, application, // or framework registers", linked from the registering file // by REGISTERS_PLUGIN. Same concept, same relationship, // different extractor. // Template — bare: two sites, flask.yaml:52 (`templates/**/*.html`) and // ansible_core.yaml:37 (`roles/*/templates/*.j2`) — a // server-side template FILE. SCOPE.Template (#3628) is the // server-side view template named by its normalized logical // path, and its doc names Flask `render_template` as one of // the producers. Same concept, from the two ends of one // render: the file that IS the template and the name a // handler renders. // // ONE DIFFERENCE INSIDE THE TEMPLATE PAIR, RECORDED RATHER THAN GLOSSED: // SCOPE.Template carries a constant synthetic SourceFile // (TemplateSourceFile) so ComputeID collapses one logical template name // across files and frameworks, while the bare kind is per-FILE by // construction (a file_conventions glob). That is an IDENTITY-SHAPE // difference, not a concept difference, and it cannot bear on membership // either way: IsValidEntityKind is membership in a string set that never // reads SourceFile. The same point was settled for `Config` in arm B7. // // WHY `Endpoint` WAS NOT HERE, and where it went. It is the one pair on // this ledger that is NOT a synonym. The bare spelling is declared only by // javascript_typescript/frameworks/electron.yaml — Electron IPC channels — // while SCOPE.Endpoint names the HTTP concept. Arm B8 would have had to // migrate it under the same "same concept, different spelling" heading as // its three, and that heading is false for it, so it was held back at // ruleDeclaredKindsDeferredMax = 1 pending #6820. Arm B9 declares it below // on a DIFFERENT footing, and takes that ledger to zero. EntityKindConstraintBare EntityKind = "Constraint" EntityKindPluginBare EntityKind = "Plugin" EntityKindTemplateBare EntityKind = "Template" // #6776 arm B9 — the LAST kind on internal/entkinds' rule-YAML ledger, and // the end of the B series. The ledger is retired rather than ratcheted to // zero; internal/entkinds/rule_ledger_retired_6776_test.go carries the // measurement that replaces it (532 rule-YAML sites, zero of them naming a // kind AllEntityKinds() rejects). // // THIS ROW IS NOT A `Bare` SPELLING OF ANYTHING, and that is the one way it // differs from every row in the B6/B7/B8 blocks above. Those all read "the // SPELLING, not a second concept". This one IS a second concept: // // Endpoint — three sites, all in // javascript_typescript/frameworks/electron.yaml:41,46,52 // (`ipcMain.handle` / `ipcRenderer.invoke` / // `contextBridge.exposeInMainWorld`). It is an ELECTRON IPC // CHANNEL. SCOPE.Endpoint (EntityKindEndpoint above) is the // HTTP entrypoint. They share seven letters and nothing else: // an IPC channel never had a URL. // // The `Bare` suffix is kept only because the identifier `EntityKindEndpoint` // is taken; it names the spelling, and the concept note is the paragraph // above rather than the suffix. // // WHY MEMBERSHIP IS STILL THE RIGHT ANSWER FOR A DIFFERENT CONCEPT. // #6776's ruling is that enum membership tracks what producers EMIT, not // what a vocabulary designer would have minted. detector.go writes the // declared string verbatim into EntityRecord.Kind, so these three sites put // `Endpoint` in every Electron graph whether or not this list carries it; // the only question membership answers is whether IsValidEntityKind then // rejects an entity grafel itself produced. A distinct concept is a reason // for a distinct MEMBER — which is what this is — not a reason to keep a // produced kind outside the vocabulary. // // WHY IT IS SAFE NOW AND WAS NOT AT ARM B8. #6820 was decided on // 2026-09-06: fix the ten consumer sites, no rename, no stored-graph // migration. `9706a5cde` repointed the HTTP and OpenAPI panes at // SCOPE.Endpoint (and `fa05d3e2c` did the `Route` twin), so no consumer // reads the bare spelling as HTTP any more. The hazard arm B8 named — a // misleading vocabulary name, with IPC channels reachable through // HTTP-shaped consumers — is closed at the consumers rather than by // withholding membership. // // MEMBERSHIP IS NOT HTTP MEMBERSHIP. IsHTTPEndpointKind must keep // REJECTING this string; http_endpoint_kind_forbidden_6894_test.go pins // that, and endpoint_bare_kind_6776_test.go pins it again in this arm's own // name, because "it is a valid kind now" is exactly the reasoning that // would fold it into the HTTP switch and re-export IPC channels into the // OpenAPI document. // // Spelling unchanged — nothing is renamed or retired — so // KindVocabularyVersion does not move. // // #6911 — DO NOT FOLD THIS INTO SCOPE.Endpoint. THE MISSING PREFIX IS NOT // AN ACCIDENT HERE. Twelve kinds in AllEntityKinds() have a `SCOPE.`-less // twin; eleven of them (Component, Model, Schema, View, Config, Operation, // Route, Service, Constraint, Plugin, Template — the `*Bare` constants // above) really are one concept written two ways, which is why the #6776 // B-series and internal/entkinds' sweep guard call the prefix an accident. // THIS PAIR IS THE ONE EXCEPTION: `Endpoint` is an Electron IPC channel, // `SCOPE.Endpoint` is an HTTP entrypoint, and the prefix is the only thing // telling them apart. Renaming the bare kind to something like `IPCChannel` // so they differed by more than a prefix was PRICED AND DECLINED BY THE // OWNER IN #6820 (a rename plus a stored-graph migration); that ruling // stands, so the confusable spelling is deliberate and is carried here // instead. Deleting this constant, or pointing it at "SCOPE.Endpoint", // re-labels every Electron IPC channel as an HTTP endpoint. // // THAT FOLD IS NOT SILENT, AND THE GUARDS ARE OLDER THAN THIS COMMENT — so // do not delete any of them as redundant. Both spellings of the fold // (repointing this constant, or dropping it from AllEntityKinds()) were // scored on #6911 and each is caught by FOUR pre-existing tests: // TestEndpointBare6776_IsADistinctValidEntityKind and // TestEndpointBare6776_MembershipIsNotHTTPMembership // (endpoint_bare_kind_6776_test.go), TestEntityKindVocabularyIsPinnedToItsVersion // (kind_vocabulary_bump_guard_6779_test.go) and // TestAllEntityKinds6830_ListsEveryDeclaredKindExactlyOnce // (all_entity_kinds_roster_6830_test.go). Re-spelling the three // electron.yaml producers is caught by a fifth, // TestRuleDeclaredLedger6776_IsRetiredBecauseThePopulationIsEmpty. // internal/types/prefix_only_pairs_6911_test.go adds a sixth lock on the // fold, kept only because its failure text names #6820; what it adds that // nothing else observed is the NEXT pair — it pins the twelve-pair // population, so a thirteenth cannot be added without someone saying which // of the two readings it is. EntityKindEndpointBare EntityKind = "Endpoint" )
func AllEntityKinds ¶
func AllEntityKinds() []EntityKind
AllEntityKinds returns every EntityKind that grafel extractors are permitted to emit. Used by tests and tooling to validate that no producer leaks a free-form kind string.
type EntityRecord ¶
type EntityRecord struct {
ID string `json:"id"`
OrgID string `json:"org_id"`
ProjectID string `json:"project_id"`
ProjectSlug string `json:"project_slug"`
// RepoID is the GitHub repository full name (e.g. "cajasmota/grafel").
// Sourced from the ExtractTriggerMessage.RepoURL in the Extract Lambda.
// Required by the Typesense entity_embeddings schema for tenant isolation.
RepoID string `json:"repo_id,omitempty"`
Name string `json:"name"`
QualifiedName string `json:"qualified_name"`
Kind string `json:"kind"`
SourceFile string `json:"source_file"`
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
Language string `json:"language"`
Content string `json:"content"`
Description string `json:"description"`
Domain string `json:"domain"`
Subtype string `json:"subtype"`
Signature string `json:"signature"`
Tags []string `json:"tags,omitempty"`
QualityScore float64 `json:"quality_score"`
// Confidence in [0.0, 1.0] reflecting how certain the extraction is.
// Phase 1C (#2769). Zero/unset reads as 1.0 via EffectiveConfidence —
// the default semantics for direct-AST extractors that never stamp.
Confidence float64 `json:"confidence,omitempty"`
EnrichmentStatus EnrichmentStatus `json:"enrichment_status"`
// EnrichmentRequired is the Extract-stage decision: does this entity need LLM enrichment?
EnrichmentRequired bool `json:"enrichment_required"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
// Properties holds framework-specific attributes.
Properties map[string]string `json:"properties,omitempty"`
Relationships []RelationshipRecord `json:"relationships,omitempty"`
}
EntityRecord is the central data contract shared by all five Lambda handlers and all extractors. Fields match the Python indexer EntityRecord schema.
func (*EntityRecord) ComputeID ¶
func (e *EntityRecord) ComputeID() string
ComputeID generates a deterministic 16-character hex ID from the entity's identity fields. Formula: sha256(OrgID+ProjectID+SourceFile+Kind+Name)[:16].
func (*EntityRecord) IsMergeTwinAlias ¶ added in v0.2.1
func (e *EntityRecord) IsMergeTwinAlias() bool
IsMergeTwinAlias reports whether this record is a merge facet of another co-located entity (see EntityTwinOfProperty).
func (*EntityRecord) StampConfidence ¶
func (e *EntityRecord) StampConfidence(src ConfidenceSource)
StampEntity sets the Confidence field on an EntityRecord from a ConfidenceSource. Helper kept here so extractors don't import math.
func (*EntityRecord) Validate ¶
func (e *EntityRecord) Validate() error
Validate checks that all required fields are present and values are in range. Returns a single error containing all violations found, not just the first.
type FileBatch ¶
type FileBatch struct {
JobID string `json:"job_id"`
OrgID string `json:"org_id"`
ProjectID string `json:"project_id"`
ProjectSlug string `json:"project_slug"`
BatchIndex int `json:"batch_index"`
TotalFiles int `json:"total_files"`
TotalBytes int64 `json:"total_bytes"`
Files []FileEntry `json:"files"`
}
FileBatch is the SQS message payload dispatched by IndexerExtract. Each message covers up to 10 files for parallel IndexerTransform invocations.
type FileEntry ¶
type FileEntry struct {
Path string `json:"path"`
Language string `json:"language"`
SizeBytes int64 `json:"size_bytes"`
S3Key string `json:"s3_key"`
}
FileEntry describes a single file within a FileBatch.
type IndexJob ¶
type IndexJob struct {
JobID string `json:"job_id"`
OrgID string `json:"org_id"`
ProjectID string `json:"project_id"`
RepoURL string `json:"repo_url"`
Branch string `json:"branch"`
Status string `json:"status"` // pending/running/completed/failed
Stage string `json:"stage"` // extract/transform/load/synthesize/qa
FilesDiscovered int `json:"files_discovered"`
FilesQueued int `json:"files_queued"`
FilesProcessed int `json:"files_processed"`
}
IndexJob represents a full indexing job checkpoint stored in Supabase index_jobs. Updated at each pipeline stage transition.
type Props ¶ added in v0.2.0
type Props []PropKV
Props is a property set stored as a slice sorted by key. The zero value is an empty, usable set.
func PropsFromMap ¶ added in v0.2.0
PropsFromMap builds a sorted Props from a map[string]string, or nil if m is empty. Used at call boundaries that still hand over a plain map.
func (Props) MarshalJSON ¶ added in v0.2.0
MarshalJSON emits the same object encoding a map[string]string would. encoding/json sorts map keys, and Props is already key-sorted, so the bytes are identical — the on-disk graph and golden fixtures are unaffected.
func (Props) Range ¶ added in v0.2.0
Range calls f for every key/value pair in key-sorted order. Iteration stops early if f returns false. Safe to call on a nil Props (no-op).
func (*Props) Set ¶ added in v0.2.0
Set sets key to val (last write wins), lazily allocating the backing slice. The receiver is a pointer because insertion may reallocate.
func (*Props) SetDefault ¶ added in v0.2.0
SetDefault sets key to val only if key is absent, reporting whether it wrote. Mirrors the `if _, ok := m[k]; !ok { m[k] = v }` idiom that the property producers use, without a second lookup.
func (Props) Snapshot ¶ added in v0.2.0
Snapshot returns an independent copy of the properties as a map, or nil if there are none. Callers must not assume the result aliases internal storage.
func (*Props) UnmarshalJSON ¶ added in v0.2.0
UnmarshalJSON decodes a JSON object into sorted Props. A JSON null decodes to nil, matching map[string]string.
type Relationship ¶
type Relationship struct {
SourceID string `json:"source_id"`
TargetID string `json:"target_id"`
Type string `json:"type"`
Properties Props `json:"properties,omitempty"`
// Confidence in [0.0, 1.0]; zero reads as 1.0. Phase 1C (#2769).
Confidence float64 `json:"confidence,omitempty"`
}
Relationship is an alias used by batch/SQS message types for source→target edges. SourceID/TargetID map to FromID/ToID in RelationshipRecord. Used by downstream handlers and matches the task brief field names.
func (*Relationship) StampConfidence ¶
func (r *Relationship) StampConfidence(src ConfidenceSource)
StampConfidence sets the Confidence field on the SQS-message Relationship.
func (*Relationship) Validate ¶
func (r *Relationship) Validate() error
Validate checks that all required fields are present.
type RelationshipKind ¶
type RelationshipKind string
RelationshipKind enumerates the directed-edge kinds emitted by grafel extractors and resolvers. As with EntityKind, producers should reference these constants rather than ad-hoc string literals.
const RelationshipKindCommitCoupled RelationshipKind = "COMMIT_COUPLED"
RelationshipKindCommitCoupled is the co-change edge emitted by the commit-coupling pass (internal/engine/commit_coupling_edges.go, which defines its KindCommitCoupled as this constant so the two cannot drift — pinned by internal/engine's commit_coupling_derived_kind_6773_test.go).
It is DERIVED: it records that two files appeared in the same commit at least `support` times, not that either file references the other.
func AllDerivedRelationshipKinds ¶ added in v0.3.2
func AllDerivedRelationshipKinds() []RelationshipKind
AllDerivedRelationshipKinds returns every derived (statistical) relationship kind producers may emit.
These are deliberately NOT in AllRelationshipKinds(): see the file comment above. A consumer that wants the structural graph alone uses that accessor and is unaffected by anything added here; one that wants the whole graph uses IsDeclaredRelationshipKind or ranges over both.
A fresh slice per call — the caller may sort or truncate it without rewriting the vocabulary for everyone else.
func AllRelationshipKinds ¶
func AllRelationshipKinds() []RelationshipKind
RETIRED KINDS — do not re-add. TYPED_AS (#6906) and HAS_TYPE (#5828) were declared and registered here for months with zero producers and zero consumers. Both named the same concept: a field / parameter → its declared type. #6984 shipped that edge for C# as RelationshipKindReferences carrying the property ref_kind="field_target_type" (internal/extractors/csharp/ field_type_refs.go), which is the spelling the custom lane had already shipped in five languages via referencesClassEdge. A third name for one concept is the defect #6451 was merged to remove for SCOPE.ExternalAPI, so both constants were deleted rather than kept as reserved words. The deletion is pinned by TestRetiredRelationshipKindsStayRetired in kinds_retired_6906_test.go.
AllRelationshipKinds returns every RelationshipKind producers may emit.
type RelationshipRecord ¶
type RelationshipRecord struct {
FromID string `json:"from_id"`
ToID string `json:"to_id"`
Kind string `json:"kind"`
Properties Props `json:"properties,omitempty"`
// Confidence in [0.0, 1.0]; zero reads as 1.0. Phase 1C (#2769).
Confidence float64 `json:"confidence,omitempty"`
}
RelationshipRecord represents a directed edge between two entities. Kind values are enumerated by RelationshipKind (see kinds.go).
func (*RelationshipRecord) StampConfidence ¶
func (r *RelationshipRecord) StampConfidence(src ConfidenceSource)
StampConfidence sets the Confidence field on a RelationshipRecord.
func (*RelationshipRecord) Validate ¶
func (r *RelationshipRecord) Validate() error
Validate checks that all required fields are present. Returns a single error containing all violations found.