@tiphys/kernel 0.0.0 → 0.2.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 +663 -0
- package/LICENSE +202 -0
- package/assurance-modes.yaml +299 -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 +92 -0
- package/dist/src/adapters/load.d.ts +202 -0
- package/dist/src/adapters/load.js +440 -0
- package/dist/src/brief.d.ts +32 -0
- package/dist/src/brief.js +66 -0
- package/dist/src/checklists.d.ts +169 -0
- package/dist/src/checklists.js +310 -0
- package/dist/src/checks.d.ts +1539 -0
- package/dist/src/checks.js +5025 -0
- package/dist/src/cli.d.ts +9 -0
- package/dist/src/cli.js +68 -0
- package/dist/src/commands/brief.d.ts +92 -0
- package/dist/src/commands/brief.js +343 -0
- package/dist/src/commands/checklist.d.ts +42 -0
- package/dist/src/commands/checklist.js +168 -0
- package/dist/src/commands/cutover.d.ts +35 -0
- package/dist/src/commands/cutover.js +448 -0
- package/dist/src/commands/doctor.d.ts +264 -0
- package/dist/src/commands/doctor.js +1546 -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 +177 -0
- package/dist/src/commands/lock.d.ts +34 -0
- package/dist/src/commands/lock.js +340 -0
- package/dist/src/commands/mode.d.ts +40 -0
- package/dist/src/commands/mode.js +134 -0
- package/dist/src/commands/next.d.ts +130 -0
- package/dist/src/commands/next.js +597 -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 +139 -0
- package/dist/src/commands/resume.d.ts +1 -0
- package/dist/src/commands/resume.js +88 -0
- package/dist/src/commands/spawn.d.ts +1 -0
- package/dist/src/commands/spawn.js +195 -0
- package/dist/src/commands/status.d.ts +21 -0
- package/dist/src/commands/status.js +122 -0
- package/dist/src/commands/sync.d.ts +47 -0
- package/dist/src/commands/sync.js +341 -0
- package/dist/src/commands/teardown.d.ts +1 -0
- package/dist/src/commands/teardown.js +87 -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 +430 -0
- package/dist/src/commands/watch.d.ts +1 -0
- package/dist/src/commands/watch.js +172 -0
- package/dist/src/cutover.d.ts +584 -0
- package/dist/src/cutover.js +1444 -0
- package/dist/src/exclusion.d.ts +389 -0
- package/dist/src/exclusion.js +843 -0
- package/dist/src/exec/env.d.ts +278 -0
- package/dist/src/exec/env.js +334 -0
- package/dist/src/fleet.d.ts +223 -0
- package/dist/src/fleet.js +298 -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 +1169 -0
- package/dist/src/gates/coverage.d.ts +375 -0
- package/dist/src/gates/coverage.js +836 -0
- package/dist/src/gates/credentials.d.ts +233 -0
- package/dist/src/gates/credentials.js +752 -0
- package/dist/src/gates/deploy.d.ts +1 -0
- package/dist/src/gates/deploy.js +33 -0
- package/dist/src/gates/gate-classes.d.ts +56 -0
- package/dist/src/gates/gate-classes.js +633 -0
- package/dist/src/gates/manifest.d.ts +99 -0
- package/dist/src/gates/manifest.js +208 -0
- package/dist/src/gates/merge-preconditions.d.ts +319 -0
- package/dist/src/gates/merge-preconditions.js +932 -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 +482 -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 +614 -0
- package/dist/src/gates/run.js +1614 -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 +87 -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 +968 -0
- package/dist/src/gates/validate.d.ts +121 -0
- package/dist/src/gates/validate.js +414 -0
- package/dist/src/hooks.d.ts +84 -0
- package/dist/src/hooks.js +125 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.js +30 -0
- package/dist/src/liveness.d.ts +321 -0
- package/dist/src/liveness.js +396 -0
- package/dist/src/lock.d.ts +256 -0
- package/dist/src/lock.js +792 -0
- package/dist/src/model-resolution.d.ts +159 -0
- package/dist/src/model-resolution.js +307 -0
- package/dist/src/modes.d.ts +149 -0
- package/dist/src/modes.js +258 -0
- package/dist/src/path-identity.d.ts +34 -0
- package/dist/src/path-identity.js +48 -0
- package/dist/src/plan.d.ts +73 -0
- package/dist/src/plan.js +153 -0
- package/dist/src/pool.d.ts +326 -0
- package/dist/src/pool.js +988 -0
- package/dist/src/roles.d.ts +461 -0
- package/dist/src/roles.js +776 -0
- package/dist/src/spawn.d.ts +482 -0
- package/dist/src/spawn.js +1003 -0
- package/dist/src/status.d.ts +116 -0
- package/dist/src/status.js +148 -0
- package/dist/src/task.d.ts +504 -0
- package/dist/src/task.js +307 -0
- package/dist/src/teardown.d.ts +39 -0
- package/dist/src/teardown.js +422 -0
- package/dist/src/tuition.d.ts +159 -0
- package/dist/src/tuition.js +311 -0
- package/dist/src/validate.d.ts +263 -0
- package/dist/src/validate.js +742 -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 +849 -0
- package/dist/src/witness/run.d.ts +299 -0
- package/dist/src/witness/run.js +1373 -0
- package/dist/src/witness/spec.d.ts +270 -0
- package/dist/src/witness/spec.js +475 -0
- package/dist/tsconfig.src.tsbuildinfo +1 -0
- package/gate-registry.yaml +526 -0
- package/gates.manifest.json +257 -0
- package/package.json +72 -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 +463 -0
- package/roles/investigator.md +138 -0
- package/roles/plan-writer.md +95 -0
- package/schemas/README.md +82 -0
- package/schemas/assurance-modes.schema.json +264 -0
- package/schemas/charter.schema.json +185 -0
- package/schemas/checklist.schema.json +114 -0
- package/schemas/cutover-state.schema.json +64 -0
- package/schemas/decision-record.schema.json +88 -0
- package/schemas/executor-record.schema.json +36 -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/model-resolution.schema.json +362 -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 +295 -0
- package/schemas/work-history.schema.json +183 -0
- package/schemas/write-bypass.schema.json +69 -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,99 @@
|
|
|
1
|
+
import type { SchemaDocument } from "./validate.ts";
|
|
2
|
+
/**
|
|
3
|
+
* THE GATE MANIFEST (kernel plan M2, M2-P1 step 6).
|
|
4
|
+
*
|
|
5
|
+
* One manifest per repository. It declares which gates exist, how each is
|
|
6
|
+
* invoked, what one unit of its work is, whether an unmet precondition
|
|
7
|
+
* fails the run, and which commands can destroy work.
|
|
8
|
+
*
|
|
9
|
+
* SCHEMA RESOLUTION FROM SOURCE AND FROM dist/ (step 5). The schema
|
|
10
|
+
* documents sit beside this module at `./schemas/*.json` and are resolved
|
|
11
|
+
* through `import.meta.url`, so the same code finds
|
|
12
|
+
* `src/gates/schemas/...` when run from source and
|
|
13
|
+
* `dist/src/gates/schemas/...` when run from the compiled entry. `tsc` does
|
|
14
|
+
* not copy JSON, which was MEASURED rather than assumed (the measurement is
|
|
15
|
+
* in the work history), so the build script copies the directory into
|
|
16
|
+
* `dist/` and criterion 10 witnesses both documents in `npm pack` output.
|
|
17
|
+
* They are deliberately NOT imported as modules: `resolveJsonModule` is off
|
|
18
|
+
* in both tsconfigs, and turning it on would inline the schema into the
|
|
19
|
+
* bundle and make the shipped document and the enforced document two
|
|
20
|
+
* different things.
|
|
21
|
+
*
|
|
22
|
+
* M2-C-6: the manifest path comes from the caller, so it is read through the
|
|
23
|
+
* DELIVERED `readRegularFileIfPresent`. A named pipe at the manifest path
|
|
24
|
+
* reports `error` naming the path and the type, and never blocks.
|
|
25
|
+
*/
|
|
26
|
+
export type PreconditionKind = "file-exists" | "file-absent" | "branch-matches" | "diff-touches" | "command-exit-zero";
|
|
27
|
+
export declare const PRECONDITION_KINDS: readonly PreconditionKind[];
|
|
28
|
+
export type RunParameter = "base" | "head" | "phase";
|
|
29
|
+
export interface PreconditionSpec {
|
|
30
|
+
id: string;
|
|
31
|
+
kind: PreconditionKind;
|
|
32
|
+
path?: string;
|
|
33
|
+
pattern?: string;
|
|
34
|
+
paths?: string[];
|
|
35
|
+
command?: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface GateEntry {
|
|
38
|
+
id: string;
|
|
39
|
+
command: string[];
|
|
40
|
+
unitLabel: string;
|
|
41
|
+
applicability: "required" | "conditional";
|
|
42
|
+
parameters?: RunParameter[];
|
|
43
|
+
precondition?: PreconditionSpec;
|
|
44
|
+
modes?: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface GateManifest {
|
|
47
|
+
version: number;
|
|
48
|
+
gates: GateEntry[];
|
|
49
|
+
destructiveCommands: string[];
|
|
50
|
+
}
|
|
51
|
+
export type ManifestLoad = {
|
|
52
|
+
ok: true;
|
|
53
|
+
manifest: GateManifest;
|
|
54
|
+
sha256: string;
|
|
55
|
+
body: string;
|
|
56
|
+
} | {
|
|
57
|
+
ok: false;
|
|
58
|
+
reason: string;
|
|
59
|
+
diagnostics: string[];
|
|
60
|
+
};
|
|
61
|
+
export declare function manifestSchema(): SchemaDocument;
|
|
62
|
+
export declare function resultSchema(): SchemaDocument;
|
|
63
|
+
/**
|
|
64
|
+
* Absolute paths of every schema document `manifest-self-check` validates.
|
|
65
|
+
*
|
|
66
|
+
* ENUMERATED, NOT ENUMERABLE-BY-HAND (fix round, M2-P1). This milestone's
|
|
67
|
+
* plan (kernel-plan-v1.md) adds four more schema documents to this same
|
|
68
|
+
* directory across M2-P2, M2-P4, M2-P5, M2-P6 and M2-P7. A fixed two-entry
|
|
69
|
+
* list here was the mechanism, not a fluke: it named the two documents that
|
|
70
|
+
* existed at the time this function was written and would keep returning
|
|
71
|
+
* exactly those two forever, so manifest-self-check would silently stop
|
|
72
|
+
* validating every document a later phase ships, while test/gates.test.ts's
|
|
73
|
+
* independent readdir-based parity check reddens correctly the moment a
|
|
74
|
+
* phase adds one (CR-812's test, `test/gates.test.ts` near line 2378). The
|
|
75
|
+
* fix is to make the SAME enumeration the source of truth here: read the
|
|
76
|
+
* directory beside this module, keep entries matching `*.schema.json`, and
|
|
77
|
+
* sort them by filename so the order is deterministic across platforms and
|
|
78
|
+
* across runs (readdir order is not guaranteed by POSIX). Every document a
|
|
79
|
+
* phase drops into `src/gates/schemas/` is then validated automatically,
|
|
80
|
+
* and the parity property the test checks becomes structural rather than
|
|
81
|
+
* a thing two call sites have to be kept in sync by hand.
|
|
82
|
+
*/
|
|
83
|
+
export declare function schemaDocumentPaths(): string[];
|
|
84
|
+
/**
|
|
85
|
+
* Validate an already-parsed manifest document. Returns the diagnostics in
|
|
86
|
+
* the contract's deterministic order (by pointer, then message).
|
|
87
|
+
*/
|
|
88
|
+
export declare function validateManifestDocument(document: unknown): string[];
|
|
89
|
+
/** Validate an already-parsed gate result record. */
|
|
90
|
+
export declare function validateResultDocument(document: unknown): string[];
|
|
91
|
+
/**
|
|
92
|
+
* Load and validate a manifest from a path the caller supplied.
|
|
93
|
+
*
|
|
94
|
+
* Fail closed at every step: not a regular file, absent, unparseable, or
|
|
95
|
+
* schema-invalid all produce `ok: false` with a reason. None of them
|
|
96
|
+
* produces a manifest with fewer gates in it, which is the failure this
|
|
97
|
+
* whole milestone exists to make impossible.
|
|
98
|
+
*/
|
|
99
|
+
export declare function loadManifest(path: string): ManifestLoad;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdirSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { readRegularFileIfPresent } from "../task.js";
|
|
5
|
+
import { DIAGNOSTIC_MESSAGES, formatDiagnostics, loadSchema, validate, } from "./validate.js";
|
|
6
|
+
export const PRECONDITION_KINDS = [
|
|
7
|
+
"file-exists",
|
|
8
|
+
"file-absent",
|
|
9
|
+
"branch-matches",
|
|
10
|
+
"diff-touches",
|
|
11
|
+
"command-exit-zero",
|
|
12
|
+
];
|
|
13
|
+
const schemaDirectory = new URL("./schemas/", import.meta.url);
|
|
14
|
+
function readSchemaDocument(name) {
|
|
15
|
+
const path = fileURLToPath(new URL(name, schemaDirectory));
|
|
16
|
+
const read = readRegularFileIfPresent(path);
|
|
17
|
+
if (read.kind !== "read") {
|
|
18
|
+
throw new Error(read.kind === "absent"
|
|
19
|
+
? `schema document ${path} is missing from this installation`
|
|
20
|
+
: read.reason);
|
|
21
|
+
}
|
|
22
|
+
const parsed = JSON.parse(read.body);
|
|
23
|
+
const loaded = loadSchema(parsed, name);
|
|
24
|
+
if (!loaded.ok) {
|
|
25
|
+
throw new Error(loaded.reason);
|
|
26
|
+
}
|
|
27
|
+
return loaded.schema;
|
|
28
|
+
}
|
|
29
|
+
/** The two schema documents this milestone ships, loaded once. */
|
|
30
|
+
let cachedManifestSchema;
|
|
31
|
+
let cachedResultSchema;
|
|
32
|
+
export function manifestSchema() {
|
|
33
|
+
if (cachedManifestSchema === undefined) {
|
|
34
|
+
cachedManifestSchema = readSchemaDocument("gate-manifest.schema.json");
|
|
35
|
+
}
|
|
36
|
+
return cachedManifestSchema;
|
|
37
|
+
}
|
|
38
|
+
export function resultSchema() {
|
|
39
|
+
if (cachedResultSchema === undefined) {
|
|
40
|
+
cachedResultSchema = readSchemaDocument("gate-result.schema.json");
|
|
41
|
+
}
|
|
42
|
+
return cachedResultSchema;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Absolute paths of every schema document `manifest-self-check` validates.
|
|
46
|
+
*
|
|
47
|
+
* ENUMERATED, NOT ENUMERABLE-BY-HAND (fix round, M2-P1). This milestone's
|
|
48
|
+
* plan (kernel-plan-v1.md) adds four more schema documents to this same
|
|
49
|
+
* directory across M2-P2, M2-P4, M2-P5, M2-P6 and M2-P7. A fixed two-entry
|
|
50
|
+
* list here was the mechanism, not a fluke: it named the two documents that
|
|
51
|
+
* existed at the time this function was written and would keep returning
|
|
52
|
+
* exactly those two forever, so manifest-self-check would silently stop
|
|
53
|
+
* validating every document a later phase ships, while test/gates.test.ts's
|
|
54
|
+
* independent readdir-based parity check reddens correctly the moment a
|
|
55
|
+
* phase adds one (CR-812's test, `test/gates.test.ts` near line 2378). The
|
|
56
|
+
* fix is to make the SAME enumeration the source of truth here: read the
|
|
57
|
+
* directory beside this module, keep entries matching `*.schema.json`, and
|
|
58
|
+
* sort them by filename so the order is deterministic across platforms and
|
|
59
|
+
* across runs (readdir order is not guaranteed by POSIX). Every document a
|
|
60
|
+
* phase drops into `src/gates/schemas/` is then validated automatically,
|
|
61
|
+
* and the parity property the test checks becomes structural rather than
|
|
62
|
+
* a thing two call sites have to be kept in sync by hand.
|
|
63
|
+
*/
|
|
64
|
+
export function schemaDocumentPaths() {
|
|
65
|
+
const directory = fileURLToPath(schemaDirectory);
|
|
66
|
+
return readdirSync(directory)
|
|
67
|
+
.filter((name) => name.endsWith(".schema.json"))
|
|
68
|
+
.sort()
|
|
69
|
+
.map((name) => fileURLToPath(new URL(name, schemaDirectory)));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The kind-specific required fields, checked here rather than in the schema.
|
|
73
|
+
*
|
|
74
|
+
* WHY NOT IN THE SCHEMA. Expressing "kind file-exists requires path" needs
|
|
75
|
+
* conditional composition (`if`/`then`, or `oneOf`), and both are outside
|
|
76
|
+
* M2-D-04's closed keyword set, which DR-0013 decided against growing. So
|
|
77
|
+
* the check lives in code and emits the SAME `INVALID <pointer> <message>`
|
|
78
|
+
* diagnostics, which keeps one diagnostic contract rather than two. When
|
|
79
|
+
* M3-P1 swaps in Ajv the constraint can move into the schema without any
|
|
80
|
+
* consumer noticing, because the output line is unchanged.
|
|
81
|
+
*/
|
|
82
|
+
const REQUIRED_FIELD_FOR_KIND = {
|
|
83
|
+
"file-exists": "path",
|
|
84
|
+
"file-absent": "path",
|
|
85
|
+
"branch-matches": "pattern",
|
|
86
|
+
"diff-touches": "paths",
|
|
87
|
+
"command-exit-zero": "command",
|
|
88
|
+
};
|
|
89
|
+
function preconditionFieldDiagnostics(manifest) {
|
|
90
|
+
const found = [];
|
|
91
|
+
const gates = manifest.gates;
|
|
92
|
+
if (!Array.isArray(gates)) {
|
|
93
|
+
return found;
|
|
94
|
+
}
|
|
95
|
+
for (let index = 0; index < gates.length; index += 1) {
|
|
96
|
+
const gate = gates[index];
|
|
97
|
+
const precondition = gate?.precondition;
|
|
98
|
+
if (precondition === undefined || precondition === null) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const kind = precondition["kind"];
|
|
102
|
+
if (typeof kind !== "string" || !PRECONDITION_KINDS.includes(kind)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const field = REQUIRED_FIELD_FOR_KIND[kind];
|
|
106
|
+
if (precondition[field] === undefined) {
|
|
107
|
+
found.push({
|
|
108
|
+
pointer: `#/gates/${index}/precondition/${field}`,
|
|
109
|
+
message: DIAGNOSTIC_MESSAGES.required(field),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return found;
|
|
114
|
+
}
|
|
115
|
+
/** Every duplicate gate id, as diagnostics. A registry keyed by name cannot hold two. */
|
|
116
|
+
function duplicateIdDiagnostics(manifest) {
|
|
117
|
+
const found = [];
|
|
118
|
+
const gates = manifest.gates;
|
|
119
|
+
if (!Array.isArray(gates)) {
|
|
120
|
+
return found;
|
|
121
|
+
}
|
|
122
|
+
const seen = new Set();
|
|
123
|
+
for (let index = 0; index < gates.length; index += 1) {
|
|
124
|
+
const id = gates[index]?.id;
|
|
125
|
+
if (typeof id !== "string") {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (seen.has(id)) {
|
|
129
|
+
found.push({
|
|
130
|
+
pointer: `#/gates/${index}/id`,
|
|
131
|
+
message: DIAGNOSTIC_MESSAGES.duplicateId(JSON.stringify(id)),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
seen.add(id);
|
|
135
|
+
}
|
|
136
|
+
return found;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Validate an already-parsed manifest document. Returns the diagnostics in
|
|
140
|
+
* the contract's deterministic order (by pointer, then message).
|
|
141
|
+
*/
|
|
142
|
+
export function validateManifestDocument(document) {
|
|
143
|
+
const diagnostics = [
|
|
144
|
+
...validate(manifestSchema(), document),
|
|
145
|
+
...preconditionFieldDiagnostics(document),
|
|
146
|
+
...duplicateIdDiagnostics(document),
|
|
147
|
+
];
|
|
148
|
+
diagnostics.sort((a, b) => {
|
|
149
|
+
if (a.pointer !== b.pointer) {
|
|
150
|
+
return a.pointer < b.pointer ? -1 : 1;
|
|
151
|
+
}
|
|
152
|
+
if (a.message === b.message) {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
return a.message < b.message ? -1 : 1;
|
|
156
|
+
});
|
|
157
|
+
return formatDiagnostics(diagnostics);
|
|
158
|
+
}
|
|
159
|
+
/** Validate an already-parsed gate result record. */
|
|
160
|
+
export function validateResultDocument(document) {
|
|
161
|
+
return formatDiagnostics(validate(resultSchema(), document));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Load and validate a manifest from a path the caller supplied.
|
|
165
|
+
*
|
|
166
|
+
* Fail closed at every step: not a regular file, absent, unparseable, or
|
|
167
|
+
* schema-invalid all produce `ok: false` with a reason. None of them
|
|
168
|
+
* produces a manifest with fewer gates in it, which is the failure this
|
|
169
|
+
* whole milestone exists to make impossible.
|
|
170
|
+
*/
|
|
171
|
+
export function loadManifest(path) {
|
|
172
|
+
const read = readRegularFileIfPresent(path);
|
|
173
|
+
if (read.kind === "absent") {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
reason: `manifest ${path} does not exist`,
|
|
177
|
+
diagnostics: [],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (read.kind === "refused") {
|
|
181
|
+
return { ok: false, reason: read.reason, diagnostics: [] };
|
|
182
|
+
}
|
|
183
|
+
let parsed;
|
|
184
|
+
try {
|
|
185
|
+
parsed = JSON.parse(read.body);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
reason: `manifest ${path} does not parse as JSON: ${error.message}`,
|
|
191
|
+
diagnostics: [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const diagnostics = validateManifestDocument(parsed);
|
|
195
|
+
if (diagnostics.length > 0) {
|
|
196
|
+
return {
|
|
197
|
+
ok: false,
|
|
198
|
+
reason: `manifest ${path} is not a valid gate manifest`,
|
|
199
|
+
diagnostics,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
manifest: parsed,
|
|
205
|
+
sha256: createHash("sha256").update(read.body).digest("hex"),
|
|
206
|
+
body: read.body,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import type { GateStatus } from "./result.ts";
|
|
2
|
+
/**
|
|
3
|
+
* THE MERGE PRECONDITION READER (kernel plan M4, M4-P12; DR-0012, DR-0036,
|
|
4
|
+
* T-009, R-064, R-065a).
|
|
5
|
+
*
|
|
6
|
+
* WHAT THIS IS NOT. It is not a merge command. M4-D-09 puts the merge
|
|
7
|
+
* capability in the plugin at cutover and DR-0036 keeps merge authority with
|
|
8
|
+
* the current process for the whole of M4. What is missing today is not the
|
|
9
|
+
* ability to merge; it is any ARTIFACT saying the six conditions of
|
|
10
|
+
* delivery/decisions/DR-0012-delegated-merge-authority.md:22 to :27 held at the
|
|
11
|
+
* head that was merged. So this gate produces one ROW PER CONDITION, each
|
|
12
|
+
* carrying the head sha it was evaluated against, and the orchestrator reads it
|
|
13
|
+
* before merging, by hand.
|
|
14
|
+
*
|
|
15
|
+
* THE HAZARD CLASS, in the plan's words: A MERGE PRECONDITION CHECK THAT IS
|
|
16
|
+
* GREEN BECAUSE IT COULD NOT LOOK. Every arm below is written against one of
|
|
17
|
+
* its members, and each member is named where it is refused rather than in a
|
|
18
|
+
* list nobody rereads:
|
|
19
|
+
*
|
|
20
|
+
* a 401 piped into a `|| true` -> `probeApi` runs FIRST and an
|
|
21
|
+
* unreachable or refusing API is
|
|
22
|
+
* `error` with units 0. CLAUDE.md
|
|
23
|
+
* standing warning 6 records this shape
|
|
24
|
+
* costing a whole watcher.
|
|
25
|
+
* an API failure called N/A -> `not-applicable` is reachable from
|
|
26
|
+
* exactly one place here, the
|
|
27
|
+
* NO-VERDICT-AT-THIS-HEAD arm, and it
|
|
28
|
+
* carries an evaluated precondition
|
|
29
|
+
* record (SC-011).
|
|
30
|
+
* CI-green read off the BRANCH -> condition 4 compares
|
|
31
|
+
* `check_run.head_sha` against the head
|
|
32
|
+
* under evaluation and reddens when they
|
|
33
|
+
* differ, which is T-009 one scope down.
|
|
34
|
+
* scope satisfied by a record EXISTING -> condition 5 reads the record's
|
|
35
|
+
* STATUS word.
|
|
36
|
+
* arbitration satisfied by a file EXISTING -> condition 6 requires the
|
|
37
|
+
* document to name BOTH verdicts and the
|
|
38
|
+
* SAME head.
|
|
39
|
+
* a ruleset read as a default -> an empty body from the ruleset API is
|
|
40
|
+
* `error`, never an assumed shape.
|
|
41
|
+
*
|
|
42
|
+
* WHY CONDITIONS 1 AND 2 ARE COMPOSED AND NOT REIMPLEMENTED (plan step 6).
|
|
43
|
+
* M4-P10 shipped `dual-review-decorrelation` (DR-0012 condition 1) and
|
|
44
|
+
* `verdict-pair-approves` (condition 2) as derived checks in `src/checks.ts`,
|
|
45
|
+
* and `scripts/check-dual-review.mjs` is the runner around them. This gate is a
|
|
46
|
+
* SECOND CALLER of the same exported primitives rather than a second copy of
|
|
47
|
+
* the rules: it resolves the corpus with `resolveCorpusSource`, refuses a
|
|
48
|
+
* context whose delivery regime is undeterminable with `missingRegimeDocument`,
|
|
49
|
+
* reads the DR-0038 declaration with `readReviewFamilies`, and then runs the
|
|
50
|
+
* two checks BY ID out of `registeredChecks()`.
|
|
51
|
+
*
|
|
52
|
+
* BY ID, AND A MISSING REGISTRATION IS `error`. `scripts/check-dual-review.mjs`
|
|
53
|
+
* prints `0 registered check(s) named X` beside its own verdict, which is the
|
|
54
|
+
* Kind B witness that deregistering the check is visible. On the MERGE path a
|
|
55
|
+
* condition with no check behind it is a condition nobody evaluated, so here it
|
|
56
|
+
* is `error` and never a green with the comparison quietly skipped.
|
|
57
|
+
*
|
|
58
|
+
* WHY THE ROW STATUS AND THE GATE STATUS ARE NOT THE SAME WORD, and this is the
|
|
59
|
+
* one place the plan and the criteria say different things, so the reconciliation
|
|
60
|
+
* is written down rather than left to a reader. Plan step 4
|
|
61
|
+
* (delivery/plan/kernel-plan-m4.md:1911) says an ABSENT scope record is `error`;
|
|
62
|
+
* criterion 4 (delivery/plan/kernel-plan-m4.md:1938) says condition 5 is RED when
|
|
63
|
+
* no record exists. Both hold at once because they speak about different objects:
|
|
64
|
+
* the CONDITION is unsatisfied either way and its row reads `red` with a reason
|
|
65
|
+
* naming which arm it was, while the GATE could not reach a verdict about an
|
|
66
|
+
* instrument that is not there, so the gate's own status is `error`. Everything a
|
|
67
|
+
* reader needs is printed: the row, its reason, and the gate word.
|
|
68
|
+
*/
|
|
69
|
+
declare const GATE_ID = "merge-preconditions";
|
|
70
|
+
declare const UNIT_LABEL = "merge preconditions evaluated";
|
|
71
|
+
/**
|
|
72
|
+
* The id of the one precondition this gate can report unmet.
|
|
73
|
+
*
|
|
74
|
+
* SC-011: `not-applicable` ASSERTS that a precondition was evaluated. The
|
|
75
|
+
* precondition here is "a merge is being proposed at this head", evidenced by
|
|
76
|
+
* at least one committed verdict document naming it. A head with no such
|
|
77
|
+
* document is not a merge waiting on six conditions; it is a branch nobody has
|
|
78
|
+
* reviewed yet, and reporting red for that would make the gate unusable on
|
|
79
|
+
* every push while making it say something false.
|
|
80
|
+
*/
|
|
81
|
+
declare const PRECONDITION_ID = "merge-preconditions-verdict-names-this-head";
|
|
82
|
+
interface Flags {
|
|
83
|
+
result?: string;
|
|
84
|
+
evidence?: string;
|
|
85
|
+
head?: string;
|
|
86
|
+
phase?: string;
|
|
87
|
+
context?: string;
|
|
88
|
+
repo?: string;
|
|
89
|
+
"api-base"?: string;
|
|
90
|
+
"scope-record"?: string;
|
|
91
|
+
arbitrations?: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* ROW STATUS IS THE SAME FOUR-WORD VOCABULARY MINUS `not-applicable`.
|
|
95
|
+
* A row cannot be not-applicable: the gate as a whole is, or every condition
|
|
96
|
+
* was evaluated. Keeping the words identical to `GateStatus` is deliberate, so
|
|
97
|
+
* a reader does not have to learn a second vocabulary for the thing the gate
|
|
98
|
+
* exists to print.
|
|
99
|
+
*/
|
|
100
|
+
export type RowStatus = "green" | "red" | "error";
|
|
101
|
+
export interface ConditionRow {
|
|
102
|
+
/** `condition-1` .. `condition-6`, or `branch-protection`. */
|
|
103
|
+
id: string;
|
|
104
|
+
/** The DR-0012 clause, or the requirement id, this row is about. */
|
|
105
|
+
clause: string;
|
|
106
|
+
status: RowStatus;
|
|
107
|
+
/** The head sha this row was evaluated against. Criterion 8 wants it here. */
|
|
108
|
+
head: string;
|
|
109
|
+
/** One sentence, printed on EVERY arm including green. */
|
|
110
|
+
sentence: string;
|
|
111
|
+
}
|
|
112
|
+
export declare function renderRow(row: ConditionRow): string;
|
|
113
|
+
/**
|
|
114
|
+
* The gate word for a set of rows.
|
|
115
|
+
*
|
|
116
|
+
* `error` DOMINATES `red`, which is M2-C-3's direction: a run that could not
|
|
117
|
+
* look at one condition has not reached a verdict about the merge, and a red
|
|
118
|
+
* would be a verdict. A red that is also accompanied by an error still reports
|
|
119
|
+
* error, and both rows print either way, so nothing is hidden by the ordering.
|
|
120
|
+
*/
|
|
121
|
+
export declare function gateStatusForRows(rows: readonly ConditionRow[]): GateStatus;
|
|
122
|
+
export type ApiResponse = {
|
|
123
|
+
ok: true;
|
|
124
|
+
status: number;
|
|
125
|
+
body: string;
|
|
126
|
+
} | {
|
|
127
|
+
ok: false;
|
|
128
|
+
reason: string;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* ONE REQUEST, AND THE FAILURE ARM IS WRITTEN FIRST.
|
|
132
|
+
*
|
|
133
|
+
* CLAUDE.md standing warning 6 records the exact defect this shape exists
|
|
134
|
+
* against: a watcher whose failure arm was a `.catch(() => {})` emitted nothing
|
|
135
|
+
* and was indistinguishable from a run still in progress. There is no catch
|
|
136
|
+
* here that returns a value the caller can mistake for data: a transport
|
|
137
|
+
* failure becomes `{ok: false, reason}` and every caller turns that into
|
|
138
|
+
* `error`.
|
|
139
|
+
*
|
|
140
|
+
* NO CREDENTIAL IS READ, AND THAT IS A RULE RATHER THAN AN OVERSIGHT.
|
|
141
|
+
* test/m2-exit-test.test.ts:425 asserts, by grepping every file under
|
|
142
|
+
* `src/gates/`, that no production gate reads a LITERAL-NAMED environment
|
|
143
|
+
* variable, because such a read is an ambient switch that changes a gate's
|
|
144
|
+
* reported status with nothing in the record to say so. An earlier draft of
|
|
145
|
+
* this module read `GH_TOKEN` and that test caught it, which is the guard
|
|
146
|
+
* working. Measured 2026-09-17 from this container, with NO Authorization
|
|
147
|
+
* header at all: `GET /repos/{slug}`, `GET /repos/{slug}/rulesets` and
|
|
148
|
+
* `GET /repos/{slug}/commits/{sha}/check-runs` each answered HTTP 200, because
|
|
149
|
+
* the agent proxy substitutes credentials on the way out and the value in
|
|
150
|
+
* `GH_TOKEN` is irrelevant (CLAUDE.md standing warning 6's invalid-token
|
|
151
|
+
* control measures the same thing). WHAT THIS COSTS, recorded rather than left
|
|
152
|
+
* to be found: in a deployment where the API genuinely requires a credential,
|
|
153
|
+
* every request here answers 401 or 404 and the gate reports `error`. That is
|
|
154
|
+
* the fail-closed direction and never a silent pass, and supplying a token
|
|
155
|
+
* would have to be a DECLARED FLAG in the registry command rather than an
|
|
156
|
+
* ambient environment read.
|
|
157
|
+
*/
|
|
158
|
+
export declare function requestJson(url: string): Promise<ApiResponse>;
|
|
159
|
+
export type JsonReading = {
|
|
160
|
+
ok: true;
|
|
161
|
+
value: unknown;
|
|
162
|
+
} | {
|
|
163
|
+
ok: false;
|
|
164
|
+
reason: string;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* PARSE A RESPONSE BODY, AND AN EMPTY ONE IS A FAILURE RATHER THAN A DEFAULT.
|
|
168
|
+
*
|
|
169
|
+
* Criterion 6 is the whole reason this is a named function: the ruleset API
|
|
170
|
+
* answering with a zero-length body must produce `error`, because a default
|
|
171
|
+
* here is the silent pass this phase exists against. `JSON.parse("")` throws,
|
|
172
|
+
* so the empty case would reach the same place anyway; it is separated out so
|
|
173
|
+
* the REASON a reader is given names the emptiness rather than a parser
|
|
174
|
+
* message that says nothing about what happened.
|
|
175
|
+
*/
|
|
176
|
+
export declare function readJsonBody(url: string, response: ApiResponse): JsonReading;
|
|
177
|
+
/**
|
|
178
|
+
* `owner/name` out of a git remote URL, or undefined when the URL is not one.
|
|
179
|
+
*
|
|
180
|
+
* DERIVED RATHER THAN WRITTEN INTO THE REGISTRY, and the reason is DR-0029: the
|
|
181
|
+
* kernel is just another project under the scheme, so a registry entry naming
|
|
182
|
+
* ONE repository would be the kernel's registry claiming to be everybody's. The
|
|
183
|
+
* remote is the fact this gate is actually about.
|
|
184
|
+
*/
|
|
185
|
+
export declare function slugFromRemote(url: string): string | undefined;
|
|
186
|
+
interface CheckRun {
|
|
187
|
+
name?: unknown;
|
|
188
|
+
status?: unknown;
|
|
189
|
+
conclusion?: unknown;
|
|
190
|
+
head_sha?: unknown;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Condition 4, and the comparison that makes it worth having.
|
|
194
|
+
*
|
|
195
|
+
* T-009 ONE SCOPE DOWN. "CI is green" is never a complete sentence: the
|
|
196
|
+
* complete one names the event and the HEAD SHA. A check that asked the API for
|
|
197
|
+
* the newest run on the BRANCH would report green off a run for an earlier
|
|
198
|
+
* head, which is exactly the state this repository spent four hours and
|
|
199
|
+
* twenty-one minutes in. So every check run the API returns is compared on
|
|
200
|
+
* `head_sha` and one that names a different commit is reported as the different
|
|
201
|
+
* commit it is, never counted.
|
|
202
|
+
*/
|
|
203
|
+
export declare function judgeCheckRuns(head: string, runs: readonly CheckRun[], context: string): {
|
|
204
|
+
ok: boolean;
|
|
205
|
+
sentence: string;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Condition 5, and the distinction the plan's hazard list names: the condition
|
|
209
|
+
* is satisfied by the record's STATUS, never by the record's EXISTENCE.
|
|
210
|
+
*
|
|
211
|
+
* The absent arm and the red arm are DIFFERENT REASONS on purpose (criterion
|
|
212
|
+
* 4). `instrument` is what the caller turns into the gate's own word: an absent
|
|
213
|
+
* record means the gate could not look, which is `error` at gate level while
|
|
214
|
+
* the condition itself is unsatisfied.
|
|
215
|
+
*/
|
|
216
|
+
export declare function judgeScopeRecord(path: string, read: {
|
|
217
|
+
kind: "read";
|
|
218
|
+
body: string;
|
|
219
|
+
} | {
|
|
220
|
+
kind: "absent";
|
|
221
|
+
} | {
|
|
222
|
+
kind: "refused";
|
|
223
|
+
reason: string;
|
|
224
|
+
}): {
|
|
225
|
+
ok: boolean;
|
|
226
|
+
sentence: string;
|
|
227
|
+
instrument: "present" | "missing";
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Condition 6, and EXISTENCE IS NOT THE TEST.
|
|
231
|
+
*
|
|
232
|
+
* Criterion 5 asks for two structurally different members of that class, and
|
|
233
|
+
* they are structurally different because they fail on different halves of the
|
|
234
|
+
* same document: one that names only ONE of the two verdicts has read half the
|
|
235
|
+
* evidence, and one that names a DIFFERENT HEAD has read the right number of
|
|
236
|
+
* documents about the wrong commit. A check that tested for the file's presence
|
|
237
|
+
* passes both.
|
|
238
|
+
*
|
|
239
|
+
* VERDICTS ARE IDENTIFIED BY FILE NAME because `schemas/verdict.schema.json`
|
|
240
|
+
* gives a verdict no id of its own; its required keys are kind, phase, head,
|
|
241
|
+
* verdict, produced-by, framing, review-contract, findings, criteria and
|
|
242
|
+
* deviations-judged. The existing arbitration documents in `delivery/review/`
|
|
243
|
+
* already cite their reviews by path, so the convention that exists is read
|
|
244
|
+
* rather than a field invented.
|
|
245
|
+
*
|
|
246
|
+
* THE FILE NAME AND NOT THE FULL PATH, and that is a measured correction rather
|
|
247
|
+
* than a preference. `loadCommittedVerdicts` returns ABSOLUTE paths when it
|
|
248
|
+
* falls back to the worktree and repository-relative ones when it reads a
|
|
249
|
+
* commit, so comparing whole paths made the same document resolve or not
|
|
250
|
+
* resolve depending on which corpus source happened to be chosen. The file name
|
|
251
|
+
* is the part that is stable across both, and it is the part an arbitration
|
|
252
|
+
* document's `- reviews:` line contains either way.
|
|
253
|
+
*/
|
|
254
|
+
export declare function judgeArbitration(path: string, head: string, verdictPaths: readonly string[], read: {
|
|
255
|
+
kind: "read";
|
|
256
|
+
body: string;
|
|
257
|
+
} | {
|
|
258
|
+
kind: "absent";
|
|
259
|
+
} | {
|
|
260
|
+
kind: "refused";
|
|
261
|
+
reason: string;
|
|
262
|
+
}): {
|
|
263
|
+
ok: boolean;
|
|
264
|
+
sentence: string;
|
|
265
|
+
};
|
|
266
|
+
interface RulesetRule {
|
|
267
|
+
type?: unknown;
|
|
268
|
+
parameters?: Record<string, unknown>;
|
|
269
|
+
}
|
|
270
|
+
export interface RulesetReading {
|
|
271
|
+
id: string;
|
|
272
|
+
name: string;
|
|
273
|
+
enforcement: string;
|
|
274
|
+
rules: RulesetRule[];
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The ruleset encoding, and the TWO members of criterion 7.
|
|
278
|
+
*
|
|
279
|
+
* `enforcement: disabled` is PRESENT-BUT-TOOTHLESS and a
|
|
280
|
+
* `required_status_checks` rule that does not name `gates` is
|
|
281
|
+
* PRESENT-BUT-WRONG. They are different failures and they are reported with
|
|
282
|
+
* different sentences, because a reader told only "the ruleset is wrong" has to
|
|
283
|
+
* go and find out which.
|
|
284
|
+
*
|
|
285
|
+
* R-065a IS DATA, NOT A VERDICT. Squash-only is an OWNER action the owner has
|
|
286
|
+
* deferred; the plan (step 7) says report its state and do not judge it, so
|
|
287
|
+
* `allowed_merge_methods` is printed and never turns this row red.
|
|
288
|
+
*/
|
|
289
|
+
export declare function judgeRulesets(rulesets: readonly RulesetReading[]): {
|
|
290
|
+
ok: boolean;
|
|
291
|
+
sentence: string;
|
|
292
|
+
};
|
|
293
|
+
export interface VerdictForHead {
|
|
294
|
+
path: string;
|
|
295
|
+
record: Record<string, unknown>;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Condition 3, and what it DOES and DOES NOT establish.
|
|
299
|
+
*
|
|
300
|
+
* DR-0012 condition 3 (delivery/decisions/DR-0012-delegated-merge-authority.md:24)
|
|
301
|
+
* says both reviewers were given the phase's acceptance criteria as their
|
|
302
|
+
* contract and both WALKED OR EXECUTED them. What is reachable from the verdict
|
|
303
|
+
* documents alone is that each one declares a `review-contract` and carries a
|
|
304
|
+
* non-empty `criteria[]` whose entries each record `met`. Whether that walk
|
|
305
|
+
* COVERS every acceptance criterion the plan declares is a comparison against a
|
|
306
|
+
* different document and it is the shipped Kind B check
|
|
307
|
+
* `verdict-criteria-complete`, which resolves a `plan.yaml` out of its context.
|
|
308
|
+
* This repository has no such document, so running it here would make this row
|
|
309
|
+
* permanently error about the instrument rather than about the merge. The row's
|
|
310
|
+
* sentence therefore says which half it established, and the other half is
|
|
311
|
+
* recorded as residue in delivery/work-history/m4-p12.md rather than implied.
|
|
312
|
+
*/
|
|
313
|
+
export declare function judgeCriteriaWalked(verdicts: readonly VerdictForHead[]): {
|
|
314
|
+
ok: boolean;
|
|
315
|
+
sentence: string;
|
|
316
|
+
};
|
|
317
|
+
export declare function runGate(flags: Flags): Promise<number>;
|
|
318
|
+
export declare function main(argv: string[]): Promise<number>;
|
|
319
|
+
export { GATE_ID, PRECONDITION_ID, UNIT_LABEL };
|