Skip to content

test(scale): each k8s e2e service app declares the nodes it writes, so the lane compiles again - #9016

Open
MusabMahmoodh wants to merge 4 commits into
jaseci-labs:mainfrom
MusabMahmoodh:fix/k8s-e2e-app-owned-nodes
Open

test(scale): each k8s e2e service app declares the nodes it writes, so the lane compiles again#9016
MusabMahmoodh wants to merge 4 commits into
jaseci-labs:mainfrom
MusabMahmoodh:fix/k8s-e2e-app-owned-nodes

Conversation

@MusabMahmoodh

Copy link
Copy Markdown
Contributor

What this changes

microservice e2e (k8s, slim) and (k8s, official-image) have been red on main since 2026-09-05. Both fail before they reach a cluster:

deploy failed: manifest generation failed: app 'products_app' entry
'.../k8s_e2e/products_app.jac' does not compile: line 8, col 29:
App 'products_app' imports 'Products', a node owned by app 'web';
nodes and edges never cross an app boundary

The fixture kept Products, Product, Cart and Order in shared/models.jac and imported all four into all three service apps. Its own docstring said why, and named the thing that changed:

All services share one anchor store, so every service must be able to deserialize every node class that might appear under root. Owner notes (enforced by convention, not by code): products_app writes Products + Product, cart_app writes Cart, orders_app writes Order.

#8920 made that convention code. E5108 refuses a node or edge that crosses an app boundary, because an app boundary is ACID within and eventual across, so a node class shared by four apps is exactly what the rule exists to stop.

The over-import turned out to be defensive rather than functional. Grepping each app for the names it imports:

app imports actually references
products_app Products, Product, Cart, Order Products, Product
cart_app Cart, Product, Products, Order Cart
orders_app Order, Cart, Product, Products Order
web (main.jac) qty_label qty_label

Cart in orders_app is the string "Cart is empty"; every other extra name appears on the import line and nowhere else. So each declaration moves into the app that writes it, and the three cross-app imports go away rather than being narrowed.

shared/models.jac keeps qty_label and its import humanize, which is the reason that module is load-bearing for this fixture: it is the declared pip dep reached by the client entry, and it pins that a deploy hands the build host the app's [dependencies].

Deleted

The four node declarations in shared/models.jac, and the three import from shared.models { ... } lines in the service apps. The stale docstring paragraph about a shared anchor store and convention-enforced ownership goes with them.

What else this touches

Nothing outside jac/jaclang/scale/tests/fixtures/k8s_e2e/. No product code, no test code, no config keys. main.jac's qty_label import is unchanged, so the client-build dependency assertion the fixture exists for is unchanged.

