@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,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE STATUS LINE (kernel plan M3, M3-P1 steps 5 and 6; R-084).
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator narrates to the owner at MILESTONES ONLY: a phase merged,
|
|
5
|
+
* an incident, a decision needed. Routine noise gets a one-line ack and
|
|
6
|
+
* nothing else. That sparseness is expressed STRUCTURALLY in
|
|
7
|
+
* `schemas/status-line.schema.json` rather than as an instruction: the state
|
|
8
|
+
* vocabulary has exactly five members and none of them means "still working",
|
|
9
|
+
* so a heartbeat is not a thing this contract can say.
|
|
10
|
+
*
|
|
11
|
+
* CONSTRAINT C-1, NAMED BECAUSE IT DECIDES THE SHAPE OF THIS MODULE.
|
|
12
|
+
* "Never read current state from the tail of an append-only log." The stream
|
|
13
|
+
* at `state/status/stream.jsonl` is the HISTORY and is append-only. The
|
|
14
|
+
* current state is `status/current.json`, a whole document rewritten
|
|
15
|
+
* atomically on every emit. `readCurrent` opens `current.json` and nothing
|
|
16
|
+
* else; it does not know the stream's path. A truncated, corrupted or
|
|
17
|
+
* half-written stream therefore cannot change what `tiphys status show`
|
|
18
|
+
* reports, and that is the property criterion 7 witnesses in both directions.
|
|
19
|
+
*
|
|
20
|
+
* THE SPLIT (M4-D-13, decided in the M4-P18 plan section). The two documents
|
|
21
|
+
* used to sit side by side under the gitignored `state/` prefix, so the one
|
|
22
|
+
* sentence that says where the pipeline stands did not survive a fleet being
|
|
23
|
+
* reclaimed: AGENTS.md's `fleet-state-commit-discipline` clause names "the
|
|
24
|
+
* state file that says where the pipeline stands" as DURABLE, and an ignored
|
|
25
|
+
* path can be neither committed nor pushed. So the pointer moved OUT of
|
|
26
|
+
* `state/` to a tracked path and the stream stayed, because the stream is
|
|
27
|
+
* history that a restart rebuilds.
|
|
28
|
+
*
|
|
29
|
+
* C-1 IS THE REASON FOR THE SPLIT AND NOT A CONSTRAINT IT HAS TO DODGE.
|
|
30
|
+
* `readCurrent` opens the whole document and has no code path that reaches
|
|
31
|
+
* the stream, so moving the document changes WHERE it lives and changes
|
|
32
|
+
* NOTHING about how current state is read. The pair of witnesses in
|
|
33
|
+
* test/status.test.ts is re-pointed at the new layout and still reddens
|
|
34
|
+
* under the same mutation: `readCurrent` aimed at STREAM_FILE.
|
|
35
|
+
*
|
|
36
|
+
* ATOMIC REWRITE means write a temp file beside the target and rename. A
|
|
37
|
+
* rename within one directory is atomic on POSIX, so a reader either sees the
|
|
38
|
+
* whole previous document or the whole new one and never a partial write.
|
|
39
|
+
* The temp file is created beside the DURABLE document, so the two paths in
|
|
40
|
+
* the rename stay inside one directory after the split.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Where the status files live inside a fleet home, and the two directories
|
|
44
|
+
* are on OPPOSITE sides of the fleet `.gitignore` by design (M4-D-13).
|
|
45
|
+
* `STATUS_DIR` is under the ignored `state/` prefix and holds the rebuilt
|
|
46
|
+
* history; `DURABLE_STATUS_DIR` is tracked and holds the one document a
|
|
47
|
+
* restart must not lose.
|
|
48
|
+
*/
|
|
49
|
+
export declare const STATUS_DIR: string;
|
|
50
|
+
export declare const DURABLE_STATUS_DIR = "status";
|
|
51
|
+
export declare const STREAM_FILE: string;
|
|
52
|
+
export declare const CURRENT_FILE: string;
|
|
53
|
+
/**
|
|
54
|
+
* The five supervisor-actionable states, blueprint section 5. Duplicated
|
|
55
|
+
* from `schemas/status-line.schema.json` so the CLI can reject a bad state
|
|
56
|
+
* before it composes a record; `test/status.test.ts` asserts the two agree,
|
|
57
|
+
* because a duplicated vocabulary that nothing compares is a vocabulary that
|
|
58
|
+
* drifts.
|
|
59
|
+
*/
|
|
60
|
+
export declare const STATUS_STATES: readonly string[];
|
|
61
|
+
export interface StatusRecord {
|
|
62
|
+
kind: "status-line";
|
|
63
|
+
at: string;
|
|
64
|
+
run: string;
|
|
65
|
+
project: string;
|
|
66
|
+
state: string;
|
|
67
|
+
detail: string;
|
|
68
|
+
refs: string[];
|
|
69
|
+
}
|
|
70
|
+
export interface EmitInput {
|
|
71
|
+
run: string;
|
|
72
|
+
project: string;
|
|
73
|
+
state: string;
|
|
74
|
+
detail?: string;
|
|
75
|
+
refs?: string[];
|
|
76
|
+
/** Injected so a test can assert an exact record rather than a shape. */
|
|
77
|
+
at?: string;
|
|
78
|
+
}
|
|
79
|
+
/** RFC 3339 UTC to whole seconds, the form the schema's pattern accepts. */
|
|
80
|
+
export declare function nowIso(): string;
|
|
81
|
+
export declare function makeStatusRecord(input: EmitInput): StatusRecord;
|
|
82
|
+
export type EmitOutcome = {
|
|
83
|
+
ok: true;
|
|
84
|
+
record: StatusRecord;
|
|
85
|
+
} | {
|
|
86
|
+
ok: false;
|
|
87
|
+
reason: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Append ONE line to the stream, then rewrite `current.json` atomically.
|
|
91
|
+
*
|
|
92
|
+
* The order is deliberate and is the same ordering rule M2's gate runner
|
|
93
|
+
* learned: the durable history is written first, so a crash between the two
|
|
94
|
+
* leaves a history that is ahead of the pointer rather than a pointer that
|
|
95
|
+
* names an event no record supports.
|
|
96
|
+
*/
|
|
97
|
+
export declare function emitStatus(fleetRoot: string, record: StatusRecord): EmitOutcome;
|
|
98
|
+
export type CurrentRead = {
|
|
99
|
+
ok: true;
|
|
100
|
+
record: StatusRecord;
|
|
101
|
+
} | {
|
|
102
|
+
ok: false;
|
|
103
|
+
reason: string;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Read the current status. C-1: this function knows only `current.json`.
|
|
107
|
+
*
|
|
108
|
+
* It does not accept a stream path, does not fall back to the stream, and
|
|
109
|
+
* has no code path that opens it. That is what makes criterion 7 a
|
|
110
|
+
* demonstrable property rather than a convention: an implementation that
|
|
111
|
+
* reads the stream is a DIFFERENT function, and the criterion requires it to
|
|
112
|
+
* be written, shown red against corrupt stream bytes, and reverted.
|
|
113
|
+
*/
|
|
114
|
+
export declare function readCurrent(fleetRoot: string): CurrentRead;
|
|
115
|
+
/** One line, the form `tiphys status show` prints. */
|
|
116
|
+
export declare function renderStatus(record: StatusRecord): string;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE STATUS LINE (kernel plan M3, M3-P1 steps 5 and 6; R-084).
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator narrates to the owner at MILESTONES ONLY: a phase merged,
|
|
5
|
+
* an incident, a decision needed. Routine noise gets a one-line ack and
|
|
6
|
+
* nothing else. That sparseness is expressed STRUCTURALLY in
|
|
7
|
+
* `schemas/status-line.schema.json` rather than as an instruction: the state
|
|
8
|
+
* vocabulary has exactly five members and none of them means "still working",
|
|
9
|
+
* so a heartbeat is not a thing this contract can say.
|
|
10
|
+
*
|
|
11
|
+
* CONSTRAINT C-1, NAMED BECAUSE IT DECIDES THE SHAPE OF THIS MODULE.
|
|
12
|
+
* "Never read current state from the tail of an append-only log." The stream
|
|
13
|
+
* at `state/status/stream.jsonl` is the HISTORY and is append-only. The
|
|
14
|
+
* current state is `status/current.json`, a whole document rewritten
|
|
15
|
+
* atomically on every emit. `readCurrent` opens `current.json` and nothing
|
|
16
|
+
* else; it does not know the stream's path. A truncated, corrupted or
|
|
17
|
+
* half-written stream therefore cannot change what `tiphys status show`
|
|
18
|
+
* reports, and that is the property criterion 7 witnesses in both directions.
|
|
19
|
+
*
|
|
20
|
+
* THE SPLIT (M4-D-13, decided in the M4-P18 plan section). The two documents
|
|
21
|
+
* used to sit side by side under the gitignored `state/` prefix, so the one
|
|
22
|
+
* sentence that says where the pipeline stands did not survive a fleet being
|
|
23
|
+
* reclaimed: AGENTS.md's `fleet-state-commit-discipline` clause names "the
|
|
24
|
+
* state file that says where the pipeline stands" as DURABLE, and an ignored
|
|
25
|
+
* path can be neither committed nor pushed. So the pointer moved OUT of
|
|
26
|
+
* `state/` to a tracked path and the stream stayed, because the stream is
|
|
27
|
+
* history that a restart rebuilds.
|
|
28
|
+
*
|
|
29
|
+
* C-1 IS THE REASON FOR THE SPLIT AND NOT A CONSTRAINT IT HAS TO DODGE.
|
|
30
|
+
* `readCurrent` opens the whole document and has no code path that reaches
|
|
31
|
+
* the stream, so moving the document changes WHERE it lives and changes
|
|
32
|
+
* NOTHING about how current state is read. The pair of witnesses in
|
|
33
|
+
* test/status.test.ts is re-pointed at the new layout and still reddens
|
|
34
|
+
* under the same mutation: `readCurrent` aimed at STREAM_FILE.
|
|
35
|
+
*
|
|
36
|
+
* ATOMIC REWRITE means write a temp file beside the target and rename. A
|
|
37
|
+
* rename within one directory is atomic on POSIX, so a reader either sees the
|
|
38
|
+
* whole previous document or the whole new one and never a partial write.
|
|
39
|
+
* The temp file is created beside the DURABLE document, so the two paths in
|
|
40
|
+
* the rename stay inside one directory after the split.
|
|
41
|
+
*/
|
|
42
|
+
import { mkdirSync, renameSync, writeFileSync, appendFileSync } from "node:fs";
|
|
43
|
+
import { join } from "node:path";
|
|
44
|
+
import { refuseOpenForWrite, readRegularFileIfPresent } from "./task.js";
|
|
45
|
+
/**
|
|
46
|
+
* Where the status files live inside a fleet home, and the two directories
|
|
47
|
+
* are on OPPOSITE sides of the fleet `.gitignore` by design (M4-D-13).
|
|
48
|
+
* `STATUS_DIR` is under the ignored `state/` prefix and holds the rebuilt
|
|
49
|
+
* history; `DURABLE_STATUS_DIR` is tracked and holds the one document a
|
|
50
|
+
* restart must not lose.
|
|
51
|
+
*/
|
|
52
|
+
export const STATUS_DIR = join("state", "status");
|
|
53
|
+
export const DURABLE_STATUS_DIR = "status";
|
|
54
|
+
export const STREAM_FILE = join(STATUS_DIR, "stream.jsonl");
|
|
55
|
+
export const CURRENT_FILE = join(DURABLE_STATUS_DIR, "current.json");
|
|
56
|
+
/**
|
|
57
|
+
* The five supervisor-actionable states, blueprint section 5. Duplicated
|
|
58
|
+
* from `schemas/status-line.schema.json` so the CLI can reject a bad state
|
|
59
|
+
* before it composes a record; `test/status.test.ts` asserts the two agree,
|
|
60
|
+
* because a duplicated vocabulary that nothing compares is a vocabulary that
|
|
61
|
+
* drifts.
|
|
62
|
+
*/
|
|
63
|
+
export const STATUS_STATES = [
|
|
64
|
+
"blocked",
|
|
65
|
+
"done",
|
|
66
|
+
"failed",
|
|
67
|
+
"needs-decision",
|
|
68
|
+
"phase-change",
|
|
69
|
+
];
|
|
70
|
+
/** RFC 3339 UTC to whole seconds, the form the schema's pattern accepts. */
|
|
71
|
+
export function nowIso() {
|
|
72
|
+
return `${new Date().toISOString().slice(0, 19)}Z`;
|
|
73
|
+
}
|
|
74
|
+
export function makeStatusRecord(input) {
|
|
75
|
+
return {
|
|
76
|
+
kind: "status-line",
|
|
77
|
+
at: input.at ?? nowIso(),
|
|
78
|
+
run: input.run,
|
|
79
|
+
project: input.project,
|
|
80
|
+
state: input.state,
|
|
81
|
+
detail: input.detail ?? "",
|
|
82
|
+
refs: input.refs ?? [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Append ONE line to the stream, then rewrite `current.json` atomically.
|
|
87
|
+
*
|
|
88
|
+
* The order is deliberate and is the same ordering rule M2's gate runner
|
|
89
|
+
* learned: the durable history is written first, so a crash between the two
|
|
90
|
+
* leaves a history that is ahead of the pointer rather than a pointer that
|
|
91
|
+
* names an event no record supports.
|
|
92
|
+
*/
|
|
93
|
+
export function emitStatus(fleetRoot, record) {
|
|
94
|
+
/* TWO DIRECTORIES SINCE THE SPLIT, and both are created before either is
|
|
95
|
+
written: the ephemeral one is rebuilt by `tiphys resume` after a reclaim
|
|
96
|
+
and the durable one comes across in a clone, so on any given fleet home
|
|
97
|
+
either can be the one that is absent. */
|
|
98
|
+
mkdirSync(join(fleetRoot, STATUS_DIR), { recursive: true });
|
|
99
|
+
mkdirSync(join(fleetRoot, DURABLE_STATUS_DIR), { recursive: true });
|
|
100
|
+
const streamPath = join(fleetRoot, STREAM_FILE);
|
|
101
|
+
const currentPath = join(fleetRoot, CURRENT_FILE);
|
|
102
|
+
const temporaryPath = `${currentPath}.tmp`;
|
|
103
|
+
for (const path of [streamPath, currentPath, temporaryPath]) {
|
|
104
|
+
const refusal = refuseOpenForWrite(path);
|
|
105
|
+
if (refusal !== undefined) {
|
|
106
|
+
return { ok: false, reason: refusal };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
appendFileSync(streamPath, `${JSON.stringify(record)}\n`, "utf8");
|
|
110
|
+
writeFileSync(temporaryPath, `${JSON.stringify(record, undefined, 2)}\n`, "utf8");
|
|
111
|
+
renameSync(temporaryPath, currentPath);
|
|
112
|
+
return { ok: true, record };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Read the current status. C-1: this function knows only `current.json`.
|
|
116
|
+
*
|
|
117
|
+
* It does not accept a stream path, does not fall back to the stream, and
|
|
118
|
+
* has no code path that opens it. That is what makes criterion 7 a
|
|
119
|
+
* demonstrable property rather than a convention: an implementation that
|
|
120
|
+
* reads the stream is a DIFFERENT function, and the criterion requires it to
|
|
121
|
+
* be written, shown red against corrupt stream bytes, and reverted.
|
|
122
|
+
*/
|
|
123
|
+
export function readCurrent(fleetRoot) {
|
|
124
|
+
const path = join(fleetRoot, CURRENT_FILE);
|
|
125
|
+
const read = readRegularFileIfPresent(path);
|
|
126
|
+
if (read.kind === "absent") {
|
|
127
|
+
return { ok: false, reason: `no status has been emitted in ${fleetRoot}` };
|
|
128
|
+
}
|
|
129
|
+
if (read.kind === "refused") {
|
|
130
|
+
return { ok: false, reason: read.reason };
|
|
131
|
+
}
|
|
132
|
+
let parsed;
|
|
133
|
+
try {
|
|
134
|
+
parsed = JSON.parse(read.body);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
reason: `${path} is not readable as a status record: ${error.message}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return { ok: true, record: parsed };
|
|
143
|
+
}
|
|
144
|
+
/** One line, the form `tiphys status show` prints. */
|
|
145
|
+
export function renderStatus(record) {
|
|
146
|
+
const refs = record.refs.length === 0 ? "" : ` refs=${record.refs.join(",")}`;
|
|
147
|
+
return `${record.at} ${record.state} run=${record.run} project=${record.project}${record.detail === "" ? "" : ` ${record.detail}`}${refs}`;
|
|
148
|
+
}
|