@particle-academy/fancy-conformance 0.21.0 → 0.21.2
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.
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.21.
|
|
1
|
+
0.21.2
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@particle-academy/fancy-conformance",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"description": "Shared cross-language conformance fixtures for the Fancy suite. One contract, N implementations, and a single table that every implementation asserts in its own CI \u2014 so 'parity' is a test result rather than a claim. Ships the fixture data itself, so a Rust, Go or Python runner can consume it without a JavaScript toolchain.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
"id": "0001-stripe-customer-fake",
|
|
7
7
|
"title": "A Stripe customer node in fake mode publishes the faker's authored fields and the node's own config.",
|
|
8
8
|
"since": "0.21.0",
|
|
9
|
-
"tags": [
|
|
9
|
+
"tags": [
|
|
10
|
+
"connector",
|
|
11
|
+
"stripe",
|
|
12
|
+
"fake",
|
|
13
|
+
"idempotency"
|
|
14
|
+
],
|
|
10
15
|
"input": {
|
|
11
16
|
"schema": {
|
|
12
17
|
"$schema": "https://particle.academy/schemas/workflow/v1.json",
|
|
@@ -19,7 +24,10 @@
|
|
|
19
24
|
"kind": "@particle-academy/manual_trigger",
|
|
20
25
|
"label": "Run it",
|
|
21
26
|
"config": {},
|
|
22
|
-
"position": {
|
|
27
|
+
"position": {
|
|
28
|
+
"x": 0,
|
|
29
|
+
"y": 0
|
|
30
|
+
}
|
|
23
31
|
},
|
|
24
32
|
{
|
|
25
33
|
"id": "customer",
|
|
@@ -30,14 +38,20 @@
|
|
|
30
38
|
"email": "ada@example.test",
|
|
31
39
|
"name": "Ada Lovelace"
|
|
32
40
|
},
|
|
33
|
-
"position": {
|
|
41
|
+
"position": {
|
|
42
|
+
"x": 280,
|
|
43
|
+
"y": 0
|
|
44
|
+
}
|
|
34
45
|
},
|
|
35
46
|
{
|
|
36
47
|
"id": "out",
|
|
37
48
|
"kind": "@particle-academy/output",
|
|
38
49
|
"label": "Result",
|
|
39
50
|
"config": {},
|
|
40
|
-
"position": {
|
|
51
|
+
"position": {
|
|
52
|
+
"x": 560,
|
|
53
|
+
"y": 0
|
|
54
|
+
}
|
|
41
55
|
}
|
|
42
56
|
],
|
|
43
57
|
"edges": [
|
|
@@ -60,8 +74,19 @@
|
|
|
60
74
|
},
|
|
61
75
|
"initialInputs": {},
|
|
62
76
|
"runIdentity": {
|
|
63
|
-
"derivation": "\"lab\" + sha256(
|
|
64
|
-
"
|
|
77
|
+
"derivation": "\"lab\" + sha256(canonicalJson(input.schema))[0:8]",
|
|
78
|
+
"canonicalJson": [
|
|
79
|
+
"Object keys sorted by Unicode CODE POINT, at every depth. Array order is preserved. (Code point order is what PHP's bytewise UTF-8 sort, Python and Rust produce natively; JavaScript's default sort is UTF-16 code-unit order and must not be used — they diverge when a supplementary-plane character sits beside one in U+E000–U+FFFF.)",
|
|
80
|
+
"No insignificant whitespace: separators are exactly `,` and `:`.",
|
|
81
|
+
"UTF-8 output. Escape only what JSON requires — PHP must pass JSON_UNESCAPED_SLASHES and JSON_UNESCAPED_UNICODE, because its default escapes `/` and every non-ASCII character and neither matches the other runtimes.",
|
|
82
|
+
"An empty object and an empty array are ONE value: emit `[]`. PHP's `json_decode(..., true)` cannot tell them apart and this package's own PHP loader decodes that way, so `[]` is the only spelling every runtime can reach.",
|
|
83
|
+
"The hashed input contains INTEGERS ONLY. No fractional value and no `x.0` literal. After a parse no runtime can recover how a number was written, and they disagree on how to print a float — measured: Python emits `1.0`, JavaScript `1`, PHP `1` or `1.0` depending on JSON_PRESERVE_ZERO_FRACTION. An integer prints identically everywhere with no flags. Enforced by a test over the case, not left to the author.",
|
|
84
|
+
"Every object key in the hashed input is ASCII. Enforced by a test. With ASCII keys every ordering rule above coincides, so the code-point-vs-code-unit divergence cannot be reached silently — a case that needs a non-ASCII key has to change that test first, and in doing so read why."
|
|
85
|
+
],
|
|
86
|
+
"note": "Supplied, not optional. The connector derives its idempotency key from this, and some connectors refuse to write without one — a runner that omits it exercises a different path.",
|
|
87
|
+
"value": "labd5f9ddb2",
|
|
88
|
+
"valueProvenance": "Pinned 2026-09-13. Derived independently by THREE runtimes under the rules above and identical in all of them: the connector lab's PHP lane and its Node lane (weaver), and a Python re-derivation made when landing it. `value` is authoritative; `derivation` and `canonicalJson` stay as provenance and so a runner can prove it agrees. A runner SHOULD derive the identity itself and refuse a run whose supplied value differs, rather than trust the literal blindly — that is what stops two lanes drifting while each still reads a pinned string.",
|
|
89
|
+
"history": "0.21.0 wrote `canonical json` without defining it. 0.21.1 defined four rules. Implementing those in PHP and Node found they still produced two different keys — `labe4b755a3` (Node, `{}` survives a parse) and `labd5f9ddb2` (PHP, `{}` collapses to `[]`) — because the definition never said what an empty object is. The loader decides that, not the case, and the PHP loader decodes to arrays. Found by the connector lab by comparing the two byte for byte; the lab also found its own PHP canonicaliser had been emitting lists with integer keys, self-consistent since it was written and so invisible to every digest it produced."
|
|
65
90
|
}
|
|
66
91
|
},
|
|
67
92
|
"expected": {
|
|
@@ -78,7 +103,9 @@
|
|
|
78
103
|
"matches": {
|
|
79
104
|
"out.data.id": "^cus_fake_[0-9a-f]{12}$"
|
|
80
105
|
},
|
|
81
|
-
"deterministic": [
|
|
106
|
+
"deterministic": [
|
|
107
|
+
"out.data.id"
|
|
108
|
+
]
|
|
82
109
|
}
|
|
83
110
|
},
|
|
84
111
|
"notes": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
"IMPORT IS STRICT HERE, and graph-runs is lenient. That difference is deliberate and worth stating rather than inheriting: a document one engine accepts and another refuses is not one document. It is also live — `fancy-flow-php`'s `FancyFlowManager::toGraph()` forces `lenient: true` on every `run()` caller while the TypeScript and Python twins default strict, so the same versionless graph runs in Laravel and is refused by the editor meant to edit it. Ungating the version check is queued on the PHP side; until it lands, a PHP runner for this suite must call the strict import path explicitly rather than going through `run()`.",
|
|
39
39
|
|
|
40
|
-
"A RUN IDENTITY MUST BE SUPPLIED. The connector derives an idempotency key from it, and some connectors refuse to write without one — so a runner that omits it is not exercising the same code path. Derive it from the graph so every runner sends the same key; the reference derivation is `\"lab\" + sha256(
|
|
40
|
+
"A RUN IDENTITY MUST BE SUPPLIED. The connector derives an idempotency key from it, and some connectors refuse to write without one — so a runner that omits it is not exercising the same code path. Derive it from the graph so every runner sends the same key; the reference derivation is `\"lab\" + sha256(canonicalJson(input.schema))[0:8]`, and `canonicalJson` is DEFINED on the case under `input.runIdentity.canonicalJson` — sorted keys, no whitespace, unescaped slashes and unicode. Not defined here as well, so there is one definition rather than two that can drift. That was originally kept short to dodge a defect in `fancy-connector-core` — `Idempotency::fit()` clamped only at the 255-character catalogue ceiling and never at the provider's own declared `idempotencyMaxLength`, so Discord (25) refused a legitimate 29-character key. Fixed in fancy-connector-core 0.4.0; the short derivation is kept here because a stable short key is good practice, not because it is still required.",
|
|
41
41
|
|
|
42
42
|
"THIS SUITE ASSERTS NAMED PATHS, NOT THE WHOLE OUTPUTS OBJECT, and that is weaker than graph-runs in one specific way: an extra key a runtime publishes at `out` will not fail a case. It is done this way because `out.data.id` is drawn from the faker's seeded sequence and cannot be authored, so a whole-object equality would have to pin a value nobody wrote down. Read the green tick as \"every named field agrees\", not \"the outputs are identical\". The softer whole-object claim is recorded in the case's own notes where it has been measured.",
|
|
43
43
|
|