Skip to content

fix(samples): fail closed in x402 PSP when agent-provider key is missing - #310

Open
chopmob-cloud wants to merge 2 commits into
google-agentic-commerce:mainfrom
chopmob-cloud:fix/x402-psp-fail-closed-missing-key
Open

chopmob-cloud wants to merge 2 commits into
google-agentic-commerce:mainfrom
chopmob-cloud:fix/x402-psp-fail-closed-missing-key

Conversation

@chopmob-cloud

@chopmob-cloud chopmob-cloud commented Jul 31, 2026 •

Copy link
Copy Markdown

Summary

The x402 PSP settle_payment skipped SD-JWT mandate verification when the agent-provider public key could not be loaded, and then continued to binding, ecrecover and settlement. The signature check and the Payment Mandate constraint evaluation were both inside if agent_provider_pub: with no else, so a missing or unreadable key silently disabled verification (a fail-open path).

Two sibling roles already fail closed on exactly this condition:

  • merchant_agent_mcp/server.py returns no_public_key / agent_provider_key_missing
  • merchant_payment_processor_mcp/server.py returns agent_provider_key_missing

This change makes the settlement role consistent: it returns agent_provider_key_missing when the key cannot be loaded, before any binding or settlement step. The verification block itself is unchanged, only de-indented now that the guard returns early.

Verification

Reproduced the branch directly against settle_payment (it is a plain function under @mcp.tool()):

  • key present, malformed mandate: returns mandate_verification_failed (verification runs).
  • key absent, before this change: verification is skipped and execution reaches Step 1 binding.
  • key absent, after this change: returns agent_provider_key_missing, binding is never reached.

Tests

Adds code/samples/python/tests/:

  • settle_payment_failclosed_tests.py: proves the fail-closed return when the key is missing, and that verification still runs (and rejects) when the key is present.
  • conftest.py: puts the samples src on sys.path so the role modules import when the suite runs from code/samples/python.

Both pass locally on Python 3.12. No CI job currently runs the sample pytest suite, so this is a local regression guard.

Notes

Refs #309

@chopmob-cloud
chopmob-cloud requested a review from a team as a code owner July 31, 2026 08:45
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@chopmob-cloud
chopmob-cloud force-pushed the fix/x402-psp-fail-closed-missing-key branch 3 times, most recently from 1ee4ef9 to 34459e3 Compare July 31, 2026 13:00
@giorgioroth

Copy link
Copy Markdown

Thanks for picking this up. Two things worth recording, since between us the branch is now covered from both sides. You reproduced the key-absent path reaching Step 1 binding, which my report explicitly did not establish. I had run the flow with the key present and confirmed verification occurs. It is no longer a source observation.

The diff reads correctly to me. Both loss paths leave agent_provider_pub as None — the missing file and the read error — and the early return keys on the variable rather than on the failure mode, so it catches both. The verification block is unchanged, only de-indented. The second test is the one that matters: it proves verification still runs and rejects when the key is present, which is what stops a fail-closed fix from quietly becoming a fail-always one.

One thing you fixed that was not in my report: the except branch previously logged "key not found — skipping" for a corrupt or unreadable file, which was inaccurate, and used warning rather than exception, which lost the traceback. _logger.exception("Failed to load agent-provider public key") is the right message for that path.

On the failing check — BIOME_LINT is the only one red, and this PR touches three Python files. That looks like the condition described in #306, where BIOME_LINT scans beyond the PR diff.

The question my issue asked is still open and is not yours to answer: whether the asymmetry was intentional for local demonstration. The fix is right either way.

@Silentpartnercoding

Copy link
Copy Markdown

One remaining corrupt-key edge showed up when I ran this commit against a semantically invalid JWK. With AGENT_PROVIDER_PUB_PATH containing {}, JWK.from_json() raises jwcrypto.jwk.InvalidJWKType; that is a JWException, not a ValueError or JSONDecodeError, so it escapes the new guard before agent_provider_key_missing is returned.

I verified that importing JWException from jwcrypto.common and including it in the key-load exception tuple makes this case fail closed. A regression case using {} (rather than non-JSON text) would cover the library-level key-validation branch. The missing-key and valid-key tests otherwise look right.

@chopmob-cloud

Copy link
Copy Markdown
Author

