This commit is contained in:
bQUARKz 2026-02-18 06:50:45 +00:00
parent f75c61004c
commit 63631578e5
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 198 additions and 912 deletions

View File

@ -0,0 +1,198 @@
Mini Spec — Borrow/Mutate Views on a GC Heap (Prometeu)
This document defines an optional “Rust-flavored” access discipline for heap objects without using RC/HIP. It is compatible with a GC-based heap, first-class functions (closures), and deterministic coroutines.
Goals
Preserve the feel of borrow (shared read) and mutate (exclusive write).
Prevent common aliasing bugs in user code.
Keep the runtime simple and GC-friendly.
Avoid restrictions that would block closures/coroutines.
Non-goals
Not a full Rust borrow checker.
Not a memory management mechanism (GC owns lifetimes).
Not a general concurrency/locking system.
1. Concepts
1.1 Heap handle
A heap object is referenced by a handle. Handles can be stored, copied, captured, and returned freely (GC-managed).
1.2 Views
A view is a temporary capability derived from a handle:
BorrowView<T>: shared read-only view
MutView<T>: exclusive mutable view
Views are stack-only and lexically scoped.
2. Core Rules (Language Semantics)
2.1 Creating views
Views can be created only by dedicated constructs:
borrow(h) { ... } produces a BorrowView for the duration of the block
mutate(h) { ... } produces a MutView for the duration of the block
2.2 View locality
Views must never escape their lexical scope:
A view cannot be:
stored into heap objects
captured by closures
returned from functions
yielded across coroutine suspension points
stored into globals
(Think: “views are ephemeral; handles are persistent.”)
2.3 Access permissions
Within a view scope:
BorrowView permits reads only
MutView permits reads and writes
Outside a view scope:
direct field access is not permitted; you must open a view scope again.
2.4 Exclusivity
At any program point, for a given handle h:
Multiple BorrowView(h) may coexist.
Exactly one MutView(h) may exist, and it excludes all borrows.
This is enforced at compile time (required) and optionally at runtime in debug mode (see §5).
2.5 Re-entrancy
Within a mutate(h) scope:
nested borrow(h) is allowed (it is trivially compatible)
nested mutate(h) is disallowed
Within a borrow(h) scope:
nested mutate(h) is disallowed
3. Interaction with Closures and Coroutines
3.1 Closures
Closures may capture handles freely.
Closures must not capture views.
If code inside borrow/mutate creates a closure, it may capture the handle, but not the view.
3.2 Coroutines
Coroutines may store handles in their stacks/locals.
Views must not live across suspension:
A coroutine cannot yield while a view is active.
A coroutine cannot sleep while a view is active.
This rule guarantees that view lifetimes remain local and deterministic.
4. Compiler Requirements (Static Enforcement)
The compiler enforces this feature by tracking view lifetimes as lexical regions.
Minimum checks:
A view cannot be assigned to a variable with heap lifetime.
A view cannot appear in a closure capture set.
A view cannot be returned.
A view cannot be live at FRAME_SYNC, YIELD, SLEEP, or any safe-point boundary defined by the language.
Exclusivity rules per handle region (borrow vs mutate overlaps) must hold.
If a rule is violated: compile error (fatal).
5. Optional Runtime Checking (Debug Mode)
Runtime checking is optional and intended for debugging or untrusted bytecode scenarios.
5.1 Object header borrow-state
Each heap object may maintain:
readers: u16
writer: bool
Runtime actions:
entering borrow increments readers if no writer
leaving borrow decrements readers
entering mutate requires readers==0 and writer==false, then sets writer=true
leaving mutate sets writer=false
Violations trap with a specific diagnostic.
5.2 Build profiles
Release builds may omit this entirely (zero overhead).
Debug builds may enable it.
6. Lowering to Bytecode (Suggested)
This feature does not require dedicated opcodes in the minimal design.
Recommended lowering strategy:
borrow/mutate are compile-time constructs.
Field reads/writes lower to existing heap access opcodes (FIELD_GET/FIELD_SET or equivalent), but only permitted while a view is considered “active” by the compiler.
In debug-runtime-check mode, the compiler may insert explicit enter/exit markers (or syscalls) to enable runtime enforcement.
7. Rationale
GC removes the need for RC/HIP, but aliasing mistakes still exist. Views provide a clean and didactic discipline:
Handles: long-lived, capturable, GC-managed.
Views: short-lived, local, safe access windows.
This keeps the VM simple while enabling expressive language features (closures, coroutines) without lifetime headaches.
8. Summary
Borrow/Mutate are access disciplines, not memory management.
Views are stack-only and non-escapable.
Closures and coroutines freely use handles, but never views.
Enforcement is compile-time, with optional runtime checks in debug mode.

