Tracking: carried over from #97 (Group 4)
Description
Three e2e specs are skipped/weakened because a revoked ChainSubmit permission does not currently make a broadcast fail:
examples/tx-demo/e2e/signing-rejected.spec.ts: skipped
examples/contracts-demo/e2e/signing-rejected.spec.ts: skipped
examples/signer-demo/e2e/permission.spec.ts: runs, but with a weakened assertion
Why this is the real bug (correction to #97's original framing)
#97's Group 4 section ("signer-demo permission-rejection contract") framed this as "the permission is checked once at connect, not per-sign: converge on a permission-rejection contract that applies per-sign." A code map showed that framing is not how the code works:
- The product-sdk signer only requests
ChainSubmit once at connect (packages/signer/src/providers/host.ts:610). It does not, and is not meant to, enforce permissions per sign.
- Enforcement is the host/container's job. Per the fake host's (the host SDK test -
@parity/host-api-test-sdk) own CLAUDE.md: "Signing is NOT gated behind any permission - real hosts don't do this. ChainSubmit is enforced by the container at transaction_broadcast level."
- The fake host's
setEnforcePermissions is a dead flag: set at host-runtime.ts:1300, the backing variable (:142) is never read, and the signing handlers (:577–647) aren't gated at all.
So the bug is: the fake host doesn't enforce ChainSubmit revocation at broadcast, so the revoke-then-sign spec can't go red. The durable fix is to let the real container enforce it and unskip against the headless host; the interim fix is to wire the dead flag in the fake host.
Fix (interim - only if the headless host is far off)
In host-api-test-sdk, src/browser/host-runtime.ts: gate the ChainSubmit / transaction_broadcast slot on grantedPermissions.has("ChainSubmit") so that revokePermission("ChainSubmit") + reject-all makes the broadcast fail with PermissionDenied. That propagates back as the TxSigningRejectedError the specs already assert. Then:
- bump the catalog pin in product-sdk (
pnpm-workspace.yaml: ^0.9.0 → new), and
- revert the 3 skips (
tx-demo + contracts-demo signing-rejected.spec.ts) and restore the strong assertion in signer-demo/permission.spec.ts.
The durable version is the "Headless-host migration" first checklist item in the #235 issue under the "Gated on the headless host existing": the real container enforces ChainSubmit and we unskip against the headless host. Don't do a big test-SDK rewrite either way.
Done when
Key references
| Thing |
Path |
| Signer host provider (ChainSubmit request at connect) |
product-sdk/packages/signer/src/providers/host.ts:595–618 |
| Permissions module |
product-sdk/packages/host/src/permissions.ts:134 |
Fake host runtime (perm handler :379–406, signing :577–647, dead flag :142,:1300) |
host-api-test-sdk/src/browser/host-runtime.ts |
Tracking: carried over from #97 (Group 4)
Description
Three e2e specs are skipped/weakened because a revoked
ChainSubmitpermission does not currently make a broadcast fail:examples/tx-demo/e2e/signing-rejected.spec.ts: skippedexamples/contracts-demo/e2e/signing-rejected.spec.ts: skippedexamples/signer-demo/e2e/permission.spec.ts: runs, but with a weakened assertionWhy this is the real bug (correction to #97's original framing)
#97's Group 4 section ("signer-demo permission-rejection contract") framed this as "the permission is checked once at connect, not per-sign: converge on a permission-rejection contract that applies per-sign." A code map showed that framing is not how the code works:
ChainSubmitonce at connect (packages/signer/src/providers/host.ts:610). It does not, and is not meant to, enforce permissions per sign.@parity/host-api-test-sdk) own CLAUDE.md: "Signing is NOT gated behind any permission - real hosts don't do this.ChainSubmitis enforced by the container attransaction_broadcastlevel."setEnforcePermissionsis a dead flag: set athost-runtime.ts:1300, the backing variable (:142) is never read, and the signing handlers (:577–647) aren't gated at all.So the bug is: the fake host doesn't enforce
ChainSubmitrevocation at broadcast, so the revoke-then-sign spec can't go red. The durable fix is to let the real container enforce it and unskip against the headless host; the interim fix is to wire the dead flag in the fake host.Fix (interim - only if the headless host is far off)
In
host-api-test-sdk,src/browser/host-runtime.ts: gate theChainSubmit/transaction_broadcastslot ongrantedPermissions.has("ChainSubmit")so thatrevokePermission("ChainSubmit")+reject-allmakes the broadcast fail withPermissionDenied. That propagates back as theTxSigningRejectedErrorthe specs already assert. Then:pnpm-workspace.yaml:^0.9.0→ new), andtx-demo+contracts-demosigning-rejected.spec.ts) and restore the strong assertion insigner-demo/permission.spec.ts.The durable version is the "Headless-host migration" first checklist item in the #235 issue under the "Gated on the headless host existing": the real container enforces
ChainSubmitand we unskip against the headless host. Don't do a big test-SDK rewrite either way.Done when
revokePermission("ChainSubmit")causes a broadcast to fail withPermissionDenied→ surfaces asTxSigningRejectedError.tx-demo/e2e/signing-rejected.spec.tsandcontracts-demo/e2e/signing-rejected.spec.tsunskipped and green.signer-demo/e2e/permission.spec.tsstrong assertion restored.pnpm test:e2eexits 0 with these specs running.Key references
product-sdk/packages/signer/src/providers/host.ts:595–618product-sdk/packages/host/src/permissions.ts:134:379–406, signing:577–647, dead flag:142,:1300)host-api-test-sdk/src/browser/host-runtime.ts