@tiphys/kernel 0.1.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 (94) hide show
  1. package/AGENTS.md +56 -4
  2. package/assurance-modes.yaml +23 -2
  3. package/dist/bin/tiphys.js +86 -8
  4. package/dist/src/adapters/load.d.ts +202 -0
  5. package/dist/src/adapters/load.js +440 -0
  6. package/dist/src/brief.js +27 -20
  7. package/dist/src/checks.d.ts +720 -9
  8. package/dist/src/checks.js +1874 -163
  9. package/dist/src/cli.js +11 -0
  10. package/dist/src/commands/brief.js +27 -4
  11. package/dist/src/commands/cutover.d.ts +35 -0
  12. package/dist/src/commands/cutover.js +448 -0
  13. package/dist/src/commands/doctor.d.ts +229 -0
  14. package/dist/src/commands/doctor.js +968 -27
  15. package/dist/src/commands/init.d.ts +3 -3
  16. package/dist/src/commands/init.js +57 -8
  17. package/dist/src/commands/lock.d.ts +33 -0
  18. package/dist/src/commands/lock.js +117 -6
  19. package/dist/src/commands/next.d.ts +130 -0
  20. package/dist/src/commands/next.js +597 -0
  21. package/dist/src/commands/pool.js +12 -1
  22. package/dist/src/commands/resume.d.ts +1 -0
  23. package/dist/src/commands/resume.js +88 -0
  24. package/dist/src/commands/spawn.js +51 -2
  25. package/dist/src/commands/status.d.ts +6 -4
  26. package/dist/src/commands/status.js +6 -4
  27. package/dist/src/commands/sync.d.ts +47 -0
  28. package/dist/src/commands/sync.js +341 -0
  29. package/dist/src/commands/teardown.js +10 -2
  30. package/dist/src/commands/validate.js +70 -0
  31. package/dist/src/cutover.d.ts +584 -0
  32. package/dist/src/cutover.js +1444 -0
  33. package/dist/src/exclusion.d.ts +389 -0
  34. package/dist/src/exclusion.js +843 -0
  35. package/dist/src/exec/env.d.ts +152 -2
  36. package/dist/src/exec/env.js +146 -2
  37. package/dist/src/fleet.d.ts +172 -0
  38. package/dist/src/fleet.js +219 -1
  39. package/dist/src/gates/citations.js +7 -1
  40. package/dist/src/gates/coverage.d.ts +113 -22
  41. package/dist/src/gates/coverage.js +166 -31
  42. package/dist/src/gates/credentials.d.ts +159 -0
  43. package/dist/src/gates/credentials.js +221 -2
  44. package/dist/src/gates/gate-classes.d.ts +56 -0
  45. package/dist/src/gates/gate-classes.js +633 -0
  46. package/dist/src/gates/merge-preconditions.d.ts +319 -0
  47. package/dist/src/gates/merge-preconditions.js +932 -0
  48. package/dist/src/gates/red-witness.js +105 -13
  49. package/dist/src/gates/run.d.ts +49 -1
  50. package/dist/src/gates/run.js +83 -5
  51. package/dist/src/gates/schemas/phase-declaration.schema.json +45 -0
  52. package/dist/src/gates/suite.js +48 -7
  53. package/dist/src/hooks.d.ts +55 -3
  54. package/dist/src/hooks.js +69 -6
  55. package/dist/src/index.d.ts +31 -0
  56. package/dist/src/index.js +30 -0
  57. package/dist/src/lock.d.ts +82 -4
  58. package/dist/src/lock.js +314 -22
  59. package/dist/src/model-resolution.d.ts +159 -0
  60. package/dist/src/model-resolution.js +307 -0
  61. package/dist/src/path-identity.d.ts +32 -0
  62. package/dist/src/path-identity.js +38 -0
  63. package/dist/src/pool.d.ts +197 -1
  64. package/dist/src/pool.js +289 -22
  65. package/dist/src/roles.d.ts +31 -0
  66. package/dist/src/roles.js +42 -0
  67. package/dist/src/spawn.d.ts +307 -2
  68. package/dist/src/spawn.js +690 -19
  69. package/dist/src/status.d.ts +27 -2
  70. package/dist/src/status.js +34 -5
  71. package/dist/src/task.d.ts +295 -55
  72. package/dist/src/task.js +125 -123
  73. package/dist/src/teardown.d.ts +7 -0
  74. package/dist/src/teardown.js +120 -12
  75. package/dist/src/validate.d.ts +44 -11
  76. package/dist/src/validate.js +44 -34
  77. package/dist/src/watcher.js +1 -11
  78. package/dist/src/witness/run.d.ts +32 -7
  79. package/dist/src/witness/run.js +76 -30
  80. package/dist/src/witness/spec.d.ts +168 -0
  81. package/dist/src/witness/spec.js +240 -18
  82. package/dist/tsconfig.src.tsbuildinfo +1 -1
  83. package/gate-registry.yaml +136 -0
  84. package/gates.manifest.json +63 -1
  85. package/package.json +18 -3
  86. package/roles/implementer.md +3 -0
  87. package/schemas/README.md +1 -0
  88. package/schemas/assurance-modes.schema.json +1 -1
  89. package/schemas/charter.schema.json +19 -0
  90. package/schemas/cutover-state.schema.json +64 -0
  91. package/schemas/executor-record.schema.json +36 -0
  92. package/schemas/model-resolution.schema.json +362 -0
  93. package/schemas/verdict.schema.json +9 -3
  94. package/schemas/write-bypass.schema.json +69 -0
