Skip to content

feat(v3): D4 Milestone 2 — staging buffer + commit pipeline (v0.15)#84

Open
mbreiser wants to merge 1 commit into
feat/v3-d4-milestone1from
feat/v3-d4-milestone2
Open

feat(v3): D4 Milestone 2 — staging buffer + commit pipeline (v0.15)#84
mbreiser wants to merge 1 commit into
feat/v3-d4-milestone1from
feat/v3-d4-milestone2

Conversation

@mbreiser
Copy link
Copy Markdown
Contributor

What this is

Milestone 2 of D4 (cross-library import). The full add → plan → commit
substrate that copies conditions (plus their anchors and plugin declarations)
from a second YAML into yours. Node-only, no UI (UI is Milestone 3). The
handoff calls M2 "most of D4's risk" — it's now fully test-covered without a
browser.

Stacked PR. Base is feat/v3-d4-milestone1 (PR #83), not main, so this
diff shows M2 only. Merge #83 first, then this (or retarget to main
once #83 lands).

Decisions from review (confirmed by repo owner)

  • Plugins merge by default when the same runtime resource — identity =
    all plugin fields except name, plus same rig when both are config-less
    (closes the empty-config false-positive both Codex passes flagged). No explicit
    per-plugin mapping wizard for v1.
  • Comment-stamp imported nodes for provenance (# imported from <source> on
    imported conditions, anchors, and added plugins) — pulled in from v1.1.

js/v3-import.js — staging API (dual-export)

  • createStagingBuffer — duplicate-anchor preflight; filename-derived prefix.
  • addToStaging — plan walk: visited-set transitive anchor closure into a
    per-batch registry (anchor referenced by two conditions imports once); plugin
    merge-vs-namespace; alias-bound plugin_name detection; built-in log left
    alone; unknown-command notes; walks imported plugin config aliases.
  • removeFromStaging / setStagingPrefix / setItemTargetName /
    setAnchorPlannedName / setPluginPlannedName — explicit overrides persist
    across prefix changes.
  • validateStaging — pre-commit blocking pass (condition-name collisions with
    suggestions, planned-anchor validity + collisions incl. empty prefix, plugin
    namespace collisions, alias-bound plugin_name, real anchor cycles — self-edges
    OK).
  • commitStaging — one shared aliasRewriteMap; topological anchor insert
    (self-edges ignored); name: rewrite; plugin_name rewrite; plugin
    merge-by-default; provenance stamps; atomic (validate → snapshot → roll back
    on any mid-commit throw). Does not call pushUndo (the M3 UI wrapper will, once).

Tests — Milestone 2 gate

Suites N7–N10 (+66 checks):

  • N7 dry-run: build/add, prefix recompute, override persistence, commit asserts
    on toString(), provenance stamp, addBareRefs:false.
  • N8 conflicts/built-ins: condition-name collision → resolve → commit; plugin
    merge when class+config match; merge false-positive guard (config-less +
    different rig → namespace); cross-buffer plugin collision → bump; alias-bound
    plugin_name blocks; plugin_name: log preserved; empty-prefix anchor collision
    blocks; unknown command type warns (non-blocking) + raw card preserved;
    transitive closure.
  • N9 edge cases: no variables:/plugins:, zero-alias, depth-3 chain topo
    order, self-edge vs genuine cycle, failed-commit rollback restores doc+mirror.
  • N10 preflight: duplicate-anchor source rejected; broken-alias source rejected
    at parse; derivePrefix/suggestUniqueName.
npm test → arena 10/10 · v2 137/137 · v3 576/576  (was 510)

Footer v0.14 → v0.15. Design doc notes the truly-circular-anchor mirror limit
(pre-existing; D4 fails safe via rollback).

Not in scope

M3 three-pane UI + locking; M4 polish/docs.

🤖 Generated with Claude Code

Second D4 milestone: the full add → plan → commit substrate, in Node, no UI
(UI is Milestone 3). This is where most of D4's risk lives, and it's now fully
test-covered.

js/v3-import.js (staging API, dual-export):
- createStagingBuffer(srcExperiment, filename, opts) — duplicate-anchor preflight
  (#6); filename-derived prefix
- addToStaging(staging, condIdx, yours) — plan walk: visited-set transitive
  anchor closure (#5) into a per-batch registry (#4); plugin merge-vs-namespace
  decision (broadened identity = all fields but name, + rig guard when
  config-less); alias-bound plugin_name detection (#7); built-in `log` left
  alone; unknown-command-type notes; walks imported plugin config aliases
- removeFromStaging / setStagingPrefix / setItemTargetName /
  setAnchorPlannedName / setPluginPlannedName — explicit name overrides persist
  across prefix changes (#4-defaults)
- validateStaging — pre-commit blocking pass: condition-name collisions (with
  suggestions), planned anchor validity + collisions (incl. empty prefix, #2),
  plugin namespace collisions (#10), alias-bound plugin_name (#7), real anchor
  cycles (self-edges OK)
- commitStaging — one shared aliasRewriteMap (#2); topological anchor insert
  (#3, self-edges ignored); name: rewrite (#1); plugin_name rewrite; plugin
  merge-by-default; comment-stamp provenance on imported conditions/anchors/
  plugins (user request); atomic — validates first, snapshots, rolls back on any
  mid-commit throw

Tests: suites N7–N10 (+66 checks) — dry-run build/add/adjust/commit, every
conflict + built-in (collision, merge, merge false-positive guard, alias-bound,
log, empty-prefix anchor collision, unknown command, transitive closure), edge
cases (no variables:/plugins:, zero-alias, depth-3 chain topo order, self-edge
vs genuine cycle, rollback), and preflight rejection (duplicate-anchor source,
broken-alias parse).

npm test: arena 10/10, v2 137/137, v3 576/576 (was 510). Footer v0.14 -> v0.15.
Design doc notes the truly-circular-anchor mirror limitation (pre-existing,
fails safe via rollback).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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