@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,159 @@
1
+ import type { Fleet } from "./fleet.ts";
2
+ /**
3
+ * THE MODEL-RESOLUTION RECORD, KERNEL SIDE (kernel plan M4, M4-P7;
4
+ * delivery/plan/kernel-plan-m4.md:1018, discharging M4-D-06's four parts at
5
+ * delivery/plan/m4-intake.md:732).
6
+ *
7
+ * WHAT THIS MODULE IS AND, MORE IMPORTANTLY, WHAT IT IS NOT. It READS a
8
+ * record a harness adapter wrote. It holds NO vocabulary: no tier-to-model
9
+ * table, no model-to-family table, and no vendor name of any kind. The whole
10
+ * vocabulary lives in the plugin (plugin/src/vocabulary.ts) and this module
11
+ * dereferences a vocabulary's IDENTITY and never its CONTENT. That single
12
+ * property is what makes a second harness plugin possible at all: a mapping
13
+ * here would close off every harness that is not the one it names
14
+ * (delivery/plan/m4-intake.md:377), and `test/schemas.test.ts` already asserts
15
+ * the absence over the whole shipped surface rather than trusting this
16
+ * paragraph.
17
+ *
18
+ * THE ORDER OF THE CHECKS IS PART OF THE CONTRACT, and it is the release
19
+ * record's order (src/gates/release.ts): the SUBJECT ECHO is compared field by
20
+ * field BEFORE anything reads the resolved identity. A record about another
21
+ * task is not a record with a wrong answer, it is a record about something
22
+ * else, and reading its answer first is how a misattributed family token
23
+ * reaches a decorrelation check as though it belonged there.
24
+ *
25
+ * WHY `observed` IS NOT `attested`, stated here because the vocabulary is the
26
+ * thing most likely to be widened later by someone who did not read M4-P1.
27
+ * The probe measured that the observation channel is harness-written, that it
28
+ * can be EMPTY at hook time under load, and that a process at the agent's own
29
+ * uid can rewrite it in place so the result is byte-shaped exactly like the
30
+ * truth (delivery/verification/m4-prototype-probes.md:410). So `observed`
31
+ * ranks above `self-reported`, which costs nothing to forge, and below any
32
+ * notion of attestation, which would need a signer this environment has not
33
+ * been shown to have.
34
+ */
35
+ /** The record's file name inside the task directory. */
36
+ export declare const MODEL_RESOLUTION_RECORD_NAME = "model-resolution.json";
37
+ /** The contract versions this kernel accepts. */
38
+ export declare const MODEL_RESOLUTION_CONTRACT_VERSIONS: readonly string[];
39
+ /**
40
+ * Where the record lives: `tasks/<id>/model-resolution.json`, NEVER inside the
41
+ * worktree (M4-P7 criterion 5, src/task.ts:31 and FM-059). The task directory
42
+ * sits outside the worktree precisely so the pool's dirty check needs no
43
+ * exemption list for the kernel's own injected files, and a record written
44
+ * into the worktree would be indistinguishable from the agent's own work.
45
+ */
46
+ export declare function modelResolutionPath(fleet: Fleet, taskId: string): string;
47
+ /** The launch request fields the record's subject echo is compared against. */
48
+ export interface ModelResolutionSubject {
49
+ taskId: string;
50
+ role: string;
51
+ requestedTier: string;
52
+ }
53
+ export interface VocabularyIdentity {
54
+ id: string;
55
+ version: number;
56
+ }
57
+ /** What one read of the record produced. */
58
+ export type ModelResolutionRead = {
59
+ kind: "read";
60
+ record: Record<string, unknown>;
61
+ } | {
62
+ kind: "error";
63
+ reason: string;
64
+ };
65
+ /** What one acceptance decision produced. */
66
+ export type ModelResolutionAcceptance = {
67
+ kind: "accepted";
68
+ family: string | undefined;
69
+ provenance: string;
70
+ } | {
71
+ kind: "refused";
72
+ reason: string;
73
+ };
74
+ /** What one cross-record family comparison produced. */
75
+ export type FamilyComparison = {
76
+ kind: "compared";
77
+ differ: boolean;
78
+ families: [string, string];
79
+ } | {
80
+ kind: "refused";
81
+ reason: string;
82
+ };
83
+ /**
84
+ * Read the record for a task.
85
+ *
86
+ * AN ABSENT RECORD IS AN ERROR AND IS NEVER GREEN AND NEVER NOT-APPLICABLE
87
+ * (M4-P7 criterion 5). This is not a new rule and deliberately not a second
88
+ * implementation of one: src/gates/release.ts:609 already produces the
89
+ * sentence for the same hazard on the release seam, and the wording is kept
90
+ * in the same shape so that a reader who has met one meets the other.
91
+ * `test/model-resolution.test.ts` drives the real release gate to capture that
92
+ * sentence and derives its assertion from the capture, rather than pinning a
93
+ * hand-written copy of it.
94
+ *
95
+ * THE READ GOES THROUGH `readRegularFileIfPresent`, which is M2-C-6 and the
96
+ * mechanism-index row `reading-a-path-whose-type-is-not-established`: a named
97
+ * pipe at this path is a REPORTED refusal, never a blocked open.
98
+ */
99
+ export declare function readModelResolutionRecord(path: string, writerExitCode: number): ModelResolutionRead;
100
+ /**
101
+ * The vocabulary IDENTITY of a record, and nothing else about the vocabulary.
102
+ *
103
+ * TWO FIELDS ARE READ AND THEY ARE NAMED HERE RATHER THAN SPREAD, which is
104
+ * the whole of criterion 4's mechanical half. A spread, an `Object.keys`, a
105
+ * `JSON.stringify` or a `structuredClone` of the vocabulary object would
106
+ * ENUMERATE it, and enumeration is dereferencing the content whatever the
107
+ * intent. `test/model-resolution.test.ts` hands this function a Proxy that
108
+ * records every property read and asserts the recorded set against these two
109
+ * names, so a later edit that reaches for a third reddens.
110
+ */
111
+ export declare function vocabularyIdentity(record: Record<string, unknown>): VocabularyIdentity | undefined;
112
+ /**
113
+ * Accept or refuse one record against the launch request the kernel holds.
114
+ *
115
+ * FOUR REFUSALS, IN THIS ORDER, and the order is the point.
116
+ *
117
+ * 1. THE CONTRACT VERSION, because a record written to a contract this kernel
118
+ * does not know is not a record this kernel can read fields out of.
119
+ * 2. THE SUBJECT ECHO, FIELD BY FIELD, BEFORE THE OUTCOME IS READ. This is
120
+ * src/gates/schemas/release-record.schema.json:26's misattribution guard.
121
+ * 3. THE VOCABULARY IDENTITY, because a family token with no vocabulary is a
122
+ * token nobody can say the meaning of.
123
+ * 4. THE PROVENANCE, which is M4-P7 criterion 6 member TWO. Member one (a
124
+ * record claiming `observed` with no observation at all) is the schema's
125
+ * and is a missing field. THIS is the present-but-inconsistent half, and a
126
+ * schema-only guard passes it green: an observation about another task, or
127
+ * naming a model other than the one the record claims, is a self-report
128
+ * with a decoration on it.
129
+ */
130
+ export declare function acceptModelResolution(record: Record<string, unknown>, subject: ModelResolutionSubject): ModelResolutionAcceptance;
131
+ /**
132
+ * Compare two accepted records' family tokens (M4-P7 criterion 3).
133
+ *
134
+ * TWO VOCABULARIES ARE NOT COMPARABLE AND THE REFUSAL NAMES BOTH IDS. A family
135
+ * token means whatever the vocabulary that minted it says it means, so
136
+ * comparing a token from one against a token from another is comparing two
137
+ * strings and calling the result a fact about models. That comparison is what
138
+ * `check-dual-review`'s decorrelation assertion rests on, and DR-0038 exists
139
+ * for exactly the environment where the two reviews come from different
140
+ * places, so a silent cross-vocabulary compare would make the assertion
141
+ * meaningless precisely where it is load-bearing.
142
+ *
143
+ * ONLY `id` IS READ HERE. The version is part of the identity a reader may
144
+ * print and is deliberately NOT part of the comparison: a vocabulary that
145
+ * added a family in v2 did not change what its v1 tokens mean, and refusing
146
+ * on version would turn a routine vocabulary revision into an outage.
147
+ */
148
+ export declare function compareResolvedFamilies(left: Record<string, unknown>, right: Record<string, unknown>): FamilyComparison;
149
+ /**
150
+ * The value closeout copies into a verdict's `produced-by` (M4-P7 criterion 8).
151
+ *
152
+ * VERBATIM, AND THE FUNCTION EXISTS SO THAT "VERBATIM" IS TESTABLE. DR-0031
153
+ * requires a pull request to carry all its own evidence, so the family token
154
+ * has to reach the verdict document rather than being left in a task
155
+ * directory that no reviewer of the pull request can see. A copy that
156
+ * normalised, lowercased or prefixed the token would satisfy every reading of
157
+ * that sentence and would break the one comparison the token exists for.
158
+ */
159
+ export declare function producedByFromRecord(record: Record<string, unknown>): string | undefined;
@@ -0,0 +1,307 @@
1
+ import { join } from "node:path";
2
+ import { readRegularFileIfPresent, taskDir } from "./task.js";
3
+ /**
4
+ * THE MODEL-RESOLUTION RECORD, KERNEL SIDE (kernel plan M4, M4-P7;
5
+ * delivery/plan/kernel-plan-m4.md:1018, discharging M4-D-06's four parts at
6
+ * delivery/plan/m4-intake.md:732).
7
+ *
8
+ * WHAT THIS MODULE IS AND, MORE IMPORTANTLY, WHAT IT IS NOT. It READS a
9
+ * record a harness adapter wrote. It holds NO vocabulary: no tier-to-model
10
+ * table, no model-to-family table, and no vendor name of any kind. The whole
11
+ * vocabulary lives in the plugin (plugin/src/vocabulary.ts) and this module
12
+ * dereferences a vocabulary's IDENTITY and never its CONTENT. That single
13
+ * property is what makes a second harness plugin possible at all: a mapping
14
+ * here would close off every harness that is not the one it names
15
+ * (delivery/plan/m4-intake.md:377), and `test/schemas.test.ts` already asserts
16
+ * the absence over the whole shipped surface rather than trusting this
17
+ * paragraph.
18
+ *
19
+ * THE ORDER OF THE CHECKS IS PART OF THE CONTRACT, and it is the release
20
+ * record's order (src/gates/release.ts): the SUBJECT ECHO is compared field by
21
+ * field BEFORE anything reads the resolved identity. A record about another
22
+ * task is not a record with a wrong answer, it is a record about something
23
+ * else, and reading its answer first is how a misattributed family token
24
+ * reaches a decorrelation check as though it belonged there.
25
+ *
26
+ * WHY `observed` IS NOT `attested`, stated here because the vocabulary is the
27
+ * thing most likely to be widened later by someone who did not read M4-P1.
28
+ * The probe measured that the observation channel is harness-written, that it
29
+ * can be EMPTY at hook time under load, and that a process at the agent's own
30
+ * uid can rewrite it in place so the result is byte-shaped exactly like the
31
+ * truth (delivery/verification/m4-prototype-probes.md:410). So `observed`
32
+ * ranks above `self-reported`, which costs nothing to forge, and below any
33
+ * notion of attestation, which would need a signer this environment has not
34
+ * been shown to have.
35
+ */
36
+ /** The record's file name inside the task directory. */
37
+ export const MODEL_RESOLUTION_RECORD_NAME = "model-resolution.json";
38
+ /** The contract versions this kernel accepts. */
39
+ export const MODEL_RESOLUTION_CONTRACT_VERSIONS = ["1"];
40
+ /**
41
+ * Where the record lives: `tasks/<id>/model-resolution.json`, NEVER inside the
42
+ * worktree (M4-P7 criterion 5, src/task.ts:31 and FM-059). The task directory
43
+ * sits outside the worktree precisely so the pool's dirty check needs no
44
+ * exemption list for the kernel's own injected files, and a record written
45
+ * into the worktree would be indistinguishable from the agent's own work.
46
+ */
47
+ export function modelResolutionPath(fleet, taskId) {
48
+ return join(taskDir(fleet, taskId), MODEL_RESOLUTION_RECORD_NAME);
49
+ }
50
+ function asRecord(value) {
51
+ return typeof value === "object" && value !== null && !Array.isArray(value)
52
+ ? value
53
+ : undefined;
54
+ }
55
+ function stringAt(container, field) {
56
+ const value = container?.[field];
57
+ return typeof value === "string" ? value : undefined;
58
+ }
59
+ /**
60
+ * Read the record for a task.
61
+ *
62
+ * AN ABSENT RECORD IS AN ERROR AND IS NEVER GREEN AND NEVER NOT-APPLICABLE
63
+ * (M4-P7 criterion 5). This is not a new rule and deliberately not a second
64
+ * implementation of one: src/gates/release.ts:609 already produces the
65
+ * sentence for the same hazard on the release seam, and the wording is kept
66
+ * in the same shape so that a reader who has met one meets the other.
67
+ * `test/model-resolution.test.ts` drives the real release gate to capture that
68
+ * sentence and derives its assertion from the capture, rather than pinning a
69
+ * hand-written copy of it.
70
+ *
71
+ * THE READ GOES THROUGH `readRegularFileIfPresent`, which is M2-C-6 and the
72
+ * mechanism-index row `reading-a-path-whose-type-is-not-established`: a named
73
+ * pipe at this path is a REPORTED refusal, never a blocked open.
74
+ */
75
+ export function readModelResolutionRecord(path, writerExitCode) {
76
+ const read = readRegularFileIfPresent(path);
77
+ if (read.kind === "absent") {
78
+ return {
79
+ kind: "error",
80
+ reason: `fail-closed rule 1: adapter exited ${String(writerExitCode)} without ` +
81
+ `writing a model-resolution record at ${path}; exit 0 with no ` +
82
+ `record is error, not success`,
83
+ };
84
+ }
85
+ if (read.kind === "refused") {
86
+ return { kind: "error", reason: read.reason };
87
+ }
88
+ let parsed;
89
+ try {
90
+ parsed = JSON.parse(read.body);
91
+ }
92
+ catch (error) {
93
+ return {
94
+ kind: "error",
95
+ reason: `${path} is not readable as JSON: ${String(error).replace(/\s+/g, " ").trim()}`,
96
+ };
97
+ }
98
+ const record = asRecord(parsed);
99
+ if (record === undefined) {
100
+ return { kind: "error", reason: `${path} does not hold a JSON object` };
101
+ }
102
+ return { kind: "read", record };
103
+ }
104
+ /**
105
+ * The vocabulary IDENTITY of a record, and nothing else about the vocabulary.
106
+ *
107
+ * TWO FIELDS ARE READ AND THEY ARE NAMED HERE RATHER THAN SPREAD, which is
108
+ * the whole of criterion 4's mechanical half. A spread, an `Object.keys`, a
109
+ * `JSON.stringify` or a `structuredClone` of the vocabulary object would
110
+ * ENUMERATE it, and enumeration is dereferencing the content whatever the
111
+ * intent. `test/model-resolution.test.ts` hands this function a Proxy that
112
+ * records every property read and asserts the recorded set against these two
113
+ * names, so a later edit that reaches for a third reddens.
114
+ */
115
+ export function vocabularyIdentity(record) {
116
+ const resolved = asRecord(record["resolved"]);
117
+ const vocabulary = asRecord(resolved?.["vocabulary"]);
118
+ if (vocabulary === undefined) {
119
+ return undefined;
120
+ }
121
+ const id = vocabulary["id"];
122
+ const version = vocabulary["version"];
123
+ if (typeof id !== "string" || typeof version !== "number") {
124
+ return undefined;
125
+ }
126
+ return { id, version };
127
+ }
128
+ /**
129
+ * Accept or refuse one record against the launch request the kernel holds.
130
+ *
131
+ * FOUR REFUSALS, IN THIS ORDER, and the order is the point.
132
+ *
133
+ * 1. THE CONTRACT VERSION, because a record written to a contract this kernel
134
+ * does not know is not a record this kernel can read fields out of.
135
+ * 2. THE SUBJECT ECHO, FIELD BY FIELD, BEFORE THE OUTCOME IS READ. This is
136
+ * src/gates/schemas/release-record.schema.json:26's misattribution guard.
137
+ * 3. THE VOCABULARY IDENTITY, because a family token with no vocabulary is a
138
+ * token nobody can say the meaning of.
139
+ * 4. THE PROVENANCE, which is M4-P7 criterion 6 member TWO. Member one (a
140
+ * record claiming `observed` with no observation at all) is the schema's
141
+ * and is a missing field. THIS is the present-but-inconsistent half, and a
142
+ * schema-only guard passes it green: an observation about another task, or
143
+ * naming a model other than the one the record claims, is a self-report
144
+ * with a decoration on it.
145
+ */
146
+ export function acceptModelResolution(record, subject) {
147
+ const contractVersion = stringAt(record, "contractVersion");
148
+ if (contractVersion === undefined || !MODEL_RESOLUTION_CONTRACT_VERSIONS.includes(contractVersion)) {
149
+ return {
150
+ kind: "refused",
151
+ reason: `the record declares contract version ${String(contractVersion)}, and this ` +
152
+ `kernel accepts ${MODEL_RESOLUTION_CONTRACT_VERSIONS.join(", ")}`,
153
+ };
154
+ }
155
+ const echo = asRecord(record["subject"]);
156
+ if (echo === undefined) {
157
+ return {
158
+ kind: "refused",
159
+ reason: "the record carries no subject echo, so what it is about could not be established",
160
+ };
161
+ }
162
+ const echoed = [
163
+ ["taskId", stringAt(echo, "taskId")],
164
+ ["role", stringAt(echo, "role")],
165
+ ["requestedTier", stringAt(echo, "requestedTier")],
166
+ ];
167
+ for (const [field, value] of echoed) {
168
+ if (value !== subject[field]) {
169
+ return {
170
+ kind: "refused",
171
+ reason: `the record's subject echo says ${field} is ${String(value)} and the ` +
172
+ `launch request says ${subject[field]}, so this record is about a ` +
173
+ `different subject`,
174
+ };
175
+ }
176
+ }
177
+ const identity = vocabularyIdentity(record);
178
+ if (identity === undefined) {
179
+ return {
180
+ kind: "refused",
181
+ reason: "the record carries no resolved.vocabulary identity, so its family token " +
182
+ "belongs to no stated vocabulary and cannot be compared with any other",
183
+ };
184
+ }
185
+ const resolved = asRecord(record["resolved"]);
186
+ const provenance = stringAt(resolved, "provenance");
187
+ const family = stringAt(resolved, "family");
188
+ const model = stringAt(resolved, "model");
189
+ if (provenance === "observed") {
190
+ const observation = asRecord(resolved?.["observation"]);
191
+ if (observation === undefined) {
192
+ return {
193
+ kind: "refused",
194
+ reason: "the record claims provenance observed and carries no observation, so " +
195
+ "the claim is a self-report wearing the word observed",
196
+ };
197
+ }
198
+ const observedTask = stringAt(observation, "taskId");
199
+ if (observedTask !== subject.taskId) {
200
+ return {
201
+ kind: "refused",
202
+ reason: `the record claims provenance observed and its observation is about ` +
203
+ `task ${String(observedTask)}, while the echoed request is about task ` +
204
+ `${subject.taskId}, so the observation contradicts the subject it is ` +
205
+ `offered as evidence for`,
206
+ };
207
+ }
208
+ const observedModel = stringAt(observation, "model");
209
+ if (observedModel !== model) {
210
+ return {
211
+ kind: "refused",
212
+ reason: `the record claims model ${String(model)} with provenance observed and ` +
213
+ `its observation saw ${String(observedModel)}, so the claimed identity ` +
214
+ `is not the observed one`,
215
+ };
216
+ }
217
+ }
218
+ else if (provenance === "self-reported") {
219
+ if (resolved?.["observation"] !== undefined) {
220
+ return {
221
+ kind: "refused",
222
+ reason: "the record ranks itself self-reported and carries an observation, so " +
223
+ "it claims evidence it also says it does not have",
224
+ };
225
+ }
226
+ }
227
+ else if (provenance === "unresolved") {
228
+ if (family !== undefined || model !== undefined) {
229
+ return {
230
+ kind: "refused",
231
+ reason: "the record reports the identity unresolved and still names a family " +
232
+ "or a model, which is the quiet fallback to the forgeable value that " +
233
+ "an unresolved observation exists to prevent",
234
+ };
235
+ }
236
+ }
237
+ else {
238
+ return {
239
+ kind: "refused",
240
+ reason: `the record declares provenance ${String(provenance)}, which this kernel has no rule for`,
241
+ };
242
+ }
243
+ return { kind: "accepted", family, provenance: provenance };
244
+ }
245
+ /**
246
+ * Compare two accepted records' family tokens (M4-P7 criterion 3).
247
+ *
248
+ * TWO VOCABULARIES ARE NOT COMPARABLE AND THE REFUSAL NAMES BOTH IDS. A family
249
+ * token means whatever the vocabulary that minted it says it means, so
250
+ * comparing a token from one against a token from another is comparing two
251
+ * strings and calling the result a fact about models. That comparison is what
252
+ * `check-dual-review`'s decorrelation assertion rests on, and DR-0038 exists
253
+ * for exactly the environment where the two reviews come from different
254
+ * places, so a silent cross-vocabulary compare would make the assertion
255
+ * meaningless precisely where it is load-bearing.
256
+ *
257
+ * ONLY `id` IS READ HERE. The version is part of the identity a reader may
258
+ * print and is deliberately NOT part of the comparison: a vocabulary that
259
+ * added a family in v2 did not change what its v1 tokens mean, and refusing
260
+ * on version would turn a routine vocabulary revision into an outage.
261
+ */
262
+ export function compareResolvedFamilies(left, right) {
263
+ const leftIdentity = vocabularyIdentity(left);
264
+ const rightIdentity = vocabularyIdentity(right);
265
+ if (leftIdentity === undefined || rightIdentity === undefined) {
266
+ return {
267
+ kind: "refused",
268
+ reason: "one of the two records carries no resolved.vocabulary identity, so the " +
269
+ "two family tokens belong to no stated common vocabulary",
270
+ };
271
+ }
272
+ if (leftIdentity.id !== rightIdentity.id) {
273
+ return {
274
+ kind: "refused",
275
+ reason: `the two records name different vocabularies, ${leftIdentity.id} and ` +
276
+ `${rightIdentity.id}, so their family tokens are not comparable and no ` +
277
+ `decorrelation claim can be made from them`,
278
+ };
279
+ }
280
+ const leftFamily = stringAt(asRecord(left["resolved"]), "family");
281
+ const rightFamily = stringAt(asRecord(right["resolved"]), "family");
282
+ if (leftFamily === undefined || rightFamily === undefined) {
283
+ return {
284
+ kind: "refused",
285
+ reason: "one of the two records names no resolved family, so there is nothing to " +
286
+ "compare; an unresolved identity is not a difference and not a match",
287
+ };
288
+ }
289
+ return {
290
+ kind: "compared",
291
+ differ: leftFamily !== rightFamily,
292
+ families: [leftFamily, rightFamily],
293
+ };
294
+ }
295
+ /**
296
+ * The value closeout copies into a verdict's `produced-by` (M4-P7 criterion 8).
297
+ *
298
+ * VERBATIM, AND THE FUNCTION EXISTS SO THAT "VERBATIM" IS TESTABLE. DR-0031
299
+ * requires a pull request to carry all its own evidence, so the family token
300
+ * has to reach the verdict document rather than being left in a task
301
+ * directory that no reviewer of the pull request can see. A copy that
302
+ * normalised, lowercased or prefixed the token would satisfy every reading of
303
+ * that sentence and would break the one comparison the token exists for.
304
+ */
305
+ export function producedByFromRecord(record) {
306
+ return stringAt(asRecord(record["resolved"]), "family");
307
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * READING THE SHIPPED ASSURANCE MODE DEFINITIONS (kernel plan M3, M3-P3
3
+ * step 5).
4
+ *
5
+ * `assurance-modes.yaml` ships at the package root, beside `gate-registry.yaml`
6
+ * and the `schemas/` directory. This module locates it, decodes it, and
7
+ * answers one question: what does a declared mode require. Nothing here
8
+ * RESOLVES a mode into behaviour and nothing here enforces one. M3 never
9
+ * executes `direct-pr` or `local-only`, and building an enforcement engine for
10
+ * a mode this milestone never enters is the M1-P3 failure the plan is trying
11
+ * not to repeat.
12
+ *
13
+ * NO VALIDATION HAPPENS HERE, deliberately. `tiphys validate --type
14
+ * assurance-modes` is the command that says whether the document is
15
+ * well-formed, and duplicating its rules in a reader would produce a second
16
+ * opinion to keep in sync. This module reads what is there and reports what it
17
+ * cannot find.
18
+ */
19
+ /** The shipped document's basename, at the package root. */
20
+ export declare const MODES_FILENAME = "assurance-modes.yaml";
21
+ /**
22
+ * Locate the package root by walking UP from this module and testing for the
23
+ * shipped document.
24
+ *
25
+ * The depth differs between the two layouts this code runs in: from source it
26
+ * is `src/` and the root is one level up, and from the built entry it is
27
+ * `dist/src/` and the root is two levels up. Counting `..` would be right in
28
+ * exactly one of them, which is the layout-dependent break
29
+ * `schemasDirectory()` already documents. Walking up and TESTING is right in
30
+ * both, and in a relocated copy as well.
31
+ */
32
+ export declare function packageRoot(): string;
33
+ /** One mode as the document declares it. Fields absent from the document stay absent. */
34
+ export interface Mode {
35
+ id: string;
36
+ declaredBy: string;
37
+ pipeline: string[];
38
+ skips: string[];
39
+ gateSets: string[];
40
+ mergeAuthority: string;
41
+ grantedBy?: string;
42
+ conditions?: string[];
43
+ reviewContracts?: string[];
44
+ escalationBounds?: Record<string, unknown>;
45
+ }
46
+ export type ModesRead =
47
+ /**
48
+ * `raw` is the DECODED DOCUMENT before this module projects it into `Mode`
49
+ * records. It is returned because a caller must be able to validate what it
50
+ * is about to serve, and the projection is lossy by design (it drops
51
+ * anything the projection does not name). Handing a caller only the
52
+ * projection would force it to validate a shape no schema describes.
53
+ */
54
+ {
55
+ ok: true;
56
+ path: string;
57
+ raw: unknown;
58
+ modes: Mode[];
59
+ } | {
60
+ ok: false;
61
+ reason: string;
62
+ };
63
+ /** Read and decode the shipped document, or say why it could not be read. */
64
+ export declare function readModes(path?: string): ModesRead;
65
+ /**
66
+ * Where the rendered document came from. `shippedDocument` is true only when
67
+ * the reader was given no `--file` and therefore read the kernel's OWN
68
+ * `assurance-modes.yaml` from the package root.
69
+ *
70
+ * It is a REQUIRED parameter rather than an option with a default, because the
71
+ * execution-status line below is a claim about a specific document and a
72
+ * default would let a caller make that claim by omission.
73
+ */
74
+ export interface RenderContext {
75
+ shippedDocument: boolean;
76
+ }
77
+ /**
78
+ * The execution status of one mode, DERIVED rather than looked up in a list of
79
+ * ids (CR-004 item 2, DR-0020).
80
+ *
81
+ * Two facts are available and both are checkable by the reader: whether this is
82
+ * the kernel's own document, and whether this mode IS the one blueprint section
83
+ * 8 names as the un-downgraded process, "The current proven process is the
84
+ * definition of `full`. Downgrades are declared, never improvised."
85
+ *
86
+ * WHY THE NAME AND NOT THE SKIP COUNT (CR-002, round 9). This function used
87
+ * `mode.skips.length === 0` as its proxy for "this is the un-downgraded mode".
88
+ * The proxy held only because `full` happened to be the only mode with an empty
89
+ * list, nothing enforced that, and `skips[]` is shipped DATA. Two measured
90
+ * consequences, both at exit 0 with every registry gate green: giving `full` one
91
+ * bogus `skips[]` entry made this function say that no phase of the tiphys
92
+ * project had ever been delivered under `full`, which is false about the mode
93
+ * this project has delivered every phase under; and giving `direct-pr` an empty
94
+ * `skips[]` made a mode nobody has ever entered claim to be the one the project
95
+ * follows, with `merge-authority: owner` printed beneath it, which is not the
96
+ * regime in force (DR-0015). Blueprint section 8 defines `full` BY NAME, so the
97
+ * name is the primary fact and the skip count is a consequence of it.
98
+ *
99
+ * THE COUNT IS STILL REPORTED, as a fact about the mode rather than as the
100
+ * ground of the claim. That distinction is the whole finding: a number may be
101
+ * shown without being believed.
102
+ *
103
+ * WHAT MAKES THE `full` SENTENCE TRUE IS DATA, AND IT IS GUARDED IN TWO PLACES
104
+ * THAT COVER DIFFERENT DOCUMENTS. Keying off the name moves the burden: the
105
+ * claim is only as good as the `full` in the document being served genuinely
106
+ * being un-downgraded.
107
+ *
108
+ * The VALIDATOR carries it for EVERY document (round 10, CRB9-02).
109
+ * `mode-no-undeclared-downgrade` rejects a `skips[]` entry that the same mode's
110
+ * pipeline runs, and also one that the reference does not run; on the reference
111
+ * those two exhaust the cases, so a `full` declaring any skipped stage at all is
112
+ * refused and this sentence is never printed over it. Until that second part
113
+ * existed, a `full` whose stage had MOVED from `pipeline` into `skips` was an
114
+ * HONEST downgrade that validated at exit 0, and `tiphys mode show --mode full`
115
+ * printed this sentence about fifteen lines above a `skips: deploy-verify` row.
116
+ *
117
+ * A REGISTERED TEST carries it for THIS repository's own document, asserting the
118
+ * shipped `full` declares no skipped stage. It is kept rather than superseded:
119
+ * a test guards the document, the check guards every other document, and the
120
+ * failure this pair exists to prevent is a property asserted where it is stated
121
+ * and not enforced where it is consumed.
122
+ *
123
+ * WHAT THIS DELIBERATELY DOES NOT SAY. It does not say that tiphys runs
124
+ * anything: nothing runs on tiphys before M4. The un-downgraded mode of the
125
+ * kernel's own document is the process the tiphys PROJECT follows for its own
126
+ * delivery; the downgraded ones have never been entered at all. And for a
127
+ * document that is not the kernel's own, the answer is that tiphys does not
128
+ * know, because it does not.
129
+ */
130
+ export declare function executionStatus(mode: Mode, context: RenderContext): string;
131
+ /**
132
+ * The standing limits of this release, printed on every invocation.
133
+ *
134
+ * IT SAYS ONLY WHAT THE SHIPPED SCHEMAS DO. The vocabularies really are closed
135
+ * enums, so "a document naming any other id is rejected" is the enum's own
136
+ * behaviour and not a claim about intent. The M4 sentence is attributed to
137
+ * DR-0020 rather than stated as a property of the code.
138
+ */
139
+ export declare const RELEASE_LIMITS: string;
140
+ /**
141
+ * Render one mode for a human or for a brief.
142
+ *
143
+ * THE SHAPE IS PART OF THE CONTRACT, because criterion 2 asserts over it: a
144
+ * section is a line ending in a colon at column zero, and its items are the
145
+ * lines indented by exactly two spaces beneath it. That makes "prints exactly
146
+ * the twelve stage ids in order" something a test can extract rather than
147
+ * something a reader has to eyeball.
148
+ */
149
+ export declare function renderMode(mode: Mode, context: RenderContext): string[];