@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,390 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, relative, resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { refuseOpenForWrite, singleLine } from "../task.js";
|
|
5
|
+
import { loadManifest } from "./manifest.js";
|
|
6
|
+
import { exitCodeForStatus, makeGateResult, renderGateResult } from "./result.js";
|
|
7
|
+
import { listWitnessSpecFiles, loadWitnessSpec, memberTouchedFiles, } from "../witness/spec.js";
|
|
8
|
+
import { SPAWN_GREP, computePhaseDiff, evaluateWitness, gitIn, makeScratchRoot, readTestFilesAtHead, removeScratchRoot, resolveRepoRoot, shellSpawnsAndParses, } from "../witness/run.js";
|
|
9
|
+
/**
|
|
10
|
+
* THE RED-WITNESS GATE (kernel plan M2, M2-P2 steps 6 and 7).
|
|
11
|
+
*
|
|
12
|
+
* Registered in gates.manifest.json as `red-witness`, applicability
|
|
13
|
+
* `required`, precondition `diff-touches` on `src/` and `bin/`, unitLabel
|
|
14
|
+
* `witnesses evaluated`. Invoked by the runner as
|
|
15
|
+
*
|
|
16
|
+
* node src/gates/red-witness.ts --result <path> --evidence <dir>
|
|
17
|
+
* --base <ref> --head <ref>
|
|
18
|
+
*
|
|
19
|
+
* plus `--baseline <ref>` when invoked directly: merge-time re-verification
|
|
20
|
+
* is a PARAMETER, not an enforcement (M2-D-08); it defaults to `--base`.
|
|
21
|
+
*
|
|
22
|
+
* WHAT THE GATE DECIDES.
|
|
23
|
+
* - Every witness spec changed in the phase diff (the phase's OWN
|
|
24
|
+
* witnesses) is evaluated by the harness.
|
|
25
|
+
* - Every STORED witness any of whose dangerous-state members touches a
|
|
26
|
+
* changed file is re-evaluated; one now green against any of its own
|
|
27
|
+
* members is red with reason "witness no longer guards its behavior"
|
|
28
|
+
* naming the witness and the measured rate (M2R-002, the N-401 shape).
|
|
29
|
+
* - A changed source file under src/ or bin/ with no witness spec
|
|
30
|
+
* covering it is RED, never not-applicable (step 7).
|
|
31
|
+
* - `--base` absent is `error` (M2-C-3).
|
|
32
|
+
*
|
|
33
|
+
* DEPTH REQUIREMENT, documented rather than assumed (STATE.md CR-902
|
|
34
|
+
* carry-forward): the gate needs `--base` and `--head` resolvable with
|
|
35
|
+
* history (the diff is base...head, a merge-base diff) and an UNSHALLOW
|
|
36
|
+
* repository, because the harness scratch-clones it and git refuses to
|
|
37
|
+
* clone from a shallow source. Both are satisfied by `fetch-depth: 0` on
|
|
38
|
+
* the CI checkout, which is owned by the workflow's owner, not this phase.
|
|
39
|
+
* A shallow repository is `error` naming the requirement. On pull_request
|
|
40
|
+
* events the checkout HEAD is a synthetic merge commit, so the audited
|
|
41
|
+
* head is always taken from `--head`, never from the checkout.
|
|
42
|
+
*/
|
|
43
|
+
const USAGE = "usage: node src/gates/red-witness.ts --result <path> --evidence <dir> " +
|
|
44
|
+
"--base <ref> [--head <ref>] [--baseline <ref>] [--phase <id>]";
|
|
45
|
+
function parseArgs(argv) {
|
|
46
|
+
const options = {};
|
|
47
|
+
const known = new Map([
|
|
48
|
+
["--result", "result"],
|
|
49
|
+
["--evidence", "evidence"],
|
|
50
|
+
["--base", "base"],
|
|
51
|
+
["--head", "head"],
|
|
52
|
+
["--baseline", "baseline"],
|
|
53
|
+
["--phase", "phase"],
|
|
54
|
+
]);
|
|
55
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
56
|
+
const flag = argv[index];
|
|
57
|
+
const field = known.get(flag);
|
|
58
|
+
if (field === undefined) {
|
|
59
|
+
return { usageError: `unknown option ${flag}` };
|
|
60
|
+
}
|
|
61
|
+
const value = argv[index + 1];
|
|
62
|
+
if (value === undefined || value.startsWith("--")) {
|
|
63
|
+
return { usageError: `${flag} requires a value` };
|
|
64
|
+
}
|
|
65
|
+
options[field] = value;
|
|
66
|
+
}
|
|
67
|
+
return { options };
|
|
68
|
+
}
|
|
69
|
+
function now() {
|
|
70
|
+
return new Date().toISOString();
|
|
71
|
+
}
|
|
72
|
+
function errorOutcome(startedAt, detail) {
|
|
73
|
+
const result = makeGateResult({
|
|
74
|
+
gate: "red-witness",
|
|
75
|
+
status: "error",
|
|
76
|
+
units: 0,
|
|
77
|
+
unitLabel: "witnesses evaluated",
|
|
78
|
+
startedAt,
|
|
79
|
+
endedAt: now(),
|
|
80
|
+
detail,
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
result,
|
|
84
|
+
exitCode: exitCodeForStatus(result.status),
|
|
85
|
+
evaluations: [],
|
|
86
|
+
reEvaluationMs: 0,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** True when the repo-relative path is a phase-audited source path. */
|
|
90
|
+
function isAuditedSource(path) {
|
|
91
|
+
return path.startsWith("src/") || path.startsWith("bin/");
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Run the red-witness gate against a repository. Exported so tests can
|
|
95
|
+
* drive it without a subprocess; the CLI below is a thin shell over it.
|
|
96
|
+
*/
|
|
97
|
+
export function runRedWitnessGate(run) {
|
|
98
|
+
const startedAt = now();
|
|
99
|
+
const rootProbe = resolveRepoRoot(run.repoRoot);
|
|
100
|
+
if (rootProbe.root === undefined) {
|
|
101
|
+
return errorOutcome(startedAt, rootProbe.reason);
|
|
102
|
+
}
|
|
103
|
+
const repoRoot = rootProbe.root;
|
|
104
|
+
const shallow = gitIn(repoRoot, ["rev-parse", "--is-shallow-repository"]);
|
|
105
|
+
if (!shallow.ok) {
|
|
106
|
+
return errorOutcome(startedAt, shallow.reason);
|
|
107
|
+
}
|
|
108
|
+
if (shallow.stdout.trim() === "true") {
|
|
109
|
+
return errorOutcome(startedAt, "this repository is a shallow clone; the red-witness gate requires " +
|
|
110
|
+
"full history (fetch-depth: 0) because the phase diff is a " +
|
|
111
|
+
"merge-base diff and the harness scratch-clones the repository, " +
|
|
112
|
+
"which git refuses from a shallow source");
|
|
113
|
+
}
|
|
114
|
+
const diffOutcome = computePhaseDiff(repoRoot, run.base, run.head ?? "HEAD");
|
|
115
|
+
if (!diffOutcome.ok) {
|
|
116
|
+
return errorOutcome(startedAt, diffOutcome.reason);
|
|
117
|
+
}
|
|
118
|
+
const diff = diffOutcome.diff;
|
|
119
|
+
const manifestPath = join(repoRoot, "gates.manifest.json");
|
|
120
|
+
const manifest = loadManifest(manifestPath);
|
|
121
|
+
if (!manifest.ok) {
|
|
122
|
+
return errorOutcome(startedAt, `the destructiveCommands list could not be read (rule (e) needs it): ` +
|
|
123
|
+
`${manifest.reason}${manifest.diagnostics.length > 0 ? `: ${manifest.diagnostics.join("; ")}` : ""}`);
|
|
124
|
+
}
|
|
125
|
+
const behaviorsShown = gitIn(repoRoot, [
|
|
126
|
+
"show",
|
|
127
|
+
`${diff.headSha}:test/behaviors.json`,
|
|
128
|
+
]);
|
|
129
|
+
if (!behaviorsShown.ok) {
|
|
130
|
+
return errorOutcome(startedAt, `test/behaviors.json could not be read at the audited head: ${behaviorsShown.reason}`);
|
|
131
|
+
}
|
|
132
|
+
let behaviors;
|
|
133
|
+
try {
|
|
134
|
+
behaviors = new Set(Object.keys(JSON.parse(behaviorsShown.stdout)));
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
return errorOutcome(startedAt, `test/behaviors.json does not parse at the audited head: ${singleLine(String(error))}`);
|
|
138
|
+
}
|
|
139
|
+
const testFilesOutcome = readTestFilesAtHead(repoRoot, diff.headSha);
|
|
140
|
+
if (!testFilesOutcome.ok) {
|
|
141
|
+
return errorOutcome(startedAt, testFilesOutcome.reason);
|
|
142
|
+
}
|
|
143
|
+
// Rule (f)'s derivation: the spawn grep over the changed files' head
|
|
144
|
+
// contents (deleted files have no head content and cannot be touched by a
|
|
145
|
+
// member either). Shell scripts (*.sh) are screened by the shell
|
|
146
|
+
// spawn-and-parse derivation instead of the JS token grep (CR-H2), so a
|
|
147
|
+
// bin/ shell script that classifies another program's output (the V-2
|
|
148
|
+
// shape) is not invisible to the capture obligation.
|
|
149
|
+
const spawningChangedFiles = [];
|
|
150
|
+
for (const [path, file] of diff.files) {
|
|
151
|
+
if (file.status === "D") {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const shown = gitIn(repoRoot, ["show", `${diff.headSha}:${path}`]);
|
|
155
|
+
if (!shown.ok) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const spawns = path.endsWith(".sh")
|
|
159
|
+
? shellSpawnsAndParses(shown.stdout)
|
|
160
|
+
: SPAWN_GREP.test(shown.stdout);
|
|
161
|
+
if (spawns) {
|
|
162
|
+
spawningChangedFiles.push(path);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
spawningChangedFiles.sort();
|
|
166
|
+
const witnessDir = join(repoRoot, "witness");
|
|
167
|
+
const listing = listWitnessSpecFiles(witnessDir);
|
|
168
|
+
if (!listing.ok) {
|
|
169
|
+
return errorOutcome(startedAt, listing.reason);
|
|
170
|
+
}
|
|
171
|
+
const reasons = [];
|
|
172
|
+
const specs = [];
|
|
173
|
+
const seenIds = new Map();
|
|
174
|
+
for (const path of listing.paths) {
|
|
175
|
+
const loaded = loadWitnessSpec(path);
|
|
176
|
+
const repoRelative = relative(repoRoot, path).split("\\").join("/");
|
|
177
|
+
if (!loaded.ok) {
|
|
178
|
+
reasons.push(`${loaded.reason}${loaded.diagnostics.length > 0 ? `: ${loaded.diagnostics.join("; ")}` : ""}`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const previous = seenIds.get(loaded.spec.id);
|
|
182
|
+
if (previous !== undefined) {
|
|
183
|
+
reasons.push(`witness id ${loaded.spec.id} is declared by both ${previous} and ${repoRelative}`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
seenIds.set(loaded.spec.id, repoRelative);
|
|
187
|
+
specs.push({ spec: loaded.spec, path, repoRelative });
|
|
188
|
+
}
|
|
189
|
+
const readPatchAtHead = (patchPath) => {
|
|
190
|
+
const shown = gitIn(repoRoot, ["show", `${diff.headSha}:${patchPath}`]);
|
|
191
|
+
return shown.ok ? shown.stdout : undefined;
|
|
192
|
+
};
|
|
193
|
+
const own = specs.filter((entry) => diff.files.has(entry.repoRelative));
|
|
194
|
+
const stored = specs.filter((entry) => !diff.files.has(entry.repoRelative));
|
|
195
|
+
const triggeredStored = stored.filter((entry) => entry.spec.dangerousStates.some((member) => memberTouchedFiles(member, readPatchAtHead).some((file) => diff.files.has(file))));
|
|
196
|
+
// Coverage (step 7): source changed with no witness spec covering it is
|
|
197
|
+
// red, never not-applicable. Coverage semantics are decision D-P2-2 in
|
|
198
|
+
// the work history.
|
|
199
|
+
const covered = new Set();
|
|
200
|
+
for (const entry of specs) {
|
|
201
|
+
for (const member of entry.spec.dangerousStates) {
|
|
202
|
+
for (const file of memberTouchedFiles(member, readPatchAtHead)) {
|
|
203
|
+
covered.add(file);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const uncovered = [];
|
|
208
|
+
for (const [path, file] of diff.files) {
|
|
209
|
+
if (file.status === "D" || !isAuditedSource(path)) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (!covered.has(path)) {
|
|
213
|
+
uncovered.push(path);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
uncovered.sort();
|
|
217
|
+
if (uncovered.length > 0) {
|
|
218
|
+
reasons.push(`source changed with no witness spec covering it: ${uncovered.join(", ")}`);
|
|
219
|
+
}
|
|
220
|
+
const evaluations = [];
|
|
221
|
+
let reEvaluationMs = 0;
|
|
222
|
+
const scratchRoot = makeScratchRoot();
|
|
223
|
+
try {
|
|
224
|
+
const baseInputs = {
|
|
225
|
+
repoRoot,
|
|
226
|
+
headSha: diff.headSha,
|
|
227
|
+
baselineRef: run.baseline ?? run.base,
|
|
228
|
+
diff,
|
|
229
|
+
destructiveCommands: manifest.manifest.destructiveCommands,
|
|
230
|
+
behaviors,
|
|
231
|
+
testFiles: testFilesOutcome.files,
|
|
232
|
+
spawningChangedFiles,
|
|
233
|
+
scratchRoot,
|
|
234
|
+
};
|
|
235
|
+
for (const entry of own) {
|
|
236
|
+
const inputs = { ...baseInputs, phaseOwn: true };
|
|
237
|
+
if (run.hooks !== undefined) {
|
|
238
|
+
inputs.hooks = run.hooks;
|
|
239
|
+
}
|
|
240
|
+
const evaluation = evaluateWitness(entry.spec, entry.repoRelative, inputs);
|
|
241
|
+
evaluations.push(evaluation);
|
|
242
|
+
if (evaluation.status !== "green") {
|
|
243
|
+
reasons.push(`witness ${evaluation.witness}: ${evaluation.status}: ${evaluation.reasons.join("; ")}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const reEvaluationStart = Date.now();
|
|
247
|
+
for (const entry of triggeredStored) {
|
|
248
|
+
const inputs = { ...baseInputs, phaseOwn: false };
|
|
249
|
+
if (run.hooks !== undefined) {
|
|
250
|
+
inputs.hooks = run.hooks;
|
|
251
|
+
}
|
|
252
|
+
const evaluation = evaluateWitness(entry.spec, entry.repoRelative, inputs);
|
|
253
|
+
evaluations.push(evaluation);
|
|
254
|
+
if (evaluation.status === "red") {
|
|
255
|
+
const rates = evaluation.members
|
|
256
|
+
.filter((member) => member.rate !== undefined)
|
|
257
|
+
.map((member) => `member ${String(member.index)} red ` +
|
|
258
|
+
`${String(member.rate?.red)}/${String(member.rate?.total)}`)
|
|
259
|
+
.join(", ");
|
|
260
|
+
reasons.push(`witness ${evaluation.witness} no longer guards its behavior ` +
|
|
261
|
+
`(${rates === "" ? evaluation.reasons.join("; ") : rates})`);
|
|
262
|
+
}
|
|
263
|
+
else if (evaluation.status === "error") {
|
|
264
|
+
reasons.push(`witness ${evaluation.witness}: error: ${evaluation.reasons.join("; ")}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
reEvaluationMs = Date.now() - reEvaluationStart;
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
removeScratchRoot(scratchRoot);
|
|
271
|
+
}
|
|
272
|
+
let status = "green";
|
|
273
|
+
if (evaluations.some((evaluation) => evaluation.status === "error")) {
|
|
274
|
+
status = "error";
|
|
275
|
+
}
|
|
276
|
+
else if (reasons.length > 0) {
|
|
277
|
+
status = "red";
|
|
278
|
+
}
|
|
279
|
+
const evidence = [];
|
|
280
|
+
if (run.evidenceDir !== undefined) {
|
|
281
|
+
const recordsPath = join(run.evidenceDir, "witness-records.json");
|
|
282
|
+
const refusal = refuseOpenForWrite(recordsPath);
|
|
283
|
+
if (refusal === undefined) {
|
|
284
|
+
try {
|
|
285
|
+
mkdirSync(run.evidenceDir, { recursive: true });
|
|
286
|
+
writeFileSync(recordsPath, `${JSON.stringify({
|
|
287
|
+
base: diff.baseSha,
|
|
288
|
+
head: diff.headSha,
|
|
289
|
+
spawningChangedFiles,
|
|
290
|
+
uncoveredSources: uncovered,
|
|
291
|
+
reEvaluationMs,
|
|
292
|
+
evaluations,
|
|
293
|
+
}, null, 2)}\n`);
|
|
294
|
+
evidence.push("witness-records.json");
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// The record still carries the verdict; evidence is best-effort.
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const detail = `${String(evaluations.length)} witness(es) evaluated ` +
|
|
302
|
+
`(${String(own.length)} own, ${String(triggeredStored.length)} stored ` +
|
|
303
|
+
`re-evaluated in ${String(reEvaluationMs)}ms); ` +
|
|
304
|
+
(reasons.length === 0
|
|
305
|
+
? "every witness red against every declared dangerous state and green at head"
|
|
306
|
+
: reasons.join("; "));
|
|
307
|
+
const result = makeGateResult({
|
|
308
|
+
gate: "red-witness",
|
|
309
|
+
status,
|
|
310
|
+
units: evaluations.length,
|
|
311
|
+
unitLabel: "witnesses evaluated",
|
|
312
|
+
startedAt,
|
|
313
|
+
endedAt: now(),
|
|
314
|
+
detail,
|
|
315
|
+
evidence,
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
result,
|
|
319
|
+
exitCode: exitCodeForStatus(result.status),
|
|
320
|
+
evaluations,
|
|
321
|
+
reEvaluationMs,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function main(argv) {
|
|
325
|
+
const parsed = parseArgs(argv);
|
|
326
|
+
if (parsed.options === undefined) {
|
|
327
|
+
process.stderr.write(`tiphys red-witness: ${parsed.usageError}\n${USAGE}\n`);
|
|
328
|
+
return 64;
|
|
329
|
+
}
|
|
330
|
+
const options = parsed.options;
|
|
331
|
+
if (options.result === undefined) {
|
|
332
|
+
process.stderr.write(`tiphys red-witness: --result is required\n${USAGE}\n`);
|
|
333
|
+
return 64;
|
|
334
|
+
}
|
|
335
|
+
let outcome;
|
|
336
|
+
if (options.base === undefined) {
|
|
337
|
+
// M2-C-3 (M2R-003): a required invocation parameter absent is error,
|
|
338
|
+
// never not-applicable and never a guess.
|
|
339
|
+
outcome = errorOutcome(now(), "--base was not supplied; the phase diff cannot be computed (M2-C-3)");
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const run = { repoRoot: process.cwd(), base: options.base };
|
|
343
|
+
if (options.head !== undefined) {
|
|
344
|
+
run.head = options.head;
|
|
345
|
+
}
|
|
346
|
+
if (options.baseline !== undefined) {
|
|
347
|
+
run.baseline = options.baseline;
|
|
348
|
+
}
|
|
349
|
+
if (options.evidence !== undefined) {
|
|
350
|
+
run.evidenceDir = options.evidence;
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
outcome = runRedWitnessGate(run);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
// No throw may escape as exit 1: that is the RED code (the runner's
|
|
357
|
+
// own crash-discipline rule, applied to this gate).
|
|
358
|
+
outcome = errorOutcome(now(), `the red-witness gate failed: ${singleLine(error.message ?? String(error))}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const refusal = refuseOpenForWrite(options.result);
|
|
362
|
+
if (refusal !== undefined) {
|
|
363
|
+
process.stderr.write(`tiphys red-witness: ${refusal}\n`);
|
|
364
|
+
return 21;
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
writeFileSync(options.result, renderGateResult(outcome.result));
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
process.stderr.write(`tiphys red-witness: the result record could not be written: ${singleLine(String(error))}\n`);
|
|
371
|
+
return 21;
|
|
372
|
+
}
|
|
373
|
+
process.stdout.write(`red-witness: ${outcome.result.status} (${outcome.result.detail})\n`);
|
|
374
|
+
return outcome.exitCode;
|
|
375
|
+
}
|
|
376
|
+
const invokedDirectly = (() => {
|
|
377
|
+
const entry = process.argv[1];
|
|
378
|
+
if (entry === undefined) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
return import.meta.url === pathToFileURL(resolve(entry)).href;
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
})();
|
|
388
|
+
if (invokedDirectly) {
|
|
389
|
+
process.exit(main(process.argv.slice(2)));
|
|
390
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import type { GateStatus } from "./result.ts";
|
|
2
|
+
import type { SchemaDocument } from "./validate.ts";
|
|
3
|
+
/**
|
|
4
|
+
* THE RELEASE-VERIFICATION CONTRACT (kernel plan M2, M2-P7 step 3; DR-0014;
|
|
5
|
+
* delivery/verification/release-verification-interface.md).
|
|
6
|
+
*
|
|
7
|
+
* ONE post-merge verification contract with two registry entries (`deploy`
|
|
8
|
+
* and `migrations`). The kernel owns the SUBJECT, the CLOCK, the LOOP and
|
|
9
|
+
* the OUTCOME MAPPING; an adapter owns the platform mapping and nothing
|
|
10
|
+
* else. An adapter is an executable named by committed configuration,
|
|
11
|
+
* spawned as a subprocess with a request file path in argv, performing ONE
|
|
12
|
+
* bounded observation per invocation and writing its response to a
|
|
13
|
+
* kernel-supplied record path. It returns an OUTCOME, never a status path:
|
|
14
|
+
* JSON-pointer extraction exists only inside the http-json adapter's own
|
|
15
|
+
* configuration, and the kernel never learns what a statusPath is.
|
|
16
|
+
*
|
|
17
|
+
* WHY THE BOUNDARY IS A PROCESS (M2-D-07, investigation section 5): it
|
|
18
|
+
* works for any language, and it makes the per-attempt timeout ENFORCEABLE
|
|
19
|
+
* rather than promised. The kernel terminates an attempt that overruns
|
|
20
|
+
* (`spawnSync`'s `timeout` and `killSignal` options) and records that
|
|
21
|
+
* attempt as `error`, so an adapter that hangs cannot hang the kernel.
|
|
22
|
+
*
|
|
23
|
+
* C-2 EXEMPTION, STATED WHERE THE TERMINATION HAPPENS AND NOWHERE ELSE.
|
|
24
|
+
* C-2 forbids pid, process liveness, signals and /proc FOR IDENTITY OR
|
|
25
|
+
* EXCLUSION. The per-attempt termination above is neither: it is a timeout
|
|
26
|
+
* bound on a child this kernel itself spawned and still holds the handle
|
|
27
|
+
* of, it probes nothing, identifies nothing and excludes nothing, and no
|
|
28
|
+
* pid is ever read, recorded or compared. That is the one place any kill
|
|
29
|
+
* may appear in this module or its adapters, and it appears as a spawn
|
|
30
|
+
* option, not as a process.kill call.
|
|
31
|
+
*
|
|
32
|
+
* NO FAILURE VOCABULARY SHIPS (plan step 2, T-003 lesson 4). No
|
|
33
|
+
* non-success platform state was ever captured, so none is named anywhere
|
|
34
|
+
* in this module or its adapters. The safe rule instead: one satisfying
|
|
35
|
+
* value per configured adapter; every other observed value is recorded
|
|
36
|
+
* verbatim and treated as `pending` until the deadline, at which point it
|
|
37
|
+
* becomes red naming the last observed value. Unknown never becomes green.
|
|
38
|
+
*
|
|
39
|
+
* NEVER auto-background (C-3): the loop is a foreground await, every spawn
|
|
40
|
+
* is spawnSync, nothing is detached and nothing is unref'd.
|
|
41
|
+
*/
|
|
42
|
+
/** The one declaration path (design decision D-p7-1 in the work history). */
|
|
43
|
+
export declare const DECLARATION_PATH = "release-verification.json";
|
|
44
|
+
/** The contract versions this kernel accepts (fail-closed rule 6). */
|
|
45
|
+
export declare const ACCEPTED_CONTRACT_VERSIONS: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* The subject: what the kernel knows for certain the moment a merge
|
|
48
|
+
* happens. Platform vocabulary (deployment id, workflow run id, migration
|
|
49
|
+
* set) is the adapter's, and the kernel never learns it.
|
|
50
|
+
*/
|
|
51
|
+
export interface ReleaseSubject {
|
|
52
|
+
repository: string;
|
|
53
|
+
integrationRef: string;
|
|
54
|
+
mergedSha: string;
|
|
55
|
+
mergedAt: string;
|
|
56
|
+
phaseId: string;
|
|
57
|
+
}
|
|
58
|
+
export declare const SUBJECT_FIELDS: readonly (keyof ReleaseSubject)[];
|
|
59
|
+
/**
|
|
60
|
+
* The outcome vocabulary, six values. There is deliberately no `unknown`,
|
|
61
|
+
* no `skipped` and no `warn`: a soft state is where a false green hides
|
|
62
|
+
* (M2-C-3).
|
|
63
|
+
*/
|
|
64
|
+
export type VerificationOutcome = "satisfied" | "failed" | "pending" | "absent" | "not-applicable" | "error";
|
|
65
|
+
export declare const VERIFICATION_OUTCOMES: readonly VerificationOutcome[];
|
|
66
|
+
/**
|
|
67
|
+
* THE TOTAL MAPPING to GateResult status, implemented in exactly one place
|
|
68
|
+
* (plan step 3). `satisfied` is green with `units` 1 per verification
|
|
69
|
+
* satisfied (or the adapter's own examined count, D-p7-3). `pending` and
|
|
70
|
+
* `absent` are NEVER terminal: they are loop states, and the two entries
|
|
71
|
+
* below are reachable only through the deadline conversion in
|
|
72
|
+
* `runVerification`, which turns both into red with TEXTUALLY DISTINCT
|
|
73
|
+
* reasons: `deadline reached, last observed <value>` for pending and
|
|
74
|
+
* `deadline reached, no release object for subject` for absent. An adapter
|
|
75
|
+
* cannot make pending mean pass.
|
|
76
|
+
*/
|
|
77
|
+
export declare const OUTCOME_TO_STATUS: Readonly<Record<VerificationOutcome, GateStatus>>;
|
|
78
|
+
export declare const DEADLINE_REASON_ABSENT = "deadline reached, no release object for subject";
|
|
79
|
+
export declare function deadlineReasonPending(lastObserved: string): string;
|
|
80
|
+
/** The kernel's clock. No defaults anywhere: the numbers are the project's. */
|
|
81
|
+
export interface VerificationClock {
|
|
82
|
+
intervalMs: number;
|
|
83
|
+
deadlineMs: number;
|
|
84
|
+
attemptTimeoutMs: number;
|
|
85
|
+
maxAttempts?: number;
|
|
86
|
+
}
|
|
87
|
+
/** What the adapter wrote, after the seven rules accepted it. */
|
|
88
|
+
export interface AdapterResponse {
|
|
89
|
+
contractVersion: string;
|
|
90
|
+
adapter: string;
|
|
91
|
+
subject: ReleaseSubject;
|
|
92
|
+
outcome: VerificationOutcome;
|
|
93
|
+
resolved?: {
|
|
94
|
+
kind: string;
|
|
95
|
+
id: string;
|
|
96
|
+
createdAt?: unknown;
|
|
97
|
+
} & Record<string, unknown>;
|
|
98
|
+
observedAt: string;
|
|
99
|
+
observation?: {
|
|
100
|
+
raw?: unknown;
|
|
101
|
+
detail: string;
|
|
102
|
+
};
|
|
103
|
+
reason?: string;
|
|
104
|
+
units?: number;
|
|
105
|
+
precondition?: {
|
|
106
|
+
id: string;
|
|
107
|
+
evidence?: string[];
|
|
108
|
+
};
|
|
109
|
+
transport?: {
|
|
110
|
+
httpStatus?: number;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** One attempt's record: number, instant, outcome, identity, transport. */
|
|
114
|
+
export interface AttemptRecord {
|
|
115
|
+
attempt: number;
|
|
116
|
+
at: string;
|
|
117
|
+
outcome: VerificationOutcome | "invalid";
|
|
118
|
+
detail: string;
|
|
119
|
+
resolved?: unknown;
|
|
120
|
+
transport: {
|
|
121
|
+
exitCode: number | null;
|
|
122
|
+
signal: string | null;
|
|
123
|
+
terminatedByTimeout: boolean;
|
|
124
|
+
httpStatus?: number;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* The older-than-the-merge observation (investigation guarantee 9,
|
|
128
|
+
* section 8 item 8): recorded, deliberately NOT promoted to an error
|
|
129
|
+
* until one real counter-example settles whether a platform reuses or
|
|
130
|
+
* backdates a record on redeploy.
|
|
131
|
+
*/
|
|
132
|
+
releaseObjectOlderThanMerge?: boolean;
|
|
133
|
+
}
|
|
134
|
+
export type VerificationVerdict = {
|
|
135
|
+
kind: "satisfied";
|
|
136
|
+
units: number;
|
|
137
|
+
detail: string;
|
|
138
|
+
resolved: unknown;
|
|
139
|
+
} | {
|
|
140
|
+
kind: "failed";
|
|
141
|
+
reason: string;
|
|
142
|
+
} | {
|
|
143
|
+
kind: "not-applicable";
|
|
144
|
+
declared: boolean;
|
|
145
|
+
reason: string;
|
|
146
|
+
preconditionId: string;
|
|
147
|
+
evidence: string[];
|
|
148
|
+
} | {
|
|
149
|
+
kind: "error";
|
|
150
|
+
reason: string;
|
|
151
|
+
} | {
|
|
152
|
+
kind: "deadline";
|
|
153
|
+
lastOutcome: "pending" | "absent";
|
|
154
|
+
reason: string;
|
|
155
|
+
};
|
|
156
|
+
export interface VerificationRun {
|
|
157
|
+
verdict: VerificationVerdict;
|
|
158
|
+
attempts: AttemptRecord[];
|
|
159
|
+
/** Paths relative to evidenceDir, in creation order. */
|
|
160
|
+
evidence: string[];
|
|
161
|
+
}
|
|
162
|
+
export declare function responseSchema(): SchemaDocument;
|
|
163
|
+
export declare function declarationSchema(): SchemaDocument;
|
|
164
|
+
/**
|
|
165
|
+
* THE SEVEN FAIL-CLOSED RULES on an adapter response (plan step 3), each
|
|
166
|
+
* corresponding to a way a verifier can be fooled. Rule 1 (exit 0 with no
|
|
167
|
+
* response written) is enforced by the caller, which owns the file's
|
|
168
|
+
* existence; rules 2 to 7 are here. The subject echo (rule 3) is checked
|
|
169
|
+
* field by field BEFORE the outcome is read, so an adapter that verified
|
|
170
|
+
* something else cannot have its outcome looked at for this subject.
|
|
171
|
+
*/
|
|
172
|
+
export type ResponseValidation = {
|
|
173
|
+
ok: true;
|
|
174
|
+
response: AdapterResponse;
|
|
175
|
+
} | {
|
|
176
|
+
ok: false;
|
|
177
|
+
rule: number;
|
|
178
|
+
reason: string;
|
|
179
|
+
};
|
|
180
|
+
export declare function validateAdapterResponse(body: string, subject: ReleaseSubject): ResponseValidation;
|
|
181
|
+
/**
|
|
182
|
+
* The encoded forms of a credential value the kernel can derive from the
|
|
183
|
+
* value alone (CR-P7H-3). Redacting only the verbatim bytes let a trivially
|
|
184
|
+
* reversible copy through: an adapter emitting the token as a standalone
|
|
185
|
+
* base64 blob (an HTTP Basic `Authorization: Basic <base64(token)>` of the
|
|
186
|
+
* token) leaked a recoverable secret into the stderr evidence. Each form here
|
|
187
|
+
* is a DETERMINISTIC, enumerable transform of the same value, so the set is
|
|
188
|
+
* bounded and cheap.
|
|
189
|
+
*
|
|
190
|
+
* WHAT THIS DOES NOT COVER, stated so a green is auditable rather than
|
|
191
|
+
* silently partial (never soften a work history): forms that fold in bytes
|
|
192
|
+
* the kernel does not hold, e.g. base64 of `"user:" + value` for a full Basic
|
|
193
|
+
* credential PAIR (the username is the project's, not the kernel's), or a
|
|
194
|
+
* value re-encoded by a transport the kernel never sees (gzip, hex, a second
|
|
195
|
+
* base64 round). Those are residue the reference adapters do not produce; a
|
|
196
|
+
* third-party adapter that composes a credential with unknown surrounding
|
|
197
|
+
* bytes owns that redaction, and the guarantee scoped here is the value and
|
|
198
|
+
* its own single-step base64 and percent encodings.
|
|
199
|
+
*/
|
|
200
|
+
export declare function secretForms(value: string): string[];
|
|
201
|
+
/**
|
|
202
|
+
* Replace every resolved credential VALUE, and its enumerable encoded forms
|
|
203
|
+
* (see secretForms), with a named placeholder, everywhere in the text.
|
|
204
|
+
*/
|
|
205
|
+
export declare function redactSecrets(text: string, secrets: readonly {
|
|
206
|
+
name: string;
|
|
207
|
+
value: string;
|
|
208
|
+
}[]): string;
|
|
209
|
+
export interface RunVerificationOptions {
|
|
210
|
+
verification: string;
|
|
211
|
+
subject: ReleaseSubject;
|
|
212
|
+
adapter: string[];
|
|
213
|
+
config: unknown;
|
|
214
|
+
clock: VerificationClock;
|
|
215
|
+
/** Absolute directory the attempt evidence is written into. */
|
|
216
|
+
evidenceDir: string;
|
|
217
|
+
/** Resolved credential values, for redaction only. Never written. */
|
|
218
|
+
secrets?: readonly {
|
|
219
|
+
name: string;
|
|
220
|
+
value: string;
|
|
221
|
+
}[];
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* THE KERNEL-OWNED LOOP. One bounded adapter observation per attempt, one
|
|
225
|
+
* record per attempt, foreground polling (C-3), verdict at the deadline.
|
|
226
|
+
* `pending` and `absent` loop; everything else is terminal, including every
|
|
227
|
+
* fail-closed violation (design decision D-p7-5: a broken adapter cannot
|
|
228
|
+
* reach a verdict, and M2-C-3 makes that `error` now, not red later).
|
|
229
|
+
*/
|
|
230
|
+
export declare function runVerification(options: RunVerificationOptions): Promise<VerificationRun>;
|
|
231
|
+
export interface DeclaredVerification {
|
|
232
|
+
mode: "none" | "adapter";
|
|
233
|
+
reason?: string;
|
|
234
|
+
adapter?: string[];
|
|
235
|
+
config?: unknown;
|
|
236
|
+
clock?: VerificationClock;
|
|
237
|
+
credentials?: string[];
|
|
238
|
+
}
|
|
239
|
+
export interface Declaration {
|
|
240
|
+
version: number;
|
|
241
|
+
repository: string;
|
|
242
|
+
integrationRef: string;
|
|
243
|
+
verifications: {
|
|
244
|
+
deploy?: DeclaredVerification;
|
|
245
|
+
migrations?: DeclaredVerification;
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
export type DeclarationLoad = {
|
|
249
|
+
ok: true;
|
|
250
|
+
declaration: Declaration;
|
|
251
|
+
/** sha256 hex of the blob bytes read from the governing ref. */
|
|
252
|
+
sha256: string;
|
|
253
|
+
/** The resolved commit sha of the governing ref. */
|
|
254
|
+
ref: string;
|
|
255
|
+
refSha: string;
|
|
256
|
+
} | {
|
|
257
|
+
ok: false;
|
|
258
|
+
reason: string;
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Read the declaration from a COMMITTED ref, never from the working tree
|
|
262
|
+
* (design decision D-p7-2; the anti-widening rule of plan step 7, same as
|
|
263
|
+
* the scope auditor's: a phase must not be able to switch off, inside its
|
|
264
|
+
* own branch, the check that would have caught it). The record carries the
|
|
265
|
+
* declaration blob's sha256 so a reviewer can see which text authorized
|
|
266
|
+
* whatever happened.
|
|
267
|
+
*/
|
|
268
|
+
export declare function loadDeclaration(ref: string, cwd: string): DeclarationLoad;
|
|
269
|
+
/**
|
|
270
|
+
* THE GATE ENTRY, shared by the two thin entry points. The manifest keeps
|
|
271
|
+
* two static entries exactly as section 1.4 declares them; this function is
|
|
272
|
+
* what each names. Returns the process exit code.
|
|
273
|
+
*
|
|
274
|
+
* The governing ref is `--base` when supplied (the anti-widening read),
|
|
275
|
+
* else HEAD: always a COMMITTED state, so the subject's mergedSha and
|
|
276
|
+
* mergedAt are the resolved commit's, and an uncommitted edit can neither
|
|
277
|
+
* enable, disable nor reconfigure a verification. R-032's blocking half:
|
|
278
|
+
* the verdict record this gate writes is keyed to that sha in its detail
|
|
279
|
+
* line, and the consumption contract is that THE NEXT DISPATCH REQUIRES A
|
|
280
|
+
* GREEN VERDICT RECORD FOR THE MERGED SHA; the enforcement is wired at M4
|
|
281
|
+
* with the pilot (M2-D-11), and M2 adds no dispatch block to spawn.
|
|
282
|
+
*/
|
|
283
|
+
export declare function runReleaseGate(name: "deploy" | "migrations", args: string[]): Promise<number>;
|