Validation

  • All four changed files pass jac check --parse_only.
  • Not run locally, stated plainly: the e2e itself. This fixture is only exercised by the microservice e2e (k8s, *) lanes, which stand up a kind cluster and a real deploy; those lanes on this PR are the run of record, and they are currently the failing ones on main, so a green result here is the whole verification.
  • Not addressed here: orders_app.jac still calls view_cart() and clear_cart() without await, which Workspace apps: one repo, many targets, one type-checked codebase (#8823) #8920's bridge contract says should be E1042. That is either a second fixture defect or a gap in the rule, it is not what is failing the lane today, and it deserves its own change rather than being folded into a red-CI fix.

The fixture kept Products, Product, Cart and Order in shared/models.jac and
imported all four into all three service apps, with a docstring saying the
ownership was 'enforced by convention, not by code'. jaseci-labs#8920 made it code:
E5108 refuses a node or edge that crosses an app boundary, so manifest
generation stopped compiling products_app and both microservice e2e (k8s)
lanes have been red since 2026-09-05.

The over-import was defensive rather than functional. Each app referenced
only the nodes it writes, and the extra names appeared on the import line
and nowhere else. So each declaration moves into the app that owns it, and
shared/models.jac keeps the pure helper the web app reaches, which is also
what pins that a declared pip dep reaches the client build host.
A cross-app 'import from cart_app { view_cart }' compiles to a bridge stub
typed as a Coroutine, per reference/apps.md: cross-app calls are coroutines
and must be awaited. orders_app called view_cart() and clear_cart()
synchronously, so create_order failed to compile on
Type "Coroutine[<any>, <any>, dict]" has no attribute "get".

E5108 was masking this: the deploy refused the workspace before the type
error was reached, so fixing only the node ownership moved the lane from
one compile failure to another.
@MusabMahmoodh

Copy link
Copy Markdown
Contributor Author

Correcting this PR's own claim, and pushing the fix for it.

As it stood, this did not make the lane compile. It removed E5108 and exposed a second, unrelated compile failure that E5108 had been masking. Rig-validated against current main (9b02a4b2d9), dev build from the checkout, both fixtures dry-run through the real deploy path:

Main's fixture:

[ERROR] Deploy failed: app 'products_app' entry '.../products_app.jac' does not
compile: line 8, col 29: App 'products_app' imports 'Products', a node owned by
app 'web'; nodes and edges never cross an app boundary

This branch, before ac613ba:

[ERROR] Deploy failed: app 'orders_app' entry '.../orders_app.jac' does not
compile: line 32, col 18: Type "Coroutine[<any>, <any>, dict]" has no
attribute "get"

orders_app calls view_cart() and clear_cart() synchronously, and a cross-app import from cart_app { ... } compiles to a bridge stub typed as a coroutine. reference/apps.md:362 states the contract plainly: "Awaited cross-app calls are coroutines (the type checker reports a missing await as E1042)". The fixture predates the rule being enforced, the same way the node ownership did.

ac613ba makes create_order async and awaits both calls. The dry-run now runs to completion:

ℹ Generated 5 pod-spec stubs (1 gateway + 4 apps)

stopping only at the bundle_storage_class RWX check, which is a cluster-config gate, not compilation, and is set by the e2e's own kind config.

Two things I want on the record rather than assumed.

First, why PR CI cannot answer this. microservice e2e (k8s, slim) and (k8s, official-image) live in nightly.yml behind workflow_dispatch or the 0 3 * * * schedule, so they never run on a pull request. Every check green here says nothing about the lanes this PR exists to fix, and the same is true of any future PR touching them. The dry-run above is the closest thing to a signal available before merge, and I do not have dispatch rights to run the real lane against this ref.

Second, what I have not proven: that the two lanes go green. I have proven the workspace compiles and plans, which is where both were dying. What remains untested is the runtime half, and specifically whether splitting the node declarations per app changes what each service sees under root. The fixture's old docstring asserted that every service must be able to deserialize every node class that might appear under root, which was the stated reason for the shared module. After this change no app references another app's node type at all (cross-app data moves as dicts through the bridge functions), so I expect it holds, but expectation is not a cluster run. I am queuing that on my own kind rig tonight and will post the result here either way.

@christianwilkins christianwilkins left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed ac613ba5ff16946be29892bf4d58258f1b02b8a2, including the full diff, all three service consumers, the retained shared helper, and the nightly workflow. No blocking defect found in the declaration moves or the awaited bridge calls.

I verified that the complete Products, Product, Cart, and Order declaration blocks are byte-identical after moving into their owner files. qty_label and its humanize dependency remain in the shared module. The follow-up ac613ba5f correctly makes create_order async and awaits both cart calls. git diff --check passes.

My local compile validation is incomplete. A basic no-codegen probe passed on both old and new fixtures, so it does not protect the change. With force_target_program=True, the new fixture instead stops on E1053 at datetime.now(UTC) in orders_app; the old fixture stops earlier on different container-type diagnostics in cart_app. These are local compiler results, not a reproduction of the reported deployment failure, and I have not demonstrated that the datetime diagnostic is caused by this patch. All temporary fixture substitutions were restored.

Current-head PR CI is green and conflict-free, but the real microservice e2e (k8s, slim/official-image) jobs are in nightly.yml, not the PR workflow. I did not run a cluster. Status: runtime validation still needs the author's kind run or a maintainer dispatch against this head; ordinary PR checks do not establish that those lanes are fixed.

One nonblocking handoff cleanup: please update the PR description to include the async-call fix and the nightly-only validation limitation already explained in your follow-up comment. The current description still says those calls are synchronous and that the PR lanes are the run of record.

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.

2 participants