Skip to content

feat(scenes): accept @ref target-override keys + engine version gate (labelle-engine#801) - #650

Merged
apotema merged 5 commits into
mainfrom
feat/801-target-overrides
Aug 4, 2026
Merged

apotema merged 5 commits into
mainfrom
feat/801-target-overrides

Conversation

@apotema

@apotema apotema commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Assembler counterpart of labelle-toolkit/labelle-engine#802 (RFC on labelle-toolkit/labelle-engine#801): "@<ref>" override keys that patch entities nested inside a referenced prefab's body.

Changes

  • Pack rewrite (codegen/scan/pack_refs):
    • pass 1 moves flat @ pairs into the synthesized wrapper — leaving one outside would manufacture a hybrid entry the author never wrote, and the engine would then warn wrapper-wins and drop the target;
    • pass 2 opens a component map for @ values in both flat and wrapped shapes, so a pack-local component name under @ gets the namespace rewrite (this was the load-bearing piece: without it, industry__TendableWorkstation under @tender silently misses the rewrite — the same silent-miss class as the namespaced-string-refs trap).
  • scene_name_lint: @ keys are structure, their contents are component refs — a bare pack name inside a @ patch gets the did-you-mean finding.
  • scene_manifest: @ counts as a flat entity-shape key — allowed at a flat reference root, trips HybridForm when mixed with an overrides: wrapper.
  • Version gate: generate hard-errors when any scene/prefab uses @ keys but the pinned engine predates 2.11.0. Compat checking is MAJOR-only (labelle-cli#269), so "new assembler + old engine" is a legal pin combo — and an old engine silently drops @ keys, which is the exact failure labelle-engine#801 exists to kill. Unparseable pins (local: dev overrides, branch pins) pass permissively. Detection is a colon-lookahead textual scan, so @ref values ("target": "@storage") never trip it.

Tests

10 new tests across pack_refs, scene_name_lint, scene_manifest_test: rewrite-under-@ (flat + wrapped), pass-1 wrapper migration, lint ref collection, HybridForm with @, top-level acceptance, usage detection (keys vs values vs comments), version predicate incl. permissive pins.

Note: flow_catalog.zig:587 (emitFlowCatalogSidecar round-trip) fails in my local environment on clean origin/main too — pre-existing local-env issue, unrelated; CI should be authoritative.

Sequencing

Engine PR labelle-toolkit/labelle-engine#802 first (releases as 2.11.0), then this; MIN_ENGINE_FOR_TARGET_OVERRIDES is pinned to 2.11.0 — bump if the engine release slips to a later minor.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features

    • Added support for @ target overrides in flat-form scenes, prefabs, and nested components.
    • Added compatibility checks requiring engine version 2.11.0 or newer when target overrides are used.
    • Improved processing and validation of nested component references within target overrides.
  • Bug Fixes

    • Prevented target override data from being dropped or misclassified during generation.
    • Improved linting to inspect component references inside overrides without reporting override keys themselves.
    • Generation now identifies files using unsupported target overrides.
  • Tests

    • Added coverage for parsing, validation, compatibility checks, escaped keys, and nested target override handling.

…e (labelle-engine#801)

Scene/prefab override maps may now carry "@<ref>" keys (labelle-engine#801)
that patch ref-named entities nested inside a referenced prefab's body.
Assembler-side support:

- pack rewrite: pass 1 moves flat @ pairs into the synthesized wrapper
  (leaving them outside would manufacture a hybrid entry and the engine
  would drop the target); pass 2 opens a component map for @ values in
  both flat and wrapped shapes, so pack-local component names under @
  get the namespace rewrite (the #801 silent-miss class).
- scene_name_lint: @ keys are structure, their CONTENTS are component
  refs (bare pack names under @ get the did-you-mean finding).
- scene_manifest: @ counts as a flat entity-shape key (allowed at a
  flat reference root; trips HybridForm when mixed with a wrapper).
- version gate: generate hard-errors when any scene/prefab uses @ keys
  but the pinned engine predates 2.11.0 — compat checking is MAJOR-only,
  so an old engine would silently DROP @ keys, which is the exact
  failure mode #801 exists to kill. Unparseable pins (local:, branch)
  pass permissively.
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: acc710ed-7019-4a66-aeb8-f0f48ee99347

📥 Commits

Reviewing files that changed from the base of the PR and between 24dae7a and 129d537.

📒 Files selected for processing (2)
  • src/scene_manifest.zig
  • src/scene_manifest_test.zig
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/scene_manifest_test.zig
  • src/scene_manifest.zig

📝 Walkthrough

Walkthrough

The change adds @ target-key support to scene parsing, pack-reference normalization, scene-name linting, and generation-time engine checks. It recognizes nested target component maps, preserves target keys during namespacing, and requires engine version 2.11.0 or newer.

Changes

Target Override Support

Layer / File(s) Summary
Manifest parsing and compatibility checks
src/scene_manifest.zig, src/scene_manifest_test.zig
Scene manifests classify literal and escaped @ keys as entity content. JSONC scanning detects target keys by scope. Engine compatibility starts at 2.11.0. Tests cover parsing, scanning, version gates, escaped keys, and deep input.
Pack-reference normalization
src/codegen/scan/pack_refs/common.zig, src/codegen/scan/pack_refs/pass1.zig, src/codegen/scan/pack_refs/pass2.zig, src/codegen/scan/pack_refs.zig
Pack-reference scanning wraps target keys with flat components, traverses nested target maps, and namespaces pack components without changing target keys.
Scene-name lint traversal
src/scene_name_lint.zig
Lint traversal detects raw and escaped target keys, collects component references inside target values, and excludes target keys from findings.
Generation-time engine gate
src/root.zig
Generation scans scenes and prefabs before code generation and returns error.EngineTooOldForTargetOverrides for unsupported engines.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Generation
  participant SceneManifest
  participant SceneFiles
  Generation->>SceneManifest: Check engineSupportsTargetOverrides
  Generation->>SceneManifest: Find target-key usage
  SceneManifest->>SceneFiles: Read scene and prefab sources
  SceneFiles-->>SceneManifest: Return source text
  SceneManifest-->>Generation: Return first matching path or no match
  Generation-->>Generation: Abort or continue code generation
Loading

Possibly related PRs

Poem

A rabbit checks each @ key,
Nested maps reveal their parts.
Pack names gain their namespace,
Target keys keep their hearts.
Old engines meet the gate.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding assembler support for @ref target-override keys in scenes with an engine version gate, directly matching the PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/801-target-overrides

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/codegen/scan/pack_refs.zig (1)

940-945: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the target pair is inside overrides.

The target_at > wrapper_at check also passes if @slot is emitted after the closing overrides brace. That output recreates the wrapper-plus-flat-target failure this test intends to prevent.

Assert the complete nested fragment, or parse the output and inspect the overrides object.

Proposed test assertion
-    const wrapper_at = std.mem.indexOf(u8, out, "\"overrides\": {").?;
-    const target_at = std.mem.indexOf(u8, out, "\"`@slot`\":").?;
-    // Exactly one wrapper, with the @ pair inside it.
+    // Exactly one wrapper, with the @ pair inside it.
     try std.testing.expectEqual(`@as`(usize, 1), std.mem.count(u8, out, "\"overrides\": {"));
-    try std.testing.expect(target_at > wrapper_at);
+    try std.testing.expect(std.mem.indexOf(
+        u8,
+        out,
+        "\"overrides\": { \"citizens__Worker\": { \"hp\": 1 }, \"`@slot`\": { \"Position\": { \"x\": 1 } } }",
+    ) != null);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codegen/scan/pack_refs.zig` around lines 940 - 945, Strengthen the
assertions in the test around the output generated by the scan flow so the
"`@slot`" target is verified to be inside the "overrides" object, not merely after
its opening text. Replace the target_at > wrapper_at check with an assertion for
the complete nested fragment or parse out and inspect the overrides object,
while preserving the existing single-wrapper and citizens__Worker checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/codegen/scan/pack_refs/common.zig`:
- Around line 146-148: Update isTargetKey and the related isFlatComponentKey
classification flow to decode JSONC member-name escapes before checking key
prefixes, so escaped names such as "\u0040slot" are treated like their decoded
forms. Retain the original source byte spans for emitted output and rewrites.

In `@src/root.zig`:
- Around line 576-581: The version-gated target-key check around
scene_manifest.findTargetKeyUsage must include pack prefab files staged by
generate_phases.loadPackScans, not only game-root prefab_names. Move the gate
until after pack prefab staging completes, or collect and scan every pack prefab
source before applying the gate, while preserving the existing error behavior
for unsupported engine versions.

In `@src/scene_manifest.zig`:
- Around line 960-973: Update the quoted-key scan around isTargetKey so it
decodes JSON string escapes before checking the key, matching the JSONC parser’s
decoded representation; preserve escaped-quote handling and colon detection. Add
coverage for a key encoded as "\u0040slot" and for an escaped @ value to verify
the safety gate and override behavior.
- Around line 1033-1040: The prefab compatibility gate in findTargetKeyUsage
must not skip files that exceed the 1 MiB read limit. Handle readFileAlloc’s
error.StreamTooLong as a compatibility failure (or replace the bounded read with
a streaming scan), ensuring oversized prefabs are rejected when they may contain
target keys while preserving normal cleanup and scanning behavior.

---

Nitpick comments:
In `@src/codegen/scan/pack_refs.zig`:
- Around line 940-945: Strengthen the assertions in the test around the output
generated by the scan flow so the "`@slot`" target is verified to be inside the
"overrides" object, not merely after its opening text. Replace the target_at >
wrapper_at check with an assertion for the complete nested fragment or parse out
and inspect the overrides object, while preserving the existing single-wrapper
and citizens__Worker checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7bdac998-b12d-48d5-ba14-9fe3eaac1557

📥 Commits

Reviewing files that changed from the base of the PR and between 38c9233 and 783957f.

📒 Files selected for processing (8)
  • src/codegen/scan/pack_refs.zig
  • src/codegen/scan/pack_refs/common.zig
  • src/codegen/scan/pack_refs/pass1.zig
  • src/codegen/scan/pack_refs/pass2.zig
  • src/root.zig
  • src/scene_manifest.zig
  • src/scene_manifest_test.zig
  • src/scene_name_lint.zig

Comment thread src/codegen/scan/pack_refs/common.zig
Comment thread src/root.zig
Comment thread src/scene_manifest.zig Outdated
Comment thread src/scene_manifest.zig
@apotema

apotema commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 783957f226

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/root.zig Outdated
Comment on lines +579 to +581
const hit: ?[]const u8 =
try scene_manifest.findTargetKeyUsage(allocator, scenes_target, jsonc_scene_names) orelse
try scene_manifest.findTargetKeyUsage(allocator, prefabs_target, prefab_names);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include pack prefabs in the engine-version gate

When an engine older than 2.11.0 is pinned and a light pack ships a prefab using an @ target override, this gate scans only the game-root scene and prefab name lists. Pack prefabs are not copied and exposed through pack_scans until loadPackScans later in generate, so the gate misses them and the old engine silently drops their targets—the exact failure the gate is intended to prevent. Scan pack prefab sources as well, or run the gate after pack staging.

Useful? React with 👍 / 👎.

Comment thread src/scene_manifest.zig Outdated
Comment on lines +971 to +972
const content = src[content_start..j];
if (isTargetKey(content) and nextSignificantIsColonAt(src, j + 1)) return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict target-key detection to entity and override scopes

For projects pinned below 2.11.0, this treats every JSON object key beginning with @ as target-override syntax, even inside opaque component payloads or metadata. For example, { "components": { "Config": { "@id": "x" } } } does not use a target override—the @id key is ordinary component data—but sourceUsesTargetKeys returns true and generation hard-fails. Track JSON scopes, or inspect the parsed entity/override structure, so payload keys do not trigger the version gate.

Useful? React with 👍 / 👎.

Comment thread src/scene_manifest.zig Outdated
allocator.free(rel);
continue;
};
defer allocator.free(source);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Free each scanned source before advancing to the next file

When the pinned engine is below 2.11.0 and no early target-key hit is found, this defer keeps every successfully read JSONC buffer alive until findTargetKeyUsage returns rather than releasing it after that iteration. Consequently, generation's peak memory grows with the total size of all scenes or all prefabs being scanned (each file may be up to 1 MiB), so sufficiently large projects can exhaust memory merely running the version gate. Free source on the no-hit path inside the loop instead of deferring all frees to function exit.

Useful? React with 👍 / 👎.

…imit hardening (round 1)

- sourceUsesTargetKeys moved to scene_name_lint's scope-walk model:
  payload @ keys (e.g. Config.@id data) no longer false-positive the
  gate; JSON-escaped \u0040 spellings now DO trip it
- version gate scans pack SOURCE trees (prefabs/ + scenes/) via new
  findTargetKeyUsageInTree — pack files aren't staged when the gate
  runs, so a light pack's @ override slipped past before
- findTargetKeyUsage: per-file buffer free (peak memory = one file),
  StreamTooLong is a hard error instead of a silent gate bypass,
  ceiling raised to 16 MiB
@apotema

apotema commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Round 1 fixes pushed (2f1d490):

  • Pack sources covered by the gate (codex P1 + CodeRabbit): the gate now scans each pack's SOURCE tree (prefabs/ + scenes/, recursive) via findTargetKeyUsageInTree — pack files aren't staged yet when the gate runs, so this closes the light-pack bypass.
  • Scope-aware detection (codex P2): sourceUsesTargetKeys now uses the lint's scope-walk model — payload keys like { "Config": { "@id": "x" } } no longer false-positive the gate.
  • Escaped spelling trips the gate (CodeRabbit, scene_manifest.zig): the raw \u0040 prefix is recognized at entity/component-map scope; tests cover flat + wrapped escaped forms.
  • StreamTooLong is a hard error (CodeRabbit): an unscannable oversized file fails generate instead of silently bypassing the gate; ceiling raised to 16 MiB.
  • Per-file buffer free (codex P2): peak memory during the scan is one file, not the sum.

Declined with reason — decode escapes in the rewrite walkers (CodeRabbit, common.zig): raw-byte key classification is a pre-existing convention shared by PascalCase detection across all five walkers (\u0057orker has the identical hole today); fixing it is a cross-cutting pass beyond this PR. Filed as #651.

Full suite green (the flow_catalog.zig:587 sidecar failure is pre-existing in my local env on clean origin/main; CI is authoritative).

@apotema

apotema commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f1d4900fd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +146 to +148
pub fn isTargetKey(name: []const u8) bool {
return name.len > 1 and name[0] == '@';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Decode escaped target keys before rewriting packs

When a pack uses the valid JSON spelling "\u0040slot", the engine decodes it to @slot, and the new version-gate scanner explicitly recognizes that spelling. However, the pack-rewrite scanners pass the raw, unescaped key to this function, so it returns false: pass 2 treats the target's value as opaque payload and does not namespace pack-local component keys, while pass 1 can leave the target outside a synthesized overrides wrapper. Thus a supported-engine build can silently lose the target patch or its pack components; recognize/decode the escaped spelling consistently in the rewrite walkers.

Useful? React with 👍 / 👎.

Comment thread src/root.zig Outdated
for (pack_entries.items) |entry| {
const src_dir = try entry.resolveSrcDir(allocator, game_dir);
defer allocator.free(src_dir);
for ([_][]const u8{ "prefabs", "scenes" }) |sub| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Limit the pack version gate to staged prefab files

If a light pack contains an auxiliary scenes/*.jsonc file with an @ key and the project pins an older engine, this loop now hard-fails generation even though pack scenes are not part of the pack runtime surface. The actual scanPack path in src/root/pack_scan.zig copies and registers components, events, prefabs, and hooks only (lines 182–198); it never stages scenes. Scan only the pack prefab tree that can reach the generated game so unused example or fixture scenes cannot impose an engine-version requirement.

Useful? React with 👍 / 👎.

…s pack prefabs only

- common/lint isTargetKey now recognize the raw \u0040 spelling, so
  the rewrite walkers agree with the version-gate scanner: pass 1
  moves an escaped target into the wrapper and pass 2 namespaces its
  contents (the PascalCase analog stays tracked in #651)
- the pack version gate scans pack prefabs/ only — scanPack never
  stages a pack's scenes/, so an example scene must not impose an
  engine-version requirement
@apotema

apotema commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Round 2 pushed (see latest commit) — both codex P2s addressed:

  • Escaped-@ parity: isTargetKey in the rewrite walkers (pack_refs/common + lint) now recognizes the raw \u0040 spelling, closing the gate-vs-rewriter inconsistency this PR itself introduced — pass 1 moves an escaped target into the synthesized wrapper, pass 2 namespaces its contents. Tests cover both. The escaped-PascalCase analog remains the pre-existing class tracked in Textual scene walkers classify keys by raw bytes — JSON-escaped spellings bypass PascalCase/@ detection #651.
  • Gate scope: the pack scan covers prefabs/ only — scanPack never stages a pack's scenes/, so example/fixture scenes no longer impose an engine-version requirement on the game.

Full suite green (same single pre-existing local flow_catalog failure as before; CI authoritative).

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b062fa812

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/scene_name_lint.zig
Comment on lines +103 to +105
if (c == '{') {
if (sp >= stack_buf.len) return false; // pathological nesting
stack_buf[sp] = childScope(if (sp > 0) stack_buf[sp - 1] else null, pending_key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fail closed when the scope stack overflows

With an engine pin below 2.11.0, a prefab containing 256 nested objects or arrays in an opaque component payload causes this function to return false immediately, so a later "@slot" override is never examined. This nesting is valid JSONC, and pack prefabs are not otherwise parsed by the assembler, so the version gate permits generation and the old engine can silently drop the target override. Treat stack exhaustion as usage/an error, or skip the balanced payload without abandoning the rest of the file.

Useful? React with 👍 / 👎.

Comment thread src/scene_name_lint.zig
Comment on lines +103 to +105
if (c == '{') {
if (sp >= stack_buf.len) return false; // pathological nesting
stack_buf[sp] = childScope(if (sp > 0) stack_buf[sp - 1] else null, pending_key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude dead duplicate roots from the version scan

For a legacy root-wrapper pack prefab with a valid first "root" member and a later duplicate root containing "@slot", this scanner classifies both root values as entities and rejects engine pins below 2.11.0. The existing rewriteWrappedShapeRefs routing confirms that the engine uses only the first root (Object.get is first-match) and deliberately treats later duplicates as opaque dead data, so this is not runtime use of the new syntax. Mirror the rootWrapperValueOpen/.file_container routing when scanning for target keys.

Useful? React with 👍 / 👎.

Comment thread src/scene_manifest.zig
Comment on lines +963 to +965
const min = std.SemanticVersion.parse(MIN_ENGINE_FOR_TARGET_OVERRIDES) catch unreachable;
const pin = std.SemanticVersion.parse(engine_version) catch return true;
return pin.order(min) != .lt;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve digit-leading branch pins in the permissive path

When engine_version is a branch name such as 2.10.0-feature, config.isSemverVersion and versionToGitRef classify it as a branch and resolve it verbatim, but std.SemanticVersion.parse accepts the same text as a prerelease below 2.11.0. Consequently, a development branch that already contains target-override support is hard-rejected despite this function's documented policy of permitting branch pins. Check the repository's release-version predicate before performing the semantic comparison.

Useful? React with 👍 / 👎.

…ch pins classified by isSemverVersion

- sourceUsesTargetKeys returns true (fail closed) past the 256-frame
  scope stack: an unscannable file must not bypass the version gate
- engineSupportsTargetOverrides consults config.isSemverVersion first
  so a digit-leading branch pin (2.10.0-feature) resolves as the git
  ref it is, not as a prerelease below the minimum
@apotema

apotema commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Round 3 pushed — 2 of 3 codex findings fixed (fail-closed on scope-stack overflow, with a 300-deep nesting test; branch pins classified via config.isSemverVersion before any semver comparison, so 2.10.0-feature stays permissive).

Declined with reason — exclude dead duplicate roots: a file with two "root" members is malformed authorship (duplicate JSON keys); the gate erring LOUD on its dead data is the correct side of the trade — the failure mode this gate exists for is silent drops, and mirroring the file_container first-match routing here would add real complexity to accommodate files no tool should produce.

Per the review policy this was the final fix round — merging on green once CI + current reviews settle (after engine #802 merges + releases).

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24dae7adce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/scene_manifest.zig Outdated
// pins are compared; everything else is permissive.
if (!config.isSemverVersion(engine_version)) return true;
const min = std.SemanticVersion.parse(MIN_ENGINE_FOR_TARGET_OVERRIDES) catch unreachable;
const pin = std.SemanticVersion.parse(engine_version) catch return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not permit unparsable release-shaped pins

When engine_version is a numeric release ref such as 2.10, config.isSemverVersion classifies it as a release and versionToGitRef resolves it as v2.10 (src/config.zig:518-546), but SemanticVersion.parse requires the full semantic-version shape and this catch reports support. If that tag points to an engine older than 2.11.0, generation therefore skips the gate and the engine silently drops any @ overrides. Once the release predicate succeeds, normalize abbreviated versions or fail closed rather than treating parse failure like a branch pin.

Useful? React with 👍 / 👎.

Comment thread src/scene_name_lint.zig
Comment on lines +148 to +150
const scope = if (sp > 0) stack_buf[sp - 1] else .entity;
if (scope == .entity or scope == .component_map) {
if (isTargetKey(content)) return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Decode structural keys before routing target scopes

When a structural key is validly JSON-escaped, such as { "prefab": "base", "over\u0072ides": { "@slot": { "Worker": {} } } }, the engine and std.json decode it to overrides, but this raw walker retains over\u0072ides as pending_key; childScope consequently marks the following object as payload and this target check never runs. With an engine pin below 2.11.0, generation then permits a real target override that the engine silently drops. The pack rewrite uses the same raw structural-key routing and also fails to namespace Worker on supported engines, so structural keys need decoding before scope classification rather than decoding only the leading @ spelling.

Useful? React with 👍 / 👎.

@apotema

apotema commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Round-4 codex triage (PR currently held open on the maintainer's call, pending the engine v2.11.0 release):

  • Abbreviated release pins (2.10 classified release but unparsable → permissive): legitimate gate bypass — queued as a pre-merge fix (normalize X.Y → X.Y.0; fail closed if still unparsable once isSemverVersion says release). Will land together with the merge when the hold lifts.
  • Escaped structural keys (over\u0072ides): same raw-byte-classification family as Textual scene walkers classify keys by raw bytes — JSON-escaped spellings bypass PascalCase/@ detection #651, which now explicitly covers structural keys — the fix belongs in the cross-walker escape-decoding pass, not one spelling at a time here.

…-shaped unparsables

The queued pre-merge fix from codex round 4: `2.10` is release-shaped
(isSemverVersion) but unparsable by SemanticVersion — treating that as
a permissive branch pin let an old-release pin bypass the gate.
Normalize `X.Y` to `X.Y.0`; anything release-shaped that still fails
to parse fails closed.
@apotema

apotema commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Hold lifted — engine v2.11.0 is released (4334930, tagged), so MIN_ENGINE_FOR_TARGET_OVERRIDES = "2.11.0" now names a real release. The queued round-4 fix (abbreviated release-pin normalization, fail-closed on release-shaped unparsables) is pushed with tests. Merging on green.

@apotema
apotema merged commit 5babde7 into main Aug 4, 2026
6 checks passed
@apotema
apotema deleted the feat/801-target-overrides branch August 4, 2026 14:46
apotema added a commit that referenced this pull request Aug 4, 2026
New scaffolds get an engine that understands `@ref` target overrides
(labelle-engine#801/#802) — a fresh project using the syntax would
otherwise trip the #650 version gate out of the box. Existing games
are unaffected (MAJOR-only compat, cli#269).
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