tigrcorn-contract 0.3.16.dev12__tar.gz → 0.3.17.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/PKG-INFO +3 -3
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/pyproject.toml +3 -3
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/__init__.py +42 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/events.py +8 -1
- tigrcorn_contract-0.3.17.dev1/src/tigrcorn_contract/registry.py +248 -0
- tigrcorn_contract-0.3.17.dev1/src/tigrcorn_contract/semantics.py +348 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract.egg-info/PKG-INFO +3 -3
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract.egg-info/SOURCES.txt +2 -0
- tigrcorn_contract-0.3.17.dev1/src/tigrcorn_contract.egg-info/requires.txt +3 -0
- tigrcorn_contract-0.3.16.dev12/src/tigrcorn_contract.egg-info/requires.txt +0 -3
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/LICENSE +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/README.md +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/setup.cfg +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/classification.py +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/compat.py +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/metadata.py +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/projection.py +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/py.typed +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/scopes.py +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract.egg-info/dependency_links.txt +0 -0
- {tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrcorn-contract
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.17.dev1
|
|
4
4
|
Summary: tigr-asgi-contract adapters and runtime-boundary classification for Tigrcorn ASGI3, WebSocket, and WebTransport surfaces.
|
|
5
5
|
Author-email: Jacob Stewart <jacob@swarmauri.com>
|
|
6
6
|
License: Apache License
|
|
@@ -186,8 +186,8 @@ Classifier: Typing :: Typed
|
|
|
186
186
|
Requires-Python: <3.15,>=3.10
|
|
187
187
|
Description-Content-Type: text/markdown
|
|
188
188
|
License-File: LICENSE
|
|
189
|
-
Requires-Dist: tigrcorn-core==0.3.
|
|
190
|
-
Requires-Dist: tigrcorn-asgi==0.3.
|
|
189
|
+
Requires-Dist: tigrcorn-core==0.3.17.dev1
|
|
190
|
+
Requires-Dist: tigrcorn-asgi==0.3.17.dev1
|
|
191
191
|
Requires-Dist: tigr-asgi-contract>=0.3.2
|
|
192
192
|
Dynamic: license-file
|
|
193
193
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tigrcorn-contract"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.17.dev1"
|
|
8
8
|
description = "tigr-asgi-contract adapters and runtime-boundary classification for Tigrcorn ASGI3, WebSocket, and WebTransport surfaces."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10,<3.15"
|
|
@@ -29,8 +29,8 @@ classifiers = [
|
|
|
29
29
|
"Typing :: Typed",
|
|
30
30
|
]
|
|
31
31
|
dependencies = [
|
|
32
|
-
"tigrcorn-core==0.3.
|
|
33
|
-
"tigrcorn-asgi==0.3.
|
|
32
|
+
"tigrcorn-core==0.3.17.dev1",
|
|
33
|
+
"tigrcorn-asgi==0.3.17.dev1",
|
|
34
34
|
"tigr-asgi-contract>=0.3.2",
|
|
35
35
|
]
|
|
36
36
|
|
{tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/__init__.py
RENAMED
|
@@ -96,6 +96,29 @@ from .projection import (
|
|
|
96
96
|
project_send_event,
|
|
97
97
|
validate_projected_event,
|
|
98
98
|
)
|
|
99
|
+
from .semantics import (
|
|
100
|
+
SemanticObservation,
|
|
101
|
+
channel_lifecycle_capabilities,
|
|
102
|
+
enforce_semantic_transition,
|
|
103
|
+
observe_backpressure as observe_contract_backpressure,
|
|
104
|
+
observe_channel_lifecycle,
|
|
105
|
+
observe_completion,
|
|
106
|
+
observe_disconnect,
|
|
107
|
+
observe_transport_cancellation,
|
|
108
|
+
require_channel_readable,
|
|
109
|
+
require_channel_writable,
|
|
110
|
+
)
|
|
111
|
+
from .registry import (
|
|
112
|
+
ContractRecord,
|
|
113
|
+
ContractStability,
|
|
114
|
+
ContractTraceability,
|
|
115
|
+
ContractTraceabilityError,
|
|
116
|
+
TraceabilityStatus,
|
|
117
|
+
contract_records,
|
|
118
|
+
export_contract_registry,
|
|
119
|
+
validate_contract_traceability,
|
|
120
|
+
validate_registry,
|
|
121
|
+
)
|
|
99
122
|
from .scopes import SUPPORTED_SCOPE_TYPES, contract_scope, validate_scope
|
|
100
123
|
|
|
101
124
|
__all__ = [
|
|
@@ -103,6 +126,10 @@ __all__ = [
|
|
|
103
126
|
"CompletionLevel",
|
|
104
127
|
"CompletionStatus",
|
|
105
128
|
"ConnectionIdentity",
|
|
129
|
+
"ContractRecord",
|
|
130
|
+
"ContractStability",
|
|
131
|
+
"ContractTraceability",
|
|
132
|
+
"ContractTraceabilityError",
|
|
106
133
|
"EndpointMetadata",
|
|
107
134
|
"FamilyCapability",
|
|
108
135
|
"GenericDatagramRuntime",
|
|
@@ -112,23 +139,29 @@ __all__ = [
|
|
|
112
139
|
"HTTPFeatureContractMap",
|
|
113
140
|
"SUPPORTED_SCOPE_TYPES",
|
|
114
141
|
"SecurityMetadata",
|
|
142
|
+
"SemanticObservation",
|
|
115
143
|
"EventProjection",
|
|
116
144
|
"ScopeProjection",
|
|
117
145
|
"StreamIdentity",
|
|
146
|
+
"TraceabilityStatus",
|
|
118
147
|
"UnitIdentity",
|
|
119
148
|
"alt_svc_contract_map",
|
|
120
149
|
"asgi3_extensions",
|
|
121
150
|
"asgi3_compat_scope",
|
|
122
151
|
"asgi_extension_bridge",
|
|
152
|
+
"channel_lifecycle_capabilities",
|
|
123
153
|
"classify_binding",
|
|
124
154
|
"compatibility_feature_parity",
|
|
125
155
|
"contract_scope",
|
|
156
|
+
"contract_records",
|
|
126
157
|
"content_coding_contract_map",
|
|
127
158
|
"datagram_identity",
|
|
128
159
|
"datagram_receive",
|
|
129
160
|
"datagram_send",
|
|
130
161
|
"emit_complete",
|
|
131
162
|
"endpoint_metadata",
|
|
163
|
+
"export_contract_registry",
|
|
164
|
+
"enforce_semantic_transition",
|
|
132
165
|
"early_hints_contract_map",
|
|
133
166
|
"family_capability",
|
|
134
167
|
"http_disconnect",
|
|
@@ -145,6 +178,11 @@ __all__ = [
|
|
|
145
178
|
"lifespan_startup_failed",
|
|
146
179
|
"map_contract_event",
|
|
147
180
|
"observability_contract_metadata",
|
|
181
|
+
"observe_contract_backpressure",
|
|
182
|
+
"observe_channel_lifecycle",
|
|
183
|
+
"observe_completion",
|
|
184
|
+
"observe_disconnect",
|
|
185
|
+
"observe_transport_cancellation",
|
|
148
186
|
"proxy_normalization_contract_map",
|
|
149
187
|
"product_surface_status",
|
|
150
188
|
"product_surface_excluded",
|
|
@@ -152,6 +190,8 @@ __all__ = [
|
|
|
152
190
|
"project_receive_event",
|
|
153
191
|
"project_scope_classification",
|
|
154
192
|
"project_send_event",
|
|
193
|
+
"require_channel_readable",
|
|
194
|
+
"require_channel_writable",
|
|
155
195
|
"require_lossless_metadata",
|
|
156
196
|
"require_product_boundary_exclusion",
|
|
157
197
|
"require_product_runtime_available",
|
|
@@ -165,12 +205,14 @@ __all__ = [
|
|
|
165
205
|
"trailers_contract_map",
|
|
166
206
|
"unit_identity",
|
|
167
207
|
"validate_binding_legality",
|
|
208
|
+
"validate_contract_traceability",
|
|
168
209
|
"validate_connection_identity",
|
|
169
210
|
"validate_datagram_event",
|
|
170
211
|
"validate_endpoint_metadata",
|
|
171
212
|
"validate_event_order",
|
|
172
213
|
"validate_projected_event",
|
|
173
214
|
"validate_security_metadata",
|
|
215
|
+
"validate_registry",
|
|
174
216
|
"validate_scope",
|
|
175
217
|
"validate_stream_event",
|
|
176
218
|
"validate_stream_identity",
|
{tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/events.py
RENAMED
|
@@ -47,7 +47,11 @@ WEBTRANSPORT_EVENT_MAP = {
|
|
|
47
47
|
|
|
48
48
|
class CompletionLevel(StrEnum):
|
|
49
49
|
ACCEPTED_BY_RUNTIME = "accepted_by_runtime"
|
|
50
|
+
QUEUED_FOR_TRANSPORT = "queued_for_transport"
|
|
50
51
|
FLUSHED_TO_TRANSPORT = "flushed_to_transport"
|
|
52
|
+
PEER_ACKNOWLEDGED = "peer_acknowledged"
|
|
53
|
+
FAILED_DURING_EMIT = "failed_during_emit"
|
|
54
|
+
ABORTED_BY_PEER = "aborted_by_peer"
|
|
51
55
|
|
|
52
56
|
|
|
53
57
|
class CompletionStatus(StrEnum):
|
|
@@ -436,9 +440,12 @@ def _normalize_completion_level(level: str) -> str:
|
|
|
436
440
|
aliases = {
|
|
437
441
|
"buffered": CompletionLevel.ACCEPTED_BY_RUNTIME.value,
|
|
438
442
|
"accepted": CompletionLevel.ACCEPTED_BY_RUNTIME.value,
|
|
443
|
+
"queued": CompletionLevel.QUEUED_FOR_TRANSPORT.value,
|
|
439
444
|
"flushed": CompletionLevel.FLUSHED_TO_TRANSPORT.value,
|
|
440
445
|
"transport": CompletionLevel.FLUSHED_TO_TRANSPORT.value,
|
|
441
|
-
"acknowledged": CompletionLevel.
|
|
446
|
+
"acknowledged": CompletionLevel.PEER_ACKNOWLEDGED.value,
|
|
447
|
+
"failed": CompletionLevel.FAILED_DURING_EMIT.value,
|
|
448
|
+
"aborted": CompletionLevel.ABORTED_BY_PEER.value,
|
|
442
449
|
}
|
|
443
450
|
return aliases.get(level, level)
|
|
444
451
|
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any, Iterable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ContractStability(str, Enum):
|
|
9
|
+
EXPERIMENTAL = "experimental"
|
|
10
|
+
PREVIEW = "preview"
|
|
11
|
+
STABLE = "stable"
|
|
12
|
+
CERTIFIED = "certified"
|
|
13
|
+
DEPRECATED = "deprecated"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TraceabilityStatus(str, Enum):
|
|
17
|
+
COMPLETE = "complete"
|
|
18
|
+
PARTIAL = "partial"
|
|
19
|
+
PROVISIONAL = "provisional"
|
|
20
|
+
FAILED = "failed"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ContractTraceabilityError(ValueError):
|
|
24
|
+
"""Raised when a contract claims certification without required proof."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class ContractTraceability:
|
|
29
|
+
rfcs: tuple[str, ...] = ()
|
|
30
|
+
spec_ids: tuple[str, ...] = ()
|
|
31
|
+
implementation_refs: tuple[str, ...] = ()
|
|
32
|
+
test_ids: tuple[str, ...] = ()
|
|
33
|
+
evidence_ids: tuple[str, ...] = ()
|
|
34
|
+
negative_test_ids: tuple[str, ...] = ()
|
|
35
|
+
status: TraceabilityStatus = TraceabilityStatus.PARTIAL
|
|
36
|
+
release_certified: bool = False
|
|
37
|
+
|
|
38
|
+
def as_dict(self) -> dict[str, Any]:
|
|
39
|
+
return {
|
|
40
|
+
"rfcs": list(self.rfcs),
|
|
41
|
+
"spec_ids": list(self.spec_ids),
|
|
42
|
+
"implementation_refs": list(self.implementation_refs),
|
|
43
|
+
"test_ids": list(self.test_ids),
|
|
44
|
+
"evidence_ids": list(self.evidence_ids),
|
|
45
|
+
"negative_test_ids": list(self.negative_test_ids),
|
|
46
|
+
"status": self.status.value,
|
|
47
|
+
"release_certified": self.release_certified,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class ContractRecord:
|
|
53
|
+
contract_id: str
|
|
54
|
+
version: str
|
|
55
|
+
title: str
|
|
56
|
+
owner_package: str
|
|
57
|
+
owner_module: str
|
|
58
|
+
stability: ContractStability
|
|
59
|
+
implemented: bool
|
|
60
|
+
certified: bool
|
|
61
|
+
traceability: ContractTraceability
|
|
62
|
+
replacement_contract_id: str | None = None
|
|
63
|
+
retirement_note: str | None = None
|
|
64
|
+
tags: tuple[str, ...] = field(default_factory=tuple)
|
|
65
|
+
|
|
66
|
+
def as_dict(self) -> dict[str, Any]:
|
|
67
|
+
payload = {
|
|
68
|
+
"contract_id": self.contract_id,
|
|
69
|
+
"version": self.version,
|
|
70
|
+
"title": self.title,
|
|
71
|
+
"owner_package": self.owner_package,
|
|
72
|
+
"owner_module": self.owner_module,
|
|
73
|
+
"stability": self.stability.value,
|
|
74
|
+
"implemented": self.implemented,
|
|
75
|
+
"certified": self.certified,
|
|
76
|
+
"traceability": self.traceability.as_dict(),
|
|
77
|
+
"tags": list(self.tags),
|
|
78
|
+
}
|
|
79
|
+
if self.replacement_contract_id is not None:
|
|
80
|
+
payload["replacement_contract_id"] = self.replacement_contract_id
|
|
81
|
+
if self.retirement_note is not None:
|
|
82
|
+
payload["retirement_note"] = self.retirement_note
|
|
83
|
+
return payload
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _traceability_status(
|
|
87
|
+
*,
|
|
88
|
+
rfcs: Iterable[str] = (),
|
|
89
|
+
spec_ids: Iterable[str] = (),
|
|
90
|
+
implementation_refs: Iterable[str] = (),
|
|
91
|
+
test_ids: Iterable[str] = (),
|
|
92
|
+
evidence_ids: Iterable[str] = (),
|
|
93
|
+
negative_test_ids: Iterable[str] = (),
|
|
94
|
+
provisional: bool = False,
|
|
95
|
+
) -> ContractTraceability:
|
|
96
|
+
traceability = ContractTraceability(
|
|
97
|
+
rfcs=tuple(sorted(rfcs)),
|
|
98
|
+
spec_ids=tuple(sorted(spec_ids)),
|
|
99
|
+
implementation_refs=tuple(sorted(implementation_refs)),
|
|
100
|
+
test_ids=tuple(sorted(test_ids)),
|
|
101
|
+
evidence_ids=tuple(sorted(evidence_ids)),
|
|
102
|
+
negative_test_ids=tuple(sorted(negative_test_ids)),
|
|
103
|
+
status=TraceabilityStatus.PROVISIONAL if provisional else TraceabilityStatus.PARTIAL,
|
|
104
|
+
)
|
|
105
|
+
complete = all(
|
|
106
|
+
(
|
|
107
|
+
traceability.rfcs,
|
|
108
|
+
traceability.spec_ids,
|
|
109
|
+
traceability.implementation_refs,
|
|
110
|
+
traceability.test_ids,
|
|
111
|
+
traceability.evidence_ids,
|
|
112
|
+
traceability.negative_test_ids,
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
if complete and not provisional:
|
|
116
|
+
return ContractTraceability(
|
|
117
|
+
rfcs=traceability.rfcs,
|
|
118
|
+
spec_ids=traceability.spec_ids,
|
|
119
|
+
implementation_refs=traceability.implementation_refs,
|
|
120
|
+
test_ids=traceability.test_ids,
|
|
121
|
+
evidence_ids=traceability.evidence_ids,
|
|
122
|
+
negative_test_ids=traceability.negative_test_ids,
|
|
123
|
+
status=TraceabilityStatus.COMPLETE,
|
|
124
|
+
release_certified=True,
|
|
125
|
+
)
|
|
126
|
+
return traceability
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
_CONTRACTS: tuple[ContractRecord, ...] = (
|
|
130
|
+
ContractRecord(
|
|
131
|
+
contract_id="asgi.http.scope",
|
|
132
|
+
version="1.0",
|
|
133
|
+
title="HTTP scope contract",
|
|
134
|
+
owner_package="tigrcorn-contract",
|
|
135
|
+
owner_module="tigrcorn_contract.scopes",
|
|
136
|
+
stability=ContractStability.CERTIFIED,
|
|
137
|
+
implemented=True,
|
|
138
|
+
certified=True,
|
|
139
|
+
traceability=_traceability_status(
|
|
140
|
+
rfcs=("RFC 9110", "RFC 9112"),
|
|
141
|
+
spec_ids=("spc:1021",),
|
|
142
|
+
implementation_refs=("tigrcorn_contract.scopes:contract_scope",),
|
|
143
|
+
test_ids=("tst:contract-http-scope", "tst:contract-unsupported-scope-rejection"),
|
|
144
|
+
evidence_ids=("evd:contract-http-scope-pytest", "evd:contract-unsupported-scope-rejection-pytest"),
|
|
145
|
+
negative_test_ids=("tst:contract-unsupported-scope-rejection",),
|
|
146
|
+
),
|
|
147
|
+
tags=("http", "scope"),
|
|
148
|
+
),
|
|
149
|
+
ContractRecord(
|
|
150
|
+
contract_id="asgi.websocket.events",
|
|
151
|
+
version="1.0",
|
|
152
|
+
title="WebSocket event contract",
|
|
153
|
+
owner_package="tigrcorn-contract",
|
|
154
|
+
owner_module="tigrcorn_contract.events",
|
|
155
|
+
stability=ContractStability.STABLE,
|
|
156
|
+
implemented=True,
|
|
157
|
+
certified=False,
|
|
158
|
+
traceability=_traceability_status(
|
|
159
|
+
rfcs=("RFC 6455",),
|
|
160
|
+
spec_ids=("spc:1021",),
|
|
161
|
+
implementation_refs=("tigrcorn_contract.events:websocket_connect",),
|
|
162
|
+
test_ids=("tst:contract-websocket-event-map",),
|
|
163
|
+
evidence_ids=("evd:contract-websocket-event-map-pytest",),
|
|
164
|
+
),
|
|
165
|
+
tags=("websocket", "events"),
|
|
166
|
+
),
|
|
167
|
+
ContractRecord(
|
|
168
|
+
contract_id="webtransport.stream.identity",
|
|
169
|
+
version="0.9",
|
|
170
|
+
title="WebTransport stream identity contract",
|
|
171
|
+
owner_package="tigrcorn-contract",
|
|
172
|
+
owner_module="tigrcorn_contract.metadata",
|
|
173
|
+
stability=ContractStability.PREVIEW,
|
|
174
|
+
implemented=True,
|
|
175
|
+
certified=False,
|
|
176
|
+
traceability=_traceability_status(
|
|
177
|
+
rfcs=("RFC 9220",),
|
|
178
|
+
spec_ids=("spc:1011",),
|
|
179
|
+
implementation_refs=("tigrcorn_contract.metadata:stream_identity",),
|
|
180
|
+
test_ids=("tst:contract-webtransport-stream-identity",),
|
|
181
|
+
evidence_ids=("evd:contract-webtransport-stream-identity-pytest",),
|
|
182
|
+
provisional=True,
|
|
183
|
+
),
|
|
184
|
+
tags=("webtransport", "identity"),
|
|
185
|
+
),
|
|
186
|
+
ContractRecord(
|
|
187
|
+
contract_id="operator.contract.registry",
|
|
188
|
+
version="0.1",
|
|
189
|
+
title="Contract registry export contract",
|
|
190
|
+
owner_package="tigrcorn-contract",
|
|
191
|
+
owner_module="tigrcorn_contract.registry",
|
|
192
|
+
stability=ContractStability.EXPERIMENTAL,
|
|
193
|
+
implemented=True,
|
|
194
|
+
certified=False,
|
|
195
|
+
traceability=_traceability_status(
|
|
196
|
+
spec_ids=("spc:2058",),
|
|
197
|
+
implementation_refs=("tigrcorn_contract.registry:export_contract_registry",),
|
|
198
|
+
test_ids=("tst:contract-registry-record-shape", "tst:contract-registry-deterministic-export"),
|
|
199
|
+
evidence_ids=("evd:contract-registry-and-traceability-test-plan",),
|
|
200
|
+
),
|
|
201
|
+
tags=("operator", "registry"),
|
|
202
|
+
),
|
|
203
|
+
ContractRecord(
|
|
204
|
+
contract_id="asgi.http.scope.v0",
|
|
205
|
+
version="0.9",
|
|
206
|
+
title="Deprecated HTTP scope contract",
|
|
207
|
+
owner_package="tigrcorn-contract",
|
|
208
|
+
owner_module="tigrcorn_contract.scopes",
|
|
209
|
+
stability=ContractStability.DEPRECATED,
|
|
210
|
+
implemented=False,
|
|
211
|
+
certified=False,
|
|
212
|
+
traceability=_traceability_status(spec_ids=("spc:1021",)),
|
|
213
|
+
replacement_contract_id="asgi.http.scope",
|
|
214
|
+
retirement_note="Replaced by the certified 1.0 HTTP scope contract.",
|
|
215
|
+
tags=("http", "scope"),
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def contract_records() -> tuple[ContractRecord, ...]:
|
|
221
|
+
return tuple(sorted(_CONTRACTS, key=lambda item: item.contract_id))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def export_contract_registry() -> dict[str, Any]:
|
|
225
|
+
return {
|
|
226
|
+
"schema_version": "1.0",
|
|
227
|
+
"registry": "tigrcorn.contracts",
|
|
228
|
+
"contracts": [record.as_dict() for record in contract_records()],
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def validate_contract_traceability(record: ContractRecord) -> None:
|
|
233
|
+
traceability = record.traceability
|
|
234
|
+
missing: list[str] = []
|
|
235
|
+
for field_name in ("rfcs", "spec_ids", "implementation_refs", "test_ids", "evidence_ids", "negative_test_ids"):
|
|
236
|
+
if not getattr(traceability, field_name):
|
|
237
|
+
missing.append(field_name)
|
|
238
|
+
if record.certified and (traceability.status != TraceabilityStatus.COMPLETE or missing):
|
|
239
|
+
raise ContractTraceabilityError(
|
|
240
|
+
f"certified contract {record.contract_id!r} lacks complete traceability: {', '.join(missing)}"
|
|
241
|
+
)
|
|
242
|
+
if record.certified and record.stability != ContractStability.CERTIFIED:
|
|
243
|
+
raise ContractTraceabilityError(f"certified contract {record.contract_id!r} must use certified stability")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def validate_registry(records: Iterable[ContractRecord] | None = None) -> None:
|
|
247
|
+
for record in records if records is not None else contract_records():
|
|
248
|
+
validate_contract_traceability(record)
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
from tigrcorn_core.errors import ProtocolError
|
|
7
|
+
|
|
8
|
+
try: # pragma: no cover - covered when the newer contract package is installed.
|
|
9
|
+
from tigr_asgi_contract import semantic_domain as _contract_semantic_domain
|
|
10
|
+
from tigr_asgi_contract import semantic_transition_target as _contract_transition_target
|
|
11
|
+
from tigr_asgi_contract import validate_semantic_transition as _contract_validate_transition
|
|
12
|
+
except Exception: # pragma: no cover - published contract fallback.
|
|
13
|
+
_contract_semantic_domain = None
|
|
14
|
+
_contract_transition_target = None
|
|
15
|
+
_contract_validate_transition = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_FALLBACK_TRANSITIONS = {
|
|
19
|
+
("completion", "accepted_by_runtime", "completion.queued"): "queued_for_transport",
|
|
20
|
+
("completion", "queued_for_transport", "completion.flushed"): "flushed_to_transport",
|
|
21
|
+
("completion", "flushed_to_transport", "completion.peer_acknowledged"): "peer_acknowledged",
|
|
22
|
+
("completion", "accepted_by_runtime", "completion.failed"): "failed_during_emit",
|
|
23
|
+
("completion", "queued_for_transport", "completion.failed"): "failed_during_emit",
|
|
24
|
+
("completion", "flushed_to_transport", "completion.failed"): "failed_during_emit",
|
|
25
|
+
("completion", "accepted_by_runtime", "completion.peer_aborted"): "aborted_by_peer",
|
|
26
|
+
("completion", "queued_for_transport", "completion.peer_aborted"): "aborted_by_peer",
|
|
27
|
+
("completion", "flushed_to_transport", "completion.peer_aborted"): "aborted_by_peer",
|
|
28
|
+
("backpressure", "writable", "backpressure.congested"): "congested",
|
|
29
|
+
("backpressure", "congested", "backpressure.saturated"): "saturated",
|
|
30
|
+
("backpressure", "saturated", "backpressure.draining"): "draining",
|
|
31
|
+
("backpressure", "draining", "backpressure.resumed"): "resumed",
|
|
32
|
+
("backpressure", "resumed", "backpressure.writable"): "writable",
|
|
33
|
+
("backpressure", "congested", "backpressure.resumed"): "resumed",
|
|
34
|
+
("cancellation", "requested", "cancellation.propagated"): "propagated",
|
|
35
|
+
("cancellation", "propagated", "cancellation.acknowledged"): "acknowledged",
|
|
36
|
+
("cancellation", "acknowledged", "cancellation.completed"): "completed",
|
|
37
|
+
("cancellation", "requested", "cancellation.ignored"): "ignored",
|
|
38
|
+
("cancellation", "propagated", "cancellation.failed"): "failed",
|
|
39
|
+
("cancellation", "acknowledged", "cancellation.failed"): "failed",
|
|
40
|
+
("disconnect", "graceful", "disconnect.peer_reset"): "peer_reset",
|
|
41
|
+
("disconnect", "graceful", "disconnect.transport_lost"): "transport_lost",
|
|
42
|
+
("disconnect", "graceful", "disconnect.timeout"): "timeout",
|
|
43
|
+
("disconnect", "graceful", "disconnect.protocol_error"): "protocol_error",
|
|
44
|
+
("disconnect", "graceful", "disconnect.server_shutdown"): "server_shutdown",
|
|
45
|
+
("channel_lifecycle", "initialized", "channel.opening"): "opening",
|
|
46
|
+
("channel_lifecycle", "opening", "channel.opened"): "open",
|
|
47
|
+
("channel_lifecycle", "open", "channel.read_closed"): "read_closed",
|
|
48
|
+
("channel_lifecycle", "open", "channel.write_closed"): "write_closed",
|
|
49
|
+
("channel_lifecycle", "open", "channel.closing"): "closing",
|
|
50
|
+
("channel_lifecycle", "open", "channel.closed"): "closed",
|
|
51
|
+
("channel_lifecycle", "open", "channel.failed"): "failed",
|
|
52
|
+
("channel_lifecycle", "open", "channel.lost"): "lost",
|
|
53
|
+
("channel_lifecycle", "read_closed", "channel.write_closed"): "closed",
|
|
54
|
+
("channel_lifecycle", "read_closed", "channel.closing"): "closing",
|
|
55
|
+
("channel_lifecycle", "read_closed", "channel.failed"): "failed",
|
|
56
|
+
("channel_lifecycle", "read_closed", "channel.lost"): "lost",
|
|
57
|
+
("channel_lifecycle", "write_closed", "channel.read_closed"): "closed",
|
|
58
|
+
("channel_lifecycle", "write_closed", "channel.closing"): "closing",
|
|
59
|
+
("channel_lifecycle", "write_closed", "channel.failed"): "failed",
|
|
60
|
+
("channel_lifecycle", "write_closed", "channel.lost"): "lost",
|
|
61
|
+
("channel_lifecycle", "closing", "channel.closed"): "closed",
|
|
62
|
+
("channel_lifecycle", "closing", "channel.failed"): "failed",
|
|
63
|
+
("channel_lifecycle", "closing", "channel.lost"): "lost",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_FALLBACK_CHANNEL_CAPABILITIES = {
|
|
67
|
+
"initialized": {"can_drain": False, "can_read": False, "can_write": False, "terminal": False},
|
|
68
|
+
"opening": {"can_drain": False, "can_read": False, "can_write": False, "terminal": False},
|
|
69
|
+
"open": {"can_drain": False, "can_read": True, "can_write": True, "terminal": False},
|
|
70
|
+
"read_closed": {"can_drain": True, "can_read": False, "can_write": True, "terminal": False},
|
|
71
|
+
"write_closed": {"can_drain": True, "can_read": True, "can_write": False, "terminal": False},
|
|
72
|
+
"closing": {"can_drain": True, "can_read": False, "can_write": False, "terminal": False},
|
|
73
|
+
"closed": {"can_drain": False, "can_read": False, "can_write": False, "terminal": True},
|
|
74
|
+
"failed": {"can_drain": False, "can_read": False, "can_write": False, "terminal": True},
|
|
75
|
+
"lost": {"can_drain": False, "can_read": False, "can_write": False, "terminal": True},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_DISCONNECT_CAUSE_EVENTS = {
|
|
79
|
+
"http.disconnect": "disconnect.peer_reset",
|
|
80
|
+
"websocket.close": "disconnect.peer_reset",
|
|
81
|
+
"websocket.disconnect": "disconnect.peer_reset",
|
|
82
|
+
"http2.rst_stream": "disconnect.peer_reset",
|
|
83
|
+
"http3.stream.reset": "disconnect.peer_reset",
|
|
84
|
+
"quic.stop_sending": "disconnect.peer_reset",
|
|
85
|
+
"quic.reset_stream": "disconnect.peer_reset",
|
|
86
|
+
"webtransport.close": "disconnect.peer_reset",
|
|
87
|
+
"webtransport.disconnect": "disconnect.peer_reset",
|
|
88
|
+
"transport.lost": "disconnect.transport_lost",
|
|
89
|
+
"tls.protocol_failure": "disconnect.protocol_error",
|
|
90
|
+
"protocol.error": "disconnect.protocol_error",
|
|
91
|
+
"timeout": "disconnect.timeout",
|
|
92
|
+
"server.shutdown": "disconnect.server_shutdown",
|
|
93
|
+
"worker.draining": "disconnect.server_shutdown",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_CANCELLATION_CAUSE_EVENTS = {
|
|
97
|
+
"client_disconnected": "cancellation.propagated",
|
|
98
|
+
"stream_reset": "cancellation.propagated",
|
|
99
|
+
"server_shutdown": "cancellation.propagated",
|
|
100
|
+
"timeout": "cancellation.propagated",
|
|
101
|
+
"worker_draining": "cancellation.propagated",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_CHANNEL_CAUSE_EVENTS = {
|
|
105
|
+
"open_requested": "channel.opening",
|
|
106
|
+
"opened": "channel.opened",
|
|
107
|
+
"h2.end_stream_received": "channel.read_closed",
|
|
108
|
+
"h2.end_stream_sent": "channel.write_closed",
|
|
109
|
+
"h2.half_closed_remote": "channel.read_closed",
|
|
110
|
+
"h2.half_closed_local": "channel.write_closed",
|
|
111
|
+
"h2.rst_stream": "channel.lost",
|
|
112
|
+
"h2.goaway": "channel.closing",
|
|
113
|
+
"h3.request_receive_closed": "channel.read_closed",
|
|
114
|
+
"h3.request_send_closed": "channel.write_closed",
|
|
115
|
+
"h3.request_cancelled": "channel.lost",
|
|
116
|
+
"h3.request_rejected": "channel.failed",
|
|
117
|
+
"h3.goaway": "channel.closing",
|
|
118
|
+
"h3.stream_error": "channel.failed",
|
|
119
|
+
"quic.receive_stream_closed": "channel.read_closed",
|
|
120
|
+
"quic.send_stream_closed": "channel.write_closed",
|
|
121
|
+
"quic.connection_close": "channel.closing",
|
|
122
|
+
"quic.draining": "channel.closing",
|
|
123
|
+
"quic.idle_timeout": "channel.lost",
|
|
124
|
+
"websocket.closing": "channel.closing",
|
|
125
|
+
"websocket.closed": "channel.closed",
|
|
126
|
+
"websocket.http2.tunnel_ended": "channel.closing",
|
|
127
|
+
"websocket.http3.tunnel_ended": "channel.closing",
|
|
128
|
+
"tcp.reset": "channel.lost",
|
|
129
|
+
"transport.lost": "channel.lost",
|
|
130
|
+
"protocol.error": "channel.failed",
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_DIRECTIONAL_RESET_EVENTS = {
|
|
134
|
+
"h3.reset_stream",
|
|
135
|
+
"quic.reset_stream",
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_DIRECTIONAL_STOP_EVENTS = {
|
|
139
|
+
"h3.stop_sending",
|
|
140
|
+
"quic.stop_sending",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass(frozen=True, slots=True)
|
|
145
|
+
class SemanticObservation:
|
|
146
|
+
domain: str
|
|
147
|
+
previous_state: str
|
|
148
|
+
event: str
|
|
149
|
+
state: str
|
|
150
|
+
source: str
|
|
151
|
+
detail: str | None = None
|
|
152
|
+
|
|
153
|
+
def as_dict(self) -> dict[str, str]:
|
|
154
|
+
payload = {
|
|
155
|
+
"domain": self.domain,
|
|
156
|
+
"event": self.event,
|
|
157
|
+
"previous_state": self.previous_state,
|
|
158
|
+
"source": self.source,
|
|
159
|
+
"state": self.state,
|
|
160
|
+
}
|
|
161
|
+
if self.detail:
|
|
162
|
+
payload["detail"] = self.detail
|
|
163
|
+
return payload
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def enforce_semantic_transition(
|
|
167
|
+
domain: str,
|
|
168
|
+
state: str,
|
|
169
|
+
event: str,
|
|
170
|
+
*,
|
|
171
|
+
source: str = "tigrcorn",
|
|
172
|
+
detail: str | None = None,
|
|
173
|
+
) -> SemanticObservation:
|
|
174
|
+
next_state = _semantic_transition_target(domain, state, event)
|
|
175
|
+
return SemanticObservation(
|
|
176
|
+
domain=domain,
|
|
177
|
+
previous_state=state,
|
|
178
|
+
event=event,
|
|
179
|
+
state=next_state,
|
|
180
|
+
source=source,
|
|
181
|
+
detail=detail,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def observe_completion(
|
|
186
|
+
state: str,
|
|
187
|
+
*,
|
|
188
|
+
queued: bool = False,
|
|
189
|
+
flushed: bool = False,
|
|
190
|
+
peer_acknowledged: bool = False,
|
|
191
|
+
failed: bool = False,
|
|
192
|
+
peer_aborted: bool = False,
|
|
193
|
+
) -> SemanticObservation:
|
|
194
|
+
event = _single_event(
|
|
195
|
+
"completion",
|
|
196
|
+
{
|
|
197
|
+
"completion.queued": queued,
|
|
198
|
+
"completion.flushed": flushed,
|
|
199
|
+
"completion.peer_acknowledged": peer_acknowledged,
|
|
200
|
+
"completion.failed": failed,
|
|
201
|
+
"completion.peer_aborted": peer_aborted,
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
return enforce_semantic_transition("completion", state, event)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def observe_backpressure(
|
|
208
|
+
state: str,
|
|
209
|
+
*,
|
|
210
|
+
queued_bytes: int,
|
|
211
|
+
high_watermark: int,
|
|
212
|
+
resume_watermark: int | None = None,
|
|
213
|
+
) -> SemanticObservation:
|
|
214
|
+
if queued_bytes < 0 or high_watermark < 0:
|
|
215
|
+
raise ProtocolError("backpressure counters must be non-negative")
|
|
216
|
+
if resume_watermark is not None and resume_watermark < 0:
|
|
217
|
+
raise ProtocolError("resume watermark must be non-negative")
|
|
218
|
+
if state == "writable" and queued_bytes >= high_watermark:
|
|
219
|
+
event = "backpressure.congested"
|
|
220
|
+
elif state == "congested" and queued_bytes >= high_watermark:
|
|
221
|
+
event = "backpressure.saturated"
|
|
222
|
+
elif state == "saturated":
|
|
223
|
+
event = "backpressure.draining"
|
|
224
|
+
elif state in {"congested", "draining"} and resume_watermark is not None and queued_bytes <= resume_watermark:
|
|
225
|
+
event = "backpressure.resumed"
|
|
226
|
+
elif state == "resumed":
|
|
227
|
+
event = "backpressure.writable"
|
|
228
|
+
else:
|
|
229
|
+
raise ProtocolError(f"no backpressure semantic transition for state {state!r}")
|
|
230
|
+
return enforce_semantic_transition("backpressure", state, event)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def observe_disconnect(state: str, cause: str) -> SemanticObservation:
|
|
234
|
+
try:
|
|
235
|
+
event = _DISCONNECT_CAUSE_EVENTS[cause]
|
|
236
|
+
except KeyError as exc:
|
|
237
|
+
raise ProtocolError(f"unsupported disconnect cause: {cause!r}") from exc
|
|
238
|
+
return enforce_semantic_transition("disconnect", state, event)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def observe_transport_cancellation(state: str, cause: str) -> SemanticObservation:
|
|
242
|
+
try:
|
|
243
|
+
event = _CANCELLATION_CAUSE_EVENTS[cause]
|
|
244
|
+
except KeyError as exc:
|
|
245
|
+
raise ProtocolError(f"unsupported transport cancellation cause: {cause!r}") from exc
|
|
246
|
+
return enforce_semantic_transition(
|
|
247
|
+
"cancellation",
|
|
248
|
+
state,
|
|
249
|
+
event,
|
|
250
|
+
detail=f"transport:{cause}",
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def observe_channel_lifecycle(
|
|
255
|
+
state: str,
|
|
256
|
+
cause: str,
|
|
257
|
+
*,
|
|
258
|
+
direction: str | None = None,
|
|
259
|
+
) -> SemanticObservation:
|
|
260
|
+
event = _channel_event_for_cause(cause, direction=direction)
|
|
261
|
+
return enforce_semantic_transition(
|
|
262
|
+
"channel_lifecycle",
|
|
263
|
+
state,
|
|
264
|
+
event,
|
|
265
|
+
detail=_channel_detail(cause, direction),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def channel_lifecycle_capabilities(state: str) -> dict[str, bool]:
|
|
270
|
+
effects = _channel_capability_effects()
|
|
271
|
+
try:
|
|
272
|
+
return dict(effects[state])
|
|
273
|
+
except KeyError as exc:
|
|
274
|
+
raise ProtocolError(f"unknown channel lifecycle state: {state!r}") from exc
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def require_channel_readable(state: str) -> None:
|
|
278
|
+
if not channel_lifecycle_capabilities(state)["can_read"]:
|
|
279
|
+
raise ProtocolError(f"channel is not readable in lifecycle state {state!r}")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def require_channel_writable(state: str) -> None:
|
|
283
|
+
if not channel_lifecycle_capabilities(state)["can_write"]:
|
|
284
|
+
raise ProtocolError(f"channel is not writable in lifecycle state {state!r}")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _single_event(domain: str, flags: Mapping[str, bool]) -> str:
|
|
288
|
+
events = [event for event, active in flags.items() if active]
|
|
289
|
+
if len(events) != 1:
|
|
290
|
+
raise ProtocolError(f"{domain} observation must select exactly one semantic event")
|
|
291
|
+
return events[0]
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _semantic_transition_target(domain: str, state: str, event: str) -> str:
|
|
295
|
+
if _contract_transition_target is not None and _contract_validate_transition is not None:
|
|
296
|
+
try:
|
|
297
|
+
if _contract_validate_transition(domain, state, event):
|
|
298
|
+
return str(_contract_transition_target(domain, state, event))
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
try:
|
|
302
|
+
return _FALLBACK_TRANSITIONS[(domain, state, event)]
|
|
303
|
+
except KeyError as exc:
|
|
304
|
+
raise ProtocolError(f"illegal semantic transition for {domain}: {state} + {event}") from exc
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _channel_event_for_cause(cause: str, *, direction: str | None) -> str:
|
|
308
|
+
if cause in _DIRECTIONAL_RESET_EVENTS or cause in _DIRECTIONAL_STOP_EVENTS:
|
|
309
|
+
if direction in {"inbound", "read", "remote"}:
|
|
310
|
+
return "channel.read_closed"
|
|
311
|
+
if direction in {"outbound", "write", "local"}:
|
|
312
|
+
return "channel.write_closed"
|
|
313
|
+
raise ProtocolError(f"{cause} requires inbound/read or outbound/write direction")
|
|
314
|
+
try:
|
|
315
|
+
return _CHANNEL_CAUSE_EVENTS[cause]
|
|
316
|
+
except KeyError as exc:
|
|
317
|
+
raise ProtocolError(f"unsupported channel lifecycle cause: {cause!r}") from exc
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _channel_detail(cause: str, direction: str | None) -> str:
|
|
321
|
+
if direction is None:
|
|
322
|
+
return f"transport:{cause}"
|
|
323
|
+
return f"transport:{cause}:{direction}"
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _channel_capability_effects() -> Mapping[str, Mapping[str, bool]]:
|
|
327
|
+
if _contract_semantic_domain is not None:
|
|
328
|
+
try:
|
|
329
|
+
effects = _contract_semantic_domain("channel_lifecycle").get("capability_effects", {})
|
|
330
|
+
if effects:
|
|
331
|
+
return effects
|
|
332
|
+
except Exception:
|
|
333
|
+
pass
|
|
334
|
+
return _FALLBACK_CHANNEL_CAPABILITIES
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
__all__ = [
|
|
338
|
+
"SemanticObservation",
|
|
339
|
+
"channel_lifecycle_capabilities",
|
|
340
|
+
"enforce_semantic_transition",
|
|
341
|
+
"observe_backpressure",
|
|
342
|
+
"observe_channel_lifecycle",
|
|
343
|
+
"observe_completion",
|
|
344
|
+
"observe_disconnect",
|
|
345
|
+
"observe_transport_cancellation",
|
|
346
|
+
"require_channel_readable",
|
|
347
|
+
"require_channel_writable",
|
|
348
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrcorn-contract
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.17.dev1
|
|
4
4
|
Summary: tigr-asgi-contract adapters and runtime-boundary classification for Tigrcorn ASGI3, WebSocket, and WebTransport surfaces.
|
|
5
5
|
Author-email: Jacob Stewart <jacob@swarmauri.com>
|
|
6
6
|
License: Apache License
|
|
@@ -186,8 +186,8 @@ Classifier: Typing :: Typed
|
|
|
186
186
|
Requires-Python: <3.15,>=3.10
|
|
187
187
|
Description-Content-Type: text/markdown
|
|
188
188
|
License-File: LICENSE
|
|
189
|
-
Requires-Dist: tigrcorn-core==0.3.
|
|
190
|
-
Requires-Dist: tigrcorn-asgi==0.3.
|
|
189
|
+
Requires-Dist: tigrcorn-core==0.3.17.dev1
|
|
190
|
+
Requires-Dist: tigrcorn-asgi==0.3.17.dev1
|
|
191
191
|
Requires-Dist: tigr-asgi-contract>=0.3.2
|
|
192
192
|
Dynamic: license-file
|
|
193
193
|
|
|
@@ -8,7 +8,9 @@ src/tigrcorn_contract/events.py
|
|
|
8
8
|
src/tigrcorn_contract/metadata.py
|
|
9
9
|
src/tigrcorn_contract/projection.py
|
|
10
10
|
src/tigrcorn_contract/py.typed
|
|
11
|
+
src/tigrcorn_contract/registry.py
|
|
11
12
|
src/tigrcorn_contract/scopes.py
|
|
13
|
+
src/tigrcorn_contract/semantics.py
|
|
12
14
|
src/tigrcorn_contract.egg-info/PKG-INFO
|
|
13
15
|
src/tigrcorn_contract.egg-info/SOURCES.txt
|
|
14
16
|
src/tigrcorn_contract.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/compat.py
RENAMED
|
File without changes
|
{tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/metadata.py
RENAMED
|
File without changes
|
{tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/projection.py
RENAMED
|
File without changes
|
{tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/py.typed
RENAMED
|
File without changes
|
{tigrcorn_contract-0.3.16.dev12 → tigrcorn_contract-0.3.17.dev1}/src/tigrcorn_contract/scopes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|