Skip to content

feat: dual-generation DstackApp contract support with app epochs - #619

Open
Leechael wants to merge 6 commits into
mainfrom
dstack-epoch-contract-compat
Open

Leechael wants to merge 6 commits into
mainfrom
dstack-epoch-contract-compat

Conversation

@Leechael

Copy link
Copy Markdown
Collaborator

Summary

Make the JS SDK and CLI fully compatible with dstack v0.6.4 epoch contracts (DstackKms/DstackApp) ahead of the on-chain proxy upgrade. Every allowlist read/write works against both contract generations: pre-epoch contracts keep the exact legacy behavior, epoch contracts use the epoch-aware functions.

SDK (@phala/cloud)

  • resolveAppEpochPlan probes the contract generation via ERC-165 (IAppEpochManagement = 0x8c0524c4, legacy IAppAuthBasicManagement = 0x8fd37527), with a currentAppEpoch() fallback. Verdicts are cached per (chainId, address); the chain id comes from the configured chain or eth_chainId, and chain-less clients that cannot identify their network are never cached. Legacy verdicts expire after 5 minutes (so an on-chain proxy upgrade is picked up by long-lived processes); epoch verdicts are permanent (upgrades only go legacy → epoch); transport errors are never cached.
  • Write planning: resolveAppEpochWritePlan distinguishes add vs remove. An omitted epoch writes [current, current+1] for additions (rows are not inherited across an epoch advance) and revokes [0, current+1] for removals (the contract still authorizes historical epochs). An explicit epoch writes exactly that epoch and is rejected on legacy contracts.
  • addDevice / removeDevice / addComposeHash route through the write plan and report epochsWritten.
  • getAllowedDevices accepts an optional epoch to read the allowlist at a specific app epoch.

