v0.5.0 I4 slice 1: decision and canonical foundation - #228
Conversation
Record the I4 Pub/Sub gate outcome as GO (spec §4, §15 Slice 1): - docs/specifications/0.5.0/i4-decision-evidence.md: independent evidence record (ASB, GCP Pub/Sub, OTel, Kafka; retrieved 2026-09-23), §4 distinction mapping, §4.2 GO disposition, §19 checklist dispositions. - ADR 0017 (Proposed): supersedes ADR 0013 only for the Topic/Subscription prohibition; both guards retained. ADR 0013 status and index updated. Add the in-memory canonical foundation (no adapter behavior, no persisted Pub/Sub node or relation, spec §11): - canonical Topic/Subscription models (not yet on ArchitectureModel); - topic_owned_id / subscription_owned_id identity helpers (§7.1/§7.2); - the three §8.4 DiagnosticCode members (vocabulary only); - Topic/Subscription uniqueness constraints; - public contract skeleton: EntityType TOPIC/SUBSCRIPTION, EvidenceRelationType PUBLISHES_TO/SUBSCRIPTION_OF, DeliveryRelationType PUBLISHES_TO, DeliveryRef.subscription with §12.2 invariants mirrored in the regenerated v0.5 schemas. A fail-closed test pins that importer/registry/canonicalization-v2 still exclude Pub/Sub until slice 2 opens that path atomically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review of |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
A new unit test pins the parameter list for subscription_owned_id but does not actually enforce the keyword-only requirement, leaving an important contract invariant untested.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Implements v0.5.0 I4 (Draft 0.3) Slice 1 by recording the GO decision and adding the in-memory canonical Pub/Sub foundation plus the contract/schema skeleton, while explicitly keeping Pub/Sub persistence closed until slice 2.
Changes:
- Added
Topic/Subscriptioncanonical models, identity helpers, diagnostic vocabulary, and Neo4j uniqueness constraints. - Extended the public contract and regenerated v0.5 JSON schemas to include Topic/Subscription +
PUBLISHES_TO/SUBSCRIPTION_OF, includingDeliveryRef.subscriptioninvariants and parity tests. - Added Slice 1 governance docs: decision evidence record + ADR 0017, and updated ADR index/ADR 0013 status note.
| File | Description |
|---|---|
| tests/unit/test_sources_owner_ids.py | Adds golden-vector tests for new Topic/Subscription identity helpers. |
| tests/unit/test_sources_model.py | Pins the exact three I4 DiagnosticCode additions. |
| tests/unit/test_canonical_pubsub_foundation.py | Adds fail-closed test ensuring persistence path remains closed until slice 2. |
| tests/unit/test_architecture_intelligence_contracts.py | Adds exhaustive Pydantic⇔schema parity tests for new contract rules. |
| tests/integration/test_importer.py | Extends constraint-name integration assertion for Topic/Subscription. |
| schemas/architecture_intelligence/v0.5/evidence-answer.schema.json | Regenerated schema reflecting Pub/Sub vocabulary + DeliveryRef.subscription. |
| schemas/architecture_intelligence/v0.5/drift-answer.schema.json | Regenerated schema reflecting Pub/Sub vocabulary + DeliveryRef.subscription. |
| schemas/architecture_intelligence/v0.5/architecture-answer.schema.json | Regenerated schema reflecting Pub/Sub vocabulary + DeliveryRef.subscription. |
| docs/specifications/0.5.0/i4-decision-evidence.md | New independent evidence record supporting the I4 GO decision. |
| docs/adr/README.md | Adds ADR 0017 entry and provenance note. |
| docs/adr/0017-source-independent-pubsub-semantics.md | New ADR recording GO and retaining ADR 0013 guards. |
| docs/adr/0013-no-topic-family-without-guards.md | Marks ADR 0013 as superseded in part (Topic/Subscription prohibition only). |
| app/sources/owner_ids.py | Adds topic_owned_id / subscription_owned_id helpers per I4 §7. |
| app/sources/model.py | Adds three I4 diagnostic codes as vocabulary. |
| app/graph/schema.py | Adds Neo4j uniqueness constraints for Topic/Subscription ids. |
| app/canonical/model.py | Adds Topic and Subscription canonical models (not yet on ArchitectureModel). |
| app/architecture_intelligence/contracts.py | Extends contract enums and validation rules to support Topic/Subscription and subscription routing. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| assert list(inspect.signature(subscription_owned_id).parameters) == [ | ||
| "stable_broker_id", | ||
| "normalized_namespace_or_empty", | ||
| "topic_id", | ||
| "exact_subscription_name", | ||
| ] |
There was a problem hiding this comment.
Fixed in eaa2ae8: added test_pubsub_owned_id_helpers_are_keyword_only, parametrized over topic_owned_id and subscription_owned_id. It asserts every parameter is KEYWORD_ONLY and that a positional call raises TypeError. Verified by temporarily removing the * from subscription_owned_id: the new test fails. Unit suite: 1957 passed.
Addresses PR #228 review: the signature test pinned parameter names but not the keyword-only contract shared with queue_owned_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Pushed |

