{
  "content": {
    "icon": "bi-box-seam",
    "id": "ship",
    "label": "Ship",
    "sections": [
      {
        "icon": "bi-link-45deg",
        "id": "one-chain",
        "intro": "One command runs every tool in this method as a single gate, in stages typed as shown in [a stage array]. The stages form a [pipeline architecture](/records/architecture/pipeline-architecture) ordered by [causal dependency](/records/architecture/causal-dependency), as shown in [the stages], and each stage runs the checks, fixers, generators and validators it owns. A check that runs only when you or the model remember its command is a convention rather than a check. Arguments can narrow the chain while you iterate, but only the whole run supports a claim that the work is done, as shown in [narrowing]. The chain is what turns a collection of tools into a verdict, and the rule it serves is described in [the gate holds the line](/disciplined-methodology/build#the-gate-holds-the-line).",
        "subsections": [
          {
            "blocks": [
              {
                "application": "In practice, the project has one entry point whose default is the whole pipeline, and arguments can narrow it but never widen it. Its stages are ordered by what each one needs from the one before, and a dependency is stated wherever a step produces an artifact that another stage consumes. Every check, fixer, generator and validator sits in a stage as a direct call, never as an alias the gate shells out to. The aggregate report is written on every exit path, and the gate governs its own tooling with the same rules it applies to the code.",
                "boundary": "Narrowing is only for speed while iterating locally. A member, a step or a bypass answers a question faster, but a claim that the work is done needs one whole-scope run with nothing bypassed. A narrowed run never overwrites the one aggregate, because a report about a narrower subject under the aggregate's name would describe a different subject under the same name.",
                "cause": "A tool outside the chain depends on a developer's memory, and memory is the one component in the system with no check on it.",
                "decision": "Every tool is routed through the one entry point, rather than kept as a command of its own.",
                "failureMode": "There are three linters, two of which have not been run since spring, and a formatter that runs on some machines only because of an editor plugin.",
                "kind": "lesson",
                "principle": "For this reason every tool runs through one chain, because a tool reached only by its own command enforces nothing.",
                "problem": "Tooling accumulates as separate commands, and the commands stop getting run.",
                "validation": "To check this, list every check the project claims to have and run the one command. Each check should appear in its output; one that does not is not a check the project has."
              },
              {
                "kind": "text",
                "text": "The stage order encodes real dependencies, [event ordering](/records/architecture/event-ordering) in the ontology's sense, so it carries weight rather than being tidy. A cleaning step runs before anything measures a file, and a type check runs before any structural check reads a tree that may not compile. The fixer stage writes the closure graph that the graph-aware rules read when they load, so it comes before linting, and a graph-aware rule fails closed when the graph is missing rather than passing over nothing. Several stages change the working tree, which is why an investigation never runs the gate, as described in [agents as executed contracts](/disciplined-methodology/collaborate#agents-as-executed-contracts)."
              },
              {
                "kind": "text",
                "text": "A registry the run consumes at load is derived again before anything loads it, so deleting a member cannot break the run that would have removed its entry. Deleting a rule is then one step, just as adding one is one dropped file. The chain also checks its own registration contract and the shape of every finding."
              },
              {
                "kind": "text",
                "text": "Where a host project already has a toolchain, the chain hands off rather than duplicating it. The host chooses which concerns to hand over, its tools stay its own, and there is one chain instead of two. A [verification](/records/architecture/verification) slot the host cannot fill, such as a build, a runtime probe or a size cap, resolves as absent. The step that reads it does not run, and the claim it would have settled is carried as observed by the developer rather than as verified."
              },
              {
                "kind": "text",
                "text": "The chain is data before it is a run. A pure planner takes the resolved scope, meaning which members, which step and which bypasses, and returns the stage array, and the runner walks that array in order, with any order that carries weight stated on the step rather than remembered. A member with no tests declares null rather than an empty string, because the two are different claims. Everything a stage-control flag can do is a function over this data, and no flag can add a step that the array does not hold."
              },
              {
                "code": "export const STAGE_SLUGS = [\"<stage>\", \"<stage>\", \"<stage>\"] as const;\nexport type StageSlug = (typeof STAGE_SLUGS)[number];\n\nexport interface Member {\n    readonly id: string;\n    readonly dir: string;\n    readonly gated: boolean;\n    readonly tests: string | null;\n}\n\nexport interface Step {\n    readonly label: string;\n    readonly command: readonly string[];\n    readonly scope: \"wide\" | \"perMember\" | \"appOnly\";\n    readonly tags: readonly (\"generate\" | \"validate\" | \"build\")[];\n    readonly produces?: string;\n    readonly consumes?: string;\n}\n\nexport interface Stage {\n    readonly slug: StageSlug;\n    readonly bypassedByDefault: boolean;\n    readonly parallel: boolean;\n    readonly steps: readonly Step[];\n}\n\nexport declare function stagesFor(scope: { readonly members: readonly Member[]; readonly only?: string; readonly bypass?: readonly StageSlug[] }): readonly Stage[];",
                "kind": "code",
                "language": "typescript",
                "title": "a stage array"
              },
              {
                "caption": "the stages",
                "kind": "mermaid",
                "text": "flowchart TB\n    command[\"one command\"]\n    rederive[\"the rule index is re-derived from disk before any stage\"]\n    prepare[\"dependency integrity, one typecheck per member\"]\n    unused[\"dead code, dead exports, dead dependencies\"]\n    fixers[\"strip comments, rebuild the graph, run the rewriters\"]\n    format[\"the formatter, fix on\"]\n    lint[\"every linter, per member, fix on, reading the graph the fixers wrote\"]\n    test[\"every suite, then the passing-test floor\"]\n    build[\"the site, the diagrams, the chapters, every generator, every derivation\"]\n    validate[\"discovery, leaks, graphs, configs, documents\"]\n    report[\"the one aggregate · written on every exit\"]\n    command --> rederive --> prepare --> unused --> fixers --> format --> lint --> test --> build --> validate --> report\n    prepare -. fail fast .-> report\n    lint -. fail fast .-> report\n    test -. fail fast .-> report"
              },
              {
                "caption": "narrowing",
                "kind": "mermaid",
                "text": "flowchart TB\n    whole[\"No arguments · every stage, whole scope, healing on\"]\n    member[\"A member · that member's steps, repo-wide steps reported as skipped\"]\n    step[\"A step · only that step\"]\n    bypass[\"A bypass · one stage skipped, local iteration only\"]\n    claim[\"A completion claim\"]\n    whole --> claim\n    member -. never satisfies .-> claim\n    step -. never satisfies .-> claim\n    bypass -. never satisfies .-> claim"
              }
            ],
            "title": "Everything through one chain"
          }
        ],
        "title": "One chain"
      },
      {
        "icon": "bi-rulers",
        "id": "scale-follows-from-structure",
        "intro": "How many parties a body of work needs is not a matter of choice. The count follows from how the work divides into concerns that must be able to contradict each other. The floor is the number of those concerns, and the ceiling is set by the worst fan-in, the point at which claims resting on one surface stop surviving; [floor, ceiling, count] shows where each comes from. The architecture page derives the same range for a system, as described in [a concern is a component](/software-architecture/scale#a-concern-is-a-component) and [the ceiling moves by cost](/software-architecture/scale#the-ceiling-moves-by-cost).",
        "subsections": [
          {
            "blocks": [
              {
                "application": "In practice, the work is first divided into concerns, and counting them gives the floor. The fan-in on each [shared surface](/pag/orchestration#shared-surfaces) is measured from the traffic the surface already records, which gives the [fan-in ceiling](/records/architecture/fan-in-ceiling). A count is then chosen inside that range, and the partition it came from is written down.",
                "boundary": "Volume is the wrong input, as described in a concern is a component; the fan-in caps a small task and a large one alike.",
                "cause": "A count chosen independently of the partition either leaves a concern with no owner or gives one surface more claims than it can hold.",
                "decision": "The work is partitioned first and counted second.",
                "failureMode": "Five agents work on a task that has two concerns, three of them wait, and the surface they all write to becomes the bottleneck.",
                "kind": "lesson",
                "principle": "For this reason the number of parties follows from the structure of the work rather than from a preference.",
                "problem": "The number of agents or people on a task comes from a preference, and a preferred number is wrong in one of two directions.",
                "validation": "To check this, ask what partition the current count came from. A count with no partition behind it is a preference, and the fan-in will expose it."
              },
              {
                "kind": "text",
                "text": "With fewer parties than the floor, one concern has no owner, so it is decided by whichever party happens to be nearest, which is the substitution every other chapter refuses. With more parties than the ceiling, the shared surface becomes the bottleneck: a claim is more often stale by the time it lands than it is read, and the parties spend their rounds re-deriving each other's reads."
              },
              {
                "kind": "text",
                "text": "The argument is carried on the architecture page in [scale follows determinism](/software-architecture/scale#scale-follows-determinism). A deterministic check returns the same verdict whoever runs it, so adding a party adds no enforcement cost, and that is why every coordination rule here is either a mechanism or a declared piece of conduct with its evidence written down."
              },
              {
                "caption": "floor, ceiling, count",
                "kind": "mermaid",
                "text": "flowchart TB\n    work[\"A body of work\"]\n    partition[\"Partition it into concerns that must be able to contradict each other\"]\n    floor[\"Floor · the number of concerns\"]\n    fanin[\"Measure the fan-in on each shared surface\"]\n    ceiling[\"Ceiling · the worst fan-in before claims stop surviving\"]\n    count[\"The count · a choice inside the range, with the partition written down\"]\n    work --> partition --> floor\n    partition --> fanin --> ceiling\n    floor --> count\n    ceiling --> count"
              }
            ],
            "title": "Floor and ceiling"
          }
        ],
        "title": "Scale follows from structure"
      },
      {
        "icon": "bi-cloud-upload",
        "id": "the-deploy-is-a-file-operation",
        "intro": "The deployable is derived from the routes the site serves, as shown in [routes to site]. The deploy is a file operation with a [rollback](/records/architecture/rollback), and it never touches a process it does not own, which is [least privilege](/records/architecture/least-privilege) applied to a deploy. [Secrets management](/records/architecture/secrets-management) keeps every secret outside the tree. Every served surface uses [encryption in transit](/records/architecture/encryption-in-transit), including the local development server, because [environment parity](/records/architecture/environment-parity) means development exercises the same transport as production; otherwise it exercises something else.",
        "subsections": [
          {
            "blocks": [
              {
                "application": "In practice, the deploy starts from the served routes and follows every path a text artifact names. Every file that nothing reaches is deleted, and the whole of what is left is uploaded. One backup is kept of what is replaced, and it is restored on any failure. A command that touches a shared machine is handed to the developer who owns that machine rather than run, and every secret stays in the one artifact declared to hold it.",
                "boundary": "On a machine that hosts other people's processes, nothing beyond your own files is touched. The deploy is a file operation because a file operation can be rolled back, while a process operation has a blast radius.",
                "cause": "Nothing between the build and the upload asks whether a file is reachable.",
                "decision": "The deployable is pruned from the routes rather than taken from the build folder on trust, and a file operation is chosen over a process operation for its rollback.",
                "failureMode": "A page fails to pre-render, and nothing reports it because the old file is still in the build folder, so the stale page ships.",
                "kind": "lesson",
                "principle": "For this reason the deployable is whatever a page can reach.",
                "problem": "A deploy that ships a build folder ships whatever happened to be in it.",
                "validation": "To check this, list every file in the deployable and the route that reaches it. A file that no route reaches is one the discovery check should have refused."
              },
              {
                "kind": "text",
                "text": "A discovery check derives, from the same registry the build reads, every route a page can serve and every file a route reaches. It fails the build on a route with no rendered file, a payload a machine cannot parse, or a file that nothing reaches, so the problem is found before a deploy rather than after."
              },
              {
                "caption": "routes to site",
                "kind": "mermaid",
                "text": "flowchart TB\n    routes[\"The served routes\"]\n    follow[\"Follow every path a text artifact names\"]\n    reached[\"What a page can reach\"]\n    prune[\"Delete everything else\"]\n    backup[\"Back up what is live\"]\n    upload[\"Upload the whole of what is left\"]\n    check{\"Healthy?\"}\n    done[\"Live\"]\n    restore[\"Restore the backup\"]\n    routes --> follow --> reached --> prune --> backup --> upload --> check\n    check -- yes --> done\n    check -- no --> restore"
              }
            ],
            "title": "Derived from the routes"
          }
        ],
        "title": "The deploy is a file operation"
      },
      {
        "icon": "bi-x-circle",
        "id": "when-not",
        "intro": "A rule that stands in for a property is a proxy. A proxy is easy to state and easy to enforce for its own sake, and it ends up guarding the wrong thing; the ontology's names for the habit are [pattern cargo cult](/records/architecture/pattern-cargo-cult) and [golden hammer](/records/architecture/golden-hammer). Two proxies come up often enough to name here, and [two proxies] shows each one with the property it stands for and what holds that property instead. Two others are retired elsewhere in the method: a fixed line count repeated in every document is retired in [one home](/disciplined-methodology/build#one-home), and a voice the model keeps up all session is retired in [a seat is a contract](/disciplined-methodology#a-seat-is-a-contract).",
        "subsections": [
          {
            "blocks": [
              {
                "application": "In practice, the question for every rule is what property it protects, and that property is named.",
                "cause": "A proxy is easier to state than the property, so it gets stated first and then outlives its reason.",
                "decision": "The property is checked directly and the proxy is let go, rather than the rule being kept beside the check.",
                "failureMode": "The team argues about whether a file may have one more line, while the module it lives in has no boundary at all.",
                "kind": "lesson",
                "principle": "For this reason a rule that stands in for a property gives way once the property has a check.",
                "problem": "A rule that outlives its reason gets enforced for its own sake.",
                "validation": "To check this, stop enforcing one rule for a cycle. If the property it stood for still holds, the rule was the proxy."
              },
              {
                "caption": "two proxies",
                "kind": "mermaid",
                "text": "flowchart TB\n    subgraph proxies[\"The proxy rule\"]\n        r1[\"Benchmark everything before any application logic\"]\n        r2[\"Synchronous over asynchronous, always\"]\n    end\n    subgraph properties[\"The property it stands for\"]\n        p1[\"A measurement precedes an optimization\"]\n        p2[\"One correct answer exists\"]\n    end\n    subgraph holds[\"What holds the property\"]\n        h1[\"Measure a hot path when you have one, never as a rite\"]\n        h2[\"Asynchronous by necessity, still one correct answer\"]\n    end\n    r1 --> p1 --> h1\n    r2 --> p2 --> h2"
              }
            ],
            "title": "The property behind the rule"
          }
        ],
        "title": "Proxies give way"
      },
      {
        "icon": "bi-dash-circle",
        "id": "the-honest-gaps",
        "intro": "Some things this method does not do. Nothing computes worth: the [utility](/records/reasoning/node-tel-utility) and [cost](/records/reasoning/node-tel-cost) nodes of the ontology's teleology axis are empty slots here, and I make that decision myself. Nothing detects a lack of progress; [diminishing returns](/records/reasoning/node-ter-diminishing-returns) is a node I notice rather than a detector that fires. [Confidence](/records/reasoning/node-ver-confidence) is a threshold rather than a distribution, and several conduct rules have no artifact behind them yet. I state these gaps because a method that claims to be complete is one whose gaps you find in production. The architecture page keeps its own list of the same kind, where each gap is declared absent and never assumed.",
        "subsections": [
          {
            "blocks": [
              {
                "application": "In practice, a list is kept of the guarantees the method claims, and each is marked as held, derived or absent.",
                "cause": "A guarantee that nothing provides gets assumed by the developer or the model who needs it, and the assumption never fails where it began.",
                "decision": "An absence is declared in the adapter rather than filled with a default in the core.",
                "failureMode": "The method reads as complete, a reader relies on a guarantee it never gave, and the reader finds out where it matters most.",
                "kind": "lesson",
                "principle": "For this reason an absence is measured and declared, rather than left for you or the model to infer or cover over.",
                "problem": "A method that hides its gaps hands them to the reader unannounced.",
                "validation": "To check this, read the declared absences; each should name what would fill it. A method with no declared absences has stopped looking for them."
              },
              {
                "kind": "text",
                "text": "The absences are named in the adapter as slots that nothing fills. Whether a task is worth doing is a decision I make with a sentence written before the work, and no mechanism computes utility against cost across the options. No step compares a previous render with a fresh one, so the generators overwrite unconditionally and the drift check is the only signal of a fixed point. The gate has no detector for a run that thrashes, so it runs every step every time and cannot tell a converging run from an oscillating one. A claim passes or fails a threshold, and nothing carries a distribution of how sure the verifier is. No registry entry carries its own test of whether it applies, so every composition iterates over everything. Finally, no predicate compares a number written in prose against a real count, because prose carries no count and the predicate would have nothing to check."
              },
              {
                "kind": "text",
                "text": "The naming standard is enforced. The reasoning ontology's epistemic and structural predicates run, and its conative layer is the gap described above, which the architecture page reads from its own side. An absence is measured before it is reported, just as a negative result is, as described in [a report, not a checkbox](/disciplined-methodology/verify#a-report-not-a-checkbox). From inside a tree, a capability that nothing calls and one that does not exist look the same."
              }
            ],
            "title": "Declared absences"
          }
        ],
        "title": "The honest gaps"
      }
    ]
  },
  "description": "One command runs every tool in this method as a single gate, in stages typed as shown in a stage array.",
  "id": "disciplined-methodology",
  "label": "Ship",
  "tab": "ship",
  "title": "Ship — Methodology — Bane's Lab",
  "url": "https://banes-lab.com/disciplined-methodology/ship"
}
