@particle-academy/fancy-conformance 0.20.0 → 0.21.0
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.
|
|
1
|
+
0.21.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@particle-academy/fancy-conformance",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
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",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../schema/case-table.schema.json",
|
|
3
|
+
"suite": "flow/connector-runs",
|
|
4
|
+
"cases": [
|
|
5
|
+
{
|
|
6
|
+
"id": "0001-stripe-customer-fake",
|
|
7
|
+
"title": "A Stripe customer node in fake mode publishes the faker's authored fields and the node's own config.",
|
|
8
|
+
"since": "0.21.0",
|
|
9
|
+
"tags": ["connector", "stripe", "fake", "idempotency"],
|
|
10
|
+
"input": {
|
|
11
|
+
"schema": {
|
|
12
|
+
"$schema": "https://particle.academy/schemas/workflow/v1.json",
|
|
13
|
+
"version": 1,
|
|
14
|
+
"name": "Stripe customer (fake)",
|
|
15
|
+
"graph": {
|
|
16
|
+
"nodes": [
|
|
17
|
+
{
|
|
18
|
+
"id": "start",
|
|
19
|
+
"kind": "@particle-academy/manual_trigger",
|
|
20
|
+
"label": "Run it",
|
|
21
|
+
"config": {},
|
|
22
|
+
"position": { "x": 0, "y": 0 }
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "customer",
|
|
26
|
+
"kind": "@particle-academy/stripe_customer",
|
|
27
|
+
"label": "Create the customer",
|
|
28
|
+
"config": {
|
|
29
|
+
"mode": "fake",
|
|
30
|
+
"email": "ada@example.test",
|
|
31
|
+
"name": "Ada Lovelace"
|
|
32
|
+
},
|
|
33
|
+
"position": { "x": 280, "y": 0 }
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "out",
|
|
37
|
+
"kind": "@particle-academy/output",
|
|
38
|
+
"label": "Result",
|
|
39
|
+
"config": {},
|
|
40
|
+
"position": { "x": 560, "y": 0 }
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"edges": [
|
|
44
|
+
{
|
|
45
|
+
"id": "start-customer",
|
|
46
|
+
"source": "start",
|
|
47
|
+
"target": "customer",
|
|
48
|
+
"sourceHandle": "out",
|
|
49
|
+
"targetHandle": "in"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "customer-out",
|
|
53
|
+
"source": "customer",
|
|
54
|
+
"target": "out",
|
|
55
|
+
"sourceHandle": "out",
|
|
56
|
+
"targetHandle": "in"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"initialInputs": {},
|
|
62
|
+
"runIdentity": {
|
|
63
|
+
"derivation": "\"lab\" + sha256(canonical json of input.schema)[0:8]",
|
|
64
|
+
"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."
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"expected": {
|
|
68
|
+
"ok": true,
|
|
69
|
+
"paths": {
|
|
70
|
+
"equals": {
|
|
71
|
+
"out.mode": "fake",
|
|
72
|
+
"out.data.object": "customer",
|
|
73
|
+
"out.data.email": "ada@example.test",
|
|
74
|
+
"out.data.name": "Ada Lovelace",
|
|
75
|
+
"out.data.created": 1767225600,
|
|
76
|
+
"out.data.livemode": false
|
|
77
|
+
},
|
|
78
|
+
"matches": {
|
|
79
|
+
"out.data.id": "^cus_fake_[0-9a-f]{12}$"
|
|
80
|
+
},
|
|
81
|
+
"deterministic": ["out.data.id"]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"notes": [
|
|
85
|
+
"`created: 1767225600` is a LITERAL in the provider's faker fixture, not a captured value. An engine fabricating plausible customer data cannot produce that number, and a faker that drifts stops producing it — which is what makes this row evidence rather than a snapshot.",
|
|
86
|
+
"`email` and `name` come from the node's CONFIG. Both are here because only one of them discriminates: the graph's email coincides with the faker fixture's own default, so a connector that ignored its config entirely would still publish the right email. `name` has no default. That was a hole in the authored golden, found by writing the discrimination probes.",
|
|
87
|
+
"`out.data.id` cannot be authored — it is drawn from the faker's seeded sequence — so it is pinned by SHAPE and by DETERMINISM instead of by value. The `_fake_` infix is deliberate: a faked id indistinguishable from a real one is the reassuring reading, and this repository exists because the reassuring reading is the one people take.",
|
|
88
|
+
"`deterministic` means two runs of the unchanged graph publish the same value AND every runner publishes the same value. Measured as `cus_fake_abaeeb55ae4a` on the PHP and Node lanes at the time of authoring; the literal is recorded here as evidence rather than asserted, because pinning it would couple the golden to the faker's seeding.",
|
|
89
|
+
"SOFTER CLAIM, measured and not asserted: `json_encode(outputs)` is byte-identical between the PHP and Node lanes for the WHOLE graph, envelope on the intermediate node included. It is recorded here rather than turned into an expectation because nothing documents `RunResult.outputs` as a stable cross-runtime surface, and an assertion would be making that promise on the engines' behalf. The lab that measured it had previously reported a divergence here and retracted it after writing the comparison down — the retraction is why this sits in notes."
|
|
90
|
+
],
|
|
91
|
+
"skip": {
|
|
92
|
+
"python": "fancy-stripe ships no `flow` module yet, so the `fancy_flow.nodes` entry point finds nothing to register. The Python executors are owner-ordered after the real fancy-connector-core Python package. The row is kept rather than removed: a removed row says the language is not part of the contract, a skipped one says it is and has not arrived."
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../schema/suite-manifest.schema.json",
|
|
3
|
+
"suite": "flow/connector-runs",
|
|
4
|
+
"title": "A connector node inside a running graph: the same WorkflowSchema in, the same published fields out",
|
|
5
|
+
"since": "0.21.0",
|
|
6
|
+
"caseFormat": "table",
|
|
7
|
+
"cases": "cases.json",
|
|
8
|
+
"contract": {
|
|
9
|
+
"function": "runConnectorGraph(schema: WorkflowSchema, runIdentity: RunIdentity) -> { ok: true, outputs: object } | { ok: false, error: string }",
|
|
10
|
+
"summary": "Import a WorkflowSchema v1 document STRICTLY against the HOST's kind registry — with a connector package's kinds and executors registered by that package's own registrar — run it with a supplied run identity, and return the run's outputs. Every connector node runs in fake mode, so the suite reaches no network by construction.",
|
|
11
|
+
"reference": "authored",
|
|
12
|
+
"referenceNote": "The expectations are read off the provider's own faker fixture (providers/stripe/provider/fixtures/customer-create.json in weaver.agi), NOT captured from any runtime. `created: 1767225600` is a literal in that fixture: an engine fabricating plausible customer data cannot produce it, and a faker that drifts stops producing it. `email` and `name` come from the node's CONFIG, which is how a connector proves it read the graph rather than its own defaults. `id` cannot be authored — it is drawn from the seeded sequence — so it is pinned by shape and by determinism instead. Authored by the connector lab (weaver) and landed here rather than held privately, so the lab and this suite cannot hold two goldens that disagree.",
|
|
13
|
+
"implementations": [
|
|
14
|
+
{
|
|
15
|
+
"language": "php",
|
|
16
|
+
"package": "particle-academy/stripe-php",
|
|
17
|
+
"symbol": "ParticleAcademy\\Stripe\\Flow\\StripeFlow::register(NodeKindRegistry, ExecutorRegistry)",
|
|
18
|
+
"engine": "particle-academy/fancy-flow-php >=0.51"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"language": "node",
|
|
22
|
+
"package": "@particle-academy/stripe-js",
|
|
23
|
+
"symbol": "STRIPE_RUNNABLE_KINDS from \"@particle-academy/stripe-js/flow\" (executor attached to each kind)",
|
|
24
|
+
"engine": "@particle-academy/fancy-flow >=0.69"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"language": "python",
|
|
28
|
+
"package": "fancy-stripe",
|
|
29
|
+
"symbol": "fancy_stripe.flow.register(kinds, executors), discovered through the fancy_flow.nodes entry point",
|
|
30
|
+
"engine": "fancy-flow >=0.19",
|
|
31
|
+
"status": "not-yet-implemented"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"notes": [
|
|
36
|
+
"WHY THIS IS NOT `flow/graph-runs`. That suite's contract specifies a LENIENT import, a LOCAL kind registry carrying only the built-in and structural kinds, and the built-in offline executors. A connector kind is none of those. A connector case dropped into graph-runs would not be red-until-an-implementation-catches-up — it would be red against a contract that excludes it, permanently, which is not what \"lands here first, red\" means. This suite exists to state the different prerequisite out loud: its runners CARRY the connector packages.",
|
|
37
|
+
|
|
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
|
+
|
|
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(canonical json)[0:8]`. 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
|
+
|
|
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
|
+
|
|
44
|
+
"ABSENT IS NOT NULL. A path that was never published FAILS its check rather than comparing equal to null. A runner that skipped the connector entirely and published nothing at `out` must not satisfy any expectation — and without this rule it would satisfy several, because a missing path and a null value are the same value in three of the four languages here.",
|
|
45
|
+
|
|
46
|
+
"STRICT EQUALITY ON TYPES. `\"1767225600\"` is not `1767225600`. A runtime that stringifies its numbers on the way out must fail, and one of the discrimination probes is exactly that mutation.",
|
|
47
|
+
|
|
48
|
+
"PYTHON IS RED ON PURPOSE and its row is kept rather than removed. `fancy-stripe` ships no `flow` module yet — the Python executors are owner-ordered after the real `fancy-connector-core` Python package — so the entry point the engine now discovers (`fancy_flow.nodes`, added in fancy-flow 0.19.0) finds nothing to register. A removed row would say the language is not part of the contract; a red one says it is and has not arrived. That is the shape \"lands here first, red\" is supposed to have.",
|
|
49
|
+
|
|
50
|
+
"DISCRIMINATION PROBES EXIST for this suite, in `tests/discrimination-connector-runs.test.ts`, and they are the reason the case asserts `name` as well as `email`. The graph's email happens to equal the faker fixture's own default, so a connector that ignored its config entirely would still publish the right email — `name` has no default and is what actually discriminates. That defect was in the authored golden before the probes were written, which is the argument for writing them at all."
|
|
51
|
+
]
|
|
52
|
+
}
|