experiment-core 0.1.0__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.
- experiment_core-0.1.0/.secrets.baseline +123 -0
- experiment_core-0.1.0/LICENSE +21 -0
- experiment_core-0.1.0/MANIFEST.in +7 -0
- experiment_core-0.1.0/PKG-INFO +45 -0
- experiment_core-0.1.0/README.md +21 -0
- experiment_core-0.1.0/docs/publishing.html +33 -0
- experiment_core-0.1.0/examples/capabilities.json +51 -0
- experiment_core-0.1.0/examples/empty-quality.json +25 -0
- experiment_core-0.1.0/examples/fixture-decision.json +23 -0
- experiment_core-0.1.0/examples/fixture-measurement.json +32 -0
- experiment_core-0.1.0/examples/operation-request.json +23 -0
- experiment_core-0.1.0/examples/policy-deferred.json +17 -0
- experiment_core-0.1.0/examples/policy-proposal.json +19 -0
- experiment_core-0.1.0/pyproject.toml +40 -0
- experiment_core-0.1.0/scripts/check_dist.py +28 -0
- experiment_core-0.1.0/scripts/check_secrets.py +19 -0
- experiment_core-0.1.0/scripts/check_tag.py +12 -0
- experiment_core-0.1.0/scripts/install_wheel.py +11 -0
- experiment_core-0.1.0/setup.cfg +4 -0
- experiment_core-0.1.0/src/experiment_core/__init__.py +8 -0
- experiment_core-0.1.0/src/experiment_core/adjustment_guidance.py +49 -0
- experiment_core-0.1.0/src/experiment_core/agent_contracts.py +200 -0
- experiment_core-0.1.0/src/experiment_core/agent_roles.py +135 -0
- experiment_core-0.1.0/src/experiment_core/background.py +98 -0
- experiment_core-0.1.0/src/experiment_core/configuration.py +250 -0
- experiment_core-0.1.0/src/experiment_core/errors.py +47 -0
- experiment_core-0.1.0/src/experiment_core/execution.py +307 -0
- experiment_core-0.1.0/src/experiment_core/interfaces.py +54 -0
- experiment_core-0.1.0/src/experiment_core/invocation.py +30 -0
- experiment_core-0.1.0/src/experiment_core/measurement.py +25 -0
- experiment_core-0.1.0/src/experiment_core/operating_mode.py +123 -0
- experiment_core-0.1.0/src/experiment_core/ownership.py +192 -0
- experiment_core-0.1.0/src/experiment_core/records.py +245 -0
- experiment_core-0.1.0/src/experiment_core/schemas/protocol.json +984 -0
- experiment_core-0.1.0/src/experiment_core/testing.py +45 -0
- experiment_core-0.1.0/src/experiment_core/validation.py +75 -0
- experiment_core-0.1.0/src/experiment_core.egg-info/PKG-INFO +45 -0
- experiment_core-0.1.0/src/experiment_core.egg-info/SOURCES.txt +45 -0
- experiment_core-0.1.0/src/experiment_core.egg-info/dependency_links.txt +1 -0
- experiment_core-0.1.0/src/experiment_core.egg-info/requires.txt +1 -0
- experiment_core-0.1.0/src/experiment_core.egg-info/top_level.txt +1 -0
- experiment_core-0.1.0/tests/test_agent_contracts.py +60 -0
- experiment_core-0.1.0/tests/test_agent_roles.py +65 -0
- experiment_core-0.1.0/tests/test_background.py +73 -0
- experiment_core-0.1.0/tests/test_contracts.py +178 -0
- experiment_core-0.1.0/tests/test_operating_mode.py +109 -0
- experiment_core-0.1.0/tests/test_runtime.py +753 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"filters_used": [
|
|
3
|
+
{
|
|
4
|
+
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "detect_secrets.filters.heuristic.is_lock_file"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "detect_secrets.filters.heuristic.is_sequential_string"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "detect_secrets.filters.heuristic.is_swagger_file"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "detect_secrets.filters.heuristic.is_templated_secret"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"generated_at": "2026-10-30T16:34:54Z",
|
|
35
|
+
"plugins_used": [
|
|
36
|
+
{
|
|
37
|
+
"name": "ArtifactoryDetector"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "AWSKeyDetector"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "AzureStorageKeyDetector"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"limit": 4.5,
|
|
47
|
+
"name": "Base64HighEntropyString"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "BasicAuthDetector"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "CloudantDetector"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "DiscordBotTokenDetector"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "GitHubTokenDetector"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "GitLabTokenDetector"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"limit": 3.0,
|
|
66
|
+
"name": "HexHighEntropyString"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "IbmCloudIamDetector"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "IbmCosHmacDetector"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "IPPublicDetector"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "JwtTokenDetector"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"keyword_exclude": "",
|
|
82
|
+
"name": "KeywordDetector"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "MailchimpDetector"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "NpmDetector"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "OpenAIDetector"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "PrivateKeyDetector"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "PypiTokenDetector"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "SendGridDetector"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "SlackDetector"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "SoftlayerDetector"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "SquareOAuthDetector"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "StripeDetector"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "TelegramBotTokenDetector"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "TwilioKeyDetector"
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"results": {},
|
|
122
|
+
"version": "1.5.0"
|
|
123
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FanHarvard contributors
|
|
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,7 @@
|
|
|
1
|
+
include LICENSE README.md
|
|
2
|
+
recursive-include docs *.html
|
|
3
|
+
recursive-include scripts *.py
|
|
4
|
+
recursive-include tests *.py *.cjs *.json *.yaml
|
|
5
|
+
recursive-include examples *.json
|
|
6
|
+
global-exclude __pycache__ *.py[cod] .env .pypirc *.pem *.key
|
|
7
|
+
include .secrets.baseline
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: experiment-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Domain-neutral contracts and local runtime for supervised experimental operations
|
|
5
|
+
Author: FanHarvard contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/FanHarvard/experiment-core
|
|
8
|
+
Project-URL: Repository, https://github.com/FanHarvard/experiment-core
|
|
9
|
+
Project-URL: Issues, https://github.com/FanHarvard/experiment-core/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: jsonschema<5,>=4.19
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# experiment-core
|
|
26
|
+
|
|
27
|
+
**Still in active development.**
|
|
28
|
+
|
|
29
|
+
Small, domain-neutral contracts and a local runtime for supervised experiments:
|
|
30
|
+
bounded operations, resource ownership, durable intent/result records,
|
|
31
|
+
cancellation, explicit approvals and immutable artifacts.
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pip install experiment-core
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Python 3.10+; depends only on `jsonschema`. No LDCon, agentbridge, provider CLI
|
|
38
|
+
or hardware dependency. Host applications supply authentication and physical
|
|
39
|
+
device adapters; unknown writes are never replayed automatically.
|
|
40
|
+
|
|
41
|
+
Tests cover schemas, lifecycle transitions, resource conflicts, cancellation,
|
|
42
|
+
approval/adoption and recovery of recorded operations. Run `python -m pytest -q`.
|
|
43
|
+
The process-locking runtime is tested on Linux.
|
|
44
|
+
|
|
45
|
+
[JSON examples](examples/) · [Publishing](docs/publishing.html) · [MIT license](LICENSE)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# experiment-core
|
|
2
|
+
|
|
3
|
+
**Still in active development.**
|
|
4
|
+
|
|
5
|
+
Small, domain-neutral contracts and a local runtime for supervised experiments:
|
|
6
|
+
bounded operations, resource ownership, durable intent/result records,
|
|
7
|
+
cancellation, explicit approvals and immutable artifacts.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install experiment-core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Python 3.10+; depends only on `jsonschema`. No LDCon, agentbridge, provider CLI
|
|
14
|
+
or hardware dependency. Host applications supply authentication and physical
|
|
15
|
+
device adapters; unknown writes are never replayed automatically.
|
|
16
|
+
|
|
17
|
+
Tests cover schemas, lifecycle transitions, resource conflicts, cancellation,
|
|
18
|
+
approval/adoption and recovery of recorded operations. Run `python -m pytest -q`.
|
|
19
|
+
The process-locking runtime is tested on Linux.
|
|
20
|
+
|
|
21
|
+
[JSON examples](examples/) · [Publishing](docs/publishing.html) · [MIT license](LICENSE)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!doctype html><html lang="en"><meta charset="utf-8">
|
|
2
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Publishing experiment-core</title>
|
|
3
|
+
<style>body{font:16px/1.7 system-ui;max-width:900px;margin:3rem auto;padding:0 1rem}pre{background:#f2f4f6;padding:1rem;overflow:auto}code{font-size:.9em}</style>
|
|
4
|
+
<h1>Publishing experiment-core</h1><p>Public repository: <a href="https://github.com/FanHarvard/experiment-core">FanHarvard/experiment-core</a>.
|
|
5
|
+
Initial version: 0.1.0. No production service is activated by publication.</p>
|
|
6
|
+
<h2>One-time PyPI setup</h2><p>A PyPI account owner creates a pending publisher at
|
|
7
|
+
<a href="https://pypi.org/manage/account/publishing/">PyPI publishing settings</a> with:</p>
|
|
8
|
+
<table><tr><th>Project name</th><td>experiment-core</td></tr><tr><th>Owner</th><td>FanHarvard</td></tr>
|
|
9
|
+
<tr><th>Repository</th><td>experiment-core</td></tr><tr><th>Workflow</th><td>release.yml</td></tr>
|
|
10
|
+
<tr><th>Environment</th><td>pypi</td></tr></table>
|
|
11
|
+
<p>Use the repository's <code>pypi</code> environment to restrict release access. Authentication uses
|
|
12
|
+
GitHub OIDC and PyPI Trusted Publishing; no long-lived PyPI API token belongs in this repository.
|
|
13
|
+
See the <a href="https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/">official PyPI setup</a>.</p>
|
|
14
|
+
<h2>Release</h2><p>Publish experiment-core before LDCon. LDCon pins the verified compatible core version.
|
|
15
|
+
Update package metadata and the LDCon CI core tag together for a future version.</p>
|
|
16
|
+
<pre>python -m build
|
|
17
|
+
python -m twine check --strict dist/*
|
|
18
|
+
python scripts/check_dist.py dist
|
|
19
|
+
python scripts/check_secrets.py
|
|
20
|
+
python -m pytest -q
|
|
21
|
+
git tag v0.1.0
|
|
22
|
+
git push origin v0.1.0</pre>
|
|
23
|
+
<p>Create a GitHub Release for the tag to run <code>release.yml</code>, or dispatch that workflow at
|
|
24
|
+
the tag. The workflow checks the tag against package metadata, builds and audits distributions,
|
|
25
|
+
tests the wheel, and publishes through the dedicated environment. Do not reuse an uploaded PyPI version.
|
|
26
|
+
The CI workflow also checks Python 3.10, 3.12 and 3.13.</p>
|
|
27
|
+
<h2>Public/private boundary</h2><p>Public: source, tests, documentation, MIT license and reviewed examples.
|
|
28
|
+
Private: credentials, .env/.pypirc files, runtime databases, job logs, agent conversations,
|
|
29
|
+
approval receipts, local deployment history and live service configuration. New public repositories
|
|
30
|
+
start from a reviewed snapshot; the internal Git history is not uploaded.</p>
|
|
31
|
+
<p>The secret scanner compares findings against individually reviewed non-secret fixtures and code
|
|
32
|
+
literals in <code>.secrets.baseline</code>. New findings fail CI. This complements artifact inspection;
|
|
33
|
+
it is not a proof that arbitrary future data is safe to publish.</p></html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1",
|
|
3
|
+
"kind": "capabilities",
|
|
4
|
+
"message_id": "capabilities-1",
|
|
5
|
+
"subject_id": "fixture-a",
|
|
6
|
+
"run_id": "run-a",
|
|
7
|
+
"issued_at": "2026-10-23T22:00:00-04:00",
|
|
8
|
+
"body": {
|
|
9
|
+
"revisions": {
|
|
10
|
+
"configuration": "cfg-1",
|
|
11
|
+
"policy": "policy-1",
|
|
12
|
+
"owner_state": "state-1"
|
|
13
|
+
},
|
|
14
|
+
"operations": [
|
|
15
|
+
{
|
|
16
|
+
"capability_id": "fixture.measure_point",
|
|
17
|
+
"effect": "control",
|
|
18
|
+
"access": "attached",
|
|
19
|
+
"resource_ids": [
|
|
20
|
+
"axis-a"
|
|
21
|
+
],
|
|
22
|
+
"available": true,
|
|
23
|
+
"reason": "",
|
|
24
|
+
"parameter_schema_id": "fixture.point.v1",
|
|
25
|
+
"result_schema_id": "fixture.point-result.v1",
|
|
26
|
+
"adoption_boundaries": [
|
|
27
|
+
"between_points"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"parameters": [
|
|
32
|
+
{
|
|
33
|
+
"parameter_id": "axis.max_step",
|
|
34
|
+
"value_schema_id": "fixture.distance.v1",
|
|
35
|
+
"unit": "mm",
|
|
36
|
+
"owner_role": "application",
|
|
37
|
+
"classification": "safety",
|
|
38
|
+
"approval": "exact_human_approval",
|
|
39
|
+
"adoption": "live_boundary",
|
|
40
|
+
"boundaries": [
|
|
41
|
+
"between_points"
|
|
42
|
+
],
|
|
43
|
+
"coupled_group": "axis-motion",
|
|
44
|
+
"validator_id": "fixture.motion-proof.v1",
|
|
45
|
+
"enforcement_scope": [
|
|
46
|
+
"host_command"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1",
|
|
3
|
+
"kind": "operation_status",
|
|
4
|
+
"message_id": "operation_status-1",
|
|
5
|
+
"subject_id": "fixture-a",
|
|
6
|
+
"run_id": "run-a",
|
|
7
|
+
"issued_at": "2026-10-23T22:00:00-04:00",
|
|
8
|
+
"body": {
|
|
9
|
+
"request_id": "request-1",
|
|
10
|
+
"state": "running",
|
|
11
|
+
"effect_state": "confirmed",
|
|
12
|
+
"revisions": {
|
|
13
|
+
"configuration": "cfg-1",
|
|
14
|
+
"policy": "policy-1",
|
|
15
|
+
"owner_state": "state-1"
|
|
16
|
+
},
|
|
17
|
+
"objective": "not_evaluated",
|
|
18
|
+
"quality": {
|
|
19
|
+
"state": "not_configured",
|
|
20
|
+
"evaluators": []
|
|
21
|
+
},
|
|
22
|
+
"reason": "No optional quality evaluator configured.",
|
|
23
|
+
"artifacts": []
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1",
|
|
3
|
+
"kind": "decision",
|
|
4
|
+
"message_id": "decision-1",
|
|
5
|
+
"subject_id": "fixture-a",
|
|
6
|
+
"run_id": "run-a",
|
|
7
|
+
"issued_at": "2026-10-23T22:00:00-04:00",
|
|
8
|
+
"body": {
|
|
9
|
+
"observation_id": "obs-1",
|
|
10
|
+
"expected": {
|
|
11
|
+
"configuration": "cfg-1",
|
|
12
|
+
"policy": "policy-1",
|
|
13
|
+
"owner_state": "state-1"
|
|
14
|
+
},
|
|
15
|
+
"action": "execute",
|
|
16
|
+
"capability_id": "fixture.measure_point",
|
|
17
|
+
"parameter_schema_id": "fixture.point.v1",
|
|
18
|
+
"parameters": {
|
|
19
|
+
"position_mm": 1.2
|
|
20
|
+
},
|
|
21
|
+
"reason": "Measure the next point in the approved interval."
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1",
|
|
3
|
+
"kind": "measurement",
|
|
4
|
+
"message_id": "measurement-1",
|
|
5
|
+
"subject_id": "fixture-a",
|
|
6
|
+
"run_id": "run-a",
|
|
7
|
+
"issued_at": "2026-10-23T22:00:00-04:00",
|
|
8
|
+
"body": {
|
|
9
|
+
"observation_id": "obs-1",
|
|
10
|
+
"revisions": {
|
|
11
|
+
"configuration": "cfg-1",
|
|
12
|
+
"policy": "policy-1",
|
|
13
|
+
"owner_state": "state-1"
|
|
14
|
+
},
|
|
15
|
+
"observed_at": "2026-10-23T22:00:00-04:00",
|
|
16
|
+
"metrics": [
|
|
17
|
+
{
|
|
18
|
+
"quantity_id": "temperature",
|
|
19
|
+
"unit": "K",
|
|
20
|
+
"value": 295.1,
|
|
21
|
+
"state": "valid",
|
|
22
|
+
"source_time": "2026-10-23T21:59:59-04:00",
|
|
23
|
+
"reason": ""
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"quality": {
|
|
27
|
+
"state": "not_configured",
|
|
28
|
+
"evaluators": []
|
|
29
|
+
},
|
|
30
|
+
"artifacts": []
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1",
|
|
3
|
+
"kind": "operation_request",
|
|
4
|
+
"message_id": "operation_request-1",
|
|
5
|
+
"subject_id": "fixture-a",
|
|
6
|
+
"run_id": "run-a",
|
|
7
|
+
"issued_at": "2026-10-23T22:00:00-04:00",
|
|
8
|
+
"body": {
|
|
9
|
+
"capability_id": "fixture.measure_point",
|
|
10
|
+
"parameter_schema_id": "fixture.point.v1",
|
|
11
|
+
"parameters": {
|
|
12
|
+
"position_mm": 1.2
|
|
13
|
+
},
|
|
14
|
+
"expected": {
|
|
15
|
+
"configuration": "cfg-1",
|
|
16
|
+
"policy": "policy-1",
|
|
17
|
+
"owner_state": "state-1"
|
|
18
|
+
},
|
|
19
|
+
"parent_run_id": null,
|
|
20
|
+
"execution_mode": "manual",
|
|
21
|
+
"reason": "Example operation request; not authorized by schema validation."
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1",
|
|
3
|
+
"kind": "policy_status",
|
|
4
|
+
"message_id": "policy_status-1",
|
|
5
|
+
"subject_id": "fixture-a",
|
|
6
|
+
"run_id": "run-a",
|
|
7
|
+
"issued_at": "2026-10-23T22:00:00-04:00",
|
|
8
|
+
"body": {
|
|
9
|
+
"proposal_id": "proposal-1",
|
|
10
|
+
"state": "deferred",
|
|
11
|
+
"desired_revision": "policy-2",
|
|
12
|
+
"effective_revision": "policy-1",
|
|
13
|
+
"hardware_revision": null,
|
|
14
|
+
"boundary": "between_points",
|
|
15
|
+
"reason": "Current admitted trajectory still uses policy-1."
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1",
|
|
3
|
+
"kind": "policy_proposal",
|
|
4
|
+
"message_id": "policy_proposal-1",
|
|
5
|
+
"subject_id": "fixture-a",
|
|
6
|
+
"run_id": "run-a",
|
|
7
|
+
"issued_at": "2026-10-23T22:00:00-04:00",
|
|
8
|
+
"body": {
|
|
9
|
+
"expected_policy_revision": "policy-1",
|
|
10
|
+
"changes": [
|
|
11
|
+
{
|
|
12
|
+
"parameter_id": "axis.max_step",
|
|
13
|
+
"old": 0.1,
|
|
14
|
+
"new": 0.2
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"reason": "Draft example only; no approval or physical operation."
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "experiment-core"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Domain-neutral contracts and local runtime for supervised experimental operations"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{name = "FanHarvard contributors"}]
|
|
14
|
+
classifiers = ["Development Status :: 3 - Alpha",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Topic :: Scientific/Engineering",
|
|
23
|
+
]
|
|
24
|
+
dependencies = ["jsonschema>=4.19,<5"]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.package-data]
|
|
30
|
+
experiment_core = ["schemas/*.json"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/FanHarvard/experiment-core"
|
|
34
|
+
Repository = "https://github.com/FanHarvard/experiment-core"
|
|
35
|
+
Issues = "https://github.com/FanHarvard/experiment-core/issues"
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
addopts = "--import-mode=importlib"
|
|
40
|
+
norecursedirs = ["support"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Audit source/wheel contents and strongly identifiable credential formats."""
|
|
2
|
+
from pathlib import Path, PurePosixPath
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
import tarfile
|
|
6
|
+
import zipfile
|
|
7
|
+
|
|
8
|
+
pattern = re.compile(rb'(?:gh[pousr]_[A-Za-z0-9_]{30,}|github_pat_[A-Za-z0-9_]{30,}|pypi-[A-Za-z0-9_-]{30,}|AKIA[A-Z0-9]{16}|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----)')
|
|
9
|
+
for artifact in sorted(Path(sys.argv[1]).iterdir()):
|
|
10
|
+
if artifact.suffix == '.whl':
|
|
11
|
+
with zipfile.ZipFile(artifact) as archive:
|
|
12
|
+
items = [(n, archive.read(n)) for n in archive.namelist() if not n.endswith('/')]
|
|
13
|
+
elif artifact.name.endswith('.tar.gz'):
|
|
14
|
+
with tarfile.open(artifact) as archive:
|
|
15
|
+
items = [(m.name.partition('/')[2], archive.extractfile(m).read()) for m in archive if m.isfile()]
|
|
16
|
+
else:
|
|
17
|
+
raise SystemExit('Unexpected distribution file: ' + artifact.name)
|
|
18
|
+
for name, data in items:
|
|
19
|
+
path = PurePosixPath(name)
|
|
20
|
+
if path.is_absolute() or '..' in path.parts:
|
|
21
|
+
raise SystemExit('Invalid archive path: ' + name)
|
|
22
|
+
if any(p in {'.git', '.venv', '__pycache__', 'node_modules', '.env', '.pypirc'} for p in path.parts) or path.suffix in {'.pem', '.key', '.sqlite3', '.jsonl', '.pyc'}:
|
|
23
|
+
raise SystemExit('Private/generated file in artifact: ' + name)
|
|
24
|
+
if artifact.suffix == '.whl' and any(p in {'tests', 'support'} for p in path.parts):
|
|
25
|
+
raise SystemExit('Test data in wheel: ' + name)
|
|
26
|
+
if pattern.search(data):
|
|
27
|
+
raise SystemExit('Potential credential in artifact (redacted): ' + name)
|
|
28
|
+
print(artifact.name, len(items), 'files audited')
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Fail on newly detected secrets without printing their values."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
baseline = json.loads(Path('.secrets.baseline').read_text())
|
|
8
|
+
allowed = {(p, item['type'], item['hashed_secret']) for p, items in baseline['results'].items()
|
|
9
|
+
for item in items if item.get('is_secret') is False}
|
|
10
|
+
result = subprocess.run([sys.executable, '-m', 'detect_secrets', 'scan', '--no-verify', '--exclude-files', r'^\.secrets\.baseline$'],
|
|
11
|
+
check=True, capture_output=True, text=True)
|
|
12
|
+
current = json.loads(result.stdout)
|
|
13
|
+
findings = [(p, item['line_number'], item['type']) for p, items in current['results'].items()
|
|
14
|
+
for item in items if (p, item['type'], item['hashed_secret']) not in allowed]
|
|
15
|
+
for path, line, kind in findings:
|
|
16
|
+
print(f'{path}:{line}: {kind} (value redacted)')
|
|
17
|
+
if findings:
|
|
18
|
+
raise SystemExit('Unreviewed potential credential detected')
|
|
19
|
+
print('No unreviewed credential findings')
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Refuse a release from a branch or a tag for another version."""
|
|
2
|
+
from email.parser import BytesParser
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import zipfile
|
|
6
|
+
|
|
7
|
+
wheel, = Path('dist').glob('*.whl')
|
|
8
|
+
with zipfile.ZipFile(wheel) as archive:
|
|
9
|
+
metadata, = [n for n in archive.namelist() if n.endswith('.dist-info/METADATA')]
|
|
10
|
+
version = BytesParser().parsebytes(archive.read(metadata))['Version']
|
|
11
|
+
if os.environ.get('GITHUB_REF') != 'refs/tags/v' + version:
|
|
12
|
+
raise SystemExit('Run this release at tag v' + version)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Install the built wheel, including declared role extras for LDCon."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
wheels = list(Path('dist').glob('*.whl'))
|
|
7
|
+
if len(wheels) != 1:
|
|
8
|
+
raise SystemExit('Expected exactly one wheel')
|
|
9
|
+
wheel = wheels[0]
|
|
10
|
+
extras = '[web,instruments,analysis]' if wheel.name.startswith('ldcon-') else ''
|
|
11
|
+
subprocess.run([sys.executable, '-m', 'pip', 'install', str(wheel.resolve()) + extras], check=True)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Domain-neutral contracts and an explicit local reference runtime.
|
|
2
|
+
|
|
3
|
+
Runtime components are imported from their documented modules. Importing this
|
|
4
|
+
package does not create stores, register applications or contact any service.
|
|
5
|
+
"""
|
|
6
|
+
from experiment_core.validation import ContractError, schema, validate_message
|
|
7
|
+
|
|
8
|
+
__all__ = ["ContractError", "schema", "validate_message"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Advisory proposal sizes; never physical limits or execution authority."""
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
from experiment_core.records import digest, safe_id
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def validate(value):
|
|
9
|
+
if (not isinstance(value, dict) or set(value) != {'schema_version', 'knobs'}
|
|
10
|
+
or type(value['schema_version']) is not int or value['schema_version'] != 1
|
|
11
|
+
or not isinstance(value['knobs'], dict) or len(value['knobs']) > 128):
|
|
12
|
+
raise ValueError('Expected versioned per-knob adjustment guidance')
|
|
13
|
+
for name, item in value['knobs'].items():
|
|
14
|
+
safe_id(name)
|
|
15
|
+
if not isinstance(item, dict) or set(item) != {'kind', 'step', 'unit', 'reason'}:
|
|
16
|
+
raise ValueError('Guidance requires kind, step, unit and reason')
|
|
17
|
+
if item['kind'] not in ('absolute', 'relative'):
|
|
18
|
+
raise ValueError('Guidance kind must be absolute or relative')
|
|
19
|
+
if type(item['step']) not in (int, float) or not math.isfinite(item['step']) or item['step'] <= 0:
|
|
20
|
+
raise ValueError('Suggested step must be positive and finite')
|
|
21
|
+
for key, maximum in (('unit', 64), ('reason', 1024)):
|
|
22
|
+
text = item[key]
|
|
23
|
+
if (not isinstance(text, str) or not text.strip() or len(text.encode()) > maximum
|
|
24
|
+
or any(ord(c) < 32 or ord(c) == 127 for c in text)):
|
|
25
|
+
raise ValueError('Invalid guidance '+key)
|
|
26
|
+
if item['kind'] == 'relative' and item['unit'] != 'fraction':
|
|
27
|
+
raise ValueError('Relative advice uses a dimensionless fraction of the current magnitude')
|
|
28
|
+
return deepcopy(value)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def snapshot(value):
|
|
32
|
+
value = validate(value)
|
|
33
|
+
return dict(document=value, revision=digest(value), advisory_only=True,
|
|
34
|
+
interpretation='An initial proposal size, not a minimum, maximum, safety limit or permission')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def instructions():
|
|
38
|
+
return ('Per-knob adjustment_guidance is advisory, not an execution bound. Start a local comparison '
|
|
39
|
+
'near the suggested initial step when it is appropriate to the current evidence. An absolute '
|
|
40
|
+
'step uses the stated physical unit; a relative step is a fraction of the magnitude of the '
|
|
41
|
+
'current parameter, not of its allowed range. Relative advice at zero does not define a step. '
|
|
42
|
+
'Use previous comparable responses, sensitivity, resolution, drift and recovery cost to choose '
|
|
43
|
+
'a smaller or larger step, and explain deviations in the decision reason. A permitted excursion '
|
|
44
|
+
'is not a recommended trial step: never select half the allowed span merely because it is allowed. '
|
|
45
|
+
'Characterization mode does not remove this distinction. Enlarge only when observations support '
|
|
46
|
+
'it; reduce after a large response or loss of the branch. Missing guidance means unknown, not '
|
|
47
|
+
'permission to use the range endpoint. Read current adjustment guidance before planning a new knob '
|
|
48
|
+
'trial if no current guidance snapshot was supplied. Advice alone never changes output, supported '
|
|
49
|
+
'capabilities, approved envelopes, physical limits, freshness requirements or foreground priority.')
|