@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.
Files changed (207) hide show
  1. package/AGENTS.md +663 -0
  2. package/LICENSE +202 -0
  3. package/assurance-modes.yaml +299 -0
  4. package/checklists/clean-room.yaml +325 -0
  5. package/checklists/env-failure-diagnosis.yaml +68 -0
  6. package/checklists/flake-playbook.yaml +68 -0
  7. package/checklists/hazard-review.yaml +144 -0
  8. package/checklists/plan-review.yaml +103 -0
  9. package/dist/bin/tiphys.d.ts +2 -0
  10. package/dist/bin/tiphys.js +92 -0
  11. package/dist/src/adapters/load.d.ts +202 -0
  12. package/dist/src/adapters/load.js +440 -0
  13. package/dist/src/brief.d.ts +32 -0
  14. package/dist/src/brief.js +66 -0
  15. package/dist/src/checklists.d.ts +169 -0
  16. package/dist/src/checklists.js +310 -0
  17. package/dist/src/checks.d.ts +1539 -0
  18. package/dist/src/checks.js +5025 -0
  19. package/dist/src/cli.d.ts +9 -0
  20. package/dist/src/cli.js +68 -0
  21. package/dist/src/commands/brief.d.ts +92 -0
  22. package/dist/src/commands/brief.js +343 -0
  23. package/dist/src/commands/checklist.d.ts +42 -0
  24. package/dist/src/commands/checklist.js +168 -0
  25. package/dist/src/commands/cutover.d.ts +35 -0
  26. package/dist/src/commands/cutover.js +448 -0
  27. package/dist/src/commands/doctor.d.ts +264 -0
  28. package/dist/src/commands/doctor.js +1546 -0
  29. package/dist/src/commands/gates.d.ts +9 -0
  30. package/dist/src/commands/gates.js +360 -0
  31. package/dist/src/commands/init.d.ts +30 -0
  32. package/dist/src/commands/init.js +177 -0
  33. package/dist/src/commands/lock.d.ts +34 -0
  34. package/dist/src/commands/lock.js +340 -0
  35. package/dist/src/commands/mode.d.ts +40 -0
  36. package/dist/src/commands/mode.js +134 -0
  37. package/dist/src/commands/next.d.ts +130 -0
  38. package/dist/src/commands/next.js +597 -0
  39. package/dist/src/commands/plan.d.ts +20 -0
  40. package/dist/src/commands/plan.js +105 -0
  41. package/dist/src/commands/pool.d.ts +1 -0
  42. package/dist/src/commands/pool.js +139 -0
  43. package/dist/src/commands/resume.d.ts +1 -0
  44. package/dist/src/commands/resume.js +88 -0
  45. package/dist/src/commands/spawn.d.ts +1 -0
  46. package/dist/src/commands/spawn.js +195 -0
  47. package/dist/src/commands/status.d.ts +21 -0
  48. package/dist/src/commands/status.js +122 -0
  49. package/dist/src/commands/sync.d.ts +47 -0
  50. package/dist/src/commands/sync.js +341 -0
  51. package/dist/src/commands/teardown.d.ts +1 -0
  52. package/dist/src/commands/teardown.js +87 -0
  53. package/dist/src/commands/tuition.d.ts +21 -0
  54. package/dist/src/commands/tuition.js +218 -0
  55. package/dist/src/commands/validate.d.ts +78 -0
  56. package/dist/src/commands/validate.js +430 -0
  57. package/dist/src/commands/watch.d.ts +1 -0
  58. package/dist/src/commands/watch.js +172 -0
  59. package/dist/src/cutover.d.ts +584 -0
  60. package/dist/src/cutover.js +1444 -0
  61. package/dist/src/exclusion.d.ts +389 -0
  62. package/dist/src/exclusion.js +843 -0
  63. package/dist/src/exec/env.d.ts +278 -0
  64. package/dist/src/exec/env.js +334 -0
  65. package/dist/src/fleet.d.ts +223 -0
  66. package/dist/src/fleet.js +298 -0
  67. package/dist/src/gates/adapters/http-json.d.ts +5 -0
  68. package/dist/src/gates/adapters/http-json.js +283 -0
  69. package/dist/src/gates/adapters/migrations-command.d.ts +1 -0
  70. package/dist/src/gates/adapters/migrations-command.js +373 -0
  71. package/dist/src/gates/citations.d.ts +408 -0
  72. package/dist/src/gates/citations.js +1169 -0
  73. package/dist/src/gates/coverage.d.ts +375 -0
  74. package/dist/src/gates/coverage.js +836 -0
  75. package/dist/src/gates/credentials.d.ts +233 -0
  76. package/dist/src/gates/credentials.js +752 -0
  77. package/dist/src/gates/deploy.d.ts +1 -0
  78. package/dist/src/gates/deploy.js +33 -0
  79. package/dist/src/gates/gate-classes.d.ts +56 -0
  80. package/dist/src/gates/gate-classes.js +633 -0
  81. package/dist/src/gates/manifest.d.ts +99 -0
  82. package/dist/src/gates/manifest.js +208 -0
  83. package/dist/src/gates/merge-preconditions.d.ts +319 -0
  84. package/dist/src/gates/merge-preconditions.js +932 -0
  85. package/dist/src/gates/migrations.d.ts +1 -0
  86. package/dist/src/gates/migrations.js +36 -0
  87. package/dist/src/gates/pin.d.ts +114 -0
  88. package/dist/src/gates/pin.js +154 -0
  89. package/dist/src/gates/red-witness.d.ts +22 -0
  90. package/dist/src/gates/red-witness.js +482 -0
  91. package/dist/src/gates/release.d.ts +283 -0
  92. package/dist/src/gates/release.js +820 -0
  93. package/dist/src/gates/result.d.ts +116 -0
  94. package/dist/src/gates/result.js +91 -0
  95. package/dist/src/gates/run.d.ts +614 -0
  96. package/dist/src/gates/run.js +1614 -0
  97. package/dist/src/gates/schemas/citation-config.schema.json +59 -0
  98. package/dist/src/gates/schemas/coverage-config.schema.json +77 -0
  99. package/dist/src/gates/schemas/gate-manifest.schema.json +125 -0
  100. package/dist/src/gates/schemas/gate-result.schema.json +160 -0
  101. package/dist/src/gates/schemas/phase-declaration.schema.json +87 -0
  102. package/dist/src/gates/schemas/release-record.schema.json +119 -0
  103. package/dist/src/gates/schemas/verifier-config.schema.json +101 -0
  104. package/dist/src/gates/schemas/witness-spec.schema.json +110 -0
  105. package/dist/src/gates/scope.d.ts +131 -0
  106. package/dist/src/gates/scope.js +1018 -0
  107. package/dist/src/gates/suite.d.ts +217 -0
  108. package/dist/src/gates/suite.js +968 -0
  109. package/dist/src/gates/validate.d.ts +121 -0
  110. package/dist/src/gates/validate.js +414 -0
  111. package/dist/src/hooks.d.ts +84 -0
  112. package/dist/src/hooks.js +125 -0
  113. package/dist/src/index.d.ts +31 -0
  114. package/dist/src/index.js +30 -0
  115. package/dist/src/liveness.d.ts +321 -0
  116. package/dist/src/liveness.js +396 -0
  117. package/dist/src/lock.d.ts +256 -0
  118. package/dist/src/lock.js +792 -0
  119. package/dist/src/model-resolution.d.ts +159 -0
  120. package/dist/src/model-resolution.js +307 -0
  121. package/dist/src/modes.d.ts +149 -0
  122. package/dist/src/modes.js +258 -0
  123. package/dist/src/path-identity.d.ts +34 -0
  124. package/dist/src/path-identity.js +48 -0
  125. package/dist/src/plan.d.ts +73 -0
  126. package/dist/src/plan.js +153 -0
  127. package/dist/src/pool.d.ts +326 -0
  128. package/dist/src/pool.js +988 -0
  129. package/dist/src/roles.d.ts +461 -0
  130. package/dist/src/roles.js +776 -0
  131. package/dist/src/spawn.d.ts +482 -0
  132. package/dist/src/spawn.js +1003 -0
  133. package/dist/src/status.d.ts +116 -0
  134. package/dist/src/status.js +148 -0
  135. package/dist/src/task.d.ts +504 -0
  136. package/dist/src/task.js +307 -0
  137. package/dist/src/teardown.d.ts +39 -0
  138. package/dist/src/teardown.js +422 -0
  139. package/dist/src/tuition.d.ts +159 -0
  140. package/dist/src/tuition.js +311 -0
  141. package/dist/src/validate.d.ts +263 -0
  142. package/dist/src/validate.js +742 -0
  143. package/dist/src/version.d.ts +3 -0
  144. package/dist/src/version.js +38 -0
  145. package/dist/src/watcher.d.ts +275 -0
  146. package/dist/src/watcher.js +849 -0
  147. package/dist/src/witness/run.d.ts +299 -0
  148. package/dist/src/witness/run.js +1373 -0
  149. package/dist/src/witness/spec.d.ts +270 -0
  150. package/dist/src/witness/spec.js +475 -0
  151. package/dist/tsconfig.src.tsbuildinfo +1 -0
  152. package/gate-registry.yaml +526 -0
  153. package/gates.manifest.json +257 -0
  154. package/package.json +72 -3
  155. package/role-model-config.yaml +88 -0
  156. package/roles/README.md +128 -0
  157. package/roles/_shared-dispatch-contract.md +87 -0
  158. package/roles/adversarial-plan-reviewer.md +80 -0
  159. package/roles/clean-room-reviewer.md +140 -0
  160. package/roles/implementer.md +463 -0
  161. package/roles/investigator.md +138 -0
  162. package/roles/plan-writer.md +95 -0
  163. package/schemas/README.md +82 -0
  164. package/schemas/assurance-modes.schema.json +264 -0
  165. package/schemas/charter.schema.json +185 -0
  166. package/schemas/checklist.schema.json +114 -0
  167. package/schemas/cutover-state.schema.json +64 -0
  168. package/schemas/decision-record.schema.json +88 -0
  169. package/schemas/executor-record.schema.json +36 -0
  170. package/schemas/final-report.schema.json +90 -0
  171. package/schemas/finding.schema.json +106 -0
  172. package/schemas/gate-registry.schema.json +260 -0
  173. package/schemas/mechanism-index.schema.json +94 -0
  174. package/schemas/model-resolution.schema.json +362 -0
  175. package/schemas/plan.schema.json +300 -0
  176. package/schemas/report.schema.json +579 -0
  177. package/schemas/role-brief.schema.json +105 -0
  178. package/schemas/role-model-config.schema.json +90 -0
  179. package/schemas/status-line.schema.json +40 -0
  180. package/schemas/tuition.schema.json +191 -0
  181. package/schemas/verdict.schema.json +295 -0
  182. package/schemas/work-history.schema.json +183 -0
  183. package/schemas/write-bypass.schema.json +69 -0
  184. package/templates/charter.example.yaml +54 -0
  185. package/templates/decision-record.example.yaml +27 -0
  186. package/templates/final-report.example.yaml +80 -0
  187. package/templates/plan.example.yaml +87 -0
  188. package/templates/report.example.yaml +236 -0
  189. package/templates/warnings.md +74 -0
  190. package/templates/work-history.example.yaml +185 -0
  191. package/tuition/README.md +76 -0
  192. package/tuition/T-001.yaml +48 -0
  193. package/tuition/T-002.yaml +51 -0
  194. package/tuition/T-003.yaml +100 -0
  195. package/tuition/T-004.yaml +52 -0
  196. package/tuition/T-005.yaml +72 -0
  197. package/tuition/T-006.yaml +81 -0
  198. package/tuition/T-007.yaml +56 -0
  199. package/tuition/T-008.yaml +111 -0
  200. package/tuition/T-009.yaml +50 -0
  201. package/tuition/T-015.yaml +36 -0
  202. package/tuition/T-016.yaml +36 -0
  203. package/tuition/T-017.yaml +46 -0
  204. package/tuition/T-018.yaml +84 -0
  205. package/tuition/T-021.yaml +40 -0
  206. package/tuition/T-022.yaml +36 -0
  207. 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>;
@@ -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[];