View File

@ -1,52 +0,0 @@
# Phase 03 Rigid Frontend API & PBS Leak Containment (Junie PR Templates)
> Goal: **finish Phase 03 with JVM-like discipline** by making the **Backend (BE) the source of truth** and forcing the PBS Frontend (FE) to implement a **strict, minimal, canonical** contract (`frontend-api`).
>
> Strategy: **surgical PRs** that (1) stop PBS types from leaking, (2) replace stringy protocols with canonical models, and (3) make imports/exports/overloads deterministic across deps.
# Notes / Operating Rules (for Junie)
1. **BE is the source of truth**: `frontend-api` defines canonical models; FE conforms.
2. **No string protocols** across layers. Strings may exist only as *display/debug*.
3. **No FE implementation imports from other FE implementations**.
4. **No BE imports PBS modules** (hard boundary).
5. **Overload resolution is signature-based** (arity alone is not valid).
---
## PR-03.07 — Phase 03 cleanup: remove legacy compatibility branches and document boundary
### Title
Remove legacy string protocol branches and document FE/BE boundary rules
### Briefing / Context
After canonical models are in place, we must delete compatibility code paths (`alias/module`, `svc:` prefixes, prefix matching, etc.) to prevent regressions.
### Target
* No legacy synthetic module path support.
* No string prefix matching for overloads.
* Documentation: “BE owns the contract; FE implements it.”
### Scope
* Delete dead code.
* Add `docs/phase-03-frontend-api.md` (or in-crate docs) summarizing invariants.
* Add CI/lints to prevent BE from importing PBS modules.
### Checklist
* [ ] Remove legacy branches.
* [ ] Add boundary docs.
* [ ] Add lint/CI guard.
### Tests
* Full workspace tests.
* Golden tests.
### Risk
Low/Medium. Mostly deletion + docs, but could expose hidden dependencies.

View File

