Skip to content

The layer journey as a scenario: self install, repo set in every combination, deploy and deploy thenobles, then the move — on every platform #855

Open

Description

@David-Noble-at-work

Feature: #463 — writ command surface: layers, segments, adoption. Asked for 2026-09-07 after the
real thing ran on Danoble-MBP-A: "We need an integration test as complete as the scenario we just ran.
It must run on our complete platform matrix: Darwin, Linux, and Windows, all architectures. Ubuntu
distro." Extended the same day: "The integration test(s) should cover this scenario: writ self install; writ repo set [base|team|personal] [url|path], all combinations in turn; writ deploy and
writ deploy <another> to be sure that writ deploy thenobles works."

The journey it replays

The real writ binary in a pristine sandbox, on every platform, from a binary that has never run to a
converged machine and through a layer's own history moving under the deployed links:

  1. writ self install — the binary installs itself into the sandbox; repo list is empty, with no
    placeholder read as a registration (writ self install creates empty layers/base and layers/team, which repo list reads as registrations and repo add refuses #840).
  2. writ repo set, all combinations in turn — base, team, personal, each by path and by file://
    URL, set over one another (replacement narrated, a writ-owned clone removed, a user path never),
    unset, and the old verbs unknown (writ repo set and unset replace add and remove: a layer has one registration, and re-pointing it is set #791, writ repo remove leaves the clone it made in the writ-owned home #792, writ names a cloned repository by its layer; it should name it the way git clone does #793); a URL-registered clone brought forward when
    its remote advances (writ has no command to refresh a repository it cloned; a layer's manifests stay at the clone's checkout until the user runs git by hand #812). Then every one of the seven non-empty layer subsets is registered and a
    bare deploy proves the implicit set is exactly common* from the registered layers plus each
    registered repository's own-named project (The implicit projects are common and one per configured layer repository; a repository-named project in any layer deploys unnamed #850) — personal alone, the state this machine lived
    in for months, among them, and base alone from a fresh self install as Break the bootstrap cycle: deploying base must not require base #477's proof.
  3. writ deploy, then writ deploy thenobles — the bare form converges the implicit set (writ deploy with no project refuses, though common is implicit and the config path handles zero projects #843,
    The implicit projects are common and one per configured layer repository; a repository-named project in any layer deploys unnamed #850); four collisions narrated, personal over base (Report conflicts when two sources provide the same path #470); selectors put .Darwin on macOS only,
    .Linux/.Debian on Ubuntu only, .Windows on Windows only, common everywhere; every consumer
    answers --help, on Windows under Git for Windows' bash. thenobles is added by name, is still
    there after the next bare deploy (The implicit projects are common and one per configured layer repository; a repository-named project in any layer deploys unnamed #850's persistence), is removed by decommission with the rest
    re-converged, and common and a repository-named project are refused by name (writ decommission: --all removes everything writ deployed, a named removal re-converges the rest, and an implicit project is refused by name #851).
  4. The move (noblefactor-ops#147, personal#172): personal advances to the commit that renames
    noblefactor to noblefactor-ops and moves its consumers out of common*, with no redeploy — the
    links dangle, counted; the dry run says nothing (writ deploy --dry-run returns before the conflict pre-flight, so a dry run cannot say what stop will refuse #853); --conflict=replace relinks everything to
    the base's helper and leaves the old links under ~/local as orphans (writ upgrade does its whole job: links added, orphans removed, stale links relinked, copies regenerated -- what the name promises #845); the store still counts
    them after a hand cleanup (writ upgrade does its whole job: links added, orphans removed, stale links relinked, copies regenerated -- what the name promises #845); the default policy accepts writ's own links; a file outside Home/
    forces --allow-dirty (writ deploy judges dirtiness at the repository root: a modified Inventory file forces --allow-dirty though only Home/ is planned or served #852).

The rule that makes it land now

Every step is the ruled interface. Where today's binary lacks it, the step skips by the name of
the issue that ships it
, keyed on a capability probe, and the run prints its skip-list. Nothing is
rewritten to today's verbs. The scenario is therefore the writ lane's executable acceptance test for
#840, #791, #792, #793, #843, #850 and #851, green on the day each lands. Part 3's rows about what
deploy leaves behind assert today's behaviour with the issue in the failure message, so a fix's landing
is visible as the scenario failing at that step.

Where it runs

The existing scenario job: darwin-arm64, linux-amd64 (ubuntu-latest, 24.04), linux-arm64
(ubuntu-24.04-arm), windows-amd64, windows-arm64, each leg independent. Decision needed: "all
architectures" includes darwin/amd64, which docs/plans/arm64-build-and-test-matrix.md ruled built but
never run — Intel macOS bills at 10×, the runner labels are a closed set (macos-15-intel,
macos-26-intel). Either that ruling stands and the matrix stays at five, or macos-15-intel joins it
for the scenario job.

Shape: the hybrid (ruled 2026-09-07)

A second scenario beside TestWritDeployScenario — its own file, cmd/writ/scenario_layer_journey_test.go,
so it does not collide with the writ lane's edits to the first — reusing the sandbox, runWrit,
initializeRepo and the assertion helpers. One test body, two ways to fill its layer table:

  • Fixture mode, the PR gate. Four checked-in trees under cmd/writ/testdata/layer-journey/
    (base, team, personal-a, personal-b; the helper is the real file from noblefactor-ops at
    dee8e84e5) become repositories named as the real ones — noblefactor-ops, devlore-cli,
    personal — each with a bare clone in the sandbox, so every layer has a path and a file:// URL and
    commit B reaches a by-URL run through its remote. No token, no network, runs on forks; the five-leg
    scenario job runs it via make test-scenario.
  • Real mode, WRIT_SCENARIO_REAL=1, never on the PR gate. noblefactor-ops by URL at dee8e84e5,
    devlore-cli as the running checkout, personal at 27e53171 and 54de61d6 — the real move, one commit
    apart — by path locally and by URL in CI with PERSONAL_LAYER_TOKEN (fine-grained, contents read,
    that one private repository). Asserts invariants, not inventories; personal's git-crypt files stay
    ciphertext in a sandbox that holds no key. Runs from scenario-real.yaml on a schedule and on
    dispatch, falling back to the fixture personal when the secret is absent.

Special-purpose GitHub repositories were weighed and set aside: three more things to keep in step, for
the one thing the two public real repositories already give — a clone over the network. The
consumer-CI half of the real move (shellcheck following the helper from a pinned base checkout) is
personal's workflow and out of scope: it ran for real on personal#174.

Acceptance

Plan

docs/plans/feature/855-layer-move-scenario.md on feature/855-layer-move-scenario.

Related

#346 (the first scenario), #470 (the three-layer fixture it asked for), #840, #791, #792, #793, #843,
#850, #851 (the rulings Parts 0–2 test), #845, #852, #853, #831, #842 (the behaviours Part 3 names),
noblefactor-ops#147 and personal#172 (what Part 3 replays).

Activity

  1. changed the title [-]A layer-move scenario: the base takes the foundation, personal renames its project, and every platform proves the deploy, the breakage, and the redeploy[/-] [+]The layer journey as a scenario: self install, repo set in every combination, deploy and deploy thenobles, then the move — on every platform[/+] on Sep 8, 2026
  2. David-Noble-at-work commented on Sep 8, 2026

    @David-Noble-at-work
    ContributorAuthor
  3. David-Noble-at-work commented on Sep 8, 2026

    @David-Noble-at-work
    ContributorAuthor

    Phases 1–4a landed in #859 (2026-09-08). The second run was green on all six legs — darwin-arm64, darwin-amd64 (macos-15-intel, its first outing), linux-amd64, linux-arm64, windows-amd64, windows-arm64 — with the same skip-list on each: #791 (carrying #792 and #793), #812, #840, #850, #851, and #860 on the Ubuntu legs. The first run taught two things, both folded in: writ names a distribution by ID alone (now #860, ruled), and a Windows checkout rewrites the fixtures' line endings (the fixture tree pins LF).

    Phase 4b — real mode, scenario-real.yaml, PERSONAL_LAYER_TOKEN — is the next PR here. The writ lane's recorded order clears #812 and #860 first; each landing should turn its skip into a pass without this scenario changing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Epic:WritDeploymentwrit deployment: layers, adoption, and drifttaskImplementation work item

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions