Skip to content

FU(279): validator parity + SessionStart/End wiring + canonicalize asymmetry + --root= form #283

@lantisprime

Description

@lantisprime

Context

5 follow-up findings from negative-scenario-reviewer code review of PR #279 (feat/279-preflight-per-session-namespacing), verdict ACCEPT-with-FU. None blocking merge; all deferred per inline-FU heuristic (architectural / cross-file / symmetric carry-forward).

Findings

FU-1 [P2] — Extend validate-plan-marker-sites.mjs to cover PREFLIGHT_MARKER_*

The validator at scripts/validate-plan-marker-sites.mjs covers PLAN_MARKER_* constants only. PR #279 added sibling PREFLIGHT_MARKER_* constants to scripts/lib/marker-paths.mjs + hooks/lib/marker-paths.sh with identical shape but no drift coverage.

Repro: change hooks/lib/marker-paths.sh line PREFLIGHT_MARKER_SUFFIX_MAXLEN=128 to =64. Run node scripts/validate-plan-marker-sites.mjs. Exit 0 — drift unobserved.

Fix: extend validator (or rename to validate-marker-sites.mjs) to round-trip-validate PREFLIGHT_MARKER_LEGACY_BASENAME, PREFLIGHT_MARKER_SUFFIX_CHARCLASS, PREFLIGHT_MARKER_SUFFIX_MAXLEN, PREFLIGHT_MARKER_BASENAME_RE shell↔mjs parity.

Estimated: ~30 LOC in the validator, plus 4-5 lines of regression tests.

FU-2 [P3] — Wire any_preflight_marker_exists into SessionStart/End

hooks/lib/marker-paths.sh defines any_preflight_marker_exists for symmetry with any_plan_marker_exists, but it has zero callers. Documented intent (SessionStart orphan-sweep + SessionEnd own-session cleanup) is unimplemented for preflight markers. Suffixed forms accumulate unboundedly across the install lifetime.

Repro: open N concurrent sessions, each writes .preflight-done.<sid> via the helper. After all sessions end, files persist in .checkpoints/.

Fix: either (a) wire into hooks/em-recall-sessionstart.sh + scripts/em-session-end-prompt.mjs mirroring the plan-marker pattern; or (b) GC via a separate cron / install-time sweep job.

FU-3 [P3] — Canonicalize per-session marker path in direct-Write deny

hooks/preflight-gate.sh:170-189 detects direct-Write to per-session marker via basename(CANON_TOOL) + dirname(CANON_TOOL) == CANON_PRIMARY_DIR. The legacy marker has CANON_PREFLIGHT = _canonicalize_one(PREFLIGHT_MARKER_LEGACY, ...) — symmetric treatment is missing for PREFLIGHT_MARKER_SID.

Repro: ln -s /tmp/elsewhere $REPO/.checkpoints/.preflight-done.<sid>, then attempt Write to that path. dirname(CANON_TOOL) would be /tmp (link target's parent), bypassing the _tool_parent = CANON_PRIMARY_DIR guard.

Mitigations in place: file-system permissions on .checkpoints/ (default install: not world-writable). Note: the identical structural shape ships unfixed for .last-user-prompt.<sid>.json today.

Fix: canonicalize a candidate PREFLIGHT_MARKER_SID path family and equality-check against CANON_TOOL (parallel to CANON_PREFLIGHT).

FU-4 [NIT] — Dead code if FU-2 not adopted

hooks/lib/marker-paths.sh any_preflight_marker_exists has zero callers. If FU-2 lands, this becomes live code. If FU-2 won't fix, remove the function.

FU-5 [NIT] — --root=/path form inconsistency in helper-invocation gate

hooks/preflight-gate.sh:261 regex '\-\-root[[:space:]]+[^[:space:]]' requires space-separated --root /path. --root=/path form is rejected by the helper's parseArgs separately. Both deny, but the gate's deny message says "missing --root" which is misleading for the = form.

Fix: either accept --root= in the gate regex AND helper parseArgs, or tighten gate deny message to explicitly require space-separated form. Add a test row for --root=/path invocation.

Disposition rationale

  • FU-1 / FU-3: P2/P3 hardening — file as issue per inline-FU heuristic (cross-file).
  • FU-2 / FU-4: architectural lifecycle wiring; tied together.
  • FU-5: NIT-level UX; cross-component.

PR #279 ships with docstrings retracted to reference this issue. None of these are regressions; PR closes the actual #279 stomp bug + adds the env-prefix structural defense as planned.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions