@tiphys/kernel 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +611 -0
- package/LICENSE +202 -0
- package/assurance-modes.yaml +278 -0
- package/checklists/clean-room.yaml +325 -0
- package/checklists/env-failure-diagnosis.yaml +68 -0
- package/checklists/flake-playbook.yaml +68 -0
- package/checklists/hazard-review.yaml +144 -0
- package/checklists/plan-review.yaml +103 -0
- package/dist/bin/tiphys.d.ts +2 -0
- package/dist/bin/tiphys.js +14 -0
- package/dist/src/brief.d.ts +32 -0
- package/dist/src/brief.js +59 -0
- package/dist/src/checklists.d.ts +169 -0
- package/dist/src/checklists.js +310 -0
- package/dist/src/checks.d.ts +828 -0
- package/dist/src/checks.js +3314 -0
- package/dist/src/cli.d.ts +9 -0
- package/dist/src/cli.js +57 -0
- package/dist/src/commands/brief.d.ts +92 -0
- package/dist/src/commands/brief.js +320 -0
- package/dist/src/commands/checklist.d.ts +42 -0
- package/dist/src/commands/checklist.js +168 -0
- package/dist/src/commands/doctor.d.ts +35 -0
- package/dist/src/commands/doctor.js +605 -0
- package/dist/src/commands/gates.d.ts +9 -0
- package/dist/src/commands/gates.js +360 -0
- package/dist/src/commands/init.d.ts +30 -0
- package/dist/src/commands/init.js +128 -0
- package/dist/src/commands/lock.d.ts +1 -0
- package/dist/src/commands/lock.js +229 -0
- package/dist/src/commands/mode.d.ts +40 -0
- package/dist/src/commands/mode.js +134 -0
- package/dist/src/commands/plan.d.ts +20 -0
- package/dist/src/commands/plan.js +105 -0
- package/dist/src/commands/pool.d.ts +1 -0
- package/dist/src/commands/pool.js +128 -0
- package/dist/src/commands/spawn.d.ts +1 -0
- package/dist/src/commands/spawn.js +146 -0
- package/dist/src/commands/status.d.ts +19 -0
- package/dist/src/commands/status.js +120 -0
- package/dist/src/commands/teardown.d.ts +1 -0
- package/dist/src/commands/teardown.js +79 -0
- package/dist/src/commands/tuition.d.ts +21 -0
- package/dist/src/commands/tuition.js +218 -0
- package/dist/src/commands/validate.d.ts +78 -0
- package/dist/src/commands/validate.js +360 -0
- package/dist/src/commands/watch.d.ts +1 -0
- package/dist/src/commands/watch.js +172 -0
- package/dist/src/exec/env.d.ts +128 -0
- package/dist/src/exec/env.js +190 -0
- package/dist/src/fleet.d.ts +51 -0
- package/dist/src/fleet.js +80 -0
- package/dist/src/gates/adapters/http-json.d.ts +5 -0
- package/dist/src/gates/adapters/http-json.js +283 -0
- package/dist/src/gates/adapters/migrations-command.d.ts +1 -0
- package/dist/src/gates/adapters/migrations-command.js +373 -0
- package/dist/src/gates/citations.d.ts +408 -0
- package/dist/src/gates/citations.js +1163 -0
- package/dist/src/gates/coverage.d.ts +284 -0
- package/dist/src/gates/coverage.js +701 -0
- package/dist/src/gates/credentials.d.ts +74 -0
- package/dist/src/gates/credentials.js +533 -0
- package/dist/src/gates/deploy.d.ts +1 -0
- package/dist/src/gates/deploy.js +33 -0
- package/dist/src/gates/manifest.d.ts +99 -0
- package/dist/src/gates/manifest.js +208 -0
- package/dist/src/gates/migrations.d.ts +1 -0
- package/dist/src/gates/migrations.js +36 -0
- package/dist/src/gates/pin.d.ts +114 -0
- package/dist/src/gates/pin.js +154 -0
- package/dist/src/gates/red-witness.d.ts +22 -0
- package/dist/src/gates/red-witness.js +390 -0
- package/dist/src/gates/release.d.ts +283 -0
- package/dist/src/gates/release.js +820 -0
- package/dist/src/gates/result.d.ts +116 -0
- package/dist/src/gates/result.js +91 -0
- package/dist/src/gates/run.d.ts +566 -0
- package/dist/src/gates/run.js +1536 -0
- package/dist/src/gates/schemas/citation-config.schema.json +59 -0
- package/dist/src/gates/schemas/coverage-config.schema.json +77 -0
- package/dist/src/gates/schemas/gate-manifest.schema.json +125 -0
- package/dist/src/gates/schemas/gate-result.schema.json +160 -0
- package/dist/src/gates/schemas/phase-declaration.schema.json +42 -0
- package/dist/src/gates/schemas/release-record.schema.json +119 -0
- package/dist/src/gates/schemas/verifier-config.schema.json +101 -0
- package/dist/src/gates/schemas/witness-spec.schema.json +110 -0
- package/dist/src/gates/scope.d.ts +131 -0
- package/dist/src/gates/scope.js +1018 -0
- package/dist/src/gates/suite.d.ts +217 -0
- package/dist/src/gates/suite.js +927 -0
- package/dist/src/gates/validate.d.ts +121 -0
- package/dist/src/gates/validate.js +414 -0
- package/dist/src/hooks.d.ts +32 -0
- package/dist/src/hooks.js +62 -0
- package/dist/src/liveness.d.ts +321 -0
- package/dist/src/liveness.js +396 -0
- package/dist/src/lock.d.ts +178 -0
- package/dist/src/lock.js +500 -0
- package/dist/src/modes.d.ts +149 -0
- package/dist/src/modes.js +258 -0
- package/dist/src/path-identity.d.ts +2 -0
- package/dist/src/path-identity.js +10 -0
- package/dist/src/plan.d.ts +73 -0
- package/dist/src/plan.js +153 -0
- package/dist/src/pool.d.ts +130 -0
- package/dist/src/pool.js +721 -0
- package/dist/src/roles.d.ts +430 -0
- package/dist/src/roles.js +734 -0
- package/dist/src/spawn.d.ts +177 -0
- package/dist/src/spawn.js +332 -0
- package/dist/src/status.d.ts +91 -0
- package/dist/src/status.js +119 -0
- package/dist/src/task.d.ts +264 -0
- package/dist/src/task.js +305 -0
- package/dist/src/teardown.d.ts +32 -0
- package/dist/src/teardown.js +314 -0
- package/dist/src/tuition.d.ts +159 -0
- package/dist/src/tuition.js +311 -0
- package/dist/src/validate.d.ts +230 -0
- package/dist/src/validate.js +732 -0
- package/dist/src/version.d.ts +3 -0
- package/dist/src/version.js +38 -0
- package/dist/src/watcher.d.ts +275 -0
- package/dist/src/watcher.js +859 -0
- package/dist/src/witness/run.d.ts +274 -0
- package/dist/src/witness/run.js +1327 -0
- package/dist/src/witness/spec.d.ts +102 -0
- package/dist/src/witness/spec.js +253 -0
- package/dist/tsconfig.src.tsbuildinfo +1 -0
- package/gate-registry.yaml +390 -0
- package/gates.manifest.json +195 -0
- package/package.json +57 -3
- package/role-model-config.yaml +88 -0
- package/roles/README.md +128 -0
- package/roles/_shared-dispatch-contract.md +87 -0
- package/roles/adversarial-plan-reviewer.md +80 -0
- package/roles/clean-room-reviewer.md +140 -0
- package/roles/implementer.md +460 -0
- package/roles/investigator.md +138 -0
- package/roles/plan-writer.md +95 -0
- package/schemas/README.md +81 -0
- package/schemas/assurance-modes.schema.json +264 -0
- package/schemas/charter.schema.json +166 -0
- package/schemas/checklist.schema.json +114 -0
- package/schemas/decision-record.schema.json +88 -0
- package/schemas/final-report.schema.json +90 -0
- package/schemas/finding.schema.json +106 -0
- package/schemas/gate-registry.schema.json +260 -0
- package/schemas/mechanism-index.schema.json +94 -0
- package/schemas/plan.schema.json +300 -0
- package/schemas/report.schema.json +579 -0
- package/schemas/role-brief.schema.json +105 -0
- package/schemas/role-model-config.schema.json +90 -0
- package/schemas/status-line.schema.json +40 -0
- package/schemas/tuition.schema.json +191 -0
- package/schemas/verdict.schema.json +289 -0
- package/schemas/work-history.schema.json +183 -0
- package/templates/charter.example.yaml +54 -0
- package/templates/decision-record.example.yaml +27 -0
- package/templates/final-report.example.yaml +80 -0
- package/templates/plan.example.yaml +87 -0
- package/templates/report.example.yaml +236 -0
- package/templates/warnings.md +74 -0
- package/templates/work-history.example.yaml +185 -0
- package/tuition/README.md +76 -0
- package/tuition/T-001.yaml +48 -0
- package/tuition/T-002.yaml +51 -0
- package/tuition/T-003.yaml +100 -0
- package/tuition/T-004.yaml +52 -0
- package/tuition/T-005.yaml +72 -0
- package/tuition/T-006.yaml +81 -0
- package/tuition/T-007.yaml +56 -0
- package/tuition/T-008.yaml +111 -0
- package/tuition/T-009.yaml +50 -0
- package/tuition/T-015.yaml +36 -0
- package/tuition/T-016.yaml +36 -0
- package/tuition/T-017.yaml +46 -0
- package/tuition/T-018.yaml +84 -0
- package/tuition/T-021.yaml +40 -0
- package/tuition/T-022.yaml +36 -0
- package/tuition/mechanism-index.yaml +256 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* READING THE SHIPPED ASSURANCE MODE DEFINITIONS (kernel plan M3, M3-P3
|
|
3
|
+
* step 5).
|
|
4
|
+
*
|
|
5
|
+
* `assurance-modes.yaml` ships at the package root, beside `gate-registry.yaml`
|
|
6
|
+
* and the `schemas/` directory. This module locates it, decodes it, and
|
|
7
|
+
* answers one question: what does a declared mode require. Nothing here
|
|
8
|
+
* RESOLVES a mode into behaviour and nothing here enforces one. M3 never
|
|
9
|
+
* executes `direct-pr` or `local-only`, and building an enforcement engine for
|
|
10
|
+
* a mode this milestone never enters is the M1-P3 failure the plan is trying
|
|
11
|
+
* not to repeat.
|
|
12
|
+
*
|
|
13
|
+
* NO VALIDATION HAPPENS HERE, deliberately. `tiphys validate --type
|
|
14
|
+
* assurance-modes` is the command that says whether the document is
|
|
15
|
+
* well-formed, and duplicating its rules in a reader would produce a second
|
|
16
|
+
* opinion to keep in sync. This module reads what is there and reports what it
|
|
17
|
+
* cannot find.
|
|
18
|
+
*/
|
|
19
|
+
import { readdirSync } from "node:fs";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
import { decodeDocument, readOperatorPath } from "./validate.js";
|
|
23
|
+
/** The shipped document's basename, at the package root. */
|
|
24
|
+
export const MODES_FILENAME = "assurance-modes.yaml";
|
|
25
|
+
/**
|
|
26
|
+
* Locate the package root by walking UP from this module and testing for the
|
|
27
|
+
* shipped document.
|
|
28
|
+
*
|
|
29
|
+
* The depth differs between the two layouts this code runs in: from source it
|
|
30
|
+
* is `src/` and the root is one level up, and from the built entry it is
|
|
31
|
+
* `dist/src/` and the root is two levels up. Counting `..` would be right in
|
|
32
|
+
* exactly one of them, which is the layout-dependent break
|
|
33
|
+
* `schemasDirectory()` already documents. Walking up and TESTING is right in
|
|
34
|
+
* both, and in a relocated copy as well.
|
|
35
|
+
*/
|
|
36
|
+
export function packageRoot() {
|
|
37
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
39
|
+
try {
|
|
40
|
+
if (readdirSync(directory).includes(MODES_FILENAME)) {
|
|
41
|
+
return directory;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* not readable here; keep walking */
|
|
46
|
+
}
|
|
47
|
+
const parent = dirname(directory);
|
|
48
|
+
if (parent === directory) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
directory = parent;
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`the shipped ${MODES_FILENAME} was not found above this module; the installation is incomplete`);
|
|
54
|
+
}
|
|
55
|
+
function asRecord(value) {
|
|
56
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
57
|
+
? value
|
|
58
|
+
: undefined;
|
|
59
|
+
}
|
|
60
|
+
function strings(record, key) {
|
|
61
|
+
const value = record[key];
|
|
62
|
+
return Array.isArray(value)
|
|
63
|
+
? value.filter((entry) => typeof entry === "string")
|
|
64
|
+
: [];
|
|
65
|
+
}
|
|
66
|
+
function optionalStrings(record, key) {
|
|
67
|
+
return record[key] === undefined ? undefined : strings(record, key);
|
|
68
|
+
}
|
|
69
|
+
/** Read and decode the shipped document, or say why it could not be read. */
|
|
70
|
+
export function readModes(path = join(packageRoot(), MODES_FILENAME)) {
|
|
71
|
+
const read = readOperatorPath(path);
|
|
72
|
+
if (!read.ok) {
|
|
73
|
+
return { ok: false, reason: read.reason };
|
|
74
|
+
}
|
|
75
|
+
const decoded = decodeDocument(read.body, path);
|
|
76
|
+
if (!decoded.ok) {
|
|
77
|
+
return { ok: false, reason: decoded.reason };
|
|
78
|
+
}
|
|
79
|
+
const document = asRecord(decoded.value);
|
|
80
|
+
if (document === undefined) {
|
|
81
|
+
return { ok: false, reason: `${path} does not decode to a mapping` };
|
|
82
|
+
}
|
|
83
|
+
const raw = document["modes"];
|
|
84
|
+
if (!Array.isArray(raw)) {
|
|
85
|
+
return { ok: false, reason: `${path} declares no modes list` };
|
|
86
|
+
}
|
|
87
|
+
const modes = [];
|
|
88
|
+
for (const entry of raw) {
|
|
89
|
+
const record = asRecord(entry);
|
|
90
|
+
if (record === undefined) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
modes.push({
|
|
94
|
+
id: String(record["id"] ?? ""),
|
|
95
|
+
declaredBy: String(record["declared-by"] ?? ""),
|
|
96
|
+
pipeline: strings(record, "pipeline"),
|
|
97
|
+
skips: strings(record, "skips"),
|
|
98
|
+
gateSets: strings(record, "gate-sets"),
|
|
99
|
+
mergeAuthority: String(record["merge-authority"] ?? ""),
|
|
100
|
+
grantedBy: typeof record["granted-by"] === "string" ? record["granted-by"] : undefined,
|
|
101
|
+
conditions: optionalStrings(record, "conditions"),
|
|
102
|
+
reviewContracts: optionalStrings(record, "review-contracts"),
|
|
103
|
+
escalationBounds: asRecord(record["escalation-bounds"]),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return { ok: true, path, raw: decoded.value, modes };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The id blueprint section 8 gives the un-downgraded process BY NAME: "The
|
|
110
|
+
* current proven process is the definition of `full`."
|
|
111
|
+
*
|
|
112
|
+
* SIBLING LITERAL, named so neither site can drift unnoticed:
|
|
113
|
+
* `REFERENCE_MODE_ID` at src/checks.ts:275 is the same string, used as the
|
|
114
|
+
* reference pipeline every other mode's omissions are measured against. The two
|
|
115
|
+
* are pinned from opposite directions by registered tests: the checks-side
|
|
116
|
+
* literal by the assertion that deleting `full` produces `no mode declares id
|
|
117
|
+
* full` (test/assurance-modes.test.ts:314), and this one by the assertion that
|
|
118
|
+
* the mode `mode show` annotates as un-downgraded is the mode named `full`.
|
|
119
|
+
*/
|
|
120
|
+
const UNDOWNGRADED_MODE_ID = "full";
|
|
121
|
+
/**
|
|
122
|
+
* The execution status of one mode, DERIVED rather than looked up in a list of
|
|
123
|
+
* ids (CR-004 item 2, DR-0020).
|
|
124
|
+
*
|
|
125
|
+
* Two facts are available and both are checkable by the reader: whether this is
|
|
126
|
+
* the kernel's own document, and whether this mode IS the one blueprint section
|
|
127
|
+
* 8 names as the un-downgraded process, "The current proven process is the
|
|
128
|
+
* definition of `full`. Downgrades are declared, never improvised."
|
|
129
|
+
*
|
|
130
|
+
* WHY THE NAME AND NOT THE SKIP COUNT (CR-002, round 9). This function used
|
|
131
|
+
* `mode.skips.length === 0` as its proxy for "this is the un-downgraded mode".
|
|
132
|
+
* The proxy held only because `full` happened to be the only mode with an empty
|
|
133
|
+
* list, nothing enforced that, and `skips[]` is shipped DATA. Two measured
|
|
134
|
+
* consequences, both at exit 0 with every registry gate green: giving `full` one
|
|
135
|
+
* bogus `skips[]` entry made this function say that no phase of the tiphys
|
|
136
|
+
* project had ever been delivered under `full`, which is false about the mode
|
|
137
|
+
* this project has delivered every phase under; and giving `direct-pr` an empty
|
|
138
|
+
* `skips[]` made a mode nobody has ever entered claim to be the one the project
|
|
139
|
+
* follows, with `merge-authority: owner` printed beneath it, which is not the
|
|
140
|
+
* regime in force (DR-0015). Blueprint section 8 defines `full` BY NAME, so the
|
|
141
|
+
* name is the primary fact and the skip count is a consequence of it.
|
|
142
|
+
*
|
|
143
|
+
* THE COUNT IS STILL REPORTED, as a fact about the mode rather than as the
|
|
144
|
+
* ground of the claim. That distinction is the whole finding: a number may be
|
|
145
|
+
* shown without being believed.
|
|
146
|
+
*
|
|
147
|
+
* WHAT MAKES THE `full` SENTENCE TRUE IS DATA, AND IT IS GUARDED IN TWO PLACES
|
|
148
|
+
* THAT COVER DIFFERENT DOCUMENTS. Keying off the name moves the burden: the
|
|
149
|
+
* claim is only as good as the `full` in the document being served genuinely
|
|
150
|
+
* being un-downgraded.
|
|
151
|
+
*
|
|
152
|
+
* The VALIDATOR carries it for EVERY document (round 10, CRB9-02).
|
|
153
|
+
* `mode-no-undeclared-downgrade` rejects a `skips[]` entry that the same mode's
|
|
154
|
+
* pipeline runs, and also one that the reference does not run; on the reference
|
|
155
|
+
* those two exhaust the cases, so a `full` declaring any skipped stage at all is
|
|
156
|
+
* refused and this sentence is never printed over it. Until that second part
|
|
157
|
+
* existed, a `full` whose stage had MOVED from `pipeline` into `skips` was an
|
|
158
|
+
* HONEST downgrade that validated at exit 0, and `tiphys mode show --mode full`
|
|
159
|
+
* printed this sentence about fifteen lines above a `skips: deploy-verify` row.
|
|
160
|
+
*
|
|
161
|
+
* A REGISTERED TEST carries it for THIS repository's own document, asserting the
|
|
162
|
+
* shipped `full` declares no skipped stage. It is kept rather than superseded:
|
|
163
|
+
* a test guards the document, the check guards every other document, and the
|
|
164
|
+
* failure this pair exists to prevent is a property asserted where it is stated
|
|
165
|
+
* and not enforced where it is consumed.
|
|
166
|
+
*
|
|
167
|
+
* WHAT THIS DELIBERATELY DOES NOT SAY. It does not say that tiphys runs
|
|
168
|
+
* anything: nothing runs on tiphys before M4. The un-downgraded mode of the
|
|
169
|
+
* kernel's own document is the process the tiphys PROJECT follows for its own
|
|
170
|
+
* delivery; the downgraded ones have never been entered at all. And for a
|
|
171
|
+
* document that is not the kernel's own, the answer is that tiphys does not
|
|
172
|
+
* know, because it does not.
|
|
173
|
+
*/
|
|
174
|
+
export function executionStatus(mode, context) {
|
|
175
|
+
if (!context.shippedDocument) {
|
|
176
|
+
return ("not determinable here. This is not the kernel's own assurance-modes.yaml, " +
|
|
177
|
+
"so nothing tiphys ships records whether any phase has been delivered under " +
|
|
178
|
+
"this mode (DR-0020).");
|
|
179
|
+
}
|
|
180
|
+
if (mode.id === UNDOWNGRADED_MODE_ID) {
|
|
181
|
+
return (`this mode is ${UNDOWNGRADED_MODE_ID}, which blueprint section 8 defines by name as the ` +
|
|
182
|
+
"un-downgraded process, and it is the one the tiphys project follows for its own delivery.");
|
|
183
|
+
}
|
|
184
|
+
return (`DECLARED AND VALIDATED, NEVER EXERCISED. This mode is not ${UNDOWNGRADED_MODE_ID}, which ` +
|
|
185
|
+
"blueprint section 8 defines by name as the un-downgraded process, so no phase of the " +
|
|
186
|
+
`tiphys project has ever been delivered under it. It declares ${String(mode.skips.length)} ` +
|
|
187
|
+
"skipped stage(s). Its pipeline and its gate selection are checked by validation only " +
|
|
188
|
+
"(DR-0020).");
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The standing limits of this release, printed on every invocation.
|
|
192
|
+
*
|
|
193
|
+
* IT SAYS ONLY WHAT THE SHIPPED SCHEMAS DO. The vocabularies really are closed
|
|
194
|
+
* enums, so "a document naming any other id is rejected" is the enum's own
|
|
195
|
+
* behaviour and not a claim about intent. The M4 sentence is attributed to
|
|
196
|
+
* DR-0020 rather than stated as a property of the code.
|
|
197
|
+
*/
|
|
198
|
+
export const RELEASE_LIMITS = "limits: the mode, stage and role vocabularies in the shipped schemas are this " +
|
|
199
|
+
"repository's own closed enums, so a document naming any other id is rejected and a " +
|
|
200
|
+
"consuming project cannot extend them at v0.1.0; whether to open them is an M4 question " +
|
|
201
|
+
"(DR-0020). This command SHOWS a declared mode: nothing in this release resolves a " +
|
|
202
|
+
"project into a mode, enforces one, or runs one.";
|
|
203
|
+
/**
|
|
204
|
+
* Render one mode for a human or for a brief.
|
|
205
|
+
*
|
|
206
|
+
* THE SHAPE IS PART OF THE CONTRACT, because criterion 2 asserts over it: a
|
|
207
|
+
* section is a line ending in a colon at column zero, and its items are the
|
|
208
|
+
* lines indented by exactly two spaces beneath it. That makes "prints exactly
|
|
209
|
+
* the twelve stage ids in order" something a test can extract rather than
|
|
210
|
+
* something a reader has to eyeball.
|
|
211
|
+
*/
|
|
212
|
+
export function renderMode(mode, context) {
|
|
213
|
+
const lines = [`mode: ${mode.id}`];
|
|
214
|
+
/* SECOND LINE, not a footnote. CR-004 measured that `mode show` printed a
|
|
215
|
+
never-exercised mode with exactly the confidence of the exercised one, and
|
|
216
|
+
that the only disclosure lived in `delivery/`, which the package excludes. */
|
|
217
|
+
lines.push(`execution-status: ${executionStatus(mode, context)}`);
|
|
218
|
+
lines.push(`merge-authority: ${mode.mergeAuthority}`);
|
|
219
|
+
if (mode.grantedBy !== undefined) {
|
|
220
|
+
lines.push(`granted-by: ${mode.grantedBy}`);
|
|
221
|
+
}
|
|
222
|
+
const section = (name, items) => {
|
|
223
|
+
if (items === undefined) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
lines.push(`${name}:`);
|
|
227
|
+
if (items.length === 0) {
|
|
228
|
+
lines.push(" (none)");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
for (const item of items) {
|
|
232
|
+
lines.push(` ${item}`);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
section("pipeline", mode.pipeline);
|
|
236
|
+
section("skips", mode.skips);
|
|
237
|
+
section("gate-sets", mode.gateSets);
|
|
238
|
+
section("review-contracts", mode.reviewContracts);
|
|
239
|
+
if (mode.escalationBounds !== undefined) {
|
|
240
|
+
/* CR-004 item 3. The bounds are DATA an orchestrator brief cites. Nothing
|
|
241
|
+
in this release counts a fix round or detects a recurrence, so a bare
|
|
242
|
+
`escalation-bounds:` header invites the reader to assume an enforcement
|
|
243
|
+
engine that does not exist. */
|
|
244
|
+
lines.push("escalation-bounds (data an orchestrator brief cites; nothing in this release counts fix rounds, detects recurrence, or enforces these):");
|
|
245
|
+
for (const key of Object.keys(mode.escalationBounds).sort()) {
|
|
246
|
+
lines.push(` ${key}: ${String(mode.escalationBounds[key])}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (mode.conditions !== undefined) {
|
|
250
|
+
lines.push("conditions:");
|
|
251
|
+
for (const condition of mode.conditions) {
|
|
252
|
+
lines.push(` ${condition.replace(/\s+/g, " ").trim()}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
lines.push(`declared-by: ${mode.declaredBy.replace(/\s+/g, " ").trim()}`);
|
|
256
|
+
lines.push(RELEASE_LIMITS);
|
|
257
|
+
return lines;
|
|
258
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
/** Compare two existing paths by their canonical filesystem identity. */
|
|
3
|
+
export function pathsIdentifySameObject(left, right) {
|
|
4
|
+
try {
|
|
5
|
+
return realpathSync(left) === realpathSync(right);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE PHASE-DECLARATION PROJECTION (kernel plan M3, M3-P1 step 2; D-M3-18).
|
|
3
|
+
*
|
|
4
|
+
* The M2-P4 scope auditor reads one document per phase from
|
|
5
|
+
* `delivery/plan/phase-declarations/<phase-id-lowercased>.json`, out of the
|
|
6
|
+
* MERGE BASE of the audited branch. Until now that document was a SECOND
|
|
7
|
+
* HAND-AUTHORED SOURCE beside the plan, and two hand-authored sources of the
|
|
8
|
+
* same fact drift. This module makes it a GENERATED VIEW of the plan, so the
|
|
9
|
+
* plan is the one source and the auditor's input is derived from it.
|
|
10
|
+
*
|
|
11
|
+
* WHAT THE PROJECTION EMITS, taken from the DELIVERED
|
|
12
|
+
* `src/gates/schemas/phase-declaration.schema.json` rather than from a
|
|
13
|
+
* description of it. The delivered schema sets `additionalProperties: false`,
|
|
14
|
+
* so an extra property is a REJECTION and not an ignored field, and the
|
|
15
|
+
* emitted key set is exactly these five, camelCase:
|
|
16
|
+
*
|
|
17
|
+
* id, branch, filesToTouch, declaredExtras, citations
|
|
18
|
+
*
|
|
19
|
+
* TWO VOCABULARIES MEET HERE AND NOWHERE ELSE. The plan spells the same
|
|
20
|
+
* fields `files-to-touch` and `extras`, because the plan is a document
|
|
21
|
+
* authored for people. This projector is the single translation point, and
|
|
22
|
+
* that is D-M3-18's whole value.
|
|
23
|
+
*
|
|
24
|
+
* THE GLOSS IS STRIPPED HERE OR NOWHERE (M2R-016). The auditor matches a
|
|
25
|
+
* declared path as a literal string or as a directory prefix and interprets
|
|
26
|
+
* nothing, so a plan entry written as
|
|
27
|
+
*
|
|
28
|
+
* `src/cli.ts` (edit only if step 4 requires it)
|
|
29
|
+
*
|
|
30
|
+
* must project to `src/cli.ts`. A projector that passed the glossed form
|
|
31
|
+
* through would emit a declaration under which every real change is
|
|
32
|
+
* undeclared, and the auditor would be right to reject it.
|
|
33
|
+
*/
|
|
34
|
+
/** The five properties the delivered phase-declaration schema requires. */
|
|
35
|
+
export interface PhaseDeclaration {
|
|
36
|
+
id: string;
|
|
37
|
+
branch: string;
|
|
38
|
+
filesToTouch: string[];
|
|
39
|
+
declaredExtras: string[];
|
|
40
|
+
citations: string[];
|
|
41
|
+
}
|
|
42
|
+
export declare function stripGloss(entry: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Is this a literal path the scope auditor can match?
|
|
45
|
+
*
|
|
46
|
+
* The auditor compares strings exactly or as a directory prefix and
|
|
47
|
+
* interprets nothing, so an entry still carrying whitespace after the gloss
|
|
48
|
+
* strip is prose. The projector REFUSES rather than emitting it: an emitted
|
|
49
|
+
* prose entry is a declaration nobody can satisfy, and the failure would
|
|
50
|
+
* surface as an unrelated scope-gate red on some later branch.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isLiteralPath(entry: string): boolean;
|
|
53
|
+
export interface ProjectionResult {
|
|
54
|
+
ok: true;
|
|
55
|
+
declaration: PhaseDeclaration;
|
|
56
|
+
/** `m3-p1.json`, the filename the auditor looks for. */
|
|
57
|
+
filename: string;
|
|
58
|
+
}
|
|
59
|
+
export interface ProjectionFailure {
|
|
60
|
+
ok: false;
|
|
61
|
+
reason: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Project one phase of a decoded plan into its declaration.
|
|
65
|
+
*
|
|
66
|
+
* The filename is the phase id LOWERCASED, because CI derives `--phase` from
|
|
67
|
+
* the branch with a lowercase regex and the auditor looks the declaration up
|
|
68
|
+
* by that name. An uppercase filename is a declaration the auditor never
|
|
69
|
+
* finds, which reads exactly like a phase with no declaration at all.
|
|
70
|
+
*/
|
|
71
|
+
export declare function projectPhase(plan: unknown, phaseId: string): ProjectionResult | ProjectionFailure;
|
|
72
|
+
/** The declaration as the auditor reads it: JSON, one trailing newline. */
|
|
73
|
+
export declare function renderDeclaration(declaration: PhaseDeclaration): string;
|
package/dist/src/plan.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE PHASE-DECLARATION PROJECTION (kernel plan M3, M3-P1 step 2; D-M3-18).
|
|
3
|
+
*
|
|
4
|
+
* The M2-P4 scope auditor reads one document per phase from
|
|
5
|
+
* `delivery/plan/phase-declarations/<phase-id-lowercased>.json`, out of the
|
|
6
|
+
* MERGE BASE of the audited branch. Until now that document was a SECOND
|
|
7
|
+
* HAND-AUTHORED SOURCE beside the plan, and two hand-authored sources of the
|
|
8
|
+
* same fact drift. This module makes it a GENERATED VIEW of the plan, so the
|
|
9
|
+
* plan is the one source and the auditor's input is derived from it.
|
|
10
|
+
*
|
|
11
|
+
* WHAT THE PROJECTION EMITS, taken from the DELIVERED
|
|
12
|
+
* `src/gates/schemas/phase-declaration.schema.json` rather than from a
|
|
13
|
+
* description of it. The delivered schema sets `additionalProperties: false`,
|
|
14
|
+
* so an extra property is a REJECTION and not an ignored field, and the
|
|
15
|
+
* emitted key set is exactly these five, camelCase:
|
|
16
|
+
*
|
|
17
|
+
* id, branch, filesToTouch, declaredExtras, citations
|
|
18
|
+
*
|
|
19
|
+
* TWO VOCABULARIES MEET HERE AND NOWHERE ELSE. The plan spells the same
|
|
20
|
+
* fields `files-to-touch` and `extras`, because the plan is a document
|
|
21
|
+
* authored for people. This projector is the single translation point, and
|
|
22
|
+
* that is D-M3-18's whole value.
|
|
23
|
+
*
|
|
24
|
+
* THE GLOSS IS STRIPPED HERE OR NOWHERE (M2R-016). The auditor matches a
|
|
25
|
+
* declared path as a literal string or as a directory prefix and interprets
|
|
26
|
+
* nothing, so a plan entry written as
|
|
27
|
+
*
|
|
28
|
+
* `src/cli.ts` (edit only if step 4 requires it)
|
|
29
|
+
*
|
|
30
|
+
* must project to `src/cli.ts`. A projector that passed the glossed form
|
|
31
|
+
* through would emit a declaration under which every real change is
|
|
32
|
+
* undeclared, and the auditor would be right to reject it.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Reduce one plan `files-to-touch` entry to the bare path.
|
|
36
|
+
*
|
|
37
|
+
* THIS FUNCTION FEEDS THE GATE THAT EXISTS TO PREVENT SCOPE WIDENING, so an
|
|
38
|
+
* over-eager strip here is worse than no strip at all.
|
|
39
|
+
*
|
|
40
|
+
* MEASURED DEFECT, fix round 1 (B-001, high). The first version truncated at
|
|
41
|
+
* the FIRST `(` and produced:
|
|
42
|
+
*
|
|
43
|
+
* "src/app/(marketing)/page.tsx" -> "src/app/"
|
|
44
|
+
* "src/(lib)/util.ts" -> "src/"
|
|
45
|
+
*
|
|
46
|
+
* `src/gates/scope.ts` treats a trailing slash as a DIRECTORY PREFIX GRANT, so
|
|
47
|
+
* each of those turned one declared file into an entire tree. Parenthesised
|
|
48
|
+
* path segments are ordinary in real projects (Next.js route groups are the
|
|
49
|
+
* obvious case), so this is not a corner.
|
|
50
|
+
*
|
|
51
|
+
* THE RULE NOW, and each clause is load-bearing:
|
|
52
|
+
*
|
|
53
|
+
* 1. A gloss is stripped only when it is TRAILING and the whole of it is
|
|
54
|
+
* parenthesised: `<path> (anything)` with the closing paren at the end.
|
|
55
|
+
* 2. It is stripped only when WHITESPACE separates it from the path. A path
|
|
56
|
+
* whose own last segment is parenthesised, `src/app/(marketing)`, has no
|
|
57
|
+
* such whitespace and is left alone.
|
|
58
|
+
* 3. What precedes the gloss must be ONE non-whitespace token, which is what
|
|
59
|
+
* a literal path is. That is the "plausible path" test, and it is why an
|
|
60
|
+
* interior `(` can never trigger a strip: the interior case never matches
|
|
61
|
+
* the trailing form at all.
|
|
62
|
+
*
|
|
63
|
+
* Anything the rule does not recognise is returned UNCHANGED and rejected
|
|
64
|
+
* loudly by `projectPhase`, because a prose entry that reaches the auditor as
|
|
65
|
+
* a literal string fails visibly, while a silently truncated one grants a
|
|
66
|
+
* tree.
|
|
67
|
+
*/
|
|
68
|
+
const TRAILING_GLOSS = /^(\S+)\s+\([\s\S]*\)$/;
|
|
69
|
+
export function stripGloss(entry) {
|
|
70
|
+
let text = entry.trim();
|
|
71
|
+
const trailing = TRAILING_GLOSS.exec(text);
|
|
72
|
+
if (trailing !== null) {
|
|
73
|
+
text = trailing[1].trim();
|
|
74
|
+
}
|
|
75
|
+
/* Backticks are markdown, never part of a path. Stripped AFTER the gloss,
|
|
76
|
+
because the plan writes `` `path` (gloss) `` and the backticks close
|
|
77
|
+
before the gloss opens. */
|
|
78
|
+
text = text.replace(/^`+/, "").replace(/`+$/, "").trim();
|
|
79
|
+
/* A trailing comma survives a list written inline. */
|
|
80
|
+
text = text.replace(/,+$/, "").trim();
|
|
81
|
+
return text;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Is this a literal path the scope auditor can match?
|
|
85
|
+
*
|
|
86
|
+
* The auditor compares strings exactly or as a directory prefix and
|
|
87
|
+
* interprets nothing, so an entry still carrying whitespace after the gloss
|
|
88
|
+
* strip is prose. The projector REFUSES rather than emitting it: an emitted
|
|
89
|
+
* prose entry is a declaration nobody can satisfy, and the failure would
|
|
90
|
+
* surface as an unrelated scope-gate red on some later branch.
|
|
91
|
+
*/
|
|
92
|
+
export function isLiteralPath(entry) {
|
|
93
|
+
return entry !== "" && !/\s/.test(entry);
|
|
94
|
+
}
|
|
95
|
+
function asRecord(value) {
|
|
96
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
97
|
+
? value
|
|
98
|
+
: undefined;
|
|
99
|
+
}
|
|
100
|
+
function stringArray(value) {
|
|
101
|
+
return Array.isArray(value)
|
|
102
|
+
? value.filter((item) => typeof item === "string")
|
|
103
|
+
: [];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Project one phase of a decoded plan into its declaration.
|
|
107
|
+
*
|
|
108
|
+
* The filename is the phase id LOWERCASED, because CI derives `--phase` from
|
|
109
|
+
* the branch with a lowercase regex and the auditor looks the declaration up
|
|
110
|
+
* by that name. An uppercase filename is a declaration the auditor never
|
|
111
|
+
* finds, which reads exactly like a phase with no declaration at all.
|
|
112
|
+
*/
|
|
113
|
+
export function projectPhase(plan, phaseId) {
|
|
114
|
+
const document = asRecord(plan);
|
|
115
|
+
if (document === undefined) {
|
|
116
|
+
return { ok: false, reason: "the plan document is not a mapping" };
|
|
117
|
+
}
|
|
118
|
+
const phases = Array.isArray(document["phases"]) ? document["phases"] : [];
|
|
119
|
+
const phase = asRecord(phases.find((candidate) => asRecord(candidate)?.["id"] === phaseId));
|
|
120
|
+
if (phase === undefined) {
|
|
121
|
+
return { ok: false, reason: `the plan contains no phase ${phaseId}` };
|
|
122
|
+
}
|
|
123
|
+
const filesToTouch = stringArray(phase["files-to-touch"])
|
|
124
|
+
.map(stripGloss)
|
|
125
|
+
.filter((path) => path !== "");
|
|
126
|
+
const declaredExtras = stringArray(phase["extras"])
|
|
127
|
+
.map(stripGloss)
|
|
128
|
+
.filter((path) => path !== "");
|
|
129
|
+
const prose = [...filesToTouch, ...declaredExtras].filter((path) => !isLiteralPath(path));
|
|
130
|
+
if (prose.length > 0) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
reason: `phase ${phaseId} declares an entry that is not a literal path and ` +
|
|
134
|
+
`cannot be projected: ${prose.map((p) => JSON.stringify(p)).join(", ")}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const declaration = {
|
|
138
|
+
id: String(phase["id"]),
|
|
139
|
+
branch: String(phase["branch"]),
|
|
140
|
+
filesToTouch,
|
|
141
|
+
declaredExtras,
|
|
142
|
+
citations: stringArray(phase["citations"]),
|
|
143
|
+
};
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
declaration,
|
|
147
|
+
filename: `${phaseId.toLowerCase()}.json`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** The declaration as the auditor reads it: JSON, one trailing newline. */
|
|
151
|
+
export function renderDeclaration(declaration) {
|
|
152
|
+
return `${JSON.stringify(declaration, undefined, 2)}\n`;
|
|
153
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Fleet } from "./fleet.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Worktree pool over a project clone (kernel plan v1, M1-P3 step 3).
|
|
4
|
+
* BUILD from the contract (plan decision D-1, FM-026): a clean disposable
|
|
5
|
+
* worktree per task at <fleet>/worktrees/<task-id>, parallel-safe through
|
|
6
|
+
* unique paths, O_EXCL record creation, and git worktree add's own
|
|
7
|
+
* locking. That safety is claimed only at the width M1 actually uses:
|
|
8
|
+
* criterion 15's two concurrent creates, which are witnessed. This
|
|
9
|
+
* phase's own verification measured failures above roughly six-way
|
|
10
|
+
* concurrency on both the fetch and the worktree add, and hardening
|
|
11
|
+
* for that width is deferred to M5 (see the deferral list in
|
|
12
|
+
* delivery/work-history/m1-p3.md). Do not read this as a guarantee at
|
|
13
|
+
* arbitrary concurrency. Substrate-neutral: pure filesystem and git
|
|
14
|
+
* (DR-0007).
|
|
15
|
+
*
|
|
16
|
+
* Base resolution is the five binding steps of EXT-F-03: resolve the
|
|
17
|
+
* project's configured remote and its default branch, fetch that branch,
|
|
18
|
+
* record the fetched base SHA in the pool record (and the CLI emits it on
|
|
19
|
+
* stdout; M1-P4 spawn copies it into tasks/<id>/meta.json as baseSha),
|
|
20
|
+
* create the task branch and worktree directly from that exact SHA, and
|
|
21
|
+
* on fetch failure fail rather than silently use a stale local branch,
|
|
22
|
+
* unless --offline was explicitly passed, in which case the last fetched
|
|
23
|
+
* remote-tracking SHA is used and offline: true is recorded. The clone's
|
|
24
|
+
* local branches are never consulted: a stale local branch is never the
|
|
25
|
+
* base, whether behind or ahead of the remote.
|
|
26
|
+
*
|
|
27
|
+
* The pool record lives BESIDE the worktree (worktrees/<task-id>.pool.json,
|
|
28
|
+
* plain JSON per D-3), never inside it, so the record can never dirty the
|
|
29
|
+
* destroy-time cleanliness check (FM-059: no exemption list, ever).
|
|
30
|
+
*
|
|
31
|
+
* Destroy refuses a dirty worktree (uncommitted changes or untracked
|
|
32
|
+
* files) unless --discard, which is reserved for the teardown scout path
|
|
33
|
+
* (PR-010). A transient git index.lock during destroy is retried; the
|
|
34
|
+
* lock file is removed only under a fail-safe staleness proof (provably
|
|
35
|
+
* no holder via lsof plus mtime age beyond a threshold; any uncertainty
|
|
36
|
+
* means leave it and fail loudly), per FM-036 and FM-051.
|
|
37
|
+
*/
|
|
38
|
+
/** Task branch created by the pool at the fetched base SHA. */
|
|
39
|
+
export declare function taskBranchName(taskId: string): string;
|
|
40
|
+
/** Task ids are single safe path segments. */
|
|
41
|
+
export declare const TASK_ID_PATTERN: RegExp;
|
|
42
|
+
export interface PoolRecord {
|
|
43
|
+
taskId: string;
|
|
44
|
+
project: string;
|
|
45
|
+
remote: string;
|
|
46
|
+
branch: string;
|
|
47
|
+
baseSha: string;
|
|
48
|
+
branchName: string;
|
|
49
|
+
offline: boolean;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
}
|
|
52
|
+
export type PoolResult<T> = {
|
|
53
|
+
ok: true;
|
|
54
|
+
value: T;
|
|
55
|
+
} | {
|
|
56
|
+
ok: false;
|
|
57
|
+
reason: string;
|
|
58
|
+
};
|
|
59
|
+
/** Exported for the contention-classification test. */
|
|
60
|
+
export declare function isTransientGitLockError(stderr: string): boolean;
|
|
61
|
+
export declare function recordPath(fleet: Fleet, taskId: string): string;
|
|
62
|
+
export declare function worktreePath(fleet: Fleet, taskId: string): string;
|
|
63
|
+
export declare function readPoolRecord(fleet: Fleet, taskId: string): PoolRecord | undefined;
|
|
64
|
+
export interface CreateOptions {
|
|
65
|
+
taskId: string;
|
|
66
|
+
project: string;
|
|
67
|
+
offline: boolean;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* pool create (EXT-F-03 five steps; see module doc). Returns the pool
|
|
71
|
+
* record on success.
|
|
72
|
+
*
|
|
73
|
+
* On failure NOTHING IS REMOVED. The pool record, the worktree
|
|
74
|
+
* directory and the task branch may each survive, depending on how far
|
|
75
|
+
* the attempt got, and the reason line names exactly which of them did
|
|
76
|
+
* and the command that clears them. The automatic rollback this
|
|
77
|
+
* docstring used to promise was deleted deliberately: it served a
|
|
78
|
+
* concurrent-create path M1 never enters (parallelism is off until M5)
|
|
79
|
+
* and produced four consecutive rounds of defects, including deleting
|
|
80
|
+
* state it had not validated. Failing loudly and leaving state is the
|
|
81
|
+
* chosen contract, not an oversight.
|
|
82
|
+
*/
|
|
83
|
+
export declare function poolCreate(fleet: Fleet, options: CreateOptions): Promise<PoolResult<PoolRecord>>;
|
|
84
|
+
export interface PoolListEntry {
|
|
85
|
+
taskId: string;
|
|
86
|
+
headSha: string;
|
|
87
|
+
}
|
|
88
|
+
/** One entry per pool record, with the worktree's current HEAD SHA. */
|
|
89
|
+
export declare function poolList(fleet: Fleet): PoolListEntry[];
|
|
90
|
+
export interface LsofProbe {
|
|
91
|
+
available: boolean;
|
|
92
|
+
exitCode: number | null;
|
|
93
|
+
stdout: string;
|
|
94
|
+
}
|
|
95
|
+
/** Age a lock file must reach before a staleness proof is even considered. */
|
|
96
|
+
export declare const STALE_LOCK_AGE_MS = 300000;
|
|
97
|
+
/**
|
|
98
|
+
* Fail-safe staleness proof for a git lock file (FM-036, FM-051): true
|
|
99
|
+
* only when the lock exists, its mtime age exceeds the threshold, and
|
|
100
|
+
* lsof is available and shows provably no holder (exit 1, empty stdout).
|
|
101
|
+
* Any uncertainty (lsof missing, erroring, or listing holders) is false:
|
|
102
|
+
* the lock is left in place and the operation fails loudly.
|
|
103
|
+
*/
|
|
104
|
+
export declare function provablyStaleLock(lockFile: string, opts?: {
|
|
105
|
+
nowMs?: number;
|
|
106
|
+
ageThresholdMs?: number;
|
|
107
|
+
runLsof?: (path: string) => LsofProbe;
|
|
108
|
+
}): boolean;
|
|
109
|
+
export interface DestroyOptions {
|
|
110
|
+
taskId: string;
|
|
111
|
+
/** Override the dirty-worktree refusal (plan step 3, PR-010). */
|
|
112
|
+
discard: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Authorize deleting a task branch that carries commits beyond its
|
|
115
|
+
* recorded base (V-1). Deliberately distinct from discard, whose
|
|
116
|
+
* plan-defined meaning is the dirty-tree override only: conflating
|
|
117
|
+
* them would make the scout path silently destroy committed work.
|
|
118
|
+
* M1-P4 teardown passes this from its ship path after its landedness
|
|
119
|
+
* judgement, which makes the dependency on teardown explicit rather
|
|
120
|
+
* than assumed.
|
|
121
|
+
*/
|
|
122
|
+
deleteBranchForce: boolean;
|
|
123
|
+
}
|
|
124
|
+
/** What a completed destroy removed, for the operator's record. */
|
|
125
|
+
export interface DestroyOutcome {
|
|
126
|
+
deletedBranch?: string;
|
|
127
|
+
/** The branch tip at deletion time: the recovery handle (V-1). */
|
|
128
|
+
deletedSha?: string;
|
|
129
|
+
}
|
|
130
|
+
export declare function poolDestroy(fleet: Fleet, options: DestroyOptions): Promise<PoolResult<DestroyOutcome>>;
|