Skip to content

refactor(app): unify Open/OpenForRead into one mode-parameterized open path (links-va-001-app-factory-g4e.1)#211

Merged
brandon-fryslie merged 1 commit into
masterfrom
va-001-app-factory
Jun 10, 2026
Merged

refactor(app): unify Open/OpenForRead into one mode-parameterized open path (links-va-001-app-factory-g4e.1)#211
brandon-fryslie merged 1 commit into
masterfrom
va-001-app-factory

Conversation

@brandon-fryslie

Copy link
Copy Markdown
Collaborator

Implements links-va-001-app-factory-g4e.1 (epic F-15: app.Factory mode unification).

What

  • One access-mode representation: app.AccessMode (string-sum, house idiom from the sealed RelationType in Seal RelationType as a sum-type with one parse boundary (links-va-001-relation-type-raq.1) #206). The cli's private appAccessMode is deleted; the registration tables, commandFamily, and resolveDoctorAccessMode consume app.AccessMode directly. [LAW:one-source-of-truth] — the type lives in app because cli depends on app, never the reverse.
  • One construction path: app.Open(ctx, cwd, mode). The duplicate OpenForRead factory and the dead OpenFromWD (zero callers) are deleted. [LAW:single-enforcer]
  • Fail closed: the old runWithApp switch routed any unrecognized mode to its default: arm — write access, the most-privileged interpretation of an illegal state. The absorbed switch returns an explicit invalid-mode error for unknown modes including the zero value. [LAW:no-silent-failure]

Deviation from the epic's sketch

The epic proposed an AppFactory struct { mode AccessMode }. A struct holding only a mode whose sole method is Open is machinery callers must assemble before acting ([LAW:composability]); a mode parameter is the same single enforcer with less carrying cost. Likewise string-sum over int+iota: iota's zero value would silently mean ReadWrite.

Tests

  • New internal/app/app_test.go: mode contract parameterized by mode (write bootstraps an uninitialized workspace, read refuses it), read-after-write interop, unknown/zero mode fails closed.
  • go vet ./... and go test ./... clean.

@github-actions github-actions 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.

Z.ai Coding Agent Review

Clean refactoring that consolidates Open, OpenForRead, and OpenFromWD into a single Open(ctx, cwd, mode) path. The AccessMode type moves from CLI-internal to the app package, establishing it as the one source of truth ([LAW:one-source-of-truth]). All call sites are mechanically updated. Tests cover the read/write behavioral contract, the read-after-write acceptance path, and rejection of unknown modes including the zero value.

No must-change issues found. Two notes for awareness:

  1. openStore switch (app.go:46). The switch m dispatches to different underlying store functions. This is control flow selecting which code runs, which is the [LAW:dataflow-not-control-flow] pattern. However, the two store functions have genuinely different contracts (bootstrap vs. require-existing), so the switch is the minimum viable boundary mapping — and it replaces the prior scattered caller-side branching, which was worse. The ideal fix would be making store.Open and store.OpenForRead a single function parameterized by intent, but that's a store-layer change outside this PR's scope.

  2. AccessMode is string (app.go:20). The zero value "" is representable but semantically invalid, compensated by runtime validation in openStore. In Go there's no sum-type mechanism to prevent this at compile time. The test at app_test.go:81 explicitly covers the zero-value rejection, so the gap is well-guarded.

✅ Approved

…n path (links-va-001-app-factory-g4e.1)

app.AccessMode is now the single access-mode representation
[LAW:one-source-of-truth]; the cli's private appAccessMode string type is
deleted and every registration table consumes app.AccessMode directly.
app.Open(ctx, cwd, mode) is the single construction path
[LAW:single-enforcer]; the duplicate OpenForRead factory and the dead
OpenFromWD are deleted.

The old runWithApp switch granted write access to any unrecognized mode
via its default arm; the absorbed switch fails closed with an explicit
invalid-mode error [LAW:no-silent-failure], pinned by test.

@github-actions github-actions 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.

Z.ai Coding Agent Review

This PR cleanly unifies Open/OpenForRead/OpenFromWD into a single Open(ctx, cwd, mode) entry point and moves AccessMode from the cli package to the app package where it belongs. The CLI layer no longer branches on access mode — it passes the value through to app.Open, which delegates to AccessMode.openStore for the single boundary dispatch. Tests cover the write-bootstrap, read-refuse, read-after-write, and unknown-mode-rejection contracts.

No must-change issues found. Two observations worth the author's attention:

  1. Pre-existing: resolveDoctorAccessMode (doctor.go:41–42) fails open to write access on flag-parse errors. On cmd.ParseFlags error, it returns AccessWrite rather than AccessRead or a hard error. This is pre-existing behavior unchanged by this diff, but it's worth auditing — a malformed flag string silently granting write access is the kind of silent-failure [LAW:no-silent-failure] guards against.

  2. The openStore switch (app.go:46–51) is a single-point boundary mapping, not scattered control flow. The PR correctly eliminated the scattered switch accessMode that was in runWithApp (old cli.go) and consolidated it into one place. A future step could push the read/write distinction into store.Open itself (e.g., store.Open(ctx, dir, wsID, store.ReadOnly)), eliminating the two separate store constructors and making the dataflow even cleaner — but that's a store-package refactor, not a blocker for this PR.

✅ Approved

@brandon-fryslie brandon-fryslie merged commit 453d08d into master Jun 10, 2026
6 checks passed
@brandon-fryslie brandon-fryslie deleted the va-001-app-factory branch June 10, 2026 09:34
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