@@ -3,7 +3,7 @@ import { closeSync, fsyncSync, openSync, renameSync, statSync, unlinkSync, watch
3
3
  import { join } from "node:path";
4
4
  import { setTimeout as sleep } from "node:timers/promises";
5
5
  import { CADENCE, readBeacon, renderBeacon, surveyTaskRecords, } from "./liveness.js";
6
- import { classifyEntry, executorRecordPath, readRegularFileIfPresent, refuseOpenForWrite, runStep, turnEndPath, } from "./task.js";
6
+ import { classifyEntry, executorRecordPath, readRegularFileIfPresent, refuseOpenForWrite, runStep, runStepAsync, turnEndPath, } from "./task.js";
7
7
  /**
8
8
  * The watcher (kernel plan v1, M1-P5 step 1; R-078, R-079; DR-0007;
9
9
  * plan constraints C-1, C-2 and C-3).
@@ -689,16 +689,6 @@ function loadOrInitCadence(fleet, nowMs) {
689
689
  writeCadenceState(fleet, fresh);
690
690
  return fresh;
691
691
  }
692
- /** runStep's shape for an async step (src/task.ts covers the sync one). */
693
- async function runStepAsync(what, step) {
694
- try {
695
- return { ok: true, value: await step() };
696
- }
697
- catch (error) {
698
- const detail = error instanceof Error ? error.message : String(error);
699
- return { ok: false, reason: `${what} failed: ${detail}` };
700
- }
701
- }
702
692
  /**
703
693
  * Scan and, if there is something to surface, surface it. Returns
704
694
  * undefined ONLY when nothing was surfaced and that is a fact this pass
@@ -49,6 +49,26 @@ export interface DiffFile {
49
49
  export interface PhaseDiff {
50
50
  baseSha: string;
51
51
  headSha: string;
52
+ /**
53
+ * The merge base of base and head, which is the revision the `base...head`
54
+ * three-dot diff below is actually taken against. Anything asking "what did
55
+ * this phase change" must read the old side HERE and not at `baseSha`: on a
56
+ * branch that has fallen behind, `baseSha` carries commits the branch never
57
+ * saw, and reading them as the branch's own starting point reproduces the
58
+ * two-dot misreading of standing warning 13.
59
+ *
60
+ * It falls back to `baseSha` when git computes no merge base, and THAT
61
+ * FALLBACK IS UNREACHABLE THROUGH THIS FUNCTION, which is said here rather
62
+ * than left for a reader to assume it is a tested path. Measured 2026-08-15
63
+ * on two orphan roots in one repository: `git merge-base A B` exits 1 with no
64
+ * output, and `git diff --name-status --no-renames A...B` exits 128 with
65
+ * `fatal: <A>...<B>: no merge base`. The diff below is the same three-dot
66
+ * form, so `computePhaseDiff` returns `ok: false` on the diff and never
67
+ * reaches the assignment. The fallback stays because the alternative is
68
+ * `"".trim()` silently becoming a sha-shaped empty string, which is a worse
69
+ * failure than an unreachable line.
70
+ */
71
+ mergeBaseSha: string;
52
72
  files: Map<string, DiffFile>;
53
73
  }
