Description
Problem
NLS language semantics are currently entangled with Python emitter quirks. The 'language' is effectively a Python macro system, not an independent language with its own semantics.
Acceptance Criteria
- Defined IR (intermediate representation) between parser/resolver and emitters — initial foundation delivered in feat(ir): introduce a minimal target-neutral expression and control IR with source spans #194 / feat(ir): target-neutral IR with source spans and canonical serialization #203
- Python emitter consumes IR, not raw AST
- TS and Rust emitters (feat: TypeScript target #20, feat: Rust target #24) also consume same IR
- Language spec can be validated against IR independently of any backend
- Emitter plugins can be authored without touching core parser/resolver
Why This Blocks v1.0
A production language platform must be separable from its implementation backend. This is the architectural gate for multi-target and long-term language stability.
Typed-control roadmap and current status
Updated 2026-09-10 after checking NLS 3110b312950233ff0f8e28cfddc2f5e9b90443cd and Osprey 8792de9f6d159922141f9d191e4bc5d256fbb971.
Completed foundations
- feat(ir): introduce a minimal target-neutral expression and control IR with source spans #194 — minimal target-neutral expression/control IR and source spans; merged in feat(ir): target-neutral IR with source spans and canonical serialization #203.
- bug(emitter): preserve ANLU calls when their return values are discarded #191 — structural expression lowering; merged in fix(emitter): preserve ANLU calls when their return values are discarded #204.
- bug(semantics): reject executable prose and implicit placeholder returns #190 — executable/scaffold contract; merged in fix(semantics): reject executable prose and implicit placeholder returns #205.
- bug(lockfile): include LOGIC and contracts in semantic hashes and cache invalidation #193 — semantic build identity; merged in fix(lockfile): hash full executable semantics via canonical IR #206.
These are closed implementation milestones. The original audit comment and issue reproducers retain their original commit baseline; they are not claims that the same failures were re-tested on current master. A representable/lowered IR is still distinct from a module that has passed every type, control, effect, and failure check.
Remaining implementation sequence
| Work | Issues | Boundary |
|---|---|---|
| Graph and checked value/control semantics | #192, #195, #196 | Valid step identities, resolved operations/types, dominance, branches, and loop-carried values |
| Observable effects and typed failures | #197, #198 | Separate required operations, resource footprints, and escaping failures |
| Backend conformance | #202 | Begin the Python/TypeScript harness now; migrate to each checked pass as it lands |
| Safe eligibility and protocol state | #199, #200 | Preserve alias/control/failure constraints and consumed resource states |
| Typed injectable handlers | #207 | Synchronous direct-return lexical handlers first |
| Affine resumption | #208 | At most one successful answer per operation request; no general continuation runtime |
| Retry/timeout policies | #201 | Ordinary bounded policies can proceed on the core passes; handler integration follows #207/#208 |
| Optional Osprey target study | #209 | Versioned compatibility probes and a go/no-go decision; does not block Python/TypeScript or require a production backend |
#195–#202 now include Osprey-informed implementation refinements, bounded acceptance cases, and pinned source references. #207–#209 contain their own implementation hints, dependencies, acceptance criteria, and scope limits. Existing Rust target work stays in #24.
Architectural invariants from the comparison
- Frontend differences end at canonical IR. Existing NLS parser/localization paths must feed the same checker and emitters. This does not require another surface syntax.
- Resolved operations carry computation contracts. Check argument/results, latent callback requirements, control regions, resource effects, and failures before calling a module semantically checked.
- Handling does not imply purity. Supplying a handler removes its operation requirement; the implementation's writes, aliases, clocks, unknown effects, and escaping failures remain relevant.
- Three obligations stay distinct. Resource-state consumption (feat(typestate): check explicit resource state transitions and consumed-state reuse #200), request-continuation consumption (feat(control): enforce affine single-shot handler resumption #208), and external replay/idempotency (feat(control): add checked Retry and Timeout policies with explicit idempotency obligations #201) cannot substitute for one another.
- Capability checks precede executable output. A backend must reject unsupported semantic forms before emitting partial programs or launching a toolchain.
- Keep the work staged. Direct handlers do not need resumption; basic retry does not need handlers; the common backend harness does not wait for every future control combinator.
Primary implementation references
- Osprey's canonical frontend contract.
- Operation requirements and callback/fixed-point analysis.
- Typed handlers and resumption semantics.
- Multiplicity implementation and two-attempt/one-receipt fixture.
- Target capability validation.
These references were inspected, not executed during this comparison. At the pinned commit, Osprey's checker rejects unsupported many and declaration-level abort operations. Its resource/continuation guarantees should not be inferred from website summaries or generalized into externally exactly-once execution.
Activity
Mnehmos commentedon Sep 10, 2026
Typed control/dataflow/effect audit and implementation series
Audited current
masterat5630d74ec9cd912d4e2f325349f1943cbc15bcc6, including compiler paths, graph helpers, the core semantic document, tests, and all 104 existing issue records plus 85 PR records available before this series. This comment uses #147 as the existing architecture umbrella rather than opening another duplicate IR epic.Audit conclusion
The proposed direction is supported by the source: NLS has useful value-dependency and state-label metadata, but no checked representation that defines operation types, control regions, effects, and escaping failures together. The next step is a minimal checked IR beneath the existing surface, with deterministic lowering and explicit operation resolution.
Several qualifications matter:
parallel_groups()currently serves graph analysis/output. Emitters still traverse source order. This is an unsound claim of parallel eligibility, not evidence that the current runtime already races bank requests.Confirmed correctness gaps
The issue bodies contain pinned source references and reproductions. Current probes established that:
None; type-only RETURNS can substitute an invented zero.[reject]()call without an output binding becomes a Python comment, erasing its error behavior.ok: truefor the unknown-call and conditional-output examples.constdeclarations.Implementation series
Every new issue includes scope, implementation hints, acceptance criteria, priorities, and integration dependencies. Existing broader work (#20, #24, #86, #143, #144, #148, #149, #150) remains referenced rather than duplicated.
Recommended sequence
Do not front-load Race, compensation/sagas, a universal ontology, general ownership, or the Rust backend into the first IR milestone. Prefer a small checkable subset plus explicit unsupported-feature diagnostics over silent target fallbacks. Compilation remains deterministic and has no LLM participation.
Validation performed
Selected existing suites for dataflow, FSM, LOGIC emission, TypeScript emission, parser parity, semantic behavior, lockfiles, and diff: 116 passed, 22 skipped. The skips were Tree-sitter parity cases because that optional backend was unavailable.
Additional isolated Python/Node probes reproduced the gaps above. Strict TypeScript compilation confirmed TS2304 for branch scope and TS2451 for rebinding; the corrupted string example passed strict compilation, demonstrating why executable semantic checks are also required.
This was an audit and issue creation task. No compiler source changes or fixes were committed in this session.
Merge pull request #243 from Mnehmos/feat/147-ir-check
Merge pull request #245 from Mnehmos/feat/147-target-registry
Mnehmos commentedon Sep 12, 2026
Criteria audit (2026-09-13)
nlsc/ir.py,nlsc/lowering.pyshared by both parser backendsIROperationstatement/expression nodes (guards, bindings, branches, augmented assigns, result); foreign content falls back to the legacy path with a scaffold diagnosticRustRunnerconformance harness) are named indocs/targets.mdnlsc ir --checkruns the shared semantic gate pluscheck_module_eligibility, reportseligible, exits non-zero on ESEM errors or EIR003 blockersTargetEmitterregistry (nlsc/targets.py); CLI/watch/capability matrix/lockfile identity all read it; plugins register in-process or via thenlsc.targetsentry-point groupThe only unmet clause is the Rust half of criterion 3, which depends on #24 and is explicitly staged there with a design-intent mapping and prerequisites in
docs/targets.md. Keeping this issue open solely for that clause; everything else has landed.Mnehmos commentedon Sep 12, 2026
Correction to the audit above: this issue was auto-closed by PR #245's body ("Closes #147's second unchecked criterion"), which is why it now shows CLOSED — not by an explicit decision in the audit.
That outcome is defensible: four of the five criteria are fully delivered (IR, Python IR consumption, IR-only spec validation, plugin registry) and the fifth — the Rust half of "TS and Rust emitters also consume same IR" — is entirely tracked in #24, which stays open with its design-intent mapping and prerequisites in
docs/targets.md. Keeping this umbrella open would duplicate that work item in two places.If you prefer the umbrella open until Rust lands, just reopen; nothing else is outstanding here.