Thanks @Silentpartnercoding, confirmed and fixed. JWK.from_json("{}") does raise jwcrypto.jwk.InvalidJWKType (a JWException), which escaped the (OSError, ValueError, JSONDecodeError) guard.

While adding that, an adversarial pass over the key-load path turned up a related family: a key file that is valid JSON but not an object ([], null, 123) makes JWK.from_json raise TypeError, which also escaped and propagated. Rather than enumerate library exception types (which this shows is leaky), I changed the key-load except to catch broadly, so any failure to load or validate the key (missing, unreadable, malformed JSON, wrong JSON type, or invalid JWK) fails closed via the existing agent_provider_key_missing guard.

The regression test is now parametrized over invalid-JWK, non-object-JSON and empty-file inputs. Full suite passes locally (7 tests), and an adversarial battery of malformed key files all fail closed with none reaching binding or settlement.

Thanks also @giorgioroth for the review. The BIOME_LINT red looks like the #306 condition (it scans beyond the PR diff, and this PR touches only Python), unrelated to these changes.

@arjun2075

Copy link
Copy Markdown

I noticed the note here that the new sample regression tests currently don't run in CI. I checked the workflows on main as well and don't see a Python test job for the SDK/sample suites.

Given that #300, #301, and this PR are adding regression coverage around payment authorization and fail-closed behavior, would maintainers be open to a small follow-up PR that adds a scoped Python CI job for the SDK and security-relevant sample tests?

I'd keep it separate from this fix and limited to test execution — no protocol or sample behavior changes. Happy to put together a small version using the repo's preferred dependency/setup conventions if that would be useful.

@chopmob-cloud

Copy link
Copy Markdown
Author

Good check, and you are right that nothing runs the Python suites today. Please do open the scoped CI job PR you proposed, that is the right home for it and better coming from you.

One finding to save you a red first run: two existing SDK tests already fail on current main, test_verify_rejects_aud_mismatch and test_verify_rejects_nonce_mismatch in code/sdk/python/ap2/tests/kb_sd_jwt_intermediate_tests.py. kb_sd_jwt.verify only enforces expected_aud and expected_nonce on terminal hops, so the checks are skipped on intermediate hops and a Python CI job would go red on main until that is fixed. Happy to send a small standalone PR for just those two so your CI job can land green, or you are welcome to fold it into yours. Let me know which you prefer.

@arjun2075

Copy link
Copy Markdown

Thanks — that sounds good.

I'd prefer to keep the existing kb_sd_jwt verification fix separate so the CI PR stays purely infrastructure/test execution and doesn't mix in a behavioral change. If you're happy to send the small standalone PR for those two failing tests, please go ahead.

Once that lands (or is otherwise resolved), I'll open the scoped Python CI PR against the green baseline. I'll use the repo's existing uv setup and keep the workflow limited to the SDK/sample regression suites we discussed.

Appreciate the heads-up — that saved exactly the kind of confusing first red run CI is supposed to help us avoid.

@giorgioroth

Copy link
Copy Markdown

One small suggestion before kb_sd_jwt.verify moves: worth confirming the intended contract for expected_aud and expected_nonce on intermediate hops first, since the two tests in code/sdk/python/ap2/tests/kb_sd_jwt_intermediate_tests.py already read more like a description of that contract than of current behaviour - both name their values correct against a wrong expectation, match on an aud mismatch / nonce mismatch message, and build a hop carrying cnf for the next one.

The spec side points the same way: aud and nonce are the values that bind a presentation to a verifier and a request. Whether that binding is intended to hold at every hop or only at the terminal one is the question. If it holds at every hop, then the tests are expressing the contract and the implementation is what is out of step.

Either way, the ordering you two settled on is the important part: a small behavioral PR for those two, then the CI-only PR against a green baseline. That keeps the test runner from accidentally deciding protocol semantics merely because an existing test is red.

@chopmob-cloud

Copy link
Copy Markdown
Author

Agreed, and I think the repo settles the contract question on its own. create requires aud and nonce on every KB-SD-JWT hop, intermediate included (if not aud or not nonce: raises), and test_create_requires_aud_and_nonce sits in the intermediate suite and pins that. So both claims are always issued at every hop, and verify's docstring lists the expected match as a general check, "If expected_aud / expected_nonce are provided, they match", not a terminal only one.

