diff --git a/docs/superpowers/plans/2026-07-19-ik0quq-app-decompile-logic.md b/docs/superpowers/plans/2026-07-19-ik0quq-app-decompile-logic.md new file mode 100644 index 0000000000000000000000000000000000000000..925bdf40ec702a26c610bf260bfe655cd254c552 --- /dev/null +++ b/docs/superpowers/plans/2026-07-19-ik0quq-app-decompile-logic.md @@ -0,0 +1,152 @@ +# IK0QUQ App Decompile Logic Recovery Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Restore entity determination and action codes when the app decompiler reconstructs `app.logic`. + +**Architecture:** Keep logic inference as a small internal helper in the app decompiler. Read rules from the schema-defined `entity.root.rules`, append the existing writeback inference, and preserve the existing ordered `Set` de-duplication. Test both the helper boundaries and the real FYBXD generate/decompile pipeline. + +**Tech Stack:** TypeScript, Bun test, existing UBML entity/app DSL schemas and fixtures. + +**Spec:** `docs/superpowers/specs/2026-07-19-ik0quq-app-decompile-logic-design.md` + +--- + +## File Map + +| File | Change | +|------|--------| +| `packages/ubml-codegen/src/app/decompile/decompile-app.ts` | Export the internal logic helper for focused tests and read `entity.root.rules`. | +| `packages/ubml-codegen/test/app-decompile.test.ts` | Add rule-boundary tests and assert the exact FYBXD round-trip logic. | + +### Task 1: Add failing logic recovery regressions + +**Files:** +- Modify: `packages/ubml-codegen/test/app-decompile.test.ts` +- Reference: `packages/ubml-codegen/src/entity/dsl/schema.ts` + +- [ ] **Step 1: Import `inferLogic` and the entity DSL type/schema** + +Expose `inferLogic` from the existing internal decompile module for direct focused testing. Build minimal valid entity DSL fixtures with a root string field. + +- [ ] **Step 2: Add focused boundary tests** + +Add tests proving: + +1. no `root.rules` returns an empty list; +2. determination-only rules return declaration-order codes; +3. action-only rules return declaration-order codes; +4. validations and `managerActions` are excluded. + +Use an empty component list so these tests isolate entity-rule behavior. + +- [ ] **Step 3: Strengthen the real round-trip test** + +After decompiling the generated FYBXD project, assert: + +```ts +expect(decompiled.dsl.app.logic).toEqual([ + "sumAmount", + "defaultValueSet", + "Audit", + "writeback", +]) +``` + +- [ ] **Step 4: Run the focused test and confirm RED** + +Run: + +```powershell +bun test packages/ubml-codegen/test/app-decompile.test.ts +``` + +Expected: determination/action assertions and FYBXD exact logic fail because the decompiler currently reads `entity.rules`. + +### Task 2: Implement the minimal schema-path fix + +**Files:** +- Modify: `packages/ubml-codegen/src/app/decompile/decompile-app.ts` + +- [ ] **Step 1: Change only the rules source** + +Make the helper test-visible and replace: + +```ts +const rules = entityDsl.entity.rules +``` + +with: + +```ts +const rules = entityDsl.entity.root.rules +``` + +Do not include validations or `managerActions`; do not change ordering or de-duplication. + +- [ ] **Step 2: Run the focused test and confirm GREEN** + +Run: + +```powershell +bun test packages/ubml-codegen/test/app-decompile.test.ts +``` + +Expected: all tests in the file pass, including the exact FYBXD logic assertion. + +- [ ] **Step 3: Review the focused diff** + +Run: + +```powershell +git diff --check +git diff -- packages/ubml-codegen/src/app/decompile/decompile-app.ts packages/ubml-codegen/test/app-decompile.test.ts +``` + +Verify the implementation contains no schema or behavior expansion. + +- [ ] **Step 4: Commit implementation** + +```powershell +git add packages/ubml-codegen/src/app/decompile/decompile-app.ts packages/ubml-codegen/test/app-decompile.test.ts +git commit -m "fix(app-decompile): restore logic from entity root rules" +``` + +### Task 3: Verify and prepare the PR + +**Files:** +- Verify all branch changes since `origin/main`. + +- [ ] **Step 1: Run package-level verification** + +Run the focused test, the relevant package test suite, and typecheck: + +```powershell +bun test packages/ubml-codegen/test/app-decompile.test.ts +bun test packages/ubml-codegen/test +bun run --cwd packages/ubml-codegen typecheck +``` + +If a broader test or typecheck has an unrelated pre-existing failure, reproduce it on `origin/main` and document the comparison. + +- [ ] **Step 2: Inspect final branch state** + +```powershell +git diff --check origin/main...HEAD +git status --short --branch +git log --oneline origin/main..HEAD +``` + +- [ ] **Step 3: Request independent code review** + +Review requirements: + +- exact `root.rules` schema path; +- determination/action ordering; +- no validation/manager-action leakage; +- exact FYBXD integration coverage; +- no unrelated changes. + +- [ ] **Step 4: Push and create a dedicated PR** + +Push `fix/ik0quq-app-decompile-logic` to the fork and open a PR to `UBML/code-agent:main` that references IK0QUQ and includes the verification results. diff --git a/docs/superpowers/specs/2026-07-19-ik0quq-app-decompile-logic-design.md b/docs/superpowers/specs/2026-07-19-ik0quq-app-decompile-logic-design.md new file mode 100644 index 0000000000000000000000000000000000000000..26418144660b0a1658b029002821c589c9f3ba21 --- /dev/null +++ b/docs/superpowers/specs/2026-07-19-ik0quq-app-decompile-logic-design.md @@ -0,0 +1,59 @@ +# IK0QUQ App Decompile Logic Recovery Design + +**Date:** 2026-07-19 +**Status:** Approved +**Issue:** IK0QUQ +**Scope:** `packages/ubml-codegen` + +## Problem + +The app decompiler reconstructs `app.logic` from the generated entity and page artifacts. `inferLogic()` currently reads `entityDsl.entity.rules`, but the entity DSL schema stores business rules at `entityDsl.entity.root.rules`. As a result, determinations and actions disappear during generate -> project -> decompile, while `writeback` survives because it is inferred from page components. + +For the FYBXD exemplar, the expected logic is: + +```yaml +logic: [sumAmount, defaultValueSet, Audit, writeback] +``` + +The current decompiler returns only `writeback`. + +## Goals + +1. Recover root determinations and actions into `app.logic`. +2. Preserve the established order: determinations, actions, then `writeback`. +3. Preserve first-occurrence de-duplication. +4. Add regression coverage for the full FYBXD round trip and rule-boundary cases. + +## Non-goals + +- Adding validation rules to `app.logic`. +- Adding `entity.managerActions` to `app.logic`. +- Changing entity, app, or generated artifact schemas. +- Changing how `writeback` is inferred. + +## Decision + +Change the rule source from `entityDsl.entity.rules` to `entityDsl.entity.root.rules`. Keep the existing collection and de-duplication behavior otherwise unchanged. + +`managerActions` remain excluded. They are a sibling of `root` in the entity DSL, compile to `BizMgrActions` through a separate pipeline, and are not present in the source FYBXD `app.logic`. Including them would expand semantics beyond the reported data-loss bug. + +## Alternatives Considered + +1. **Include validations and manager actions.** Rejected because these are separate entity concepts and would add entries that were never in the original app DSL. +2. **Change only the property path and rely on the existing round-trip golden.** Rejected because the golden comparison does not currently assert `app.logic`, so the original regression passed unnoticed. +3. **Recommended: minimal path fix plus focused and integration regressions.** This repairs the bug without broadening behavior and makes each supported boundary explicit. + +## Testing + +- A focused helper test with no rules returns no rule logic. +- A focused helper test with only determinations returns their codes in declaration order. +- A focused helper test with only actions returns their codes in declaration order. +- The FYBXD generate -> project -> decompile test asserts the exact logic sequence `sumAmount`, `defaultValueSet`, `Audit`, `writeback`. +- Existing app-decompile and package tests remain green apart from documented pre-existing platform baselines, if any. + +## Success Criteria + +1. FYBXD decompiles to the exact original `app.logic` order. +2. Missing or partial `root.rules` values do not throw. +3. `managerActions` and validations do not leak into `app.logic`. +4. The implementation is limited to the app-decompile logic inference and its tests. diff --git a/packages/ubml-codegen/src/app/decompile/decompile-app.ts b/packages/ubml-codegen/src/app/decompile/decompile-app.ts index c9ef770c518a34ea93965c935da2ae51fe81b3a5..4d2053eb27200185cb5a03ba50ba50eb6ddd7807 100644 --- a/packages/ubml-codegen/src/app/decompile/decompile-app.ts +++ b/packages/ubml-codegen/src/app/decompile/decompile-app.ts @@ -48,9 +48,9 @@ function inferPageType(code: string): string { return "card" } -function inferLogic(entityDsl: EntityDsl, components: ComponentItemDsl[]): string[] { +export function inferLogic(entityDsl: EntityDsl, components: ComponentItemDsl[]): string[] { const logic: string[] = [] - const rules = entityDsl.entity.rules + const rules = entityDsl.entity.root.rules for (const d of rules?.determinations ?? []) logic.push(d.code) for (const a of rules?.actions ?? []) logic.push(a.code) if (components.some((c) => c.kind === "writeback")) logic.push("writeback") diff --git a/packages/ubml-codegen/test/app-decompile.test.ts b/packages/ubml-codegen/test/app-decompile.test.ts index 5518c0c585eb9a468dbd94f2122e63fae41294f3..521b6f0dd614f595aadb5706dbdc602d3ac56309 100644 --- a/packages/ubml-codegen/test/app-decompile.test.ts +++ b/packages/ubml-codegen/test/app-decompile.test.ts @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" import { readFile } from "node:fs/promises" -import { decompileAppFromProject } from "../src/app/decompile/decompile-app" +import { decompileAppFromProject, inferLogic } from "../src/app/decompile/decompile-app" import { decompileComponentsJson } from "../src/app/decompile/decompile-component" import { decompilePageflowJson } from "../src/app/decompile/decompile-pageflow" import { decompileQueryJson } from "../src/app/decompile/decompile-query" @@ -13,6 +13,7 @@ import { formatGoldenDiff, } from "../src/app/golden/compare-app-golden" import { generateAppFromYaml } from "../src/app/generate-app" +import { entityDslSchema, type EntityDsl } from "../src/entity/dsl/schema" import { findRepoRoot } from "../src/repo-root" const REPO = findRepoRoot() @@ -23,6 +24,88 @@ async function loadJson(name: string): Promise> { return JSON.parse(await readFile(join(FIXTURES, name), "utf8")) as Record } +function entityDsl(options: { + rules?: NonNullable + managerActions?: NonNullable +} = {}): EntityDsl { + return entityDslSchema.parse({ + entity: { + code: "TestEntity", + name: "Test entity", + namespace: "test", + kind: "bill", + root: { + code: "TestEntity", + name: "Test entity", + fields: [{ code: "Name", name: "Name", type: "string" }], + rules: options.rules, + }, + managerActions: options.managerActions, + }, + }) +} + +describe("inferLogic", () => { + test("returns no rule logic when root rules are missing", () => { + expect(inferLogic(entityDsl(), [])).toEqual([]) + }) + + test("returns determination codes in declaration order", () => { + const dsl = entityDsl({ + rules: { + determinations: [ + { + code: "determineFirst", + name: "Determine first", + timing: "afterCreate", + component: "FirstDetermination", + }, + { + code: "determineSecond", + name: "Determine second", + timing: "afterModify", + component: "SecondDetermination", + }, + ], + }, + }) + + expect(inferLogic(dsl, [])).toEqual(["determineFirst", "determineSecond"]) + }) + + test("returns action codes in declaration order", () => { + const dsl = entityDsl({ + rules: { + actions: [ + { code: "actionFirst", name: "Action first" }, + { code: "actionSecond", name: "Action second" }, + ], + }, + }) + + expect(inferLogic(dsl, [])).toEqual(["actionFirst", "actionSecond"]) + }) + + test("excludes validations and manager actions", () => { + const dsl = entityDsl({ + rules: { + validations: [ + { + code: "validateName", + name: "Validate name", + timing: "beforeSave", + triggerFields: ["Name"], + component: "NameValidation", + }, + ], + }, + managerActions: [{ code: "manageEntity", name: "Manage entity" }], + }) + + expect(inferLogic(dsl, [])).toEqual([]) + }) +}) + describe("app decompile units", () => { test("decompileQueryJson bxdqo matches exemplar field params", async () => { const be = await loadJson("fybxd.be.json") @@ -89,6 +172,12 @@ describe("app decompile round-trip", () => { repoRoot: REPO, }) + expect(decompiled.dsl.app.logic).toEqual([ + "sumAmount", + "defaultValueSet", + "Audit", + "writeback", + ]) expect(decompiled.dsl.app.entities).toEqual(["FYBXD"]) expect(decompiled.dsl.app.pages).toEqual( expect.arrayContaining(["FYBXDJ", "FYBXDJManagement", "FYBXDJQuery"]),