Skip to content

feat: check each method call of a resource with @nexus.guard - #121

Draft
moetemp wants to merge 3 commits into
moe/up/helper-method-type-parameterfrom
moe/up/resource-guard
Draft

moetemp wants to merge 3 commits into
moe/up/helper-method-type-parameterfrom
moe/up/resource-guard

Conversation

@moetemp

@moetemp moetemp commented Nov 14, 2026

Copy link
Copy Markdown
Owner

What changed?

This PR adds @nexus.guard, which makes a resource class call a support function each time code takes one of its methods.

/// @nexus.guard python="check_stream_call"
resource %stream {
  constructor(ref: stream-ref, created: option<bool>);
}
_support.check_stream_call(target="workflow", method="append")
  • The class of each target calls the guard with its target and the method name. The guard refuses a call by raising.
  • The methods stay async def. The check sits in a __getattribute__ that a type checker doesn't see, so a misspelled method is still an error.
  • Only Python takes a guard. The stream sample guards its Stream.

Why?

A Workflow Stream works only in Workflow code, and a client Stream only outside it. A plain function, such as the predicate of a take, can't await. Its stream.append(x) makes a coroutine that never runs, so the write is lost with no error. A check inside the async def can't catch that, because its body never runs.

How did you test it?

cargo validate passes. The stream tests check that a call with no await raises at its line, and that inspect.iscoroutinefunction is still true for a guarded method. Unit tests cover the parser, the catalog and the set of guarded names.

Link to a test plan if any -

  • Unit Tests
  • Staging
  • End to End Tests

moetemp and others added 3 commits November 14, 2026 13:08
An `async def` body runs only when it is awaited, so a call that is never awaited is lost.
The guard runs when code takes the method, before any coroutine exists.
The sample's support guard records each call and refuses one from a plain function of a caller.
Its tests show that a call with no await fails at its line.
Authors need the call that the class makes and why a check inside the method can't catch a lost
call.

Co-authored-by: Tim Conley <timothy.conley@temporal.io>
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