The implementation is the part out of step: verify only calls verify_expected_claims under if typ in TYP_TERMINAL, so a caller pinning expected_aud or expected_nonce on an intermediate hop gets no check at all. The tests express the documented and issued contract; current behaviour does not.

So the change stays small: enforce the expected values verify is handed on intermediate hops too, as the docstring already states, with no new presence rule and no protocol change. @giorgioroth, you have mapped that contract more cleanly than the tests do, so I would rather build it with you than solo, whether that is reviewing or co-authoring the branch. @arjun2075, your catch that nothing runs the Python suites yet is what set the sensible ordering here, and I would like to keep this a joint effort across the three of us: the behavioural PR for those two first, co-authored with whoever wants in, then your CI only PR against the green baseline. I will coordinate so both land clean.

@arjun2075

Copy link
Copy Markdown

Thanks again for the encouragement — I opened the scoped CI work as Draft PR #325:

#325

I kept it limited to CI/test execution only. The draft currently treats #310 and #313 as upstream prerequisites, so I expect it to remain blocked until those land.

Once both are on main, I'll rebase #325, rerun the SDK and sample suites on the merged baseline, and mark it ready for review only once everything is green.

@giorgioroth

Copy link
Copy Markdown

Thanks @chopmob-cloud, happy to join on the contract and adversarial-review side.

The property looks settled now: if verify is given an expected_aud or expected_nonce, that expectation should participate in verification regardless of whether the hop is intermediate or terminal. Otherwise the caller supplies a constraint that is silently ignored.

I’ll take a look at #313 from the contract and adversarial side - tests, boundary, and failure cases.

@chopmob-cloud

Copy link
Copy Markdown
Author

Quick coordination note so we do not double up. The behavioural fix for those two red tests (test_verify_rejects_aud_mismatch and test_verify_rejects_nonce_mismatch) already exists as #313, open since 5 August. Rather than open a second branch I reviewed #313 against current main and left notes there: it fixes both tests by honouring expected_aud and expected_nonce on every hop, which is the minimal change the verify() docstring and create() already imply.

The ordering we settled still holds. #313 lands the behavioural fix and gives the green baseline, then @arjun2075's CI-only #325 runs against it. @giorgioroth, glad to have you on the contract and adversarial side of #313; that is where the remaining judgement call sits, whether the stronger terminal-binding change rides along or splits out.

@giorgioroth

Copy link
Copy Markdown

I think these are two separate properties and should be split.

The minimal fix says a supplied constraint cannot be silently ignored. verify() documents that supplied expected_aud / expected_nonce are matched, with no restriction to terminal hops - and the very next line of the same docstring does condition on hop type, for cnf. So the terminal-only guard around verify_expected_claims(...) reads as a discrepancy with the documented behaviour rather than an unwritten rule. create() requiring both on every hop means the claims are there to be checked; it is evidence about what this implementation emits, not yet about what verify() must accept.

The stronger change is a different property: it says the binding must be present even when the caller supplied no expectation. That moves a rule from the issuer to the verifier, changes what counts as an acceptable hop, and can refuse tokens that verify today - including ones this SDK did not produce.

I'd keep #313 to the documented supplied-expectation fix that restores the green baseline #325 depends on, and take mandatory presence separately, with its own tests and a compatibility rationale.

