v0.5.0 I4 slice 4: Topic/Subscription in Architecture Intelligence answers - #232
Conversation
…swers Project declared PUBLISHES_TO routes into get_service_dependencies and get_architecture_drift per spec §12.2-§12.5: Topic fallback, Subscription fallback, and one RESOLVED_SERVICE claim per consumer, each carrying its DeliveryRef.subscription route. The claim-id payload gains an optional subscription_id that is omitted entirely for HTTP/Queue claims, so their ids stay byte-identical. The reference claim_id and its CLI are mirrored. Qualification is publisher-only (Queue parity). A route needs evidenced SUBSCRIPTION_OF, and that evidence joins resolution_evidence_refs on resolved claims only (frozen contract: fallback claims carry none). The shared messaging-coverage rule now counts observed PUBLISHES_TO->Topic and RECEIVES_FROM->Subscription. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XvQdzYyycuPbtbvBh5H49p
|
Review of head I reviewed the Slice 4 projection against v0.5.0 I4 Draft 0.3 §§12.2–12.6 and the documented owner decisions in the PR. The reads and projection preserve the Topic/Subscription route, scope consumer and The 66 focused unit tests passed locally. All PR checks, including integration-core, demo-e2e, quality and CodeQL, are green. The PR's integration tests cover same-snapshot evidence resolution, drift/dependency consistency, service/REST/MCP equivalence and zero read-path writes. Disposition: No changes required for approval. |
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It changes core dependency/drift/evidence behavior and Neo4j read logic across multiple subsystems, so final human review is warranted despite strong automated test coverage.
Review effort: Lite
Findings: None
What changed in this PR
Implements v0.5.0 I4 slice 4 by projecting declared Topic/Subscription topology into the public Architecture Intelligence answers (dependencies, drift, and evidence drill-down), while keeping HTTP/Queue claim-id payloads byte-identical and aligning service/REST/MCP outputs.
Changes:
- Extend dependency projection and graph reads to include
PUBLISHES_TO(Service→Topic),SUBSCRIPTION_OF(Subscription→Topic), andRECEIVES_FROM(Service→Subscription), includingsubscription_idin claim identity only when a Subscription route exists. - Widen shared messaging telemetry coverage to treat observed
PUBLISHES_TO→TopicandRECEIVES_FROM→Subscriptionas messaging coverage signals. - Add unit/integration coverage for Pub/Sub projection rows, claim-id compatibility, reference CLI support, and service/REST/MCP equivalence with zero-write read assertions.
| File | Description |
|---|---|
| tests/unit/test_qualification_declared_observed.py | Adds coverage-signal test ensuring PUBLISHES_TO is treated as a messaging relation for coverage. |
| tests/unit/test_architecture_intelligence_service.py | Updates dependency-row test fixtures to include new Pub/Sub row keys. |
| tests/unit/test_architecture_intelligence_dependency_projection.py | Updates projection test harness rows to include Pub/Sub keys. |
| tests/unit/test_architecture_intelligence_dependency_projection_pubsub.py | New unit suite pinning Pub/Sub projection behaviors and claim-id compatibility rules. |
| tests/unit/test_architecture_answers_reference.py | Adds reference-vs-real claim-id parity for Topic/Subscription shapes and exercises new CLI flag. |
| tests/integration/test_pubsub_architecture_intelligence.py | New integration suite validating Pub/Sub dependencies/drift/evidence, equivalence across surfaces, and zero writes. |
| evaluation/architecture_answers/reference/identities.py | Mirrors production claim-id logic with optional subscription_id omitted when None. |
| evaluation/architecture_answers/reference/__main__.py | Extends reference CLI claim-id subcommand with optional --subscription-id. |
| app/qualification/declared_observed.py | Adds PUBLISHES_TO to messaging relation types and updates coverage mapping docstring. |
| app/architecture_intelligence/repository.py | Adds Pub/Sub Cypher reads and includes their evidence IDs in the referenced-evidence union. |
| app/architecture_intelligence/dependency_projection.py | Adds Pub/Sub destination resolution, routed subscription_id claim identity, and fallback limitation noun handling. |
| app/analysis/runtime.py | Widens messaging coverage queries to include observed Pub/Sub relations while keeping label/type gating explicit. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This PR implements I4 slice 4 (spec §15), the Architecture Intelligence vertical slice. Declared Topic/Subscription topology now appears in
get_service_dependencies,get_architecture_driftandget_evidence. The service, REST and MCP return identical answers. There are no contract, schema or answer-scenario changes, and HTTP/Queue claim ids are byte-identical.Approved plan (verbatim)
Context
Slices 1–3 are merged, ending at
5958b4d(#228–#231). Topic and Subscription nodes are persisted:PUBLISHES_TO,SUBSCRIPTION_OF,RECEIVES_FROM→SubscriptionandTopic CARRIES Message. They are also in the snapshot (canonicalization v3) and runtime-qualified (observedPUBLISHES_TOandRECEIVES_FROM→Subscriptionevidence), and the public contract and v0.5 schemas already carry them. What is still missing is the public answer path.get_service_dependenciesandget_architecture_driftread onlyCALLS→OperationandSENDS→Queue, the qualification kernel raises onPUBLISHES_TO, and coverage is Queue-typed. Slice 4 makes Pub/Sub routes into public dependency and drift claims, following §12.Governing Specification
docs/specifications/0.5.0/i4-source-independent-pubsub-semantics.md, Draft 0.3 (#226 + #227 →f98e48b). The sections that apply:Scope & Non-Goals
Scope:
DeliveryRef.subscriptionand the claim-idsubscription_id.Non-goals:
evaluation/architecture_answersscenarios, the full matrix and the two-run byte-identity check. These are slice 5.evaluation/model.pyandprojector.pyharness.reference/snapshot.py, which is still at v1. Disclosed only.Decisions settled by the user for spec gaps (disclose in the PR; fold into the spec or completion record in slice 6)
evidence_refscome only from the subject'sPUBLISHES_TOevidence.RECEIVES_FROM→Subscriptionevidence goes only into its own route'sresolution_evidence_refs, so it never reaches a sibling Subscription.SUBSCRIPTION_OFhas accepted (non-dangling) evidence.resolution_evidence_refs, which makes it reachable throughget_evidence(§12.6).PUBLISHES_TO→TopicandRECEIVES_FROM→Subscriptionnow setmessaging_observedin the sharedtelemetry_coverage.PUBLISHES_TOjoins the kernel's messaging relation set.messagingObservedbecome true for services that only have Pub/Sub telemetry.Affected Components
app/qualification/declared_observed.py:37:_MESSAGING_RELATION_TYPESgainsPUBLISHES_TO. Update the docstring.app/analysis/runtime.py:307-314: widen the two observed queries to-[r:SENDS|PUBLISHES_TO]->(t) WHERE t:Queue OR t:Topicand-[r:RECEIVES_FROM]->(t) WHERE t:Queue OR t:Subscription. Update the comments.app/architecture_intelligence/repository.py:_PUBLISHES_QUERY, which returns(Service {id})-[PUBLISHES_TO]->(Topic)with id, name, protocol, namespace and evidence_ids._SUBSCRIPTIONS_FOR_TOPICS_QUERY, which returns(Subscription)-[SUBSCRIPTION_OF]->(Topic) WHERE topic.id IN $idswith the sub's id, name, protocol and namespace plus evidence_ids._RECEIVES_FOR_SUBSCRIPTIONS_QUERY, for(Service)-[RECEIVES_FROM]->(Subscription).read_service_dependency_rowsgains the row keyspublishes,subscriptionsandsubscription_receives. All their evidence ids are added to the_referenced_evidence_idsunion.app/architecture_intelligence/dependency_projection.py:compute_claim_id(..., subscription_id: str | None = None)adds the key only when it is not None.canonical_json_bytesdoes not drop None, so this keeps HTTP and Queue ids byte-identical._build_claimpassesdelivery.subscription.idwhen it is present._topic_refand_subscription_ref, which follow the pattern of_queue_ref._resolve_pubsub_destinations(publish, subscriptions, receivers_by_sub, evidence_by_id), which reuses_group_evidenced_rows. Per §12.3:object=Topic,via=Topic,subscription=None,DIRECT_TARGET_FALLBACK;object=Subscription,via=Topic,subscription=Sub,DIRECT_TARGET_FALLBACK, and resolution refs from SUBSCRIPTION_OF;RESOLVED_SERVICEclaim per distinct consumer, where resolution refs = SUBSCRIPTION_OF ∪ that consumer's RECEIVES_FROM evidence.project_service_dependencies:_qualify(..., relation_type="PUBLISHES_TO");Noneresult gives_insufficient_evidence_limitation("PUBLISHES_TO", …);UNRESOLVED_IDENTITY._unresolved_identity_limitationtakes the fallback object ref._merge_duplicate_claimsstays as it is, since it is correct once the Subscription is in the claim id.service.py,drift_projection.py,evidence_projection.py, the REST/MCP adapters,contracts.pyorschemas/. Changing any of these triggers a stop (see below).evaluation/architecture_answers/reference/identities.py:claim_id(..., subscription_id=None)uses the same "omit when None" rule.reference/__main__.py: add an optional--subscription-idto theclaim-idsubcommand.Requirement → Task Mapping
viain the same snapshotsubscriptiononly from SUBSCRIPTION_OF rows read inside the sameread_extrasnapshot read_resolve_pubsub_destinations+ publish loopsubscription_id; via = Topic idcompute_claim_id+ reference mirror_SUPPORTING_RELATIONS_QUERYalready covers the new relationsAcceptance Criteria → Test/Evidence Mapping
tests/unit/test_architecture_intelligence_dependency_projection_pubsub.py:architecture-answer.schema.json.subscription_idkey.tests/unit/test_architecture_answers_reference.py): the real and reference ids agree for a Queue case, a Topic case and a Subscription case, and the CLI flag works.tests/unit/test_qualification_declared_observed.py):tests/unit/test_architecture_intelligence_service.pyandtests/unit/test_architecture_intelligence_dependency_projection.pygain the three empty keys..get(..., [])in the projection only if it is really needed. Prefer explicit keys, matching the repository's contract.tests/integration/test_pubsub_architecture_intelligence.py, which reuses_pubsub_scene,_importandTOPIC_IDfromtest_pubsub_persistence.pyand the span helpers fromtest_pubsub_runtime.py:service:ordersdependencies give two RESOLVED_SERVICE claims, one each for billing and shipping, with the correctsubscriptionroutes;test_api_architecture_intelligence_equivalence.pyandtest_mcp_*_equivalence.py);tests/integration/test_evaluation_architecture_answers.py, with all 23 scenarios unchanged, andtest_qualification_consistency.pygreen.Validation Commands
Open Questions / Assumptions / Stop Conditions
contracts.pyneeds a change;i4-slice-4-architecture-intelligence.Reconciliation against the plan
Completed As Planned
repository.py):_PUBLISHES_QUERY,_SUBSCRIPTIONS_FOR_TOPICS_QUERYand_RECEIVES_FOR_SUBSCRIPTIONS_QUERY. They run inside the same snapshotread_extra, and their evidence ids join the evidence union. The stale "7 relation kinds" comment is fixed.dependency_projection.py) covers every §12.3 row:RESOLVED_SERVICEclaim per distinct consumer, each sharing its Subscription route;UNRESOLVED_IDENTITYon every fallback;INSUFFICIENT_EVIDENCEwhen there is no safe claim.compute_claim_idaddssubscription_idonly when a claim has a Subscription route. The referenceidentities.claim_idand the CLI--subscription-idmirror it.PUBLISHES_TOis in the kernel's messaging set, and the sharedtelemetry_coveragecounts observedPUBLISHES_TO→TopicandRECEIVES_FROM→Subscription.contracts.py,schemas/,evaluation/architecture_answers/scenarios,service.py,drift_projection.py,evidence_projection.pyor the REST/MCP adapters.Deviations & Justification
DependencyClaiminvariant (contracts.py, mirrored in the committed schema) requiresresolution_evidence_refsto be empty for everyDIRECT_TARGET_FALLBACKclaim.SUBSCRIPTION_OFevidence still decides whether a route is usable, but it is referenced only fromRESOLVED_SERVICEclaims._unresolved_identity_limitationtakes the fallback claim'sobjectrather thandelivery.via. For Operation/Queue fallbacks these are identical, so that message text is unchanged.Specification Questions Discovered
All of these were resolved by the owner during planning or implementation, and are to be folded into the spec or completion record in slice 6:
resolution_evidence_refs.SUBSCRIPTION_OFcounts. A route is usable only whenSUBSCRIPTION_OFhas accepted evidence. Its evidence goes into resolved claims' resolution refs, as limited above.messagingObservedbecome true for services whose only telemetry is Pub/Sub. O1–O4 stay Queue-only.Deferred Work
evaluation/architecture_answers/reference/snapshot.py, which is still at v1: disclosed, not fixed.Test/Qualification Evidence
uv run ruff format --check .: 351 files already formatted.uv run ruff check .: all checks passed.uv run pytest tests/unit: 2132 passed.uv run pytest tests/integration --deselect tests/integration/test_mcp_demo_script.py: 417 passed, 12 deselected.test_mcp_demo_scriptis blocked locally because the maintainer's demo compose stack holds its ports. CI runs it.evaluation/architecture_answersscenarios pass unchanged, andtest_qualification_consistency.pyis green.git diff origin/main -- schemas/ app/architecture_intelligence/contracts.py evaluation/architecture_answers/scenarios examplesis empty.runtime.pycoverage widening makes 2 of the 8 new integration tests fail. They are restored and passing.Remaining Limitations
(object.id, kind, via.id, claim_id)has no Subscription component. Two routes to the same object and Topic are ordered byclaim_id, which is deterministic but not grouped by route.🤖 Generated with Claude Code
https://claude.ai/code/session_01XvQdzYyycuPbtbvBh5H49p