@ -1,179 +0,0 @@
# Prometeu Industrial-Grade Refactor Plan (JVM-like)
**Language policy:** All implementation notes, code comments, commit messages, PR descriptions, and review discussion **must be in English**.
**Reset policy:** This is a **hard reset**. We do **not** keep compatibility with the legacy bytecode/linker/verifier behaviors. No heuristics, no “temporary support”, no string hacks.
**North Star:** A JVM-like philosophy:
* Control-flow is **method-local** and **canonical**.
* The linker resolves **symbols** and **tables**, not intra-function branches.
* A **single canonical layout/decoder/spec** is used across compiler/linker/verifier/VM.
* Any invalid program fails with clear diagnostics, not panics.
---
## Phase 3 — JVM-like Symbol Identity: Signature-based Overload & Constant-Pool Mindset
### PR-09 (3 pts) — Overload resolution rules (explicit, deterministic)
**Briefing**
Once overload exists, resolution rules must be explicit.
**Target**
Implement a deterministic overload resolver based on exact type match (no implicit hacks).
**Scope**
* Exact-match resolution only (initially).
* Clear diagnostic when ambiguous or missing.
**Requirements Checklist**
* [ ] No best-effort fallback.
**Completion Tests**
* [ ] Ambiguous call produces a clear diagnostic.
* [ ] Missing overload produces a clear diagnostic.
---
## Phase 4 — Eliminate Stringly-Typed Protocols & Debug Hacks
### PR-10 (5 pts) — Replace `origin: Option<String>` and all string protocols with structured enums
**Briefing**
String prefixes like `svc:` and `@dep:` are fragile and non-industrial.
**Target**
All origins and external references become typed data.
**Scope**
* Replace string origins with enums.
* Update lowering/collector/output accordingly.
**Requirements Checklist**
* [ ] No `.starts_with('@')`, `split(':')` protocols.
**Completion Tests**
* [ ] Grep-based test/lint step fails if forbidden patterns exist.
---
### PR-11 (5 pts) — DebugInfo V1: structured function metadata (no `name@offset+len`)
**Briefing**
Encoding debug metadata in strings is unacceptable.
**Target**
Introduce a structured debug info format that stores offset/len as fields.
**Scope**
* Add `DebugFunctionInfo { func_idx, name, code_offset, code_len }`.
* Remove all parsing of `@offset+len`.
* Update orchestrator/linker/emit to use structured debug info.
**Requirements Checklist**
* [ ] No code emits or parses `@offset+len`.
**Completion Tests**
* [ ] A test that fails if any debug name contains `@` pattern.
* [ ] Debug info roundtrip test.
---
## Phase 5 — Hardening: Diagnostics, Error Handling, and Regression Shields
### PR-12 (3 pts) — Replace panics in critical build pipeline with typed errors + diagnostics
**Briefing**
`unwrap/expect` in compiler/linker transforms user errors into crashes.
**Target**
Introduce typed errors and surface diagnostics.
**Scope**
* Replace unwraps in:
* symbol resolution
* import/export linking
* entrypoint selection
* Ensure clean error return with context.
**Requirements Checklist**
* [ ] No panic paths for invalid user programs.
**Completion Tests**
* [ ] Invalid program produces diagnostics, not panic.
---
### PR-13 (3 pts) — Add regression test suite: link-order invariance + opcode-change immunity
**Briefing**
We need a system immune to opcode churn.
**Target**
Add tests that fail if:
* linker steps bytes manually
* decoder/spec drift exists
* link order changes semantics
**Scope**
* Link-order invariance tests.
* Spec coverage tests.
* Optional: lightweight “forbidden patterns” tests.
**Requirements Checklist**
* [ ] Changing an opcode immediate size requires updating only the spec and tests.
**Completion Tests**
* [ ] All new regression tests pass.
---
## Summary of Estimated Cost (Points)
* Phase 1: PR-01 (3) + PR-02 (5) + PR-03 (3) = **11**
* Phase 2: PR-04 (5) + PR-05 (3) + PR-06 (3) = **11**
* Phase 3: PR-07 (5) + PR-08 (5) + PR-09 (3) = **13**
* Phase 4: PR-10 (5) + PR-11 (5) = **10**
* Phase 5: PR-12 (3) + PR-13 (3) = **6**
**Total: 51 points**
> Note: If any PR starts to exceed 5 points in practice, it must be split into smaller PRs.
---
## Non-Negotiables
* No compatibility with legacy encodings.
* No heuristics.
* No string hacks.
* One canonical decoder/spec/layout.
* Everything in English (including review comments).

View File

