|
| 1 | +import Link from "next/link"; |
| 2 | +import type { ReactNode } from "react"; |
| 3 | +import type { Metadata } from "next"; |
| 4 | +import { SiteShell } from "@/components/site-shell"; |
| 5 | +import { mono, pre, table, td, th } from "../openontology/ui"; |
| 6 | + |
| 7 | +export const metadata: Metadata = { |
| 8 | + title: "ASDLC · LogicSRC", |
| 9 | + description: |
| 10 | + "ASDLC is the Agentic Software Development Lifecycle: nine phases for building software when agents work in parallel and CI/CD is the only gate, with four conformance levels and a ratchet rule that makes testing in production defensible.", |
| 11 | + alternates: { canonical: "/asdlc" } |
| 12 | +}; |
| 13 | + |
| 14 | +const PHASES: Array<[string, string]> = [ |
| 15 | + ["Frame", "A human states intent; the agent restates scope and names what it is leaving out. The one phase where being wrong is expensive."], |
| 16 | + ["Fan out", "Split into concerns that do not share files. One agent each, one working tree each, running concurrently."], |
| 17 | + ["Gate locally", "The project's own checks run before anything is pushed. Not a duplicate of CI: it exists to keep the shared pipeline green."], |
| 18 | + ["Merge", "Merge to trunk. A pull request parked for review that is not coming is a change nothing real has tested."], |
| 19 | + ["Release", "Cut it in the same unit of work. Where users install artifacts, a merge to trunk reaches nobody."], |
| 20 | + ["Verify live", "Confirm the deployment is serving the change. A green pipeline proves a build succeeded, not that users got it."], |
| 21 | + ["Correct", "Production is the test environment. A failure returns to fan-out in minutes, and that speed is why the loop is allowed to be the test."], |
| 22 | + ["Ratchet", "Every escape becomes a permanent automated check, confirmed to fail when the bug is reintroduced. The load-bearing phase."], |
| 23 | + ["Promote", "Announce it. Work nobody hears about did not ship in any sense the business recognises."] |
| 24 | +]; |
| 25 | + |
| 26 | +const COMPARISON: Array<[string, string, string]> = [ |
| 27 | + ["Unit of work", "A ticket, worked serially", "A concern, worked in parallel by N agents"], |
| 28 | + ["Isolation", "A branch per developer", "A worktree per agent, on one checkout"], |
| 29 | + ["Gate", "Code review by a person", "A program: typecheck, tests, CI, release guards"], |
| 30 | + ["Test environment", "Staging, then prod", "Prod, because staging lies"], |
| 31 | + ["Done means", "Merged", "Verified live and announced"], |
| 32 | + ["After an escape", "A postmortem", "A permanent automated check"], |
| 33 | + ["Cost of a release", "High, so releases are batched", "Near zero, so releases are continuous"] |
| 34 | +]; |
| 35 | + |
| 36 | +const LEVELS: Array<[string, string]> = [ |
| 37 | + ["Level 0, serial", "Agents are used one at a time, and a human reviews and merges each change. Most teams calling themselves AI-assisted are here."], |
| 38 | + ["Level 1, isolated", "Agents work in parallel in isolated trees. The local gate is defined and runs before every push."], |
| 39 | + ["Level 2, continuous", "Trunk deploys automatically. Releases are cut per unit of work, and the release process itself refuses invalid states."], |
| 40 | + ["Level 3, ratcheted", "Every recent production escape has a corresponding automated check, each confirmed to fail when its bug returns. Promotion runs as the last phase."] |
| 41 | +]; |
| 42 | + |
| 43 | +export default function AsdlcPage(): ReactNode { |
| 44 | + return ( |
| 45 | + <SiteShell active="ASDLC"> |
| 46 | + <div className="band"> |
| 47 | + <div className="section-head"> |
| 48 | + <p className="eyebrow">LogicSRC standards surface</p> |
| 49 | + <h2>ASDLC</h2> |
| 50 | + <p> |
| 51 | + The Agentic Software Development Lifecycle: how software gets built when most of the |
| 52 | + work is done by agents running in parallel, and CI/CD is the only gate that matters. |
| 53 | + </p> |
| 54 | + </div> |
| 55 | + <p style={{ color: "#41505d" }}> |
| 56 | + The traditional SDLC assumes the scarce resource is engineering time, so it spends process |
| 57 | + on deciding whether each change is worth building. When agents write the code, engineering |
| 58 | + time stops being scarce and two other things become scarce instead:{" "} |
| 59 | + <strong>human attention</strong> and <strong>trunk stability</strong>. ASDLC is what a |
| 60 | + lifecycle looks like when you optimise for those two. |
| 61 | + </p> |
| 62 | + <p style={{ color: "#5b6b7a", fontSize: "0.95rem" }}> |
| 63 | + Status: <strong>0.1</strong>. A description of a practice already in production, published |
| 64 | + so others can copy it, not a proposal. |
| 65 | + </p> |
| 66 | + </div> |
| 67 | + |
| 68 | + <div className="band"> |
| 69 | + <div className="section-head"> |
| 70 | + <h2>What actually changes</h2> |
| 71 | + <p>The last row carries the most weight. Everything else follows from it.</p> |
| 72 | + </div> |
| 73 | + <table style={table}> |
| 74 | + <thead> |
| 75 | + <tr> |
| 76 | + <th style={th} /> |
| 77 | + <th style={th}>SDLC</th> |
| 78 | + <th style={th}>ASDLC</th> |
| 79 | + </tr> |
| 80 | + </thead> |
| 81 | + <tbody> |
| 82 | + {COMPARISON.map(([label, before, after]) => ( |
| 83 | + <tr key={label}> |
| 84 | + <td style={{ ...td, color: "#5b6b7a", fontWeight: 600, whiteSpace: "nowrap" }}> |
| 85 | + {label} |
| 86 | + </td> |
| 87 | + <td style={{ ...td, color: "#7a8794" }}>{before}</td> |
| 88 | + <td style={{ ...td, color: "#1d2a35" }}>{after}</td> |
| 89 | + </tr> |
| 90 | + ))} |
| 91 | + </tbody> |
| 92 | + </table> |
| 93 | + <p style={{ color: "#41505d", marginTop: "1rem" }}> |
| 94 | + Make a release cheap enough that shipping four times in a day is unremarkable, and the |
| 95 | + rest of the table follows. |
| 96 | + </p> |
| 97 | + </div> |
| 98 | + |
| 99 | + <div className="band"> |
| 100 | + <div className="section-head"> |
| 101 | + <h2>The nine phases</h2> |
| 102 | + <p>Correct returns to fan-out. The loop is the point.</p> |
| 103 | + </div> |
| 104 | + <pre style={pre}>{`Frame → Fan out → Gate locally → Merge → Release → Verify live → Correct → Ratchet → Promote |
| 105 | + ↑ ↓ |
| 106 | + └────────────────────────────────────────────────────────┘`}</pre> |
| 107 | + <ol style={{ color: "#41505d", lineHeight: 1.75, paddingLeft: "1.2rem", marginTop: "1rem" }}> |
| 108 | + {PHASES.map(([name, detail]) => ( |
| 109 | + <li key={name} style={{ marginBottom: "0.55rem" }}> |
| 110 | + <strong>{name}.</strong> {detail} |
| 111 | + </li> |
| 112 | + ))} |
| 113 | + </ol> |
| 114 | + </div> |
| 115 | + |
| 116 | + <div className="band"> |
| 117 | + <div className="section-head"> |
| 118 | + <h2>The ratchet rule</h2> |
| 119 | + <p>What separates this from shipping carelessly and calling it a methodology.</p> |
| 120 | + </div> |
| 121 | + <p style={{ color: "#41505d" }}> |
| 122 | + Testing in production is only defensible if production failures are one-time events. So |
| 123 | + every escape becomes a permanent automated check before the incident is closed. Not a note |
| 124 | + in a document: a program that fails, in CI or in the local gate, when the bug comes back. |
| 125 | + </p> |
| 126 | + <p style={{ color: "#41505d" }}> |
| 127 | + And the check itself has to be checked. The test for a ratchet is whether it actually fails |
| 128 | + when you reintroduce the bug, which must be confirmed rather than assumed. A fix without a |
| 129 | + ratchet is how the same class of bug ships three times. |
| 130 | + </p> |
| 131 | + </div> |
| 132 | + |
| 133 | + <div className="band"> |
| 134 | + <div className="section-head"> |
| 135 | + <h2>Invariants</h2> |
| 136 | + <p>Skip these and you do not have ASDLC, you have moving fast.</p> |
| 137 | + </div> |
| 138 | + <ul style={{ color: "#41505d", lineHeight: 1.8, paddingLeft: "1.1rem" }}> |
| 139 | + <li> |
| 140 | + <strong>Isolation before parallelism.</strong> N agents on one checkout corrupt each |
| 141 | + other. N agents on N worktrees do not. |
| 142 | + </li> |
| 143 | + <li> |
| 144 | + <strong>The gate is a program, not a person.</strong> A rule nobody wrote down as a |
| 145 | + check is not enforced at agent throughput. |
| 146 | + </li> |
| 147 | + <li> |
| 148 | + <strong>The tooling refuses rather than warns.</strong> A release script that warns about |
| 149 | + a dirty tree gets ignored. One that exits non-zero cannot be. |
| 150 | + </li> |
| 151 | + <li> |
| 152 | + <strong>Prod is the only honest environment.</strong> Reproduce the real conditions or |
| 153 | + accept that the test proves nothing. |
| 154 | + </li> |
| 155 | + <li> |
| 156 | + <strong>Verified live, not merged, is done.</strong> |
| 157 | + </li> |
| 158 | + <li> |
| 159 | + <strong>Every escape ratchets.</strong> |
| 160 | + </li> |
| 161 | + </ul> |
| 162 | + </div> |
| 163 | + |
| 164 | + <div className="band"> |
| 165 | + <div className="section-head"> |
| 166 | + <h2>Conformance levels</h2> |
| 167 | + <p>Each level includes the ones below it.</p> |
| 168 | + </div> |
| 169 | + <ul style={{ color: "#41505d", lineHeight: 1.8, paddingLeft: "1.1rem" }}> |
| 170 | + {LEVELS.map(([name, detail]) => ( |
| 171 | + <li key={name} style={{ marginBottom: "0.5rem" }}> |
| 172 | + <strong>{name}.</strong> {detail} |
| 173 | + </li> |
| 174 | + ))} |
| 175 | + </ul> |
| 176 | + <p style={{ color: "#5b6b7a", marginTop: "0.9rem" }}> |
| 177 | + Level 3 is the claim that matters, and the only one that requires evidence rather than |
| 178 | + intent. |
| 179 | + </p> |
| 180 | + </div> |
| 181 | + |
| 182 | + <div className="band"> |
| 183 | + <div className="section-head"> |
| 184 | + <h2>Worked example</h2> |
| 185 | + <p>DiskPush, an rsync desktop and CLI, on one working day. All of it public in the repo.</p> |
| 186 | + </div> |
| 187 | + <p style={{ color: "#41505d" }}> |
| 188 | + <strong>Horizontal scale.</strong> Eight agent worktrees open on one checkout at once, |
| 189 | + covering unrelated concerns: SSH auth discovery, symlink handling, fleet runs across |
| 190 | + servers, the desktop content security policy, file operations, connection defaults and |
| 191 | + file list sorting. None waited on another. |
| 192 | + </p> |
| 193 | + <p style={{ color: "#41505d" }}> |
| 194 | + <strong>Cadence.</strong> Four releases reached users between 08:53 and 14:56 UTC:{" "} |
| 195 | + <code style={mono}>v0.2.17</code>, <code style={mono}>v0.3.0</code>,{" "} |
| 196 | + <code style={mono}>v0.4.0</code> and <code style={mono}>v0.5.0</code>, each carrying one |
| 197 | + merged concern and shipping desktop and CLI artifacts. |
| 198 | + </p> |
| 199 | + <p style={{ color: "#41505d" }}> |
| 200 | + <strong>The gate refusing.</strong> The release script checks every precondition before it |
| 201 | + writes anything: a dirty tree, a branch that is not trunk, a tag that exists, a version |
| 202 | + that does not sort above the newest release, and any workspace package missing from its |
| 203 | + manifest list. That last guard exists because a package was added and silently left behind |
| 204 | + at an old version, release after release, with nothing failing. |
| 205 | + </p> |
| 206 | + <p style={{ color: "#41505d" }}> |
| 207 | + <strong>Test in prod, then ratchet.</strong> The desktop shipped a visibly broken window |
| 208 | + across three releases, and each layer was only visible in production. v0.2.0 rendered |
| 209 | + unstyled: the bundle loaded over <code style={mono}>file://</code> and every root-absolute |
| 210 | + asset resolved against the filesystem root and 404ed. v0.2.1 fixed the assets and rendered |
| 211 | + blank instead, because the export carries its payload in inline scripts and the window sent{" "} |
| 212 | + <code style={mono}>script-src 'self'</code>, refusing all seven. That was |
| 213 | + invisible before only because nothing had run at all. v0.2.2 hashed the inline scripts into |
| 214 | + the policy. |
| 215 | + </p> |
| 216 | + <p style={{ color: "#41505d" }}> |
| 217 | + No local harness could have caught the first bug: a static server resolves absolute paths |
| 218 | + correctly by construction, so the bug only exists under <code style={mono}>file://</code>. |
| 219 | + The ratchet is one command that now guards all three layers, and each guard was confirmed |
| 220 | + to fail when its bug is reintroduced. |
| 221 | + </p> |
| 222 | + </div> |
| 223 | + |
| 224 | + <div className="band"> |
| 225 | + <div className="section-head"> |
| 226 | + <h2>Where everything lives</h2> |
| 227 | + </div> |
| 228 | + <ul style={{ color: "#41505d", lineHeight: 1.9, paddingLeft: "1.1rem" }}> |
| 229 | + <li> |
| 230 | + <Link href="/docs/asdlc">Specification</Link>, with the phases, invariants, conformance |
| 231 | + levels, worked example and an adoption order |
| 232 | + </li> |
| 233 | + <li> |
| 234 | + <Link href="/openprd">OpenPRD</Link>, for the product decision that precedes a fan-out |
| 235 | + </li> |
| 236 | + <li> |
| 237 | + <Link href="/openontology">OpenOntology</Link>, for durable domain knowledge shared |
| 238 | + across agents |
| 239 | + </li> |
| 240 | + </ul> |
| 241 | + </div> |
| 242 | + </SiteShell> |
| 243 | + ); |
| 244 | +} |
0 commit comments