Skip to content

writ.segments is shipped in the defaults file and schema, but the command never reads it #746

Open

Description

@David-Noble-at-work

Feature: #762 — The writ lifecycle surface: reconcile, scopes, and the vocabulary settlement

Custom segments are documented in the schema, shipped as an option in the defaults file, and used as the
example in writ deploy's own help. A user who follows any of those gets silence.

This is about the documented surface, not about unfinished internals. The functions that implement the
design are written and waiting; the gap is that shipped documentation describes them as live.

The documented contract

Platform segments come free: DetectSegments() returns OS, DISTRO (Linux only), and ARCH. Anything
else is declared once in config, then assigned. schema/defaults/writ.yaml ships this for a user to
uncomment:

writ:
  # Custom segments (beyond built-in OS, DISTRO, ARCH)
  segments:
    - ROLE
    - SITE

  vars:
    ROLE: desktop

Schema-backed at schema/devlore-config.json:190 — an array of uppercase names, examples: [["ROLE", "SITE"]].
writ deploy --help carries writ deploy -s ROLE=desktop noblefactor as an example.

What the command does

cmd/writ/writ/config.go builds segments identically at lines 81, 130, and 164:

cfg.Segments = segment.DetectSegments().LoadFromEnv()

DetectSegments() returns the platform three. The writ.segments key is never read.

What a user experiences

  1. Uncommenting writ.segments in the defaults file does nothing. The key has no reader, so declaring
    ROLE leaves the segment list unchanged.

  2. WRIT_SEGMENT_ROLE=desktop does nothing, silently. LoadFromEnv populates values only for segments
    already in the list, and the list is OS/DISTRO/ARCH. The variable is read and discarded.

  3. --segment ROLE=desktop works — config.go:88 calls Segments.Set, which appends when the name is
    not found (segment.go:135). So the flag from the help example works, while the config from the defaults
    file does not, and nothing distinguishes them to the user.

  4. The error message points at the inert key. Segments.SetValues returns
    "undefined segment: ROLE (must be defined in config)". Whichever path eventually surfaces that message,
    it currently names config as the remedy for a key nothing reads.

Why the silence is the problem

Segments.Match (segment.go:84) requires every suffix of a layer directory to be a value the machine
reports; a directory whose suffix matches nothing is simply not selected. That is correct for
noblefactor.Windows on a Mac, and indistinguishable from it when a segment is missing because its
declaration was never loaded.

So Home/noblefactor.desktop, on a machine whose config declares ROLE and sets ROLE=desktop, is skipped
with no error — because a non-match is the normal case. A layer that never deploys and never says why.

Suggested fix

Read the key through the function already written for it:

cfg.Segments = segment.DetectSegmentsWithNames(viper.GetStringSlice("writ.segments")).LoadFromEnv()

at all three sites. Then settle whether --segment keeps Set's append or moves to SetValues. Those are
two different products, and the choice decides what the docs should say:

  • SetValues — declaration is mandatory, the config key is load-bearing, and a typo'd --segment is
    caught rather than silently creating a segment that matches no directory. This matches what the defaults
    file and the error message currently describe.
  • Set — the flag stays an escape hatch needing no ceremony, and config exists for values that persist.
    Then UndefinedSegmentError describes a rule that is not the rule, and its wording should change.

Verification

  • writ.segments: [ROLE] with vars: {ROLE: desktop} selects Home/<project>.desktop.
  • WRIT_SEGMENT_ROLE=desktop does the same.
  • A declared-but-unassigned segment keeps behaving like DISTRO on macOS: empty value, suffix matches nothing.
  • Undeclared --segment behaves per whichever rule is chosen, pinned by a test.

Note on evidence

The code paths above were read, not run. --segment is registered on deploy only
(cmd/writ/writ/commands.go:54), and writ deploy --dry-run on this machine stops earlier at
layers have uncommitted changes: [personal], so item 3's runtime behavior is inferred from Set's append
rather than observed.

Activity

  1. changed the title [-]writ.segments is documented, schema-backed, and read by nothing; only --segment works, and by accident[/-] [+]writ.segments is shipped in the defaults file and schema, but the command never reads it[/+] on Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Area:CLICommand-line interfaceEpic:WritDeploymentwrit deployment: layers, adoption, and driftbugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions