@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,9 @@
|
|
|
1
|
+
/** Exit code for usage errors, per BSD sysexits EX_USAGE. */
|
|
2
|
+
export declare const EX_USAGE = 64;
|
|
3
|
+
export declare function usageLine(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Dispatch argv (already stripped of the node and script entries) to a
|
|
6
|
+
* subcommand handler. A missing or unknown subcommand prints the usage
|
|
7
|
+
* line to stderr and returns EX_USAGE.
|
|
8
|
+
*/
|
|
9
|
+
export declare function run(argv: string[]): Promise<number>;
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { cmdBrief } from "./commands/brief.js";
|
|
2
|
+
import { cmdChecklist } from "./commands/checklist.js";
|
|
3
|
+
import { cmdCutover } from "./commands/cutover.js";
|
|
4
|
+
import { cmdDoctor } from "./commands/doctor.js";
|
|
5
|
+
import { cmdGates } from "./commands/gates.js";
|
|
6
|
+
import { cmdInit } from "./commands/init.js";
|
|
7
|
+
import { cmdLock } from "./commands/lock.js";
|
|
8
|
+
import { cmdMode } from "./commands/mode.js";
|
|
9
|
+
import { cmdNext } from "./commands/next.js";
|
|
10
|
+
import { cmdPlan } from "./commands/plan.js";
|
|
11
|
+
import { cmdPool } from "./commands/pool.js";
|
|
12
|
+
import { cmdResume } from "./commands/resume.js";
|
|
13
|
+
import { cmdSpawn } from "./commands/spawn.js";
|
|
14
|
+
import { cmdStatus } from "./commands/status.js";
|
|
15
|
+
import { cmdSync } from "./commands/sync.js";
|
|
16
|
+
import { cmdTeardown } from "./commands/teardown.js";
|
|
17
|
+
import { cmdTuition } from "./commands/tuition.js";
|
|
18
|
+
import { cmdValidate } from "./commands/validate.js";
|
|
19
|
+
import { cmdWatch } from "./commands/watch.js";
|
|
20
|
+
import { printVersion } from "./version.js";
|
|
21
|
+
/** Exit code for usage errors, per BSD sysexits EX_USAGE. */
|
|
22
|
+
export const EX_USAGE = 64;
|
|
23
|
+
/**
|
|
24
|
+
* The one dispatch table. Subcommands added by later phases register here.
|
|
25
|
+
*/
|
|
26
|
+
const commands = new Map([
|
|
27
|
+
["version", printVersion],
|
|
28
|
+
["brief", cmdBrief],
|
|
29
|
+
["checklist", cmdChecklist],
|
|
30
|
+
/* M4-P25. The rollback handlers M4-P26 shipped were reachable only by
|
|
31
|
+
import until this row existed, so the rollback document printed command
|
|
32
|
+
lines that did not run. */
|
|
33
|
+
["cutover", cmdCutover],
|
|
34
|
+
["init", cmdInit],
|
|
35
|
+
["doctor", cmdDoctor],
|
|
36
|
+
["gates", cmdGates],
|
|
37
|
+
["lock", cmdLock],
|
|
38
|
+
["mode", cmdMode],
|
|
39
|
+
["next", cmdNext],
|
|
40
|
+
["plan", cmdPlan],
|
|
41
|
+
["pool", cmdPool],
|
|
42
|
+
["resume", cmdResume],
|
|
43
|
+
["spawn", cmdSpawn],
|
|
44
|
+
["status", cmdStatus],
|
|
45
|
+
["sync", cmdSync],
|
|
46
|
+
["teardown", cmdTeardown],
|
|
47
|
+
["tuition", cmdTuition],
|
|
48
|
+
["validate", cmdValidate],
|
|
49
|
+
["watch", cmdWatch],
|
|
50
|
+
]);
|
|
51
|
+
export function usageLine() {
|
|
52
|
+
const names = [...commands.keys()].sort().join(" | ");
|
|
53
|
+
return `usage: tiphys <${names}>`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Dispatch argv (already stripped of the node and script entries) to a
|
|
57
|
+
* subcommand handler. A missing or unknown subcommand prints the usage
|
|
58
|
+
* line to stderr and returns EX_USAGE.
|
|
59
|
+
*/
|
|
60
|
+
export async function run(argv) {
|
|
61
|
+
const [subcommand, ...rest] = argv;
|
|
62
|
+
const handler = subcommand === undefined ? undefined : commands.get(subcommand);
|
|
63
|
+
if (handler === undefined) {
|
|
64
|
+
process.stderr.write(`${usageLine()}\n`);
|
|
65
|
+
return EX_USAGE;
|
|
66
|
+
}
|
|
67
|
+
return handler(rest);
|
|
68
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tiphys brief compose --role <id> --phase <plan-file> --phase-id <id>
|
|
3
|
+
* [--out <file>]` (kernel plan M3, M3-P5 step 6).
|
|
4
|
+
*
|
|
5
|
+
* Resolves `roles/<id>.md` from the installed kernel, checks every
|
|
6
|
+
* mandated-reading path, expands the shared clause include, and emits the
|
|
7
|
+
* composed brief: the frontmatter-driven header (which carries the resolved
|
|
8
|
+
* mandated-reading list, in order), the brief body, the named phase rendered
|
|
9
|
+
* from the plan instance, and the fleet warnings file when one is present.
|
|
10
|
+
*
|
|
11
|
+
* Exit codes:
|
|
12
|
+
* 0 the brief composed
|
|
13
|
+
* 1 a resolution failed, and the reason names what
|
|
14
|
+
* 64 usage error (BSD sysexits EX_USAGE)
|
|
15
|
+
*
|
|
16
|
+
* WHAT THIS COMMAND WRITES IS WHAT `tiphys spawn --brief` CONSUMES.
|
|
17
|
+
* `src/brief.ts` stays the assembly spawn performs at launch and is not
|
|
18
|
+
* rewritten here, so no M1 contract changes (M3-P5 step 6, and CR-521's
|
|
19
|
+
* lesson that patching another phase's module from this one is its own
|
|
20
|
+
* defect).
|
|
21
|
+
*
|
|
22
|
+
* D-M3-27, AND IT IS THE POINT OF THE COMMAND RATHER THAN A GARNISH. This
|
|
23
|
+
* command's entire job is resolving and reading paths it did not create:
|
|
24
|
+
* a role file named by an operator flag, a plan file named by another, every
|
|
25
|
+
* mandated-reading entry a brief declares, an included clause block, an
|
|
26
|
+
* output file, and the fleet warnings file. Every one of those goes through
|
|
27
|
+
* `classifyEntry` or `refuseOpenForWrite` in src/task.ts, so a named pipe at
|
|
28
|
+
* any of them is a reported refusal naming the path and the observed entry
|
|
29
|
+
* type in bounded time. Nothing here opens a path whose type has not been
|
|
30
|
+
* established, and this module adds no thirteenth instance to the open class
|
|
31
|
+
* `delivery/STATE.md` records against src/brief.ts, src/hooks.ts and
|
|
32
|
+
* src/pool.ts.
|
|
33
|
+
*/
|
|
34
|
+
/** Exit code for usage errors, per BSD sysexits EX_USAGE. */
|
|
35
|
+
export declare const EX_USAGE = 64;
|
|
36
|
+
/**
|
|
37
|
+
* The fleet's environment-warnings file, the same name src/brief.ts appends
|
|
38
|
+
* at spawn (`WARNINGS_FILE`). Composition has no fleet argument in the usage
|
|
39
|
+
* the plan fixes, so it looks for the file in the CURRENT WORKING DIRECTORY,
|
|
40
|
+
* which is the fleet root when an operator composes inside a fleet. Stated
|
|
41
|
+
* here rather than left implicit, because "the fleet warnings file" names a
|
|
42
|
+
* location the command is not given.
|
|
43
|
+
*/
|
|
44
|
+
export declare const WARNINGS_FILE = "warnings.md";
|
|
45
|
+
export interface ComposeOptions {
|
|
46
|
+
roleId: string;
|
|
47
|
+
planFile: string;
|
|
48
|
+
phaseId: string;
|
|
49
|
+
/** The installed kernel root. Injected so tests can stage one. */
|
|
50
|
+
root: string;
|
|
51
|
+
/** Where to look for the fleet warnings file. */
|
|
52
|
+
workingDirectory: string;
|
|
53
|
+
/**
|
|
54
|
+
* Which review contract the clean-room brief is running (M3-P6 criterion 10,
|
|
55
|
+
* T-007). `undefined` means the caller named none, which DEFAULTS to
|
|
56
|
+
* `criteria` for the one role that has contracts and is a usage error for
|
|
57
|
+
* every other role. The default is not a shrug: `criteria` is the contract
|
|
58
|
+
* R-053 already described and the one every existing dispatch means, so the
|
|
59
|
+
* three briefs M3-P5 shipped and every caller that predates this flag keep
|
|
60
|
+
* composing unchanged.
|
|
61
|
+
*/
|
|
62
|
+
reviewContract?: string;
|
|
63
|
+
}
|
|
64
|
+
export type ComposeResult = {
|
|
65
|
+
ok: true;
|
|
66
|
+
text: string;
|
|
67
|
+
} | {
|
|
68
|
+
ok: false;
|
|
69
|
+
reason: string;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Compose one brief. Exported so tests drive every arm without a subprocess
|
|
73
|
+
* and, more importantly, against a STAGED kernel root: the missing-path arm
|
|
74
|
+
* (criterion 2) and the named-pipe arm (criterion 6c) both need a role brief
|
|
75
|
+
* pointing at a path this repository must not ship.
|
|
76
|
+
*/
|
|
77
|
+
export declare function composeBrief(options: ComposeOptions): ComposeResult;
|
|
78
|
+
/**
|
|
79
|
+
* Validate one role brief's clause round trip. Used by
|
|
80
|
+
* `tiphys validate --type role-brief` after the frontmatter passes its
|
|
81
|
+
* schema, and exported here so the two live beside the include that makes
|
|
82
|
+
* them necessary.
|
|
83
|
+
*/
|
|
84
|
+
export declare function roleBriefBodyDiagnostics(briefPath: string, body: string, clauses: readonly string[]): {
|
|
85
|
+
ok: true;
|
|
86
|
+
lines: string[];
|
|
87
|
+
} | {
|
|
88
|
+
ok: false;
|
|
89
|
+
reason: string;
|
|
90
|
+
};
|
|
91
|
+
/** `tiphys brief <subcommand>`. */
|
|
92
|
+
export declare function cmdBrief(argv: string[]): number;
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tiphys brief compose --role <id> --phase <plan-file> --phase-id <id>
|
|
3
|
+
* [--out <file>]` (kernel plan M3, M3-P5 step 6).
|
|
4
|
+
*
|
|
5
|
+
* Resolves `roles/<id>.md` from the installed kernel, checks every
|
|
6
|
+
* mandated-reading path, expands the shared clause include, and emits the
|
|
7
|
+
* composed brief: the frontmatter-driven header (which carries the resolved
|
|
8
|
+
* mandated-reading list, in order), the brief body, the named phase rendered
|
|
9
|
+
* from the plan instance, and the fleet warnings file when one is present.
|
|
10
|
+
*
|
|
11
|
+
* Exit codes:
|
|
12
|
+
* 0 the brief composed
|
|
13
|
+
* 1 a resolution failed, and the reason names what
|
|
14
|
+
* 64 usage error (BSD sysexits EX_USAGE)
|
|
15
|
+
*
|
|
16
|
+
* WHAT THIS COMMAND WRITES IS WHAT `tiphys spawn --brief` CONSUMES.
|
|
17
|
+
* `src/brief.ts` stays the assembly spawn performs at launch and is not
|
|
18
|
+
* rewritten here, so no M1 contract changes (M3-P5 step 6, and CR-521's
|
|
19
|
+
* lesson that patching another phase's module from this one is its own
|
|
20
|
+
* defect).
|
|
21
|
+
*
|
|
22
|
+
* D-M3-27, AND IT IS THE POINT OF THE COMMAND RATHER THAN A GARNISH. This
|
|
23
|
+
* command's entire job is resolving and reading paths it did not create:
|
|
24
|
+
* a role file named by an operator flag, a plan file named by another, every
|
|
25
|
+
* mandated-reading entry a brief declares, an included clause block, an
|
|
26
|
+
* output file, and the fleet warnings file. Every one of those goes through
|
|
27
|
+
* `classifyEntry` or `refuseOpenForWrite` in src/task.ts, so a named pipe at
|
|
28
|
+
* any of them is a reported refusal naming the path and the observed entry
|
|
29
|
+
* type in bounded time. Nothing here opens a path whose type has not been
|
|
30
|
+
* established, and this module adds no thirteenth instance to the open class
|
|
31
|
+
* `delivery/STATE.md` records against src/brief.ts, src/hooks.ts and
|
|
32
|
+
* src/pool.ts.
|
|
33
|
+
*/
|
|
34
|
+
import { writeFileSync } from "node:fs";
|
|
35
|
+
import { join } from "node:path";
|
|
36
|
+
import { REVIEW_CONTRACTS, REVIEW_CONTRACT_ROLE, ROLE_BRIEF_FILES, ROLE_IDS, clauseRoundTripDiagnostics, expandIncludes, kernelRoot, missingRequiredSections, renderPhase, resolveMandatedReading, roleBriefFile, selectReviewContract, splitFrontmatter, } from "../roles.js";
|
|
37
|
+
import { refuseOpenForWrite, readRegularFileIfPresent } from "../task.js";
|
|
38
|
+
import { decodeDocument, formatDiagnostics, readOperatorPath } from "../validate.js";
|
|
39
|
+
/** Exit code for usage errors, per BSD sysexits EX_USAGE. */
|
|
40
|
+
export const EX_USAGE = 64;
|
|
41
|
+
/**
|
|
42
|
+
* The fleet's environment-warnings file, the same name src/brief.ts appends
|
|
43
|
+
* at spawn (`WARNINGS_FILE`). Composition has no fleet argument in the usage
|
|
44
|
+
* the plan fixes, so it looks for the file in the CURRENT WORKING DIRECTORY,
|
|
45
|
+
* which is the fleet root when an operator composes inside a fleet. Stated
|
|
46
|
+
* here rather than left implicit, because "the fleet warnings file" names a
|
|
47
|
+
* location the command is not given.
|
|
48
|
+
*/
|
|
49
|
+
export const WARNINGS_FILE = "warnings.md";
|
|
50
|
+
function asRecord(value) {
|
|
51
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
52
|
+
? value
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
function stringList(value) {
|
|
56
|
+
return Array.isArray(value) ? value.map((entry) => String(entry)) : [];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Compose one brief. Exported so tests drive every arm without a subprocess
|
|
60
|
+
* and, more importantly, against a STAGED kernel root: the missing-path arm
|
|
61
|
+
* (criterion 2) and the named-pipe arm (criterion 6c) both need a role brief
|
|
62
|
+
* pointing at a path this repository must not ship.
|
|
63
|
+
*/
|
|
64
|
+
export function composeBrief(options) {
|
|
65
|
+
if (!ROLE_IDS.includes(options.roleId)) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
reason: `unknown role ${options.roleId}; the roles are ${ROLE_IDS.join(", ")}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/* THE BRIEF'S LOCATION IS DECLARED, NOT COMPUTED FROM THE ID. Five of the
|
|
72
|
+
six roles ship as `roles/<id>.md` and the orchestrator's ships as
|
|
73
|
+
`AGENTS.md` at the package root, so `join(root, "roles", id + ".md")`
|
|
74
|
+
was a path that does not exist for one advertised role. A role the
|
|
75
|
+
vocabulary declares and the package does not place is refused BY NAME
|
|
76
|
+
here, before anything is read, rather than surfacing as a missing file
|
|
77
|
+
the operator reads as a broken install (src/roles.ts:54). Includes are
|
|
78
|
+
still resolved against `roles/`, which is where `$include:` targets such
|
|
79
|
+
as `_shared-dispatch-contract.md` live for every role. */
|
|
80
|
+
const relative = roleBriefFile(options.roleId);
|
|
81
|
+
if (relative === undefined) {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
reason: `role ${options.roleId} is declared in the role vocabulary and this ` +
|
|
85
|
+
`package does not say where its brief ships; the placed roles are ` +
|
|
86
|
+
`${Object.keys(ROLE_BRIEF_FILES).join(", ")}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const rolePath = join(options.root, relative);
|
|
90
|
+
const roleRead = readOperatorPath(rolePath);
|
|
91
|
+
if (!roleRead.ok) {
|
|
92
|
+
return { ok: false, reason: `role brief ${rolePath}: ${roleRead.reason}` };
|
|
93
|
+
}
|
|
94
|
+
const split = splitFrontmatter(roleRead.body, rolePath);
|
|
95
|
+
if (!split.ok) {
|
|
96
|
+
return { ok: false, reason: split.reason };
|
|
97
|
+
}
|
|
98
|
+
const decoded = decodeDocument(split.frontmatter, `${rolePath} frontmatter`);
|
|
99
|
+
if (!decoded.ok) {
|
|
100
|
+
return { ok: false, reason: decoded.reason };
|
|
101
|
+
}
|
|
102
|
+
const frontmatter = asRecord(decoded.value);
|
|
103
|
+
if (frontmatter === undefined) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
reason: `${rolePath} frontmatter is not a mapping, so it carries no role-brief fields`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const reading = stringList(frontmatter["mandated-reading"]);
|
|
110
|
+
const resolution = resolveMandatedReading(reading, options.root);
|
|
111
|
+
if (!resolution.ok) {
|
|
112
|
+
return { ok: false, reason: resolution.reason };
|
|
113
|
+
}
|
|
114
|
+
/* INCLUDES RESOLVE AGAINST THE BRIEF'S OWN DIRECTORY, not against `roles/`.
|
|
115
|
+
Every brief under `roles/` writes `$include: _shared-dispatch-contract.md`
|
|
116
|
+
and AGENTS.md, which is the orchestrator's brief at the package root,
|
|
117
|
+
writes `$include: roles/_shared-dispatch-contract.md`. Both are correct
|
|
118
|
+
relative to the document they are in, and a fixed `roles/` base turned the
|
|
119
|
+
second into `roles/roles/_shared-dispatch-contract.md`. */
|
|
120
|
+
const expanded = expandIncludes(split.body, dirnameOf(rolePath), rolePath);
|
|
121
|
+
if (!expanded.ok) {
|
|
122
|
+
return { ok: false, reason: expanded.reason };
|
|
123
|
+
}
|
|
124
|
+
/* R-033a, CRITERION 2. Checked on the INCLUDE-EXPANDED body and only for the
|
|
125
|
+
implementer, because R-033a is the implementer brief's template and no
|
|
126
|
+
other role's row enumerates sections. A brief that has lost one is refused
|
|
127
|
+
HERE rather than emitted with a hole, because the failure mode this guards
|
|
128
|
+
is a brief that reads complete: five sections and no gate list composes
|
|
129
|
+
cleanly, dispatches cleanly, and instructs an agent to pass gates it was
|
|
130
|
+
never shown. */
|
|
131
|
+
if (options.roleId === "implementer") {
|
|
132
|
+
const missing = missingRequiredSections(expanded.text);
|
|
133
|
+
if (missing.length > 0) {
|
|
134
|
+
return { ok: false, reason: `${rolePath}: ${missing.join("; ")}` };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/* T-007, CRITERION 10. The composed brief declares ONE contract and carries
|
|
138
|
+
one contract's clauses. The flag is refused for any other role rather than
|
|
139
|
+
ignored: silently accepting it would let a dispatch believe it had selected
|
|
140
|
+
a contract for a role that has none. */
|
|
141
|
+
let body = expanded.text;
|
|
142
|
+
let reviewContract;
|
|
143
|
+
if (options.roleId === REVIEW_CONTRACT_ROLE) {
|
|
144
|
+
reviewContract = options.reviewContract ?? "criteria";
|
|
145
|
+
if (!REVIEW_CONTRACTS.includes(reviewContract)) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
reason: `unknown review contract ${reviewContract}; the contracts are ${REVIEW_CONTRACTS.join(", ")}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const selected = selectReviewContract(body, reviewContract);
|
|
152
|
+
if (!selected.ok) {
|
|
153
|
+
return { ok: false, reason: selected.reason };
|
|
154
|
+
}
|
|
155
|
+
body = selected.text;
|
|
156
|
+
}
|
|
157
|
+
else if (options.reviewContract !== undefined) {
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
reason: `--review-contract applies to ${REVIEW_CONTRACT_ROLE} and ${options.roleId} declares no review contracts`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const planRead = readOperatorPath(options.planFile);
|
|
164
|
+
if (!planRead.ok) {
|
|
165
|
+
return { ok: false, reason: `plan ${options.planFile}: ${planRead.reason}` };
|
|
166
|
+
}
|
|
167
|
+
const planDecoded = decodeDocument(planRead.body, options.planFile);
|
|
168
|
+
if (!planDecoded.ok) {
|
|
169
|
+
return { ok: false, reason: planDecoded.reason };
|
|
170
|
+
}
|
|
171
|
+
const plan = asRecord(planDecoded.value);
|
|
172
|
+
const phases = Array.isArray(plan?.["phases"]) ? plan["phases"] : [];
|
|
173
|
+
const phase = phases
|
|
174
|
+
.map((candidate) => asRecord(candidate))
|
|
175
|
+
.find((candidate) => candidate?.["id"] === options.phaseId);
|
|
176
|
+
if (phase === undefined) {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
reason: `${options.planFile} declares no phase with id ${options.phaseId}`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const lines = [
|
|
183
|
+
`# Brief: ${options.roleId}`,
|
|
184
|
+
"",
|
|
185
|
+
`role: ${String(frontmatter["role"] ?? options.roleId)}`,
|
|
186
|
+
`lifetime: ${String(frontmatter["lifetime"] ?? "")}`,
|
|
187
|
+
`model-tier: ${String(frontmatter["model-tier"] ?? "")}`,
|
|
188
|
+
...(reviewContract === undefined ? [] : [`review-contract: ${reviewContract}`]),
|
|
189
|
+
"",
|
|
190
|
+
"## Mandated reading, in order",
|
|
191
|
+
"",
|
|
192
|
+
];
|
|
193
|
+
for (let index = 0; index < resolution.paths.length; index += 1) {
|
|
194
|
+
lines.push(`${String(index + 1)}. ${resolution.paths[index]}`);
|
|
195
|
+
}
|
|
196
|
+
lines.push("");
|
|
197
|
+
lines.push("## Sees");
|
|
198
|
+
lines.push("");
|
|
199
|
+
for (const entry of stringList(frontmatter["sees"])) {
|
|
200
|
+
lines.push(`- ${entry}`);
|
|
201
|
+
}
|
|
202
|
+
lines.push("");
|
|
203
|
+
lines.push("## Never");
|
|
204
|
+
lines.push("");
|
|
205
|
+
for (const entry of stringList(frontmatter["never"])) {
|
|
206
|
+
lines.push(`- ${entry}`);
|
|
207
|
+
}
|
|
208
|
+
lines.push("");
|
|
209
|
+
lines.push("## Verifiers");
|
|
210
|
+
lines.push("");
|
|
211
|
+
for (const entry of stringList(frontmatter["verifiers"])) {
|
|
212
|
+
lines.push(`- ${entry}`);
|
|
213
|
+
}
|
|
214
|
+
lines.push("");
|
|
215
|
+
lines.push("## Outputs");
|
|
216
|
+
lines.push("");
|
|
217
|
+
for (const entry of stringList(frontmatter["outputs"])) {
|
|
218
|
+
lines.push(`- ${entry}`);
|
|
219
|
+
}
|
|
220
|
+
lines.push("");
|
|
221
|
+
lines.push("# Brief body");
|
|
222
|
+
lines.push("");
|
|
223
|
+
lines.push(body.replace(/^\n+/, "").replace(/\n+$/, ""));
|
|
224
|
+
lines.push("");
|
|
225
|
+
lines.push(...renderPhase(phase));
|
|
226
|
+
const warnings = readRegularFileIfPresent(join(options.workingDirectory, WARNINGS_FILE));
|
|
227
|
+
if (warnings.kind === "refused") {
|
|
228
|
+
return { ok: false, reason: warnings.reason };
|
|
229
|
+
}
|
|
230
|
+
if (warnings.kind === "read") {
|
|
231
|
+
lines.push("# Environment warnings");
|
|
232
|
+
lines.push("");
|
|
233
|
+
lines.push(warnings.body.replace(/\n+$/, ""));
|
|
234
|
+
lines.push("");
|
|
235
|
+
}
|
|
236
|
+
return { ok: true, text: `${lines.join("\n").replace(/\n+$/, "")}\n` };
|
|
237
|
+
}
|
|
238
|
+
function usage() {
|
|
239
|
+
return ("usage: tiphys brief compose --role <" +
|
|
240
|
+
ROLE_IDS.join(" | ") +
|
|
241
|
+
"> --phase <plan-file> --phase-id <id> [--out <file>] " +
|
|
242
|
+
`[--review-contract <${REVIEW_CONTRACTS.join(" | ")}>]`);
|
|
243
|
+
}
|
|
244
|
+
function parseArgs(argv) {
|
|
245
|
+
const options = {};
|
|
246
|
+
const flags = new Map([
|
|
247
|
+
["--role", "role"],
|
|
248
|
+
["--phase", "phase"],
|
|
249
|
+
["--phase-id", "phaseId"],
|
|
250
|
+
["--out", "out"],
|
|
251
|
+
["--review-contract", "reviewContract"],
|
|
252
|
+
]);
|
|
253
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
254
|
+
const argument = argv[index];
|
|
255
|
+
const field = flags.get(argument);
|
|
256
|
+
if (field === undefined) {
|
|
257
|
+
return { usageError: `unknown option ${argument}` };
|
|
258
|
+
}
|
|
259
|
+
const value = argv[index + 1];
|
|
260
|
+
if (value === undefined || value.startsWith("--")) {
|
|
261
|
+
return { usageError: `${argument} requires a value` };
|
|
262
|
+
}
|
|
263
|
+
options[field] = value;
|
|
264
|
+
index += 1;
|
|
265
|
+
}
|
|
266
|
+
const optional = new Set(["out", "reviewContract"]);
|
|
267
|
+
for (const [flag, field] of flags) {
|
|
268
|
+
if (!optional.has(field) && options[field] === undefined) {
|
|
269
|
+
return { usageError: `${flag} is required` };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { options };
|
|
273
|
+
}
|
|
274
|
+
function cmdCompose(argv) {
|
|
275
|
+
const parsed = parseArgs(argv);
|
|
276
|
+
if (parsed.options === undefined) {
|
|
277
|
+
process.stderr.write(`tiphys brief compose: ${parsed.usageError ?? "usage error"}\n`);
|
|
278
|
+
process.stderr.write(`${usage()}\n`);
|
|
279
|
+
return EX_USAGE;
|
|
280
|
+
}
|
|
281
|
+
const { role, phase, phaseId, out, reviewContract } = parsed.options;
|
|
282
|
+
let root;
|
|
283
|
+
try {
|
|
284
|
+
root = kernelRoot();
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
process.stderr.write(`tiphys brief compose: ${error.message}\n`);
|
|
288
|
+
return 1;
|
|
289
|
+
}
|
|
290
|
+
const composed = composeBrief({
|
|
291
|
+
roleId: role,
|
|
292
|
+
planFile: phase,
|
|
293
|
+
phaseId: phaseId,
|
|
294
|
+
root,
|
|
295
|
+
workingDirectory: process.cwd(),
|
|
296
|
+
...(reviewContract === undefined ? {} : { reviewContract }),
|
|
297
|
+
});
|
|
298
|
+
if (!composed.ok) {
|
|
299
|
+
process.stderr.write(`tiphys brief compose: ${composed.reason}\n`);
|
|
300
|
+
return 1;
|
|
301
|
+
}
|
|
302
|
+
if (out === undefined) {
|
|
303
|
+
process.stdout.write(composed.text);
|
|
304
|
+
return 0;
|
|
305
|
+
}
|
|
306
|
+
const refusal = refuseOpenForWrite(out);
|
|
307
|
+
if (refusal !== undefined) {
|
|
308
|
+
process.stderr.write(`tiphys brief compose: ${refusal}\n`);
|
|
309
|
+
return 1;
|
|
310
|
+
}
|
|
311
|
+
writeFileSync(out, composed.text);
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Validate one role brief's clause round trip. Used by
|
|
316
|
+
* `tiphys validate --type role-brief` after the frontmatter passes its
|
|
317
|
+
* schema, and exported here so the two live beside the include that makes
|
|
318
|
+
* them necessary.
|
|
319
|
+
*/
|
|
320
|
+
export function roleBriefBodyDiagnostics(briefPath, body, clauses) {
|
|
321
|
+
const expanded = expandIncludes(body, dirnameOf(briefPath), briefPath);
|
|
322
|
+
if (!expanded.ok) {
|
|
323
|
+
return { ok: false, reason: expanded.reason };
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
ok: true,
|
|
327
|
+
lines: formatDiagnostics(clauseRoundTripDiagnostics(clauses, expanded.text)),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function dirnameOf(path) {
|
|
331
|
+
const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
332
|
+
return index === -1 ? "." : path.slice(0, index);
|
|
333
|
+
}
|
|
334
|
+
/** `tiphys brief <subcommand>`. */
|
|
335
|
+
export function cmdBrief(argv) {
|
|
336
|
+
const [subcommand, ...rest] = argv;
|
|
337
|
+
if (subcommand === "compose") {
|
|
338
|
+
return cmdCompose(rest);
|
|
339
|
+
}
|
|
340
|
+
process.stderr.write(`tiphys brief: ${subcommand === undefined ? "a subcommand is required" : `unknown subcommand ${subcommand}`}\n`);
|
|
341
|
+
process.stderr.write(`${usage()}\n`);
|
|
342
|
+
return EX_USAGE;
|
|
343
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tiphys checklist resolve --checklist <id> [--extra <file>] [--framing <id>]`
|
|
3
|
+
* (kernel plan M3, M3-P7 step 5; R-054, T-001).
|
|
4
|
+
*
|
|
5
|
+
* PROBE INJECTION AS DATA. The orchestrator writes a per-phase probe file and
|
|
6
|
+
* this command merges it into the canonical checklist, so a per-phase probe
|
|
7
|
+
* set is reviewable and reusable instead of living in a dispatch prompt
|
|
8
|
+
* nobody can diff. The AGENTS.md duty to WRITE them is M3-P9's; this is the
|
|
9
|
+
* mechanism they plug into.
|
|
10
|
+
*
|
|
11
|
+
* IT VALIDATES BEFORE IT SERVES. Both documents go through exactly what
|
|
12
|
+
* `tiphys validate --type checklist` runs, schema first and derived checks
|
|
13
|
+
* second, and a document that fails is REFUSED rather than partly printed.
|
|
14
|
+
* That is the M3-P3 `mode show` fix round applied at the site that would
|
|
15
|
+
* otherwise repeat it: as first shipped, that command read a document and
|
|
16
|
+
* printed it, invoking neither the schema nor any registered check, so every
|
|
17
|
+
* guard its phase built was bypassed on the one path built for humans. A
|
|
18
|
+
* partial answer from an invalid checklist is worse than an error, because
|
|
19
|
+
* the reviewer cannot tell.
|
|
20
|
+
*
|
|
21
|
+
* THE CONTEXT FOR THE DERIVED CHECKS IS THE PACKAGE ROOT, which is where
|
|
22
|
+
* `gate-registry.yaml` sits, so `gate-probes-resolve` RUNS here rather than
|
|
23
|
+
* SKIPPING. A skip fails the run, and a command that always failed would be a
|
|
24
|
+
* command nobody uses.
|
|
25
|
+
*
|
|
26
|
+
* Exit codes:
|
|
27
|
+
* 0 both documents are valid, the framing resolved, and the list printed
|
|
28
|
+
* 1 a document could not be read or is invalid, an extra probe collided
|
|
29
|
+
* or required no evidence, or the named framing is not declared
|
|
30
|
+
* 64 usage error (BSD sysexits EX_USAGE)
|
|
31
|
+
*
|
|
32
|
+
* A FRAMING ID THAT IS NOT DECLARED IS 1, NOT 64, for the reason
|
|
33
|
+
* `tiphys mode show` records for an undeclared mode: 64 means the caller used
|
|
34
|
+
* the command wrongly, 1 means the command worked and the answer is no.
|
|
35
|
+
* `--framing fix-round` against a checklist that declares only
|
|
36
|
+
* `criteria-contract` is a well formed question with a negative answer.
|
|
37
|
+
*/
|
|
38
|
+
/** Exit code for usage errors, per BSD sysexits EX_USAGE. */
|
|
39
|
+
export declare const EX_USAGE = 64;
|
|
40
|
+
export declare function cmdChecklist(argv: string[]): number;
|
|
41
|
+
/** The shipped ids, for the usage line and for tests that must not hand-list them. */
|
|
42
|
+
export declare function declaredChecklistIds(): string[];
|