Skip to content

refactor(explorer): simplify authoringv2 command application into cohesive mutation families (#192) - #210

Draft
matthewpeterkort wants to merge 1 commit into
feature/postgres-backendfrom
ao/repo-simplify/192-authoring-command-families
Draft

matthewpeterkort wants to merge 1 commit into
feature/postgres-backendfrom
ao/repo-simplify/192-authoring-command-families

Conversation

@matthewpeterkort

Copy link
Copy Markdown
Collaborator

Summary

Extracts the 29 substantial command-case bodies from the ~694-line applyCommand switch in internal/explorer/authoringv2/commands.go into private helpers in four family files, grouped by the responsibilities named in #192:

  • command_tables.go — table/population mutations (create, duplicate, delete, rename, reorder, set root, set/clear population)
  • command_routes.go — route mutations (add, update edge, set match mode, remove)
  • command_columns.go — column mutations (add, update, construction-output presentation, transformation, contributor set/clear, source update, row-value policy, remove)
  • command_proposals.go — server-resolved proposal applications (root rebase incl. replace mode, construction choice, interpretation candidate, row-definition/table-shape/population-member/construction proposals, draft restore)

ApplyCommands remains the single atomic batch pipeline (clone once → migrations → ordered application → normalize once → contributor/BuilderState validation). applyCommand keeps the shared pre-switch rowValuePolicy refusal, the small explicit command.Type switch in unchanged case order, the unsupported-type fallthrough, and the existing tiny delegations inline (applyFrameSourceCommand, applyColumnSource, applySemanticSelections). Each helper receives only its actual data dependencies (workspace, catalog, commandID, index, command as needed) — no service objects, no registry, no one-line forwarding layers, no new wire schema, command type, global, or backend I/O. No map iteration is used for dispatch.

Closes #192.

Why this reduces reasoning burden (not line count)

  • A reviewer answers a command question in one hop. Before: any mutation question required holding the entire 694-line switch in view to locate the case and to confirm no later case shares state. After: the switch is a 29-case index where each line names the single helper that owns the mutation, and each helper is one verifiable unit (~6–75 lines).
  • Feature contracts stay in one place per family. Live accumulated policy (same-table root replacement, ROOT_REBASE_REQUIRED, route max-hops/repeated-edge refusals, stable source-column IDs, row-value-policy bindings, server-resolution refusals) now reads as contiguous family code instead of interleaved cases, while the shared invariants (batch atomicity, ordering, clone semantics) have exactly one owner (ApplyCommands).
  • Explicit data dependencies are checkable. Each helper signature states precisely what mutation state it touches; catalog/commandID/index appear only where the body uses them, so reasoning about a helper needs no mental model of mutable service state.
  • Zero interpretation risk in the move. Bodies moved verbatim under machine token-identity verification (below), so the only cognitive load added is naming, not re-derivation.

Extraction tooling (>500 touched lines rule)

Transformation done by a rerunnable AST-based transformer (go/ast + go/scanner) that:

  1. replaces each planned case body with a single dispatch line,
  2. emits each helper as func applyXCommand(deps…) (CommandResult, error) { result := CommandResult{Type: CommandResultTableChanged, OutputID: command.OutputID}; <case body verbatim> },
  3. verifies token identity of every moved body (29/29 MATCH, 6,008 moved tokens total) apart from the deliberate per-helper result prologue,
  4. verifies the case list/order is unchanged, ApplyCommands is token-unchanged, and the applyCommand prelude/guard (clone-once pipeline, rowValuePolicy refusal) is token-unchanged,
  5. recomputes imports mechanically.

All checks: ALL CHECKS PASSED: 29 cases extracted, 3 remain inline, case order preserved.

Verification

Focused checks first, then full suites, on base 80e88771309881e9d473d76dc2226ed10f204af9:

Check Result
git diff --check clean (exit 0)
go test -json ./internal/explorer/authoringv2 -run TestApplyCommands -count=1 23 matched, 23 passed, 0 skipped
go test -json ./internal/explorer/authoringv2 -count=1 (full package) 357 matched, 357 passed, 0 skipped
go test -json ./internal/explorer ./internal/explorer/lifecycle -count=1 explorer 124/124; lifecycle 715 matched, 713 passed, 2 skipped — identical to the pre-change baseline (same two environment skips: TestBuilderFullUIRequestReplay, TestConstructionProposalOperationHistoryTraces)
go build ./..., go vet ./internal/explorer/..., gofmt -l clean
scripts/check_no_code_comments.py on all five touched files scanned=5, violations=0

Preserved named guards: TestApplyCommandsRejectsBatchAtomically, TestApplyCommandsSourceEditsPreserveColumnIdentityAndAreIdempotent, TestApplyCommandsRejectsRouteBeyondCatalogMaxHopsWithoutMutation, and the root-rebase refusals all pass unmodified. No test expectations were changed; no test was edited.

Canonical-bytes differential (before vs after trees): a 71-scenario differential harness (31 success paths + 40 refusal paths, ≥1 scenario per command family, including invalid-last mixed batches spanning multiple families, route too-long/repeated-edge refusals, root-rebase refusal, server-resolution refusals, restore semantics, replay idempotency) recorded canonical workspace digests, CommandResult JSON, and error strings against baseline and the extracted tree: 0 differing scenarios of 71; all success-path canonical digests equal; caller-input non-mutation asserted per scenario. Behavior-preserving extraction confirmed at the observable-bytes level.

Source-integrity/browser acceptance: no source-integrity map or shared-state consumer in the repo references applyCommand internals (verified by repo-wide search), so the ticket's conditional Builder command-browser acceptance requirement is not triggered; the two environment-skipped lifecycle tests are unchanged from baseline and are not affected by this pure-in-package move.

Risks / follow-ups

  • Pure relocation refactor; the only added code is the mechanical per-helper result prologue and parameter plumbing.
  • The two lifecycle skips are pre-existing environment-gated (missing live services) and were recorded, not silenced.
  • Review comments and the AO native review remain the coordinator's call per ticket rules.

… of applyCommand

Extract the 29 substantial command-case bodies from the ~694-line
applyCommand switch into private helpers in four family files
(command_tables.go, command_routes.go, command_columns.go,
command_proposals.go), grouped by table/population, route, column, and
server-resolved proposal responsibility. Keep ApplyCommands as the
single atomic batch pipeline, the pre-switch rowValuePolicy refusal,
the small explicit command.Type switch, exact case order, and the
existing tiny delegations (frame sources, ADD_COLUMN_SOURCE,
ADD_SEMANTICAL via applySemanticSelections) inline. Helpers receive
only their actual data dependencies; no registry, no forwarding layers,
no new wire surface.

Extraction was performed and verified by a rerunnable AST-based
transformer that moves case bodies verbatim and token-verifies each
moved body against the original apart from the deliberate per-helper
result prologue; the tool also verifies case order, the unchanged
ApplyCommands pipeline, and the unchanged guard prelude.

Behavior evidence (identical before/after on 80e8877 base):
- go test -json ./internal/explorer/authoringv2 -run TestApplyCommands -count=1: 23/23 pass, 0 skip
- full authoringv2 suite: 357/357 pass, 0 skip
- go test -json ./internal/explorer ./internal/explorer/lifecycle -count=1: 124/124 and 713/715 pass with the same 2 environment skips as baseline (TestBuilderFullUIRequestReplay, TestConstructionProposalOperationHistoryTraces)
- 71-scenario differential dump (31 success paths, 40 refusal paths across every command family, including invalid-last mixed batches): canonical workspace digests, CommandResult JSON, and error strings byte-identical before/after; caller-input non-mutation asserted per scenario
- scripts/check_no_code_comments.py on all five touched files: 0 violations

Closes #192

@matthewpeterkort matthewpeterkort left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review of 9bd971d against base 80e8877: APPROVE - no changes required.

I re-verified the extraction independently rather than trusting the transformer receipt:

  • All 29 extracted case bodies compared line-by-line against the base applyCommand: every body is verbatim; the only addition is the mechanical per-helper result prologue, identical to the original prelude, so all result-based returns are unchanged.
  • ApplyCommands (clone once -> migrations -> ordered application -> normalize once -> contributor/BuilderState validation), the pre-switch rowValuePolicy refusal, the unsupported-type fallthrough, and the three inline cases (frame-source trio, ADD_COLUMN_SOURCE, ADD_SEMANTIC_SELECTIONS) are byte-identical; the dispatch switch preserves the exact case set and order.
  • Control-flow audit: no fallthrough or goto; every break and continue in the moved code sits inside a for loop, so extraction cannot rebind them; the by-value mutation of command in the construction-choice path and the workspace pointer writes in root-rebase and restore preserve base semantics.
  • Parameter plumbing is exact: commandID and index appear only in helpers that consume commandGeneratedID / stagedSourceColumnID / ensureConstructionRoute / applyColumnSource, and catalog only where referenced.
  • Import hygiene: commands.go loses only the sort import (its sole consumer REORDER_TABLES moved out); each family file imports exactly what it uses; the 29 new helper names have no collisions and the delegation targets (applyColumnSource, applySemanticSelections, applyFrameSourceCommand, stagedSourceColumnID, ensureConstructionRoute) are untouched.
  • No test files were touched; existing package coverage (357 tests) plus the documented 71-scenario canonical-bytes differential are appropriate evidence for a pure move.

Optional, non-blocking: the 29-fold result prologue is inert in helpers that never mutate it (e.g. applyCreateTableCommand), and the moved copy := shadow in applyDuplicateTableCommand and documentIndex := documentIndex(...) shadow in applyUpdateConstructionOutputCommand are carried verbatim from the base rather than introduced here. Neither needs action.

Verdict: approved.

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