span-contract 1.0.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Margaret Nanyonga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: span-contract
3
+ Version: 1.0.1
4
+ Summary: An admission contract for AI training jobs that cross a data hall boundary
5
+ Author: Margaret Nanyonga
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/dimaggi-ai/span-contract
8
+ Project-URL: Research, https://dimaggi-ai.github.io/research
9
+ Keywords: distributed training,admission control,data center interconnect,optical circuit switching,capacity planning
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Classifier: Topic :: System :: Distributed Computing
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest==8.3.4; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # The span contract: deciding whether an AI training job may cross a data hall
24
+
25
+ **When a job asks to run across more than one hall, who decides, and on what facts?**
26
+
27
+ Today, three systems decide, and none of them talks to the others. The scheduler
28
+ knows how many accelerators are free. The compiler knows what plan it emitted and
29
+ what topology it assumed. The circuit controller knows what glass is lit. Each
30
+ holds a different half-truth, and the job discovers which one was wrong at step
31
+ time — or worse, at checkpoint time, three hours in.
32
+
33
+ This repository is one object that makes them agree before the job starts:
34
+ twenty-one fields, six decisions, three conditions under which the contract
35
+ refuses rather than guesses, and a reference validator that turns the first into
36
+ the second and prints its reasons. It is a research artifact, not a product.
37
+
38
+ ---
39
+
40
+ ## What it is not
41
+
42
+ It is not a fabric, a scheduler, or a network model. It decides **admission** —
43
+ may this job cross — and holds no opinion on **capacity** — what the job would
44
+ retain if it did. That second question is answered by the latency-regime atlas in
45
+ [network-vs-more-gpus](https://github.com/dimaggi-ai/network-vs-more-gpus), and
46
+ this repository deliberately does not offer a second answer to it. The two are
47
+ designed to be read together: the atlas says a tensor-parallel cut retains 0.004
48
+ across any stitch at any distance, and the contract is what refuses to launch it.
49
+
50
+ ## The shape of it
51
+
52
+ ```python
53
+ from spancontract import SpanEnvelope, validate
54
+
55
+ verdict = validate(envelope)
56
+ verdict.decision # Decision.DENY
57
+ verdict.reasons() # ['[FC2] path measurement is 7200s old, past the 300s TTL']
58
+ verdict.not_checked # what the validator could not check, printed rather than omitted
59
+ ```
60
+
61
+ ```
62
+ $ spancontract validate examples/long-haul-training-too-far.json
63
+ decision: DENY
64
+ regime: region (2958 us RTT)
65
+
66
+ findings:
67
+ - [LR1] synchronous train at 2958 us RTT (region) exceeds the 2000 us limit;
68
+ the default policy is that training stays hall-local
69
+
70
+ not checked:
71
+ ? XP1 declared-vs-computed blast radius: no plant graph supplied, so the
72
+ envelope's blast_radius was taken on trust
73
+ ? XP2 topology currency: no live topology hash supplied, so the envelope's
74
+ topology_hash was taken on trust
75
+ ```
76
+
77
+ ## The three conditions that fail closed
78
+
79
+ Each describes a state in which the contract *does not know* something it needs,
80
+ and in each case not knowing is a refusal. A validator that read silence as
81
+ health would be worse than no validator, because it would be trusted.
82
+
83
+ | | Condition | Why it is a refusal and not a warning |
84
+ |---|---|---|
85
+ | **FC1** | The circuit API is dark | The state of the stitch is unknown. Unknown is not healthy. |
86
+ | **FC2** | The measured path is stale past its TTL | A declared topology is a claim. Only a fresh measurement is an observation. |
87
+ | **FC3** | The compile cache was keyed on a topology that no longer exists | The binary was built for a placement that is gone. |
88
+
89
+ ## The six decisions
90
+
91
+ `local` · `span` · `shrink` · `move` · `escalate` · `deny`, on a severity ladder.
92
+ Rules join with a maximum, so a rule can only push a verdict toward refusal and
93
+ never away from it — which is what makes the order the rules run in irrelevant to
94
+ the answer. The registry proves that by shuffling the rule list over 400 random
95
+ envelopes and requiring every verdict to be identical.
96
+
97
+ ## What the validator refuses to pretend
98
+
99
+ `make validate` prints the registry, and then prints the list of what it
100
+ **declined** to check. That second list is the more useful one. It says, among
101
+ other things, that no published figure fixes any threshold in this repository,
102
+ that no verdict here has been checked against a job that actually ran, and that
103
+ the emulated circuit models amplifier gain without the optical signal-to-noise
104
+ cost that comes with it, so it can demonstrate a refusal but cannot support a
105
+ claim of health.
106
+
107
+ There is exactly **one calibrated point** in the registry. That is the honest
108
+ count, not a gap. See [`docs/the-contract.md`](docs/the-contract.md).
109
+
110
+ ## Install and run
111
+
112
+ ```bash
113
+ git clone https://github.com/dimaggi-ai/span-contract
114
+ cd span-contract
115
+ make venv
116
+ make smoke-test # tests, mutation tests, registry, examples — under a minute
117
+ ```
118
+
119
+ Or from PyPI:
120
+
121
+ ```bash
122
+ pip install span-contract
123
+ spancontract example > envelope.json
124
+ spancontract validate envelope.json
125
+ ```
126
+
127
+ | Target | What it does |
128
+ |---|---|
129
+ | `make test` | 91 tests, including 12 mutation tests that delete machinery and require the registry to go red |
130
+ | `make validate` | the validation registry, and the nine things it declines to check |
131
+ | `make examples` | eleven example envelopes, each checked against its documented verdict |
132
+ | `make schema` | regenerate `schema/span_contract.schema.json` from the code |
133
+
134
+ ## Repository map
135
+
136
+ | Path | What is in it |
137
+ |---|---|
138
+ | `src/spancontract/envelope.py` | the twenty-one fields, inert by design |
139
+ | `src/spancontract/rules.py` | every rule and every threshold, none of them inline |
140
+ | `src/spancontract/validator.py` | the join, the plant cross-checks, the hash-chained record |
141
+ | `src/spancontract/plant.py` | halls, circuits, and what one failure actually costs |
142
+ | `src/spancontract/compile_cache.py` | keyed as specified; refuses entries from a dead topology |
143
+ | `src/spancontract/adapters/delay_node.py` | an emulated circuit, and the `tc` line that reproduces it |
144
+ | `validation/validate_contract.py` | the registry: one calibrated, seven emergent, ten sanity, nine declined |
145
+ | `tests/test_mutations.py` | delete a piece, name the points that must go red |
146
+ | `docs/the-contract.md` | the specification as implemented, including where it contradicts itself |
147
+ | `docs/integration.md` | the surface an existing policy engine would bind to |
148
+
149
+ ## The discrepancy that was carried rather than fixed
150
+
151
+ The specification lists six decisions, and then enumerates only five of them in
152
+ the `span_mode` field: `escalate` is missing. Both are implemented exactly as
153
+ written — a verdict can be `escalate`, and `span_mode` cannot — and a registry
154
+ point asserts the gap is still there, so that quietly closing it in a future
155
+ commit shows up as a failing check rather than a tidy-up. `DECISIONS.md` D3 has
156
+ the reasoning.
157
+
158
+ ## Series
159
+
160
+ Part of a program on the usable capacity of large accelerator fleets:
161
+ [dimaggi-ai.github.io/research](https://dimaggi-ai.github.io/research).
162
+
163
+ ## License
164
+
165
+ MIT. Copyright (c) 2026 Margaret Nanyonga.
@@ -0,0 +1,143 @@
1
+ # The span contract: deciding whether an AI training job may cross a data hall
2
+
3
+ **When a job asks to run across more than one hall, who decides, and on what facts?**
4
+
5
+ Today, three systems decide, and none of them talks to the others. The scheduler
6
+ knows how many accelerators are free. The compiler knows what plan it emitted and
7
+ what topology it assumed. The circuit controller knows what glass is lit. Each
8
+ holds a different half-truth, and the job discovers which one was wrong at step
9
+ time — or worse, at checkpoint time, three hours in.
10
+
11
+ This repository is one object that makes them agree before the job starts:
12
+ twenty-one fields, six decisions, three conditions under which the contract
13
+ refuses rather than guesses, and a reference validator that turns the first into
14
+ the second and prints its reasons. It is a research artifact, not a product.
15
+
16
+ ---
17
+
18
+ ## What it is not
19
+
20
+ It is not a fabric, a scheduler, or a network model. It decides **admission** —
21
+ may this job cross — and holds no opinion on **capacity** — what the job would
22
+ retain if it did. That second question is answered by the latency-regime atlas in
23
+ [network-vs-more-gpus](https://github.com/dimaggi-ai/network-vs-more-gpus), and
24
+ this repository deliberately does not offer a second answer to it. The two are
25
+ designed to be read together: the atlas says a tensor-parallel cut retains 0.004
26
+ across any stitch at any distance, and the contract is what refuses to launch it.
27
+
28
+ ## The shape of it
29
+
30
+ ```python
31
+ from spancontract import SpanEnvelope, validate
32
+
33
+ verdict = validate(envelope)
34
+ verdict.decision # Decision.DENY
35
+ verdict.reasons() # ['[FC2] path measurement is 7200s old, past the 300s TTL']
36
+ verdict.not_checked # what the validator could not check, printed rather than omitted
37
+ ```
38
+
39
+ ```
40
+ $ spancontract validate examples/long-haul-training-too-far.json
41
+ decision: DENY
42
+ regime: region (2958 us RTT)
43
+
44
+ findings:
45
+ - [LR1] synchronous train at 2958 us RTT (region) exceeds the 2000 us limit;
46
+ the default policy is that training stays hall-local
47
+
48
+ not checked:
49
+ ? XP1 declared-vs-computed blast radius: no plant graph supplied, so the
50
+ envelope's blast_radius was taken on trust
51
+ ? XP2 topology currency: no live topology hash supplied, so the envelope's
52
+ topology_hash was taken on trust
53
+ ```
54
+
55
+ ## The three conditions that fail closed
56
+
57
+ Each describes a state in which the contract *does not know* something it needs,
58
+ and in each case not knowing is a refusal. A validator that read silence as
59
+ health would be worse than no validator, because it would be trusted.
60
+
61
+ | | Condition | Why it is a refusal and not a warning |
62
+ |---|---|---|
63
+ | **FC1** | The circuit API is dark | The state of the stitch is unknown. Unknown is not healthy. |
64
+ | **FC2** | The measured path is stale past its TTL | A declared topology is a claim. Only a fresh measurement is an observation. |
65
+ | **FC3** | The compile cache was keyed on a topology that no longer exists | The binary was built for a placement that is gone. |
66
+
67
+ ## The six decisions
68
+
69
+ `local` · `span` · `shrink` · `move` · `escalate` · `deny`, on a severity ladder.
70
+ Rules join with a maximum, so a rule can only push a verdict toward refusal and
71
+ never away from it — which is what makes the order the rules run in irrelevant to
72
+ the answer. The registry proves that by shuffling the rule list over 400 random
73
+ envelopes and requiring every verdict to be identical.
74
+
75
+ ## What the validator refuses to pretend
76
+
77
+ `make validate` prints the registry, and then prints the list of what it
78
+ **declined** to check. That second list is the more useful one. It says, among
79
+ other things, that no published figure fixes any threshold in this repository,
80
+ that no verdict here has been checked against a job that actually ran, and that
81
+ the emulated circuit models amplifier gain without the optical signal-to-noise
82
+ cost that comes with it, so it can demonstrate a refusal but cannot support a
83
+ claim of health.
84
+
85
+ There is exactly **one calibrated point** in the registry. That is the honest
86
+ count, not a gap. See [`docs/the-contract.md`](docs/the-contract.md).
87
+
88
+ ## Install and run
89
+
90
+ ```bash
91
+ git clone https://github.com/dimaggi-ai/span-contract
92
+ cd span-contract
93
+ make venv
94
+ make smoke-test # tests, mutation tests, registry, examples — under a minute
95
+ ```
96
+
97
+ Or from PyPI:
98
+
99
+ ```bash
100
+ pip install span-contract
101
+ spancontract example > envelope.json
102
+ spancontract validate envelope.json
103
+ ```
104
+
105
+ | Target | What it does |
106
+ |---|---|
107
+ | `make test` | 91 tests, including 12 mutation tests that delete machinery and require the registry to go red |
108
+ | `make validate` | the validation registry, and the nine things it declines to check |
109
+ | `make examples` | eleven example envelopes, each checked against its documented verdict |
110
+ | `make schema` | regenerate `schema/span_contract.schema.json` from the code |
111
+
112
+ ## Repository map
113
+
114
+ | Path | What is in it |
115
+ |---|---|
116
+ | `src/spancontract/envelope.py` | the twenty-one fields, inert by design |
117
+ | `src/spancontract/rules.py` | every rule and every threshold, none of them inline |
118
+ | `src/spancontract/validator.py` | the join, the plant cross-checks, the hash-chained record |
119
+ | `src/spancontract/plant.py` | halls, circuits, and what one failure actually costs |
120
+ | `src/spancontract/compile_cache.py` | keyed as specified; refuses entries from a dead topology |
121
+ | `src/spancontract/adapters/delay_node.py` | an emulated circuit, and the `tc` line that reproduces it |
122
+ | `validation/validate_contract.py` | the registry: one calibrated, seven emergent, ten sanity, nine declined |
123
+ | `tests/test_mutations.py` | delete a piece, name the points that must go red |
124
+ | `docs/the-contract.md` | the specification as implemented, including where it contradicts itself |
125
+ | `docs/integration.md` | the surface an existing policy engine would bind to |
126
+
127
+ ## The discrepancy that was carried rather than fixed
128
+
129
+ The specification lists six decisions, and then enumerates only five of them in
130
+ the `span_mode` field: `escalate` is missing. Both are implemented exactly as
131
+ written — a verdict can be `escalate`, and `span_mode` cannot — and a registry
132
+ point asserts the gap is still there, so that quietly closing it in a future
133
+ commit shows up as a failing check rather than a tidy-up. `DECISIONS.md` D3 has
134
+ the reasoning.
135
+
136
+ ## Series
137
+
138
+ Part of a program on the usable capacity of large accelerator fleets:
139
+ [dimaggi-ai.github.io/research](https://dimaggi-ai.github.io/research).
140
+
141
+ ## License
142
+
143
+ MIT. Copyright (c) 2026 Margaret Nanyonga.
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "span-contract"
7
+ version = "1.0.1"
8
+ description = "An admission contract for AI training jobs that cross a data hall boundary"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Margaret Nanyonga" }]
13
+ keywords = [
14
+ "distributed training",
15
+ "admission control",
16
+ "data center interconnect",
17
+ "optical circuit switching",
18
+ "capacity planning",
19
+ ]
20
+ classifiers = [
21
+ "Intended Audience :: Science/Research",
22
+ "Intended Audience :: System Administrators",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Scientific/Engineering",
26
+ "Topic :: System :: Distributed Computing",
27
+ ]
28
+ dependencies = []
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest==8.3.4"]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/dimaggi-ai/span-contract"
35
+ Research = "https://dimaggi-ai.github.io/research"
36
+
37
+ [project.scripts]
38
+ spancontract = "spancontract.cli:main"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests"]
45
+ pythonpath = ["src", "validation"]
46
+ addopts = "-q"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: span-contract
3
+ Version: 1.0.1
4
+ Summary: An admission contract for AI training jobs that cross a data hall boundary
5
+ Author: Margaret Nanyonga
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/dimaggi-ai/span-contract
8
+ Project-URL: Research, https://dimaggi-ai.github.io/research
9
+ Keywords: distributed training,admission control,data center interconnect,optical circuit switching,capacity planning
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Classifier: Topic :: System :: Distributed Computing
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest==8.3.4; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # The span contract: deciding whether an AI training job may cross a data hall
24
+
25
+ **When a job asks to run across more than one hall, who decides, and on what facts?**
26
+
27
+ Today, three systems decide, and none of them talks to the others. The scheduler
28
+ knows how many accelerators are free. The compiler knows what plan it emitted and
29
+ what topology it assumed. The circuit controller knows what glass is lit. Each
30
+ holds a different half-truth, and the job discovers which one was wrong at step
31
+ time — or worse, at checkpoint time, three hours in.
32
+
33
+ This repository is one object that makes them agree before the job starts:
34
+ twenty-one fields, six decisions, three conditions under which the contract
35
+ refuses rather than guesses, and a reference validator that turns the first into
36
+ the second and prints its reasons. It is a research artifact, not a product.
37
+
38
+ ---
39
+
40
+ ## What it is not
41
+
42
+ It is not a fabric, a scheduler, or a network model. It decides **admission** —
43
+ may this job cross — and holds no opinion on **capacity** — what the job would
44
+ retain if it did. That second question is answered by the latency-regime atlas in
45
+ [network-vs-more-gpus](https://github.com/dimaggi-ai/network-vs-more-gpus), and
46
+ this repository deliberately does not offer a second answer to it. The two are
47
+ designed to be read together: the atlas says a tensor-parallel cut retains 0.004
48
+ across any stitch at any distance, and the contract is what refuses to launch it.
49
+
50
+ ## The shape of it
51
+
52
+ ```python
53
+ from spancontract import SpanEnvelope, validate
54
+
55
+ verdict = validate(envelope)
56
+ verdict.decision # Decision.DENY
57
+ verdict.reasons() # ['[FC2] path measurement is 7200s old, past the 300s TTL']
58
+ verdict.not_checked # what the validator could not check, printed rather than omitted
59
+ ```
60
+
61
+ ```
62
+ $ spancontract validate examples/long-haul-training-too-far.json
63
+ decision: DENY
64
+ regime: region (2958 us RTT)
65
+
66
+ findings:
67
+ - [LR1] synchronous train at 2958 us RTT (region) exceeds the 2000 us limit;
68
+ the default policy is that training stays hall-local
69
+
70
+ not checked:
71
+ ? XP1 declared-vs-computed blast radius: no plant graph supplied, so the
72
+ envelope's blast_radius was taken on trust
73
+ ? XP2 topology currency: no live topology hash supplied, so the envelope's
74
+ topology_hash was taken on trust
75
+ ```
76
+
77
+ ## The three conditions that fail closed
78
+
79
+ Each describes a state in which the contract *does not know* something it needs,
80
+ and in each case not knowing is a refusal. A validator that read silence as
81
+ health would be worse than no validator, because it would be trusted.
82
+
83
+ | | Condition | Why it is a refusal and not a warning |
84
+ |---|---|---|
85
+ | **FC1** | The circuit API is dark | The state of the stitch is unknown. Unknown is not healthy. |
86
+ | **FC2** | The measured path is stale past its TTL | A declared topology is a claim. Only a fresh measurement is an observation. |
87
+ | **FC3** | The compile cache was keyed on a topology that no longer exists | The binary was built for a placement that is gone. |
88
+
89
+ ## The six decisions
90
+
91
+ `local` · `span` · `shrink` · `move` · `escalate` · `deny`, on a severity ladder.
92
+ Rules join with a maximum, so a rule can only push a verdict toward refusal and
93
+ never away from it — which is what makes the order the rules run in irrelevant to
94
+ the answer. The registry proves that by shuffling the rule list over 400 random
95
+ envelopes and requiring every verdict to be identical.
96
+
97
+ ## What the validator refuses to pretend
98
+
99
+ `make validate` prints the registry, and then prints the list of what it
100
+ **declined** to check. That second list is the more useful one. It says, among
101
+ other things, that no published figure fixes any threshold in this repository,
102
+ that no verdict here has been checked against a job that actually ran, and that
103
+ the emulated circuit models amplifier gain without the optical signal-to-noise
104
+ cost that comes with it, so it can demonstrate a refusal but cannot support a
105
+ claim of health.
106
+
107
+ There is exactly **one calibrated point** in the registry. That is the honest
108
+ count, not a gap. See [`docs/the-contract.md`](docs/the-contract.md).
109
+
110
+ ## Install and run
111
+
112
+ ```bash
113
+ git clone https://github.com/dimaggi-ai/span-contract
114
+ cd span-contract
115
+ make venv
116
+ make smoke-test # tests, mutation tests, registry, examples — under a minute
117
+ ```
118
+
119
+ Or from PyPI:
120
+
121
+ ```bash
122
+ pip install span-contract
123
+ spancontract example > envelope.json
124
+ spancontract validate envelope.json
125
+ ```
126
+
127
+ | Target | What it does |
128
+ |---|---|
129
+ | `make test` | 91 tests, including 12 mutation tests that delete machinery and require the registry to go red |
130
+ | `make validate` | the validation registry, and the nine things it declines to check |
131
+ | `make examples` | eleven example envelopes, each checked against its documented verdict |
132
+ | `make schema` | regenerate `schema/span_contract.schema.json` from the code |
133
+
134
+ ## Repository map
135
+
136
+ | Path | What is in it |
137
+ |---|---|
138
+ | `src/spancontract/envelope.py` | the twenty-one fields, inert by design |
139
+ | `src/spancontract/rules.py` | every rule and every threshold, none of them inline |
140
+ | `src/spancontract/validator.py` | the join, the plant cross-checks, the hash-chained record |
141
+ | `src/spancontract/plant.py` | halls, circuits, and what one failure actually costs |
142
+ | `src/spancontract/compile_cache.py` | keyed as specified; refuses entries from a dead topology |
143
+ | `src/spancontract/adapters/delay_node.py` | an emulated circuit, and the `tc` line that reproduces it |
144
+ | `validation/validate_contract.py` | the registry: one calibrated, seven emergent, ten sanity, nine declined |
145
+ | `tests/test_mutations.py` | delete a piece, name the points that must go red |
146
+ | `docs/the-contract.md` | the specification as implemented, including where it contradicts itself |
147
+ | `docs/integration.md` | the surface an existing policy engine would bind to |
148
+
149
+ ## The discrepancy that was carried rather than fixed
150
+
151
+ The specification lists six decisions, and then enumerates only five of them in
152
+ the `span_mode` field: `escalate` is missing. Both are implemented exactly as
153
+ written — a verdict can be `escalate`, and `span_mode` cannot — and a registry
154
+ point asserts the gap is still there, so that quietly closing it in a future
155
+ commit shows up as a failing check rather than a tidy-up. `DECISIONS.md` D3 has
156
+ the reasoning.
157
+
158
+ ## Series
159
+
160
+ Part of a program on the usable capacity of large accelerator fleets:
161
+ [dimaggi-ai.github.io/research](https://dimaggi-ai.github.io/research).
162
+
163
+ ## License
164
+
165
+ MIT. Copyright (c) 2026 Margaret Nanyonga.
@@ -0,0 +1,22 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/span_contract.egg-info/PKG-INFO
5
+ src/span_contract.egg-info/SOURCES.txt
6
+ src/span_contract.egg-info/dependency_links.txt
7
+ src/span_contract.egg-info/entry_points.txt
8
+ src/span_contract.egg-info/requires.txt
9
+ src/span_contract.egg-info/top_level.txt
10
+ src/spancontract/__init__.py
11
+ src/spancontract/cli.py
12
+ src/spancontract/compile_cache.py
13
+ src/spancontract/decisions.py
14
+ src/spancontract/envelope.py
15
+ src/spancontract/plant.py
16
+ src/spancontract/rules.py
17
+ src/spancontract/schema.py
18
+ src/spancontract/validator.py
19
+ src/spancontract/adapters/__init__.py
20
+ src/spancontract/adapters/delay_node.py
21
+ tests/test_contract.py
22
+ tests/test_mutations.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ spancontract = spancontract.cli:main
@@ -0,0 +1,3 @@
1
+
2
+ [dev]
3
+ pytest==8.3.4
@@ -0,0 +1 @@
1
+ spancontract
@@ -0,0 +1,64 @@
1
+ """span-contract: one object a job carries when it wants to cross a hall.
2
+
3
+ A scale-across decision is made today by three systems that do not talk: a
4
+ scheduler that knows how many accelerators are free, a compiler that knows what
5
+ plan it emitted, and a circuit controller that knows what glass is lit. Each
6
+ holds a different half-truth about the topology, and the job finds out which
7
+ one was wrong at step time.
8
+
9
+ This package is the object that makes them agree before the job starts: a
10
+ twenty-one field envelope, six decisions, three conditions that fail closed,
11
+ and a reference validator that turns the first into the second and prints the
12
+ reasons. It decides admission. It does not decide capacity --- what a job
13
+ retains across a given cut is answered by the latency-regime atlas in the
14
+ ``network-vs-more-gpus`` repository, and this package deliberately holds no
15
+ second opinion on it.
16
+
17
+ >>> from spancontract import SpanEnvelope, validate
18
+ >>> verdict = validate(envelope) # doctest: +SKIP
19
+ >>> verdict.decision, verdict.reasons() # doctest: +SKIP
20
+ """
21
+
22
+ from .plant import SpanGraph, Stitch, blast_radius, load_bearing_stitches
23
+ from .compile_cache import CompileCache, compile_cache_key
24
+ from .decisions import SPAN_MODES, Decision, ScaleOut
25
+ from .envelope import (
26
+ AUTONOMY_LEVELS,
27
+ REGIME_BOUNDS,
28
+ SPEC_FIELDS,
29
+ SliceRect,
30
+ SpanEnvelope,
31
+ latency_regime,
32
+ )
33
+ from .rules import FAIL_CLOSED_RULE_IDS, RULES, Finding, Policy
34
+ from .validator import Plant, Verdict, audit_record, validate, verify_chain
35
+
36
+ __version__ = "1.0.1"
37
+
38
+ __all__ = [
39
+ "AUTONOMY_LEVELS",
40
+ "CompileCache",
41
+ "Decision",
42
+ "FAIL_CLOSED_RULE_IDS",
43
+ "Finding",
44
+ "Plant",
45
+ "Policy",
46
+ "REGIME_BOUNDS",
47
+ "RULES",
48
+ "SPAN_MODES",
49
+ "SPEC_FIELDS",
50
+ "ScaleOut",
51
+ "SliceRect",
52
+ "SpanEnvelope",
53
+ "SpanGraph",
54
+ "Stitch",
55
+ "Verdict",
56
+ "audit_record",
57
+ "blast_radius",
58
+ "compile_cache_key",
59
+ "latency_regime",
60
+ "load_bearing_stitches",
61
+ "validate",
62
+ "verify_chain",
63
+ "__version__",
64
+ ]
@@ -0,0 +1,29 @@
1
+ """Adapters between the contract and something that can answer for a circuit.
2
+
3
+ The contract needs one thing from the outside world: a measurement of the path,
4
+ with an age and an honest "I could not reach the controller". That is the whole
5
+ :class:`StitchController` surface. Everything else in this repository works off
6
+ the envelope.
7
+
8
+ Two adapters ship here. :mod:`spancontract.adapters.delay_node` is an emulated
9
+ circuit, which is what section 9's seventh sequence item asks for and what a
10
+ reader can run today. A real controller --- a lab OCS, a campus ROADM --- binds
11
+ to the same protocol; this repository does not ship one, because writing a
12
+ driver against a plant nobody has run it on would be fiction.
13
+ """
14
+
15
+ from .delay_node import (
16
+ DelayNode,
17
+ EmulatedController,
18
+ PathMeasurement,
19
+ StitchController,
20
+ apply_measurement,
21
+ )
22
+
23
+ __all__ = [
24
+ "DelayNode",
25
+ "EmulatedController",
26
+ "PathMeasurement",
27
+ "StitchController",
28
+ "apply_measurement",
29
+ ]