@ -1,341 +0,0 @@
# Prometeu Base Script (PBS)
> **A didactic scripting language for game development with explicit cost, explicit memory, and predictable runtime.**
---
## 0. What PBS Is (and What It Is Not)
PBS (Prometeu Base Script) is a **small, explicit scripting language designed for game engines and realtime runtimes**.
Its core goals are:
* **Didactic clarity** — the language teaches how memory, data, and APIs really work.
* **Gamefriendly execution** — predictable runtime, no hidden allocation, no tracing GC.
* **Explicit cost model** — you always know *when* you allocate, *where* data lives, and *who* can mutate it.
PBS is **not**:
* a generalpurpose application language
* a productivity scripting language like Python or Lua
* a language that hides runtime cost
PBS is intentionally opinionated. It is built for developers who want **control, predictability, and understanding**, especially in **game and engine contexts**.
---
## 1. The Core Philosophy
PBS is built around one rule:
> **If you do not allocate, you are safe. If you allocate, you are responsible.**
From this rule, everything else follows.
### 1.1 Two Worlds
PBS has **two explicit memory worlds**:
| World | Purpose | Properties |
| ----- | -------------- | ---------------------------------------------- |
| SAFE | Stack / values | No aliasing, no leaks, no shared mutation |
| HIP | Storage / heap | Explicit aliasing, shared mutation, refcounted |
You never cross between these worlds implicitly.
---
## 2. First Contact: SAFEOnly PBS
A PBS program that never allocates lives entirely in the SAFE world.
SAFE code:
* uses value semantics
* copies data on assignment (conceptually)
* cannot leak memory
* cannot accidentally share mutable state
This makes SAFE PBS ideal for:
* gameplay logic
* math and simulation
* AI logic
* scripting without fear
Example:
```pbs
let a = Vector(1, 2);
let b = a; // conceptual copy
b.scale(2);
// a is unchanged
```
No pointers. No references. No surprises.
---
## 3. Values, Not Objects
PBS does not have objects with identity by default.
### 3.1 Value Types
All basic types are **values**:
* numbers
* bool
* string (immutable)
* tuples
* userdefined `struct`
A value:
* has no identity
* has no lifetime beyond its scope
* is safe to copy
This matches how most gameplay data *should* behave.
---
## 4. Structs (Data First)
Structs are **pure data models**.
```pbs
declare struct Vector(x: float, y: float)
{
pub fn len(self: this): float { ... }
pub fn scale(self: mut this, s: float): void { ... }
}
```
Rules:
* fields are private
* mutation is explicit (`mut this`)
* no inheritance
* no identity
Structs are designed to be:
* cachefriendly
* predictable
* easy to reason about
---
## 5. Mutability Is a Property of Bindings
In PBS, **types are never mutable**. Bindings are.
```pbs
let v = Vector.ZERO;
v.scale(); // ERROR
let w = mut Vector.ZERO;
w.scale(); // OK
```
This single rule eliminates entire classes of bugs.
---
## 6. When You Need Power: Allocation (HIP World)
Allocation is **explicit**.
```pbs
let enemies = alloc list<Enemy>();
```
Once you allocate:
* data lives in Storage (heap)
* access happens through **gates** (handles)
* aliasing is real and visible
This is intentional and explicit.
---
## 7. Gates (Handles, Not Pointers)
A gate is a small value that refers to heap storage.
Properties:
* cheap to copy
* may alias shared data
* managed by reference counting
### 7.1 Strong vs Weak Gates
* **Strong gate** — keeps data alive
* **Weak gate** — observes without ownership
```pbs
let a = alloc Node;
let w: weak<Node> = a as weak;
let maybeA = w as strong;
```
This model mirrors real engine constraints without hiding them.
---
## 8. Controlled Access to Storage
Heap data is never accessed directly.
You must choose *how*:
* `peek` — copy to SAFE
* `borrow` — temporary readonly access
* `mutate` — temporary mutable access
```pbs
mutate enemies as e
{
e.push(newEnemy);
}
```
This keeps mutation visible and scoped.
---
## 9. Errors Are Values
PBS has no exceptions.
### 9.1 `optional<T>`
Used when absence is normal.
```pbs
let x = maybeValue else 0;
```
### 9.2 `result<T, E>`
Used when failure matters.
```pbs
let v = loadTexture(path)?;
```
Error propagation is explicit and typed.
---
## 10. Control Flow Without Surprises
PBS favors explicit flow:
* `if` — control only
* `when` — expression
* `for` — bounded loops only
```pbs
for i in [0b..count] {
update(i);
}
```
No unbounded iteration by accident.
---
## 11. Services: Explicit API Boundaries
A `service` is how behavior crosses module boundaries.
```pbs
pub service Audio
{
fn play(sound: Sound): void;
}
```
Services are:
* explicit
* statically checked
* singletonlike
They map naturally to engine subsystems.
---
## 12. Contracts: Static Guarantees
Contracts define **what exists**, not how.
```pbs
pub declare contract Gfx host
{
fn drawText(x: int, y: int, msg: string): void;
}
```
Contracts:
* have no runtime cost
* are validated at compile time
* define engine ↔ script boundaries
---
## 13. Modules (Simple and Predictable)
* one directory = one module
* only `pub` symbols cross modules
* no side effects on import
This keeps build and runtime deterministic.
---
## 14. Why PBS Works for Games
PBS is designed around real engine needs:
* framebased execution
* explicit allocation
* deterministic cleanup
* predictable performance
It teaches developers **why engines are written the way they are**, instead of hiding reality.
---
## 15. Who PBS Is For
PBS is for:
* game developers who want control
* engine developers
* students learning systems concepts
* educators teaching memory and runtime models
PBS is *not* for:
* rapid prototyping without constraints
* general app scripting
* hiding complexity
---
## 16. Design Promise
PBS makes one promise:
> **Nothing happens behind your back.**
If you understand PBS, you understand your runtime.
That is the product.

