--- id: AGD-0016 ticket: pbs-service-facade-reserved-metadata title: SDK Service Bodies Calling Builtin/Intrinsic Proxies as Ordinary PBS Code status: accepted created: 2026-04-03 resolved: 2026-04-03 decision: DEC-0013 tags: [compiler, pbs, sdk, stdlib, lowering, service, intrinsic, sdk-interface] --- ## Pain Domain owner: `compiler/pbs` The PBS SDK currently supports two stable execution models: 1. public service wrappers over reserved host declarations such as `@sdk:log`, `@sdk:gfx`, and `@sdk:asset`; 2. public builtin-const roots that lower directly to intrinsic owners. The input surface now needs a service-oriented version of the intrinsic-backed case: - a public `service Input`; - a private builtin type plus builtin const proxy used only inside the SDK; - ordinary PBS service bodies that call the private builtin proxy and return values normally. Today this shape parses and passes frontend semantics, but executable lowering does not treat imported SDK service methods as ordinary executable call targets with real bodies. As a result, user code like `Input.touch()` and `Input.pad().a().pressed()` fails with `E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE` even though the service body itself is valid PBS code. ## Context - `@sdk:input` currently wants to hide the builtin root and expose only the public service. - The lowering path already knows how to: - resolve host-backed service wrappers; - resolve builtin-const-rooted intrinsics. - The intended rule is that SDK service methods should behave as ordinary PBS methods even when their bodies call builtin/intrinsic proxies such as `LowInput`. - Existing tests in the PBS frontend assume `Input` chains resolve to intrinsic callsites and to builtin-owned chained receivers. - The user clarified the desired behavior: - `declare service Input { fn touch() -> InputTouch { let touch = LowInput.touch(); return touch; } }` must compile as normal PBS service code; - the body may include locals and additional processing before returning; - the public callsite does not need to lower directly to the intrinsic as long as the overall executable model works correctly. - Repository workflow requires decision before plan/implementation. This agenda exists to converge on the normative model first. ## Open Questions - [x] What executable artifact model should represent SDK service methods so imported user callsites can lower them as ordinary callable bodies rather than as metadata-only surfaces? Answer: eligible SDK service methods should be represented as ordinary executable callables with lowered bodies available to imported callsites. - [x] Should `SDK_INTERFACE` continue forbidding emitted executable bodies globally, or should it gain a selective path for executable service methods that are safe to import and call? Answer: keep the global prohibition by default, but add a narrow selective path for eligible executable SDK service methods. - [x] How should chained owner propagation work for values returned from SDK service methods whose bodies call builtin intrinsics, such as `Input.touch()` returning `InputTouch` and `Input.pad()` returning `InputPad`? Answer: owner propagation should follow the value actually returned by the lowered intrinsic call inside the SDK service body, so callers preserve the same chained builtin semantics as direct intrinsic use. - [x] What constraints must apply so the compiler can support ordinary SDK service bodies without accidentally turning all interface code into full executable implementation code? Answer: restrict the feature to reserved SDK service methods that stay within a supported executable subset and are explicitly validated as importable/lowerable. - [x] Which existing frontend tests must be reoriented from "public callsite resolves directly as intrinsic" to "public service callsite compiles normally and intrinsic resolution succeeds inside the imported method body"? Answer: the `@sdk:input` frontend and lowering tests should be reoriented to validate ordinary service calls, intrinsic lowering inside imported SDK method bodies, and chained owner preservation on returned values. - [x] What diagnostics should the compiler emit when an SDK service body calls reserved proxies in ways that cannot be imported or lowered safely? Answer: add dedicated diagnostics for non-importable or non-lowerable executable SDK service bodies instead of collapsing to generic unresolved-callee failures. ## Options ### Option A - Treat SDK Service Bodies as Ordinary Executable Callables - **Approach:** Preserve executable bodies for eligible SDK service methods and import them as ordinary callable targets. Lowering of user code emits `CALL_FUNC` to the service method, and the service body itself lowers calls like `LowInput.touch()` to intrinsics. - **Pro:** Matches the intended product model exactly. Keeps service semantics ordinary and avoids facade-specific lowering rules. - **Con:** Requires widening the current `SDK_INTERFACE` lowering model, which today suppresses emitted function bodies. - **Maintainability:** Strong if the eligibility rules stay explicit and narrow. ### Option B - Explicit Reserved Facade Metadata on Service Methods - **Approach:** Add explicit reserved metadata on service methods declaring which intrinsic owner and method they represent. Lowering may continue to skip executable imported service bodies and instead lower the public callsite through the reserved metadata bridge. - **Pro:** Smaller execution-model change than importing executable service bodies. - **Con:** It does not deliver the user-requested semantics of "ordinary PBS service code with arbitrary local processing". It also creates a special case distinct from normal service behavior. - **Maintainability:** Medium. More explicit than inference, but still introduces a second-class meaning for service methods. ### Option C - Keep Builtin Root Public for Intrinsic-Backed SDK Modules - **Approach:** Continue exposing builtin const roots publicly and avoid service wrappers for intrinsic-backed modules such as `@sdk:input`. - **Pro:** Reuses the current lowering path with minimal compiler work. - **Con:** Conflicts directly with the intended SDK API shape and the user requirement that `Input` remain a normal public service. - **Maintainability:** Medium for the compiler, weak for the SDK/API design because it leaks implementation-facing reserved roots into public user code. ## Discussion The user clarified that the target model is not a special "facade intrinsic" abstraction. The target model is ordinary service execution: - SDK service methods should behave like normal PBS service methods; - they may call private reserved proxies such as `LowInput`; - they may allocate locals, perform intermediate work, and then return; - callers should not need direct access to the reserved proxy. The core mismatch is therefore different from the original framing. `Log`, `Gfx`, and `Assets` work because the compiler already supports the relevant host-backed wrappers under the current SDK model. `Input` fails because intrinsic-backed SDK service bodies are not currently imported and lowered as ordinary executable code paths. The compiler therefore likely needs first-class support for executable SDK service bodies, or an explicitly accepted alternative that is intentionally less general than normal service semantics. ## Resolution Recommended direction: pursue **Option A - Treat SDK Service Bodies as Ordinary Executable Callables**. Why: - it matches the clarified requirement exactly: the public `service Input` should work as ordinary PBS code; - it allows locals and additional processing inside the method body without requiring special facade inference; - it keeps the private builtin proxy as an implementation detail rather than a user-facing surface; - it avoids introducing a second semantic class of service methods that only pretend to be ordinary services. Suggested next step: 1. write a decision that defines the SDK executable model for service methods that call reserved proxies; 2. specify how imported SDK service bodies are represented, lowered, and validated; 3. derive an implementation plan from that accepted decision. The discussion is now converged enough to move to the decision stage.