fix: gc must traverse oneOf and preserve template content #94

Merged
xingyue merged 1 commits from fix/93-gc-collectrefs-oneof into main 2026-06-07 13:36:09 +00:00
Owner

What

Fix ocas gc falsely deleting CAS nodes that are reachable through oneOf schema combinators (e.g. uwf step chains using oneOf: [{type:"null"}, {type:"string", format:"ocas_ref"}] for nullable prev/detail/start). Also align gc with closure.ts Phase 3 so template content nodes (@ocas/template/text/<schema>) survive when their schema is reachable.

Why

collectRefs() in @ocas/core traverses every JSON-Schema combinator that the meta-schema accepts (anyOf, allOf, if/then/else, not, contains, propertyNames, etc.) except oneOf. uwf uses the standard JSON-Schema idiom for nullable references — oneOf: [{type:"null"}, {type:"string", format:"ocas_ref"}] — so walk() never reaches prev/detail/start, and gc() marks only the head step + its schema. Every intermediate step, detail node, and workflow node was swept as a false orphan.

Secondary defect: gc() did not include @ocas/template/text/<schema-hash> content the way closure.ts does in Phase 3, so rendered template content for a reachable schema was incorrectly collected.

Changes

  • packages/core/src/schema.tscollectRefs() now traverses oneOf exactly the way anyOf is traversed (every variant may contribute refs).
  • packages/core/src/gc.ts — after walking from variable roots, walk every @ocas/template/text/<hash> variable whose schema-hash is in the reachable set, mirroring closure.ts Phase 3.
  • Tests — Suite A (4 unit tests for collectRefs oneOf), Suite B (5 tests for gc with oneOf chains), Suite C (2 tests for template var preservation/collection), Suite D (1 CLI integration test). 695/695 tests passing.
  • Changeset.changeset/93-gc-collectrefs-oneof.md (@ocas/core: patch).

No public type, function signature, or output schema changes. CLI behavior unchanged on the surface.

Ref

Fixes #93

## What Fix `ocas gc` falsely deleting CAS nodes that are reachable through `oneOf` schema combinators (e.g. uwf step chains using `oneOf: [{type:"null"}, {type:"string", format:"ocas_ref"}]` for nullable `prev`/`detail`/`start`). Also align `gc` with `closure.ts` Phase 3 so template content nodes (`@ocas/template/text/<schema>`) survive when their schema is reachable. ## Why `collectRefs()` in `@ocas/core` traverses every JSON-Schema combinator that the meta-schema accepts (`anyOf`, `allOf`, `if/then/else`, `not`, `contains`, `propertyNames`, etc.) **except `oneOf`**. uwf uses the standard JSON-Schema idiom for nullable references — `oneOf: [{type:"null"}, {type:"string", format:"ocas_ref"}]` — so `walk()` never reaches `prev`/`detail`/`start`, and `gc()` marks only the head step + its schema. Every intermediate step, detail node, and workflow node was swept as a false orphan. Secondary defect: `gc()` did not include `@ocas/template/text/<schema-hash>` content the way `closure.ts` does in Phase 3, so rendered template content for a reachable schema was incorrectly collected. ## Changes - **`packages/core/src/schema.ts`** — `collectRefs()` now traverses `oneOf` exactly the way `anyOf` is traversed (every variant may contribute refs). - **`packages/core/src/gc.ts`** — after walking from variable roots, walk every `@ocas/template/text/<hash>` variable whose schema-hash is in the reachable set, mirroring `closure.ts` Phase 3. - **Tests** — Suite A (4 unit tests for `collectRefs` `oneOf`), Suite B (5 tests for `gc` with `oneOf` chains), Suite C (2 tests for template var preservation/collection), Suite D (1 CLI integration test). 695/695 tests passing. - **Changeset** — `.changeset/93-gc-collectrefs-oneof.md` (`@ocas/core: patch`). No public type, function signature, or output schema changes. CLI behavior unchanged on the surface. ## Ref Fixes #93
xiaoju added 1 commit 2026-06-07 13:13:22 +00:00
fix: gc must traverse oneOf and preserve template content
CI / check (pull_request) Successful in 3m46s
4659258693
collectRefs silently skipped oneOf even though it is in the meta-schema's
allowed keys. uwf step nodes use the standard JSON-Schema idiom
oneOf: [{type:"null"}, {type:"string", format:"ocas_ref"}] for nullable
prev/detail/start refs, so walk() never reached the chain and gc swept
the intermediate steps as false orphans. Mirror the anyOf branch in
collectRefs so every oneOf variant contributes refs.

Also align gc with closure.ts Phase 3: walk @ocas/template/text/<schema>
content for every reachable schema so rendered template nodes survive
when their schema is reachable, and are still collected when the schema
itself is unreachable.

Fixes #93
xingyue approved these changes 2026-06-07 13:36:07 +00:00
xingyue left a comment
Owner

LGTM 根因分析精准,修复干净。

oneOf 修复 — 与 anyOf handler 完全对称,递归设计天然处理嵌套场景。over-collect 安全(false reachable = 不丢数据),under-collect 致命 — 这个设计选择正确。

template 保留 — 与 closure.ts Phase 3 结构一一对应,snapshot-before-walk 防止级联。

12 个测试 — 三层覆盖:

  • Suite A (4): collectRefs 单元,含 nested combinator + null variant
  • Suite B (5): gc 集成,3-step chain 完整 bug 复现
  • Suite C (2): template 保留/回收
  • CLI (1): 端到端 round-trip

全流程一次通过,reviewer 零 reject,小橘 solve-issue 管线真的成熟了 👏

LGTM ✅ 根因分析精准,修复干净。 **oneOf 修复** — 与 anyOf handler 完全对称,递归设计天然处理嵌套场景。over-collect 安全(false reachable = 不丢数据),under-collect 致命 — 这个设计选择正确。 **template 保留** — 与 closure.ts Phase 3 结构一一对应,snapshot-before-walk 防止级联。 **12 个测试** — 三层覆盖: - Suite A (4): collectRefs 单元,含 nested combinator + null variant - Suite B (5): gc 集成,3-step chain 完整 bug 复现 - Suite C (2): template 保留/回收 - CLI (1): 端到端 round-trip 全流程一次通过,reviewer 零 reject,小橘 solve-issue 管线真的成熟了 👏
xingyue merged commit 40dacee1be into main 2026-06-07 13:36:09 +00:00
xingyue deleted branch fix/93-gc-collectrefs-oneof 2026-06-07 13:36:09 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: shazhou/ocas#94