Summary
Implements I4 Draft 0.3 §15 Slice 1 — Decision and canonical foundation, the first I4 slice.
GO. The independent evidence record isdocs/specifications/0.5.0/i4-decision-evidence.md. It uses seven sources retrieved 2026-09-23: Azure Service Bus, Google Pub/Sub, the OpenTelemetry messaging registry and spans, and Kafka Design. It maps each §4 distinction to a source, gives the §4.2 GO disposition, and records a disposition for every §19 checklist item.Proposedand moves to Accepted in slice 6. It supersedes ADR 0013 only for the Topic/Subscription prohibition and keeps both guards. The ADR 0013 status line and the ADR index are updated.TopicandSubscriptionmodels with exactly the §6.2 fields. They are not yet onArchitectureModel.topic_owned_idandsubscription_owned_id(§7.1/§7.2). The Subscription key binds the Topic id and has no consumer-group input.DiagnosticCodemembers, as vocabulary only.EntityType.TOPIC|SUBSCRIPTION,EvidenceRelationType.PUBLISHES_TO|SUBSCRIPTION_OFandDeliveryRelationType.PUBLISHES_TO.EntityRefprotocol/namespace are allowed for QUEUE/TOPIC/SUBSCRIPTION.DeliveryRef.subscriptionis optional: it must be null unlessviais a Topic, and must be SUBSCRIPTION-typed when set.schema_versionstays0.5.NODE_LABELS/KNOWN_RELATION_TYPES,graph_schemaRELATIONS,ArchitectureModeland_CANONICALIZATION_VERSION == 2all still exclude Pub/Sub. Slice 2 opens that path atomically.Test plan
uv run ruff format --check ./uv run ruff check .: cleanuv run python -m app.architecture_intelligence.schema_export: regeneration is idempotent (no diff on rerun)uv run pytest tests/unit: 1955 passed (140 new)uv run pytest tests/integration: 405 passedDeliveryRefschema rules is load-bearing. Deleting any one makes the exhaustive Pydantic⇔schema cross-product diverge on 2–8 cases.Reconciliation against the approved plan
Completed As Planned
Plan items 1–9: the evidence record, ADR 0017, the ADR 0013/README bookkeeping, the canonical models, the identity helpers, the DiagnosticCodes, the constraints, the public contract skeleton, and the schema regeneration. All acceptance tests are present:
SHOW CONSTRAINTScheck;DeliveryRefandEntityRefPydantic⇔schema parity checks;Deviations & Justification
expected_answer.jsonfiles already omit null-valued optional fields: OPERATION refs carry noprotocolkey, for example. The loader reads them throughmodel_validate. Adding"subscription": nullwould have broken that established style for no semantic gain. Live output does emitsubscription: null, as pinned bytest_delivery_ref_subscription_defaults_to_null_and_is_emitted. No fixture changed, so claim ids are trivially unchanged.EvidenceRelationTypedocstring was updated. It said "the 7 canonical graph relation kinds … (only CALLS/SENDS)", which was already stale after I3 and wrong after I4. The docstring appears in the evidence schema'sdescription.Specification Questions Discovered
_compute_mapping_context_digestwith Topic/Subscription mapping keys would change every pre-I4 mapping-context digest, even with empty arrays. §7.3 requires the digest to cover the new arrays but does not say whether pre-I4 digests must stay stable. This is not decided here.Deferred Work
This matches the plan's non-goals:
topicMappings/subscriptionMappingsartifact, the persistence path and canonicalization v3, and the dead-letter carrier.subscription_idand projection.Test/Qualification Evidence
Unit: 1955 passed. Integration: 405 passed. Ruff format and check: clean. Schema regeneration: idempotent.
Remaining Limitations
test_claim_with_invalid_delivery_pair_fails_both_pydantic_and_schemaand its neighbours._answer_dict_with_claim_dictdoesn't fill in the I3deployment_claim_ids/deployment_resolutions, so every payload is invalid for that reason alone. The new I4 tests use a complete answer and have a positive control. The existing tests are unchanged here and are worth a separate small fix.Approved plan (persisted verbatim per this repo's specification-driven-implementation workflow)
I4 Slice 1 — Decision and Canonical Foundation
Planning started at 2026-09-23T16:26:49Z. The PR body must carry
<!-- aip-agent-metadata:v1 {"planning_started_at":"2026-09-23T16:26:49Z"} -->.Context
I3 is complete (#225). The I4 specification merged in #226/#227 (
f98e48b) as aGOcandidate. Itstates that implementation is not authorized until it and ADR 0017 are reviewed, and that ADR 0013
stays authoritative until then. Slice 1 (§15) is where that decision gets made: the evidence record,
ADR 0017, and the GO decision. With GO, the slice also adds the in-memory canonical foundation:
models, identity helpers, graph constraints and the public contract skeleton. It adds no adapter
behavior and persists no Pub/Sub node or relation (§11, §15).
Decisions settled with the user:
Proposedand becomesAcceptedin slice 6. ADR 0013 is marked as supersededin part.
docs/specifications/0.5.0/i4-decision-evidence.md.stay closed, and a test pins that.
Governing Specification
docs/specifications/0.5.0/i4-source-independent-pubsub-semantics.md, Draft 0.3, as merged atf98e48b(#226 plus #227 residuals). Applicable sections: §4, §5, §6.2–6.3, §7.1–7.2, §8.4, §11,§12.1–12.2, §12.6, §15 Slice 1, §16, §19.
The parent spec is
docs/specifications/0.5.0/specification.mdPart III (§§776–838). ROADMAP I4row: "Recorded GO or DEFER".
Scope & Non-Goals
In scope
docs/specifications/0.5.0/i4-decision-evidence.md. It contains:(queues are competing-consumer; each subscription gets a copy; consumers compete within a
subscription), GCP Pub/Sub (fan-out across subscriptions; load-balancing within one), the OTel
messaging registry (
messaging.destination.subscription.nameandmessaging.consumer.group.nameare distinct attributes, both Development stability), andKafka (consumer groups).
the spec text or deferred to a named later slice.
docs/adr/0017-source-independent-pubsub-semantics.md, StatusProposed. Itcovers every §5 bullet:
app/telemetry/messaging_guards.pydecide_destination_semanticsanddecide_service_identity) and their regression tests;Destinationnormalization, consumer-group-to-Subscription equivalence, andruntime-only minting;
docs/adr/0013-*.md: the status line gains "Superseded in part by 0017 (Topic/Subscriptionprohibition only; both guards retained)". The body is unchanged.
docs/adr/README.md: add a row for 0017 and a one-line provenance note.app/canonical/model.py: addTopicandSubscriptionwith exactly the§6.2 fields
id,name,protocol: str | None,namespace: str | None. There is noqueue_type, no topic back-reference (that is carried only bySUBSCRIPTION_OF), and noconsumer, group, partition or filter fields. They are not added to
ArchitectureModel.app/sources/owner_ids.py. Both mirrorqueue_owned_id(L123-144): purehash formulas, keyword-only arguments, and the caller applies NFC, as the Queue docstring
documents. Both reuse
length_delimitedandsha256_hexfromapp/sources/encoding.py.topic_owned_id(*, stable_broker_id, normalized_namespace_or_empty, exact_topic_address)returns
topic:owned:<sha256>(§7.1).subscription_owned_id(*, stable_broker_id, normalized_namespace_or_empty, topic_id, exact_subscription_name)returnssubscription:owned:<sha256>(§7.2), with the Topic idbound into the key.
app/sources/model.pyDiagnosticCode: add exactlyTOPIC_IDENTITY_CONFLICT,SUBSCRIPTION_IDENTITY_CONFLICTandSUBSCRIPTION_IDENTITY_MISSING(§8.4). They are defined but not emitted yet.
app/graph/schema.pyCONSTRAINTS: addtopic_id/(t:Topic) REQUIRE t.id IS UNIQUEandsubscription_id/(s:Subscription) REQUIRE s.id IS UNIQUE, in the existing pattern. This is a DB-level invariantonly; no nodes are written.
app/architecture_intelligence/contracts.py. Every runtime rulehas a
json_schema_extramirror.EntityType: addTOPIC,SUBSCRIPTION.EvidenceRelationType: add exactlyPUBLISHES_TO,SUBSCRIPTION_OF(§12.1).DeliveryRelationType: addPUBLISHES_TO.EntityRef:protocol/namespaceare allowed forQUEUE | TOPIC | SUBSCRIPTION. Update theruntime validator, and change the schema
if const QUEUEtoif enum [...]._ALLOWED_DELIVERY_PAIRS: add(ASYNC_MESSAGE, PUBLISHES_TO, TOPIC).DeliveryRef.subscription: EntityRef | None = None, emitted asnulllike the existingoptional
EntityReffields. Invariants (§12.2):subscriptionmust be null unlessvia.type == TOPIC;subscriptionmust havetype == SUBSCRIPTION.ASYNC_MESSAGEadmitsSENDS/QUEUEorPUBLISHES_TO/TOPIC,and add the subscription rules as if/then.
is a projection invariant for slice 4, not a model rule.
schemas/architecture_intelligence/v0.5/*.schema.jsonfileswith
uv run python -m app.architecture_intelligence.schema_export.schema_versionstays"0.5".delivery.subscription: null:tests/fixtures/architecture_intelligence/{i1,i2,i3}/*.jsonandevaluation/architecture_answers/scenarios/*/expected_answer.json;"subscription": nulllines.Non-goals, deferred to the named slice
x-aip-destination-kind: topic, andtopicMappings/subscriptionMappingsartifact widening, including validator, index, resolver and
mapping_context_digest: slice 2.ArchitectureModel.topics/subscriptions, importerNODE_LABELS/KNOWN_RELATION_TYPES,graph_schema/registry.pyRELATIONS, canonical validation, dedicated Topic/Subscriptioncanonicalization queries, and the
_CANONICALIZATION_VERSION2→3 bump: slice 2, atomically.SubscriptionDeadLetterConfigurationcarrier: slice 2.subscription_idoptional payload field, in bothdependency_projection.pyandevaluation/architecture_answers/reference/identities.py, plus the dependency/drift projectionand evidence resolution: slice 4.
docs/canonical-model.mdanddocs/graph-model.md(slice 6), and MCP tools.Affected Components
docs/specifications/0.5.0/i4-decision-evidence.md(new)docs/adr/0017-source-independent-pubsub-semantics.md(new)docs/adr/0013-no-topic-family-without-guards.md(status line only)docs/adr/README.mdapp/canonical/model.pyapp/sources/owner_ids.pyapp/sources/model.pyapp/graph/schema.pyapp/architecture_intelligence/contracts.pyexpected_answer.jsonfiles (subscriptionnull only).
tests/unit/test_architecture_intelligence_contracts.pytests/unit/test_sources_owner_ids.pytests/unit/test_sources_model.pytests/unit/test_canonical_pubsub_foundation.pyembed the delivery shape
Requirement -> Task Mapping
0.5Acceptance Criteria -> Test/Evidence Mapping
Identity helpers, in
test_sources_owner_ids.py:hashliband manual 8-bytebig-endian length prefixes, not by calling
length_delimited.topic:owned:andsubscription:owned:.Canonical models: the field set is exactly
{id, name, protocol, namespace}.TopicandSubscriptionare not inArchitectureModel.model_fields.Fail-closed persistence guard (new test):
PUBLISHES_TOandSUBSCRIPTION_OFare not inKNOWN_RELATION_TYPESorRELATIONS.TopicandSubscriptionare not inNODE_LABELSvalues._CANONICALIZATION_VERSION == 2.DiagnosticCode: the current membership minus a literal pre-I4 set equals exactly the three §8.4
members.
Constraints: unit assertion that both Cypher strings are in
CONSTRAINTS. The integration testtest_ensure_schema_creates_constraintscovers them against real Neo4j (a subset check, so addboth names).
Contract, Pydantic ⇔ schema parity. An exhaustive parametrized cross-product over:
kind×relation_type×via.type(all 6 non-WORKLOAD types);subscription∈ {null, SUBSCRIPTION ref, QUEUE-typed ref, TOPIC-typed ref}.For each combination, Pydantic acceptance must equal
jsonschemaacceptance against the committedschema. Exactly these are valid:
SYNC_HTTP/CALLS/OPERATIONwith null subscription;ASYNC_MESSAGE/SENDS/QUEUEwith null subscription;ASYNC_MESSAGE/PUBLISHES_TO/TOPICwith null or aSUBSCRIPTIONref.EntityRefprotocol/namespace parity runs across all types. Evidence schema enum membershipincludes the two new relation types.
Schema frozen parity: the existing byte-equality tests pass after regeneration, and a rerun
is idempotent.
Queue non-regression:
git diff -U0 -- tests/fixtures evaluation | grep '^[-+] ' | grep -v subscriptionmust beempty apart from headers.
MCP: tool count is still 3 (existing tests).
Validation Commands
uv run ruff format ., thenuv run ruff check --fix ., before any test run.uv run python -m app.architecture_intelligence.schema_export, run twice to confirm the outputis idempotent.
uv run pytest tests/unituv run pytest tests/integration(real Neo4j)uv run ruff check .anduv run ruff format --check .git diff --statfor scope review.Open Questions / Assumptions / Stop Conditions
§13.1's "exactly three" check is pinned now.
DeliveryRef.subscriptionis always emitted asnull, matching theEntityRefoptional-field pattern and spec §12.2's
EntityRef | null. This causes fixture churn but noclaim-id change.
DependencyClaimneeds no new object-type rule. The existing rule allows TOPICand SUBSCRIPTION objects, as §12.3's
DIRECT_TARGET_FALLBACKrequires._compute_mapping_context_digestwithnew
sharedTopicMappings/sharedSubscriptionMappingskeys would change every existingmapping-context digest even when the arrays are empty. §7.3 requires the digest to cover the new
arrays, but it doesn't say whether pre-I4 digests must stay stable. Slice 2 planning must raise
this.
and return to §4 (a DEFER risk).
main, namedi4-slice-1-decision-foundation.🤖 Generated with Claude Code