sysml2kit 0.3.0__tar.gz → 0.4.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.
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/CHANGELOG.md +60 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/CLAUDE.md +1 -2
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/PKG-INFO +12 -7
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/README.md +10 -5
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/SPEC.md +43 -17
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/rf-library.md +7 -6
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/verification.md +50 -1
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/pyproject.toml +5 -4
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/_version.py +2 -2
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/api/client.py +59 -6
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/api/models.py +9 -1
- sysml2kit-0.4.0/src/sysml2kit/backends/_sysmlpy_patches.py +156 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/backends/sysmlpy.py +186 -7
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/cli.py +87 -4
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/interchange/reader.py +24 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/mcp/tools_requirements.py +9 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/builder.py +32 -4
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/text/writer.py +42 -17
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/validation.py +36 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/verify/binding.py +29 -1
- sysml2kit-0.4.0/src/sysml2kit/verify/runner.py +427 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_api_client.py +30 -6
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_api_live.py +23 -5
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_backend_fidelity.py +114 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_cli_api.py +37 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_cli_fmt.py +46 -0
- sysml2kit-0.4.0/tests/test_cli_verify.py +278 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_text_writer/test_vehicle_golden.sysml +2 -2
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_text_writer.py +2 -2
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_validation.py +23 -1
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_verify.py +140 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/uv.lock +6 -6
- sysml2kit-0.3.0/src/sysml2kit/verify/runner.py +0 -251
- sysml2kit-0.3.0/tests/test_cli_verify.py +0 -120
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/.gitignore +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/.pre-commit-config.yaml +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/.python-version +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/CITATION.cff +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/CONTRIBUTING.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/LICENSE +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/NOTICE +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/SECURITY.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docker/compose.yaml +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/api-client.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/backends.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/cli.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/concepts.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/index.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/interchange.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/mcp.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/quickstart.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/reference.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/traceability.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/docs/views.md +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/mkdocs.yml +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/scripts/slopcheck.sh +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/scripts/wait_for_api.sh +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/__init__.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/api/__init__.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/api/errors.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/backends/__init__.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/backends/protocol.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/diff.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/graph.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/interchange/__init__.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/interchange/typemap.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/interchange/writer.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/interop/__init__.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/interop/requirements.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/mcp/__init__.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/mcp/_common.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/mcp/server.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/mcp/tools_model.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/__init__.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/analysis.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/base.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/container.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/metadata.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/relations.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/requirements.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/structure.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/model/values.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/py.typed +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/query.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/text/__init__.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/text/keywords.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/units.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/verify/__init__.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/verify/engines.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/views.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/src/sysml2kit/workspace.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/conftest.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/fixtures/RFParts.sysml +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/fixtures/RFRequirements.sysml +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/fixtures/SatcomTerminal28GHz.sysml +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/fixtures/vehicle.sysml +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_backend_sysmlpy.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_cli.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_diff.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_interchange.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_interop.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_mcp.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_model.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_package.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_query.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_roundtrip_json.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_roundtrip_text.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_values_units.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_views/test_trace_contains_all_edge_kinds.trace.mmd +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_views/test_tree_contains_hierarchy.tree.mmd +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_views.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tests/test_workspace.py +0 -0
- {sysml2kit-0.3.0 → sysml2kit-0.4.0}/tools/conformance/run_oracle.py +0 -0
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 — 2026-08-21
|
|
4
|
+
|
|
5
|
+
The multi-fidelity release: one analysis, several engines, honest error
|
|
6
|
+
bars, and budgeted escalation — from JSON or from the textual notation.
|
|
7
|
+
|
|
8
|
+
- **Fidelity ladders.** An analysis may carry sibling
|
|
9
|
+
`verificationBinding` annotations labeled by two new reserved keys,
|
|
10
|
+
`fidelity` (rung name) and `costSeconds` (declared wall-clock, which
|
|
11
|
+
orders the ladder). Sibling bindings are named metadata usages typed
|
|
12
|
+
by a `metadata def verificationBinding` (`builder.metadata_def` +
|
|
13
|
+
`builder.metadata(..., definition=...)`), since distinct names are
|
|
14
|
+
what stable-id hashing needs; a single binding may still just be named
|
|
15
|
+
`verificationBinding`. Validation rule S2K010 rejects duplicate rung
|
|
16
|
+
labels and warns on mixed labeled/unlabeled siblings.
|
|
17
|
+
- **Runner policies.** `run_verification(..., policy=...)`: `all`
|
|
18
|
+
(default) runs every rung and reports the cross-rung `spread` per
|
|
19
|
+
requirement; `cheapest` runs one; `escalate` runs the cheapest rungs,
|
|
20
|
+
ranks must-requirements by margin thinness, and escalates within
|
|
21
|
+
`budget_s` against declared costs (verdicts carry `escalated_from`).
|
|
22
|
+
Every run records measured `seconds_by_fidelity`; write-back uses the
|
|
23
|
+
highest-fidelity verdict and names the rung in its provenance. CLI:
|
|
24
|
+
`verify --policy/--budget-s/--fidelity`; MCP `requirements_verify`
|
|
25
|
+
gains `policy`/`budget_s` and returns `seconds_by_fidelity`.
|
|
26
|
+
- **Text notation carries the whole loop.** A guarded runtime shim
|
|
27
|
+
(`backends/_sysmlpy_patches.py`, mirroring upstream mycr0ft/sysmlpy
|
|
28
|
+
#6, #7, and #9) fixes three visitor defects, and the writer emits
|
|
29
|
+
named dependencies (`dependency verify_1 from A to B;`), typed
|
|
30
|
+
bindings, and package-level metadata — so satisfy, verify, derive,
|
|
31
|
+
allocate, and bindings all round-trip through `.sysml` text.
|
|
32
|
+
`sysml2kit verify` accepts several text files parsed as one model
|
|
33
|
+
(the file declaring the `metadata def` rides along) and warns instead
|
|
34
|
+
of passing vacuously when no bindings are found.
|
|
35
|
+
- **Ownership survives the pilot server.** `push_model` runs in two
|
|
36
|
+
phases (elements with `aliasIds`, then `OwningMembership` records
|
|
37
|
+
mapped through server-minted ids); the interchange reader folds
|
|
38
|
+
membership records back into the owner map. New `api branches` and
|
|
39
|
+
`api commits` subcommands.
|
|
40
|
+
- Pins: sysmlpy floor 0.36.3; the hatchling `<1.32` cap is lifted
|
|
41
|
+
(Metadata-Version 2.5 passes current twine); the 2026-05 spec pin was
|
|
42
|
+
re-confirmed as newest on 2026-08-21.
|
|
43
|
+
|
|
44
|
+
## 0.3.1 — 2026-08-21
|
|
45
|
+
|
|
46
|
+
Hotfix release; upgrade recommended.
|
|
47
|
+
|
|
48
|
+
- **`fmt` could silently delete verification bindings and verify links.**
|
|
49
|
+
The loss gate's grammar signature missed MetadataFeature nodes, and
|
|
50
|
+
dependency statements leave no grammar node at all, so a format pass on a
|
|
51
|
+
binding-bearing file removed both without triggering the refusal. The
|
|
52
|
+
signature now covers metadata/annotation/dependency nodes and a textual
|
|
53
|
+
keyword guard catches statements the parser drops entirely; `fmt` on such
|
|
54
|
+
files now refuses unless `--lossy` is passed.
|
|
55
|
+
- Metadata values in textual output were rendered with Python repr
|
|
56
|
+
(single-quoted strings, `True`/`False`); they now render as SysML text
|
|
57
|
+
(double-quoted strings, `true`/`false`).
|
|
58
|
+
- With multiple verificationBinding annotations on one analysis, requirement
|
|
59
|
+
checking used the last binding while write-back provenance named the
|
|
60
|
+
first. Both now use the first binding, extra bindings execute with a
|
|
61
|
+
logged warning, and full multi-binding support is planned.
|
|
62
|
+
|
|
3
63
|
## 0.3.0 — 2026-08-21
|
|
4
64
|
|
|
5
65
|
- **Verification execution** (`sysml2kit.verify`): `verificationBinding`
|
|
@@ -50,8 +50,7 @@ never edit `_version.py` or add a version literal.
|
|
|
50
50
|
- `interop/` — `RequirementSpec` extraction; the `metricKey` attribute
|
|
51
51
|
convention bridges to phased-array-systems and aedl (adapters live in those
|
|
52
52
|
repos, not here).
|
|
53
|
-
- `cli.py` — typer app
|
|
54
|
-
`src/sysml2kit/mcp/` following apab's `docs/mcp-conventions.md`.
|
|
53
|
+
- `cli.py` — typer app; `mcp/` follows apab's `docs/mcp-conventions.md`.
|
|
55
54
|
|
|
56
55
|
## Constraints
|
|
57
56
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysml2kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: API-first Python tooling for building, querying, validating, and automating SysML v2 models
|
|
5
5
|
Project-URL: Homepage, https://github.com/jman4162/sysml2kit
|
|
6
6
|
Project-URL: Source, https://github.com/jman4162/sysml2kit
|
|
@@ -33,7 +33,7 @@ Requires-Dist: networkx>=3.2; extra == 'graph'
|
|
|
33
33
|
Provides-Extra: mcp
|
|
34
34
|
Requires-Dist: mcp<2,>=1.26; extra == 'mcp'
|
|
35
35
|
Provides-Extra: parse
|
|
36
|
-
Requires-Dist: sysmlpy<0.37,>=0.36.
|
|
36
|
+
Requires-Dist: sysmlpy<0.37,>=0.36.3; extra == 'parse'
|
|
37
37
|
Provides-Extra: verify
|
|
38
38
|
Requires-Dist: pyyaml>=6.0; extra == 'verify'
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
@@ -49,11 +49,11 @@ Description-Content-Type: text/markdown
|
|
|
49
49
|
API-first Python tooling for building, querying, validating, and automating
|
|
50
50
|
SysML v2 models.
|
|
51
51
|
|
|
52
|
-
> **Status: pre-alpha.** The 0.
|
|
52
|
+
> **Status: pre-alpha.** The 0.4.x line covers the full loop: model, writer,
|
|
53
53
|
> interchange, queries, validation, diff, mermaid views, parse backend, MCP
|
|
54
|
-
> server, API client with a live-server harness, and
|
|
55
|
-
> The API may still move between minor versions; pin
|
|
56
|
-
> depend on it. Changes: [CHANGELOG.md](CHANGELOG.md).
|
|
54
|
+
> server, API client with a live-server harness, and multi-fidelity
|
|
55
|
+
> verification execution. The API may still move between minor versions; pin
|
|
56
|
+
> an exact version if you depend on it. Changes: [CHANGELOG.md](CHANGELOG.md).
|
|
57
57
|
|
|
58
58
|
`sysml2kit` is the requirements/architecture/traceability layer for
|
|
59
59
|
engineering automation stacks: build a system model in Python, emit standard
|
|
@@ -82,7 +82,12 @@ https://jman4162.github.io/sysml2kit/
|
|
|
82
82
|
- **Verification execution**: `verificationBinding` metadata binds an
|
|
83
83
|
analysis case to an engine from the `sysml2kit.engines` entry-point group;
|
|
84
84
|
`sysml2kit verify` runs it and checks each requirement with margins, and
|
|
85
|
-
can write results back into the model with provenance.
|
|
85
|
+
can write results back into the model with provenance. Sibling bindings
|
|
86
|
+
labeled with `fidelity`/`costSeconds` form a fidelity ladder: `--policy
|
|
87
|
+
all` reports the cross-rung spread as an error bar, `--policy escalate
|
|
88
|
+
--budget-s N` spends a compute budget on the thinnest margins first, and
|
|
89
|
+
every run records measured seconds per rung. The whole loop — bindings,
|
|
90
|
+
verify links, policies — works from `.sysml` text as well as JSON.
|
|
86
91
|
- **Mermaid views**: ownership-tree and requirement-trace diagrams.
|
|
87
92
|
- **MCP server**: nine tools for agents (`sysml2kit mcp serve`).
|
|
88
93
|
- **API client**: a thin HTTP client for the OMG Systems Modeling API and
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
API-first Python tooling for building, querying, validating, and automating
|
|
10
10
|
SysML v2 models.
|
|
11
11
|
|
|
12
|
-
> **Status: pre-alpha.** The 0.
|
|
12
|
+
> **Status: pre-alpha.** The 0.4.x line covers the full loop: model, writer,
|
|
13
13
|
> interchange, queries, validation, diff, mermaid views, parse backend, MCP
|
|
14
|
-
> server, API client with a live-server harness, and
|
|
15
|
-
> The API may still move between minor versions; pin
|
|
16
|
-
> depend on it. Changes: [CHANGELOG.md](CHANGELOG.md).
|
|
14
|
+
> server, API client with a live-server harness, and multi-fidelity
|
|
15
|
+
> verification execution. The API may still move between minor versions; pin
|
|
16
|
+
> an exact version if you depend on it. Changes: [CHANGELOG.md](CHANGELOG.md).
|
|
17
17
|
|
|
18
18
|
`sysml2kit` is the requirements/architecture/traceability layer for
|
|
19
19
|
engineering automation stacks: build a system model in Python, emit standard
|
|
@@ -42,7 +42,12 @@ https://jman4162.github.io/sysml2kit/
|
|
|
42
42
|
- **Verification execution**: `verificationBinding` metadata binds an
|
|
43
43
|
analysis case to an engine from the `sysml2kit.engines` entry-point group;
|
|
44
44
|
`sysml2kit verify` runs it and checks each requirement with margins, and
|
|
45
|
-
can write results back into the model with provenance.
|
|
45
|
+
can write results back into the model with provenance. Sibling bindings
|
|
46
|
+
labeled with `fidelity`/`costSeconds` form a fidelity ladder: `--policy
|
|
47
|
+
all` reports the cross-rung spread as an error bar, `--policy escalate
|
|
48
|
+
--budget-s N` spends a compute budget on the thinnest margins first, and
|
|
49
|
+
every run records measured seconds per rung. The whole loop — bindings,
|
|
50
|
+
verify links, policies — works from `.sysml` text as well as JSON.
|
|
46
51
|
- **Mermaid views**: ownership-tree and requirement-trace diagrams.
|
|
47
52
|
- **MCP server**: nine tools for agents (`sysml2kit mcp serve`).
|
|
48
53
|
- **API client**: a thin HTTP client for the OMG Systems Modeling API and
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
## Reference spec release
|
|
4
4
|
|
|
5
5
|
This package targets the OMG SysML v2 standard as published in the
|
|
6
|
-
`Systems-Modeling/SysML-v2-Release` repository, tag **`2026-05
|
|
6
|
+
`Systems-Modeling/SysML-v2-Release` repository, tag **`2026-05`** (still the
|
|
7
|
+
newest release tag as of 2026-08-21).
|
|
7
8
|
|
|
8
9
|
Policy: the pin moves at most quarterly, in a minor release, with the change
|
|
9
10
|
noted in the changelog. `src/sysml2kit/interchange/typemap.py` is the single
|
|
@@ -50,22 +51,34 @@ not drop elements the profile lacks classes for.
|
|
|
50
51
|
|
|
51
52
|
The backend parses via ``sysmlpy.load_grammar_antlr`` and walks the raw
|
|
52
53
|
ANTLR dict (the wrapper-object loader rebuilds usage bodies lossily and is
|
|
53
|
-
not used).
|
|
54
|
+
not used). The backend also applies guarded runtime patches to sysmlpy's visitor
|
|
55
|
+
(``backends/_sysmlpy_patches.py``; upstream: mycr0ft/sysmlpy #6 and #7 plus
|
|
56
|
+
the metadata-body issue filed with them). Patches activate only for sysmlpy
|
|
57
|
+
0.3x with the expected visitor surface; otherwise the pre-patch losses
|
|
58
|
+
apply and their tests document them.
|
|
54
59
|
|
|
55
|
-
| Round-trips
|
|
60
|
+
| Round-trips (with patches active) | Still lost |
|
|
56
61
|
|---|---|
|
|
57
|
-
| names, short names | `dependency A to B;` statements (
|
|
58
|
-
| docs (package/part/requirement scope) | `
|
|
59
|
-
| feature typing, incl. cross-package | `
|
|
60
|
-
| multiplicity |
|
|
62
|
+
| names, short names | unnamed `dependency A to B;` statements (the writer emits named `verify_N`/`derive_N` dependencies, which reify) |
|
|
63
|
+
| docs (package/part/requirement scope) | `connect a.pa to b.pb;` endpoints |
|
|
64
|
+
| feature typing, incl. cross-package | `verification` case usages (dropped entirely) |
|
|
65
|
+
| multiplicity | metadata inside definition/case bodies (hence the package-level placement convention below) |
|
|
61
66
|
| attribute values with units | value provenance (`source`/`confidence` have no textual slot) |
|
|
62
67
|
| requirement subject and text | |
|
|
63
|
-
| satisfy
|
|
68
|
+
| satisfy, verify, derive, allocate | |
|
|
69
|
+
| package-level metadata with scalar values (verificationBinding included) | |
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
Verify/derive encode as **named dependencies**: ``dependency verify_1 from
|
|
72
|
+
ana to R1;`` — the ``verify_``/``derive_`` name prefix is the round-trip
|
|
73
|
+
convention. Metadata annotations are placed at package level (the ``about``
|
|
74
|
+
reference carries the attachment); ``builder.metadata`` defaults to this.
|
|
75
|
+
Endpoint resolution prefers candidates under the same root package, so
|
|
76
|
+
multi-package files with repeated short names resolve correctly.
|
|
77
|
+
|
|
78
|
+
Consequence: **a `.sysml` file is a complete verification artifact** —
|
|
79
|
+
`sysml2kit verify` on text produces the same verdicts as on interchange
|
|
80
|
+
JSON. Remaining losses are pinned by tests in
|
|
81
|
+
`tests/test_backend_fidelity.py`.
|
|
69
82
|
|
|
70
83
|
## Identity and ownership
|
|
71
84
|
|
|
@@ -79,15 +92,28 @@ test failure.
|
|
|
79
92
|
|
|
80
93
|
## Verification binding convention
|
|
81
94
|
|
|
82
|
-
A ``MetadataUsage``
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
A ``MetadataUsage`` annotating an ``AnalysisCaseUsage`` binds that
|
|
96
|
+
analysis to an executable engine when it is recognizable as a
|
|
97
|
+
``verificationBinding`` in either of two forms:
|
|
98
|
+
|
|
99
|
+
- the usage itself is named ``verificationBinding``
|
|
100
|
+
(``metadata verificationBinding about study {...}``), or
|
|
101
|
+
- the usage is a *named* usage typed by a
|
|
102
|
+
``metadata def verificationBinding``
|
|
103
|
+
(``metadata analyticBinding : verificationBinding about study {...}``).
|
|
104
|
+
|
|
105
|
+
The typed form is required for a fidelity ladder: sibling usages must
|
|
106
|
+
carry distinct names for stable-id hashing, so they share the annotation
|
|
107
|
+
kind through the definition instead of the name. Values (flat scalars,
|
|
108
|
+
per the metadata model):
|
|
85
109
|
|
|
86
110
|
| key | type | meaning |
|
|
87
111
|
|---|---|---|
|
|
88
112
|
| ``engine`` | str, required | registry name, e.g. ``"phased-array-systems"`` |
|
|
89
113
|
| ``configRef`` | str, optional | ``.yaml``/``.yml``/``.json`` payload file next to the model file (resolved relative to its directory, containment-checked; no ``..``) |
|
|
90
114
|
| ``payload.<dotted>`` | scalar, optional | override deep-merged over the loaded config; dotted keys expand to nested dicts |
|
|
115
|
+
| ``fidelity`` | str, optional | rung label; sibling bindings on one analysis form a fidelity ladder (labels must be distinct, rule S2K010) |
|
|
116
|
+
| ``costSeconds`` | number, optional | declared wall-clock estimate; orders the ladder and gates the escalate policy's budget |
|
|
91
117
|
|
|
92
118
|
Engines resolve **by name** against a registry populated from the
|
|
93
119
|
``sysml2kit.engines`` entry-point group and explicit caller registration.
|
|
@@ -101,8 +127,8 @@ the analysis (``source`` starts with ``sysml2kit.verify``) and a
|
|
|
101
127
|
``verificationVerdict`` metadata on each requirement; both replace prior
|
|
102
128
|
same-named results, so reruns do not accumulate.
|
|
103
129
|
|
|
104
|
-
Bindings
|
|
105
|
-
|
|
130
|
+
Bindings round-trip through interchange JSON and, at package level, through
|
|
131
|
+
the textual notation (see the fidelity table above).
|
|
106
132
|
|
|
107
133
|
## Provenance on values
|
|
108
134
|
|
|
@@ -21,12 +21,13 @@ for spec in extract_requirements(model):
|
|
|
21
21
|
It ships four library packages (RFVocabulary quantity kinds with units,
|
|
22
22
|
RFParts part/port definitions, RFRequirements requirement definitions using
|
|
23
23
|
the metricKey convention, RFAnalyses analysis case definitions) plus
|
|
24
|
-
**SatcomTerminal28GHz**
|
|
25
|
-
benchmark:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
two worked examples. **SatcomTerminal28GHz** mirrors the aedl `t3-001`
|
|
25
|
+
benchmark: eight machine-checkable requirements, each satisfied by a part
|
|
26
|
+
and verified by an analysis. **SatcomTerminalPAS** is executable: its
|
|
27
|
+
`pasStudy` analysis carries a `verificationBinding` for the
|
|
28
|
+
`phased-array-systems` engine, so `sysml2kit verify` runs a real study and
|
|
29
|
+
all five requirements pass with margin (see
|
|
30
|
+
[verification](verification.md)).
|
|
30
31
|
|
|
31
32
|
The library demonstrates the intended division of labor: domain vocabulary
|
|
32
33
|
as SysML v2 model content, generic mechanics in the kit, physics engines
|
|
@@ -70,8 +70,57 @@ a provenance `source` (`sysml2kit.verify <engine>==<version> <timestamp>`)
|
|
|
70
70
|
and a `verificationVerdict` metadata on each requirement, replacing prior
|
|
71
71
|
results so reruns do not accumulate.
|
|
72
72
|
|
|
73
|
+
## Fidelity ladders and allocation
|
|
74
|
+
|
|
75
|
+
An analysis may carry several bindings at different fidelities. Two more
|
|
76
|
+
reserved metadata keys describe the ladder: `fidelity` (a rung label such
|
|
77
|
+
as `analytic` or `pattern`) and `costSeconds` (the declared wall-clock
|
|
78
|
+
estimate that orders the rungs):
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
builder.metadata(
|
|
82
|
+
model,
|
|
83
|
+
analysis,
|
|
84
|
+
{
|
|
85
|
+
"engine": "phased-array-systems",
|
|
86
|
+
"configRef": "study.yaml",
|
|
87
|
+
"fidelity": "analytic",
|
|
88
|
+
"costSeconds": 0.001,
|
|
89
|
+
},
|
|
90
|
+
name="verificationBinding",
|
|
91
|
+
)
|
|
92
|
+
builder.metadata(
|
|
93
|
+
model,
|
|
94
|
+
analysis,
|
|
95
|
+
{
|
|
96
|
+
"engine": "phased-array-systems-pattern",
|
|
97
|
+
"configRef": "study.yaml",
|
|
98
|
+
"fidelity": "pattern",
|
|
99
|
+
"costSeconds": 1.0,
|
|
100
|
+
},
|
|
101
|
+
name="verificationBinding",
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The runner's `policy` decides which rungs execute. `all` (default) runs
|
|
106
|
+
every rung and reports a verdict per rung, with the cross-rung `spread` as
|
|
107
|
+
an honest error bar. `cheapest` runs only the lowest-cost rung per
|
|
108
|
+
analysis. `escalate` runs the cheapest rungs, ranks must-requirements by
|
|
109
|
+
margin thinness (`|margin| / |threshold|`), and spends the remaining
|
|
110
|
+
`budget_s` escalating the thinnest to the next rung; those verdicts carry
|
|
111
|
+
`escalated_from`. Every run records `seconds_by_fidelity`, so allocation
|
|
112
|
+
claims are auditable.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
sysml2kit verify model.json --policy escalate --budget-s 5 --report run.json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Validation rule S2K010 rejects two bindings on one analysis that share a
|
|
119
|
+
fidelity label and warns when only some sibling bindings declare one.
|
|
120
|
+
|
|
73
121
|
Over MCP, the `requirements_verify` tool runs the same flow with
|
|
74
|
-
entry-point engines only
|
|
122
|
+
entry-point engines only (plus `policy` and `budget_s` parameters),
|
|
123
|
+
returning `passed`, must-failures, `seconds_by_fidelity`, and the report
|
|
75
124
|
path.
|
|
76
125
|
|
|
77
126
|
## End to end with the RF library
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
# hatchling 1.32 emits Metadata-Version 2.5
|
|
3
|
-
# pipeline's
|
|
2
|
+
# hatchling 1.32 emits Metadata-Version 2.5. Current twine accepts it, but
|
|
3
|
+
# the release pipeline's build-and-inspect action vendors an older twine
|
|
4
|
+
# that rejects it (seen failing 2026-08-21); keep the cap until it updates.
|
|
4
5
|
requires = ["hatchling>=1.27,<1.32", "hatch-vcs>=0.4"]
|
|
5
6
|
build-backend = "hatchling.build"
|
|
6
7
|
|
|
@@ -48,7 +49,7 @@ dependencies = [
|
|
|
48
49
|
[project.optional-dependencies]
|
|
49
50
|
# sysmlpy has a single maintainer; cap the minor version and bump deliberately
|
|
50
51
|
# after checking the release notes.
|
|
51
|
-
parse = ["sysmlpy>=0.36.
|
|
52
|
+
parse = ["sysmlpy>=0.36.3,<0.37"]
|
|
52
53
|
graph = ["networkx>=3.2"]
|
|
53
54
|
# mcp 2.0.0 removed mcp.server.fastmcp; stay on 1.x until the surface is ported.
|
|
54
55
|
mcp = ["mcp>=1.26,<2"]
|
|
@@ -73,7 +74,7 @@ test = [
|
|
|
73
74
|
"respx>=0.22",
|
|
74
75
|
]
|
|
75
76
|
lint = ["mypy>=1.15", "ruff>=0.16", "types-PyYAML>=6.0"]
|
|
76
|
-
parse-test = ["sysmlpy>=0.36.
|
|
77
|
+
parse-test = ["sysmlpy>=0.36.3,<0.37"]
|
|
77
78
|
mcp-test = ["mcp>=1.26,<2"]
|
|
78
79
|
verify-test = ["pyyaml>=6.0"]
|
|
79
80
|
docs = ["mkdocs-material>=9.5", "mkdocstrings[python]>=0.27"]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.4.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -9,6 +9,7 @@ hundred lines against the REST/JSON binding and returns sysml2kit models.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import types
|
|
12
|
+
import uuid
|
|
12
13
|
from typing import Any, Self
|
|
13
14
|
|
|
14
15
|
import httpx
|
|
@@ -19,6 +20,9 @@ from sysml2kit.model.container import Model
|
|
|
19
20
|
from .errors import ApiError
|
|
20
21
|
from .models import Branch, Commit, Project
|
|
21
22
|
|
|
23
|
+
#: Deterministic ids for pushed OwningMembership records.
|
|
24
|
+
_MEMBERSHIP_NAMESPACE = uuid.UUID("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
|
25
|
+
|
|
22
26
|
|
|
23
27
|
class SysMLApiClient:
|
|
24
28
|
"""Synchronous client; use as a context manager to reuse one connection."""
|
|
@@ -140,18 +144,67 @@ class SysMLApiClient:
|
|
|
140
144
|
branch: str | None = None,
|
|
141
145
|
message: str | None = None,
|
|
142
146
|
) -> Commit:
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
"""Push a model as one or two commits (elements, then ownership).
|
|
148
|
+
|
|
149
|
+
The pilot re-mints element ids and resolves references only against
|
|
150
|
+
already-committed rows, so ownership cannot ride along with the
|
|
151
|
+
elements. Each element therefore carries its local id in
|
|
152
|
+
``aliasIds``; after the element commit, the aliases map local ids to
|
|
153
|
+
server-minted ids and a second commit adds one ``OwningMembership``
|
|
154
|
+
per ownership pair. A model without ownership pushes as a single
|
|
155
|
+
commit. The returned commit is the head (the membership commit when
|
|
156
|
+
one was needed).
|
|
157
|
+
"""
|
|
158
|
+
change = []
|
|
159
|
+
for record in model_to_json(model):
|
|
160
|
+
adapted = self._to_server_record(record)
|
|
161
|
+
adapted["aliasIds"] = [record["@id"]]
|
|
162
|
+
change.append({"@type": "DataVersion", "payload": adapted})
|
|
148
163
|
payload: dict[str, Any] = {"@type": "Commit", "change": change}
|
|
149
164
|
if message:
|
|
150
165
|
payload["description"] = message
|
|
151
166
|
path = f"/projects/{project_id}/commits"
|
|
152
167
|
if branch:
|
|
153
168
|
path += f"?branchId={branch}"
|
|
154
|
-
|
|
169
|
+
element_commit = Commit.model_validate(self._post(path, payload))
|
|
170
|
+
if not model.owner:
|
|
171
|
+
return element_commit
|
|
172
|
+
|
|
173
|
+
minted: dict[str, str] = {}
|
|
174
|
+
listing = self._get(f"/projects/{project_id}/commits/{element_commit.id}/elements")
|
|
175
|
+
for summary in listing:
|
|
176
|
+
record = summary
|
|
177
|
+
if "aliasIds" not in record:
|
|
178
|
+
record = self.get_element(project_id, element_commit.id, summary["@id"])
|
|
179
|
+
for alias in record.get("aliasIds") or []:
|
|
180
|
+
minted[str(alias)] = record["@id"]
|
|
181
|
+
|
|
182
|
+
memberships = []
|
|
183
|
+
for member, owner in model.owner.items():
|
|
184
|
+
member_id, owner_id = minted.get(str(member)), minted.get(str(owner))
|
|
185
|
+
if member_id is None or owner_id is None:
|
|
186
|
+
continue # e.g. an element the server rejected
|
|
187
|
+
memberships.append(
|
|
188
|
+
{
|
|
189
|
+
"@type": "DataVersion",
|
|
190
|
+
"payload": {
|
|
191
|
+
"@type": "OwningMembership",
|
|
192
|
+
"@id": str(uuid.uuid5(_MEMBERSHIP_NAMESPACE, f"{owner}->{member}")),
|
|
193
|
+
"memberElement": {"@id": member_id},
|
|
194
|
+
"membershipOwningNamespace": {"@id": owner_id},
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
if not memberships:
|
|
199
|
+
return element_commit
|
|
200
|
+
ownership_payload: dict[str, Any] = {
|
|
201
|
+
"@type": "Commit",
|
|
202
|
+
"previousCommit": {"@id": element_commit.id},
|
|
203
|
+
"change": memberships,
|
|
204
|
+
}
|
|
205
|
+
if message:
|
|
206
|
+
ownership_payload["description"] = f"{message} (ownership)"
|
|
207
|
+
return Commit.model_validate(self._post(path, ownership_payload))
|
|
155
208
|
|
|
156
209
|
def head_commit(self, project_id: str) -> Commit:
|
|
157
210
|
"""Return the newest commit of a project; raises ApiError when empty."""
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Project(BaseModel):
|
|
@@ -24,6 +24,14 @@ class Branch(BaseModel):
|
|
|
24
24
|
name: str | None = None
|
|
25
25
|
head: str | None = None
|
|
26
26
|
|
|
27
|
+
@field_validator("head", mode="before")
|
|
28
|
+
@classmethod
|
|
29
|
+
def _head_ref_to_id(cls, value: object) -> object:
|
|
30
|
+
# The pilot sends head as an identified ref: {"@id": "..."}.
|
|
31
|
+
if isinstance(value, dict):
|
|
32
|
+
return value.get("@id")
|
|
33
|
+
return value
|
|
34
|
+
|
|
27
35
|
|
|
28
36
|
class Commit(BaseModel):
|
|
29
37
|
"""A commit within a project."""
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Runtime patches for sysmlpy's ANTLR visitor, applied under a version guard.
|
|
2
|
+
|
|
3
|
+
Three upstream defects lose data before the raw dict exists; fixes are
|
|
4
|
+
open upstream (mycr0ft/sysmlpy #6, #7, and #9, filed as issue #8). Until a
|
|
5
|
+
release ships them, this module applies equivalent patches to
|
|
6
|
+
``sysmlpy.antlr_visitor`` at backend import:
|
|
7
|
+
|
|
8
|
+
- ``allocate X to Y`` endpoints reach the dict (a ``part`` connector, the
|
|
9
|
+
same key ``ConnectionUsage`` uses),
|
|
10
|
+
- ``dependency [name] [from] A to B;`` statements are dispatched instead of
|
|
11
|
+
silently dropped,
|
|
12
|
+
- braced metadata bodies surface their feature texts (``bodyFeatures``).
|
|
13
|
+
|
|
14
|
+
Guards: only sysmlpy < 0.37, only when every patched attribute still looks
|
|
15
|
+
as expected; any mismatch logs a warning and leaves sysmlpy untouched, in
|
|
16
|
+
which case the documented pinned-loss behavior applies.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from importlib import metadata
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_APPLIED_MARKER = "_sysml2kit_patched"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _dependency_dict(visitor: Any, ctx: Any, prefix: Any) -> dict[str, Any]:
|
|
31
|
+
decl = ctx
|
|
32
|
+
if hasattr(ctx, "dependencyDeclaration") and ctx.dependencyDeclaration():
|
|
33
|
+
decl = ctx.dependencyDeclaration()
|
|
34
|
+
identification = None
|
|
35
|
+
if hasattr(decl, "identification") and decl.identification():
|
|
36
|
+
identification = visitor._build_identification_dict(decl.identification())
|
|
37
|
+
clients: list[dict[str, Any]] = []
|
|
38
|
+
suppliers: list[dict[str, Any]] = []
|
|
39
|
+
after_to = False
|
|
40
|
+
for child in decl.getChildren():
|
|
41
|
+
class_name = child.__class__.__name__
|
|
42
|
+
if class_name == "QualifiedNameContext":
|
|
43
|
+
names = child.getText().split("::")
|
|
44
|
+
(suppliers if after_to else clients).append({"name": "QualifiedName", "names": names})
|
|
45
|
+
elif not after_to and hasattr(child, "getText") and child.getText() == "to":
|
|
46
|
+
after_to = True
|
|
47
|
+
return {
|
|
48
|
+
"name": "PackageMember",
|
|
49
|
+
"prefix": prefix,
|
|
50
|
+
"ownedRelatedElement": {
|
|
51
|
+
"name": "DefinitionElement",
|
|
52
|
+
"ownedRelatedElement": {
|
|
53
|
+
"name": "Dependency",
|
|
54
|
+
"identification": identification,
|
|
55
|
+
"client": clients,
|
|
56
|
+
"supplier": suppliers,
|
|
57
|
+
"body": {"name": "RelationshipBody", "ownedRelatedElement": []},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _find_node(tree: Any, name: str) -> dict[str, Any] | None:
|
|
64
|
+
if isinstance(tree, dict):
|
|
65
|
+
if tree.get("name") == name:
|
|
66
|
+
return tree
|
|
67
|
+
for key, value in tree.items():
|
|
68
|
+
if key != "name":
|
|
69
|
+
found = _find_node(value, name)
|
|
70
|
+
if found is not None:
|
|
71
|
+
return found
|
|
72
|
+
elif isinstance(tree, list):
|
|
73
|
+
for item in tree:
|
|
74
|
+
found = _find_node(item, name)
|
|
75
|
+
if found is not None:
|
|
76
|
+
return found
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def apply_patches() -> bool:
|
|
81
|
+
"""Patch sysmlpy's visitor in place; returns whether patches are active."""
|
|
82
|
+
try:
|
|
83
|
+
import sysmlpy.antlr_visitor as visitor
|
|
84
|
+
except ImportError:
|
|
85
|
+
return False
|
|
86
|
+
if getattr(visitor, _APPLIED_MARKER, False):
|
|
87
|
+
return True
|
|
88
|
+
try:
|
|
89
|
+
version = metadata.version("sysmlpy")
|
|
90
|
+
except metadata.PackageNotFoundError:
|
|
91
|
+
version = "0"
|
|
92
|
+
if not version.startswith("0.3"):
|
|
93
|
+
logger.warning("sysmlpy %s outside the patch guard; pinned-loss behavior applies", version)
|
|
94
|
+
return False
|
|
95
|
+
required = (
|
|
96
|
+
"_make_allocation_usage_dict",
|
|
97
|
+
"_visit_definition_element_dict",
|
|
98
|
+
"_visit_metadata_feature_dict",
|
|
99
|
+
"_build_connector_part_dict",
|
|
100
|
+
"_build_identification_dict",
|
|
101
|
+
)
|
|
102
|
+
if not all(hasattr(visitor, attr) for attr in required):
|
|
103
|
+
logger.warning("sysmlpy visitor surface changed; skipping fidelity patches")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
original_allocation = visitor._make_allocation_usage_dict
|
|
107
|
+
original_definition_element = visitor._visit_definition_element_dict
|
|
108
|
+
original_metadata = visitor._visit_metadata_feature_dict
|
|
109
|
+
|
|
110
|
+
def patched_allocation(ctx: Any, prefix: Any = None) -> dict[str, Any]:
|
|
111
|
+
result: dict[str, Any] = original_allocation(ctx, prefix)
|
|
112
|
+
try:
|
|
113
|
+
aud = None
|
|
114
|
+
if (
|
|
115
|
+
ctx is not None
|
|
116
|
+
and hasattr(ctx, "allocationUsageDeclaration")
|
|
117
|
+
and ctx.allocationUsageDeclaration()
|
|
118
|
+
):
|
|
119
|
+
aud = ctx.allocationUsageDeclaration()
|
|
120
|
+
if aud is not None and hasattr(aud, "connectorPart") and aud.connectorPart():
|
|
121
|
+
node = _find_node(result, "AllocationUsage")
|
|
122
|
+
if node is not None:
|
|
123
|
+
node["part"] = visitor._build_connector_part_dict(aud.connectorPart())
|
|
124
|
+
except Exception:
|
|
125
|
+
logger.exception("allocate endpoint patch failed; endpoints dropped")
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
def patched_definition_element(def_elem_ctx: Any, prefix: Any = None) -> Any:
|
|
129
|
+
try:
|
|
130
|
+
if hasattr(def_elem_ctx, "dependency") and def_elem_ctx.dependency():
|
|
131
|
+
return _dependency_dict(visitor, def_elem_ctx.dependency(), prefix)
|
|
132
|
+
except Exception:
|
|
133
|
+
logger.exception("dependency patch failed; statement dropped")
|
|
134
|
+
return original_definition_element(def_elem_ctx, prefix)
|
|
135
|
+
|
|
136
|
+
def patched_metadata(ctx: Any) -> dict[str, Any]:
|
|
137
|
+
result: dict[str, Any] = original_metadata(ctx)
|
|
138
|
+
try:
|
|
139
|
+
body = ctx.metadataBody() if hasattr(ctx, "metadataBody") else None
|
|
140
|
+
if body is not None and getattr(body, "LBRACE", lambda: None)():
|
|
141
|
+
texts = []
|
|
142
|
+
elements = body.metadataBodyElement()
|
|
143
|
+
for element in elements or []:
|
|
144
|
+
texts.append(element.getText())
|
|
145
|
+
if texts:
|
|
146
|
+
result["bodyFeatures"] = texts
|
|
147
|
+
except Exception:
|
|
148
|
+
logger.exception("metadata body patch failed; values dropped")
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
visitor._make_allocation_usage_dict = patched_allocation
|
|
152
|
+
visitor._visit_definition_element_dict = patched_definition_element
|
|
153
|
+
visitor._visit_metadata_feature_dict = patched_metadata
|
|
154
|
+
setattr(visitor, _APPLIED_MARKER, True)
|
|
155
|
+
logger.info("sysmlpy %s fidelity patches active (upstream: mycr0ft/sysmlpy#6, #7, #9)", version)
|
|
156
|
+
return True
|