behavior-contracts 0.7.2__tar.gz → 0.7.3__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.
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/PKG-INFO +3 -3
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/README.md +2 -2
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/pyproject.toml +1 -1
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/__init__.py +3 -1
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/behavior.py +70 -2
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/PKG-INFO +3 -3
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/tests/test_codegen_generated.py +1 -1
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/tests/test_conformance.py +1 -1
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/tests/test_public_api.py +1 -1
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/setup.cfg +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/canonical.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/codec.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/conformance.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/envelope.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/expr_eval.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/fingerprint.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/guard.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/plan.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/primitives.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts/template.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/SOURCES.txt +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/dependency_links.txt +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/entry_points.txt +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/requires.txt +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/top_level.txt +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/tests/test_plan_parallel.py +0 -0
- {behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/tests/test_primitives.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: behavior-contracts
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: Multi-language IR-Runtime common core (Python port). The COMMON 'thin core' primitives from dsl-contracts runtime-boundary.md — validate_envelope / evaluate_expression / render_template / run_plan / canonical_value / canonical_json / py_float_repr / assert_portable — with zero backend (DynamoDB/graphddb) dependencies.
|
|
5
5
|
Author: foo-log
|
|
6
6
|
License: MIT
|
|
@@ -60,12 +60,12 @@ string matching the conformance protocol's failure-code set.
|
|
|
60
60
|
|
|
61
61
|
## Conformance
|
|
62
62
|
|
|
63
|
-
Runs the 7 shared suites (
|
|
63
|
+
Runs the 7 shared suites (165 vectors) from `../conformance/vectors/*.json`
|
|
64
64
|
through this package per [`../conformance/PROTOCOL.md`](../conformance/PROTOCOL.md),
|
|
65
65
|
including the pre-flight version fail-closed sweep.
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
python -m behavior_contracts.conformance # → "164 passed, 0 failed /
|
|
68
|
+
python -m behavior_contracts.conformance # → "164 passed, 0 failed / 165 vectors across 7 suites"
|
|
69
69
|
# or the console script:
|
|
70
70
|
behavior-contracts-conformance
|
|
71
71
|
```
|
|
@@ -48,12 +48,12 @@ string matching the conformance protocol's failure-code set.
|
|
|
48
48
|
|
|
49
49
|
## Conformance
|
|
50
50
|
|
|
51
|
-
Runs the 7 shared suites (
|
|
51
|
+
Runs the 7 shared suites (165 vectors) from `../conformance/vectors/*.json`
|
|
52
52
|
through this package per [`../conformance/PROTOCOL.md`](../conformance/PROTOCOL.md),
|
|
53
53
|
including the pre-flight version fail-closed sweep.
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
python -m behavior_contracts.conformance # → "164 passed, 0 failed /
|
|
56
|
+
python -m behavior_contracts.conformance # → "164 passed, 0 failed / 165 vectors across 7 suites"
|
|
57
57
|
# or the console script:
|
|
58
58
|
behavior-contracts-conformance
|
|
59
59
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "behavior-contracts"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.3"
|
|
8
8
|
description = "Multi-language IR-Runtime common core (Python port). The COMMON 'thin core' primitives from dsl-contracts runtime-boundary.md — validate_envelope / evaluate_expression / render_template / run_plan / canonical_value / canonical_json / py_float_repr / assert_portable — with zero backend (DynamoDB/graphddb) dependencies."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -22,7 +22,7 @@ SPEC_VERSIONS = {
|
|
|
22
22
|
"template": 1,
|
|
23
23
|
"plan": 1,
|
|
24
24
|
"canonical": 2, # v2: obj 値の __proto__ own key を FORBIDDEN_KEY に(fail-closed)
|
|
25
|
-
"behavior":
|
|
25
|
+
"behavior": 3, # v3: first-class fanout node kind(deduped connection list-in fan-out)。v1/v2 意味論は不変(additive)
|
|
26
26
|
"guard": 1, # 初版(bc#25: assert_portable_component_graph の accept/reject を vector-pin)
|
|
27
27
|
"c2": 1, # 初版(bc#28: c2-catalog-swap — catalog-swap 実行 + IR 構造一致の 5 言語 pin)
|
|
28
28
|
}
|
|
@@ -80,6 +80,7 @@ from .guard import ( # noqa: E402
|
|
|
80
80
|
# ── behavior(component-graph IR + run_behavior) ──────────────────────────────
|
|
81
81
|
from .behavior import ( # noqa: E402
|
|
82
82
|
BehaviorFailure,
|
|
83
|
+
fanout_dedup_drop,
|
|
83
84
|
run_behavior,
|
|
84
85
|
)
|
|
85
86
|
|
|
@@ -143,6 +144,7 @@ __all__ = [
|
|
|
143
144
|
"PortabilityError",
|
|
144
145
|
# behavior
|
|
145
146
|
"run_behavior",
|
|
147
|
+
"fanout_dedup_drop",
|
|
146
148
|
"BehaviorFailure",
|
|
147
149
|
# codec
|
|
148
150
|
"decode_value",
|
|
@@ -52,16 +52,20 @@ def _bfail(code: str, message: str) -> "None":
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def _node_kind(n: Mapping[str, Any]) -> str:
|
|
55
|
+
if "fanout" in n:
|
|
56
|
+
return "fanout"
|
|
55
57
|
if "map" in n:
|
|
56
58
|
return "map"
|
|
57
59
|
if "cond" in n:
|
|
58
60
|
return "cond"
|
|
59
61
|
if "component" in n:
|
|
60
62
|
return "componentRef"
|
|
61
|
-
_bfail("UNKNOWN_NODE_KIND", f"body node '{n.get('id', '?')}' is not componentRef/map/cond")
|
|
63
|
+
_bfail("UNKNOWN_NODE_KIND", f"body node '{n.get('id', '?')}' is not componentRef/map/cond/fanout")
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
def _node_parent(n: Mapping[str, Any]) -> Optional[str]:
|
|
67
|
+
if "fanout" in n:
|
|
68
|
+
return n["fanout"].get("parent")
|
|
65
69
|
if "map" in n:
|
|
66
70
|
return n["map"].get("parent")
|
|
67
71
|
if "cond" in n:
|
|
@@ -70,12 +74,14 @@ def _node_parent(n: Mapping[str, Any]) -> Optional[str]:
|
|
|
70
74
|
|
|
71
75
|
|
|
72
76
|
def _node_bind_field(n: Mapping[str, Any]) -> Optional[str]:
|
|
73
|
-
if "map" in n or "cond" in n:
|
|
77
|
+
if "map" in n or "cond" in n or "fanout" in n:
|
|
74
78
|
return None
|
|
75
79
|
return n.get("bindField")
|
|
76
80
|
|
|
77
81
|
|
|
78
82
|
def _node_policy(n: Mapping[str, Any]) -> Optional[str]:
|
|
83
|
+
if "fanout" in n:
|
|
84
|
+
return n["fanout"].get("policy")
|
|
79
85
|
if "map" in n:
|
|
80
86
|
return n["map"].get("policy")
|
|
81
87
|
if "cond" in n:
|
|
@@ -84,6 +90,8 @@ def _node_policy(n: Mapping[str, Any]) -> Optional[str]:
|
|
|
84
90
|
|
|
85
91
|
|
|
86
92
|
def _node_relation_kind(n: Mapping[str, Any]) -> Optional[str]:
|
|
93
|
+
if "fanout" in n:
|
|
94
|
+
return n["fanout"].get("relationKind")
|
|
87
95
|
if "map" in n:
|
|
88
96
|
return n["map"].get("relationKind")
|
|
89
97
|
if "cond" in n:
|
|
@@ -91,6 +99,37 @@ def _node_relation_kind(n: Mapping[str, Any]) -> Optional[str]:
|
|
|
91
99
|
return n.get("relationKind")
|
|
92
100
|
|
|
93
101
|
|
|
102
|
+
def fanout_dedup_drop(aligned_bodies: List[Value], dedupe_key: str, drop: str,
|
|
103
|
+
implicit_source: Optional[str]) -> List[Value]:
|
|
104
|
+
"""THE ONE dedup/drop definition (behaviorVersion 3) — Python twin of behavior.ts fanoutDedupDrop.
|
|
105
|
+
|
|
106
|
+
Applies first-seen dedupe (by the body's ``dedupe_key`` field) + dangling drop (null/non-object/
|
|
107
|
+
absent-key body when ``drop=='dangling'``) + implicitSource strip to the aligned raw list; returns
|
|
108
|
+
the connection ``items`` (the wrap is done by the caller). MUST stay byte-equal to the TS/native defs.
|
|
109
|
+
"""
|
|
110
|
+
items: List[Value] = []
|
|
111
|
+
seen: set = set()
|
|
112
|
+
for body in aligned_bodies:
|
|
113
|
+
rec = body if isinstance(body, dict) else None
|
|
114
|
+
key_val = rec.get(dedupe_key) if rec is not None else None
|
|
115
|
+
has_key = key_val is not None
|
|
116
|
+
if not has_key:
|
|
117
|
+
if drop == "dangling":
|
|
118
|
+
continue
|
|
119
|
+
items.append(body)
|
|
120
|
+
continue
|
|
121
|
+
seen_key = ("s:" + key_val) if isinstance(key_val, str) else ("j:" + repr(key_val))
|
|
122
|
+
if seen_key in seen:
|
|
123
|
+
continue
|
|
124
|
+
seen.add(seen_key)
|
|
125
|
+
if implicit_source is not None and rec is not None and implicit_source in rec:
|
|
126
|
+
copy = {k: v for k, v in rec.items() if k != implicit_source}
|
|
127
|
+
items.append(copy)
|
|
128
|
+
else:
|
|
129
|
+
items.append(body)
|
|
130
|
+
return items
|
|
131
|
+
|
|
132
|
+
|
|
94
133
|
def _eval_ports(ports: Mapping[str, Any], scope: Dict[str, Value]) -> Dict[str, Value]:
|
|
95
134
|
# key はソートせず宣言順のまま。FORBIDDEN_KEY 等は evaluate が投げる(fail-closed)。
|
|
96
135
|
return {k: evaluate_expression(v, scope) for k, v in ports.items()}
|
|
@@ -235,6 +274,35 @@ def run_behavior(
|
|
|
235
274
|
augmented.append(el)
|
|
236
275
|
return {"ok": augmented}
|
|
237
276
|
|
|
277
|
+
if kind == "fanout":
|
|
278
|
+
# fanout(v3): over(id-list)→ dedup 済み 1 回の batched handler → dedupe/drop/strip →
|
|
279
|
+
# connection {items, cursor:None}。整列制約(MAP_BATCH_RESULT_MISMATCH)は適用されない。
|
|
280
|
+
f = node["fanout"]
|
|
281
|
+
over = evaluate_expression(f["over"], base_scope())
|
|
282
|
+
if not isinstance(over, list):
|
|
283
|
+
_bfail("FANOUT_OVER_NOT_ARRAY", f"fanout '{op['id']}': 'over' did not evaluate to an array")
|
|
284
|
+
handler = handlers.get(f["component"])
|
|
285
|
+
if handler is None:
|
|
286
|
+
_bfail("UNKNOWN_COMPONENT", f"component '{f['component']}' has no handler (fail-closed)")
|
|
287
|
+
items_ports: List[Value] = []
|
|
288
|
+
for el in over:
|
|
289
|
+
scope = {**base_scope(), f["as"]: el}
|
|
290
|
+
items_ports.append(_eval_ports(f["ports"], scope))
|
|
291
|
+
if not items_ports:
|
|
292
|
+
return {"ok": {"items": [], "cursor": None}}
|
|
293
|
+
outcome = handler({"items": items_ports}, {"nodeId": op["id"], "component": f["component"]})
|
|
294
|
+
if "error" in outcome:
|
|
295
|
+
return outcome
|
|
296
|
+
r = outcome["ok"]
|
|
297
|
+
if not isinstance(r, list) or len(r) != len(items_ports):
|
|
298
|
+
_bfail(
|
|
299
|
+
"FANOUT_BATCH_RESULT_MISMATCH",
|
|
300
|
+
f"fanout '{op['id']}': batched handler must return a list aligned to the deduped id list "
|
|
301
|
+
f"(want {len(items_ports)}, got {len(r) if isinstance(r, list) else type(r).__name__})",
|
|
302
|
+
)
|
|
303
|
+
items_out = fanout_dedup_drop(r, f["dedupeKey"], f["drop"], f.get("implicitSource"))
|
|
304
|
+
return {"ok": {"items": items_out, "cursor": None}}
|
|
305
|
+
|
|
238
306
|
# componentRef
|
|
239
307
|
handler = handlers.get(node["component"])
|
|
240
308
|
if handler is None:
|
{behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: behavior-contracts
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: Multi-language IR-Runtime common core (Python port). The COMMON 'thin core' primitives from dsl-contracts runtime-boundary.md — validate_envelope / evaluate_expression / render_template / run_plan / canonical_value / canonical_json / py_float_repr / assert_portable — with zero backend (DynamoDB/graphddb) dependencies.
|
|
5
5
|
Author: foo-log
|
|
6
6
|
License: MIT
|
|
@@ -60,12 +60,12 @@ string matching the conformance protocol's failure-code set.
|
|
|
60
60
|
|
|
61
61
|
## Conformance
|
|
62
62
|
|
|
63
|
-
Runs the 7 shared suites (
|
|
63
|
+
Runs the 7 shared suites (165 vectors) from `../conformance/vectors/*.json`
|
|
64
64
|
through this package per [`../conformance/PROTOCOL.md`](../conformance/PROTOCOL.md),
|
|
65
65
|
including the pre-flight version fail-closed sweep.
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
python -m behavior_contracts.conformance # → "164 passed, 0 failed /
|
|
68
|
+
python -m behavior_contracts.conformance # → "164 passed, 0 failed / 165 vectors across 7 suites"
|
|
69
69
|
# or the console script:
|
|
70
70
|
behavior-contracts-conformance
|
|
71
71
|
```
|
|
@@ -110,7 +110,7 @@ def test_tampered_generated_module_is_loudly_rejected(tmp_path):
|
|
|
110
110
|
code = f.read()
|
|
111
111
|
|
|
112
112
|
# spec-version skew(焼き込み定数の改変)→ ロード時 RuntimeError。
|
|
113
|
-
skew = code.replace('"behavior":
|
|
113
|
+
skew = code.replace('"behavior": 3', '"behavior": 99')
|
|
114
114
|
assert skew != code
|
|
115
115
|
p = tmp_path / "skew.py"
|
|
116
116
|
p.write_text(skew, encoding="utf-8")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/requires.txt
RENAMED
|
File without changes
|
{behavior_contracts-0.7.2 → behavior_contracts-0.7.3}/src/behavior_contracts.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|