Summary
Under --features test-mode, the ACS query helpers in crates/decman/src/server/queries.rs ignore the requested Template/Interface filter and instead issue a WildcardFilter, then match rows in memory against the concrete template's module_name/entity_name. The stated rationale is "mock auth doesn't have TemplateFilter permissions".
An interface query passes the interface's module/entity (e.g. Splice.Api.RewardAssignmentV1 / RewardCoupon), which never equals any concrete template's name (e.g. Splice.Amulet / RewardCouponV2). Every row is therefore dropped and the query returns [] with no error — a result indistinguishable from "the party holds no such contracts". Silent-empty is the core defect: a caller cannot tell an unsupported query from a genuinely empty one.
Scope
Eleven test_mode branches across ten functions, not one:
| Function |
Line |
get_contracts |
273 |
get_governance_confirmations |
659 |
get_governance_state |
1488 |
get_vaults |
1927 |
get_provider_services |
2143 |
get_user_services |
2313 |
get_credential_offers |
2489 |
get_registrar_services |
2665 |
get_instruments |
2851 |
query_contracts_by_template |
3067, 3113 |
Most use TemplateFilter in their production path; query_contracts_by_template is the one that also serves interface queries and so is where the silent-empty surfaces.
State of the rationale
The rationale does not hold for interface filters. reward_automation::active_created_records builds a real InterfaceFilter unconditionally, including under test-mode, and works on localnet: the reward IT reads unassigned RewardCouponV2 contracts through the RewardCoupon interface and assigns 60 of them on every CI run.
Filter type is not a Canton auth capability. The mock token (crates/decman/src/auth/mock.rs, UnsafeClaims { aud, sub, iat }, sub = ledger-api-user) authorizes a user whose party read-rights resolve server-side, so "TemplateFilter permissions" does not describe a real gate.
Whether TemplateFilter also works under the mock token is not established. That is the open question, and it governs most of the call sites above.
Impact
Test-only. Production uses JwtValidator with real filters and is unaffected. The cost is a latent trap: any future code path that queries by interface and is exercised on localnet returns silent-empty, and the localnet ITs can only approximate the production interface-query path rather than exercise it.
Requirements
- Establish whether a real
TemplateFilter ACS query succeeds under the mock token on a localnet participant. (InterfaceFilter is already known to.)
- If it does: remove the
test_mode branches at all eleven sites and use the real filters. The full localnet IT — /contracts/query, governance confirmations, vaults, services, instruments, rewards — must pass afterwards.
- If a genuine asymmetry remains at some sites: the in-memory fallback must either match a contract whose concrete template implements the requested interface, or return an error for an unsupported interface query. Returning
[] for a query the code cannot serve is not acceptable in either outcome.
- The reward IT then queries the
RewardCoupon interface on both targets, dropping its localnet-only concrete-template path: crates/decman/tests/common/phases/coupon_reassignment.rs — reward_coupon_v2_query_path and the target-aware branch in query_reward_coupons.
References
crates/decman/src/server/queries.rs — the eleven test_mode branches above; the "mock auth doesn't have TemplateFilter permissions" comment at get_contracts (~247).
crates/decman/src/server/reward_automation/mod.rs — active_created_records, interface_view = true → InterfaceFilter regardless of test_mode.
crates/decman/src/auth/mock.rs — the mock token.
Summary
Under
--features test-mode, the ACS query helpers incrates/decman/src/server/queries.rsignore the requested Template/Interface filter and instead issue aWildcardFilter, then match rows in memory against the concrete template'smodule_name/entity_name. The stated rationale is "mock auth doesn't have TemplateFilter permissions".An interface query passes the interface's
module/entity(e.g.Splice.Api.RewardAssignmentV1/RewardCoupon), which never equals any concrete template's name (e.g.Splice.Amulet/RewardCouponV2). Every row is therefore dropped and the query returns[]with no error — a result indistinguishable from "the party holds no such contracts". Silent-empty is the core defect: a caller cannot tell an unsupported query from a genuinely empty one.Scope
Eleven
test_modebranches across ten functions, not one:get_contractsget_governance_confirmationsget_governance_stateget_vaultsget_provider_servicesget_user_servicesget_credential_offersget_registrar_servicesget_instrumentsquery_contracts_by_templateMost use
TemplateFilterin their production path;query_contracts_by_templateis the one that also serves interface queries and so is where the silent-empty surfaces.State of the rationale
The rationale does not hold for interface filters.
reward_automation::active_created_recordsbuilds a realInterfaceFilterunconditionally, including undertest-mode, and works on localnet: the reward IT reads unassignedRewardCouponV2contracts through theRewardCouponinterface and assigns 60 of them on every CI run.Filter type is not a Canton auth capability. The mock token (
crates/decman/src/auth/mock.rs,UnsafeClaims { aud, sub, iat },sub = ledger-api-user) authorizes a user whose party read-rights resolve server-side, so "TemplateFilter permissions" does not describe a real gate.Whether
TemplateFilteralso works under the mock token is not established. That is the open question, and it governs most of the call sites above.Impact
Test-only. Production uses
JwtValidatorwith real filters and is unaffected. The cost is a latent trap: any future code path that queries by interface and is exercised on localnet returns silent-empty, and the localnet ITs can only approximate the production interface-query path rather than exercise it.Requirements
TemplateFilterACS query succeeds under the mock token on a localnet participant. (InterfaceFilteris already known to.)test_modebranches at all eleven sites and use the real filters. The full localnet IT —/contracts/query, governance confirmations, vaults, services, instruments, rewards — must pass afterwards.[]for a query the code cannot serve is not acceptable in either outcome.RewardCouponinterface on both targets, dropping its localnet-only concrete-template path:crates/decman/tests/common/phases/coupon_reassignment.rs—reward_coupon_v2_query_pathand the target-aware branch inquery_reward_coupons.References
crates/decman/src/server/queries.rs— the eleventest_modebranches above; the "mock auth doesn't have TemplateFilter permissions" comment atget_contracts(~247).crates/decman/src/server/reward_automation/mod.rs—active_created_records,interface_view = true→InterfaceFilterregardless oftest_mode.crates/decman/src/auth/mock.rs— the mock token.