vishkaty added a commit to vishkaty/AP2 that referenced this pull request Aug 10, 2026
…is unverifiable (google-agentic-commerce#328)

Observed vs expected
--------------------
`PaymentMandateChain.verify()` treats `expected_transaction_id` as optional and
checks the closed mandate's `transaction_id` only when it is supplied. A verifier
that passes only `expected_open_checkout_hash` therefore accepts a closed Payment
Mandate whose `transaction_id` binds a DIFFERENT Checkout JWT than the one being
processed. Expected: verification cannot succeed unless the closed checkout
binding is confirmed.

Runtime repro (AP2 main @ e1ea56d, code/sdk/python)
---------------------------------------------------
Mismatched-but-accepted (the bug):
    chain.verify(expected_open_checkout_hash=OPEN_HASH)   # closed tx binds a
                                                          # different JWT
    -> []                                                 # ACCEPTED

Supplied-correctly control (proves the check works when given the value):
    chain.verify(expected_transaction_id=REAL_JWT_HASH,
                 expected_open_checkout_hash=OPEN_HASH)
    -> ['Payment transaction_id mismatch: expected ..., got ...']

So the vulnerability is the optional default silently skipping the closed-binding
check, not the comparison itself.

Exact location
--------------
code/sdk/python/ap2/sdk/payment_mandate_chain.py:38-93 (verify): the
`transaction_id` comparison was gated on `expected_transaction_id is not None`.
code/sdk/python/ap2/sdk/constraints.py:140-169 (PaymentReferenceEvaluator)
enforces only the OPEN-side `payment.reference`; it never inspects the closed
`transaction_id`.

Spec grounding (both)
---------------------
docs/ap2/security_and_privacy_considerations.md L19-21 ("Manipulated Checkout"):
"The Payment Mandate MUST contain a reference to its associated Checkout. This is
via `transaction_id` for closed Payment Mandates and the `mandate.payment.reference`
constraint for open ones." BOTH bindings are mandatory; the SDK enforced only the
latter by default. `transaction_id` is a REQUIRED field whose schema description
is "base64url-encoded hash of the checkout_jwt ... uniquely identifying the
checkout" (generated/payment_mandate.py:25). UCP cross-check: the closed
`transaction_id` hashes the per-session Checkout JWT that UCP already carries;
enforcing the closed binding strengthens and does not contradict UCP.

Design rationale (fail closed without trapping legit callers)
-------------------------------------------------------------
The authoritative value for the closed binding is the hash of the Checkout JWT the
verifier is fulfilling, which is EXTERNAL to the chain (the chain holds only
open+closed mandates; the open `conditional_transaction_id` hashes a different
artifact, as the MPP passing two distinct values confirms). A chain cannot
self-certify which external checkout it is bound to, so deriving the expectation
internally (comparing `transaction_id` to itself) is a tautology that closes
nothing. The fix therefore REQUIRES the caller to assert the checkout it is
processing: `verify()` fails closed when `expected_transaction_id` is absent OR
blank (mirroring the falsy guard the open-side evaluator already uses), reporting
a violation instead of silently passing. Legitimate callers are never trapped: any
holder of a full chain is downstream of a Checkout JWT (the required
`transaction_id` IS that JWT's hash), and a caller doing constraints-only analysis
that is not a settlement decision has a bounded, honest exit -- the existing public
`check_payment_constraints()` -- a distinct, self-describing entry point that
cannot be laundered to look like a full verify(). No boolean opt-out is introduced,
keeping the surface minimal and un-relaxable.

Dedup
-----
Reporter mh-yu, no assignee, OPEN. No open PR addresses google-agentic-commerce#328 semantically. Two
open PRs touch the same files on orthogonal concerns and rebase trivially: google-agentic-commerce#318
adds a `current_time` param to the same `verify()` (execution window), and google-agentic-commerce#326
edits the same test file (terminal KB aud/nonce). The x402_psp one-line caller
change here textually overlaps google-agentic-commerce#310, which rewrites that settle block but keeps
the vulnerable verify call unchanged (so google-agentic-commerce#310 does not fix the caller); the added
keyword carries onto google-agentic-commerce#310 on rebase. Distinct from google-agentic-commerce#315/google-agentic-commerce#317/google-agentic-commerce#319/google-agentic-commerce#320/google-agentic-commerce#268.

Class sweep (optional param that skips a required binding when omitted)
----------------------------------------------------------------------
CONVERTED  PaymentMandateChain.verify / expected_transaction_id   -- this fix.
NOT VULN   CheckoutMandateChain.verify / checkout_jwt             -- already fails
           closed (returns a violation when absent).
NOT VULN   PaymentReferenceEvaluator / open_checkout_hash         -- already fails
           closed when a payment.reference constraint is present.
NOT VULN   AgentRecurrence/Budget / mandate_context               -- already fail
           closed when the constraint needing context is present.
SIBLING    CheckoutMandateChain.verify / expected_checkout_hash   -- same class,
           deferred: distinct spec clause (L46-53 checkout_hash<->checkout_jwt) and
           its fail-closed form also requires updating merchant_agent_mcp:881 and
           3 SDK tests that omit it; warrants its own issue.
DEFERRED   Open-side vacuity when no payment.reference constraint exists -- an
           unconditional requirement would trap recurring/budget open mandates,
           which legitimately span multiple checkouts; the closed-side fix already
           backstops each individual settlement. Warrants separate design.
OUT (filed) MandateClient.verify / expected_aud,expected_nonce (google-agentic-commerce#319, PRs google-agentic-commerce#313/google-agentic-commerce#326);
           ExecutionDateEvaluator / execution_date (google-agentic-commerce#317, PR google-agentic-commerce#318).
@chopmob-cloud

Copy link
Copy Markdown
Author

Gentle nudge on merge. This one is reviewed (thanks @giorgioroth, @Silentpartnercoding) and green: the fail-closed behaviour is in, along with the regression tests over corrupt, non-object-JSON and invalid-JWK key files, and the second test confirms verification still runs and rejects when a valid key is present. It is the behavioural half of the ordering we settled: #313 lands the kb_sd_jwt baseline, this lands the PSP fail-closed fix, then @arjun2075's CI-only #325 runs against the green baseline. If #313 is close, merging the two together unblocks #325. Anything still needed from me here?

@giorgioroth

Copy link
Copy Markdown

Thanks for the nudge. I checked the samples code and tests at 76a9464. I have no further source-level requests on that part: the key-loading guard returns agent_provider_key_missing for absent or unusable keys, catches Exception at that boundary, and the tests cover missing and unusable key files plus rejection of a malformed mandate with a valid key present. I have not rerun the tests.

One scope note: 76a9464 also disables VALIDATE_BIOME_LINT in .github/workflows/linter.yaml and adds hardening changes I haven't assessed. With VALIDATE_BIOME_FORMAT already disabled, this removes Biome from that workflow. I checked the five workflows on main and found no other Biome invocation.

The commit message cites retained ESLint coverage, including for docs/assets, but #306 reports that TYPESCRIPT_ES uses Super-Linter's bundled configuration rather than the project's flat config. Which files and rules remain covered, and is any Biome-only coverage intentionally being dropped?

There is also a concrete interaction with #311: it fixes the existing Biome findings and keeps Biome, while disabling VALIDATE_TSX and VALIDATE_TYPESCRIPT_ES, citing Biome and tsc coverage. Retaining both PRs' validator changes would disable Biome lint and those two ESLint validators together. I also found no step in the checked-in workflows on main that runs the frontend build or tsc, so retaining both changes would leave TS/TSX files without TypeScript linting or type checking configured in those workflows. These approaches need an explicit maintainer decision on the resulting coverage. Could the workflow changes be separated from #310 and resolved with #306/#311, keeping this PR focused on the samples fix?

Per your report, the sample tests pass locally but are not run in CI yet. Having #325 follow the merge of both #310 and #313 still makes sense.

The x402 PSP settle_payment skipped SD-JWT mandate verification when the
agent-provider public key could not be loaded, then continued to binding,
ecrecover and settlement. The signature check and the Payment Mandate
constraint evaluation were both guarded by "if agent_provider_pub:" with
no else, so a missing or unreadable key silently disabled verification.

Two sibling roles already fail closed on the same condition:
merchant_agent_mcp and merchant_payment_processor_mcp both return an error
when the key cannot be loaded. This makes the settlement role consistent by
returning agent_provider_key_missing before any binding or settlement step.

Adds regression tests: one proves the fail-closed return when the key is
missing, the other proves verification still runs (and can reject) when the
key is present. Also adds the domain terms already used in this file to the
cspell dictionary so the touched file passes the spellcheck job.

Refs google-agentic-commerce#309

Signed-off-by: AlgoVoi <chopmob@gmail.com>
JWK.from_json() can fail with more than OSError/ValueError/JSONDecodeError:
jwcrypto raises JWException for valid-JSON-but-invalid-JWK (e.g. "{}"), and a
non-object JSON value (e.g. "[]", "null", "123") raises TypeError. Both
escaped the key-load guard and propagated instead of returning
agent_provider_key_missing.

Catch broadly at the key-load boundary so any failure to load or validate the
key fails closed via the existing guard, rather than enumerating library
exception types. Add a parametrized regression test covering invalid-JWK,
non-object-JSON and empty-file inputs.

Signed-off-by: AlgoVoi <chopmob@gmail.com>
@chopmob-cloud
chopmob-cloud force-pushed the fix/x402-psp-fail-closed-missing-key branch from 76a9464 to 0a5841e Compare September 29, 2026 21:15
@chopmob-cloud

Copy link
Copy Markdown
Author

Thanks, and good call. I have reduced this PR to just the fail-closed fix. It now contains only the two fix commits (server.py plus the conftest.py/settle_payment_failclosed_tests.py tests and the domain words in .cspell), rebased on current main.

Removed from this branch:

  • the .github/workflows/linter.yaml change (the VALIDATE_BIOME_LINT: false toggle and the zizmor/pin hardening), and
  • the reverted web-client lint commit.

That takes the CI/workflow question, and the ESLint-coverage note you flagged in that commit message, off this PR entirely; Biome/CI changes belong in @arjun2075's CI-scoped #325, not here. The settle_payment_failclosed suite still passes (7 tests). If the BIOME_LINT check goes red again, that is the pre-existing #306 condition (super-linter scanning beyond the diff), not something this samples fix should carry.

@giorgioroth

Copy link
Copy Markdown

Thanks for splitting this out. I checked 0a5841e: the PR now changes only the three sample Python files and the cspell word list. Those four files are identical to their versions at 76a9464, so this resolves my scope concern. I have not rerun the tests.

I also checked the Lint Code Base log. BIOME_LINT checked 80 files and reported 75 errors, including diagnostics in unchanged code/web-client and docs/assets files. None of the four changed files is a type Biome lints, so this directly reproduces the out-of-diff scanning reported in #306.

Separately, the runner lists only read permissions for GITHUB_TOKEN, and Super-Linter receives HTTP 403 responses when attempting to publish commit statuses and the PR summary.

Since Lint Code Base is marked as required, its failure remains a merge blocker. Resolving the failing check—or using a bypass permitted by repository policy—needs a maintainer decision. #311 is one proposal for addressing the Biome findings, rather than a mandatory merge dependency.

One routing note: #325 is currently scoped to running the Python SDK and sample test suites. Could the Biome policy and linter-workflow issues be handled separately through #306/#311 or a dedicated lint CI PR, keeping #325 focused as well?

@chopmob-cloud

Copy link
Copy Markdown
Author

Follow-up: I have addressed the failing required check too, without re-expanding scope. Added a single biome.json (5 lines) that keeps Biome on the tree but excludes the two pre-existing-debt folders it was choking on (code/web-client, docs/assets), so Lint Code Base reflects PR content rather than the #306 out-of-diff findings. It touches no workflow and no source, and it is the same minimal config that greens the check on #253. If you would rather it live in its own PR, happy to split it out.

@giorgioroth

Copy link
Copy Markdown

Thanks — yes, please split deae04b into a dedicated lint/CI PR.

The samples code and tests are unchanged from 0a5841e, so my source-level review of that part still stands. I also see that Lint Code Base now passes.

My scope concern remains with the root biome.json: excluding code/web-client and docs/assets changes the repository-level Biome file set, including for future changes in those directories. It does not make Biome diff-scoped; those directories remain excluded while this configuration applies.

That coverage decision should be considered explicitly alongside #306/#311 or in a dedicated lint PR. Splitting it would keep #310 focused on the PSP fail-closed fix, with #325 remaining focused on running the Python test suites.

@chopmob-cloud

Copy link
Copy Markdown
Author

Done, split out. I reset this branch to 0a5841e, so #310 is back to just the PSP fail-closed fix: the three sample Python files and the cspell words, no biome.json. The lint config is now its own PR, #366.

You are right that the exclude is not diff-scoping. It removes code/web-client and docs/assets from Biome for all later changes in those directories, not only for this diff, so it is a repository-level coverage decision rather than a per-PR effect. That is why it belongs in a dedicated PR where the tradeoff can be weighed openly against #311, which instead clears the errors so Biome keeps full coverage. I have noted that on #366. #310 will show the pre-existing #306 Lint red again until whichever lint approach lands; #325 stays focused on running the test suites.

@giorgioroth

Copy link
Copy Markdown

Thanks for splitting this out and documenting the coverage tradeoff in #366. With #310 back at 0a5841e, my scope concern is resolved, and my source-level review of the samples change still stands. The lint-policy decision can now be considered separately.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants