@ploaness/governance 1.0.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 (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/access-boundary.d.ts +41 -0
  4. package/dist/access-boundary.d.ts.map +1 -0
  5. package/dist/access-boundary.js +26 -0
  6. package/dist/access-boundary.js.map +1 -0
  7. package/dist/agent-references.d.ts +12 -0
  8. package/dist/agent-references.d.ts.map +1 -0
  9. package/dist/agent-references.js +53 -0
  10. package/dist/agent-references.js.map +1 -0
  11. package/dist/architecture-layers.d.ts +11 -0
  12. package/dist/architecture-layers.d.ts.map +1 -0
  13. package/dist/architecture-layers.js +47 -0
  14. package/dist/architecture-layers.js.map +1 -0
  15. package/dist/asset-policy.d.ts +138 -0
  16. package/dist/asset-policy.d.ts.map +1 -0
  17. package/dist/asset-policy.js +312 -0
  18. package/dist/asset-policy.js.map +1 -0
  19. package/dist/banned-typography.d.ts +13 -0
  20. package/dist/banned-typography.d.ts.map +1 -0
  21. package/dist/banned-typography.js +78 -0
  22. package/dist/banned-typography.js.map +1 -0
  23. package/dist/bundle-budget.d.ts +16 -0
  24. package/dist/bundle-budget.d.ts.map +1 -0
  25. package/dist/bundle-budget.js +37 -0
  26. package/dist/bundle-budget.js.map +1 -0
  27. package/dist/commit-message.d.ts +37 -0
  28. package/dist/commit-message.d.ts.map +1 -0
  29. package/dist/commit-message.js +144 -0
  30. package/dist/commit-message.js.map +1 -0
  31. package/dist/config-references.d.ts +17 -0
  32. package/dist/config-references.d.ts.map +1 -0
  33. package/dist/config-references.js +54 -0
  34. package/dist/config-references.js.map +1 -0
  35. package/dist/container-failure.d.ts +40 -0
  36. package/dist/container-failure.d.ts.map +1 -0
  37. package/dist/container-failure.js +154 -0
  38. package/dist/container-failure.js.map +1 -0
  39. package/dist/container-files.d.ts +36 -0
  40. package/dist/container-files.d.ts.map +1 -0
  41. package/dist/container-files.js +74 -0
  42. package/dist/container-files.js.map +1 -0
  43. package/dist/container-freshness.d.ts +102 -0
  44. package/dist/container-freshness.d.ts.map +1 -0
  45. package/dist/container-freshness.js +129 -0
  46. package/dist/container-freshness.js.map +1 -0
  47. package/dist/dependency-freshness.d.ts +129 -0
  48. package/dist/dependency-freshness.d.ts.map +1 -0
  49. package/dist/dependency-freshness.js +161 -0
  50. package/dist/dependency-freshness.js.map +1 -0
  51. package/dist/doc-blocks.d.ts +13 -0
  52. package/dist/doc-blocks.d.ts.map +1 -0
  53. package/dist/doc-blocks.js +75 -0
  54. package/dist/doc-blocks.js.map +1 -0
  55. package/dist/document-references.d.ts +44 -0
  56. package/dist/document-references.d.ts.map +1 -0
  57. package/dist/document-references.js +94 -0
  58. package/dist/document-references.js.map +1 -0
  59. package/dist/editorconfig-policy.d.ts +24 -0
  60. package/dist/editorconfig-policy.d.ts.map +1 -0
  61. package/dist/editorconfig-policy.js +106 -0
  62. package/dist/editorconfig-policy.js.map +1 -0
  63. package/dist/environment-files.d.ts +42 -0
  64. package/dist/environment-files.d.ts.map +1 -0
  65. package/dist/environment-files.js +76 -0
  66. package/dist/environment-files.js.map +1 -0
  67. package/dist/file-roles.d.ts +39 -0
  68. package/dist/file-roles.d.ts.map +1 -0
  69. package/dist/file-roles.js +82 -0
  70. package/dist/file-roles.js.map +1 -0
  71. package/dist/generated-denial.d.ts +42 -0
  72. package/dist/generated-denial.d.ts.map +1 -0
  73. package/dist/generated-denial.js +87 -0
  74. package/dist/generated-denial.js.map +1 -0
  75. package/dist/history-policy.d.ts +32 -0
  76. package/dist/history-policy.d.ts.map +1 -0
  77. package/dist/history-policy.js +50 -0
  78. package/dist/history-policy.js.map +1 -0
  79. package/dist/image-assets.d.ts +14 -0
  80. package/dist/image-assets.d.ts.map +1 -0
  81. package/dist/image-assets.js +205 -0
  82. package/dist/image-assets.js.map +1 -0
  83. package/dist/index.d.ts +49 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +53 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/install-policy.d.ts +35 -0
  88. package/dist/install-policy.d.ts.map +1 -0
  89. package/dist/install-policy.js +61 -0
  90. package/dist/install-policy.js.map +1 -0
  91. package/dist/json-shapes.d.ts +86 -0
  92. package/dist/json-shapes.d.ts.map +1 -0
  93. package/dist/json-shapes.js +124 -0
  94. package/dist/json-shapes.js.map +1 -0
  95. package/dist/license-allowlist.d.ts +15 -0
  96. package/dist/license-allowlist.d.ts.map +1 -0
  97. package/dist/license-allowlist.js +157 -0
  98. package/dist/license-allowlist.js.map +1 -0
  99. package/dist/network-policy.d.ts +69 -0
  100. package/dist/network-policy.d.ts.map +1 -0
  101. package/dist/network-policy.js +173 -0
  102. package/dist/network-policy.js.map +1 -0
  103. package/dist/paired-assets.d.ts +34 -0
  104. package/dist/paired-assets.d.ts.map +1 -0
  105. package/dist/paired-assets.js +63 -0
  106. package/dist/paired-assets.js.map +1 -0
  107. package/dist/payload-access.d.ts +9 -0
  108. package/dist/payload-access.d.ts.map +1 -0
  109. package/dist/payload-access.js +168 -0
  110. package/dist/payload-access.js.map +1 -0
  111. package/dist/payload-policy.d.ts +18 -0
  112. package/dist/payload-policy.d.ts.map +1 -0
  113. package/dist/payload-policy.js +124 -0
  114. package/dist/payload-policy.js.map +1 -0
  115. package/dist/payload-source.d.ts +29 -0
  116. package/dist/payload-source.d.ts.map +1 -0
  117. package/dist/payload-source.js +84 -0
  118. package/dist/payload-source.js.map +1 -0
  119. package/dist/release-age.d.ts +23 -0
  120. package/dist/release-age.d.ts.map +1 -0
  121. package/dist/release-age.js +38 -0
  122. package/dist/release-age.js.map +1 -0
  123. package/dist/request-limits.d.ts +39 -0
  124. package/dist/request-limits.d.ts.map +1 -0
  125. package/dist/request-limits.js +57 -0
  126. package/dist/request-limits.js.map +1 -0
  127. package/dist/run-plan.d.ts +32 -0
  128. package/dist/run-plan.d.ts.map +1 -0
  129. package/dist/run-plan.js +43 -0
  130. package/dist/run-plan.js.map +1 -0
  131. package/dist/run-policy.d.ts +32 -0
  132. package/dist/run-policy.d.ts.map +1 -0
  133. package/dist/run-policy.js +37 -0
  134. package/dist/run-policy.js.map +1 -0
  135. package/dist/secret-policy.d.ts +13 -0
  136. package/dist/secret-policy.d.ts.map +1 -0
  137. package/dist/secret-policy.js +69 -0
  138. package/dist/secret-policy.js.map +1 -0
  139. package/dist/settings-layering.d.ts +45 -0
  140. package/dist/settings-layering.d.ts.map +1 -0
  141. package/dist/settings-layering.js +134 -0
  142. package/dist/settings-layering.js.map +1 -0
  143. package/dist/settings.d.ts +238 -0
  144. package/dist/settings.d.ts.map +1 -0
  145. package/dist/settings.js +378 -0
  146. package/dist/settings.js.map +1 -0
  147. package/dist/skill-manifest.d.ts +18 -0
  148. package/dist/skill-manifest.d.ts.map +1 -0
  149. package/dist/skill-manifest.js +125 -0
  150. package/dist/skill-manifest.js.map +1 -0
  151. package/dist/source-text.d.ts +83 -0
  152. package/dist/source-text.d.ts.map +1 -0
  153. package/dist/source-text.js +310 -0
  154. package/dist/source-text.js.map +1 -0
  155. package/dist/suppression-ceiling.d.ts +48 -0
  156. package/dist/suppression-ceiling.d.ts.map +1 -0
  157. package/dist/suppression-ceiling.js +115 -0
  158. package/dist/suppression-ceiling.js.map +1 -0
  159. package/dist/tailwind-tokens.d.ts +16 -0
  160. package/dist/tailwind-tokens.d.ts.map +1 -0
  161. package/dist/tailwind-tokens.js +70 -0
  162. package/dist/tailwind-tokens.js.map +1 -0
  163. package/dist/text-escapes.d.ts +7 -0
  164. package/dist/text-escapes.d.ts.map +1 -0
  165. package/dist/text-escapes.js +14 -0
  166. package/dist/text-escapes.js.map +1 -0
  167. package/dist/toolchain-pins.d.ts +42 -0
  168. package/dist/toolchain-pins.d.ts.map +1 -0
  169. package/dist/toolchain-pins.js +99 -0
  170. package/dist/toolchain-pins.js.map +1 -0
  171. package/dist/url-safety.d.ts +12 -0
  172. package/dist/url-safety.d.ts.map +1 -0
  173. package/dist/url-safety.js +58 -0
  174. package/dist/url-safety.js.map +1 -0
  175. package/dist/version-policy.d.ts +99 -0
  176. package/dist/version-policy.d.ts.map +1 -0
  177. package/dist/version-policy.js +306 -0
  178. package/dist/version-policy.js.map +1 -0
  179. package/dist/vulnerability-policy.d.ts +66 -0
  180. package/dist/vulnerability-policy.d.ts.map +1 -0
  181. package/dist/vulnerability-policy.js +106 -0
  182. package/dist/vulnerability-policy.js.map +1 -0
  183. package/dist/wiring-policy.d.ts +226 -0
  184. package/dist/wiring-policy.d.ts.map +1 -0
  185. package/dist/wiring-policy.js +458 -0
  186. package/dist/wiring-policy.js.map +1 -0
  187. package/dist/wiring-violation.d.ts +6 -0
  188. package/dist/wiring-violation.d.ts.map +1 -0
  189. package/dist/wiring-violation.js +8 -0
  190. package/dist/wiring-violation.js.map +1 -0
  191. package/dist/workspace-policy.d.ts +177 -0
  192. package/dist/workspace-policy.d.ts.map +1 -0
  193. package/dist/workspace-policy.js +228 -0
  194. package/dist/workspace-policy.js.map +1 -0
  195. package/dist/yaml-blocks.d.ts +43 -0
  196. package/dist/yaml-blocks.d.ts.map +1 -0
  197. package/dist/yaml-blocks.js +87 -0
  198. package/dist/yaml-blocks.js.map +1 -0
  199. package/package.json +41 -0
@@ -0,0 +1,144 @@
1
+ // Pure commit-message logic, kept out of the `commit-history` gate in packages/cli/src/checks/history.ts
2
+ // so it can be unit- and coverage-tested without a fixture repository (that gate keeps all I/O). It enforces
3
+ // Conventional Commits headers, subject quality, the shared AI-typography ban, the AI-agent
4
+ // attribution ban (no co-author trailer, session id, or generated-by signature naming an agent), and a
5
+ // mandatory body explaining WHY for non-trivial changes (>2 files or >50 changed lines).
6
+ import { findAgentReferences } from './agent-references.js';
7
+ import { findTypographyViolations } from './banned-typography.js';
8
+ // The governing standard's own list, in its own order. `revert` is deliberately absent: git writes a
9
+ // revert commit with a `Revert "..."` subject, and the standard requires that subject be replaced by a
10
+ // conforming one by hand rather than given a type of its own.
11
+ const TYPES = [
12
+ 'build',
13
+ 'chore',
14
+ 'ci',
15
+ 'docs',
16
+ 'feat',
17
+ 'fix',
18
+ 'perf',
19
+ 'refactor',
20
+ 'test',
21
+ ];
22
+ const TYPES_LABEL = TYPES.join(', ');
23
+ // Built from TYPES rather than written out again. The two were separate literals, and they drifted:
24
+ // the alternation is what actually decided a verdict, so a type removed from the list above stayed
25
+ // accepted here. A pattern derived from the list cannot disagree with it.
26
+ const HEADER_PATTERN = new RegExp(String.raw `^(?:${TYPES.join('|')})(?:\([a-z0-9-]+\))?: .+$`);
27
+ // The standard says the subject "contains none of these words", so the match is unanchored. It was
28
+ // anchored to the first word, which accepted `fix: clear the tmp directory` - a subject the standard
29
+ // rejects. The list is the standard's own; `update` and `change` are not on it and are not added,
30
+ // because unanchored they would reject a legitimate `chore(deps): update the pinned biome version`.
31
+ const JUNK_DESCRIPTION = /\b(?:wip|tmp|temp|misc|stuff|asdf|fixup)\b/i;
32
+ // What separates the type (and optional scope) from the subject.
33
+ const HEADER_SEPARATOR = ': ';
34
+ // A numstat row reports added and deleted counts before the path.
35
+ const NUMSTAT_COLUMNS = 2;
36
+ const MAX_HEADER_LENGTH = 72;
37
+ const MIN_DESCRIPTION_LENGTH = 15;
38
+ const MAX_TRIVIAL_FILES = 2;
39
+ const MAX_TRIVIAL_LINES = 50;
40
+ const SCISSORS = '# ------------------------ >8 ------------------------';
41
+ /**
42
+ * Splits a raw commit message into header and body, dropping git comment lines and any verbose-diff
43
+ * section below the scissors marker.
44
+ * @param raw the full commit-message file contents.
45
+ * @returns the first non-blank line as `header` and the remaining prose as `body` (both may be empty).
46
+ */
47
+ export const parseMessage = (raw) => {
48
+ // Line endings are normalised before anything reads a line. `HEADER_PATTERN` ends in `.+$` with no
49
+ // `m` flag, and `.` does not cross a `\r`, so on a machine with `core.autocrlf=true` every commit was
50
+ // rejected as an invalid header - naming the header pattern rather than the one character that
51
+ // actually differed. The trailing-period check was defeated by the same `\r`.
52
+ const lines = (raw.replaceAll('\r\n', '\n').split(SCISSORS)[0] ?? '')
53
+ .split('\n')
54
+ .filter((line) => !line.startsWith('#'));
55
+ const headerIndex = lines.findIndex((line) => line.trim().length > 0);
56
+ if (headerIndex === -1) {
57
+ return { header: '', body: '' };
58
+ }
59
+ return {
60
+ header: lines[headerIndex] ?? '',
61
+ body: lines
62
+ .slice(headerIndex + 1)
63
+ .join('\n')
64
+ .trim(),
65
+ };
66
+ };
67
+ const descriptionOf = (header) => {
68
+ const marker = header.indexOf(HEADER_SEPARATOR);
69
+ return marker === -1 ? header : header.slice(marker + HEADER_SEPARATOR.length);
70
+ };
71
+ const validateHeaderFormat = (header) => HEADER_PATTERN.test(header)
72
+ ? []
73
+ : [
74
+ `invalid header "${header}": expected "<type>(<scope>): <description>", type one of ${TYPES_LABEL}`,
75
+ ];
76
+ const SUBJECT_RULES = [
77
+ (header) => header.length > MAX_HEADER_LENGTH
78
+ ? `header is ${String(header.length)} chars; keep it at most ${String(MAX_HEADER_LENGTH)}`
79
+ : undefined,
80
+ (header) => header.endsWith('.') ? 'header must not end with a period' : undefined,
81
+ (_header, description) => description.length < MIN_DESCRIPTION_LENGTH
82
+ ? `description "${description}" is too short; be specific (>= ${String(MIN_DESCRIPTION_LENGTH)} chars)`
83
+ : undefined,
84
+ (_header, description) => JUNK_DESCRIPTION.test(description)
85
+ ? `description "${description}" looks low-effort; describe the actual change`
86
+ : undefined,
87
+ ];
88
+ const validateSubjectQuality = (header) => {
89
+ const description = descriptionOf(header);
90
+ return SUBJECT_RULES.flatMap((rule) => {
91
+ const problem = rule(header, description);
92
+ return problem === undefined ? [] : [problem];
93
+ });
94
+ };
95
+ const validateBody = (body, isBodyRequired) => isBodyRequired && body.length === 0
96
+ ? [
97
+ `change touches >${String(MAX_TRIVIAL_FILES)} files or >${String(MAX_TRIVIAL_LINES)} lines; ` +
98
+ 'add a body (blank line then prose) explaining WHY',
99
+ ]
100
+ : [];
101
+ const typographyProblems = (message) => findTypographyViolations(`${message.header}\n${message.body}`).map((found) => `banned ${found.label} (line ${String(found.line)}); use ${found.replacement}`);
102
+ const agentReferenceProblems = (message) => findAgentReferences(`${message.header}\n${message.body}`).map((found) => `references an AI agent or its session (${found.label}, line ${String(found.line)}); a commit must ` +
103
+ 'not attribute the change to an agent, so remove the trailer/signature');
104
+ /**
105
+ * Validates a parsed commit message, returning a human-readable problem per rule violation. Every
106
+ * commit is held to every rule: there is no exemption list, so a git-generated merge, revert, or
107
+ * autosquash subject must be rewritten by hand to conform.
108
+ * @param message the header/body pair from {@link parseMessage}.
109
+ * @param isBodyRequired whether the change is non-trivial and therefore must carry an explanatory body.
110
+ * @returns an empty array when the message passes.
111
+ */
112
+ export const validateMessage = (message, isBodyRequired) => [
113
+ ...validateHeaderFormat(message.header),
114
+ ...validateSubjectQuality(message.header),
115
+ ...validateBody(message.body, isBodyRequired),
116
+ ...typographyProblems(message),
117
+ ...agentReferenceProblems(message),
118
+ ];
119
+ const toCount = (raw) => {
120
+ const parsed = Number(raw);
121
+ return Number.isFinite(parsed) ? parsed : 0;
122
+ };
123
+ /**
124
+ * Parses `git --numstat` output into a file count and a total changed-line count.
125
+ * @param numstat the raw stdout of a `--numstat` diff (tab-separated added/deleted/path rows).
126
+ * @returns the number of changed files and the sum of added plus deleted lines (binary rows count 0).
127
+ */
128
+ export const parseNumstat = (numstat) => {
129
+ const rows = numstat
130
+ .split('\n')
131
+ .filter((row) => row.length > 0);
132
+ const lines = rows.reduce((total, row) => {
133
+ const [added, deleted] = row.split('\t', NUMSTAT_COLUMNS);
134
+ return total + toCount(added) + toCount(deleted);
135
+ }, 0);
136
+ return { files: rows.length, lines };
137
+ };
138
+ /**
139
+ * Decides whether a diff is large enough to require an explanatory commit body.
140
+ * @param stat the file and line counts from {@link parseNumstat}.
141
+ * @returns true when the change touches more than 2 files or more than 50 lines.
142
+ */
143
+ export const isNonTrivial = (stat) => stat.files > MAX_TRIVIAL_FILES || stat.lines > MAX_TRIVIAL_LINES;
144
+ //# sourceMappingURL=commit-message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commit-message.js","sourceRoot":"","sources":["../src/commit-message.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Extract the concrete, existence-checkable source paths a config file references. Returns each unique
3
+ * path (sorted for deterministic output) with any leading `!` negation stripped; glob/regex patterns
4
+ * and non-source tokens are excluded.
5
+ */
6
+ export declare const extractLiteralSourcePaths: (content: string) => readonly string[];
7
+ /** A config path reference that no longer resolves to a real file. */
8
+ export interface ConfigReferenceViolation {
9
+ readonly path: string;
10
+ readonly reason: string;
11
+ }
12
+ /**
13
+ * Return every extracted path that does not exist, with `isExistingFile` injected so the core stays pure. An
14
+ * empty array means every source file the config carves out is still present.
15
+ */
16
+ export declare const findMissingConfigReferences: (paths: readonly string[], isExistingFile: (relativePath: string) => boolean) => readonly ConfigReferenceViolation[];
17
+ //# sourceMappingURL=config-references.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-references.d.ts","sourceRoot":"","sources":["../src/config-references.ts"],"names":[],"mappings":"AAsCA;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,MAAM,KAAG,SAAS,MAAM,EAU1E,CAAA;AAED,sEAAsE;AACtE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;GAGG;AACH,eAAO,MAAM,2BAA2B,GACtC,OAAO,SAAS,MAAM,EAAE,EACxB,gBAAgB,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,KAChD,SAAS,wBAAwB,EAQ/B,CAAA"}
@@ -0,0 +1,54 @@
1
+ // Config-reference integrity policy: the pure logic that finds LITERAL source-file paths carved out of
2
+ // (or into) a tool config - a Biome `includes`, an ESLint `ignores`, a Vitest `coverageExclude` - that
3
+ // no longer exist on disk. The `config-refs` gate in packages/cli/src/checks/references.ts reads the
4
+ // config files and probes the filesystem.
5
+ //
6
+ // This is the sibling of the doc-rot gate (document-references.ts), applied to configs instead of docs,
7
+ // and chosen to be HIGH-SIGNAL and false-positive-free. A quoted token is only checked when it is
8
+ // unambiguously a concrete project file: it starts with a known source directory (`src/`, `tests/`,
9
+ // `scripts/`, `public/`), carries a file extension, and contains NO glob or regex metacharacter. Globs
10
+ // (`src/**/*.tsx`), path regexes (`^src/lib/`), and bare tokens are deliberately ignored, because a
11
+ // config legitimately uses those and existence-checking them would make the gate lie. A leading `!`
12
+ // (Biome negation) is stripped before the path is returned.
13
+ // The directories a carve-out may name. `DEFAULT_SOURCE_ROOTS` is imported rather than restated - the
14
+ // three names were written out here as a second alternation, so a change to either list left the other
15
+ // describing a project that no longer exists. `public` is added on top because a config legitimately
16
+ // carves out a static asset, and no source root claims that directory.
17
+ const CARVE_OUT_DIRECTORIES = [...DEFAULT_SOURCE_ROOTS, 'public'];
18
+ import { DEFAULT_SOURCE_ROOTS } from './settings.js';
19
+ import { escapeForRegex } from './text-escapes.js';
20
+ // A quoted string whose content, after an optional leading `!`, starts with one of those directories and
21
+ // ends with a file extension. The three quote styles cover JSON (`"`) and JS/TS config literals
22
+ // (`'`, `` ` ``).
23
+ const CARVE_OUT_ALTERNATION = CARVE_OUT_DIRECTORIES.map((directory) => escapeForRegex(directory)).join('|');
24
+ const LITERAL_SOURCE_PATH = new RegExp(String.raw `(['"\`])(!?(?:${CARVE_OUT_ALTERNATION})/[^'"\`\n]*?\.[A-Za-z0-9]+)\1`, 'g');
25
+ // Glob and regex metacharacters. A token carrying any of these is a pattern, not a concrete path, so it
26
+ // cannot be existence-checked and is dropped.
27
+ const METACHARACTER = /[*?[\]{}()|^$\\+]/;
28
+ /**
29
+ * Extract the concrete, existence-checkable source paths a config file references. Returns each unique
30
+ * path (sorted for deterministic output) with any leading `!` negation stripped; glob/regex patterns
31
+ * and non-source tokens are excluded.
32
+ */
33
+ export const extractLiteralSourcePaths = (content) => {
34
+ const candidates = [...content.matchAll(LITERAL_SOURCE_PATH)]
35
+ // The second group is the quoted body, which the pattern always captures when it matches. The
36
+ // conversion is written out rather than guarded, because a guard here would describe a case that
37
+ // cannot occur and would report as an untested branch forever.
38
+ .map((match) => String(match[2]))
39
+ .map((token) => (token.startsWith('!') ? token.slice(1) : token))
40
+ .filter((candidate) => !METACHARACTER.test(candidate));
41
+ const paths = new Set(candidates);
42
+ return [...paths].sort((left, right) => left.localeCompare(right));
43
+ };
44
+ /**
45
+ * Return every extracted path that does not exist, with `isExistingFile` injected so the core stays pure. An
46
+ * empty array means every source file the config carves out is still present.
47
+ */
48
+ export const findMissingConfigReferences = (paths, isExistingFile) => paths
49
+ .filter((path) => !isExistingFile(path))
50
+ .map((path) => ({
51
+ path,
52
+ reason: 'carved out of a tool config but the file does not exist',
53
+ }));
54
+ //# sourceMappingURL=config-references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-references.js","sourceRoot":"","sources":["../src/config-references.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,uGAAuG;AACvG,qGAAqG;AACrG,0CAA0C;AAC1C,EAAE;AACF,wGAAwG;AACxG,kGAAkG;AAClG,oGAAoG;AACpG,uGAAuG;AACvG,oGAAoG;AACpG,oGAAoG;AACpG,4DAA4D;AAE5D,sGAAsG;AACtG,uGAAuG;AACvG,qGAAqG;AACrG,uEAAuE;AACvE,MAAM,qBAAqB,GAAsB,CAAC,GAAG,oBAAoB,EAAE,QAAQ,CAAC,CAAA;AAEpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAElD,yGAAyG;AACzG,gGAAgG;AAChG,kBAAkB;AAClB,MAAM,qBAAqB,GAAW,qBAAqB,CAAC,GAAG,CAAC,CAAC,SAAiB,EAAU,EAAE,CAC5F,cAAc,CAAC,SAAS,CAAC,CAC1B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAEX,MAAM,mBAAmB,GAAW,IAAI,MAAM,CAC5C,MAAM,CAAC,GAAG,CAAA,iBAAiB,qBAAqB,gCAAgC,EAChF,GAAG,CACJ,CAAA;AAED,wGAAwG;AACxG,8CAA8C;AAC9C,MAAM,aAAa,GAAW,mBAAmB,CAAA;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,OAAe,EAAqB,EAAE;IAC9E,MAAM,UAAU,GAAsB,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;QAC9E,8FAA8F;QAC9F,iGAAiG;QACjG,+DAA+D;SAC9D,GAAG,CAAC,CAAC,KAAsB,EAAU,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;SACzD,GAAG,CAAC,CAAC,KAAa,EAAU,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;SAChF,MAAM,CAAC,CAAC,SAAiB,EAAW,EAAE,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAA;IACzE,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAS,UAAU,CAAC,CAAA;IAC9D,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;AAC5F,CAAC,CAAA;AAQD;;;GAGG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,KAAwB,EACxB,cAAiD,EACZ,EAAE,CACvC,KAAK;KACF,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;KACxD,GAAG,CACF,CAAC,IAAY,EAA4B,EAAE,CAAC,CAAC;IAC3C,IAAI;IACJ,MAAM,EAAE,yDAAyD;CAClE,CAAC,CACH,CAAA"}
@@ -0,0 +1,40 @@
1
+ /** One finished invocation, reduced to what a classifier reads. */
2
+ export interface ContainerRun {
3
+ readonly code: number;
4
+ readonly output: string;
5
+ }
6
+ /** Which failure of the container toolchain a gate hit. */
7
+ export type DockerFailureKind = 'absent' | 'daemonDown' | 'rateLimited' | 'unreachable' | 'manifestUnknown' | 'unstartable';
8
+ /** A failure of Docker itself, worded for the gate that hit it. */
9
+ export interface DockerFailure {
10
+ readonly kind: DockerFailureKind;
11
+ readonly summary: string;
12
+ readonly remedies: readonly string[];
13
+ }
14
+ /** The exit code a shell reports for a command it cannot find. */
15
+ export declare const COMMAND_NOT_FOUND: number;
16
+ /**
17
+ * Classify a failure of `docker image inspect` or `docker pull`.
18
+ *
19
+ * Safe to read the output here, and only here: the command is docker's own and every byte it wrote is
20
+ * docker's. Acquiring the image as a step of its own is what creates that guarantee - it moves "the
21
+ * analyzer could not run" onto a different command from "the analyzer found something", so the two stop
22
+ * being one exit code read two ways.
23
+ * @param gate the gate's name, opening the sentence the summary completes.
24
+ * @param invocation the finished `docker` invocation.
25
+ * @returns the failure, or undefined when the image was acquired.
26
+ */
27
+ export declare const classifyImageFailure: (gate: string, invocation: ContainerRun) => DockerFailure | undefined;
28
+ /**
29
+ * Classify a failure of a run whose output the project may have authored.
30
+ *
31
+ * Exit code only. gitleaks prints the commit it found a secret in, hadolint prints the Dockerfile line,
32
+ * actionlint prints the workflow step - so a marker matched against that output would let a commit
33
+ * message decide whether a real finding is reported, which is the same defect one level deeper. The codes
34
+ * `docker run` reserves for itself carry no such ambiguity.
35
+ * @param gate the gate's name, opening the sentence the summary completes.
36
+ * @param invocation the finished `docker run` or shell invocation.
37
+ * @returns the failure, or undefined when the exit code is the tool's own verdict.
38
+ */
39
+ export declare const classifyContainerExit: (gate: string, invocation: ContainerRun) => DockerFailure | undefined;
40
+ //# sourceMappingURL=container-failure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-failure.d.ts","sourceRoot":"","sources":["../src/container-failure.ts"],"names":[],"mappings":"AAmBA,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,2DAA2D;AAC3D,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,YAAY,GACZ,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,aAAa,CAAA;AAEjB,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC;AAED,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,EAAE,MAAY,CAAA;AAyG5C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAC/B,MAAM,MAAM,EACZ,YAAY,YAAY,KACvB,aAAa,GAAG,SAalB,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,MAAM,EACZ,YAAY,YAAY,KACvB,aAAa,GAAG,SAOlB,CAAA"}
@@ -0,0 +1,154 @@
1
+ // Why a containerised gate failed, told apart from what its analyzer reported.
2
+ //
3
+ // `docker run` passes the container's own exit code through, so a gate that reads every non-zero exit as
4
+ // the tool's verdict reads a failure of Docker as a finding. The consequence was not cosmetic: a Docker
5
+ // Hub pull-rate limit - 100 anonymous pulls per six hours per IP, which several teams behind one CI
6
+ // egress reach - made the secret scan announce that it had found a secret in the git history. That is the
7
+ // one verdict a project cannot dismiss on its own judgement, and the worst thing this harness can say. A
8
+ // registry outage, an unreachable network, and a digest the registry no longer serves all landed the same
9
+ // way.
10
+ //
11
+ // Two classifiers rather than one, and the split is the actual repair. `classifyImageFailure` reads
12
+ // markers out of the output because it judges `docker image inspect` and `docker pull`, whose every byte
13
+ // docker wrote. `classifyContainerExit` judges a run of the analyzer, whose output carries content the
14
+ // PROJECT authored - a commit message, a Dockerfile, a workflow - so it reads the exit code and nothing
15
+ // else. Matching `no such host` against a commit message would invert the same bug rather than fix it.
16
+ //
17
+ // The rules are pure so they are exercised against captured `docker` output rather than against a live
18
+ // daemon: every branch below describes a failure that is expensive or impossible to stage on demand.
19
+ /** The exit code a shell reports for a command it cannot find. */
20
+ export const COMMAND_NOT_FOUND = 127;
21
+ // `docker run` reserves these for a failure of docker itself, distinct from whatever the container exits
22
+ // with: 125 when docker could not run the container at all, 126 when the entry point was not executable.
23
+ // No analyzer this harness runs emits either - gitleaks, hadolint and actionlint all answer with 0 and 1 -
24
+ // so they separate docker's failure from the tool's verdict before a byte of output is read.
25
+ const DOCKER_ITSELF_FAILED = 125;
26
+ const ENTRY_POINT_NOT_EXECUTABLE = 126;
27
+ const WORDING = {
28
+ absent: {
29
+ suffix: 'needs Docker, which is not installed on this machine',
30
+ remedies: ['Docker is already required for the Payload test database; install it and retry'],
31
+ },
32
+ daemonDown: {
33
+ suffix: 'needs Docker, which is installed but not running',
34
+ remedies: ['start the Docker daemon and retry; nothing about the project has been judged yet'],
35
+ },
36
+ rateLimited: {
37
+ suffix: 'could not pull its analyzer image: the container registry is rate-limiting this network',
38
+ remedies: [
39
+ 'Docker Hub caps anonymous pulls per IP, and a shared CI egress reaches that cap sooner than one machine does',
40
+ 'run `docker login` and retry, or retry once the window resets',
41
+ 'no analyzer ran, so this says nothing about the project',
42
+ ],
43
+ },
44
+ unreachable: {
45
+ suffix: 'could not reach the container registry to pull its analyzer image',
46
+ remedies: [
47
+ 'this gate is fail-closed by design; retry with network access',
48
+ 'an image already pulled is reused, so a machine that has run this gate before needs no network',
49
+ ],
50
+ },
51
+ manifestUnknown: {
52
+ suffix: 'names an analyzer image the registry does not serve',
53
+ remedies: [
54
+ 'the image is pinned by digest in ploaness, not by the project',
55
+ 'this is a ploaness packaging defect; report it',
56
+ ],
57
+ },
58
+ unstartable: {
59
+ suffix: 'could not start its analyzer container',
60
+ remedies: ['docker refused to run the image; no analyzer ran, so no finding has been made'],
61
+ },
62
+ };
63
+ const failureFor = (kind, gate) => ({
64
+ kind,
65
+ summary: `${gate} ${WORDING[kind].suffix}`,
66
+ remedies: WORDING[kind].remedies,
67
+ });
68
+ // Ordered, because the first match wins and the categories are not disjoint in principle: a rate-limited
69
+ // pull and an unreachable registry both mention the registry host. The more specific cause is listed
70
+ // first so it is the one reported.
71
+ //
72
+ // `manifestUnknown` sits BELOW `unreachable` for that same rule read the other way round. Docker frames
73
+ // every registry failure the same way - `failed to resolve reference "<ref>": <cause>` - so a bad
74
+ // digest, a refused connection and a host that does not resolve all carry that opening, and only the
75
+ // clause after the colon says which one happened. The frame is therefore the LEAST specific marker here
76
+ // rather than the most, and matching it above the transport markers would report a ploaness packaging
77
+ // defect for a team's network outage: the same misattribution this classifier exists to prevent, moved
78
+ // one category over.
79
+ const MARKERS = [
80
+ ['absent', ['enoent', 'command not found', 'executable file not found']],
81
+ [
82
+ 'daemonDown',
83
+ [
84
+ 'cannot connect to the docker daemon',
85
+ 'is the docker daemon running',
86
+ 'error during connect',
87
+ 'docker daemon is not running',
88
+ ],
89
+ ],
90
+ ['rateLimited', ['toomanyrequests', 'pull rate limit', 'too many requests']],
91
+ [
92
+ 'unreachable',
93
+ [
94
+ 'dial tcp',
95
+ 'no such host',
96
+ 'i/o timeout',
97
+ 'tls handshake',
98
+ 'connection refused',
99
+ 'network is unreachable',
100
+ 'temporary failure in name resolution',
101
+ ],
102
+ ],
103
+ [
104
+ 'manifestUnknown',
105
+ [
106
+ 'manifest unknown',
107
+ 'not found: manifest',
108
+ 'repository does not exist',
109
+ 'failed to resolve reference',
110
+ ],
111
+ ],
112
+ ];
113
+ /**
114
+ * Classify a failure of `docker image inspect` or `docker pull`.
115
+ *
116
+ * Safe to read the output here, and only here: the command is docker's own and every byte it wrote is
117
+ * docker's. Acquiring the image as a step of its own is what creates that guarantee - it moves "the
118
+ * analyzer could not run" onto a different command from "the analyzer found something", so the two stop
119
+ * being one exit code read two ways.
120
+ * @param gate the gate's name, opening the sentence the summary completes.
121
+ * @param invocation the finished `docker` invocation.
122
+ * @returns the failure, or undefined when the image was acquired.
123
+ */
124
+ export const classifyImageFailure = (gate, invocation) => {
125
+ if (invocation.code === 0) {
126
+ return undefined;
127
+ }
128
+ if (invocation.code === COMMAND_NOT_FOUND) {
129
+ return failureFor('absent', gate);
130
+ }
131
+ const haystack = invocation.output.toLowerCase();
132
+ const matched = MARKERS.find(([, markers]) => markers.some((marker) => haystack.includes(marker)));
133
+ return failureFor(matched === undefined ? 'unstartable' : matched[0], gate);
134
+ };
135
+ /**
136
+ * Classify a failure of a run whose output the project may have authored.
137
+ *
138
+ * Exit code only. gitleaks prints the commit it found a secret in, hadolint prints the Dockerfile line,
139
+ * actionlint prints the workflow step - so a marker matched against that output would let a commit
140
+ * message decide whether a real finding is reported, which is the same defect one level deeper. The codes
141
+ * `docker run` reserves for itself carry no such ambiguity.
142
+ * @param gate the gate's name, opening the sentence the summary completes.
143
+ * @param invocation the finished `docker run` or shell invocation.
144
+ * @returns the failure, or undefined when the exit code is the tool's own verdict.
145
+ */
146
+ export const classifyContainerExit = (gate, invocation) => {
147
+ if (invocation.code === COMMAND_NOT_FOUND) {
148
+ return failureFor('absent', gate);
149
+ }
150
+ return invocation.code === DOCKER_ITSELF_FAILED || invocation.code === ENTRY_POINT_NOT_EXECUTABLE
151
+ ? failureFor('unstartable', gate)
152
+ : undefined;
153
+ };
154
+ //# sourceMappingURL=container-failure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-failure.js","sourceRoot":"","sources":["../src/container-failure.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /** A directory compose would treat as a project, and the file that identifies it. */
2
+ export interface ComposeProject {
3
+ /** Repo-relative directory holding the project; empty at the repository root. */
4
+ readonly directory: string;
5
+ /** Repo-relative path of the file that identified it. */
6
+ readonly file: string;
7
+ }
8
+ /**
9
+ * Decide whether a path names a Dockerfile.
10
+ * @param file a repo-relative path.
11
+ * @returns true when the file is a Dockerfile under any of its conventional spellings.
12
+ */
13
+ export declare const isDockerfile: (file: string) => boolean;
14
+ /**
15
+ * Decide whether a path names a file compose would read on its own.
16
+ * @param file a repo-relative path.
17
+ * @returns true when the file identifies a compose project.
18
+ */
19
+ export declare const isComposeFile: (file: string) => boolean;
20
+ /**
21
+ * Every Dockerfile the repository tracks.
22
+ * @param tracked the repo-relative paths of the tracked files.
23
+ * @returns the Dockerfiles, ordered so a report reads the same on every machine.
24
+ */
25
+ export declare const dockerfilesIn: (tracked: readonly string[]) => readonly string[];
26
+ /**
27
+ * Every compose project the repository tracks, one per directory.
28
+ *
29
+ * Grouped by directory rather than listed per file, because that is the unit compose itself validates: it
30
+ * reads the base file and every override beside it as one project, and the directory is what a
31
+ * developer's own `docker compose config` would be run from.
32
+ * @param tracked the repo-relative paths of the tracked files.
33
+ * @returns one project per directory holding a compose file, in directory order.
34
+ */
35
+ export declare const composeProjectsIn: (tracked: readonly string[]) => readonly ComposeProject[];
36
+ //# sourceMappingURL=container-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-files.d.ts","sourceRoot":"","sources":["../src/container-files.ts"],"names":[],"mappings":"AASA,qFAAqF;AACrF,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAqBD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,OAG3C,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,OAAuD,CAAA;AAMpG;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,SAAS,MAAM,EAAE,KAAG,SAAS,MAAM,EAGO,CAAA;AAEjF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,SAAS,MAAM,EAAE,KAAG,SAAS,cAAc,EAcrF,CAAA"}
@@ -0,0 +1,74 @@
1
+ // Which files in a tracked tree are container definitions, and which directories hold a compose project.
2
+ //
3
+ // Discovery is stated here for both kinds at once, because stating it for only one is how the two halves
4
+ // drifted. The Dockerfile half already walked the tracked tree - a project may keep a Dockerfile in any
5
+ // directory, and a hard-coded path would silently skip the ones it did not anticipate - while the compose
6
+ // half looked at the repository root and nowhere else. A Payload project that keeps its application in a
7
+ // member directory keeps its compose file beside it, so the gate linted that member's Dockerfile and
8
+ // reported a pass having validated no compose project at all.
9
+ // Sliced rather than split, so neither helper carries a case that cannot happen. `lastIndexOf`
10
+ // returns -1 for a bare basename, which makes these the whole string and the empty string - the two
11
+ // answers a repository-root file wants - without a fallback no input could reach.
12
+ const basenameOf = (file) => file.slice(file.lastIndexOf('/') + 1);
13
+ const directoryOf = (file) => file.slice(0, Math.max(file.lastIndexOf('/'), 0));
14
+ // The names compose reads on its own, in the precedence it applies to them.
15
+ //
16
+ // An override file is deliberately absent: compose merges one into the project its base file defines, so
17
+ // a directory is validated once, through that base. Discovering an override as a project of its own would
18
+ // hand compose a fragment to validate alone and report a defect the project does not have.
19
+ const COMPOSE_BASENAMES = [
20
+ 'compose.yaml',
21
+ 'compose.yml',
22
+ 'docker-compose.yaml',
23
+ 'docker-compose.yml',
24
+ ];
25
+ /**
26
+ * Decide whether a path names a Dockerfile.
27
+ * @param file a repo-relative path.
28
+ * @returns true when the file is a Dockerfile under any of its conventional spellings.
29
+ */
30
+ export const isDockerfile = (file) => {
31
+ const name = basenameOf(file);
32
+ return name === 'Dockerfile' || name.endsWith('.Dockerfile') || name.startsWith('Dockerfile.');
33
+ };
34
+ /**
35
+ * Decide whether a path names a file compose would read on its own.
36
+ * @param file a repo-relative path.
37
+ * @returns true when the file identifies a compose project.
38
+ */
39
+ export const isComposeFile = (file) => COMPOSE_BASENAMES.includes(basenameOf(file));
40
+ // Where a name sits in compose's own precedence. Only ever asked of a file `isComposeFile` accepted,
41
+ // so the -1 `indexOf` returns for anything else is unreachable rather than meaningful.
42
+ const precedenceOf = (file) => COMPOSE_BASENAMES.indexOf(basenameOf(file));
43
+ /**
44
+ * Every Dockerfile the repository tracks.
45
+ * @param tracked the repo-relative paths of the tracked files.
46
+ * @returns the Dockerfiles, ordered so a report reads the same on every machine.
47
+ */
48
+ export const dockerfilesIn = (tracked) => tracked
49
+ .filter((file) => isDockerfile(file))
50
+ .toSorted((left, right) => left.localeCompare(right));
51
+ /**
52
+ * Every compose project the repository tracks, one per directory.
53
+ *
54
+ * Grouped by directory rather than listed per file, because that is the unit compose itself validates: it
55
+ * reads the base file and every override beside it as one project, and the directory is what a
56
+ * developer's own `docker compose config` would be run from.
57
+ * @param tracked the repo-relative paths of the tracked files.
58
+ * @returns one project per directory holding a compose file, in directory order.
59
+ */
60
+ export const composeProjectsIn = (tracked) => {
61
+ const found = tracked.filter((file) => isComposeFile(file));
62
+ return [...new Set(found.map((file) => directoryOf(file)))]
63
+ .toSorted((left, right) => left.localeCompare(right))
64
+ .flatMap((directory) =>
65
+ // The directory's files are ranked and the first is taken, rather than the names being tried in
66
+ // order against the list. Both spell compose's precedence; only this one is total, so there is no
67
+ // "no file matched" case to answer for in a directory that is here because a file matched.
68
+ found
69
+ .filter((file) => directoryOf(file) === directory)
70
+ .toSorted((left, right) => precedenceOf(left) - precedenceOf(right))
71
+ .slice(0, 1)
72
+ .map((file) => ({ directory, file })));
73
+ };
74
+ //# sourceMappingURL=container-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-files.js","sourceRoot":"","sources":["../src/container-files.ts"],"names":[],"mappings":"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"}