@codyswann/lisa 4.12.0 → 4.13.1

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/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +7 -1
  2. package/dist/cli/doctor-two-channel-drift.d.ts +20 -0
  3. package/dist/cli/doctor-two-channel-drift.d.ts.map +1 -0
  4. package/dist/cli/doctor-two-channel-drift.js +190 -0
  5. package/dist/cli/doctor-two-channel-drift.js.map +1 -0
  6. package/dist/cli/doctor-wiki.d.ts +8 -0
  7. package/dist/cli/doctor-wiki.d.ts.map +1 -0
  8. package/dist/cli/doctor-wiki.js +42 -0
  9. package/dist/cli/doctor-wiki.js.map +1 -0
  10. package/dist/cli/doctor.d.ts.map +1 -1
  11. package/dist/cli/doctor.js +7 -30
  12. package/dist/cli/doctor.js.map +1 -1
  13. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  14. package/dist/core/lisa-owned-hash-ledger.js +3 -0
  15. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  16. package/dist/core/two-channel-delivery-scan.d.ts +81 -0
  17. package/dist/core/two-channel-delivery-scan.d.ts.map +1 -0
  18. package/dist/core/two-channel-delivery-scan.js +191 -0
  19. package/dist/core/two-channel-delivery-scan.js.map +1 -0
  20. package/dist/core/two-channel-delivery.d.ts +215 -0
  21. package/dist/core/two-channel-delivery.d.ts.map +1 -0
  22. package/dist/core/two-channel-delivery.js +259 -0
  23. package/dist/core/two-channel-delivery.js.map +1 -0
  24. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  25. package/dist/core/upstream-evidence-manifest.js +18 -5
  26. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  27. package/dist/opencode/plugin-templates/parity-safety-net.sh +7 -1
  28. package/package.json +4 -2
  29. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa/hooks/parity-safety-net.sh +7 -1
  32. package/plugins/lisa/rules/eager/falsifiable-checks.md +1 -1
  33. package/plugins/lisa/rules/reference/falsifiable-checks.md +1 -1
  34. package/plugins/lisa-agy/hooks/parity-safety-net.sh +7 -1
  35. package/plugins/lisa-agy/plugin.json +1 -1
  36. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  38. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  39. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +7 -1
  43. package/plugins/lisa-copilot/rules/eager/falsifiable-checks.md +1 -1
  44. package/plugins/lisa-copilot/rules/reference/falsifiable-checks.md +1 -1
  45. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +7 -1
  47. package/plugins/lisa-cursor/rules/falsifiable-checks-reference.mdc +1 -1
  48. package/plugins/lisa-cursor/rules/falsifiable-checks.mdc +1 -1
  49. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-expo-agy/plugin.json +1 -1
  52. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  57. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  62. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  67. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  71. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  72. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  76. package/plugins/lisa-rails-agy/plugin.json +1 -1
  77. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  81. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  82. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  86. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  87. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  89. package/plugins/src/base/hooks/parity-safety-net.sh +7 -1
  90. package/plugins/src/base/rules/eager/falsifiable-checks.md +1 -1
  91. package/plugins/src/base/rules/reference/falsifiable-checks.md +1 -1
  92. package/scripts/generate-two-channel-couplings.ts +427 -0
  93. package/scripts/two-channel-couplings.json +214 -0
  94. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +19 -20