54
74
  export interface EvaluationInputs {
@@ -67,8 +87,18 @@ export interface EvaluationInputs {
67
87
  /** Changed files the spawn grep matched (rule (f) derivation). */
68
88
  spawningChangedFiles: string[];
69
89
  scratchRoot: string;
70
- /** True for the phase's own witnesses; rule (d) applies only to them. */
71
- phaseOwn: boolean;
90
+ /**
91
+ * Indices into `spec.dangerousStates` that THIS PHASE AUTHORED, which is
92
+ * the exact scope of rule (d). Empty for a stored witness the phase did not
93
+ * touch, and empty is the whole of "rule (d) does not apply here".
94
+ *
95
+ * This replaced a file-granular `phaseOwn: boolean`. The boolean was decided
96
+ * by the spec FILE appearing in the diff and then gated a PER MEMBER
97
+ * obligation, so any edit to any member imposed rule (d) on every sibling
98
+ * member of the same file. See `phaseOwnedMemberIndices` in ../witness/spec.ts
99
+ * for the derivation and for why an unreadable baseline owns everything.
100
+ */
101
+ phaseOwnedMembers: ReadonlySet<number>;
72
102
  hooks?: WitnessHooks;
73
103
  }
74
104
  export interface MemberRun {
@@ -134,11 +164,6 @@ interface GitResult {
134
164
  stderr: string;
135
165
  reason: string;
136
166
  }
137
- /**
138
- * Run git with a pinned C locale (verification-m1-p3-fix-round.md U-8: a
139
- * message-text classification with no locale pin silently stops matching in
140
- * a localized environment).
141
- */
142
167
  export declare function gitIn(cwd: string, args: string[]): GitResult;
143
168
  /**
144
169
  * Compute the phase diff base...head: changed files with status and the
@@ -11,10 +11,23 @@ import { describeMember, memberTouchedFiles } from "./spec.js";
11
11
  * message-text classification with no locale pin silently stops matching in
12
12
  * a localized environment).
13
13
  */
14
+ /* THE DEFAULT maxBuffer IS 1 MiB AND A PHASE DIFF EXCEEDS IT, which turns this
15
+ helper's ENOBUFS into a gate `error` on exactly the pull requests that carry
16
+ the most to check. Measured 2026-08-15 on the M3 exit-test bundle:
17
+ `git diff -U0 --no-renames origin/main...HEAD` produced 9,120,827 bytes and
18
+ `red-witness` reported
19
+ `error: ... could not be run: Error: spawnSync git ENOBUFS`.
20
+
21
+ A cap is still wanted rather than none, so that a runaway diff fails loudly
22
+ instead of exhausting memory; 256 MiB is far above any real diff and far
23
+ below that risk. The failure remains an `error` with its reason, never a
24
+ green, which is M2-C-3. */
25
+ const GIT_MAX_BUFFER_BYTES = 256 * 1024 * 1024;
14
26
  export function gitIn(cwd, args) {
15
27
  const result = spawnSync("git", args, {
16
28
  cwd,
17
29
  encoding: "utf8",
30
+ maxBuffer: GIT_MAX_BUFFER_BYTES,
18
31
  env: { ...process.env, LC_ALL: "C", LANG: "C" },
19
32
  });
20
33
  if (result.error !== undefined) {
@@ -63,6 +76,8 @@ export function computePhaseDiff(repoRoot, base, head) {
63
76
  }
64
77
  const baseSha = baseResolved.stdout.trim();
65
78
  const headSha = headResolved.stdout.trim();
79
+ const mergeBase = gitIn(repoRoot, ["merge-base", baseSha, headSha]);
80
+ const mergeBaseSha = mergeBase.ok ? mergeBase.stdout.trim() : baseSha;
66
81
  const names = gitIn(repoRoot, [
67
82
  "diff",
68
83
  "--name-status",
@@ -108,7 +123,7 @@ export function computePhaseDiff(repoRoot, base, head) {
108
123
  }
109
124
  }
110
125
  }
111
- return { ok: true, diff: { baseSha, headSha, files } };
126
+ return { ok: true, diff: { baseSha, headSha, mergeBaseSha, files } };
112
127
  }
113
128
  /**
114
129
  * The spawn grep of rule (f), over the changed files' head-state contents.
@@ -934,8 +949,31 @@ function evaluateRefusalRules(spec, inputs, namedTestFiles, derivation) {
934
949
  }
935
950
  }
936
951
  // (f) derived capture obligation from the spawn grep over changed files.
952
+ //
953
+ // THE SCOPE IS PER MEMBER, exactly as rule (d)'s is eleven lines down, and
954
+ // for the same reason. `7b18144` fixed that scope for rule (d) and left its
955
+ // sibling reading EVERY member of the spec, so an obligation derived from a
956
+ // file THIS PHASE changed was imposed on members the phase never authored,
957
+ // and could not have been discharged when they were written.
958
+ //
959
+ // MEASURED, M4-P11 at `a7d007f`: giving `src/checks.ts` its first subprocess
960
+ // call turned it into a spawning changed file and reddened THIRTY-NINE
961
+ // pre-existing witness specs over checklist parsing, verdict vocabulary,
962
+ // mode enums and hazard resolution. None of them had anything to do with git
963
+ // output; none of them could have declared a capture, because `src/checks.ts`
964
+ // spawned nothing when they were written. A required gate went red for every
965
+ // spec in a file's neighbourhood because one unrelated line was added.
966
+ //
967
+ // WHAT THE RULE STILL BINDS, so this is a scope correction and not a defang:
968
+ // a member THIS PHASE added or changed that touches a spawning changed file
969
+ // takes the capture obligation in full. Editing a member makes it owned, so
970
+ // a phase cannot launder an old member into an exemption by rewriting it.
937
971
  const touched = new Set();
938
- for (const member of spec.dangerousStates) {
972
+ for (let index = 0; index < spec.dangerousStates.length; index += 1) {
973
+ if (!inputs.phaseOwnedMembers.has(index)) {
974
+ continue;
975
+ }
976
+ const member = spec.dangerousStates[index];
939
977
  for (const file of memberTouchedFiles(member, readPatch)) {
940
978
  touched.add(file);
941
979
  }
@@ -947,36 +985,44 @@ function evaluateRefusalRules(spec, inputs, namedTestFiles, derivation) {
947
985
  `spawn-and-parse) matched, so consumesExternalOutput is required and ` +
948
986
  `this witness omits it`);
949
987
  }
950
- // (d) diff intersection, for the phase's own witnesses.
951
- if (inputs.phaseOwn) {
952
- for (let index = 0; index < spec.dangerousStates.length; index += 1) {
953
- const member = spec.dangerousStates[index];
954
- if (member.kind === "baseline-ref") {
955
- continue;
988
+ // (d) diff intersection, for the members THIS PHASE AUTHORED.
989
+ //
990
+ // The scope is per MEMBER, never per spec file: `inputs.phaseOwnedMembers`
991
+ // carries the indices the phase added or changed relative to the merge base.
992
+ // A member a sibling edit dragged into the file's diff is not owned and
993
+ // takes no obligation from this rule. The property rule (d) protects is
994
+ // unchanged: a member this phase AUTHORED must intersect this phase's diff,
995
+ // so a phase still cannot add a dangerous state about unrelated code.
996
+ for (let index = 0; index < spec.dangerousStates.length; index += 1) {
997
+ if (!inputs.phaseOwnedMembers.has(index)) {
998
+ continue;
999
+ }
1000
+ const member = spec.dangerousStates[index];
1001
+ if (member.kind === "baseline-ref") {
1002
+ continue;
1003
+ }
1004
+ const files = memberTouchedFiles(member, readPatch);
1005
+ const changedTouched = files.filter((file) => inputs.diff.files.has(file));
1006
+ if (changedTouched.length === 0) {
1007
+ reasons.push(`rule (d): declared dangerous state does not intersect the phase diff ` +
1008
+ `(member ${String(index)}, ${describeMember(member)})`);
1009
+ continue;
1010
+ }
1011
+ if (member.kind === "mutation") {
1012
+ const diffFile = inputs.diff.files.get(member.file);
1013
+ const shown = gitIn(inputs.repoRoot, [
1014
+ "show",
1015
+ `${inputs.headSha}:${member.file}`,
1016
+ ]);
1017
+ let insideHunk = false;
1018
+ if (diffFile !== undefined && shown.ok) {
1019
+ const occupied = findOccurrenceLines(shown.stdout, member.find);
1020
+ insideHunk = occupied.some((lineNo) => diffFile.hunks.some(([start, end]) => lineNo >= start && lineNo <= end));
956
1021
  }
957
- const files = memberTouchedFiles(member, readPatch);
958
- const changedTouched = files.filter((file) => inputs.diff.files.has(file));
959
- if (changedTouched.length === 0) {
1022
+ if (!insideHunk) {
960
1023
  reasons.push(`rule (d): declared dangerous state does not intersect the phase diff ` +
961
- `(member ${String(index)}, ${describeMember(member)})`);
962
- continue;
963
- }
964
- if (member.kind === "mutation") {
965
- const diffFile = inputs.diff.files.get(member.file);
966
- const shown = gitIn(inputs.repoRoot, [
967
- "show",
968
- `${inputs.headSha}:${member.file}`,
969
- ]);
970
- let insideHunk = false;
971
- if (diffFile !== undefined && shown.ok) {
972
- const occupied = findOccurrenceLines(shown.stdout, member.find);
973
- insideHunk = occupied.some((lineNo) => diffFile.hunks.some(([start, end]) => lineNo >= start && lineNo <= end));
974
- }
975
- if (!insideHunk) {
976
- reasons.push(`rule (d): declared dangerous state does not intersect the phase diff ` +
977
- `(member ${String(index)}, mutation of ${member.file} touches no ` +
978
- `line inside a changed hunk)`);
979
- }
1024
+ `(member ${String(index)}, mutation of ${member.file} touches no ` +
1025
+ `line inside a changed hunk)`);
980
1026
  }
981
1027
  }
982
1028
  }
@@ -71,8 +71,176 @@ export type WitnessSpecLoad = {
71
71
  reason: string;
72
72
  diagnostics: string[];
73
73
  };
74
+ /**
75
+ * Validate and materialise one spec from its BODY, with `label` naming the
76
+ * source in any reason. Split out of `loadWitnessSpec` so a spec can also be
77
+ * materialised from a git object (`git show <ref>:<path>`) rather than only
78
+ * from a working-tree path: the per-member ownership derivation below needs
79
+ * the merge-base version of a spec, which has no path.
80
+ */
81
+ export declare function parseWitnessSpec(body: string, label: string): WitnessSpecLoad;
74
82
  /** Load and validate one spec from a path the caller supplied (M2-C-6). */
75
83
  export declare function loadWitnessSpec(path: string): WitnessSpecLoad;
84
+ /**
85
+ * How a patch member's BODY is read, one reader per revision. A patch member's
86
+ * `patch` field is a PATH; the dangerous state it declares lives in the file at
87
+ * that path, so establishing whether the member changed needs the body on BOTH
88
+ * sides, at the merge base and at the audited head.
89
+ */
90
+ export interface PatchBodyReaders {
91
+ head: (patchPath: string) => string | undefined;
92
+ baseline: (patchPath: string) => string | undefined;
93
+ }
94
+ /**
95
+ * CANONICAL FORM OF ONE DANGEROUS-STATE MEMBER: everything that determines the
96
+ * dangerous state, in a fixed order, JSON-encoded. Two members are the SAME
97
+ * member when their canonical forms are equal. Field order inside the source
98
+ * document is not significant, so reformatting a spec is not authorship.
99
+ *
100
+ * PER KIND, and the three kinds are NOT alike, which is the correction fix
101
+ * round 1 makes:
102
+ *
103
+ * - `mutation` declares `file`, `find` and `replace`, and all three are INLINE
104
+ * in the spec document. The canonical form carries all three, so it is
105
+ * complete: nothing outside the document can change what this member does.
106
+ * - `patch` declares one field, `patch`, and it is a POINTER. The dangerous
107
+ * state is the patch FILE'S BODY, which lives outside the spec document
108
+ * entirely. Keying on the path alone made a member whose body was rewritten
109
+ * top to bottom compare EQUAL to its own previous version, so a phase could
110
+ * turn a dangerous state about file X into one about an unrelated file Y and
111
+ * be judged not to have authored it. The body's sha256 is therefore part of
112
+ * the canonical form. An unreadable body yields `undefined`, which the caller
113
+ * treats as "cannot be established", never as "unchanged".
114
+ * - `baseline-ref` declares `ref`, which is ALSO a pointer: the dangerous state
115
+ * is the whole tree at that ref, and a ref moves. It is deliberately NOT
116
+ * resolved, for two reasons stated rather than assumed. First, rule (d)
117
+ * SKIPS `baseline-ref` members outright (src/witness/run.ts:1295), so a
118
+ * baseline-ref member's ownership has no consequence anywhere: ownership is
119
+ * read in exactly one place and that place skips this kind. Second, resolving
120
+ * the ref would attribute a ref moved by SOMEBODY ELSE to this phase, which
121
+ * is the opposite of what an authorship derivation should say. If rule (d)
122
+ * ever stops skipping this kind, this decision has to be revisited, and the
123
+ * fix would be the patch one: fold the resolved tree, not the ref name.
124
+ */
125
+ export declare function canonicalMember(member: DangerousStateMember, readPatchBody: (patchPath: string) => string | undefined): string | undefined;
126
+ /**
127
+ * HAS THE SPEC BEEN RE-POINTED, as opposed to strengthened?
128
+ *
129
+ * A witness spec says "these named TESTS guard this BEHAVIOR, and here are the
130
+ * dangerous states they have been shown red against". Rule (d) is an obligation
131
+ * on that sentence, not only on its members: it exists so a phase cannot claim
132
+ * coverage using a dangerous state about code it did not touch. So a phase that
133
+ * rewrites the sentence takes the obligation for every state offered under it.
134
+ *
135
+ * THE TEST IS DIRECTIONAL, NOT EQUALITY, AND THAT DISTINCTION IS THE WHOLE
136
+ * FUNCTION. Fix round 1 compared `[behavior, sortedTests]` for equality, which
137
+ * is wrong in the safe direction and was caught by delta verification: a phase
138
+ * that ADDS one guarding test to an existing spec, changing no member and no
139
+ * behavior, was told its untouched siblings had to intersect the diff. Adding a
140
+ * test is not a re-point. It is strictly strengthening, and that is a MEASURED
141
+ * property of the harness rather than a judgement:
142
+ *
143
+ * - RED arm, src/witness/run.ts:918: `red: exitCode !== 0 && failed.length
144
+ * === tests.length`. A repetition counts as red only when EVERY named test
145
+ * failed, so an added test must ALSO redden against every declared
146
+ * dangerous state or the member stops being red and the spec goes red.
147
+ * - GREEN arm, src/witness/run.ts:1676: `headGreen = exitCode === 0 &&
148
+ * passedNamedTests.length === spec.tests.length`. An added test must ALSO
149
+ * pass at head.
150
+ *
151
+ * Both arms gain an obligation, neither loses one, and both are demonstrated by
152
+ * EXECUTION rather than accepted on trust. So there is nothing for rule (d) to
153
+ * police in an extension: no new dangerous state is declared, and the existing
154
+ * states' burden only grows.
155
+ *
156
+ * WHAT STILL COUNTS AS RE-POINTING, and each is a real relaxation:
157
+ *
158
+ * - `behavior` changed. The sentence is now about something else, and an older
159
+ * phase's dangerous state becomes evidence for a behavior this phase
160
+ * introduced. Any change, in either direction.
161
+ * - A baseline test name DROPPED. That relaxes both arms above: one fewer test
162
+ * must redden against every member and one fewer must pass at head. A phase
163
+ * can drop the test that was doing the work and keep the coverage claim.
164
+ * - A test name SWAPPED, which is a drop plus an addition and is caught by the
165
+ * drop half.
166
+ *
167
+ * So the predicate is `behavior` equality AND `baselineTests` being a SUBSET of
168
+ * `headTests`. Reordering is not a change: the comparison is over sets, which
169
+ * is the same positional indifference the member matching gets from comparing
170
+ * canonical forms rather than indices. Duplicates in `tests` collapse under set
171
+ * semantics, and that is harmless because both arms above count `tests`
172
+ * positionally on the same array, so a duplicate adds an obligation to each
173
+ * side identically.
174
+ *
175
+ * WHY MEMBERS ARE NOT TREATED THE SAME WAY, since adding a member also only
176
+ * ADDS an obligation (every member must independently redden). Because a member
177
+ * IS the thing rule (d) is an obligation on, and a test is not. An added member
178
+ * is a newly declared dangerous state, and checking that a declared dangerous
179
+ * state relates to the phase's own diff is the entire purpose of the rule; make
180
+ * added members exempt and rule (d) is empty. An added test declares no
181
+ * dangerous state at all.
182
+ *
183
+ * WHICH FIELDS ARE CONSIDERED, AND WHY THE OTHERS ARE NOT. The test applied to
184
+ * every field of the closed schema was: does changing THIS FIELD ALONE let a
185
+ * phase assert something new about its own diff while reusing a dangerous state
186
+ * somebody else authored?
187
+ *
188
+ * - `behavior`, `tests`: YES, in the directional sense above. IN.
189
+ * - `class`: NO. It selects which refusal rules apply (rules (a), (e), (g)) and
190
+ * none of them is ownership-gated, so a class change is evaluated in full on
191
+ * every run whether the spec is owned or not. A weakened class is refused by
192
+ * rule (e), which DERIVES the class from the named tests' sources rather than
193
+ * trusting the declaration. OUT, and including it would buy false reds on
194
+ * class fix-ups while closing no attack.
195
+ * - `id`: NO. It is the handle, checked for collisions at
196
+ * src/gates/red-witness.ts:263. Renaming a spec asserts nothing new. OUT.
197
+ * - `deterministic`, `repeats`: NO. They set the red THRESHOLD in the member
198
+ * execution loop (src/witness/run.ts:1517 and src/witness/run.ts:1605), which
199
+ * runs for every member of every evaluated spec regardless of ownership. OUT.
200
+ * - `consumesExternalOutput`: NO. Rules (c) and (f) read it and neither is
201
+ * ownership-gated. OUT.
202
+ */
203
+ export declare function claimRePointed(headSpec: WitnessSpec, baselineSpec: WitnessSpec): boolean;
204
+ /**
205
+ * WHICH MEMBERS OF A SPEC THIS PHASE AUTHORED (the ownership scope of rule
206
+ * (d)).
207
+ *
208
+ * Rule (d) requires a declared dangerous state to intersect the phase diff, so
209
+ * that a phase cannot add a witness about unrelated code and claim coverage.
210
+ * That obligation is a claim about AUTHORSHIP, and authorship is per MEMBER.
211
+ * Deriving it from the spec FILE appearing in the diff is one granularity too
212
+ * coarse: it makes every sibling member of an edited file acquire an obligation
213
+ * its author never took on. Measured by the M3 exit test at stage E1.6, where
214
+ * repairing one member's quoted source line reddened two untouched members of
215
+ * the same file.
216
+ *
217
+ * THREE WAYS A PHASE AUTHORS, and the round-1 reviews found that only the first
218
+ * was implemented:
219
+ *
220
+ * 1. A member whose CANONICAL FORM changed, which now includes a patch body.
221
+ * 2. A member with no counterpart at the merge base. Matching is a MULTISET
222
+ * consume rather than a set membership test, so a second copy of an existing
223
+ * member is authored (rule (g) is what refuses that copy, and it must still
224
+ * see it as new).
225
+ * 3. THE CLAIM WAS RE-POINTED. Then every member is authored, because every
226
+ * declared dangerous state is now being offered as evidence for a sentence
227
+ * this phase wrote. There is no narrower attribution available: a claim
228
+ * change cannot be pinned on one member, since the claim is a property of
229
+ * the document. This is the ONLY whole-spec trigger, and keeping it that
230
+ * narrow is the point. An edit to a sibling member, a `repeats` bump, a
231
+ * reformat, a rename, and ADDING A GUARDING TEST all still author nothing,
232
+ * which is what the converse tests hold. `claimRePointed` carries the
233
+ * derivation of why extension is safe and dropping a test is not.
234
+ *
235
+ * `baselineSpec` is `undefined` when the spec did not exist at the merge base,
236
+ * did not parse there, or could not be read there. All three mean the phase is
237
+ * answerable for the whole file, so every member is owned. That is the
238
+ * conservative direction, and it is applied identically to an unreadable patch
239
+ * BODY on either side: the failure mode of this derivation is a member wrongly
240
+ * EXEMPTED, so anything the derivation cannot establish keeps the obligation
241
+ * rather than dropping it.
242
+ */
243
+ export declare function phaseOwnedMemberIndices(headSpec: WitnessSpec, baselineSpec: WitnessSpec | undefined, readers: PatchBodyReaders): Set<number>;
76
244
  export type WitnessSpecListing = {
77
245
  ok: true;
78
246
  paths: string[];