@agent-cards/checkout 0.9.0 → 0.9.1
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/PREFLIGHT.md +6 -0
- package/examples/preflight/kernel-native/README.md +112 -0
- package/examples/preflight/kernel-native/documented-adapters.json +113 -0
- package/examples/preflight/kernel-native/inventory.json +224 -0
- package/examples/preflight/kernel-native/qualification.mjs +182 -0
- package/package.json +5 -4
package/PREFLIGHT.md
CHANGED
|
@@ -212,6 +212,12 @@ The JSON contract uses `schema_version: 1`. The catalog bundled with this releas
|
|
|
212
212
|
|
|
213
213
|
Kernel can integrate the JSON contract immediately and supply its validated native capabilities later. No deployed Agentcard endpoint or payment request is required for that work.
|
|
214
214
|
|
|
215
|
+
## Review native evidence
|
|
216
|
+
|
|
217
|
+
Use the [native qualification kit](./examples/preflight/kernel-native/README.md) to review every catalog processor before declaring native support. The kit records pinned Kernel documentation separately from runtime evidence and checks that a release profile names the tested native build.
|
|
218
|
+
|
|
219
|
+
The current inventory leaves every native runtime status `unknown`. Kernel documents five adapters; the remaining processors have no declaration in those sources. Documentation, client SDK versions, and direct SDK test results cannot establish native support.
|
|
220
|
+
|
|
215
221
|
## Interpret the result
|
|
216
222
|
|
|
217
223
|
| Status | Meaning | Next step |
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Qualify Kernel native coverage
|
|
2
|
+
|
|
3
|
+
You can detect a checkout's PSP before entering card details with `@agent-cards/checkout@0.9.0`. Kernel native support needs a separate profile for the native adapter that actually runs the checkout. The direct SDK's coverage does not establish Kernel's native coverage.
|
|
4
|
+
|
|
5
|
+
No qualified native profile accompanies this kit. The inventory covers all 23 PSPs in the checkout catalog and reports `unknown` for every native runtime status. Kernel's pinned documentation lists Stripe, Shopify, Square, Recurly, and Razorpay. The other processors are unestablished, rather than explicitly unsupported. See the endpoint constraints and source hashes in [documented-adapters.json](./documented-adapters.json).
|
|
6
|
+
|
|
7
|
+
## Use the detector now
|
|
8
|
+
|
|
9
|
+
Install the published detector in your application:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save-exact @agent-cards/checkout@0.9.0
|
|
13
|
+
node node_modules/@agent-cards/checkout/examples/preflight/classify-kernel.mjs
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The example reads synthetic Stripe observations and an empty native profile. The detector identifies Stripe and returns `unknown` for native support. The example needs no credentials and submits no payment. Follow [PREFLIGHT.md](../../../PREFLIGHT.md) to collect observations from a browser or supply normalized JSON from Kernel.
|
|
17
|
+
|
|
18
|
+
The qualification commands below are available in this source kit. The published `0.9.0` detector does not contain the new qualification commands. From the checkout package directory, build the source kit with Node.js 22 or later:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd apps/agent-cards/packages/checkout
|
|
22
|
+
npm run build
|
|
23
|
+
node examples/preflight/kernel-native/qualification.mjs inventory
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The inventory pairs each canonical PSP and mode with its documentation status. `documentation: "documented"` records a statement in the pinned source; `status: "unknown"` records the absence of qualified runtime evidence. The documentation baseline grants no permission to send card data anywhere.
|
|
27
|
+
|
|
28
|
+
The checker compares PSP membership and payment modes with the SDK's separate request registry before comparing the saved inventory. Removing a processor from both the preflight catalog and the inventory still fails this check.
|
|
29
|
+
|
|
30
|
+
When an installed SDK package includes this kit, run the commands directly from that package:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node node_modules/@agent-cards/checkout/examples/preflight/kernel-native/qualification.mjs inventory --check
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Identify the native build
|
|
37
|
+
|
|
38
|
+
Kernel must provide access to the native adapter source and build, or a test deployment whose adapter identity can be independently checked. Record the actual adapter's version and SHA-256 artifact digest in a runtime JSON file with the fields `version` and `artifact_sha256`.
|
|
39
|
+
|
|
40
|
+
Obtain the runtime identity from the deployed artifact or a trusted deployment record. Do not copy the identity from the test report, use the client SDK's npm version, or use the CLI version. Public API and client documentation do not currently supply a native adapter build identity.
|
|
41
|
+
|
|
42
|
+
Kernel must also provide a way to exercise that build with the intended payment environment. A documented sandbox processor hostname does not establish that the native card API permits sandbox testing. Agree on the supported test environment before preparing an execution run.
|
|
43
|
+
|
|
44
|
+
## Declare only tested coverage
|
|
45
|
+
|
|
46
|
+
Start from [kernel-profile.empty.json](../kernel-profile.empty.json). Set `integration.version` to the independently identified native adapter version. Keep unqualified declarations absent so that the detector returns `unknown`.
|
|
47
|
+
|
|
48
|
+
A `processor_entries` declaration covers a PSP, mode, and scenario. A flow entry in `entries` also binds the specific flow, operation, and operation version. A successful test of one checkout flow cannot justify processor-wide coverage. The reviewer must check that the tests cover the whole declaration being released.
|
|
49
|
+
|
|
50
|
+
Every declaration in this kit needs explicit `limitations`, including supported declarations. Use `unsupported` only for an established native exclusion, with evidence and a useful explanation. Shared Vault exclusions still apply; a profile cannot override them. The checker refuses supported subscription renewals and hosted-form scenarios other than `one_time`.
|
|
51
|
+
|
|
52
|
+
A profile is trusted application configuration. Never accept a profile from a merchant page. Continue to supply the actual running adapter version separately when calling `assessCheckoutSupport`.
|
|
53
|
+
|
|
54
|
+
## Test the native path
|
|
55
|
+
|
|
56
|
+
Run each declared case through Kernel's native Agentcard interception and approval path. A browser connected over CDP while the direct checkout SDK performs interception tests the direct SDK. Synthetic fixtures and unit tests verify the checker; they do not qualify a native adapter.
|
|
57
|
+
|
|
58
|
+
Each supported declaration requires the following passed checks and retained evidence:
|
|
59
|
+
|
|
60
|
+
| Check ID | What the evidence must establish |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `native_recognition` | The identified native build recognizes the intended request layout and processor operation. |
|
|
63
|
+
| `approval_pause_resume` | The payment pauses for approval and resumes once after approval. |
|
|
64
|
+
| `processor_replay` | The approved request reaches the processor through the native path and its response returns to the merchant. |
|
|
65
|
+
| `merchant_confirmation` | The merchant confirms the intended payment outcome; approval or token creation alone is insufficient. |
|
|
66
|
+
| `unrecognized_request_passthrough` | A request outside the declared native recognition rules follows the documented unrecognized-request behavior. |
|
|
67
|
+
| `decline_or_error` | A processor refusal or error reaches the caller as a failure rather than a successful purchase. |
|
|
68
|
+
| `no_duplicate_submission` | Timeout, retry, and repeated approval handling do not produce duplicate submissions. |
|
|
69
|
+
| `authentication_or_explicit_exclusion` | Required authentication completes, or the profile states and the evidence establishes the excluded authentication cases. |
|
|
70
|
+
|
|
71
|
+
Supported `save_card` declarations also require `stored_card_consent` and `merchant_card_saved`. Supported `subscription_initial` declarations also require `stored_card_consent` and `merchant_subscription_confirmation`. An unsupported declaration requires `native_exclusion` instead of the support checks.
|
|
72
|
+
|
|
73
|
+
Retain sanitized evidence files without card details, credentials, or approval secrets. Store evidence under one directory and reference each file by a relative path and its SHA-256 digest.
|
|
74
|
+
|
|
75
|
+
## Bind the execution report
|
|
76
|
+
|
|
77
|
+
The report records `schema_version: 1`, `execution: "kernel_native"`, the adapter's `version` and `artifact_sha256`, the harness commit, and the reviewer's identity. `harness_commit` must be a full 40-character Git commit. `reviewed_by` identifies the person accountable for checking the native execution evidence.
|
|
78
|
+
|
|
79
|
+
The report's `catalog_sha256` and `profile_sha256` bind the exact catalog and profile JSON. Compute both using the exported `digest` function in `qualification.mjs`. The function sorts object keys recursively before hashing JSON; a hash of a pretty-printed file is different. Evidence digests hash the file's exact bytes.
|
|
80
|
+
|
|
81
|
+
The report contains exactly one `claims` entry per profile declaration. Each entry has a `key` and `checks`. Use these key formats:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
processor:<psp>:<mode>:<scenario>
|
|
85
|
+
flow:<flow>:<operation>:<operation_version>:<mode>:<scenario>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Each check contains `id`, `outcome: "passed"`, and `evidence: { "path": "relative-file", "sha256": "file-digest" }`. Failed, missing, duplicate, and extra checks prevent qualification. The checker also rejects evidence that escapes the evidence directory or differs from its retained digest.
|
|
89
|
+
|
|
90
|
+
## Check a candidate release
|
|
91
|
+
|
|
92
|
+
After a reviewed native execution run has produced the profile, report, independent runtime identity, and retained evidence, run:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
node examples/preflight/kernel-native/qualification.mjs release \
|
|
96
|
+
--profile /tmp/kernel-native-validation/profile.json \
|
|
97
|
+
--report /tmp/kernel-native-validation/report.json \
|
|
98
|
+
--runtime /tmp/kernel-native-validation/runtime.json \
|
|
99
|
+
--evidence-dir /tmp/kernel-native-validation/evidence
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The command prints `status: "qualified"` only when the supplied records satisfy the checks. The command does not publish a package, deploy the adapter, or install the profile in an application. Requalify after a change to the native build, profile, or catalog.
|
|
103
|
+
|
|
104
|
+
The checker verifies consistency and retained file bytes. A matching digest does not prove that a native execution happened, that the stated deployment identity is truthful, or that the evidence supports every declared case. A trusted reviewer must establish those facts before accepting the profile.
|
|
105
|
+
|
|
106
|
+
Without a complete candidate, the command exits with status `1`. The following stderr was captured by running `node examples/preflight/kernel-native/qualification.mjs release` without its required files:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
Native qualification failed. Check the arguments, catalog, build identity, profile, and retained evidence against README.md.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Supply the completed files with the command above. Keep native results `unknown` until the execution evidence and independent build identity are available.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"baseline_version": "kernel-native-docs-2026-09-11.1",
|
|
4
|
+
"reviewed_at": "2026-09-11",
|
|
5
|
+
"evidence_level": "documentation_only",
|
|
6
|
+
"native_build": null,
|
|
7
|
+
"sources": [
|
|
8
|
+
{
|
|
9
|
+
"id": "kernel-payments-overview",
|
|
10
|
+
"repository": "kernel/docs",
|
|
11
|
+
"commit": "fd81f0d8c7ff33e81d6feae2dd314347b6ebb355",
|
|
12
|
+
"path": "integrations/payments/overview.mdx",
|
|
13
|
+
"url": "https://github.com/kernel/docs/blob/fd81f0d8c7ff33e81d6feae2dd314347b6ebb355/integrations/payments/overview.mdx",
|
|
14
|
+
"sha256": "a6c3633ef6aa8cb09deb210d5a36cccb2f3561cffa470ccb5cf2f7de5e7b4ab9"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "kernel-agentcard-guide",
|
|
18
|
+
"repository": "kernel/docs",
|
|
19
|
+
"commit": "fd81f0d8c7ff33e81d6feae2dd314347b6ebb355",
|
|
20
|
+
"path": "integrations/payments/agentcard.mdx",
|
|
21
|
+
"url": "https://github.com/kernel/docs/blob/fd81f0d8c7ff33e81d6feae2dd314347b6ebb355/integrations/payments/agentcard.mdx",
|
|
22
|
+
"sha256": "0075bd44e45fe43147f63348a01bf6e9bb6dbdd6eb7c33eea46f09fcfc778a7b"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"documented_adapters": [
|
|
26
|
+
{
|
|
27
|
+
"psp": "stripe",
|
|
28
|
+
"method": "POST",
|
|
29
|
+
"scheme": "https",
|
|
30
|
+
"body_encoding": "form",
|
|
31
|
+
"url_patterns": [
|
|
32
|
+
"https://api.stripe.com/v1/payment_methods",
|
|
33
|
+
"https://api.stripe.com/v1/tokens",
|
|
34
|
+
"https://api.stripe.com/v1/payment_intents/{id}/confirm",
|
|
35
|
+
"https://api.stripe.com/v1/payment_pages/{id}/confirm"
|
|
36
|
+
],
|
|
37
|
+
"source_id": "kernel-payments-overview",
|
|
38
|
+
"limitations": [
|
|
39
|
+
"Other layouts, payment scenarios, and authentication flows are unqualified."
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"psp": "shopify",
|
|
44
|
+
"method": "POST",
|
|
45
|
+
"scheme": "https",
|
|
46
|
+
"body_encoding": "json",
|
|
47
|
+
"url_patterns": [
|
|
48
|
+
"https://checkout.pci.shopifyinc.com/sessions",
|
|
49
|
+
"https://deposit.<region>.shopifycs.com/sessions"
|
|
50
|
+
],
|
|
51
|
+
"source_id": "kernel-payments-overview",
|
|
52
|
+
"limitations": [
|
|
53
|
+
"Card sessions only; this list does not qualify native checkout GraphQL.",
|
|
54
|
+
"Allowed regions are not enumerated."
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"psp": "square",
|
|
59
|
+
"method": "POST",
|
|
60
|
+
"scheme": "https",
|
|
61
|
+
"body_encoding": "json",
|
|
62
|
+
"url_patterns": [
|
|
63
|
+
"https://pci-connect.squareup.com/v2/card-nonce",
|
|
64
|
+
"https://pci-connect.squareupsandbox.com/v2/card-nonce"
|
|
65
|
+
],
|
|
66
|
+
"source_id": "kernel-payments-overview",
|
|
67
|
+
"limitations": [
|
|
68
|
+
"A listed sandbox hostname does not establish sandbox card-item availability.",
|
|
69
|
+
"CORS, preparation, and replay timing remain unqualified."
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"psp": "recurly",
|
|
74
|
+
"method": "POST",
|
|
75
|
+
"scheme": "https",
|
|
76
|
+
"body_encoding": "form",
|
|
77
|
+
"url_patterns": [
|
|
78
|
+
"https://api.recurly.com/js/v1/token",
|
|
79
|
+
"https://api.eu.recurly.com/js/v1/token"
|
|
80
|
+
],
|
|
81
|
+
"source_id": "kernel-payments-overview",
|
|
82
|
+
"limitations": [
|
|
83
|
+
"Form POST only; JSONP, saved-card use, and subscriptions remain unqualified."
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"psp": "razorpay",
|
|
88
|
+
"method": "POST",
|
|
89
|
+
"scheme": "https",
|
|
90
|
+
"body_encoding": "form",
|
|
91
|
+
"url_patterns": [
|
|
92
|
+
"https://api.razorpay.com/v1/payments/create/ajax",
|
|
93
|
+
"https://api.razorpay.com/v1/standard_checkout/payments/create/ajax"
|
|
94
|
+
],
|
|
95
|
+
"source_id": "kernel-payments-overview",
|
|
96
|
+
"limitations": [
|
|
97
|
+
"Tokenization-only layouts and authentication continuation remain unqualified."
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"limitations": [
|
|
102
|
+
"Documentation claims only; this baseline is neither runtime-test evidence nor a deployable capability profile.",
|
|
103
|
+
"Kernel lists these five adapters for Link and Agentcard. Unlisted processors remain unestablished, not explicitly unsupported.",
|
|
104
|
+
"Recognition requires every alias and the expected HTTPS method, destination, content type, and card-field layout. Complete body schemas are unavailable.",
|
|
105
|
+
"Kernel says non-Stripe coverage needs further tests with actual SDKs and hosted checkouts.",
|
|
106
|
+
"Encrypted, differently structured, or unrecognized requests bypass native handoff.",
|
|
107
|
+
"Patterns describe the documentation; they grant no payment admission or native execution capability.",
|
|
108
|
+
"Agentcard begins authorization after recognition; no separate authorize operation is advertised.",
|
|
109
|
+
"Ready item state, approval, and replay results do not confirm a paid merchant order.",
|
|
110
|
+
"No adapter build identity or conformance results accompany this table. Client SDK and CLI versions identify different artifacts.",
|
|
111
|
+
"Direct SDK tests cannot qualify Kernel native support. Release requires evidence for the actual native build."
|
|
112
|
+
]
|
|
113
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"catalog_version": "2026-09-11.2",
|
|
4
|
+
"catalog_sha256": "367915b082fe3360cd6a9115feaeb8edeb0e8493388cee88557c8a5a12eff821",
|
|
5
|
+
"baseline_version": "kernel-native-docs-2026-09-11.1",
|
|
6
|
+
"baseline_sha256": "d86722f49633d8223fccae54cd3d485df4375aee06aa156a273cc689b7c8507c",
|
|
7
|
+
"native_build": null,
|
|
8
|
+
"processors": [
|
|
9
|
+
{
|
|
10
|
+
"psp": "shopify",
|
|
11
|
+
"mode": "token",
|
|
12
|
+
"documentation": "documented",
|
|
13
|
+
"status": "unknown",
|
|
14
|
+
"limitations": [
|
|
15
|
+
"Card sessions only; this list does not qualify native checkout GraphQL.",
|
|
16
|
+
"Allowed regions are not enumerated.",
|
|
17
|
+
"Native runtime evidence is missing."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"psp": "stripe",
|
|
22
|
+
"mode": "token",
|
|
23
|
+
"documentation": "documented",
|
|
24
|
+
"status": "unknown",
|
|
25
|
+
"limitations": [
|
|
26
|
+
"Other layouts, payment scenarios, and authentication flows are unqualified.",
|
|
27
|
+
"Native runtime evidence is missing."
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"psp": "braintree",
|
|
32
|
+
"mode": "token",
|
|
33
|
+
"documentation": "not_documented",
|
|
34
|
+
"status": "unknown",
|
|
35
|
+
"limitations": [
|
|
36
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"psp": "checkout_com",
|
|
41
|
+
"mode": "token",
|
|
42
|
+
"documentation": "not_documented",
|
|
43
|
+
"status": "unknown",
|
|
44
|
+
"limitations": [
|
|
45
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"psp": "adyen",
|
|
50
|
+
"mode": "cse",
|
|
51
|
+
"documentation": "not_documented",
|
|
52
|
+
"status": "unknown",
|
|
53
|
+
"limitations": [
|
|
54
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"psp": "tranzila",
|
|
59
|
+
"mode": "hosted_form",
|
|
60
|
+
"documentation": "not_documented",
|
|
61
|
+
"status": "unknown",
|
|
62
|
+
"limitations": [
|
|
63
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"psp": "square",
|
|
68
|
+
"mode": "token",
|
|
69
|
+
"documentation": "documented",
|
|
70
|
+
"status": "unknown",
|
|
71
|
+
"limitations": [
|
|
72
|
+
"A listed sandbox hostname does not establish sandbox card-item availability.",
|
|
73
|
+
"CORS, preparation, and replay timing remain unqualified.",
|
|
74
|
+
"Native runtime evidence is missing."
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"psp": "authorize_net",
|
|
79
|
+
"mode": "token",
|
|
80
|
+
"documentation": "not_documented",
|
|
81
|
+
"status": "unknown",
|
|
82
|
+
"limitations": [
|
|
83
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"psp": "worldpay",
|
|
88
|
+
"mode": "token",
|
|
89
|
+
"documentation": "not_documented",
|
|
90
|
+
"status": "unknown",
|
|
91
|
+
"limitations": [
|
|
92
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"psp": "nuvei",
|
|
97
|
+
"mode": "token",
|
|
98
|
+
"documentation": "not_documented",
|
|
99
|
+
"status": "unknown",
|
|
100
|
+
"limitations": [
|
|
101
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"psp": "airwallex",
|
|
106
|
+
"mode": "token",
|
|
107
|
+
"documentation": "not_documented",
|
|
108
|
+
"status": "unknown",
|
|
109
|
+
"limitations": [
|
|
110
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"psp": "rapyd",
|
|
115
|
+
"mode": "token",
|
|
116
|
+
"documentation": "not_documented",
|
|
117
|
+
"status": "unknown",
|
|
118
|
+
"limitations": [
|
|
119
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"psp": "dlocal",
|
|
124
|
+
"mode": "token",
|
|
125
|
+
"documentation": "not_documented",
|
|
126
|
+
"status": "unknown",
|
|
127
|
+
"limitations": [
|
|
128
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"psp": "ebanx",
|
|
133
|
+
"mode": "token",
|
|
134
|
+
"documentation": "not_documented",
|
|
135
|
+
"status": "unknown",
|
|
136
|
+
"limitations": [
|
|
137
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"psp": "mercado_pago",
|
|
142
|
+
"mode": "token",
|
|
143
|
+
"documentation": "not_documented",
|
|
144
|
+
"status": "unknown",
|
|
145
|
+
"limitations": [
|
|
146
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"psp": "payu",
|
|
151
|
+
"mode": "token",
|
|
152
|
+
"documentation": "not_documented",
|
|
153
|
+
"status": "unknown",
|
|
154
|
+
"limitations": [
|
|
155
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"psp": "razorpay",
|
|
160
|
+
"mode": "token",
|
|
161
|
+
"documentation": "documented",
|
|
162
|
+
"status": "unknown",
|
|
163
|
+
"limitations": [
|
|
164
|
+
"Tokenization-only layouts and authentication continuation remain unqualified.",
|
|
165
|
+
"Native runtime evidence is missing."
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"psp": "mollie",
|
|
170
|
+
"mode": "token",
|
|
171
|
+
"documentation": "not_documented",
|
|
172
|
+
"status": "unknown",
|
|
173
|
+
"limitations": [
|
|
174
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"psp": "paysafe",
|
|
179
|
+
"mode": "token",
|
|
180
|
+
"documentation": "not_documented",
|
|
181
|
+
"status": "unknown",
|
|
182
|
+
"limitations": [
|
|
183
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"psp": "recurly",
|
|
188
|
+
"mode": "token",
|
|
189
|
+
"documentation": "documented",
|
|
190
|
+
"status": "unknown",
|
|
191
|
+
"limitations": [
|
|
192
|
+
"Form POST only; JSONP, saved-card use, and subscriptions remain unqualified.",
|
|
193
|
+
"Native runtime evidence is missing."
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"psp": "moneris",
|
|
198
|
+
"mode": "token",
|
|
199
|
+
"documentation": "not_documented",
|
|
200
|
+
"status": "unknown",
|
|
201
|
+
"limitations": [
|
|
202
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"psp": "bambora",
|
|
207
|
+
"mode": "token",
|
|
208
|
+
"documentation": "not_documented",
|
|
209
|
+
"status": "unknown",
|
|
210
|
+
"limitations": [
|
|
211
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"psp": "global_payments",
|
|
216
|
+
"mode": "token",
|
|
217
|
+
"documentation": "not_documented",
|
|
218
|
+
"status": "unknown",
|
|
219
|
+
"limitations": [
|
|
220
|
+
"No adapter declaration was found in the pinned documentation. Native runtime evidence is missing."
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Maintainer tooling. Reports are trusted review inputs, never merchant-page inputs.
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
4
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { BUILTIN_REGISTRY } from '@agent-cards/checkout';
|
|
7
|
+
import { assessCheckoutSupport, getCheckoutPreflightCatalog } from '@agent-cards/checkout/preflight';
|
|
8
|
+
|
|
9
|
+
export const SUPPORTED_CHECKS = Object.freeze([
|
|
10
|
+
'native_recognition', 'approval_pause_resume', 'processor_replay',
|
|
11
|
+
'merchant_confirmation', 'unrecognized_request_passthrough', 'decline_or_error',
|
|
12
|
+
'no_duplicate_submission', 'authentication_or_explicit_exclusion',
|
|
13
|
+
]);
|
|
14
|
+
const SCENARIOS = ['one_time', 'save_card', 'subscription_initial', 'subscription_renewal'];
|
|
15
|
+
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
16
|
+
const canonical = value => Array.isArray(value) ? value.map(canonical)
|
|
17
|
+
: value !== null && typeof value === 'object'
|
|
18
|
+
? Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])])) : value;
|
|
19
|
+
export const digest = value => sha256(JSON.stringify(canonical(value)));
|
|
20
|
+
const fail = message => { throw new Error(message); };
|
|
21
|
+
const requireThat = (condition, message) => { if (!condition) fail(message); };
|
|
22
|
+
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
23
|
+
const id = value => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._+-]{0,119}$/.test(value);
|
|
24
|
+
const hash = value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
|
|
25
|
+
const text = value => typeof value === 'string' && value.trim().length > 0 && value.length <= 256 && !/[\x00-\x1f]/.test(value);
|
|
26
|
+
const keys = (value, required, optional = []) => record(value)
|
|
27
|
+
&& required.every(key => Object.hasOwn(value, key))
|
|
28
|
+
&& Object.keys(value).every(key => [...required, ...optional].includes(key));
|
|
29
|
+
|
|
30
|
+
function requireCompleteCatalog(catalog) {
|
|
31
|
+
requireThat(record(catalog) && catalog.schema_version === 1 && Array.isArray(catalog.processors), 'Invalid catalog.');
|
|
32
|
+
// This projection comes from request recognition, independently of preflight's
|
|
33
|
+
// capability projection. Regenerating a reduced inventory cannot hide a PSP.
|
|
34
|
+
const expected = new Map(BUILTIN_REGISTRY.map(({ psp, mode = 'token' }) => [psp, mode]));
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
for (const processor of catalog.processors) {
|
|
37
|
+
requireThat(record(processor) && !seen.has(processor.psp) && expected.has(processor.psp)
|
|
38
|
+
&& expected.get(processor.psp) === processor.mode, 'Canonical processor membership or mode mismatch.');
|
|
39
|
+
seen.add(processor.psp);
|
|
40
|
+
}
|
|
41
|
+
requireThat(seen.size === expected.size, 'The catalog omits a canonical processor.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function buildInventory(catalog, baseline) {
|
|
45
|
+
requireCompleteCatalog(catalog);
|
|
46
|
+
requireThat(baseline.evidence_level === 'documentation_only' && baseline.native_build === null
|
|
47
|
+
&& Array.isArray(baseline.documented_adapters), 'Expected a documentation-only baseline.');
|
|
48
|
+
const documented = new Map(baseline.documented_adapters.map(adapter => [adapter.psp, adapter]));
|
|
49
|
+
requireThat(documented.size === baseline.documented_adapters.length
|
|
50
|
+
&& [...documented.keys()].every(psp => catalog.processors.some(processor => processor.psp === psp)), 'Baseline processor drift.');
|
|
51
|
+
return {
|
|
52
|
+
schema_version: 1, catalog_version: catalog.catalog_version, catalog_sha256: digest(catalog),
|
|
53
|
+
baseline_version: baseline.baseline_version, baseline_sha256: digest(baseline), native_build: null,
|
|
54
|
+
processors: catalog.processors.map(({ psp, mode }) => ({
|
|
55
|
+
psp, mode, documentation: documented.has(psp) ? 'documented' : 'not_documented', status: 'unknown',
|
|
56
|
+
limitations: documented.has(psp) ? [...documented.get(psp).limitations, 'Native runtime evidence is missing.']
|
|
57
|
+
: ['No adapter declaration was found in the pinned documentation. Native runtime evidence is missing.'],
|
|
58
|
+
})),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function profileClaims(catalog, profile, now) {
|
|
63
|
+
requireThat(keys(profile, ['schema_version', 'profile_version', 'catalog_version', 'integration', 'entries'], ['processor_entries', 'expires_at'])
|
|
64
|
+
&& profile.schema_version === 1 && id(profile.profile_version)
|
|
65
|
+
&& keys(profile.integration, ['id', 'version']) && profile.integration.id === 'kernel_native'
|
|
66
|
+
&& id(profile.integration.version), 'Invalid native profile.');
|
|
67
|
+
requireThat(profile.catalog_version === catalog.catalog_version, 'Profile catalog mismatch.');
|
|
68
|
+
// Ask the installed classifier to validate its own contract, including dates.
|
|
69
|
+
// Empty observations exercise profile validation without claiming a checkout.
|
|
70
|
+
const assessment = assessCheckoutSupport({ schema_version: 1, catalog_version: catalog.catalog_version,
|
|
71
|
+
signals: [], observation: { complete: true, truncated: false, reasons: [] } },
|
|
72
|
+
{ integration: profile.integration, profile, now });
|
|
73
|
+
requireThat(assessment.reason_codes.every(code => ['psp_not_detected', 'checkout_flow_unidentified'].includes(code)),
|
|
74
|
+
'The installed classifier rejected the profile, catalog, or current time.');
|
|
75
|
+
requireThat(Array.isArray(profile.entries) && profile.entries.length <= 256
|
|
76
|
+
&& (profile.processor_entries === undefined || (Array.isArray(profile.processor_entries) && profile.processor_entries.length <= 256)), 'Invalid profile declarations.');
|
|
77
|
+
const claims = new Map();
|
|
78
|
+
const add = (entry, kind) => {
|
|
79
|
+
const isFlow = kind === 'flow';
|
|
80
|
+
const required = isFlow ? ['flow', 'operation', 'operation_version', 'mode', 'scenario', 'status', 'limitations']
|
|
81
|
+
: ['psp', 'mode', 'scenario', 'status', 'limitations'];
|
|
82
|
+
requireThat(keys(entry, required) && SCENARIOS.includes(entry.scenario)
|
|
83
|
+
&& ['supported', 'unsupported'].includes(entry.status)
|
|
84
|
+
&& Array.isArray(entry.limitations) && entry.limitations.length > 0 && entry.limitations.length <= 16
|
|
85
|
+
&& entry.limitations.every(text), 'Each declaration needs a valid status, scenario, and explicit limitations.');
|
|
86
|
+
const definition = isFlow ? catalog.flows.find(flow => flow.id === entry.flow)
|
|
87
|
+
: catalog.processors.find(processor => processor.psp === entry.psp);
|
|
88
|
+
requireThat(definition && definition.mode === entry.mode
|
|
89
|
+
&& (!isFlow || (definition.operation === entry.operation && definition.operation_version === entry.operation_version)), 'Unknown declaration or operation/mode mismatch.');
|
|
90
|
+
if (entry.status === 'supported') {
|
|
91
|
+
requireThat(definition.server_constraint !== 'unsupported' && entry.scenario !== 'subscription_renewal'
|
|
92
|
+
&& !(entry.mode === 'hosted_form' && entry.scenario !== 'one_time'), 'Shared Vault exclusion cannot be overridden.');
|
|
93
|
+
}
|
|
94
|
+
const key = isFlow ? `flow:${entry.flow}:${entry.operation}:${entry.operation_version}:${entry.mode}:${entry.scenario}`
|
|
95
|
+
: `processor:${entry.psp}:${entry.mode}:${entry.scenario}`;
|
|
96
|
+
requireThat(!claims.has(key), 'Duplicate profile declaration.');
|
|
97
|
+
const checks = entry.status === 'supported' ? [...SUPPORTED_CHECKS] : ['native_exclusion'];
|
|
98
|
+
if (entry.status === 'supported' && ['save_card', 'subscription_initial'].includes(entry.scenario)) {
|
|
99
|
+
checks.push('stored_card_consent', entry.scenario === 'save_card' ? 'merchant_card_saved' : 'merchant_subscription_confirmation');
|
|
100
|
+
}
|
|
101
|
+
claims.set(key, checks);
|
|
102
|
+
};
|
|
103
|
+
profile.entries.forEach(entry => add(entry, 'flow'));
|
|
104
|
+
(profile.processor_entries ?? []).forEach(entry => add(entry, 'processor'));
|
|
105
|
+
requireThat(claims.size > 0, 'An empty profile is an integration draft, not a qualified release.');
|
|
106
|
+
return claims;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function verifyEvidence(evidence, directory) {
|
|
110
|
+
requireThat(keys(evidence, ['path', 'sha256']) && typeof evidence.path === 'string'
|
|
111
|
+
&& evidence.path.length > 0 && evidence.path.length <= 1024 && !/[\x00-\x1f\\]/.test(evidence.path)
|
|
112
|
+
&& !isAbsolute(evidence.path) && !evidence.path.split('/').includes('..') && hash(evidence.sha256), 'Invalid evidence path or digest.');
|
|
113
|
+
const actual = await realpath(resolve(directory, evidence.path));
|
|
114
|
+
const inside = relative(directory, actual);
|
|
115
|
+
requireThat(inside !== '' && inside !== '..' && !inside.startsWith(`..${sep}`) && !isAbsolute(inside), 'Evidence escapes its directory.');
|
|
116
|
+
requireThat(sha256(await readFile(actual)) === evidence.sha256, 'Evidence digest mismatch.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Validates a trusted attestation and its retained files, not the truth of a test run. */
|
|
120
|
+
export async function verifyRelease({ catalog, profile, report, runtime, evidenceDirectory, now = new Date().toISOString() }) {
|
|
121
|
+
requireCompleteCatalog(catalog);
|
|
122
|
+
const claims = profileClaims(catalog, profile, now);
|
|
123
|
+
requireThat(keys(runtime, ['version', 'artifact_sha256']) && id(runtime.version) && hash(runtime.artifact_sha256), 'Actual native build identity is required.');
|
|
124
|
+
requireThat(keys(report, ['schema_version', 'execution', 'adapter', 'catalog_sha256', 'profile_sha256', 'harness_commit', 'reviewed_by', 'claims'])
|
|
125
|
+
&& report.schema_version === 1 && report.execution === 'kernel_native'
|
|
126
|
+
&& keys(report.adapter, ['version', 'artifact_sha256'])
|
|
127
|
+
&& typeof report.harness_commit === 'string' && /^[a-f0-9]{40}$/.test(report.harness_commit)
|
|
128
|
+
&& text(report.reviewed_by) && Array.isArray(report.claims), 'A reviewed native execution report is required.');
|
|
129
|
+
requireThat(report.adapter.version === runtime.version && report.adapter.artifact_sha256 === runtime.artifact_sha256
|
|
130
|
+
&& profile.integration.version === runtime.version, 'Native build mismatch.');
|
|
131
|
+
requireThat(report.catalog_sha256 === digest(catalog), 'Catalog evidence drift. Requalify this catalog.');
|
|
132
|
+
requireThat(report.profile_sha256 === digest(profile), 'Profile evidence drift. Requalify these declarations.');
|
|
133
|
+
requireThat(report.claims.length === claims.size, 'Evidence must cover exactly the declared claims.');
|
|
134
|
+
const directory = await realpath(evidenceDirectory);
|
|
135
|
+
const seen = new Set();
|
|
136
|
+
for (const claim of report.claims) {
|
|
137
|
+
requireThat(keys(claim, ['key', 'checks']) && claims.has(claim.key) && !seen.has(claim.key)
|
|
138
|
+
&& Array.isArray(claim.checks), 'Unknown or duplicate evidence claim.');
|
|
139
|
+
seen.add(claim.key);
|
|
140
|
+
const expected = claims.get(claim.key);
|
|
141
|
+
requireThat(claim.checks.length === expected.length, 'Missing or extra native checks.');
|
|
142
|
+
const checks = new Set();
|
|
143
|
+
for (const check of claim.checks) {
|
|
144
|
+
requireThat(keys(check, ['id', 'outcome', 'evidence']) && expected.includes(check.id)
|
|
145
|
+
&& !checks.has(check.id) && check.outcome === 'passed', 'Missing, failed, or duplicate native check.');
|
|
146
|
+
checks.add(check.id);
|
|
147
|
+
await verifyEvidence(check.evidence, directory);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { status: 'qualified', integration: profile.integration, profile_sha256: digest(profile) };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function main(args) {
|
|
154
|
+
const catalog = getCheckoutPreflightCatalog();
|
|
155
|
+
const readJSON = async path => JSON.parse(await readFile(path, 'utf8'));
|
|
156
|
+
if (args[0] === 'inventory' && (args.length === 1 || (args.length === 2 && args[1] === '--check'))) {
|
|
157
|
+
const inventory = buildInventory(catalog, await readJSON(new URL('./documented-adapters.json', import.meta.url)));
|
|
158
|
+
if (args[1] === '--check') {
|
|
159
|
+
requireThat(digest(inventory) === digest(await readJSON(new URL('./inventory.json', import.meta.url))), 'Inventory drift. Review the catalog and documentation before updating inventory.json.');
|
|
160
|
+
console.log('Native inventory matches the reviewed catalog and documentation. Runtime support remains unknown.');
|
|
161
|
+
} else console.log(JSON.stringify(inventory, null, 2));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const flags = ['--profile', '--report', '--runtime', '--evidence-dir'];
|
|
165
|
+
requireThat(args[0] === 'release' && args.length === 9, 'Use inventory [--check] or release --profile JSON --report JSON --runtime JSON --evidence-dir DIRECTORY.');
|
|
166
|
+
const values = {};
|
|
167
|
+
for (let index = 1; index < args.length; index += 2) {
|
|
168
|
+
requireThat(flags.includes(args[index]) && !Object.hasOwn(values, args[index]) && args[index + 1], 'Invalid or duplicate release option.');
|
|
169
|
+
values[args[index]] = args[index + 1];
|
|
170
|
+
}
|
|
171
|
+
const result = await verifyRelease({ catalog, profile: await readJSON(values['--profile']), report: await readJSON(values['--report']),
|
|
172
|
+
runtime: await readJSON(values['--runtime']), evidenceDirectory: values['--evidence-dir'] });
|
|
173
|
+
console.log(JSON.stringify(result, null, 2));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
|
|
177
|
+
main(process.argv.slice(2)).catch(() => {
|
|
178
|
+
// Paths/reports may contain private deployment metadata. Keep failures out of CLI logs.
|
|
179
|
+
console.error('Native qualification failed. Check the arguments, catalog, build identity, profile, and retained evidence against README.md.');
|
|
180
|
+
process.exitCode = 1;
|
|
181
|
+
});
|
|
182
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-cards/checkout",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Let browser agents pay with the user's own card, without your infrastructure ever touching card data.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,13 +27,14 @@
|
|
|
27
27
|
"_comment_build": "The public SDK keeps zero runtime dependencies. Its build vendors deterministic metadata and substitution artifacts from the internal payment core; TypeScript remains pinned.",
|
|
28
28
|
"build": "node ../payment-core/scripts/build.mjs && node scripts/generate-payment-core.mjs && npx -y -p typescript@5.9.3 tsc && node scripts/generate-preflight-contract.mjs",
|
|
29
29
|
"prepublishOnly": "pnpm build",
|
|
30
|
-
"test": "node test.mjs && node --test lifecycle.test.mjs merchant-abort.test.mjs preparation.test.mjs braintree.test.mjs autopilot.test.mjs payment-core.test.mjs prepared-processor.test.mjs minimum-delay.test.mjs paysafe.test.mjs attachment.test.mjs && node --test preflight-package.test.mjs preflight-collector.test.mjs",
|
|
30
|
+
"test": "node test.mjs && node --test lifecycle.test.mjs merchant-abort.test.mjs preparation.test.mjs braintree.test.mjs autopilot.test.mjs payment-core.test.mjs prepared-processor.test.mjs minimum-delay.test.mjs paysafe.test.mjs attachment.test.mjs && node --test preflight-package.test.mjs preflight-collector.test.mjs && node --test kernel-native-qualification.test.mjs",
|
|
31
31
|
"test:browser": "node browser.test.mjs && node stripe-browser.test.mjs && node preparation-browser.test.mjs && node owned-shop-browser.test.mjs",
|
|
32
32
|
"check:payment-core": "node scripts/generate-payment-core.mjs --check",
|
|
33
|
-
"test:preflight": "node --test preflight-package.test.mjs preflight-collector.test.mjs",
|
|
33
|
+
"test:preflight": "node --test preflight-package.test.mjs preflight-collector.test.mjs kernel-native-qualification.test.mjs",
|
|
34
34
|
"test:preflight:browser": "node preflight-browser.test.mjs",
|
|
35
35
|
"pack:preview": "node scripts/pack-preflight-preview.mjs",
|
|
36
|
-
"test:preflight:schemas": "node --test preflight-schemas.test.mjs"
|
|
36
|
+
"test:preflight:schemas": "node --test preflight-schemas.test.mjs",
|
|
37
|
+
"check:kernel-native": "node examples/preflight/kernel-native/qualification.mjs inventory --check"
|
|
37
38
|
},
|
|
38
39
|
"keywords": [
|
|
39
40
|
"payments",
|