View File

@ -1,340 +0,0 @@
# Prometeu VM/Compiler/Bytecode — Atomic PR Plan (Junie-ready)
> **Entry point contract (confirmed)**
>
> * The PBS entry point is **`/src/main/modules/main.pbs::frame(): void`**.
> * The compiler must inject **`FRAME_SYNC` immediately before `RET`** at the end of this function.
> * `FRAME_SYNC` is a **signal only** (no GC opcodes). The VM uses it as a safe point.
> * Missing entry point is a **fatal compile error**.
> **Goal**: Deliver a sequence of small, incremental PRs that bring the implementation closer to the published PBS/VM specs.
>
> **Rules for Junie (strict)**
>
> * **Do not make product decisions.** Only implement what is specified in the PR.
> * **If anything is unclear, stop and ask.** Do not improvise.
> * **All new/modified code comments must be in English.**
> * Each PR must be **atomic** and **mergeable**.
> * Each PR must include: **Briefing**, **Target**, **Non-goals**, **Implementation notes**, **Tests**, **Acceptance criteria**.
---
## PR-02 — VM: Introduce GateId vs heap index and a minimal GatePool (no RC yet)
### Briefing
Current runtime treats `Value::Gate(x)` as a **direct heap base index**. The spec requires a **Gate Pool** where `GateId` resolves to `{alive, base, slots, type_id, rc...}` and heap access happens only through gate validation + resolution.
This PR introduces the **data model** without changing ownership/RC yet, enabling later RC work.
### Target
* Add `GateId` type (e.g., `u32`) and a `GateEntry` struct with fields:
* `alive: bool`
* `base: u32`
* `slots: u32`
* `type_id: u32` (store it, even if VM doesnt use it yet)
* Add `GatePool` container to the VM state.
* Update `ALLOC(type_id, slots)` to:
* bump-alloc `slots` in heap
* insert new `GateEntry { alive: true, base, slots, type_id }`
* push `Value::Gate(GateId)` (GateId is index into gate_pool)
* Update `GATE_LOAD/GATE_STORE` to:
* validate GateId (in-range + alive)
* bounds-check offset against `slots`
* translate to heap index: `base + offset`
### Non-goals
* No reference counting yet.
* No reclaim/free yet.
* No enforcement of borrow/mutate rules yet.
### Implementation notes
* Keep heap as `Vec<Value>` (or existing representation). In this PR, do not change heap layout.
* Add a helper: `resolve_gate(gate_id) -> Result<&GateEntry, Trap>` and `resolve_gate_mut(...)`.
* Define two new trap codes (or map onto existing ones if already defined):
* `TRAP_INVALID_GATE` (gate_id out of range)
* `TRAP_DEAD_GATE` (entry exists but `alive == false`)
* Make sure the VM never reads/writes heap directly for gate operations without resolution.
### Tests
1. VM unit tests:
* Allocate 2 gates; ensure they get distinct GateIds.
* Store to gate offset 0, load back, assert equal.
* Store to offset == slots (OOB) triggers OOB trap.
* Use an invalid GateId triggers INVALID_GATE trap.
2. If trap codes are new, test for the exact trap code.
### Acceptance criteria
* `ALLOC` returns GateId (not heap base).
* `GATE_LOAD/STORE` uses gate_pool resolution.
* Invalid/dead gate attempts trap deterministically.
---
## PR-03 — VM: Add strong reference counting (RC) with deterministic retain/release semantics
### Briefing
The spec requires strong RC tracking for gates and deterministic behavior for invalid/dead gates. Today `GATE_RETAIN`/`GATE_RELEASE` are no-ops (or effectively pop-only).
This PR implements **strong_rc** and updates runtime to adjust RC in well-defined places.
### Target
* Extend `GateEntry` with:
* `strong_rc: u32`
* Define semantics:
* New allocation starts with `strong_rc = 1` (gate value returned on stack owns 1 reference)
* `GATE_RETAIN`: increment strong_rc
* `GATE_RELEASE`: decrement strong_rc; if reaches 0 then mark gate `alive=false` and schedule reclaim
### Non-goals
* No compacting heap.
* No weak refs.
* Reclaim can be minimal (safe-point only) and may not actually reuse memory yet.
### Implementation notes
* Implement a `reclaim_queue: Vec<GateId>` in VM state.
* On `strong_rc` reaching 0:
* set `alive = false`
* push gate_id into `reclaim_queue`
* **Safe point**: drain reclaim queue on `FRAME_SYNC`.
* Compiler guarantees `FRAME_SYNC` at the end of the PBS entry point `main.pbs::frame(): void` (PR-00).
* In this PR, reclaim may simply:
* overwrite the heap range `[base, base+slots)` with `Value::Nil` (or a safe default)
* keep gate_id non-reusable
### Tests
1. RC lifecycle test:
* alloc gate (rc=1)
* retain (rc=2)
* release (rc=1)
* release (rc=0) => gate becomes dead
* subsequent load/store traps DEAD_GATE
2. Reclaim effect test (if you overwrite heap slots):
* store a value, release to 0, run safe-point
* confirm heap region is cleared (only if heap is inspectable in tests)
### Acceptance criteria
* `GATE_RETAIN/RELEASE` changes RC.
* Gate transitions to `dead` at rc==0.
* Dead gate access traps.
* Reclaim happens at the chosen safe point.
---
## PR-04 — VM: Automatic RC adjustments on stack/local/global/heap moves (no more “manual RC correctness”)
### Briefing
Relying on explicit `GATE_RETAIN/RELEASE` everywhere is error-prone. The spec indicates RC must be adjusted on assignments/pops/stores. This PR makes RC correctness **a VM invariant**: when a gate value is copied into a slot, RC increments; when replaced/dropped, RC decrements.
### Target
* Implement centralized helpers:
* `inc_rc_if_gate(Value)`
* `dec_rc_if_gate(Value)`
* Apply them in all places where values are moved or overwritten:
* Stack `PUSH`/`POP` (when dropping values)
* Local set/get if they clone values
* Global set/get
* `GATE_STORE` (heap cell overwrite)
* Any instruction that overwrites an existing slot (e.g., `STORE_LOCAL`, `STORE_GLOBAL`, etc.)
### Non-goals
* No changes to compiler output.
* No borrow/mutate enforcement.
### Implementation notes
* When writing into a slot:
1. `dec_rc_if_gate(old_value)`
2. write new value
3. `inc_rc_if_gate(new_value)` **only if the semantics is “copy into slot”**
* When moving (not copying) is possible, avoid double inc/dec.
* If the VM uses `clone()` widely, be explicit about when RC should increase.
> ⚠️ If its unclear whether an opcode is “move” or “copy”, **stop and ask** (do not guess).
### Tests
1. Stack drop test:
* alloc gate, push into local, pop stack, ensure rc doesnt underflow.
2. Overwrite test:
* local = gateA, then local = gateB
* rc of gateA decremented
* gateA becomes dead if no other refs
3. Heap store overwrite test:
* gateX stores gateA into offset 0
* then stores gateB into same offset
* rc adjusts accordingly
### Acceptance criteria
* No RC leaks on overwrites.
* No premature dead gates when references still exist.
* Tests cover overwrite in at least 2 storage kinds (local + heap).
---
## PR-05 — VM: Implement (debug-mode) Borrow/Mutate/Peek scopes as observable state
### Briefing
Currently `GATE_BEGIN_PEEK/BORROW/MUTATE` and `GATE_END_*` are no-ops. Even if v0 is permissive, the VM should at least track scope state to enable future enforcement and better diagnostics.
### Target
* Add per-gate “scope counters” (or a small state machine) in `GateEntry`:
* `peek_count: u32`
* `borrow_count: u32`
* `mutate_count: u32` (should be 0/1 if exclusive)
* Implement opcodes to increment/decrement and validate balanced usage:
* End without begin => trap (or panic if considered VM bug)
* Negative underflow => trap
* In **debug builds**, optionally enforce:
* cannot `begin_mutate` when `borrow_count>0`
* cannot `begin_borrow` when `mutate_count>0`
### Non-goals
* No compiler changes.
* No runtime copy-back scratch buffers yet.
### Implementation notes
* Keep enforcement behind a feature flag or debug-only cfg.
* Always keep counters balanced; mismatches should be deterministic.
### Tests
* Begin/End balance test per scope.
* Debug-only conflict test (if enabled).
### Acceptance criteria
* Scopes are no longer no-ops.
* Misbalanced begin/end produces deterministic error.
---
## PR-06 — Bytecode/VM: Represent strings as ConstId (dedup + stable value size)
### Briefing
`Value::String(String)` stores dynamic payload in runtime values. The spec direction prefers string refs into constant pools for stability and dedup. This PR migrates runtime string values to `ConstId` references.
### Target
* Add `Value::StringRef(ConstId)` (or `Value::String(ConstId)`)
* Ensure program image contains a string pool.
* Update `PUSH_CONST` behavior for string constants:
* push `StringRef(id)` instead of allocating a runtime `String`
### Non-goals
* No interning beyond the existing constant pool.
* No changes to the source language.
### Implementation notes
* Decide where the string pool lives (ProgramImage / ConstPool).
* Update debug printing and trap formatting if needed.
### Tests
* Constant string pushed twice references same ConstId.
* Equality/comparison behavior remains unchanged (if supported).
### Acceptance criteria
* Strings in runtime values are pool references.
* Existing programs using constants still run.
---
## PR-07 — Compiler: Enforce import placement rules (top-of-file)
### Briefing
The PBS module model specifies that imports must be at the top-level (and typically before other declarations). This PR makes the compiler reject invalid import placement to align with the module/linking spec.
### Target
* In parser/collector phase, detect imports appearing after non-import declarations.
* Emit a diagnostic with a clear message and span.
### Non-goals
* No changes to how linking works.
* No auto-fix.
### Implementation notes
* Track a boolean `seen_non_import_decl` during file scan.
* When an import is encountered and the flag is set, produce an error diagnostic.
### Tests
* One file with valid imports at top => ok.
* One file with import after a function/type => error.
### Acceptance criteria
* Compiler rejects invalid import placement with stable diagnostic.
---
# Suggested merge order
1. PR-00 (entry point validation + FRAME_SYNC injection)
2. PR-01 (linker JMP relocation)
3. PR-02 (GatePool + GateId)
4. PR-03 (RC strong + retain/release + reclaim at FRAME_SYNC)
5. PR-04 (automatic RC on moves/overwrites)
6. PR-05 (scope tracking)
7. PR-06 (StringRef ConstId)
8. PR-07 (import placement enforcement)
---
# Open questions (must be answered before PR-04)
> Junie must stop and ask if any of these cannot be determined from the codebase.
1. Which exact opcodes correspond to storage overwrites (locals/globals) in the current VM implementation (names may differ).
2. Which trap codes are already defined for gate errors vs whether we introduce new ones.
> ✅ Note: the safe point for reclaim is **FRAME_SYNC** (compiler inserts it at the end of `fn frame(): void`).