@@ -0,0 +1,191 @@
1
+ /** How a workflow spells a path inside the installed Lisa package. */
2
+ const PACKAGE_PREFIX = "node_modules/@codyswann/lisa/";
3
+ /** Directories a consumer keeps its own executable artifacts in. */
4
+ const CALLER_DIRECTORIES = "scripts|bin|tools";
5
+ /**
6
+ * A caller-tree path literal: one of the caller directories, a body, and a
7
+ * file extension, with nothing path-like immediately before it.
8
+ */
9
+ const CALLER_PATH = new RegExp(`(?<![A-Za-z0-9_@./-])((?:${CALLER_DIRECTORIES})/[A-Za-z0-9._/-]*[A-Za-z0-9_-]\\.[A-Za-z0-9]+)`, "g");
10
+ /** A package path literal, used only to decide whether a step is covered. */
11
+ const PACKAGE_PATH = new RegExp(`${PACKAGE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[A-Za-z0-9_@./-]+`, "g");
12
+ /** A line that is nothing but a YAML comment. */
13
+ const COMMENT_LINE = /^\s*#/;
14
+ /** The start of a step in a `steps:` list, capturing a `name:` value. */
15
+ const STEP_START = /^\s*-\s+(?:name:\s*(.*)|(?:uses|run|id):)/;
16
+ /**
17
+ * Split a workflow into steps, keeping the preamble as an unnamed first step.
18
+ *
19
+ * The preamble is not dead weight: `nightly-e2e-health.yml` declares
20
+ * `inputs.health_script` with a default of `scripts/check-nightly-e2e-health.mjs`,
21
+ * which is a caller-tree read that never appears in a step at all. Dropping the
22
+ * preamble would lose it.
23
+ * @param text - Raw workflow YAML
24
+ * @returns Steps in file order, the first being the preamble
25
+ */
26
+ export function extractSteps(text) {
27
+ const lines = text.split("\n").filter(line => !COMMENT_LINE.test(line));
28
+ return lines.reduce((steps, line) => {
29
+ const started = STEP_START.exec(line);
30
+ const current = steps[steps.length - 1];
31
+ const appended = {
32
+ name: current?.name ?? "",
33
+ body: `${current?.body ?? ""}\n${line}`,
34
+ };
35
+ return started === null
36
+ ? [...steps.slice(0, -1), appended]
37
+ : [...steps, { name: (started[1] ?? "").trim(), body: line }];
38
+ }, [{ name: "", body: "" }]);
39
+ }
40
+ /**
41
+ * Every distinct match of one global pattern in a chunk of text.
42
+ * @param pattern - A global regular expression with one capturing group, or none
43
+ * @param text - Text to search
44
+ * @returns Distinct matches, in first-seen order
45
+ */
46
+ function matchesIn(pattern, text) {
47
+ return [
48
+ ...new Set([...text.matchAll(pattern)].map(match => match[1] ?? match[0])),
49
+ ];
50
+ }
51
+ /**
52
+ * Whether a step names a package-relative candidate for the same artifact.
53
+ *
54
+ * Compared by suffix rather than by basename: a package candidate for
55
+ * `scripts/lisa-gates.mjs` spells the whole tail
56
+ * `…/all/copy-overwrite/scripts/lisa-gates.mjs`, and matching on the bare file
57
+ * name would let an unrelated package file with a colliding name vouch for a
58
+ * caller path it has nothing to do with.
59
+ * @param packagePaths - Package literals found in the step
60
+ * @param callerPath - The caller-tree path
61
+ * @returns Whether the fast channel already delivers this artifact
62
+ */
63
+ function isPackageBacked(packagePaths, callerPath) {
64
+ return packagePaths.some(candidate => candidate.endsWith(`/${callerPath}`));
65
+ }
66
+ /** Shell file-test flags whose operand is a path that may be absent. */
67
+ const FILE_TEST_FLAG = /-[defrswx]$/;
68
+ /** How the Actions expression language asks whether a path exists. */
69
+ const HASH_FILES = "hashFiles(";
70
+ /** Quoting and leading-`./` noise sitting between a flag and its operand. */
71
+ const OPERAND_NOISE = new Set(['"', "'", ".", "/", " ", "\t"]);
72
+ /**
73
+ * Drop trailing quoting and path noise, so a flag sits at the end or does not.
74
+ *
75
+ * A loop rather than a `/["\'./]+$/` replace: a quantified class anchored at
76
+ * the end of a caller-supplied slice is the shape linters flag for
77
+ * super-linear backtracking, and nothing here needs a pattern.
78
+ * @param value - Text to trim
79
+ * @returns The same text without its trailing noise characters
80
+ */
81
+ function withoutOperandNoise(value) {
82
+ return value.length > 0 && OPERAND_NOISE.has(value.slice(-1))
83
+ ? withoutOperandNoise(value.slice(0, -1))
84
+ : value;
85
+ }
86
+ /** How far back to look for whatever introduced an occurrence. */
87
+ const LOOKBEHIND = 32;
88
+ /**
89
+ * Every index at which `needle` occurs in `haystack`.
90
+ *
91
+ * Written with `split` rather than a global regular expression because the
92
+ * needle is a caller-supplied path: compiling it into a pattern is where a
93
+ * super-linear match would come from, and there is nothing here to match
94
+ * loosely.
95
+ * @param haystack - Text to search
96
+ * @param needle - Literal to find
97
+ * @returns Start indices, ascending
98
+ */
99
+ function occurrenceIndices(haystack, needle) {
100
+ return haystack
101
+ .split(needle)
102
+ .slice(0, -1)
103
+ .reduce((indices, chunk) => {
104
+ const previous = indices.at(-1) ?? -needle.length;
105
+ return [...indices, previous + needle.length + chunk.length];
106
+ }, []);
107
+ }
108
+ /**
109
+ * Whether a step tests for the path's existence before reading it.
110
+ *
111
+ * Recognises the two spellings that actually occur — a shell file test
112
+ * (`[ -f scripts/x.mjs ]`, `test -e …`) and the Actions `hashFiles()`
113
+ * expression. It does not attempt to decide what the absent branch DOES,
114
+ * because that needs a shell parser and the answer would still be a guess.
115
+ * What it reports is narrower and checkable: the step can tell the path is
116
+ * missing, so its absence need not be loud.
117
+ * @param body - The step's text
118
+ * @param callerPath - The caller-tree path
119
+ * @returns Whether the read sits behind an existence test
120
+ */
121
+ function isGuarded(body, callerPath) {
122
+ return occurrenceIndices(body, callerPath).some(index => {
123
+ const before = body.slice(Math.max(0, index - LOOKBEHIND), index);
124
+ return (FILE_TEST_FLAG.test(withoutOperandNoise(before)) ||
125
+ before.includes(HASH_FILES));
126
+ });
127
+ }
128
+ /**
129
+ * Every caller-tree read in one step.
130
+ * @param step - The step
131
+ * @returns One occurrence per distinct caller path
132
+ */
133
+ function occurrencesIn(step) {
134
+ const packagePaths = matchesIn(PACKAGE_PATH, step.body);
135
+ return matchesIn(CALLER_PATH, step.body).map(callerPath => ({
136
+ step: step.name,
137
+ path: callerPath,
138
+ packageBacked: isPackageBacked(packagePaths, callerPath),
139
+ guarded: isGuarded(step.body, callerPath),
140
+ }));
141
+ }
142
+ /**
143
+ * Collapse repeated reads of one path into the single coupling they are.
144
+ * @param workflow - Workflow file name
145
+ * @param occurrences - Every occurrence found in that workflow
146
+ * @param lanesFor - Delivery lanes shipping a caller path
147
+ * @returns One coupling per distinct path, sorted by path
148
+ */
149
+ function collapse(workflow, occurrences, lanesFor) {
150
+ const paths = [...new Set(occurrences.map(occurrence => occurrence.path))];
151
+ return paths
152
+ .map(callerPath => {
153
+ const found = occurrences.filter(occurrence => occurrence.path === callerPath);
154
+ return {
155
+ workflow,
156
+ step: found[0]?.step ?? "",
157
+ path: callerPath,
158
+ lanes: lanesFor(callerPath),
159
+ packageBacked: found.every(occurrence => occurrence.packageBacked),
160
+ guarded: found.some(occurrence => occurrence.guarded),
161
+ };
162
+ })
163
+ .sort((left, right) => left.path.localeCompare(right.path));
164
+ }
165
+ /**
166
+ * Read every caller-tree coupling out of one reusable workflow.
167
+ * @param options - Inputs
168
+ * @param options.workflow - Workflow file name, as a consumer spells it after `@main`
169
+ * @param options.text - Raw workflow YAML
170
+ * @param options.lanesFor - Delivery lanes shipping a caller path
171
+ * @returns One coupling per distinct caller path, sorted by path
172
+ */
173
+ export function scanWorkflow(options) {
174
+ const steps = extractSteps(options.text);
175
+ return collapse(options.workflow, steps.flatMap(occurrencesIn), options.lanesFor);
176
+ }
177
+ /**
178
+ * Whether a workflow can be called by another repository at all.
179
+ *
180
+ * Only a `workflow_call` workflow travels the `@main` channel; the rest run in
181
+ * this repository and reach no consumer, so a caller-tree read in one of them
182
+ * is a claim about this checkout and not about anybody else's.
183
+ * @param text - Raw workflow YAML
184
+ * @returns Whether the workflow declares `workflow_call`
185
+ */
186
+ export function isReusable(text) {
187
+ return text
188
+ .split("\n")
189
+ .some(line => line.trimStart().startsWith("workflow_call:"));
190
+ }
191
+ //# sourceMappingURL=two-channel-delivery-scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"two-channel-delivery-scan.js","sourceRoot":"","sources":["../../src/core/two-channel-delivery-scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Measure the gap between the two speeds Lisa ships a change at.
3
+ *
4
+ * Lisa reaches a consumer down two channels, and they do not move together:
5
+ *
6
+ * | half | channel | reaches a consumer |
7
+ * | --- | --- | --- |
8
+ * | the reusable workflow body | `uses: …@main` | their next workflow run |
9
+ * | scripts, configs, declarations | `lisa apply` | their next apply |
10
+ *
11
+ * For an *additive* change the asymmetry is harmless — the new workflow finds
12
+ * no declaration and falls back. For a change that REMOVES a built-in and
13
+ * replaces it with something the consumer's tree must provide, the halves are
14
+ * not interchangeable and they land in the wrong order: the removing half is
15
+ * live everywhere immediately, the restoring half only where someone applied.
16
+ * In the gap the property is proved by nothing, and nothing reads as red —
17
+ * the replacement never runs, so it posts no context, and an ABSENT required
18
+ * context is not a failing one (CodySwannGT/lisa#3050).
19
+ *
20
+ * ## What this module measures, and what it deliberately does not
21
+ *
22
+ * A **coupling** is one step of a `@main`-delivered reusable workflow naming a
23
+ * path in the CALLER's tree. `scripts/check-e2e-coverage.mjs` is a coupling;
24
+ * `node_modules/@codyswann/lisa/all/copy-overwrite/scripts/lisa-gates.mjs` is
25
+ * not — that path is inside the installed package, and whether it survives a
26
+ * release is `check-workflow-package-paths.mjs`'s question (#2960).
27
+ *
28
+ * That script states its own boundary: *"HOST-relative candidates in the same
29
+ * step (`scripts/foo.mjs`) do not count and are never extracted."* This module
30
+ * is exactly that excluded arm. The two do not overlap, and neither subsumes
31
+ * the other: #2960 asks whether a PACKAGE path survives a release, this asks
32
+ * whether a HOST path ever arrives at all.
33
+ *
34
+ * ## Why the resolver loop matters more than it looks
35
+ *
36
+ * Most steps resolve a script by trying candidates in order — the packaged
37
+ * copy first, the consumer's own copy as a fallback. Such a step is
38
+ * `package-backed`: the package channel delivers it, and the host copy is
39
+ * belt-and-braces. **A step naming a host path with NO package candidate has
40
+ * exactly one delivery channel, and it is the slow one.** Those are the
41
+ * couplings this module exists to count.
42
+ *
43
+ * ## Why an inventory of apply-lagged couplings is the deliverable, not a failure
44
+ *
45
+ * Fourteen of Lisa's own fifteen host-only couplings are `apply-lagged`, and
46
+ * every one of them is deliberate — Lisa's design is that `quality.yml` calls
47
+ * scripts `lisa apply` installs. Failing on them would mean ratifying fourteen
48
+ * entries on day one, and an allowlist that large stops being a record of
49
+ * decisions and becomes the bypass (the lesson of #2639). So `apply-lagged` is
50
+ * RECORDED. The ledger is the baseline; a change that adds a coupling shows up
51
+ * as a ledger diff a reviewer must look at, which is the reachable check the
52
+ * issue asked for.
53
+ *
54
+ * What fails is the shape nothing restores: `never-delivered` (create-only, so
55
+ * an existing consumer receives it once at scaffold time or never) and
56
+ * `undelivered` (Lisa ships no such path anywhere). Those are #3030's lesson
57
+ * — *"fixed upstream" is not "a bump brings it"* — reached from the other
58
+ * side, and #3169's honestly-stated haves/have-nots split.
59
+ *
60
+ * ## Refusing to pass on nothing
61
+ *
62
+ * Zero workflows, zero steps, zero couplings, or an empty delivery inventory
63
+ * all mean the measurement did not happen, and every one of them is reported
64
+ * as unmeasured rather than as a clean sweep. An empty comparison and a
65
+ * converged consumer must not produce the same output — this whole subject is
66
+ * failures that read as normal, and reproducing that here would be perverse.
67
+ * @module core/two-channel-delivery
68
+ */
69
+ /**
70
+ * How one artifact reaches a consumer that already exists.
71
+ *
72
+ * Named by SPEED rather than by mechanism, because speed is what the two-
73
+ * channel defect is about. `workflow-ref` and `package` both arrive without
74
+ * anyone acting on the consumer's side; `apply` needs an apply; `create-only`
75
+ * and `undelivered` never arrive at all.
76
+ */
77
+ export type DeliveryChannel =
78
+ /** Travels with the workflow body itself — live on the next run. */
79
+ "workflow-ref"
80
+ /** Inside the installed package — live on the next dependency bump. */
81
+ | "package"
82
+ /** A refreshing copy strategy — live on the next `lisa apply`. */
83
+ | "apply"
84
+ /** Written once at scaffold time — an existing consumer never receives it. */
85
+ | "create-only"
86
+ /** Lisa ships nothing at this path; the consumer must author it. */
87
+ | "undelivered";
88
+ /**
89
+ * One coupling's verdict, in the vocabulary an operator can act on.
90
+ *
91
+ * Four members rather than a drifted/clean pair, because the remedies differ
92
+ * and one of the four is not a defect at all.
93
+ */
94
+ export type CouplingVerdict =
95
+ /** The step also names a package candidate, so the fast channel covers it. */
96
+ "package-backed"
97
+ /** Host-only, and a refreshing lane delivers it — arrives on the next apply. */
98
+ | "apply-lagged"
99
+ /** Host-only, and only `create-only` delivers it — never arrives. */
100
+ | "never-delivered"
101
+ /** Host-only, and Lisa delivers no such path at all. */
102
+ | "undelivered";
103
+ /** What to do about one verdict. */
104
+ export type CouplingRemedy = "none" | "run-lisa-apply" | "adopt-the-artifact" | "author-the-artifact";
105
+ /** One host-relative path read by one step of one reusable workflow. */
106
+ export interface CouplingInput {
107
+ /** Workflow file name, as a consumer spells it after `@main`. */
108
+ readonly workflow: string;
109
+ /** The step's `name:`, or an empty string for an unnamed step. */
110
+ readonly step: string;
111
+ /** The caller-tree path the step names. */
112
+ readonly path: string;
113
+ /**
114
+ * Delivery lanes shipping that path, as `<stack>/<strategy>` (for example
115
+ * `all/copy-overwrite`). Empty when Lisa ships nothing there.
116
+ */
117
+ readonly lanes: readonly string[];
118
+ /** Whether the same step also names a package-relative candidate. */
119
+ readonly packageBacked: boolean;
120
+ /**
121
+ * Whether the read sits behind a file-existence test.
122
+ *
123
+ * Not a verdict, and deliberately not one. A guard changes the SHAPE of the
124
+ * failure rather than its existence: unguarded, an absent path fails the job
125
+ * loudly; guarded, the step skips and the gate silently proves nothing. The
126
+ * second is the worse outcome and the one #3050 is about, so it is recorded
127
+ * on the entry and said in the detail rather than folded into the verdict.
128
+ */
129
+ readonly guarded: boolean;
130
+ }
131
+ /** One coupling, its verdict, and the evidence behind it. */
132
+ export interface CouplingEntry extends CouplingInput {
133
+ /** Stable identity, `<workflow>::<path>`, used to key ratifications. */
134
+ readonly key: string;
135
+ /** The channel the path actually arrives on. */
136
+ readonly channel: DeliveryChannel;
137
+ /** The verdict. */
138
+ readonly verdict: CouplingVerdict;
139
+ /** The action this verdict calls for. */
140
+ readonly remedy: CouplingRemedy;
141
+ /** One operator-readable sentence. */
142
+ readonly detail: string;
143
+ }
144
+ /** What a run looked at, so a clean report can never mean an empty one. */
145
+ export interface InspectionCounts {
146
+ /** Reusable workflows read. */
147
+ readonly workflows: number;
148
+ /** Steps inside them. */
149
+ readonly steps: number;
150
+ /** Caller-tree path reads found in those steps. */
151
+ readonly couplings: number;
152
+ /** Paths in the delivery inventory the lanes were resolved against. */
153
+ readonly inventory: number;
154
+ }
155
+ /** The whole measurement. */
156
+ export interface TwoChannelReport {
157
+ /** One entry per coupling, sorted by key. */
158
+ readonly entries: readonly CouplingEntry[];
159
+ /** How many entries carry each verdict. Every key is present. */
160
+ readonly counts: Readonly<Record<CouplingVerdict, number>>;
161
+ /** What the run inspected. Printed on every run, clean or not. */
162
+ readonly inspected: InspectionCounts;
163
+ /**
164
+ * Unrestorable couplings nobody ratified — the findings.
165
+ *
166
+ * Sorted by key, so two runs over the same tree emit the same bytes.
167
+ */
168
+ readonly findings: readonly CouplingEntry[];
169
+ /**
170
+ * Ratification keys matching no live coupling.
171
+ *
172
+ * Reported as a failure, not pruned silently. A ratification is a record of
173
+ * a decision about a specific coupling; once that coupling is gone the entry
174
+ * is an unexamined permission that the next matching path inherits for free,
175
+ * which is how an allowlist added to harden a guard becomes the bypass.
176
+ */
177
+ readonly staleRatifications: readonly string[];
178
+ /** False when the run did not actually measure anything. */
179
+ readonly measured: boolean;
180
+ /** Why the run measured nothing, or null when it measured something. */
181
+ readonly unmeasuredReason: string | null;
182
+ }
183
+ /**
184
+ * Which channel a path arrives on, given the lanes shipping it.
185
+ *
186
+ * A refreshing lane wins over `create-only` when both ship the same path,
187
+ * because an apply reaching ANY refreshing lane rewrites the file. That is
188
+ * deliberately optimistic and stated rather than hidden: a project on only the
189
+ * create-only stack still never receives it, and resolving lanes per active
190
+ * stack is a later stage. Optimism that is written down can be argued with;
191
+ * optimism inside a `find` cannot.
192
+ * @param lanes - `<stack>/<strategy>` lanes shipping the path
193
+ * @returns The channel an existing consumer receives it on
194
+ */
195
+ export declare function resolveDeliveryChannel(lanes: readonly string[]): DeliveryChannel;
196
+ /**
197
+ * Classify every coupling and decide whether the run measured anything.
198
+ *
199
+ * Total and pure — the same inputs always produce the same entries in the same
200
+ * order. Reaching the tree is the caller's job, and a caller that could not
201
+ * reach it must pass the zeroes it actually saw rather than omit them: this
202
+ * function reports an empty sweep as unmeasured precisely so a caller cannot
203
+ * turn "I could not look" into "there was nothing to find".
204
+ * @param options - Inputs
205
+ * @param options.couplings - Every caller-tree path read that was found
206
+ * @param options.inspected - What the run looked at
207
+ * @param options.ratified - Ratification reasons, keyed `<workflow>::<path>`
208
+ * @returns The measurement
209
+ */
210
+ export declare function classifyTwoChannelDelivery(options: {
211
+ couplings: readonly CouplingInput[];
212
+ inspected: InspectionCounts;
213
+ ratified: Readonly<Record<string, string>>;
214
+ }): TwoChannelReport;
215
+ //# sourceMappingURL=two-channel-delivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"two-channel-delivery.d.ts","sourceRoot":"","sources":["../../src/core/two-channel-delivery.ts"],"names":[],"mappings":"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"}