Skip to content

test: migrate unit suites to async backend-aware simulator (dry + live)#620

Open
0xisk wants to merge 10 commits into
mainfrom
chore/simulator-async-migration
Open

test: migrate unit suites to async backend-aware simulator (dry + live)#620
0xisk wants to merge 10 commits into
mainfrom
chore/simulator-async-migration

Conversation

@0xisk

@0xisk 0xisk commented Jun 22, 2026

Copy link
Copy Markdown
Member

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update (if none of the other choices apply)

Migrates every module's per-contract simulator and unit specs from the old
synchronous createSimulator to the new async, backend-aware API published as
@openzeppelin/compact-simulator@0.2.0 (OpenZeppelin/compact-tools#121). One
factory, two backends: a single spec file runs on both MIDNIGHT_BACKEND=dry and
=live.

Scope. Unit suites + the local unit live-testing setup. The integration
suite is left untouched (stays on main), and the live CI workflow is a
follow-up (a sharded GitHub-hosted matrix, since a single job caps at 6h).

What changed

  • Simulators (src/**/test/simulators/*.ts): constructor(...)static async create(...); delegating methods return Promise<R>; private-state helpers use the async getPrivateState/setPrivateState; artifactName added so the live harness can locate compiled assets + ZK keys.
  • Specs (src/**/test/*.test.ts): new X()await X.create(); every circuit/state call awaited; failure paths use await expect(...).rejects.toThrow(...); caller identity uses alias strings (as('OWNER')) in access + multisig.
  • Local live setup: test:live script, vitest.live.config.ts, the test/integration/_harness registerLiveBackend harness (deploys a fresh contract per test), and make env-up / local-env.yml (node + indexer + proof-server) — so make env-up && yarn test:live runs the unit specs on a real node.
  • Dependency: @openzeppelin/compact-simulator bumped ^0.1.0^0.2.0.

Validation

  • Dry: full unit suite green (vitest run); default test CI is green.
  • Live: unit specs run through the harness on a local stack (make env-up && yarn test:live); the automated CI run lands with the follow-up workflow PR.

PR Checklist

  • I have read the Contributing Guide
  • I have added tests that prove my fix is effective or that my feature works
  • I have added documentation for new methods or changes to existing method behavior
  • CI Workflows Are Passing

Further comments

A single async factory (rather than a separate sync/async pair) keeps one simulator
file and one test file per module — no duplicate *Backend twins — at the cost of a
one-time, mostly-mechanical async migration. The integration suite on main still uses
the old synchronous API and will be migrated in the follow-up.

Summary by CodeRabbit

  • New Features

    • Added local environment tooling for starting, stopping, checking, and tailing Docker-based services.
    • Introduced a live test configuration for running contract tests against a live backend.
  • Bug Fixes

    • Updated contract simulators and tests to use async/await flows for more reliable setup and assertions.
    • Improved handling of initialization, permissions, and error cases across token, multisig, security, and access flows.

Migrate every module's per-contract simulator and specs from the old
synchronous createSimulator to the new async, backend-aware API: the
factory now returns a class whose create() is awaited and whose circuits
return promises, so one spec file runs on both backends selected by
MIDNIGHT_BACKEND=dry|live.

* simulators: constructor -> static async create(...); delegating methods
  return Promise<R>; private-state injection helpers use the async
  getPrivateState/setPrivateState; artifactName added so the live harness
  can locate compiled assets + ZK keys.
* specs: new X() -> await X.create(); every circuit/state call awaited;
  failure paths use await expect(...).rejects.toThrow(...); caller identity
  uses alias strings (as('OWNER')) in access + multisig.
* live: a test:live script, vitest.live.config.ts, a registerLiveBackend
  harness (deploy-per-test against the local stack), and a label-gated
  live-tests CI workflow; make env-up / local-env.yml bring up node +
  indexer + proof server.
* deps: consume @openzeppelin/compact-simulator via portal: against the
  sibling compact-tools checkout until the live backend is published.

Validated dry: 28 files, 1183 tests green. Live: proven end-to-end on the
security module against a local node.

Note: the live _harness/ (network/deploy/wallet/ownWallet/providers) is
shared infra that overlaps the in-flight native-shielded-token branch and
should be de-duplicated at merge.
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR converts simulator wrappers and their tests to async factory and Promise-based APIs across access, multisig, token, security, and utils. It also adds local Docker Compose, logging, and live Vitest configuration files.

Changes

Access simulator migration

Layer / File(s) Summary
Unshielded access APIs
contracts/src/access/test/simulators/AccessControlSimulator.ts, contracts/src/access/test/simulators/OwnableSimulator.ts, contracts/src/access/test/AccessControl.test.ts, contracts/src/access/test/Ownable.test.ts
AccessControlSimulator and OwnableSimulator expose async create factories, Promise-returning role and ownership methods, and async private-state helpers; the matching tests await simulator setup and promise-based assertions.
Shielded access APIs
contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts, contracts/src/access/test/simulators/ZOwnablePKSimulator.ts, contracts/src/access/test/ShieldedAccessControl.test.ts, contracts/src/access/test/ZOwnablePK.test.ts
ShieldedAccessControlSimulator and ZOwnablePKSimulator switch to async factories, Promise-returning role and ownership helpers, and async commitment and private-state helpers; the matching tests use awaited simulator calls and async rejection checks.

Multisig simulator migration

Layer / File(s) Summary
Signer APIs
contracts/src/multisig/test/simulators/SignerSimulator.ts, contracts/src/multisig/test/simulators/SignerManagerSimulator.ts, contracts/src/multisig/test/Signer.test.ts, contracts/src/multisig/test/SignerManager.test.ts
SignerSimulator and SignerManagerSimulator expose async create factories and Promise-returning signer and threshold methods; the signer tests await those calls and convert failure checks to promise rejections.
Proposal, treasury, and shielded multisig APIs
contracts/src/multisig/test/simulators/ProposalManagerSimulator.ts, contracts/src/multisig/test/simulators/ShieldedTreasurySimulator.ts, contracts/src/multisig/test/simulators/ShieldedMultiSigSimulator.ts, contracts/src/multisig/test/simulators/ShieldedMultiSigV2Simulator.ts, contracts/src/multisig/test/simulators/ShieldedMultiSigV3Simulator.ts, contracts/src/multisig/test/ProposalManager.test.ts, contracts/src/multisig/test/ShieldedTreasury.test.ts, contracts/src/multisig/test/ShieldedMultiSig.test.ts, contracts/src/multisig/test/ShieldedMultiSigV2.test.ts, contracts/src/multisig/test/ShieldedMultiSigV3.test.ts
ProposalManagerSimulator, ShieldedTreasurySimulator, and ShieldedMultiSig* simulators switch to async factories and Promise-returning proposal, treasury, approval, mint/burn, and ledger methods; the related tests await those operations.
Forwarder APIs
contracts/src/multisig/test/simulators/MockForwarderPrivateSimulator.ts, contracts/src/multisig/test/simulators/MockForwarderShieldedSimulator.ts, contracts/src/multisig/test/simulators/MockForwarderUnshieldedSimulator.ts, contracts/src/multisig/test/simulators/presets/ForwarderPrivateSimulator.ts, contracts/src/multisig/test/simulators/presets/ForwarderShieldedSimulator.ts, contracts/src/multisig/test/simulators/presets/ForwarderUnshieldedSimulator.ts, contracts/src/multisig/test/Forwarder.test.ts, contracts/src/multisig/test/presets/ForwarderPrivate.test.ts, contracts/src/multisig/test/presets/ForwarderShielded.test.ts, contracts/src/multisig/test/presets/ForwarderUnshielded.test.ts
MockForwarder* and Forwarder* simulators switch to async create factories and Promise-returning deposit, drain, and parent access methods; the forwarder tests await initialization, deposits, drains, and state reads.

Token simulator migration

Layer / File(s) Summary
Archive token APIs
contracts/src/archive/test/simulators/ShieldedTokenSimulator.ts, contracts/src/archive/test/ShieldedToken.test.ts
ShieldedTokenSimulator exposes an async create factory and Promise-returning token queries and mint/burn methods; the archive token tests await those calls and use async rejection assertions.
Fungible token APIs
contracts/src/token/test/simulators/FungibleTokenSimulator.ts, contracts/src/token/test/FungibleToken.test.ts
FungibleTokenSimulator switches to an async factory plus Promise-returning token, allowance, transfer, mint, burn, and private-state helpers; the tests await the new API and convert error checks to promise rejections.
Multi-token APIs
contracts/src/token/test/simulators/MultiTokenSimulator.ts, contracts/src/token/test/MultiToken.test.ts
MultiTokenSimulator moves to an async factory with Promise-returning balance, approval, transfer, mint, burn, URI, and private-state methods; the tests follow the async API across initialization, transfers, approvals, and wiring checks.
Non-fungible token APIs
contracts/src/token/test/simulators/NonFungibleTokenSimulator.ts, contracts/src/token/test/nonFungibleToken.test.ts
NonFungibleTokenSimulator exposes an async factory and Promise-returning query, approval, transfer, mint, burn, and private-state methods; the test suite awaits those operations and updates all failure checks to async rejection assertions.

Security and utility simulator migration

Layer / File(s) Summary
Initializable and Pausable APIs
contracts/src/security/test/simulators/InitializableSimulator.ts, contracts/src/security/test/simulators/PausableSimulator.ts, contracts/src/security/test/Initializable.test.ts, contracts/src/security/test/Pausable.test.ts
InitializableSimulator and PausableSimulator expose async factories and Promise-returning initialization and pause-state methods; the tests await simulator setup and switch failure cases to promise rejections.
Utils APIs
contracts/src/utils/test/simulators/UtilsSimulator.ts, contracts/src/utils/test/utils.test.ts
UtilsSimulator moves to an async factory and Promise-returning helper methods; the utility tests await each helper and the public-state wiring check.

Local live environment tooling

Layer / File(s) Summary
Local compose environment
Makefile, local-env.yml
local-env.yml adds proof-server, indexer, and node service definitions, and the new Makefile adds environment lifecycle, log, and status targets for that stack.
Live test harness
contracts/vitest.live.config.ts, contracts/package.json
contracts/package.json adds test:live and updates the compact simulator dependency, and contracts/vitest.live.config.ts adds the live Vitest configuration.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

Suggested reviewers

  • andrew-fleming

Poem

A rabbit hopped through async night,
With logs and tests all soft and bright.
The sims now wait, the tails now sing,
And carrots bloom in every ring. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: migrating unit tests and simulators to the new async backend-aware flow for dry and live runs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/simulator-async-migration

Comment @coderabbitai help to get the list of available commands.

0xisk added 3 commits June 24, 2026 19:06
The async backend-aware simulator (OpenZeppelin/compact-tools#121) is
now published as @openzeppelin/compact-simulator@0.2.0. Replace the
placeholder `portal:` dependency on a sibling compact-tools checkout
with the published package, so the default `test` CI can install the
dependency and run the dry suite (the `portal:` link could not resolve
in CI).

Validated dry on the utils, access, token, and security modules
(298 tests green) against the published API; the imported surface
(createSimulator, registerLiveBackend, LiveBackendRequest, LiveContext,
SimulatorOptions) is unchanged from the portal build.
The live backend is now consumed from the published
@openzeppelin/compact-simulator, so the sibling compact-tools checkout
and `yarn workspace ... build` step are no longer needed to resolve the
dependency. Remove both and install against the committed lockfile with
`--immutable` (was `--no-immutable`, required only while the `portal:`
link had to be re-resolved).

Behaviour-only cleanup of the label-gated workflow; the dry `test` CI is
unaffected.
Rescope this PR to the dry async unit-test migration only.

Remove the live-backend wiring (test/integration/_harness, the
vitest.live.config.ts, the test:live script, Makefile + local-env.yml)
and the label-gated live-tests workflow. The live workflow returns in a
follow-up PR (as a sharded GitHub-hosted matrix, since a single job
caps at 6h).

Also drop the pre-existing integration suite (test/integration specs,
fixtures, _mocks, vitest.integration.config.ts, the compact:integration
and test:integration scripts): it uses the old synchronous simulator
API and does not run against the async @openzeppelin/compact-simulator
0.2.0, and it is not wired into CI. It will be re-added, migrated,
alongside the live workflow.

What remains: the per-module simulator + spec async migration and the
@openzeppelin/compact-simulator ^0.2.0 pin.
@0xisk 0xisk changed the title test: migrate suites to async backend-aware simulator (dry + live) test: migrate unit suites to async backend-aware simulator (dry) Jun 25, 2026
0xisk added 2 commits June 25, 2026 11:08
Restore the test/integration specs, fixtures, _mocks,
vitest.integration.config.ts, and the compact:integration /
test:integration scripts that the previous commit removed. They predate
this PR (they live on main) and are not part of this migration, so the
PR leaves them untouched.

Only the live-backend wiring stays removed and deferred to a follow-up
PR: test/integration/_harness, vitest.live.config.ts, the test:live
script, Makefile, local-env.yml, and the live-tests workflow.
Bring back the local live-backend testing setup removed earlier and
extend it to the integration suite. The live CI workflow stays out (it
returns in a follow-up PR as a sharded matrix).

* Restore the local stack (Makefile + local-env.yml), vitest.live.config.ts,
  the test/integration/_harness live wiring, and the test:live script — so
  `make env-up && yarn test:live` runs the unit specs on a real node.
* Migrate the integration suite (composedTokens + sharedInitCollision
  fixtures, initStateIsolation spec) to the async backend-aware simulator,
  matching the unit migration: static create, awaited circuits,
  rejects/resolves assertions. Add artifactName so the live harness can
  deploy each composed contract.
* Add integration live wiring: vitest.integration.live.config.ts, plus
  compact:integration:live (real ZK keys, no SKIP_ZK) and test:integration:live.

The integration suite shares the unit live harness unchanged — the harness
deploys any contract by artifactName, so no live-backend code is added.

Validated dry: integration spec 5/5 green against the async API.
@0xisk 0xisk changed the title test: migrate unit suites to async backend-aware simulator (dry) test: migrate unit + integration suites to async backend-aware simulator (dry + live) Jun 25, 2026
The integration suite is out of scope for this PR. Revert the
integration fixtures (composedTokens, sharedInitCollision) and the
initStateIsolation spec to their main versions, and remove the
integration live wiring: vitest.integration.live.config.ts and the
test:integration:live / compact:integration:live scripts.

The integration tests are now untouched relative to main. Migrating
them to the async simulator (and wiring them for live) is deferred to
the follow-up PR alongside the live CI workflow.

Kept: the unit async migration and the local unit live setup
(test:live, vitest.live.config.ts, the _harness backend, Makefile,
local-env.yml).
@0xisk 0xisk changed the title test: migrate unit + integration suites to async backend-aware simulator (dry + live) test: migrate unit suites to async backend-aware simulator (dry + live) Jun 25, 2026
Reduce this PR to the migration itself: only the existing
simulator-using files (per-module simulators + specs) move to the async
backend-aware @openzeppelin/compact-simulator 0.2.0, plus the dependency
bump. No new files.

Remove the live scaffolding added earlier — the test/integration/_harness
backend, vitest.live.config.ts, the test:live script, Makefile, and
local-env.yml. The local live setup and the live CI workflow belong in a
follow-up PR.

The integration suite is untouched relative to main.
@0xisk 0xisk changed the title test: migrate unit suites to async backend-aware simulator (dry + live) test: migrate unit suites to async backend-aware simulator Jun 25, 2026
Bring back the local unit live-testing setup: the `test:live` script,
vitest.live.config.ts, the test/integration/_harness live backend, and
make env-up / local-env.yml. `make env-up && yarn test:live` runs the
migrated unit specs against a real node (MIDNIGHT_BACKEND=live).

The integration suite stays untouched relative to main, and the live CI
workflow is still deferred to a follow-up PR.
@0xisk 0xisk changed the title test: migrate unit suites to async backend-aware simulator test: migrate unit suites to async backend-aware simulator (dry + live) Jun 25, 2026
Drop the live-backend harness directory. Default dry CI is unaffected
(it uses vitest.config.ts, not the live setup).
@0xisk 0xisk marked this pull request as ready for review June 25, 2026 10:04
@0xisk 0xisk requested review from a team as code owners June 25, 2026 10:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (3)
contracts/src/token/test/simulators/FungibleTokenSimulator.ts (1)

309-313: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Await the private-state write before returning.

Line 313 starts the state update but injectSecretKey resolves immediately. In the async/live backend path, the next awaited circuit call can observe the previous key.

Proposed fix
     injectSecretKey: async (
       newSK: Uint8Array,
     ): Promise<FungibleTokenPrivateState> => {
       const updatedState = FungibleTokenPrivateState.withSecretKey(newSK);
-      this.setPrivateState(updatedState);
+      await this.setPrivateState(updatedState);
       return updatedState;
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/token/test/simulators/FungibleTokenSimulator.ts` around lines
309 - 313, The async injectSecretKey flow in FungibleTokenSimulator is returning
before the private state write completes, which can let later circuit calls see
stale data. Update injectSecretKey to await the private-state update triggered
by setPrivateState on FungibleTokenPrivateState, and make sure the method only
resolves after the new secret key has been persisted.
contracts/src/token/test/simulators/NonFungibleTokenSimulator.ts (1)

465-469: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Await the private-state write before returning.

Line 469 should not fire-and-forget the private-state update; callers rely on await injectSecretKey(...) before authorization-sensitive circuit calls.

Proposed fix
     injectSecretKey: async (
       newSK: Uint8Array,
     ): Promise<NonFungibleTokenPrivateState> => {
       const updatedState = NonFungibleTokenPrivateState.withSecretKey(newSK);
-      this.setPrivateState(updatedState);
+      await this.setPrivateState(updatedState);
       return updatedState;
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/token/test/simulators/NonFungibleTokenSimulator.ts` around
lines 465 - 469, The injectSecretKey method in NonFungibleTokenSimulator is
updating private state without waiting for the write to complete, which breaks
callers that rely on awaiting it before sensitive operations. Update
injectSecretKey so the setPrivateState call is awaited before returning the
updated NonFungibleTokenPrivateState, keeping the async contract consistent and
ensuring the secret-key state is fully persisted first.
contracts/src/token/test/simulators/MultiTokenSimulator.ts (1)

254-258: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Await the private-state write before returning.

Line 258 should wait for the backend private-state update; otherwise await injectSecretKey(...) does not guarantee the next circuit uses the injected key.

Proposed fix
     injectSecretKey: async (
       newSK: Uint8Array,
     ): Promise<MultiTokenPrivateState> => {
       const updatedState = MultiTokenPrivateState.withSecretKey(newSK);
-      this.setPrivateState(updatedState);
+      await this.setPrivateState(updatedState);
       return updatedState;
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/token/test/simulators/MultiTokenSimulator.ts` around lines 254
- 258, The injectSecretKey method in MultiTokenSimulator returns before the
private-state update is committed, so awaiting it does not guarantee the
injected key is available for the next circuit. Update injectSecretKey to await
the backend write via setPrivateState before returning the updated
MultiTokenPrivateState, and keep the flow anchored around
MultiTokenPrivateState.withSecretKey and setPrivateState so the async contract
is honored.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contracts/src/access/test/AccessControl.test.ts`:
- Around line 153-155: `AccessControlSimulator.assertOnlyRole()` returns a
resolved promise value rather than throwing, so the `AccessControl.test.ts`
assertion using `resolves.not.toThrow()` is the wrong matcher. Update the test
around `assertOnlyRole(OPERATOR_ROLE_1)` to use a direct `await` on the call, or
explicitly assert the resolved empty array, and apply the same correction to any
other `assertOnlyRole` checks in this file.

In `@contracts/src/access/test/Ownable.test.ts`:
- Around line 164-165: The assertions in Ownable.test.ts are using the wrong
matcher for assertOnlyOwner, since ownable.assertOnlyOwner() resolves to a value
rather than throwing; update the affected test cases to use a direct await or
assert the resolved value instead of resolves.not.toThrow(). Make this change in
the test block around assertOnlyOwner and apply the same correction to the
repeated pattern elsewhere in the Ownable.test suite.

In `@contracts/src/access/test/ShieldedAccessControl.test.ts`:
- Around line 159-166: The success-case assertions in
ShieldedAccessControl.test.ts are using resolves.not.toThrow() on
promise-returning circuit calls, which checks the wrong subject. Update the
repeated test cases in the ShieldedAccessControlSimulator test block to await
the circuit call directly, and if needed assert the returned value or resulting
state instead of using toThrow. Use the circuit invocation inside the "%s should
succeed" test as the reference point for the fix.

In `@contracts/src/access/test/ZOwnablePK.test.ts`:
- Around line 226-240: The transfer ownership tests are passing the commitment
into transferOwnership() instead of the new owner ID, so they do not validate
the intended id-to-commitment flow. Update the calls in ZOwnablePK.test.ts to
use the newOwnerId value when invoking
ownable.as('OWNER').transferOwnership(...) and keep the unauthorized case
aligned with the same API shape. This will ensure the transferOwnership and
_transferOwnership paths, including the counter bump assertion, are exercised
correctly.
- Around line 138-140: The assertions around ZOwnablePK._computeOwnerId and
assertOnlyOwner are using the wrong matcher because these methods return a
Uint8Array, not a thrown promise; update the tests to await the call directly or
assert on the returned bytes, using the ownable._computeOwnerId and
assertOnlyOwner symbols to locate the cases. Also fix transferOwnership to pass
the computed owner id/commitment value, so the newOwnerCommitment argument uses
the previously derived id value (such as newIdHash or repeatedId) instead of the
mismatched placeholder.

In `@contracts/src/token/test/MultiToken.test.ts`:
- Around line 183-185: The `balanceOf` assertion in `MultiToken.test.ts` is
using the wrong matcher because `token.balanceOf(OWNER.either, TOKEN_ID)`
returns a `bigint`, not a function that can throw. Update the expectation in the
`balanceOf` test to assert on the resolved value directly with a value matcher,
and use the `balanceOf` call itself as the unique location to replace
`resolves.not.toThrow()` with an assertion against the expected bigint result.

In `@contracts/src/token/test/nonFungibleToken.test.ts`:
- Around line 630-632: The test is asserting on the resolved promise value with
resolves.not.toThrow(), which does not validate the async circuit call
correctly. Update the affected cases in nonFungibleToken.test.ts around
token.transferFrom and the other matching circuit assertions to await the async
call directly instead of wrapping it in expect(...).resolves.not.toThrow(), then
keep the existing post-state checks to verify behavior after execution.

In `@local-env.yml`:
- Line 4: Pin the Docker image references in local-env.yml so the test:live
stack is reproducible and compatible with midnightntwrk/midnight-node:0.22.2;
replace the floating midnightntwrk/proof-server:latest and
midnightntwrk/indexer-standalone:latest entries with the documented compatible
tags midnightntwrk/proof-server:8.0.3 and
midnightntwrk/indexer-standalone:4.0.1, or equivalent digests, using the
existing service definitions as the location to update.
- Line 5: The container command is being passed as one argv token, so split the
`command` entry for the proof server into separate arguments using the existing
`command` field so it starts correctly. Also replace the floating
`midnightntwrk/proof-server:latest` and
`midnightntwrk/indexer-standalone:latest` image tags with fixed versioned tags
in the same local stack config to keep the environment reproducible.

In `@Makefile`:
- Around line 8-14: The env-up target currently returns immediately after docker
compose up -d, so it can finish before proof-server, indexer, and node are
actually ready. Update the env-up recipe to wait for the stack to become healthy
before exiting, using the existing SERVCES/COMPOSE_FILE flow and any
health-check or readiness mechanism already available in the compose setup. Keep
the log streaming behavior, but make sure the env-up target does not complete
until the key services are reported healthy and ready for test:live.

---

Nitpick comments:
In `@contracts/src/token/test/simulators/FungibleTokenSimulator.ts`:
- Around line 309-313: The async injectSecretKey flow in FungibleTokenSimulator
is returning before the private state write completes, which can let later
circuit calls see stale data. Update injectSecretKey to await the private-state
update triggered by setPrivateState on FungibleTokenPrivateState, and make sure
the method only resolves after the new secret key has been persisted.

In `@contracts/src/token/test/simulators/MultiTokenSimulator.ts`:
- Around line 254-258: The injectSecretKey method in MultiTokenSimulator returns
before the private-state update is committed, so awaiting it does not guarantee
the injected key is available for the next circuit. Update injectSecretKey to
await the backend write via setPrivateState before returning the updated
MultiTokenPrivateState, and keep the flow anchored around
MultiTokenPrivateState.withSecretKey and setPrivateState so the async contract
is honored.

In `@contracts/src/token/test/simulators/NonFungibleTokenSimulator.ts`:
- Around line 465-469: The injectSecretKey method in NonFungibleTokenSimulator
is updating private state without waiting for the write to complete, which
breaks callers that rely on awaiting it before sensitive operations. Update
injectSecretKey so the setPrivateState call is awaited before returning the
updated NonFungibleTokenPrivateState, keeping the async contract consistent and
ensuring the secret-key state is fully persisted first.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e16b1f8f-99ff-41f0-a4dd-328c39e1e4d2

📥 Commits

Reviewing files that changed from the base of the PR and between 7582746 and a2b22b3.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (51)
  • Makefile
  • contracts/package.json
  • contracts/src/access/test/AccessControl.test.ts
  • contracts/src/access/test/Ownable.test.ts
  • contracts/src/access/test/ShieldedAccessControl.test.ts
  • contracts/src/access/test/ZOwnablePK.test.ts
  • contracts/src/access/test/simulators/AccessControlSimulator.ts
  • contracts/src/access/test/simulators/OwnableSimulator.ts
  • contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts
  • contracts/src/access/test/simulators/ZOwnablePKSimulator.ts
  • contracts/src/archive/test/ShieldedToken.test.ts
  • contracts/src/archive/test/simulators/ShieldedTokenSimulator.ts
  • contracts/src/multisig/test/Forwarder.test.ts
  • contracts/src/multisig/test/ForwarderPrivate.test.ts
  • contracts/src/multisig/test/ProposalManager.test.ts
  • contracts/src/multisig/test/ShieldedMultiSig.test.ts
  • contracts/src/multisig/test/ShieldedMultiSigV2.test.ts
  • contracts/src/multisig/test/ShieldedMultiSigV3.test.ts
  • contracts/src/multisig/test/ShieldedTreasury.test.ts
  • contracts/src/multisig/test/Signer.test.ts
  • contracts/src/multisig/test/SignerManager.test.ts
  • contracts/src/multisig/test/presets/ForwarderPrivate.test.ts
  • contracts/src/multisig/test/presets/ForwarderShielded.test.ts
  • contracts/src/multisig/test/presets/ForwarderUnshielded.test.ts
  • contracts/src/multisig/test/simulators/MockForwarderPrivateSimulator.ts
  • contracts/src/multisig/test/simulators/MockForwarderShieldedSimulator.ts
  • contracts/src/multisig/test/simulators/MockForwarderUnshieldedSimulator.ts
  • contracts/src/multisig/test/simulators/ProposalManagerSimulator.ts
  • contracts/src/multisig/test/simulators/ShieldedMultiSigSimulator.ts
  • contracts/src/multisig/test/simulators/ShieldedMultiSigV2Simulator.ts
  • contracts/src/multisig/test/simulators/ShieldedMultiSigV3Simulator.ts
  • contracts/src/multisig/test/simulators/ShieldedTreasurySimulator.ts
  • contracts/src/multisig/test/simulators/SignerManagerSimulator.ts
  • contracts/src/multisig/test/simulators/SignerSimulator.ts
  • contracts/src/multisig/test/simulators/presets/ForwarderPrivateSimulator.ts
  • contracts/src/multisig/test/simulators/presets/ForwarderShieldedSimulator.ts
  • contracts/src/multisig/test/simulators/presets/ForwarderUnshieldedSimulator.ts
  • contracts/src/security/test/Initializable.test.ts
  • contracts/src/security/test/Pausable.test.ts
  • contracts/src/security/test/simulators/InitializableSimulator.ts
  • contracts/src/security/test/simulators/PausableSimulator.ts
  • contracts/src/token/test/FungibleToken.test.ts
  • contracts/src/token/test/MultiToken.test.ts
  • contracts/src/token/test/nonFungibleToken.test.ts
  • contracts/src/token/test/simulators/FungibleTokenSimulator.ts
  • contracts/src/token/test/simulators/MultiTokenSimulator.ts
  • contracts/src/token/test/simulators/NonFungibleTokenSimulator.ts
  • contracts/src/utils/test/simulators/UtilsSimulator.ts
  • contracts/src/utils/test/utils.test.ts
  • contracts/vitest.live.config.ts
  • local-env.yml

Comment on lines +153 to +155
await expect(
accessControl.assertOnlyRole(OPERATOR_ROLE_1),
).resolves.not.toThrow();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the test file and related symbols
ast-grep outline contracts/src/access/test/AccessControl.test.ts --view expanded || true

# Find all occurrences of the matcher pattern in the repo
rg -n "resolves\.not\.toThrow\(|resolves\.toThrow\(|not\.toThrow\(" contracts/src || true

# Locate the implementation/sig of assertOnlyRole and nearby helpers
rg -n "assertOnlyRole" contracts/src || true

Repository: OpenZeppelin/compact-contracts

Length of output: 10876


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' contracts/src/access/test/simulators/AccessControlSimulator.ts
printf '\n---\n'
sed -n '140,220p' contracts/src/access/test/AccessControl.test.ts
printf '\n---\n'
sed -n '1,120p' contracts/src/access/AccessControl.compact

Repository: OpenZeppelin/compact-contracts

Length of output: 13656


Replace resolves.not.toThrow() with a direct await

AccessControlSimulator.assertOnlyRole() returns Promise<[]>, so toThrow is the wrong matcher here. Use await accessControl.assertOnlyRole(OPERATOR_ROLE_1); or assert the resolved [] explicitly. This same pattern appears elsewhere in the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/access/test/AccessControl.test.ts` around lines 153 - 155,
`AccessControlSimulator.assertOnlyRole()` returns a resolved promise value
rather than throwing, so the `AccessControl.test.ts` assertion using
`resolves.not.toThrow()` is the wrong matcher. Update the test around
`assertOnlyRole(OPERATOR_ROLE_1)` to use a direct `await` on the call, or
explicitly assert the resolved empty array, and apply the same correction to any
other `assertOnlyRole` checks in this file.

Comment on lines +164 to 165
await expect(ownable.assertOnlyOwner()).resolves.not.toThrow();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

set -e
git ls-files 'contracts/src/access/test/*' 'contracts/src/access/*' | sed -n '1,200p'
printf '\n--- Ownable.test.ts excerpt ---\n'
nl -ba contracts/src/access/test/Ownable.test.ts | sed -n '130,210p'
printf '\n--- search for assertOnlyOwner definition/usages ---\n'
rg -n "assertOnlyOwner\\(" contracts/src -S
printf '\n--- search for resolves.not.toThrow usages ---\n'
rg -n "resolves\.not\.toThrow\(" contracts/src -S
printf '\n--- search for toThrow matcher around promises ---\n'
rg -n "toThrow\(" contracts/src -S

Repository: OpenZeppelin/compact-contracts

Length of output: 50386


🏁 Script executed:

set -e
git ls-files 'contracts/src/access/test/*' 'contracts/src/access/*' | sed -n '1,200p'
printf '\n--- Ownable.test.ts excerpt ---\n'
nl -ba contracts/src/access/test/Ownable.test.ts | sed -n '130,210p'
printf '\n--- search for assertOnlyOwner definition/usages ---\n'
rg -n "assertOnlyOwner\\(" contracts/src -S
printf '\n--- search for resolves.not.toThrow usages ---\n'
rg -n "resolves\.not\.toThrow\(" contracts/src -S
printf '\n--- search for toThrow matcher around promises ---\n'
rg -n "toThrow\(" contracts/src -S

Repository: OpenZeppelin/compact-contracts

Length of output: 50386


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
path = Path('contracts/src/access/test/Ownable.test.ts')
for i, line in enumerate(path.read_text().splitlines(), 1):
    if 155 <= i <= 170:
        print(f"{i:4d}: {line}")
PY

Repository: OpenZeppelin/compact-contracts

Length of output: 888


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
path = Path('contracts/src/access/test/Ownable.test.ts')
for i, line in enumerate(path.read_text().splitlines(), 1):
    if 155 <= i <= 170:
        print(f"{i:4d}: {line}")
PY

Repository: OpenZeppelin/compact-contracts

Length of output: 888


🌐 Web query:

Vitest expect resolves toThrow matcher resolved promise value not a function docs

💡 Result:

In Vitest, the expect().resolves and expect().rejects modifiers expect a promise as their input [1][2]. If you pass a function instead of a promise, you will encounter a "TypeError: You must provide a Promise to expect when using.resolves (or.rejects), not 'function'" [3][2]. The toThrow matcher is designed to assert that a function throws an error when called [1]. Because it needs to execute a function to catch its thrown error, the usage patterns for synchronous and asynchronous code differ: 1. Synchronous Code: Wrap the code in a function so that toThrow can invoke it safely [1]. Example: expect( => mySyncFunction).toThrow [1] 2. Asynchronous Code: Use the rejects modifier, which is specifically designed to handle promises and works similarly to toThrow for async errors [1]. Example: await expect(myAsyncPromise).rejects.toThrow [1] Passing a function directly to expect().resolves is not supported because resolves is intended to wait for a promise to settle and then validate the resulting value, not to execute a function and catch its errors [4][2]. If you need to test that an asynchronous operation does not throw, you should await the promise normally and use not.toThrow() only if you are checking for synchronous errors within the resolved value, or simply ensure the promise resolves without rejection [1][5].

Citations:


Use an await or value assertion here instead of resolves.not.toThrow()
ownable.assertOnlyOwner() resolves to [], so toThrow() is the wrong matcher. Use await ownable.assertOnlyOwner() or assert the resolved value directly; the same pattern repeats in this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/access/test/Ownable.test.ts` around lines 164 - 165, The
assertions in Ownable.test.ts are using the wrong matcher for assertOnlyOwner,
since ownable.assertOnlyOwner() resolves to a value rather than throwing; update
the affected test cases to use a direct await or assert the resolved value
instead of resolves.not.toThrow(). Make this change in the test block around
assertOnlyOwner and apply the same correction to the repeated pattern elsewhere
in the Ownable.test suite.

Comment on lines +159 to +166
)('%s should succeed', async (circuitName, args) => {
await expect(
(
contract[circuitName as keyof ShieldedAccessControlSimulator] as (
...a: unknown[]
) => unknown
)(...args);
}).not.toThrow();
) => Promise<unknown>
)(...args),
).resolves.not.toThrow();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and inspect the relevant section with line numbers.
git ls-files contracts/src/access/test/ShieldedAccessControl.test.ts
wc -l contracts/src/access/test/ShieldedAccessControl.test.ts
sed -n '140,185p' contracts/src/access/test/ShieldedAccessControl.test.ts

# Search for similar matcher usage in this file and nearby tests.
rg -n "\.resolves\.not\.toThrow\(\)|\.rejects\.toThrow\(\)|toThrow\(\)" contracts/src/access/test/ShieldedAccessControl.test.ts contracts/src/access/test -g '*.ts'

Repository: OpenZeppelin/compact-contracts

Length of output: 9205


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant test file and surrounding matcher patterns.
git ls-files contracts/src/access/test/ShieldedAccessControl.test.ts
wc -l contracts/src/access/test/ShieldedAccessControl.test.ts
sed -n '140,185p' contracts/src/access/test/ShieldedAccessControl.test.ts

# Find all occurrences of the same matcher shape in this file and nearby tests.
rg -n "\.resolves\.not\.toThrow\(\)|\.rejects\.toThrow\(\)|toThrow\(\)" contracts/src/access/test/ShieldedAccessControl.test.ts contracts/src/access/test -g '*.ts'

Repository: OpenZeppelin/compact-contracts

Length of output: 9205


🌐 Web query:

Vitest expect resolves not.toThrow promise matcher semantics

💡 Result:

In Vitest, using expect(...).resolves.not.toThrow carries specific semantic and technical constraints based on the type of input provided. 1. Expectation Semantics: The.resolves modifier is intended for use with Promises [1]. It unwraps the resolved value of a promise so that subsequent matchers can be applied [1]. Therefore, expect(promise).resolves.not.toThrow asserts that the promise successfully resolves without the underlying execution throwing an error [2]. 2. Input Requirements: Historically, Vitest required a Promise as the argument for expect when using.resolves [3]. Attempting to pass a function (e.g., expect(async =>...).resolves.not.toThrow) would typically result in a TypeError stating that a Promise must be provided [4][3]. 3. Recent Developments: As of mid-2026, there have been active community efforts and pull requests (such as #10284, #10342, and #10403) to generalize support for functions within.resolves, similar to how they are supported in.rejects [4]. While.rejects inherently requires a function to catch and inspect thrown errors [1][5],.resolves is primarily for handling resolved values. If you encounter issues, ensure you are testing a Promise. If you are testing an asynchronous function, common practice is to await the function execution first, or use.rejects to verify expected failures [1][2]. For verifying that code does not throw, ensuring the promise resolves successfully is the standard approach using.resolves [2].

Citations:


🌐 Web query:

Vitest expect resolves not.toThrow promise matcher semantics

💡 Result:

In Vitest, the .resolves matcher is designed to unwrap a successfully resolved promise and assert that the resulting value matches a given condition [1][2]. The .toThrow matcher, however, is specifically designed to assert that a function throws an error when executed [1][3]. When using expect(...).resolves.not.toThrow(), Vitest attempts to apply toThrow to the resolved value of the promise, rather than treating that value as a function to be executed [4]. Consequently, this matcher chain is generally inappropriate for testing that an asynchronous process does not throw, because toThrow expects a function as its input [3], not a resolved promise value [1][4]. Key technical points regarding these semantics include: 1. Promise Handling vs. Function Execution: .resolves unwraps the promise and passes the settled value to the next matcher [2][4]. If you pass a value that is not a function to .toThrow, the matcher will fail or behave unexpectedly because it cannot execute the value to catch an error [3][4]. 2. Alternative for Async Exceptions: To verify that an asynchronous operation does not throw an error, the standard approach is to simply await the promise [2]. If the promise rejects, the test will fail automatically unless you explicitly handle the rejection (e.g., with a try/catch block or by using .rejects.toThrow()) [5][2]. 3. Intended Use of.rejects: The .rejects matcher is the intended way to test for asynchronous exceptions [2]. It is specifically built to catch and assert against the rejection reason of a promise [1][3]. If you need to ensure an async operation finishes without error, awaiting the promise directly is sufficient, as any rejection will cause the test to fail [2]. If you specifically wish to verify that an async function does not throw, you should ensure that the function itself is awaited during the test execution, rather than attempting to chain not.toThrow() after a .resolves call [5].

Citations:


🌐 Web query:

Vitest expect resolves not.toThrow promise matcher semantics

💡 Result:

In Vitest, the .resolves and .rejects matchers are designed to handle asynchronous code by unwrapping promises [1][2]. Regarding not.toThrow, the semantics are as follows: 1. Primary Usage: The .resolves matcher is intended to be used with a promise [1][3]. When you use await expect(promise).resolves.not.toThrow(), Vitest waits for the promise to resolve successfully; if it resolves, the assertion passes because no error was thrown during the resolution of the promise [1][4]. 2. Function Parameter Support: Historically, passing a function to expect().resolves (e.g., expect(async () => { ... }).resolves.not.toThrow()) was not supported and would throw a TypeError stating that you must provide a Promise, not a function [5][3]. While there has been recent community discussion and development (as of mid-2026) regarding aligning this behavior with Jest to allow functions in .resolves, the standard and stable way to assert that an asynchronous operation does not throw is to execute the function and pass the resulting promise to expect [5][3]. 3. Contrast with .rejects: The .rejects matcher is explicitly designed to handle functions that might throw (or return rejected promises) [1][2]. This is why await expect(async () => { ... }).rejects.toThrow() works—rejects captures and unwraps the rejection [1][2]. If you need to verify that an asynchronous function does not throw, the standard practice is to await the function call and assert against the resulting promise: await expect(myAsyncFunction).resolves.not.toThrow; [4] If you use a function as an argument, it is generally recommended to await the execution of that function before asserting, or use the .rejects pattern if you expect an error [1][4].

Citations:


🌐 Web query:

Vitest expect resolves not.toThrow promise matcher semantics

💡 Result:

In Vitest, expect(...).resolves.not.toThrow() is a valid assertion, but it behaves differently from the standard toThrow matcher used on functions. 1. Semantics of.resolves: The .resolves modifier is designed to unwrap a promise [1]. When you use await expect(promise).resolves.not.toThrow(), Vitest awaits the promise. If the promise resolves successfully, the .not.toThrow() matcher then checks the resolved value (or the lack of a thrown error during the process) [1][2]. 2. Important Constraint: Unlike .rejects, which is specifically intended for testing that an operation throws an error (and therefore accepts a function to execute), .resolves is intended to assert the value a promise resolves to [1]. Historically, Vitest required a Promise as the input to expect() when using .resolves [3][4]. Passing a function to expect(async () => { ... }).resolves.not.toThrow() has been a source of technical inconsistency, often resulting in a TypeError (e.g., "You must provide a Promise to expect when using.resolves, not 'function'") [3][4]. 3. How to use correctly: If you want to ensure an asynchronous operation completes without throwing an error: - If the operation is a Promise, simply use: await expect(myPromise).resolves.not.toThrow() [5][2]. - If the operation is a function that returns a Promise, you should invoke it first to get the Promise: await expect(myFunction()).resolves.not.toThrow() [6][2]. If your goal is to test that a function does not throw an error synchronously, you should avoid .resolves entirely and use the standard: expect(() => myFunction()).not.toThrow() [7][8]. Current development in Vitest (as of mid-2026) has seen discussions and efforts to improve consistency, such as allowing functions in .resolves to match the behavior of .rejects, but the standard and safest approach remains passing the resolved Promise to expect() when using the .resolves modifier [3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Identify the Vitest version used by this repository and inspect related test patterns.
git ls-files package.json pnpm-lock.yaml yarn.lock package-lock.json contracts/package.json contracts/pnpm-lock.yaml contracts/yarn.lock contracts/package-lock.json

rg -n '"vitest"|vitest' package.json contracts/package.json pnpm-lock.yaml yarn.lock package-lock.json contracts/pnpm-lock.yaml contracts/yarn.lock contracts/package-lock.json -g '!*node_modules*'

# Inspect a couple of neighboring assertions in the same file for comparison.
sed -n '150,175p' contracts/src/access/test/ShieldedAccessControl.test.ts
sed -n '320,360p' contracts/src/access/test/ShieldedAccessControl.test.ts

Repository: OpenZeppelin/compact-contracts

Length of output: 3599


🌐 Web query:

site:vitest.dev/api/expect toThrow resolves function matcher

💡 Result:

The toThrow (and its alias toThrowError) matcher is not supported with the .resolves modifier in testing frameworks like Jest or Vitest [1]. The toThrow matcher is designed to assert that a function throws an error when executed synchronously [2][1]. Because .resolves is used to unwrap a promise and assert its resolved value, it is conceptually incompatible with testing for thrown errors [1]. To test that an asynchronous operation throws or rejects, you should use the .rejects modifier instead of .resolves [2][3][1]. Example of correct usage for async error handling: await expect(myAsyncFunction).rejects.toThrow; If you attempt to use expect(...).resolves.toThrow(), the test runner will typically indicate that the matcher is not supported or that the assertion is invalid [1].

Citations:


Replace resolves.not.toThrow() in ShieldedAccessControl.test.ts

toThrow does not belong on these promise assertions: the circuits resolve to values, so this chain is checking the wrong subject. Use a plain await for success cases, or assert the returned value/state explicitly. Update the repeated occurrences in this file as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/access/test/ShieldedAccessControl.test.ts` around lines 159 -
166, The success-case assertions in ShieldedAccessControl.test.ts are using
resolves.not.toThrow() on promise-returning circuit calls, which checks the
wrong subject. Update the repeated test cases in the
ShieldedAccessControlSimulator test block to await the circuit call directly,
and if needed assert the returned value or resulting state instead of using
toThrow. Use the circuit invocation inside the "%s should succeed" test as the
reference point for the fix.

Comment on lines +138 to +140
await expect(
ownable._computeOwnerId(eitherOwner, randomByteArray),
).resolves.not.toThrow();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== File outline =="
ast-grep outline contracts/src/access/test/ZOwnablePK.test.ts --view expanded || true

echo
echo "== Relevant lines around 120-170 =="
sed -n '120,170p' contracts/src/access/test/ZOwnablePK.test.ts | cat -n

echo
echo "== Simulator signature =="
ast-grep outline contracts/src/access/test/simulators/ZOwnablePKSimulator.ts --view expanded || true

echo
echo "== transferOwnership occurrences =="
rg -n "transferOwnership\(" contracts/src/access/test -g '*.ts'

Repository: OpenZeppelin/compact-contracts

Length of output: 7173


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('contracts/src/access/test/ZOwnablePK.test.ts')
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if 130 <= i <= 150:
        print(f"{i:4}: {line}")
PY

Repository: OpenZeppelin/compact-contracts

Length of output: 942


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ZOwnablePK.test.ts excerpt =="
nl -ba contracts/src/access/test/ZOwnablePK.test.ts | sed -n '130,155p'

echo
echo "== search for `.resolves.not.toThrow()` =="
rg -n "\.resolves\.not\.toThrow\(\)" contracts/src/access/test/ZOwnablePK.test.ts

echo
echo "== _computeOwnerId definition =="
rg -n "computeOwnerId|_computeOwnerId" contracts/src/access/test contracts/src/access -g '*.ts' -A 4 -B 4

echo
echo "== simulator transferOwnership signature =="
nl -ba contracts/src/access/test/simulators/ZOwnablePKSimulator.ts | sed -n '1,120p'

Repository: OpenZeppelin/compact-contracts

Length of output: 241


🌐 Web query:

Vitest expect.resolves toThrow matcher semantics function only docs

💡 Result:

In Vitest, using expect.resolves.toThrow is not supported, and attempting to do so will result in a TypeError [1][2]. The matcher.resolves is designed to unwrap a promise and assert its resolved value; if you attempt to use it with.toThrow, Vitest will throw an error stating: "You must provide a Promise to expect when using.resolves, not 'function'" [1][2]. This is because.resolves expects an object with a.then method (a promise), whereas.toThrow is a synchronous matcher that expects a function to be passed to expect [3][2]. To test that an asynchronous function throws an error, you should use.rejects.toThrow instead [3][4]: await expect(asyncFunction).rejects.toThrow; If you have a function that returns a promise but you want to ensure it completes successfully without throwing, you can use.resolves.not.toThrow in some contexts [5], but you must ensure the subject passed to expect is a promise, not a function [1][2]. If your goal is to test a function that throws synchronously, use expect( => function).toThrow [3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('contracts/src/access/test/ZOwnablePK.test.ts')
lines = p.read_text().splitlines()
for start,end in [(170,270),(1,80)]:
    print(f"== lines {start}-{end} ==")
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:4}: {lines[i-1]}")
    print()
PY

Repository: OpenZeppelin/compact-contracts

Length of output: 7041


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('contracts/src/access/test/ZOwnablePK.test.ts')
lines = p.read_text().splitlines()

for start, end in [(170, 270), (1, 80)]:
    print(f"== lines {start}-{end} ==")
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:4}: {lines[i-1]}")
    print()
PY

Repository: OpenZeppelin/compact-contracts

Length of output: 7041


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('contracts/src/access/test/ZOwnablePK.test.ts')
lines = p.read_text().splitlines()
for start, end in [(1, 70), (180, 250)]:
    print(f"== lines {start}-{end} ==")
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:4}: {lines[i-1]}")
    print()
PY

Repository: OpenZeppelin/compact-contracts

Length of output: 5515


Replace the matcher and pass the owner id
await expect(ownable._computeOwnerId(...)).resolves.not.toThrow() is the wrong assertion here: _computeOwnerId() resolves to a Uint8Array, so assert on the bytes or just await the call. The same matcher pattern later on assertOnlyOwner() has the same problem.

transferOwnership(...) should receive the new owner id; the newOwnerCommitment argument later in the file should be newIdHash/repeatedId instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/access/test/ZOwnablePK.test.ts` around lines 138 - 140, The
assertions around ZOwnablePK._computeOwnerId and assertOnlyOwner are using the
wrong matcher because these methods return a Uint8Array, not a thrown promise;
update the tests to await the call directly or assert on the returned bytes,
using the ownable._computeOwnerId and assertOnlyOwner symbols to locate the
cases. Also fix transferOwnership to pass the computed owner id/commitment
value, so the newOwnerCommitment argument uses the previously derived id value
(such as newIdHash or repeatedId) instead of the mismatched placeholder.

Comment on lines +226 to +240
it('should fail when unauthorized transfers ownership', async () => {
await expect(
ownable.as('UNAUTHORIZED').transferOwnership(newOwnerCommitment),
).rejects.toThrow('ZOwnablePK: caller is not the owner');
});

/**
* @description More thoroughly tested in `_transferOwnership`
* */
it('should bump instance after transfer', () => {
const beforeInstance = ownable.getPublicState().ZOwnablePK__counter;
it('should bump instance after transfer', async () => {
const beforeInstance = (await ownable.getPublicState())
.ZOwnablePK__counter;

// Transfer
ownable.as(OWNER).transferOwnership(newOwnerCommitment);
await ownable.as('OWNER').transferOwnership(newOwnerCommitment);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pass the new owner ID into transferOwnership(), not the commitment.

transferOwnership(...) takes newOwnerId, but Lines 228 and 240 pass newOwnerCommitment. That means these tests can still pass without exercising the intended id-to-commitment path, especially the counter-bump case.

Suggested fix
-          ownable.as('UNAUTHORIZED').transferOwnership(newOwnerCommitment),
+          ownable.as('UNAUTHORIZED').transferOwnership(newIdHash),
@@
-        await ownable.as('OWNER').transferOwnership(newOwnerCommitment);
+        await ownable.as('OWNER').transferOwnership(newIdHash);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should fail when unauthorized transfers ownership', async () => {
await expect(
ownable.as('UNAUTHORIZED').transferOwnership(newOwnerCommitment),
).rejects.toThrow('ZOwnablePK: caller is not the owner');
});
/**
* @description More thoroughly tested in `_transferOwnership`
* */
it('should bump instance after transfer', () => {
const beforeInstance = ownable.getPublicState().ZOwnablePK__counter;
it('should bump instance after transfer', async () => {
const beforeInstance = (await ownable.getPublicState())
.ZOwnablePK__counter;
// Transfer
ownable.as(OWNER).transferOwnership(newOwnerCommitment);
await ownable.as('OWNER').transferOwnership(newOwnerCommitment);
it('should fail when unauthorized transfers ownership', async () => {
await expect(
ownable.as('UNAUTHORIZED').transferOwnership(newIdHash),
).rejects.toThrow('ZOwnablePK: caller is not the owner');
});
/**
* `@description` More thoroughly tested in `_transferOwnership`
* */
it('should bump instance after transfer', async () => {
const beforeInstance = (await ownable.getPublicState())
.ZOwnablePK__counter;
// Transfer
await ownable.as('OWNER').transferOwnership(newIdHash);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/access/test/ZOwnablePK.test.ts` around lines 226 - 240, The
transfer ownership tests are passing the commitment into transferOwnership()
instead of the new owner ID, so they do not validate the intended
id-to-commitment flow. Update the calls in ZOwnablePK.test.ts to use the
newOwnerId value when invoking ownable.as('OWNER').transferOwnership(...) and
keep the unauthorized case aligned with the same API shape. This will ensure the
transferOwnership and _transferOwnership paths, including the counter bump
assertion, are exercised correctly.

Comment on lines +183 to +185
await expect(
token.balanceOf(OWNER.either, TOKEN_ID),
).resolves.not.toThrow();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package.json vitest version =="
rg -n '"vitest"|resolves\.not\.toThrow|toThrow\(' package.json contracts/package.json . -g 'package.json' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.mjs' -g '*.cjs' | sed -n '1,120p'

echo
echo "== target file excerpt =="
cat -n contracts/src/token/test/MultiToken.test.ts | sed -n '160,200p'

Repository: OpenZeppelin/compact-contracts

Length of output: 15798


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
git ls-files | rg '(^|/)package\.json$|MultiToken\.test\.ts$|vitest'

echo
echo "== vitest usage in repo =="
rg -n "resolves\.not\.toThrow|resolves\.toThrow|toThrow\(" contracts/src -g '*.ts' -g '*.tsx' | sed -n '1,160p'

echo
echo "== target file around lines 183-185 =="
cat -n contracts/src/token/test/MultiToken.test.ts | sed -n '175,190p'

Repository: OpenZeppelin/compact-contracts

Length of output: 19679


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,240p' contracts/src/token/test/MultiToken.test.ts

Repository: OpenZeppelin/compact-contracts

Length of output: 7406


🌐 Web query:

Vitest 4.1.9 expect resolves toThrow matcher resolved value function bigint

💡 Result:

In Vitest, the.resolves matcher is used to assert on the fulfilled value of a promise, while.toThrow is used to assert that a function throws an error [1][2]. These are distinct mechanisms:.toThrow requires a function to be passed to expect so it can safely catch and inspect the thrown error [1][3]. Regarding your query: 1. Resolves and toThrow: You cannot directly combine.resolves.toThrow because.resolves expects a promise that fulfills to a value, not a function that throws [1][4][3]. If you need to test that an asynchronous function (returning a promise) rejects with a specific error, you should use.rejects.toThrow instead [1][2][3]. 2. BigInt support: Vitest's expect matchers, such as.toBeGreaterThan or.toEqual, generally support BigInt where appropriate, as they often rely on deep equality or standard comparison operators that handle BigInt [5][6][7]. If you are asserting that a function throws a BigInt or an error containing a BigInt,.toThrow supports deep equality checks, which can handle BigInt values [1][7]. 3. Function parameter support: As of mid-2026, there have been ongoing updates to Vitest regarding the types of values accepted by.resolves [4]. Historically,.resolves strictly required a Promise, while.rejects was more permissive with functions for compatibility with Jest [4][8]. Example of correct usage for async error testing: // Correct way to test an async function that throws await expect(myAsyncFunction).rejects.toThrow; // If you need to check for a specific thrown value (including BigInt) await expect(myAsyncFunction).rejects.toThrow(100n); // 100n is a BigInt If you are attempting to test that a promise resolves to a function that, when called, throws an error, you must first await the promise to retrieve the function, and then wrap that function in another expectation [9]: const myFunc = await myAsyncFunctionReturningFunc; expect( => myFunc).toThrow;

Citations:


🌐 Web query:

Vitest resolves.toThrow matcher on resolved promise value documentation

💡 Result:

In Vitest, you should not use.resolves.toThrow to test for thrown errors [1][2]. Instead, you must use.rejects.toThrow when asserting that a promise will reject [1][3]. The.resolves and.rejects helpers are designed to unwrap promises and apply subsequent matchers to the resolved value or the rejected reason, respectively [3]. Key points regarding this behavior: 1. Correct Usage for Errors: To assert that an asynchronous function or promise rejects with an error, use: await expect(promise).rejects.toThrow; [1][4] 2. Function Wrapping: Unlike synchronous toThrow assertions, which require wrapping the throwing code in a function (e.g., expect( => code).toThrow), async assertions using.rejects do not require this wrapping because the promise rejection itself is the signal that an error occurred [1][5]. 3. Misuse of.resolves: Using.resolves.toThrow does not work as expected because.resolves expects the promise to complete successfully, at which point toThrow will fail because no error was thrown [1][6]. While you may see code in internal test suites that uses.resolves.not.toThrow to verify that a promise resolves successfully without throwing, this is simply confirming the absence of an error during resolution [6][7]. Always remember to await the entire expect expression to ensure the assertion is properly executed and waited for by the test runner [8][3].

Citations:


Replace resolves.not.toThrow() with a value assertion. balanceOf(...) resolves to a bigint, so toThrow is the wrong matcher here.

Proposed fix
-      await expect(
-        token.balanceOf(OWNER.either, TOKEN_ID),
-      ).resolves.not.toThrow();
+      expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await expect(
token.balanceOf(OWNER.either, TOKEN_ID),
).resolves.not.toThrow();
expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/token/test/MultiToken.test.ts` around lines 183 - 185, The
`balanceOf` assertion in `MultiToken.test.ts` is using the wrong matcher because
`token.balanceOf(OWNER.either, TOKEN_ID)` returns a `bigint`, not a function
that can throw. Update the expectation in the `balanceOf` test to assert on the
resolved value directly with a value matcher, and use the `balanceOf` call
itself as the unique location to replace `resolves.not.toThrow()` with an
assertion against the expected bigint result.

Comment on lines +630 to +632
await expect(
token.transferFrom(OWNER.either, OWNER.either, TOKENID_1),
).resolves.not.toThrow();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant test file and matcher usages.
git ls-files 'contracts/src/token/test/nonFungibleToken.test.ts'
rg -n "resolves\.not\.toThrow|toThrow\(" contracts/src/token/test/nonFungibleToken.test.ts

Repository: OpenZeppelin/compact-contracts

Length of output: 4052


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the surrounding test cases around the reported lines.
sed -n '600,700p' contracts/src/token/test/nonFungibleToken.test.ts
sed -n '1180,1335p' contracts/src/token/test/nonFungibleToken.test.ts

Repository: OpenZeppelin/compact-contracts

Length of output: 10529


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'contracts/src/token/test/nonFungibleToken.test.ts'
rg -n "resolves\.not\.toThrow|toThrow\(" contracts/src/token/test/nonFungibleToken.test.ts

Repository: OpenZeppelin/compact-contracts

Length of output: 4052


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '600,700p' contracts/src/token/test/nonFungibleToken.test.ts
sed -n '1180,1335p' contracts/src/token/test/nonFungibleToken.test.ts

Repository: OpenZeppelin/compact-contracts

Length of output: 10529


Replace resolves.not.toThrow() on circuit promises
These assertions target the resolved value, not the async call. Use await token... and keep the post-state checks instead. Affects lines 632, 1194, 1235, and 1317.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/token/test/nonFungibleToken.test.ts` around lines 630 - 632,
The test is asserting on the resolved promise value with resolves.not.toThrow(),
which does not validate the async circuit call correctly. Update the affected
cases in nonFungibleToken.test.ts around token.transferFrom and the other
matching circuit assertions to await the async call directly instead of wrapping
it in expect(...).resolves.not.toThrow(), then keep the existing post-state
checks to verify behavior after execution.

Comment thread local-env.yml
# WARNING: Insecure default credentials below. For local development only — do not use in production.
services:
proof-server:
image: 'midnightntwrk/proof-server:latest'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What image tags or digests are recommended for midnightntwrk/proof-serverandmidnightntwrk/indexer-standaloneto pair withmidnightntwrk/midnight-node:0.22.2 for local development/live testing?

💡 Result:

To pair with midnightntwrk/midnight-node:0.22.2 for local development and live testing, the recommended image tags for the proof server and standalone indexer are [1]: - midnightntwrk/proof-server:8.0.3 [1][2][3] - midnightntwrk/indexer-standalone:4.0.1 [1][2][3] These versions are part of the standard compatibility set used in Midnight's local development environments (e.g., in the standalone.yml configuration) to ensure inter-component functionality [1][2][3]. While newer versions of these components may be available on Docker Hub, using the versions listed in the official compatibility documentation is recommended to prevent issues during local development [1][4][5]. Always ensure that your local environment configuration correctly maps the ports and environment variables required for these services, such as setting the APPINFRASECRET for the indexer [2][6].

Citations:


🏁 Script executed:

git ls-files | rg '(^|/)(local-env\.yml|standalone\.yml|COMPATIBILITY\.md|README\.md|docker-compose|compose)' && printf '\n---\n' && sed -n '1,120p' local-env.yml && printf '\n---\n' && rg -n 'proof-server|indexer-standalone|midnight-node|8\.0\.3|4\.0\.1|latest' -S .

Repository: OpenZeppelin/compact-contracts

Length of output: 3705


Pin the Docker images to compatible releases. local-env.yml still floats midnightntwrk/proof-server and midnightntwrk/indexer-standalone on latest; pin them to the documented compatible tags for midnightntwrk/midnight-node:0.22.2 (midnightntwrk/proof-server:8.0.3 and midnightntwrk/indexer-standalone:4.0.1) or to digests so test:live stays reproducible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@local-env.yml` at line 4, Pin the Docker image references in local-env.yml so
the test:live stack is reproducible and compatible with
midnightntwrk/midnight-node:0.22.2; replace the floating
midnightntwrk/proof-server:latest and midnightntwrk/indexer-standalone:latest
entries with the documented compatible tags midnightntwrk/proof-server:8.0.3 and
midnightntwrk/indexer-standalone:4.0.1, or equivalent digests, using the
existing service definitions as the location to update.

Comment thread local-env.yml
services:
proof-server:
image: 'midnightntwrk/proof-server:latest'
command: ['midnight-proof-server -v']

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Docker Compose, does command: ['midnight-proof-server -v']pass one argv token or split it intomidnight-proof-serverand-v?

💡 Result:

In Docker Compose, when you provide the command as an array containing a single string—such as command: ['midnight-proof-server -v']—it is treated as a single argument token, not two [1]. Docker Compose parses the list ['midnight-proof-server -v'] literally [1]. It attempts to execute an executable named exactly midnight-proof-server -v (including the space and the hyphen) because that entire string is passed as the first element of the array [1]. Because Docker Compose's command uses exec form, there is no shell to interpret the space as a separator or to perform argument splitting [2][3]. If you intended to pass midnight-proof-server as the command and -v as an argument, you must split them into separate array elements [1]: command: ['midnight-proof-server', '-v'] This ensures the first element is recognized as the executable and the second as its flag [1]. While some documentation or third-party tutorials (such as certain Midnight Network guides) may incorrectly use the single-string array format, it is technically incorrect if the executable does not literally have a space in its filename [1][4][5].

Citations:


🏁 Script executed:

sed -n '1,120p' local-env.yml

Repository: OpenZeppelin/compact-contracts

Length of output: 2036


🏁 Script executed:

sed -n '1,120p' local-env.yml

Repository: OpenZeppelin/compact-contracts

Length of output: 2036


Split the proof-server argv and pin the floating images.

  • command: ['midnight-proof-server -v'] passes a single argv token; split it into ['midnight-proof-server', '-v'] so the container can start.
  • midnightntwrk/proof-server:latest and midnightntwrk/indexer-standalone:latest are floating tags; pin them to exact versions to keep the local stack reproducible.
Possible fix
-    command: ['midnight-proof-server -v']
+    command: ['midnight-proof-server', '-v']
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
command: ['midnight-proof-server -v']
command: ['midnight-proof-server', '-v']
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@local-env.yml` at line 5, The container command is being passed as one argv
token, so split the `command` entry for the proof server into separate arguments
using the existing `command` field so it starts correctly. Also replace the
floating `midnightntwrk/proof-server:latest` and
`midnightntwrk/indexer-standalone:latest` image tags with fixed versioned tags
in the same local stack config to keep the environment reproducible.

Comment thread Makefile
Comment on lines +8 to +14
env-up: env-down
docker compose -f $(COMPOSE_FILE) up -d
@mkdir -p $(LOGS_DIR)
@for svc in $(SERVICES); do \
docker compose -f $(COMPOSE_FILE) logs -f --no-log-prefix $$svc > $(LOGS_DIR)/$$svc.log 2>&1 & \
done
@echo "Logs streaming to $(LOGS_DIR)/"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Wait for the stack to become healthy before env-up returns.

docker compose ... up -d exits once the containers start, not when proof-server, indexer, and node are actually ready. With the new test:live flow, running the suite immediately after make env-up can still fail intermittently during startup.

Possible fix
 env-up: env-down
-	docker compose -f $(COMPOSE_FILE) up -d
+	docker compose -f $(COMPOSE_FILE) up -d --wait
 	`@mkdir` -p $(LOGS_DIR)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env-up: env-down
docker compose -f $(COMPOSE_FILE) up -d
@mkdir -p $(LOGS_DIR)
@for svc in $(SERVICES); do \
docker compose -f $(COMPOSE_FILE) logs -f --no-log-prefix $$svc > $(LOGS_DIR)/$$svc.log 2>&1 & \
done
@echo "Logs streaming to $(LOGS_DIR)/"
env-up: env-down
docker compose -f $(COMPOSE_FILE) up -d --wait
`@mkdir` -p $(LOGS_DIR)
`@for` svc in $(SERVICES); do \
docker compose -f $(COMPOSE_FILE) logs -f --no-log-prefix $$svc > $(LOGS_DIR)/$$svc.log 2>&1 & \
done
`@echo` "Logs streaming to $(LOGS_DIR)/"
🧰 Tools
🪛 checkmake (0.3.2)

[warning] 8-8: Target body for "env-up" exceeds allowed length of 5 lines (6).

(maxbodylength)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 8 - 14, The env-up target currently returns
immediately after docker compose up -d, so it can finish before proof-server,
indexer, and node are actually ready. Update the env-up recipe to wait for the
stack to become healthy before exiting, using the existing SERVCES/COMPOSE_FILE
flow and any health-check or readiness mechanism already available in the
compose setup. Keep the log streaming behavior, but make sure the env-up target
does not complete until the key services are reported healthy and ready for
test:live.

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