CLI (phala)

  • allow-devices add/remove expose --epoch on epoch-aware contracts. The flag is threaded through --wait verification (reads the requested epoch instead of the current one) and interactive candidate selection (with --epoch, the on-chain read at that epoch decides — devices the backend's current-epoch view reports as removed stay candidates, so historical rows remain revocable).
  • New command-level tests drive add/remove with --epoch/--wait and the interactive remove path end to end against mocks.

Test infrastructure

  • scripts/focused_tests.py now runs each CLI test file in its own process: bun test shares the module registry across files, which leaked whole-module mocks between files.

Notes

  • Plan-then-write is intentionally non-atomic against advanceAppEpoch: the write plan is captured at plan time (current/next), which is the contract's own add-flow semantics.
  • kms-auth-shim needs no changes (pure HTTP signing proxy; payload additions are tolerated).

Test plan

  • sdks/js: 1191 tests pass (fmt/lint/type-check green)
  • sdks/cli: 700 tests pass (fmt/lint/type-check green)
  • Independent code review: 3 rounds, APPROVE, no remaining findings

dstack v0.6.4 removes the epoch-less allowlist setters (addDevice,
removeDevice, addComposeHash) from DstackApp and stores allowlist rows
per app epoch without inheritance across advances.

- Probe the contract generation via ERC-165 (IAppEpochManagement
  0x8c0524c4 vs IAppAuthBasicManagement 0x8fd37527, currentAppEpoch()
  fallback); cache only the generation, never the epoch number, and
  rethrow transport errors so a flaky RPC cannot downgrade a verdict.
- add_device / remove_device / add_compose_hash accept an optional
  epoch: omitted writes [current, current+1] on epoch contracts (the
  legacy permanent-row semantics under the writable-range cap), given
  writes exactly that epoch, and errors on legacy contracts.
- check_device_allowed / check_compose_hash_allowed /
  get_allowed_devices / check_on_chain_prerequisites read the per-epoch
  getters on epoch contracts.
- Selectors and both ERC-165 ids are pinned by tests.

Ref: docs/plans/2026-11-13-dstack-epoch-contract-compat-plan.md
- allow-devices add/remove gain an optional --epoch flag passed through
  to the SDK; on legacy contracts an explicit epoch fails fast with a
  clear error instead of an on-chain revert.
- allow-devices list shows the current app epoch on epoch-aware
  contracts and reports the written epoch range after add/remove.
- deploy / instances add / cvms replicate / envs update surface the
  SDK's epochsWritten metadata in debug output; no signature changes.
- focused_tests.py runs each CLI test file in its own bun process:
  'bun test' shares one module registry across the files it loads, so
  a whole-module mock.module() leaked into sibling files (pre-existing;
  surfaced when the hook selected instances/add + allow-devices
  together).
…reads

Review findings on the epoch compatibility layer:

- Removals default to [0, current+1]: the contract still authorizes any
  epoch ≤ current, so removing only [current, current+1] left the value
  fetchable at historical epochs.
- The generation probe cache is keyed by chain id + address, and legacy
  verdicts expire after 5 minutes so an on-chain proxy upgrade is picked
  up by long-lived processes (epoch verdicts stay permanent; upgrades
  only go legacy → epoch).
- getAllowedDevices accepts an optional epoch to read the allowlist at
  a specific app epoch (CLI --wait/--epoch verification needs it).
- allow-devices add/remove --wait now verifies the allowlist at the
  requested epoch instead of always reading the current one, so
  '--epoch N --wait' no longer times out on a correct write.
- Interactive add/remove candidate lists are re-checked on-chain at the
  requested epoch when --epoch is set (the backend list reflects the
  current epoch only).
- The --epoch help text documents the default ranges: additions write
  [current, current+1], removals revoke [0, current+1].
A PublicClient without a configured chain previously fell into one
"unknown" bucket, so verdicts leaked across networks (reproduced with
two real chain-less clients against different chains). The chain id is
now resolved from the configured chain or eth_chainId, and the verdict
is not cached at all when neither is available. Also correct the
removeDevice docstring to the [0, current+1] revocation default.
…epoch

The interactive removal pre-filtered candidates by the backend's
current-epoch status, so a device that is removed at the current epoch
but still allowed at the requested historical epoch never became a
candidate. With --epoch the candidate pool is now every known device,
and only the on-chain read at the requested epoch decides. Covered by a
new interactive test case.
@github-actions

Copy link
Copy Markdown
Contributor

📋 Check Results

✨ JS SDK - Code Formatting

Show format check results
✓ No formatting issues found

🔍 JS SDK - TypeScript Type Check

Show type check output
$ tsc --noEmit

🧪 JS SDK - Test Results

Show test output
$ vitest --run --exclude '**/*.e2e.test.ts'

�[1m�[30m�[46m RUN �[49m�[39m�[22m �[36mv4.1.12 �[39m�[90m/home/runner/work/phala-cloud/phala-cloud/js�[39m

 �[32m✓�[39m src/utils/errors.test.ts �[2m(�[22m�[2m42 tests�[22m�[2m)�[22m�[32m 81�[2mms�[22m�[39m
�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould add compose hash successfully with default parameters
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould handle custom timeout
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould use custom schema when provided
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould return raw data when schema is false
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould throw when custom schema validation fails
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould work with wallet client authentication
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould work with both clients provided
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould skip prerequisite checks when configured
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould use retry mechanism when enabled
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mStandard Version�[2m > �[22m�[2mshould handle progress callbacks
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mSafe Version�[2m > �[22m�[2mshould return success result when operation succeeds
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mSafe Version�[2m > �[22m�[2mshould work with custom schema
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mSafe Version�[2m > �[22m�[2mshould return raw data when schema is false
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mSafe Version�[2m > �[22m�[2mshould work without parameters
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mSafe Version�[2m > �[22m�[2mshould work with empty parameters object
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mSchema Flexibility�[2m > �[22m�[2mshould allow extra fields in transaction receipt for forward compatibility
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mSchema Flexibility�[2m > �[22m�[2mshould handle ComposeHashAdded event when present
�[22m�[39m[]

 �[32m✓�[39m src/client.test.ts �[2m(�[22m�[2m49 tests�[22m�[2m)�[22m�[32m 32�[2mms�[22m�[39m
�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mType Inference�[2m > �[22m�[2mshould infer correct types for default schema
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mType Inference�[2m > �[22m�[2mshould infer correct types for custom schema
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mType Inference�[2m > �[22m�[2mshould infer unknown type when schema is false
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mSafe Version Type Inference�[2m > �[22m�[2mshould infer correct SafeResult types for default schema
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash�[2m > �[22m�[2mSafe Version Type Inference�[2m > �[22m�[2mshould infer correct SafeResult types for custom schema
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash epoch handling�[2m > �[22m�[2mkeeps the epoch-less setter on a legacy contract
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash epoch handling�[2m > �[22m�[2mwrites the current and next epoch when no epoch is requested
�[22m�[39m[]

�[90mstdout�[2m | src/actions/blockchains/add_compose_hash.test.ts�[2m > �[22m�[2maddComposeHash epoch handling�[2m > �[22m�[2mwrites exactly the requested epoch
�[22m�[39m[]

 �[32m✓�[39m src/actions/blockchains/add_compose_hash.test.ts �[2m(�[22m�[2m38 tests�[22m�[2m)�[22m�[32m 92�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/blockchains/epoch_compat.test.ts �[2m(�[22m�[2m38 tests�[22m�[2m)�[22m�[32m 23�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/blockchains/deploy_app_auth.test.ts �[2m(�[22m�[2m27 tests�[22m�[2m)�[22m�[32m 30�[2mms�[22m�[39m
 �[32m✓�[39m src/utils/define-action.test.ts �[2m(�[22m�[2m24 tests�[22m�[2m)�[22m�[32m 23�[2mms�[22m�[39m
 �[32m✓�[39m src/utils/define-action.type.test.ts �[2m(�[22m�[2m20 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
 �[32m✓�[39m src/types/cvm_id.test.ts �[2m(�[22m�[2m53 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
 �[32m✓�[39m src/utils/get_mr_config_id.test.ts �[2m(�[22m�[2m28 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
 �[32m✓�[39m src/version-inference.runtime.test.ts �[2m(�[22m�[2m21 tests�[22m�[2m)�[22m�[32m 38�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/provision_cvm_compose_file_update.test.ts �[2m(�[22m�[2m29 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
 �[32m✓�[39m src/utils/hostname.test.ts �[2m(�[22m�[2m53 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/provision_cvm.test.ts �[2m(�[22m�[2m27 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
 �[32m✓�[39m src/parse_dotenv.test.ts �[2m(�[22m�[2m71 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
 �[32m✓�[39m src/redirect-security.test.ts �[2m(�[22m�[2m48 tests�[22m�[2m)�[22m�[32m 225�[2mms�[22m�[39m
 �[32m✓�[39m src/create-client.parity.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 39�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/watch_more_contract.test.ts �[2m(�[22m�[2m20 tests�[22m�[2m)�[22m�[32m 43�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/patch_cvm.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/get_current_user.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/cvm_detail_v20261111.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 28�[2mms�[22m�[39m
 �[32m✓�[39m src/version-inference.type.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 26�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/watch_cvm_state.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 70�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/commit_cvm_compose_file_update.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/watch_contract.test.ts �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[32m 31�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_cvm_info.test.ts �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[32m 20�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_cvm_compose_file.test.ts �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/blockchains/check_on_chain_prerequisites.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/blockchains/remove_device.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/blockchains/add_device.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
 �[32m✓�[39m src/client.extend.type.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/blockchains/get_allowed_devices.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
 �[32m✓�[39m src/types/app_compose.test.ts �[2m(�[22m�[2m16 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/placement-identifiers.test.ts �[2m(�[22m�[2m15 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/kms/get_app_env_encrypt_pubkey.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_cvm_list.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 19�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/commit_cvm_provision.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/cvm_lifecycle_versioning.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[32m 17�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/get_available_nodes.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 14�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/blockchains/check_device_allowed.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_cvm_status_batch.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/apps/app_allowlist_versioning.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/kms/get_kms_list.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 15�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/kms/get_kms_info.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/get_cvm_create_resources.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 16�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_cvm_containers_stats.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/blockchains/check_compose_hash_allowed.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_cvm_stats.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
 �[32m✓�[39m src/utils/verify-webhook.test.ts �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
 �[32m✓�[39m src/client.extend.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/confirm_cvm_patch.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/update_cvm_envs.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/blockchains/abi/dstack_app.test.ts �[2m(�[22m�[2m27 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
 �[32m✓�[39m src/utils/validate-parameters.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/ssh_keys/create_ssh_key.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/shutdown_cvm.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/restart_cvm.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/start_cvm.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/stop_cvm.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/refresh_cvm_instance_ids.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/refresh_cvm_instance_id.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/upgrade_prelaunch_script.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/ssh_keys/update_cvm_ssh_keys.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/ssh_keys/delete_ssh_key.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/ssh_keys/sync_github_ssh_keys.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/delete_cvm.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_prelaunch_script_upgrade_status.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/user-agent.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 4�[2mms�[22m�[39m
 �[32m✓�[39m src/types/cvm_info_v20260121.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/apps/get_app_attestation.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
 �[32m✓�[39m src/utils/as-hex.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_cvm_state.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 10�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/update_cvm_visibility.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 11�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/ssh_keys/get_cvm_ssh_keys.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/kms/list_kms_contracts.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/apps/create_app_instance.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/workspaces/list_workspaces.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
 �[32m✓�[39m src/utils/get_compose_hash.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/kms/list_kms_contract_nodes.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/api_tokens/revoke_current_api_token.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/replicate_cvm.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/apps/app_launch_token.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/kms/get_kms_contract.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_available_os_images.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 5�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/log-time.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[32m 4�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/put_cvm_init_scripts.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/cvm_annotations.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/update_cvm_resources.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_cvm_init_scripts.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/get_cvm_user_config.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 7�[2mms�[22m�[39m
 �[32m✓�[39m src/actions/cvms/logs.test.ts �[2m(�[22m�[2m55 tests�[22m�[2m)�[22m�[33m 90146�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m node follow reconnects with tail=0 and drops only exact heartbeat lines �[33m 1006�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m node short empty EOF backs off before reconnecting and does not stop �[33m 2005�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m ordinary network failures stop after five consecutive failures �[33m 5005�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m a successful HTTP 200 with a record resets the ordinary failure counter �[33m 3003�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m in-stream gateway error lines count toward the 5-failure cap �[33m 5002�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m container follow de-duplicates the boundary second and reconnects with tail=all �[33m 1000�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m repeated HTTP ERR-17-003 refreshes only a finite number of times �[33m 3000�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m HTTP 502 ERR-17-009 still reconnects in follow �[33m 5003�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m in-stream ndjson gateway error counts toward the 5-failure cap �[33m 5005�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m container follow stops after 5 empty clean ends �[33m 5004�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m container follow with data keeps reconnecting �[33m 5001�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m delivered record then in-stream error resets the failure counter �[33m 6002�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m empty-end counter resets on HTTP failure �[33m 7007�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m quiet tail=0 reconnect uses first-200 since and tail=all �[33m 1001�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m HTTP 429 is retried up to five times �[33m 5004�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m reconnect without a timestamp keeps the caller since �[33m 1002�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m follow retries lookup network errors up to five times �[33m 5005�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m follow waits at least 1s between consecutive connections �[33m 1001�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m HTTP 401 ERR-17-002 is retried as a failure �[33m 5005�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m partial output then a read error recovers and continues �[33m 1001�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m container long idle EOF keeps following �[33m 6002�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m follow resolves composition once and CVM-info once per connection �[33m 2002�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m follow retries a 503 on the first composition lookup �[33m 5005�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m expiredRetries resets after a connection that delivered records �[33m 5004�[2mms�[22m�[39m

�[2m Test Files �[22m �[1m�[32m90 passed�[39m�[22m�[90m (90)�[39m
�[2m      Tests �[22m �[1m�[32m1168 passed�[39m�[22m�[90m (1168)�[39m
�[2m   Start at �[22m 05:20:29
�[2m   Duration �[22m 90.65s�[2m (transform 1.44s, setup 0ms, import 18.89s, tests 91.78s, environment 7ms)�[22m

📝 JS SDK - Lint Check

Show lint results
✓ No linting issues found

🌐 JS SDK - Browser Compatibility

Show browser test results

🌐 Browser Compatibility Report

Browser compatibility tests completed across:

  • ✓ Chromium
  • ✓ Firefox
  • ✓ WebKit (Safari)

The SDK has been verified to work in modern browser environments.


Check run: https://github.com/Phala-Network/phala-cloud/actions/runs/43371320963

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.

1 participant